diff --git a/ui/gtk/capture_file_dlg.c b/ui/gtk/capture_file_dlg.c index 5243657d79..41e654cf22 100644 --- a/ui/gtk/capture_file_dlg.c +++ b/ui/gtk/capture_file_dlg.c @@ -1723,7 +1723,7 @@ file_save_as_cmd(capture_file *cf, gboolean must_support_comments, gchar *dirname; gboolean discard_comments = FALSE; - /* + /* * Loop until the user either selects a file or gives up. */ for (;;) { diff --git a/ui/qt/capture_file_dialog.cpp b/ui/qt/capture_file_dialog.cpp index 411488d78d..d72a358400 100644 --- a/ui/qt/capture_file_dialog.cpp +++ b/ui/qt/capture_file_dialog.cpp @@ -35,6 +35,7 @@ #endif #include +#include "file.h" #include "../../epan/addr_resolv.h" #include "../../epan/prefs.h" #include "../../epan/filesystem.h" @@ -46,6 +47,7 @@ #include #include #include +#include #include @@ -119,20 +121,20 @@ extern void topic_cb(gpointer *widget, int topic) { CaptureFileDialog::CaptureFileDialog(QWidget *parent, QString &display_filter) : QFileDialog(parent), display_filter_(display_filter) +#if !defined(Q_WS_WIN) + , default_ft_(-1) +#else + , file_type_(-1) +#endif { #if !defined(Q_WS_WIN) - // Add extra widgets // http://qt-project.org/faq/answer/how_can_i_add_widgets_to_my_qfiledialog_instance setOption(QFileDialog::DontUseNativeDialog, true); QGridLayout *fd_grid = qobject_cast(layout()); QHBoxLayout *h_box = new QHBoxLayout(); - fd_grid->addWidget(new QLabel(tr("Display Filter:")), fd_grid->rowCount(), 0, 1, 1); - - display_filter_edit_ = new DisplayFilterEdit(this, true); - display_filter_edit_->setText(display_filter_); - fd_grid->addWidget(display_filter_edit_, fd_grid->rowCount() - 1, 1, 1, 1); + df_row_ = fd_grid->rowCount(); fd_grid->addLayout(h_box, fd_grid->rowCount(), 1, 1, -1); @@ -146,6 +148,89 @@ CaptureFileDialog::CaptureFileDialog(QWidget *parent, QString &display_filter) : #endif // Q_WS_WIN } +#if !defined(Q_WS_WIN) +check_savability_t CaptureFileDialog::checkSaveAsWithComments(capture_file *cf, int file_type) { + QMessageBox msg_dialog; + int response; + + /* Do we have any comments? */ + if (!cf_has_comments(cf)) { + /* No. Let the save happen; no comments to delete. */ + return SAVE; + } + + /* XXX - for now, we "know" that pcap-ng is the only format for which + we support comments. We should really ask Wiretap what the + format in question supports (and handle different types of + comments, some but not all of which some file formats might + not support). */ + if (file_type == WTAP_FILE_PCAPNG) { + /* Yes - they selected pcap-ng. Let the save happen; we can + save the comments, so no need to delete them. */ + return SAVE; + } + /* No. Is pcap-ng one of the formats in which we can write this file? */ + if (wtap_dump_can_write_encaps(WTAP_FILE_PCAPNG, cf->linktypes)) { + QPushButton *default_button; + /* Yes. Offer the user a choice of "Save in a format that + supports comments", "Discard comments and save in the + format you selected", or "Cancel", meaning "don't bother + saving the file at all". */ + msg_dialog.setIcon(QMessageBox::Question); + msg_dialog.setText("This capture file contains comments."); + msg_dialog.setInformativeText("The file format you chose doesn't support comments. " + "Do you want to save the capture in a format that supports comments " + "or discard the comments and save in the format you chose?"); + msg_dialog.setStandardButtons(QMessageBox::Cancel); + // The predefined roles don't really match the tasks at hand... + msg_dialog.addButton("Discard comments and save", QMessageBox::DestructiveRole); + default_button = msg_dialog.addButton("Save in another format", QMessageBox::AcceptRole); + msg_dialog.setDefaultButton(default_button); + } else { + /* No. Offer the user a choice of "Discard comments and + save in the format you selected" or "Cancel". */ + msg_dialog.setIcon(QMessageBox::Question); + msg_dialog.setText("This capture file contains comments."); + msg_dialog.setInformativeText("No file format in which it can be saved supports comments. " + "Do you want to discard the comments and save in the format you chose?"); + msg_dialog.setStandardButtons(QMessageBox::Cancel); + msg_dialog.addButton("Discard comments and save", QMessageBox::DestructiveRole); + msg_dialog.setDefaultButton(QMessageBox::Cancel); + } + + response = msg_dialog.exec(); + + switch (response) { + + case QMessageBox::Save: + /* OK, the only other format we support is pcap-ng. Make that + the one and only format in the combo box, and return to + let the user continue with the dialog. + + XXX - removing all the formats from the combo box will clear + the compressed checkbox; get the current value and restore + it. + + XXX - we know pcap-ng can be compressed; if we ever end up + supporting saving comments in a format that *can't* be + compressed, such as NetMon format, we must check this. */ + /* XXX - need a compressed checkbox here! */ + return SAVE_IN_ANOTHER_FORMAT; + + case QMessageBox::Discard: + /* Save without the comments and, if that succeeds, delete the + comments. */ + return SAVE_WITHOUT_COMMENTS; + + case QMessageBox::Cancel: + default: + /* Just give up. */ + break; + } + return CANCELLED; +} +#endif // Q_WS_WIN + void CaptureFileDialog::addPreview(QVBoxLayout &v_box) { QGridLayout *preview_grid = new QGridLayout(); QLabel *lbl; @@ -215,8 +300,9 @@ QString CaptureFileDialog::fileType(int ft, bool extension_globs) filter = wtap_file_type_string(ft); - if (!extension_globs) + if (!extension_globs) { return filter; + } filter += " ("; @@ -267,15 +353,22 @@ QStringList CaptureFileDialog::buildFileOpenTypeList() { // Windows #ifdef Q_WS_WIN +int CaptureFileDialog::selectedFileType() { + return file_type_; +} + +int CaptureFileDialog::isCompressed() { + return compressed_; +} + int CaptureFileDialog::open(QString &file_name) { GString *fname = g_string_new(file_name.toUtf8().constData()); GString *dfilter = g_string_new(display_filter_.toUtf8().constData()); gboolean wof_status; wof_status = win32_open_file(parentWidget()->effectiveWinId(), fname, dfilter); - file_name.append(QString::fromUtf8(fname->str)); - display_filter_.clear(); - display_filter_.append(QString::fromUtf8(dfilter->str)); + file_name = fname->str; + display_filter_ = dfilter->str; g_string_free(fname, TRUE); g_string_free(dfilter, TRUE); @@ -283,15 +376,30 @@ int CaptureFileDialog::open(QString &file_name) { return (int) wof_status; } +check_savability_t CaptureFileDialog::saveAs(capture_file *cf, QString &file_name, bool must_support_comments) { + GString *fname = g_string_new(file_name.toUtf8().constData()); + gboolean wsf_status; + + wsf_status = win32_save_as_file(parentWidget()->effectiveWinId(), cf, fname, &file_type_, &compressed_, must_support_comments); + file_name = fname->str; + + g_string_free(fname, TRUE); + + if (wsf_status) { + return win32_check_save_as_with_comments(parentWidget()->effectiveWinId(), cf, file_type); + } + + return CANCELLED; +} + int CaptureFileDialog::merge(QString &file_name) { GString *fname = g_string_new(file_name.toUtf8().constData()); GString *dfilter = g_string_new(display_filter_.toUtf8().constData()); gboolean wmf_status; wmf_status = win32_merge_file(parentWidget()->effectiveWinId(), fname, dfilter, &merge_type_); - file_name.append(QString::fromUtf8(fname->str)); - display_filter_.clear(); - display_filter_.append(QString::fromUtf8(dfilter->str)); + file_name = fname->str; + display_filter_ = dfilter->str; g_string_free(fname, TRUE); g_string_free(dfilter, TRUE); @@ -308,7 +416,18 @@ int CaptureFileDialog::selectedFileType() { return type_hash_.value(selectedNameFilter(), -1); } +bool CaptureFileDialog::isCompressed() { + return compress_.isChecked(); +} + void CaptureFileDialog::addDisplayFilterEdit() { + QGridLayout *fd_grid = qobject_cast(layout()); + + fd_grid->addWidget(new QLabel(tr("Display Filter:")), df_row_, 0, 1, 1); + + display_filter_edit_ = new DisplayFilterEdit(this, true); + display_filter_edit_->setText(display_filter_); + fd_grid->addWidget(display_filter_edit_, df_row_, 1, 1, 1); } @@ -330,26 +449,37 @@ void CaptureFileDialog::addResolutionControls(QVBoxLayout &v_box) { v_box.addWidget(&external_res_); } +void CaptureFileDialog::addGzipControls(QVBoxLayout &v_box, capture_file *cf) { + compress_.setText(tr("Compress with g&zip")); + if (cf->iscompressed && wtap_dump_can_compress(default_ft_)) { + compress_.setChecked(true); + } else { + compress_.setChecked(false); + } + v_box.addWidget(&compress_); + +} + int CaptureFileDialog::open(QString &file_name) { setWindowTitle(tr("Wireshark: Open Capture File")); setNameFilters(buildFileOpenTypeList()); setFileMode(QFileDialog::ExistingFile); - file_name.clear(); - display_filter_.clear(); - + addDisplayFilterEdit(); addResolutionControls(left_v_box_); addPreview(right_v_box_); // Grow the dialog to account for the extra widgets. resize(width(), height() + left_v_box_.minimumSize().height() + display_filter_edit_->minimumSize().height()); + display_filter_.clear(); + if (!file_name.isEmpty()) { selectFile(file_name); } if (QFileDialog::exec() && selectedFiles().length() > 0) { - file_name.append(selectedFiles()[0]); + file_name = selectedFiles()[0]; display_filter_.append(display_filter_edit_->text()); gbl_resolv_flags.mac_name = mac_res_.isChecked(); @@ -363,17 +493,43 @@ int CaptureFileDialog::open(QString &file_name) { } } +check_savability_t CaptureFileDialog::saveAs(capture_file *cf, QString &file_name, bool must_support_comments) { + setWindowTitle(tr("Wireshark: Save Capture File As")); + // XXX There doesn't appear to be a way to use setNameFilters without restricting + // what the user can select. We might want to use our own combobox instead and + // let the user select anything. + setNameFilters(buildFileSaveAsTypeList(cf, must_support_comments)); + setAcceptMode(QFileDialog::AcceptSave); + setLabelText(FileType, "Save as:"); + + addGzipControls(left_v_box_, cf); + + // Grow the dialog to account for the extra widgets. + resize(width(), height() + left_v_box_.minimumSize().height()); + + if (!file_name.isEmpty()) { + selectFile(file_name); + } + + if (QFileDialog::exec() && selectedFiles().length() > 0) { + file_name = selectedFiles()[0]; + return checkSaveAsWithComments(cf, selectedFileType()); + } + return CANCELLED; +} + int CaptureFileDialog::merge(QString &file_name) { setWindowTitle(tr("Wireshark: Merge Capture File")); setNameFilters(buildFileOpenTypeList()); setFileMode(QFileDialog::ExistingFile); - file_name.clear(); - display_filter_.clear(); - + addDisplayFilterEdit(); addMergeControls(left_v_box_); addPreview(right_v_box_); + file_name.clear(); + display_filter_.clear(); + // Grow the dialog to account for the extra widgets. resize(width(), height() + right_v_box_.minimumSize().height() + display_filter_edit_->minimumSize().height()); @@ -392,7 +548,6 @@ QStringList CaptureFileDialog::buildFileSaveAsTypeList(capture_file *cf, bool mu GArray *savable_file_types; guint i; int ft; - int default_ft = -1; type_hash_.clear(); savable_file_types = wtap_get_savable_file_types(cf->cd_t, cf->linktypes); @@ -408,9 +563,9 @@ QStringList CaptureFileDialog::buildFileSaveAsTypeList(capture_file *cf, bool mu if (ft != WTAP_FILE_PCAPNG) continue; } - if (default_ft == -1) - default_ft = ft; /* first file type is the default */ - file_type = fileType(ft, false); + if (default_ft_ < 1) + default_ft_ = ft; /* first file type is the default */ + file_type = fileType(ft); filters << file_type; type_hash_[file_type] = ft; } diff --git a/ui/qt/capture_file_dialog.h b/ui/qt/capture_file_dialog.h index c2a1205b5f..94d1dbadbe 100644 --- a/ui/qt/capture_file_dialog.h +++ b/ui/qt/capture_file_dialog.h @@ -29,6 +29,8 @@ #include "packet_list_record.h" #include "cfile.h" +#include "ui/file_dialog.h" + #include class CaptureFileDialog : public QFileDialog @@ -60,10 +62,12 @@ class CaptureFileDialog : public QFileDialog Q_OBJECT public: explicit CaptureFileDialog(QWidget *parent = NULL, QString &display_filter = *new QString()); - int mergeType(); #if !defined(Q_WS_WIN) - int selectedFileType(); + static check_savability_t checkSaveAsWithComments(capture_file *cf, int file_type); #endif // Q_WS_WIN + int mergeType(); + int selectedFileType(); + bool isCompressed(); private: void addMergeControls(QVBoxLayout &v_box); @@ -77,6 +81,7 @@ private: QString &display_filter_; DisplayFilterEdit* display_filter_edit_; + int df_row_; QLabel preview_format_; QLabel preview_size_; @@ -93,15 +98,22 @@ private: #if !defined(Q_WS_WIN) void addResolutionControls(QVBoxLayout &v_box); + void addGzipControls(QVBoxLayout &v_box, capture_file *cf); QStringList buildFileSaveAsTypeList(capture_file *cf, bool must_support_comments); + int default_ft_; + QCheckBox mac_res_; QCheckBox transport_res_; QCheckBox network_res_; QCheckBox external_res_; + + QCheckBox compress_; #else // Q_WS_WIN + int file_type_; int merge_type_; + bool compressed_; #endif // Q_WS_WIN signals: @@ -110,6 +122,7 @@ public slots: int exec(); int open(QString &file_name); + check_savability_t saveAs(capture_file *cf, QString &file_name, bool must_support_comments); int merge(QString &file_name); private slots: diff --git a/ui/qt/import_text_dialog.cpp b/ui/qt/import_text_dialog.cpp index ba367fad97..e40f6502b8 100644 --- a/ui/qt/import_text_dialog.cpp +++ b/ui/qt/import_text_dialog.cpp @@ -47,7 +47,6 @@ #include #include #include -#include #include #include diff --git a/ui/qt/main_window.cpp b/ui/qt/main_window.cpp index 97389a311d..63b6e3efe8 100644 --- a/ui/qt/main_window.cpp +++ b/ui/qt/main_window.cpp @@ -470,7 +470,7 @@ void MainWindow::mergeCaptureFile() case QMessageBox::Save: /* Save the file but don't close it */ - saveCapture(cap_file_, FALSE); + saveCaptureFile(cap_file_, FALSE); break; case QMessageBox::Cancel: @@ -617,10 +617,258 @@ void MainWindow::importCaptureFile() { openCaptureFile(import_dlg.capfileName()); } -void MainWindow::saveCapture(capture_file *cf, bool close_capture) { - Q_UNUSED(cf); - Q_UNUSED(close_capture); - g_log(NULL, G_LOG_LEVEL_DEBUG, "FIX: saveCapture"); +void MainWindow::saveCaptureFile(capture_file *cf, bool stay_closed) { + QString file_name; + gboolean discard_comments; + cf_write_status_t status; + + if (cf->is_tempfile) { + /* This is a temporary capture file, so saving it means saving + it to a permanent file. Prompt the user for a location + to which to save it. Don't require that the file format + support comments - if it's a temporary capture file, it's + probably pcap-ng, which supports comments and, if it's + not pcap-ng, let the user decide what they want to do + if they've added comments. */ + saveAsCaptureFile(cf, FALSE, stay_closed); + } else { + if (cf->unsaved_changes) { + /* This is not a temporary capture file, but it has unsaved + changes, so saving it means doing a "safe save" on top + of the existing file, in the same format - no UI needed + unless the file has comments and the file's format doesn't + support them. + + If the file has comments, does the file's format support them? + If not, ask the user whether they want to discard the comments + or choose a different format. */ + switch (CaptureFileDialog::checkSaveAsWithComments(cf, cf->cd_t)) { + + case SAVE: + /* The file can be saved in the specified format as is; + just drive on and save in the format they selected. */ + discard_comments = FALSE; + break; + + case SAVE_WITHOUT_COMMENTS: + /* The file can't be saved in the specified format as is, + but it can be saved without the comments, and the user + said "OK, discard the comments", so save it in the + format they specified without the comments. */ + discard_comments = TRUE; + break; + + case SAVE_IN_ANOTHER_FORMAT: + /* There are file formats in which we can save this that + support comments, and the user said not to delete the + comments. Do a "Save As" so the user can select + one of those formats and choose a file name. */ + saveAsCaptureFile(cf, TRUE, stay_closed); + return; + + case CANCELLED: + /* The user said "forget it". Just return. */ + return; + + default: + /* Squelch warnings that discard_comments is being used + uninitialized. */ + g_assert_not_reached(); + return; + } + + /* XXX - cf->filename might get freed out from under us, because + the code path through which cf_save_packets() goes currently + closes the current file and then opens and reloads the saved file, + so make a copy and free it later. */ + file_name = cf->filename; + status = cf_save_packets(cf, file_name.toUtf8().constData(), cf->cd_t, cf->iscompressed, + discard_comments, stay_closed); + switch (status) { + + case CF_WRITE_OK: + /* The save succeeded; we're done. + If we discarded comments, redraw the packet list to reflect + any packets that no longer have comments. */ + if (discard_comments) + packet_list_queue_draw(); + break; + + case CF_WRITE_ERROR: + /* The write failed. + XXX - OK, what do we do now? Let them try a + "Save As", in case they want to try to save to a + different directory r file system? */ + break; + + case CF_WRITE_ABORTED: + /* The write was aborted; just drive on. */ + break; + } + } + /* Otherwise just do nothing. */ + } +} + +void MainWindow::saveAsCaptureFile(capture_file *cf, bool must_support_comments, bool stay_closed) { + QString file_name = ""; + int file_type; + gboolean compressed; + cf_write_status_t status; + QString file_name_lower; + QString file_suffix; + GSList *extensions_list, *extension; + gboolean add_extension; + gchar *dirname; + gboolean discard_comments = FALSE; + + if (!cf) { + return; + } + + for (;;) { + CaptureFileDialog save_as_dlg(this); + + switch (prefs.gui_fileopen_style) { + + case FO_STYLE_LAST_OPENED: + /* The user has specified that we should start out in the last directory + we looked in. If we've already opened a file, use its containing + directory, if we could determine it, as the directory, otherwise + use the "last opened" directory saved in the preferences file if + there was one. */ + /* This is now the default behaviour in file_selection_new() */ + break; + + case FO_STYLE_SPECIFIED: + /* The user has specified that we should always start out in a + specified directory; if they've specified that directory, + start out by showing the files in that dir. */ + if (prefs.gui_fileopen_dir[0] != '\0') + save_as_dlg.setDirectory(prefs.gui_fileopen_dir); + break; + } + + /* If the file has comments, does the format the user selected + support them? If not, ask the user whether they want to + discard the comments or choose a different format. */ + switch(save_as_dlg.saveAs(cf, file_name, must_support_comments)) { + + case SAVE: + /* The file can be saved in the specified format as is; + just drive on and save in the format they selected. */ + discard_comments = FALSE; + break; + + case SAVE_WITHOUT_COMMENTS: + /* The file can't be saved in the specified format as is, + but it can be saved without the comments, and the user + said "OK, discard the comments", so save it in the + format they specified without the comments. */ + discard_comments = TRUE; + break; + + case SAVE_IN_ANOTHER_FORMAT: + /* There are file formats in which we can save this that + support comments, and the user said not to delete the + comments. The combo box of file formats has had the + formats that don't support comments trimmed from it, + so run the dialog again, to let the user decide + whether to save in one of those formats or give up. */ + discard_comments = FALSE; + must_support_comments = TRUE; + continue; + + case CANCELLED: + /* The user said "forget it". Just get rid of the dialog box + and return. */ + return; + } + file_type = save_as_dlg.selectedFileType(); + compressed = save_as_dlg.isCompressed(); + + /* + * Append the default file extension if there's none given by + * the user or if they gave one that's not one of the valid + * extensions for the file type. + */ + file_name_lower = file_name.toLower(); + extensions_list = wtap_get_file_extensions_list(file_type, FALSE); + if (extensions_list != NULL) { + /* We have one or more extensions for this file type. + Start out assuming we need to add the default one. */ + add_extension = TRUE; + + /* OK, see if the file has one of those extensions. */ + for (extension = extensions_list; extension != NULL; + extension = g_slist_next(extension)) { + file_suffix += tr(".") + (char *)extension->data; + if (file_name_lower.endsWith(file_suffix)) { + /* + * The file name has one of the extensions for + * this file type. + */ + add_extension = FALSE; + break; + } + file_suffix += ".gz"; + if (file_name_lower.endsWith(file_suffix)) { + /* + * The file name has one of the extensions for + * this file type. + */ + add_extension = FALSE; + break; + } + } + } else { + /* We have no extensions for this file type. Don't add one. */ + add_extension = FALSE; + } + if (add_extension) { + if (wtap_default_file_extension(file_type) != NULL) { + file_name += tr(".") + wtap_default_file_extension(file_type); + if (compressed) { + file_name += ".gz"; + } + } + } + +//#ifndef _WIN32 +// /* If the file exists and it's user-immutable or not writable, +// ask the user whether they want to override that. */ +// if (!file_target_unwritable_ui(top_level, file_name.toUtf8().constData())) { +// /* They don't. Let them try another file name or cancel. */ +// continue; +// } +//#endif + + /* Attempt to save the file */ + status = cf_save_packets(&cfile, file_name.toUtf8().constData(), file_type, compressed, + discard_comments, stay_closed); + switch (status) { + + case CF_WRITE_OK: + /* The save succeeded; we're done. */ + /* Save the directory name for future file dialogs. */ + dirname = get_dirname(file_name.toUtf8().data()); /* Overwrites cf_name */ + set_last_open_dir(dirname); + /* If we discarded comments, redraw the packet list to reflect + any packets that no longer have comments. */ + if (discard_comments) + packet_list_queue_draw(); + return; + + case CF_WRITE_ERROR: + /* The save failed; let the user try again. */ + continue; + + case CF_WRITE_ABORTED: + /* The user aborted the save; just return. */ + return; + } + } + return; } bool MainWindow::testCaptureFileClose(capture_file *cf, bool from_quit, QString &before_what) { @@ -725,7 +973,7 @@ bool MainWindow::testCaptureFileClose(capture_file *cf, bool from_quit, QString captureStop(cf); #endif /* Save the file and close it */ - saveCapture(cf, TRUE); + saveCaptureFile(cf, TRUE); break; case QMessageBox::Discard: @@ -1253,6 +1501,16 @@ void MainWindow::on_actionFileClose_triggered() { main_ui_->mainStack->setCurrentWidget(main_welcome_); } +void MainWindow::on_actionFileSave_triggered() +{ + saveCaptureFile(cap_file_, FALSE); +} + +void MainWindow::on_actionFileSaveAs_triggered() +{ + saveAsCaptureFile(cap_file_, FALSE, TRUE); +} + // View Menu // Expand / collapse slots in proto_tree diff --git a/ui/qt/main_window.h b/ui/qt/main_window.h index 37dba2285a..c8e784edf8 100644 --- a/ui/qt/main_window.h +++ b/ui/qt/main_window.h @@ -97,7 +97,8 @@ private: void openCaptureFile(QString& cf_path = *new QString()); void mergeCaptureFile(); void importCaptureFile(); - void saveCapture(capture_file *cf, bool close_capture); + void saveCaptureFile(capture_file *cf, bool stay_closed); + void saveAsCaptureFile(capture_file *cf, bool must_support_comments, bool stay_closed); bool testCaptureFileClose(capture_file *cf, bool from_quit = false, QString& before_what = *new QString()); void captureStop(capture_file *cf); @@ -143,6 +144,8 @@ private slots: void on_actionFileMerge_triggered(); void on_actionFileImport_triggered(); void on_actionFileClose_triggered(); + void on_actionFileSave_triggered(); + void on_actionFileSaveAs_triggered(); void on_actionGoGoToPacket_triggered(); void resetPreviousFocus();