Implement "Save" and "Save As".

svn path=/trunk/; revision=45156
This commit is contained in:
Gerald Combs 2012-09-26 19:52:53 +00:00
parent 0ac06207ce
commit 068815cc67
6 changed files with 463 additions and 35 deletions

View File

@ -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 (;;) {

View File

@ -35,6 +35,7 @@
#endif
#include <errno.h>
#include "file.h"
#include "../../epan/addr_resolv.h"
#include "../../epan/prefs.h"
#include "../../epan/filesystem.h"
@ -46,6 +47,7 @@
#include <QLineEdit>
#include <QCheckBox>
#include <QFileInfo>
#include <QMessageBox>
#include <QDebug>
@ -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<QGridLayout*>(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<QGridLayout*>(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;
}

View File

@ -29,6 +29,8 @@
#include "packet_list_record.h"
#include "cfile.h"
#include "ui/file_dialog.h"
#include <QFileDialog>
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:

View File

@ -47,7 +47,6 @@
#include <QFileDialog>
#include <QDebug>
#include <QFile>
#include <QMessageBox>
#include <QDesktopServices>
#include <QUrl>

View File

@ -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

View File

@ -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();