Redo the block options APIs.
[metze/wireshark/wip.git] / ui / qt / capture_file_properties_dialog.cpp
1 /* capture_file_properties_dialog.cpp
2  *
3  * GSoC 2013 - QtShark
4  *
5  * Wireshark - Network traffic analyzer
6  * By Gerald Combs <gerald@wireshark.org>
7  * Copyright 1998 Gerald Combs
8  *
9  * This program is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU General Public License
11  * as published by the Free Software Foundation; either version 2
12  * of the License, or (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22  */
23
24 #include "capture_file_properties_dialog.h"
25 #include <ui_capture_file_properties_dialog.h>
26
27 #include "summary.h"
28
29 #include "wsutil/str_util.h"
30 #include "ws_version_info.h"
31
32 #include "qt_ui_utils.h"
33 #include "wireshark_application.h"
34
35 #include <QPushButton>
36 #include <QScrollBar>
37 #include <QTextStream>
38
39 // To do:
40 // - Add file hashes
41 // - Add formats (HTML, plain text, YAML)?
42
43 CaptureFilePropertiesDialog::CaptureFilePropertiesDialog(QWidget &parent, CaptureFile &capture_file) :
44     WiresharkDialog(parent, capture_file),
45     ui(new Ui::CaptureFilePropertiesDialog)
46 {
47     ui->setupUi(this);
48     loadGeometry(parent.width() * 2 / 3, parent.height());
49
50     ui->detailsTextEdit->setAcceptRichText(true);
51
52     QPushButton *button = ui->buttonBox->button(QDialogButtonBox::Reset);
53     if (button) {
54         button->setText(tr("Refresh"));
55     }
56
57     button = ui->buttonBox->button(QDialogButtonBox::Apply);
58     if (button) {
59         button->setText(tr("Copy To Clipboard"));
60     }
61
62     button = ui->buttonBox->button(QDialogButtonBox::Save);
63     if (button) {
64         button->setText(tr("Save Comments"));
65     }
66
67     setWindowSubtitle(tr("Capture File Properties"));
68     QTimer::singleShot(0, this, SLOT(updateWidgets()));
69 }
70
71 /*
72  * Slots
73  */
74
75 CaptureFilePropertiesDialog::~CaptureFilePropertiesDialog()
76 {
77     delete ui;
78 }
79
80 /**/
81
82 void CaptureFilePropertiesDialog::updateWidgets()
83 {
84     QPushButton *refresh_bt = ui->buttonBox->button(QDialogButtonBox::Reset);
85     QPushButton *save_bt = ui->buttonBox->button(QDialogButtonBox::Save);
86
87     if (file_closed_ || !cap_file_.isValid()) {
88         if (refresh_bt) {
89             refresh_bt->setEnabled(false);
90         }
91         ui->commentsTextEdit->setReadOnly(true);
92         if (save_bt) {
93             save_bt->setEnabled(false);
94         }
95         WiresharkDialog::updateWidgets();
96         return;
97     }
98
99     bool enable = wtap_dump_can_write(cap_file_.capFile()->linktypes, WTAP_COMMENT_PER_SECTION);
100     save_bt->setEnabled(enable);
101     ui->commentsTextEdit->setEnabled(enable);
102
103     fillDetails();
104     ui->commentsTextEdit->setText(cf_read_shb_comment(cap_file_.capFile()));
105
106     WiresharkDialog::updateWidgets();
107 }
108
109 static const QString section_tmpl_ = "<p><strong>%1</strong></p>\n";
110 static const QString para_tmpl_ = "<p>%1</p>\n";
111
112 QString CaptureFilePropertiesDialog::summaryToHtml()
113 {
114     summary_tally summary;
115     double seconds = 0.0;
116     double disp_seconds = 0.0;
117     double marked_seconds = 0.0;
118
119     memset(&summary, 0, sizeof(summary_tally));
120
121     QString table_begin, table_end;
122     QString table_row_begin, table_ul_row_begin, table_row_end;
123     QString table_vheader_tmpl, table_hheader20_tmpl, table_hheader25_tmpl;
124     QString table_data_tmpl;
125
126     table_begin = "<p><table>\n";
127     table_end = "</table></p>\n";
128     table_row_begin = "<tr>\n";
129     table_ul_row_begin = "<tr style=\"border-bottom: 1px solid gray;\">\n";
130     table_row_end = "</tr>\n";
131     table_vheader_tmpl = "<td width=\"20%\">%1:</td>"; // <th align="left"> looked odd
132     table_hheader20_tmpl = "<td width=\"20%\"><u>%1</u></td>";
133     table_hheader25_tmpl = "<td width=\"25%\"><u>%1</u></td>";
134     table_data_tmpl = "<td>%1</td>";
135
136     if (!file_closed_) {
137         /* initial computations */
138         summary_fill_in(cap_file_.capFile(), &summary);
139 #ifdef HAVE_LIBPCAP
140         summary_fill_in_capture(cap_file_.capFile(), &global_capture_opts, &summary);
141 #endif
142     }
143
144     seconds = summary.stop_time - summary.start_time;
145     disp_seconds = summary.filtered_stop - summary.filtered_start;
146     marked_seconds = summary.marked_stop - summary.marked_start;
147
148     QString summary_str;
149     QTextStream out(&summary_str);
150     QString unknown = tr("Unknown");
151
152     // File Section
153     out << section_tmpl_.arg(tr("File"));
154     out << table_begin;
155
156     out << table_row_begin
157         << table_vheader_tmpl.arg(tr("Name"))
158         << table_data_tmpl.arg(summary.filename)
159         << table_row_end;
160
161     out << table_row_begin
162         << table_vheader_tmpl.arg(tr("Length"))
163         << table_data_tmpl.arg(file_size_to_qstring(summary.file_length))
164         << table_row_end;
165
166     QString format_str = wtap_file_type_subtype_string(summary.file_type);
167     if (summary.iscompressed) {
168         format_str.append(tr(" (gzip compressed)"));
169     }
170     out << table_row_begin
171         << table_vheader_tmpl.arg(tr("Format"))
172         << table_data_tmpl.arg(format_str)
173         << table_row_end;
174
175     QString encaps_str;
176     if (summary.file_encap_type == WTAP_ENCAP_PER_PACKET) {
177         for (guint i = 0; i < summary.packet_encap_types->len; i++)
178         {
179             encaps_str = QString(wtap_encap_string(g_array_index(summary.packet_encap_types, int, i)));
180         }
181     } else {
182         encaps_str = QString(wtap_encap_string(summary.file_encap_type));
183     }
184     out << table_row_begin
185         << table_vheader_tmpl.arg(tr("Encapsulation"))
186         << table_data_tmpl.arg(encaps_str)
187         << table_row_end;
188
189     if (summary.has_snap) {
190         out << table_row_begin
191             << table_vheader_tmpl.arg(tr("Snapshot length"))
192             << table_data_tmpl.arg(summary.snap)
193             << table_row_end;
194     }
195
196     out << table_end;
197
198     // Time Section
199     if (summary.packet_count_ts == summary.packet_count &&
200             summary.packet_count >= 1)
201     {
202         out << section_tmpl_.arg(tr("Time"));
203         out << table_begin;
204
205         // start time
206         out << table_row_begin
207             << table_vheader_tmpl.arg(tr("First packet"))
208             << table_data_tmpl.arg(time_t_to_qstring((time_t)summary.start_time))
209             << table_row_end;
210
211         // stop time
212         out << table_row_begin
213             << table_vheader_tmpl.arg(tr("Last packet"))
214             << table_data_tmpl.arg(time_t_to_qstring((time_t)summary.stop_time))
215             << table_row_end;
216
217         // elapsed seconds (capture duration)
218         if (summary.packet_count_ts >= 2)
219         {
220             /* elapsed seconds */
221             QString elapsed_str;
222             unsigned int elapsed_time = (unsigned int)summary.elapsed_time;
223             if (elapsed_time/86400)
224             {
225                 elapsed_str = QString("%1 days ").arg(elapsed_time / 86400);
226             }
227
228             elapsed_str += QString("%1:%2:%3")
229                     .arg(elapsed_time % 86400 / 3600, 2, 10, QChar('0'))
230                     .arg(elapsed_time % 3600 / 60, 2, 10, QChar('0'))
231                     .arg(elapsed_time % 60, 2, 10, QChar('0'));
232             out << table_row_begin
233                 << table_vheader_tmpl.arg(tr("Elapsed"))
234                 << table_data_tmpl.arg(elapsed_str)
235                 << table_row_end;
236         }
237
238         out << table_end;
239     }
240
241     // Capture Section
242     out << section_tmpl_.arg(tr("Capture"));
243     out << table_begin;
244
245     wtap_block_t shb_inf = wtap_file_get_shb(cap_file_.capFile()->wth);
246     char *str;
247
248     if (shb_inf != NULL) {
249       QString capture_hardware(unknown);
250       if (wtap_block_get_string_option_value(shb_inf, OPT_SHB_HARDWARE, &str) == WTAP_OPTTYPE_SUCCESS) {
251           if (str != NULL && str[0] != '\0') {
252               capture_hardware = str;
253           }
254       }
255       // capture HW
256       out << table_row_begin
257           << table_vheader_tmpl.arg(tr("Hardware"))
258           << table_data_tmpl.arg(capture_hardware)
259           << table_row_end;
260
261       QString capture_os(unknown);
262       if (wtap_block_get_string_option_value(shb_inf, OPT_SHB_OS, &str) == WTAP_OPTTYPE_SUCCESS) {
263           if (str != NULL && str[0] != '\0') {
264               capture_os = str;
265           }
266       }
267       out << table_row_begin
268           << table_vheader_tmpl.arg(tr("OS"))
269           << table_data_tmpl.arg(capture_os)
270           << table_row_end;
271
272       QString capture_app(unknown);
273       if (wtap_block_get_string_option_value(shb_inf, OPT_SHB_USERAPPL, &str) == WTAP_OPTTYPE_SUCCESS) {
274           if (str != NULL && str[0] != '\0') {
275               capture_app = str;
276           }
277       }
278       out << table_row_begin
279           << table_vheader_tmpl.arg(tr("Application"))
280           << table_data_tmpl.arg(capture_app)
281           << table_row_end;
282     }
283
284     out << table_end;
285
286     // capture interfaces info
287     if (summary.ifaces->len > 0) {
288         out << section_tmpl_.arg(tr("Interfaces"));
289         out << table_begin;
290
291         out << table_ul_row_begin
292             << table_hheader20_tmpl.arg(tr("Interface"))
293             << table_hheader20_tmpl.arg(tr("Dropped packets"))
294             << table_hheader20_tmpl.arg(tr("Capture filter"))
295             << table_hheader20_tmpl.arg(tr("Link type"))
296             << table_hheader20_tmpl.arg(tr("Packet size limit"))
297             << table_row_end;
298     }
299
300     for (guint i = 0; i < summary.ifaces->len; i++) {
301         iface_options iface;
302         iface = g_array_index(summary.ifaces, iface_options, i);
303
304         /* interface */
305         QString interface_name(unknown);
306         if (iface.descr) {
307             interface_name = iface.descr;
308         } else if (iface.name)
309         {
310             interface_name = iface.name;
311         }
312
313         /* Dropped count */
314         QString interface_drops(unknown);
315         if (iface.drops_known) {
316             interface_drops = QString("%1 (%2 %)").arg(iface.drops).arg(QString::number(
317                 /* MSVC cannot convert from unsigned __int64 to float, so first convert to signed __int64 */
318                 summary.packet_count ?(100.0 * (gint64)iface.drops)/summary.packet_count : 0.0f, 'g', 1));
319         }
320
321         /* Capture filter */
322         QString interface_cfilter(unknown);
323         if (iface.cfilter && iface.cfilter[0] != '\0') {
324             interface_cfilter = iface.cfilter;
325         } else if (iface.name) {
326             interface_cfilter = QString(tr("none"));
327         }
328
329         QString interface_snaplen = QString(tr("%1 bytes").arg(iface.snap));
330
331         out << table_row_begin
332             << table_data_tmpl.arg(interface_name)
333             << table_data_tmpl.arg(interface_drops)
334             << table_data_tmpl.arg(interface_cfilter)
335             << table_data_tmpl.arg(wtap_encap_string(iface.encap_type))
336             << table_data_tmpl.arg(interface_snaplen)
337             << table_row_end;
338
339     }
340     if (summary.ifaces->len > 0) {
341         out << table_end;
342     }
343
344     // Statistics Section
345     out << section_tmpl_.arg(tr("Statistics"));
346     out << table_begin;
347
348     out << table_ul_row_begin
349         << table_hheader25_tmpl.arg(tr("Measurement"))
350         << table_hheader25_tmpl.arg(tr("Captured"))
351         << table_hheader25_tmpl.arg(tr("Displayed"))
352         << table_hheader25_tmpl.arg(tr("Marked"))
353         << table_row_end;
354
355     // TRANSLATOR Abbreviation for "not applicable"
356     QString n_a = tr("N/A");
357     QString captured_str, displayed_str, marked_str;
358
359     // Packets
360     displayed_str = marked_str = n_a;
361     if (summary.filtered_count > 0 && summary.packet_count > 0) {
362             displayed_str = QString("%1 (%2%)")
363             .arg(summary.filtered_count)
364             .arg(100.0 * summary.filtered_count / summary.packet_count, 1, 'f', 1);
365     }
366     if (summary.packet_count > 0 && summary.marked_count > 0) {
367             marked_str = QString("%1 (%2%)")
368             .arg(summary.marked_count)
369             .arg(100.0 * summary.marked_count / summary.packet_count, 1, 'f', 1);
370     }
371
372     out << table_row_begin
373         << table_data_tmpl.arg(tr("Packets"))
374         << table_data_tmpl.arg(summary.packet_count)
375         << table_data_tmpl.arg(displayed_str)
376         << table_data_tmpl.arg(marked_str)
377         << table_row_end;
378
379     // Time between first and last
380     captured_str = displayed_str = marked_str = n_a;
381     if (seconds > 0) {
382             captured_str = QString("%1").arg(seconds, 1, 'f', 3);
383     }
384     if (disp_seconds > 0) {
385             displayed_str = QString("%1").arg(disp_seconds, 1, 'f', 3);
386     }
387     if (marked_seconds > 0) {
388             marked_str = QString("%1").arg(marked_seconds, 1, 'f', 3);
389     }
390     out << table_row_begin
391         << table_data_tmpl.arg(tr("Time span, s"))
392         << table_data_tmpl.arg(captured_str)
393         << table_data_tmpl.arg(displayed_str)
394         << table_data_tmpl.arg(marked_str)
395         << table_row_end;
396
397     // Average packets per second
398     captured_str = displayed_str = marked_str = n_a;
399     if (seconds > 0) {
400             captured_str = QString("%1").arg(summary.packet_count/seconds, 1, 'f', 1);
401     }
402     if (disp_seconds > 0) {
403             displayed_str = QString("%1").arg(summary.filtered_count/disp_seconds, 1, 'f', 1);
404     }
405     if (marked_seconds > 0) {
406             marked_str = QString("%1").arg(summary.marked_count/marked_seconds, 1, 'f', 1);
407     }
408     out << table_row_begin
409         << table_data_tmpl.arg(tr("Average pps"))
410         << table_data_tmpl.arg(captured_str)
411         << table_data_tmpl.arg(displayed_str)
412         << table_data_tmpl.arg(marked_str)
413         << table_row_end;
414
415     // Average packets per second
416     captured_str = displayed_str = marked_str = n_a;
417     if (summary.packet_count > 0) {
418             captured_str = QString("%1").arg(summary.bytes/summary.packet_count + 0.5, 1, 'f', 1);
419     }
420     if (summary.filtered_count > 0) {
421             displayed_str = QString("%1").arg(summary.filtered_bytes/summary.filtered_count + 0.5, 1, 'f', 1);
422     }
423     if (summary.marked_count > 0) {
424             marked_str = QString("%1").arg(summary.marked_bytes/summary.marked_count + 0.5, 1, 'f', 1);
425     }
426     out << table_row_begin
427         << table_data_tmpl.arg(tr("Average packet size, B"))
428         << table_data_tmpl.arg(captured_str)
429         << table_data_tmpl.arg(displayed_str)
430         << table_data_tmpl.arg(marked_str)
431         << table_row_end;
432
433     // Byte count
434     displayed_str = marked_str = "0";
435     if (summary.bytes > 0 && summary.filtered_bytes > 0) {
436         displayed_str = QString("%1 (%2%)")
437                 .arg(summary.filtered_bytes)
438                 .arg(100.0 * summary.filtered_bytes / summary.bytes, 1, 'f', 1);
439     }
440     if (summary.bytes > 0 && summary.marked_bytes > 0) {
441         marked_str = QString("%1 (%2%)")
442                 .arg(summary.marked_bytes)
443                 .arg(100.0 * summary.marked_bytes / summary.bytes, 1, 'f', 1);
444     }
445     out << table_row_begin
446         << table_data_tmpl.arg(tr("Bytes"))
447         << table_data_tmpl.arg(summary.bytes)
448         << table_data_tmpl.arg(displayed_str)
449         << table_data_tmpl.arg(marked_str)
450         << table_row_end;
451
452     // Bytes per second
453     captured_str = displayed_str = marked_str = n_a;
454     if (seconds > 0) {
455         captured_str =
456                 gchar_free_to_qstring(format_size(summary.bytes / seconds, format_size_unit_none|format_size_prefix_si));
457     }
458     if (disp_seconds > 0) {
459         displayed_str =
460                 gchar_free_to_qstring(format_size(summary.filtered_bytes / disp_seconds, format_size_unit_none|format_size_prefix_si));
461     }
462     if (marked_seconds > 0) {
463         marked_str =
464                 gchar_free_to_qstring(format_size(summary.marked_bytes / marked_seconds, format_size_unit_none|format_size_prefix_si));
465     }
466     out << table_row_begin
467         << table_data_tmpl.arg(tr("Average bytes/s"))
468         << table_data_tmpl.arg(captured_str)
469         << table_data_tmpl.arg(displayed_str)
470         << table_data_tmpl.arg(marked_str)
471         << table_row_end;
472
473     // Bits per second
474     captured_str = displayed_str = marked_str = n_a;
475     if (seconds > 0) {
476             captured_str =
477                     gchar_free_to_qstring(format_size(summary.bytes * 8 / seconds, format_size_unit_none|format_size_prefix_si));
478     }
479     if (disp_seconds > 0) {
480             displayed_str =
481                     gchar_free_to_qstring(format_size(summary.filtered_bytes * 8 / disp_seconds, format_size_unit_none|format_size_prefix_si));
482     }
483     if (marked_seconds > 0) {
484             marked_str =
485                     gchar_free_to_qstring(format_size(summary.marked_bytes * 8 / marked_seconds, format_size_unit_none|format_size_prefix_si));
486     }
487     out << table_row_begin
488         << table_data_tmpl.arg(tr("Average bits/s"))
489         << table_data_tmpl.arg(captured_str)
490         << table_data_tmpl.arg(displayed_str)
491         << table_data_tmpl.arg(marked_str)
492         << table_row_end;
493
494     out << table_end;
495
496     return summary_str;
497 }
498
499 void CaptureFilePropertiesDialog::fillDetails()
500 {
501     if (!cap_file_.isValid()) return;
502
503     ui->detailsTextEdit->clear();
504
505     QTextCursor cursor = ui->detailsTextEdit->textCursor();
506     QString summary = summaryToHtml();
507     cursor.insertHtml(summary);
508     cursor.insertBlock(); // Work around rendering oddity.
509
510     QString file_comments = cf_read_shb_comment(cap_file_.capFile());
511     if (!file_comments.isEmpty()) {
512         QString file_comments_html;
513
514         QString comment_escaped;
515 #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
516             comment_escaped = Qt::escape(file_comments);
517 #else
518             comment_escaped = file_comments.toHtmlEscaped();
519 #endif
520         file_comments_html = section_tmpl_.arg(tr("File Comment"));
521         file_comments_html += para_tmpl_.arg(comment_escaped);
522
523         cursor.insertBlock();
524         cursor.insertHtml(file_comments_html);
525     }
526
527     if (cap_file_.capFile()->packet_comment_count > 0) {
528         cursor.insertBlock();
529         cursor.insertHtml(section_tmpl_.arg(tr("Packet Comments")));
530
531         for (guint32 framenum = 1; framenum <= cap_file_.capFile()->count ; framenum++) {
532             frame_data *fdata = frame_data_sequence_find(cap_file_.capFile()->frames, framenum);
533             char *pkt_comment = cf_get_comment(cap_file_.capFile(), fdata);
534
535             if (pkt_comment) {
536                 QString frame_comment_html = tr("<p>Frame %1: ").arg(framenum);
537                 QString raw_comment = pkt_comment;
538
539 #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
540                 frame_comment_html += Qt::escape(raw_comment);
541 #else
542                 frame_comment_html += raw_comment.toHtmlEscaped();
543 #endif
544                 frame_comment_html += "</p>\n";
545                 cursor.insertBlock();
546                 cursor.insertHtml(frame_comment_html);
547             }
548         }
549     }
550     ui->detailsTextEdit->verticalScrollBar()->setValue(0);
551 }
552
553 void CaptureFilePropertiesDialog::changeEvent(QEvent* event)
554 {
555     if (0 != event)
556     {
557         switch (event->type())
558         {
559         case QEvent::LanguageChange:
560             ui->retranslateUi(this);
561             updateWidgets();
562             break;
563         default:
564             break;
565         }
566     }
567     QDialog::changeEvent(event);
568 }
569
570 void CaptureFilePropertiesDialog::on_buttonBox_helpRequested()
571 {
572     wsApp->helpTopicAction(HELP_STATS_SUMMARY_DIALOG);
573 }
574
575 void CaptureFilePropertiesDialog::on_buttonBox_accepted()
576 {
577     if (file_closed_ || !cap_file_.capFile()->filename) {
578         return;
579     }
580
581     if (wtap_dump_can_write(cap_file_.capFile()->linktypes, WTAP_COMMENT_PER_SECTION))
582     {
583         gchar *str = qstring_strdup(ui->commentsTextEdit->toPlainText());
584         cf_update_capture_comment(cap_file_.capFile(), str);
585         emit captureCommentChanged();
586         fillDetails();
587     }
588 }
589
590 void CaptureFilePropertiesDialog::on_buttonBox_clicked(QAbstractButton *button)
591 {
592     if (button == ui->buttonBox->button(QDialogButtonBox::Apply)) {
593         QClipboard *clipboard = QApplication::clipboard();
594         QString details = tr("Created by Wireshark %1\n\n").arg(get_ws_vcs_version_info());
595         details.append(ui->detailsTextEdit->toPlainText());
596         clipboard->setText(details);
597     } else if (button == ui->buttonBox->button(QDialogButtonBox::Reset)) {
598         updateWidgets();
599     }
600 }
601
602 void CaptureFilePropertiesDialog::on_buttonBox_rejected()
603 {
604     reject();
605 }
606
607 /*
608  * Editor modelines
609  *
610  * Local Variables:
611  * c-basic-offset: 4
612  * tab-width: 8
613  * indent-tabs-mode: nil
614  * End:
615  *
616  * ex: set shiftwidth=4 tabstop=8 expandtab:
617  * :indentSize=4:tabSize=8:noTabs=true:
618  */