1 /* capture_file_dialog.cpp
3 * Wireshark - Network traffic analyzer
4 * By Gerald Combs <gerald@wireshark.org>
5 * Copyright 1998 Gerald Combs
7 * SPDX-License-Identifier: GPL-2.0-or-later
14 #include <wiretap/wtap.h>
16 #include "packet_range_group_box.h"
17 #include "capture_file_dialog.h"
21 #include "wsutil/filesystem.h"
22 #include "wsutil/nstime.h"
23 #include "wsutil/str_util.h"
24 #include "wsutil/utf8_entities.h"
26 #include "ui/all_files_wildcard.h"
30 #include <QGridLayout>
31 #include <QHBoxLayout>
34 #include <QSortFilterProxyModel>
35 #include <QSpacerItem>
36 #include <QVBoxLayout>
38 #include <QPushButton>
39 #include <QMessageBox>
41 #include <ui/qt/utils/qt_ui_utils.h>
42 #include <main_application.h>
44 static const double WIDTH_SCALE_FACTOR
= 1.4;
45 static const double HEIGHT_SCALE_FACTOR
= 1.4;
47 CaptureFileDialog::CaptureFileDialog(QWidget
*parent
, capture_file
*cf
) :
48 WiresharkFileDialog(parent
),
50 display_filter_edit_(NULL
),
53 help_topic_(TOPIC_ACTION_NONE
)
55 setDirectory(mainApp
->openDialogInitialDir());
58 // https://wiki.qt.io/Qt_project_org_faq#How_can_I_add_widgets_to_my_QFileDialog_instance.3F
59 setOption(QFileDialog::DontUseNativeDialog
, true);
60 setOption(QFileDialog::HideNameFilterDetails
, true);
61 QGridLayout
*fd_grid
= qobject_cast
<QGridLayout
*>(layout());
62 QHBoxLayout
*h_box
= new QHBoxLayout();
64 last_row_
= fd_grid
->rowCount();
66 fd_grid
->addItem(new QSpacerItem(1, 1), last_row_
, 0);
67 fd_grid
->addLayout(h_box
, last_row_
, 0, 1, 2);
70 // Left and right boxes for controls and preview
71 h_box
->addLayout(&left_v_box_
);
72 h_box
->addLayout(&right_v_box_
);
75 check_savability_t
CaptureFileDialog::checkSaveAsWithComments(QWidget
*parent
, capture_file
*cf
, int file_type
) {
76 uint32_t comment_types
;
77 bool all_comment_types_supported
= true;
79 /* What types of comments do we have? */
80 comment_types
= cf_comment_types(cf
);
82 /* Does the file's format support all the comments we have? */
83 if (comment_types
& WTAP_COMMENT_PER_SECTION
) {
84 if (wtap_file_type_subtype_supports_option(file_type
,
86 OPT_COMMENT
) == OPTION_NOT_SUPPORTED
)
87 all_comment_types_supported
= false;
89 if (comment_types
& WTAP_COMMENT_PER_INTERFACE
) {
90 if (wtap_file_type_subtype_supports_option(file_type
,
91 WTAP_BLOCK_IF_ID_AND_INFO
,
92 OPT_COMMENT
) == OPTION_NOT_SUPPORTED
)
93 all_comment_types_supported
= false;
95 if (comment_types
& WTAP_COMMENT_PER_PACKET
) {
96 if (wtap_file_type_subtype_supports_option(file_type
,
98 OPT_COMMENT
) == OPTION_NOT_SUPPORTED
)
99 all_comment_types_supported
= false;
101 if (all_comment_types_supported
) {
102 /* Yes. Let the save happen; we can save all the comments, so
103 there's no need to delete them. */
107 QMessageBox
msg_dialog(parent
);
108 QPushButton
*save_button
;
109 QPushButton
*discard_button
;
111 msg_dialog
.setIcon(QMessageBox::Question
);
112 msg_dialog
.setText(tr("This capture file contains comments."));
113 msg_dialog
.setStandardButtons(QMessageBox::Cancel
);
115 /* No. Are there formats in which we can write this file that
116 supports all the comments in this file? */
117 if (wtap_dump_can_write(cf
->linktypes
, comment_types
)) {
118 /* Yes. Offer the user a choice of "Save in a format that
119 supports comments", "Discard comments and save in the
120 format you selected", or "Cancel", meaning "don't bother
121 saving the file at all". */
122 msg_dialog
.setInformativeText(tr("The file format you chose doesn't support comments. "
123 "Do you want to save the capture in a format that supports comments "
124 "or discard the comments and save in the format you chose?"));
125 // The predefined roles don't really match the tasks at hand...
126 discard_button
= msg_dialog
.addButton(tr("Discard comments and save"), QMessageBox::DestructiveRole
);
127 save_button
= msg_dialog
.addButton(tr("Save in another format"), QMessageBox::AcceptRole
);
128 msg_dialog
.setDefaultButton(save_button
);
130 /* No. Offer the user a choice of "Discard comments and
131 save in the format you selected" or "Cancel". */
132 msg_dialog
.setInformativeText(tr("No file format in which it can be saved supports comments. "
133 "Do you want to discard the comments and save in the format you chose?"));
135 discard_button
= msg_dialog
.addButton(tr("Discard comments and save"), QMessageBox::DestructiveRole
);
136 msg_dialog
.setDefaultButton(QMessageBox::Cancel
);
139 #if defined(Q_OS_MAC)
141 * In macOS, the "default button" is not necessarily the button that
142 * has the input focus; Enter/Return activates the default button, and
143 * the spacebar activates the button that has the input focus, and
144 * they might be different buttons.
146 * In a "do you want to save" dialog, for example, the "save" button
147 * is the default button, and the "don't save" button has the input
148 * focus, so you can press Enter/Return to save or space not to save
149 * (or Escape to dismiss the dialog).
151 * In Qt terms, this means "no auto-default", as auto-default makes the
152 * button with the input focus the default button, so that Enter/Return
155 QList
<QAbstractButton
*> buttons
= msg_dialog
.buttons();
156 for (int i
= 0; i
< buttons
.size(); ++i
) {
157 QPushButton
*button
= static_cast<QPushButton
*>(buttons
.at(i
));
158 button
->setAutoDefault(false);
162 * It also means that the "don't save" button should be the one
163 * initially given the focus.
165 discard_button
->setFocus();
169 /* According to the Qt doc:
170 * when using QMessageBox with custom buttons, exec() function returns an opaque value.
172 * Therefore we should use clickedButton() to determine which button was clicked. */
174 if (msg_dialog
.clickedButton() == save_button
) {
175 /* OK, the only other format we support is pcapng. Make that
176 the one and only format in the combo box, and return to
177 let the user continue with the dialog.
179 XXX - removing all the formats from the combo box will clear
180 the compressed checkbox; get the current value and restore
183 XXX - we know pcapng can be compressed; if we ever end up
184 supporting saving comments in a format that *can't* be
185 compressed, such as NetMon format, we must check this. */
186 /* XXX - need a compressed checkbox here! */
187 return SAVE_IN_ANOTHER_FORMAT
;
189 } else if (msg_dialog
.clickedButton() == discard_button
) {
190 /* Save without the comments and, if that succeeds, delete the
192 return SAVE_WITHOUT_COMMENTS
;
200 void CaptureFileDialog::accept()
203 // If this is a dialog for writing files, we want to ensure that
204 // the filename has a valid extension before performing file
205 // existence checks and before closing the dialog.
206 // This isn't necessary for dialogs for reading files; the name
207 // has to exactly match the name of the file you want to open,
208 // and doesn't need to be, and shouldn't be, modified.
210 // XXX also useful for Windows, but that uses a different dialog...
212 if (acceptMode() == QFileDialog::AcceptSave
) {
213 // HACK: ensure that the filename field does not have the focus,
214 // otherwise selectFile will not change the filename.
216 fixFilenameExtension();
218 WiresharkFileDialog::accept();
222 // You have to use open, merge, saveAs, or exportPackets. We should
223 // probably just make each type a subclass.
224 int CaptureFileDialog::exec() {
225 return QDialog::Rejected
;
229 QString
CaptureFileDialog::fileExtensionType(int et
, bool extension_globs
)
231 QString extension_type_name
;
232 QStringList all_wildcards
;
233 QStringList no_compression_suffix_wildcards
;
234 GSList
*extensions_list
;
237 extension_type_name
= wtap_get_file_extension_type_name(et
);
239 if (!extension_globs
) {
240 return extension_type_name
;
243 extensions_list
= wtap_get_file_extension_type_extensions(et
);
245 // Get the list of compression-type extensions.
246 GSList
*compression_type_extensions
= wtap_get_all_compression_type_extensions_list();
248 /* Construct the list of patterns. */
249 for (extension
= extensions_list
; extension
!= NULL
;
250 extension
= g_slist_next(extension
)) {
251 QString bare_wc
= QStringLiteral("*.%1").arg((char *)extension
->data
);
252 all_wildcards
<< bare_wc
;
254 // Does this end with a compression suffix?
255 bool ends_with_compression_suffix
= false;
256 for (GSList
*compression_type_extension
= compression_type_extensions
;
257 compression_type_extension
!= NULL
;
258 compression_type_extension
= g_slist_next(compression_type_extension
)) {
259 QString suffix
= QStringLiteral(".") + (char *)compression_type_extension
->data
;
260 if (bare_wc
.endsWith(suffix
)) {
261 ends_with_compression_suffix
= true;
266 // If it doesn't, add it to the list of wildcards-without-
267 // compression-suffixes.
268 if (!ends_with_compression_suffix
)
269 no_compression_suffix_wildcards
<< bare_wc
;
271 g_slist_free(compression_type_extensions
);
272 wtap_free_extensions_list(extensions_list
);
274 // We set HideNameFilterDetails so that "All Files" and "All Capture
275 // Files" don't show a wildcard list. We want to show the associated
276 // wildcards for individual file types so we add them twice.
277 return QStringLiteral("%1 (%2) (%3)")
278 .arg(extension_type_name
)
279 .arg(no_compression_suffix_wildcards
.join(" "))
280 .arg(all_wildcards
.join(" "));
283 // Returns " (...)", containing the suffix list suitable for setNameFilters.
284 // All extensions ("pcap", "pcap.gz", etc.) are also returned in "suffixes".
285 QString
CaptureFileDialog::fileType(int ft
, QStringList
&suffixes
)
288 GSList
*extensions_list
;
292 extensions_list
= wtap_get_file_extensions_list(ft
, true);
293 if (extensions_list
== NULL
) {
294 /* This file type doesn't have any particular extension
295 conventionally used for it, so we'll just use a
296 wildcard that matches all file names - even those
297 with no extension, so we don't need to worry about
298 compressed file extensions. */
299 filter
+= ALL_FILES_WILDCARD
;
301 // HACK: at least for Qt 5.10 and before, if the first extension is
302 // empty ("."), it will prevent the default (broken) extension
303 // replacement from being applied in the non-native Save file dialog.
306 /* Construct the list of patterns. */
307 for (GSList
*extension
= extensions_list
; extension
!= NULL
;
308 extension
= g_slist_next(extension
)) {
309 QString
suffix((char *)extension
->data
);
310 filter
+= " *." + suffix
;
313 wtap_free_extensions_list(extensions_list
);
319 QStringList
CaptureFileDialog::buildFileOpenTypeList() {
322 GSList
*extensions_list
;
327 * Microsoft's UI guidelines say, of the file filters in open and
330 * For meta-filters, remove the file extension list to eliminate
331 * clutter. Examples: "All files," "All pictures," "All music,"
334 * On both Windows XP and Windows 7, Wordpad doesn't do that, but
337 * XXX - on Windows, does Qt do that here? For "All Capture Files",
338 * the filter will be a bit long, so it *really* shouldn't be shown.
339 * What about other platforms?
341 filters
<< tr("All Files (" ALL_FILES_WILDCARD
")");
344 * Add an "All Capture Files" entry, with all the capture file
345 * extensions we know about.
347 filter
= tr("All Capture Files");
350 * Construct its list of patterns.
352 extensions_list
= wtap_get_all_capture_file_extensions_list();
354 for (extension
= extensions_list
; extension
!= NULL
;
355 extension
= g_slist_next(extension
)) {
358 filter
+= (char *)extension
->data
;
361 wtap_free_extensions_list(extensions_list
);
365 /* Include all the file types Wireshark supports. */
366 for (et
= 0; et
< wtap_get_num_file_type_extensions(); et
++) {
367 filters
<< fileExtensionType(et
);
373 // Replaces or appends an extension based on the current file filter
374 // and compression setting.
375 // Used in dialogs that select a file to write.
376 void CaptureFileDialog::fixFilenameExtension()
378 QFileInfo
fi(selectedFiles()[0]);
379 QString filename
= fi
.fileName();
380 if (fi
.isDir() || filename
.isEmpty()) {
381 // no file selected, or a directory was selected. Ignore.
386 QString
new_suffix(wtap_default_file_extension(selectedFileType()));
387 QStringList valid_extensions
= type_suffixes_
.value(selectedNameFilter());
388 // Find suffixes such as "pcap" or "pcap.gz" if any
389 if (!fi
.suffix().isEmpty()) {
390 QStringList
current_suffixes(fi
.suffix());
391 int pos
= static_cast<int>(filename
.lastIndexOf('.', -2 - current_suffixes
.at(0).size()));
393 current_suffixes
.prepend(filename
.right(filename
.size() - (pos
+ 1)));
396 // If the current suffix is valid for the current file type, try to
397 // preserve it. Otherwise use the default file extension (if available).
398 foreach (const QString
¤t_suffix
, current_suffixes
) {
399 if (valid_extensions
.contains(current_suffix
)) {
400 old_suffix
= current_suffix
;
401 new_suffix
= current_suffix
;
405 if (old_suffix
.isEmpty()) {
406 foreach (const QString
¤t_suffix
, current_suffixes
) {
407 foreach (const QStringList
&suffixes
, type_suffixes_
.values()) {
408 if (suffixes
.contains(current_suffix
)) {
409 old_suffix
= current_suffix
;
413 if (!old_suffix
.isEmpty()) {
420 // Fixup the new suffix based on whether we're compressing or not.
421 // Strip off any compression suffix
422 GSList
*compression_type_extensions
= wtap_get_all_compression_type_extensions_list();
423 for (GSList
*compression_type_extension
= compression_type_extensions
;
424 compression_type_extension
!= NULL
;
425 compression_type_extension
= g_slist_next(compression_type_extension
)) {
426 QString suffix
= QStringLiteral(".") + (char *)compression_type_extension
->data
;
427 if (new_suffix
.endsWith(suffix
)) {
429 // It ends with this compression suffix; chop it off.
431 new_suffix
.chop(suffix
.size());
435 g_slist_free(compression_type_extensions
);
436 if (compressionType() != WTAP_UNCOMPRESSED
) {
437 // Compressing; append the appropriate compression suffix.
438 QString compressed_file_extension
= QStringLiteral(".") + wtap_compression_type_extension(compressionType());
439 if (valid_extensions
.contains(new_suffix
+ compressed_file_extension
)) {
440 new_suffix
+= compressed_file_extension
;
444 if (!new_suffix
.isEmpty() && old_suffix
!= new_suffix
) {
445 filename
.chop(old_suffix
.size());
446 if (old_suffix
.isEmpty()) {
449 filename
+= new_suffix
;
450 selectFile(filename
);
454 void CaptureFileDialog::addPreview(QVBoxLayout
&v_box
) {
455 QGridLayout
*preview_grid
= new QGridLayout();
458 preview_labels_
.clear();
459 v_box
.addLayout(preview_grid
);
461 preview_grid
->setColumnStretch(0, 0);
462 preview_grid
->setColumnStretch(1, 10);
464 lbl
= new QLabel(tr("Format:"));
465 preview_grid
->addWidget(lbl
, 0, 0);
466 preview_grid
->addWidget(&preview_format_
, 0, 1);
467 preview_labels_
<< lbl
<< &preview_format_
;
469 lbl
= new QLabel(tr("Size:"));
470 preview_grid
->addWidget(lbl
, 1, 0);
471 preview_grid
->addWidget(&preview_size_
, 1, 1);
472 preview_labels_
<< lbl
<< &preview_size_
;
474 lbl
= new QLabel(tr("Start / elapsed:"));
475 preview_grid
->addWidget(lbl
, 3, 0);
476 preview_grid
->addWidget(&preview_first_elapsed_
, 3, 1);
477 preview_labels_
<< lbl
<< &preview_first_elapsed_
;
479 connect(this, &CaptureFileDialog::currentChanged
, this, &CaptureFileDialog::preview
);
484 void CaptureFileDialog::addMergeControls(QVBoxLayout
&v_box
) {
486 merge_prepend_
.setText(tr("Prepend packets"));
487 merge_prepend_
.setToolTip(tr("Insert packets from the selected file before the current file. Packet timestamps will be ignored."));
488 v_box
.addWidget(&merge_prepend_
, 0, Qt::AlignTop
);
490 merge_chrono_
.setText(tr("Merge chronologically"));
491 merge_chrono_
.setToolTip(tr("Insert packets in chronological order."));
492 merge_chrono_
.setChecked(true);
493 v_box
.addWidget(&merge_chrono_
, 0, Qt::AlignTop
);
495 merge_append_
.setText(tr("Append packets"));
496 merge_append_
.setToolTip(tr("Insert packets from the selected file after the current file. Packet timestamps will be ignored."));
497 v_box
.addWidget(&merge_append_
, 0, Qt::AlignTop
);
500 int CaptureFileDialog::selectedFileType() {
501 return type_hash_
.value(selectedNameFilter(), WTAP_FILE_TYPE_SUBTYPE_UNKNOWN
);
504 wtap_compression_type
CaptureFileDialog::compressionType() {
505 return compress_group_box_
.compressionType();
508 void CaptureFileDialog::addDisplayFilterEdit(QString
&display_filter
) {
509 QGridLayout
*fd_grid
= qobject_cast
<QGridLayout
*>(layout());
511 fd_grid
->addWidget(new QLabel(tr("Read filter:")), last_row_
, 0);
513 display_filter_edit_
= new DisplayFilterEdit(this, ReadFilterToApply
);
514 display_filter_edit_
->setText(display_filter
);
515 fd_grid
->addWidget(display_filter_edit_
, last_row_
, 1);
519 void CaptureFileDialog::addFormatTypeSelector(QVBoxLayout
&v_box
) {
521 /* Put Auto, as well as pcap and pcapng (which are the first two entries in
522 open_routines), at the top of the file type list. */
523 format_type_
.addItem(tr("Automatically detect file type"));
524 for (i
= 0; i
< 2; i
+= 1) {
525 format_type_
.addItem(open_routines
[i
].name
);
527 /* Generate a sorted list of the remaining file types. */
528 QStringList routine_names
;
529 for ( /* keep using i */ ; open_routines
[i
].name
!= NULL
; i
+= 1) {
530 routine_names
+= QString(open_routines
[i
].name
);
532 routine_names
.sort(Qt::CaseInsensitive
);
533 for (i
= 0; i
< routine_names
.size(); i
+= 1) {
534 format_type_
.addItem(routine_names
.at(i
));
537 v_box
.addWidget(&format_type_
, 0, Qt::AlignTop
);
540 void CaptureFileDialog::addGzipControls(QVBoxLayout
&v_box
) {
541 if (wtap_dump_can_compress(default_ft_
)) {
542 compress_group_box_
.setCompressionType(cap_file_
->compression_type
);
544 v_box
.addWidget(&compress_group_box_
, 0, Qt::AlignTop
);
545 connect(&compress_group_box_
, &CompressionGroupBox::stateChanged
, this, &CaptureFileDialog::fixFilenameExtension
);
549 void CaptureFileDialog::addRangeControls(QVBoxLayout
&v_box
, packet_range_t
*range
, QString selRange
) {
550 packet_range_group_box_
.initRange(range
, selRange
);
551 v_box
.addWidget(&packet_range_group_box_
, 0, Qt::AlignTop
);
554 QDialogButtonBox
*CaptureFileDialog::addHelpButton(topic_action_e help_topic
)
556 // This doesn't appear to be documented anywhere but it seems pretty obvious
558 QDialogButtonBox
*button_box
= findChild
<QDialogButtonBox
*>();
560 help_topic_
= help_topic
;
563 button_box
->addButton(QDialogButtonBox::Help
);
564 connect(button_box
, &QDialogButtonBox::helpRequested
, this, &CaptureFileDialog::on_buttonBox_helpRequested
);
569 int CaptureFileDialog::open(QString
&file_name
, unsigned int &type
, QString
&display_filter
) {
570 setWindowTitle(mainApp
->windowTitleString(tr("Open Capture File")));
571 QStringList
open_type_filters(buildFileOpenTypeList());
572 setNameFilters(open_type_filters
);
573 selectNameFilter(open_type_filters
.at(1));
574 setFileMode(QFileDialog::ExistingFile
);
576 addFormatTypeSelector(left_v_box_
);
577 addDisplayFilterEdit(display_filter
);
578 addPreview(right_v_box_
);
579 addHelpButton(HELP_OPEN_DIALOG
);
581 // Grow the dialog to account for the extra widgets.
582 resize(width() * WIDTH_SCALE_FACTOR
, height() * HEIGHT_SCALE_FACTOR
+ left_v_box_
.minimumSize().height() + display_filter_edit_
->minimumSize().height());
584 display_filter
.clear();
586 if (!file_name
.isEmpty()) {
587 selectFile(file_name
);
590 if (WiresharkFileDialog::exec() && selectedFiles().length() > 0) {
591 file_name
= selectedNativePath();
592 type
= open_info_name_to_type(qPrintable(format_type_
.currentText()));
593 display_filter
.append(display_filter_edit_
->text());
595 return QDialog::Accepted
;
597 return QDialog::Rejected
;
601 check_savability_t
CaptureFileDialog::saveAs(QString
&file_name
, bool must_support_all_comments
) {
602 setWindowTitle(mainApp
->windowTitleString(tr("Save Capture File As")));
603 // XXX There doesn't appear to be a way to use setNameFilters without restricting
604 // what the user can select. We might want to use our own combobox instead and
605 // let the user select anything.
606 setNameFilters(buildFileSaveAsTypeList(must_support_all_comments
));
607 setAcceptMode(QFileDialog::AcceptSave
);
608 setLabelText(FileType
, tr("Save as:"));
610 addGzipControls(left_v_box_
);
611 addHelpButton(HELP_SAVE_DIALOG
);
613 // Grow the dialog to account for the extra widgets.
614 resize(width() * WIDTH_SCALE_FACTOR
, height() * HEIGHT_SCALE_FACTOR
+ left_v_box_
.minimumSize().height());
616 if (!file_name
.isEmpty()) {
617 selectFile(file_name
);
619 connect(this, &QFileDialog::filterSelected
, this, &CaptureFileDialog::fixFilenameExtension
);
621 if (WiresharkFileDialog::exec() && selectedFiles().length() > 0) {
624 file_name
= selectedNativePath();
625 file_type
= selectedFileType();
626 /* Is the file type bogus? */
627 if (file_type
== WTAP_FILE_TYPE_SUBTYPE_UNKNOWN
) {
628 /* This "should not happen". */
629 QMessageBox msg_dialog
;
631 msg_dialog
.setIcon(QMessageBox::Critical
);
632 msg_dialog
.setText(tr("Unknown file type returned by save as dialog."));
633 msg_dialog
.setInformativeText(tr("Please report this as a Wireshark issue at https://gitlab.com/wireshark/wireshark/-/issues."));
637 return checkSaveAsWithComments(this, cap_file_
, file_type
);
642 check_savability_t
CaptureFileDialog::exportSelectedPackets(QString
&file_name
, packet_range_t
*range
, QString selRange
) {
643 QDialogButtonBox
*button_box
;
645 setWindowTitle(mainApp
->windowTitleString(tr("Export Specified Packets")));
646 // XXX See comment in ::saveAs regarding setNameFilters
647 setNameFilters(buildFileSaveAsTypeList(false));
648 setAcceptMode(QFileDialog::AcceptSave
);
649 setLabelText(FileType
, tr("Export as:"));
651 addRangeControls(left_v_box_
, range
, selRange
);
652 addGzipControls(right_v_box_
);
653 button_box
= addHelpButton(HELP_EXPORT_FILE_DIALOG
);
656 save_bt_
= button_box
->button(QDialogButtonBox::Save
);
658 connect(&packet_range_group_box_
, &PacketRangeGroupBox::validityChanged
,
659 save_bt_
, &QPushButton::setEnabled
);
663 // Grow the dialog to account for the extra widgets.
664 resize(width() * WIDTH_SCALE_FACTOR
, height() * HEIGHT_SCALE_FACTOR
+ (packet_range_group_box_
.height() * 2 / 3));
666 if (!file_name
.isEmpty()) {
667 selectFile(file_name
);
669 connect(this, &QFileDialog::filterSelected
, this, &CaptureFileDialog::fixFilenameExtension
);
671 if (WiresharkFileDialog::exec() && selectedFiles().length() > 0) {
674 file_name
= selectedNativePath();
675 file_type
= selectedFileType();
676 /* Is the file type bogus? */
677 if (file_type
== WTAP_FILE_TYPE_SUBTYPE_UNKNOWN
) {
678 /* This "should not happen". */
679 QMessageBox msg_dialog
;
681 msg_dialog
.setIcon(QMessageBox::Critical
);
682 msg_dialog
.setText(tr("Unknown file type returned by save as dialog."));
683 msg_dialog
.setInformativeText(tr("Please report this as a Wireshark issue at https://gitlab.com/wireshark/wireshark/-/issues."));
687 return checkSaveAsWithComments(this, cap_file_
, file_type
);
692 int CaptureFileDialog::merge(QString
&file_name
, QString
&display_filter
) {
693 setWindowTitle(mainApp
->windowTitleString(tr("Merge Capture File")));
694 setNameFilters(buildFileOpenTypeList());
695 setFileMode(QFileDialog::ExistingFile
);
697 addDisplayFilterEdit(display_filter
);
698 addMergeControls(left_v_box_
);
699 addPreview(right_v_box_
);
700 addHelpButton(HELP_MERGE_DIALOG
);
703 display_filter
.clear();
705 // Grow the dialog to account for the extra widgets.
706 resize(width() * WIDTH_SCALE_FACTOR
, height() * HEIGHT_SCALE_FACTOR
+ right_v_box_
.minimumSize().height() + display_filter_edit_
->minimumSize().height());
708 if (WiresharkFileDialog::exec() && selectedFiles().length() > 0) {
709 file_name
.append(selectedNativePath());
710 display_filter
.append(display_filter_edit_
->text());
712 return QDialog::Accepted
;
714 return QDialog::Rejected
;
718 QStringList
CaptureFileDialog::buildFileSaveAsTypeList(bool must_support_all_comments
) {
720 uint32_t required_comment_types
;
721 GArray
*savable_file_types_subtypes
;
725 type_suffixes_
.clear();
727 /* What types of comments do we have to support? */
728 if (must_support_all_comments
)
729 required_comment_types
= cf_comment_types(cap_file_
); /* all the ones the file has */
731 required_comment_types
= 0; /* none of them */
733 /* What types of file can we save this file as? */
734 savable_file_types_subtypes
= wtap_get_savable_file_types_subtypes_for_file(cap_file_
->cd_t
,
735 cap_file_
->linktypes
,
736 required_comment_types
,
737 FT_SORT_BY_DESCRIPTION
);
739 if (savable_file_types_subtypes
!= NULL
) {
741 /* OK, we have at least one file type we can save this file as.
742 (If we didn't, we shouldn't have gotten here in the first
743 place.) Add them all to the combo box. */
744 for (i
= 0; i
< savable_file_types_subtypes
->len
; i
++) {
745 ft
= g_array_index(savable_file_types_subtypes
, int, i
);
747 default_ft_
= ft
; /* first file type is the default */
748 QString
type_name(wtap_file_type_subtype_description(ft
));
749 QString
filter(type_name
+ fileType(ft
, type_suffixes_
[type_name
]));
750 /* Before Qt 6.8, selectedNameFilter() returns the current filter
751 text; i.e., as we set HideNameFilterDetails it does not include
752 the parenthetical extension list.
753 As of 6.8, hidden details are included.
754 https://bugreports.qt.io/browse/QTBUG-127924
755 One simple approach is to just add both to the hash.
757 type_hash_
[filter
] = ft
;
758 type_hash_
[type_name
] = ft
;
761 g_array_free(savable_file_types_subtypes
, true);
767 int CaptureFileDialog::mergeType() {
768 if (merge_prepend_
.isChecked())
770 else if (merge_append_
.isChecked())
780 /* do a preview run on the currently selected capture file */
781 void CaptureFileDialog::preview(const QString
& path
)
786 ws_file_preview_stats stats
;
787 ws_file_preview_stats_status status
;
790 unsigned int elapsed_time
;
792 foreach (QLabel
*lbl
, preview_labels_
) {
793 lbl
->setEnabled(false);
796 preview_format_
.setText(tr(UTF8_EM_DASH
));
797 preview_size_
.setText(tr(UTF8_EM_DASH
));
798 preview_first_elapsed_
.setText(tr(UTF8_EM_DASH
));
800 if (path
.length() < 1) {
804 if (test_for_directory(path
.toUtf8().data()) == EISDIR
) {
805 preview_format_
.setText(tr("directory"));
809 wth
= wtap_open_offline(path
.toUtf8().data(), WTAP_TYPE_AUTO
, &err
, &err_info
, true);
811 if (err
== WTAP_ERR_FILE_UNKNOWN_FORMAT
) {
812 preview_format_
.setText(tr("unknown file format"));
814 preview_format_
.setText(tr("error opening file"));
820 foreach (QLabel
*lbl
, preview_labels_
) {
821 lbl
->setEnabled(true);
825 preview_format_
.setText(QString::fromUtf8(wtap_file_type_subtype_description(wtap_file_type_subtype(wth
))));
828 int64_t filesize
= wtap_file_size(wth
, &err
);
829 // Finder and Windows Explorer use IEC. What do the various Linux file managers use?
830 QString
size_str(gchar_free_to_qstring(format_size(filesize
, FORMAT_SIZE_UNIT_BYTES
, FORMAT_SIZE_PREFIX_IEC
)));
832 status
= get_stats_for_preview(wth
, &stats
, &err
, &err_info
);
834 if (status
== PREVIEW_READ_ERROR
) {
835 // XXX - give error details?
837 preview_size_
.setText(tr("%1, error after %Ln data record(s)", "", stats
.records
)
844 if (status
== PREVIEW_TIMED_OUT
) {
845 preview_size_
.setText(tr("%1, timed out at %Ln data record(s)", "", stats
.data_records
)
848 preview_size_
.setText(tr("%1, %Ln data record(s)", "", stats
.data_records
)
852 // First packet + elapsed time
853 QString first_elapsed
;
854 if (stats
.have_times
) {
856 // We saw at least one record with a time stamp, so we can give
857 // a start time (if we have a mix of records with and without
858 // time stamps, and there were records without time stamps
859 // before the first one with a time stamp, this may be inaccurate).
861 ti_time
= (long)stats
.start_time
;
862 ti_tm
= localtime(&ti_time
);
865 first_elapsed
= QStringLiteral("%1-%2-%3 %4:%5:%6")
866 .arg(ti_tm
->tm_year
+ 1900, 4, 10, QChar('0'))
867 .arg(ti_tm
->tm_mon
+ 1, 2, 10, QChar('0'))
868 .arg(ti_tm
->tm_mday
, 2, 10, QChar('0'))
869 .arg(ti_tm
->tm_hour
, 2, 10, QChar('0'))
870 .arg(ti_tm
->tm_min
, 2, 10, QChar('0'))
871 .arg(ti_tm
->tm_sec
, 2, 10, QChar('0'));
874 first_elapsed
= tr("unknown");
878 first_elapsed
+= " / ";
879 if (status
== PREVIEW_SUCCEEDED
&& stats
.have_times
) {
881 // We didn't time out, so we looked at all packets, and we got
882 // at least one packet with a time stamp, so we can calculate
883 // an elapsed time from the time stamp of the last packet with
884 // with a time stamp (if we have a mix of records with and without
885 // time stamps, and there were records without time stamps after
886 // the last one with a time stamp, this may be inaccurate).
888 elapsed_time
= (unsigned int)(stats
.stop_time
-stats
.start_time
);
889 if (elapsed_time
/86400) {
890 first_elapsed
+= QStringLiteral("%1 days ").arg(elapsed_time
/86400, 2, 10, QChar('0'));
891 elapsed_time
= elapsed_time
% 86400;
893 first_elapsed
+= QStringLiteral("%2:%3:%4")
894 .arg(elapsed_time
%86400/3600, 2, 10, QChar('0'))
895 .arg(elapsed_time
%3600/60, 2, 10, QChar('0'))
896 .arg(elapsed_time
%60, 2, 10, QChar('0'));
898 first_elapsed
+= tr("unknown");
900 preview_first_elapsed_
.setText(first_elapsed
);
905 void CaptureFileDialog::on_buttonBox_helpRequested()
907 if (help_topic_
!= TOPIC_ACTION_NONE
) mainApp
->helpTopicAction(help_topic_
);