Qt: improve extension selection in Save As dialog

The default Qt behavior for extension adjustment is quite bad. When the
file type filter is changed, the extension always becomes "gz" because
"pcap.gz" happens to be the first extension in the list. It also did not
check that the last suffix is actually a valid extension (e.g.
"capture.2018.01" became "capture.2018.gz").

Improvements:
- Respect the "compression" checkbox when adjusting the filename.
- Replace the extension only if it is a known one, append otherwise.
- Use a better default extension (from "wtap_default_file_extension").

Affects only macOS and Linux since Windows has its own native dialog.
See also https://bugreports.qt.io/browse/QTBUG-67993

Bug: 14600
Change-Id: I8cd0788f2abac0c6d7e29490b1ebb381f5a926d0
Reviewed-on: https://code.wireshark.org/review/27186
Reviewed-by: Peter Wu <peter@lekensteyn.nl>
Petri-Dish: Peter Wu <peter@lekensteyn.nl>
Tested-by: Petri Dish Buildbot
Reviewed-by: Anders Broman <a.broman58@gmail.com>
This commit is contained in:
Peter Wu 2018-04-28 13:15:36 +02:00 committed by Anders Broman
parent 6e4caf3d90
commit 552ef8b1f4
2 changed files with 88 additions and 25 deletions

View File

@ -303,7 +303,7 @@ int CaptureFileDialog::mergeType() {
return merge_type_;
}
#else // not Q_OS_WINDOWS
#else // ! Q_OS_WIN
// Not Windows
// We use the Qt dialogs here
QString CaptureFileDialog::fileExtensionType(int et, bool extension_globs)
@ -342,18 +342,14 @@ QString CaptureFileDialog::fileExtensionType(int et, bool extension_globs)
.arg(all_wildcards.join(" "));
}
QString CaptureFileDialog::fileType(int ft, bool extension_globs)
// Returns " (...)", containing the suffix list suitable for setNameFilters.
// All extensions ("pcap", "pcap.gz", etc.) are also returned in "suffixes".
QString CaptureFileDialog::fileType(int ft, QStringList &suffixes)
{
QString filter;
GSList *extensions_list;
filter = wtap_file_type_subtype_string(ft);
if (!extension_globs) {
return filter;
}
filter += " (";
filter = " (";
extensions_list = wtap_get_file_extensions_list(ft, TRUE);
if (extensions_list == NULL) {
@ -364,20 +360,22 @@ QString CaptureFileDialog::fileType(int ft, bool extension_globs)
compressed file extensions. */
filter += ALL_FILES_WILDCARD;
} else {
GSList *extension;
// HACK: at least for Qt 5.10 and before, if the first extension is
// empty ("."), it will prevent the default (broken) extension
// replacement from being applied in the non-native Save file dialog.
filter += '.';
/* Construct the list of patterns. */
for (extension = extensions_list; extension != NULL;
for (GSList *extension = extensions_list; extension != NULL;
extension = g_slist_next(extension)) {
if (!filter.endsWith('('))
filter += ' ';
filter += "*.";
filter += (char *)extension->data;
QString suffix((char *)extension->data);
filter += " *." + suffix;;
suffixes << suffix;
}
wtap_free_extensions_list(extensions_list);
}
filter += ')';
return filter;
/* XXX - does QStringList's destructor destroy the strings in the list? */
}
QStringList CaptureFileDialog::buildFileOpenTypeList() {
@ -434,6 +432,68 @@ QStringList CaptureFileDialog::buildFileOpenTypeList() {
return filters;
}
// Replaces or appends an extension based on the current file filter.
void CaptureFileDialog::fixFilenameExtension()
{
QFileInfo fi(selectedFiles()[0]);
QString filename = fi.fileName();
if (fi.isDir() || filename.isEmpty()) {
// no file selected, or a directory was selected. Ignore.
return;
}
QString old_suffix;
QString new_suffix(wtap_default_file_extension(selectedFileType()));
QStringList valid_extensions = type_suffixes_.value(selectedNameFilter());
// Find suffixes such as "pcap" or "pcap.gz" if any
if (!fi.suffix().isEmpty()) {
QStringList current_suffixes(fi.suffix());
int pos = filename.lastIndexOf('.', -2 - current_suffixes.at(0).size());
if (pos > 0) {
current_suffixes.prepend(filename.right(filename.size() - (pos + 1)));
}
// If the current suffix is valid for the current file type, try to
// preserve it. Otherwise use the default file extension (if available).
foreach (const QString &current_suffix, current_suffixes) {
if (valid_extensions.contains(current_suffix)) {
old_suffix = current_suffix;
new_suffix = current_suffix;
break;
}
}
if (old_suffix.isEmpty()) {
foreach (const QString &current_suffix, current_suffixes) {
foreach (const QStringList &suffixes, type_suffixes_.values()) {
if (suffixes.contains(current_suffix)) {
old_suffix = current_suffix;
break;
}
}
if (!old_suffix.isEmpty()) {
break;
}
}
}
}
// Fixup the new suffix based on compression availability.
if (!isCompressed() && new_suffix.endsWith(".gz")) {
new_suffix.chop(3);
} else if (isCompressed() && valid_extensions.contains(new_suffix + ".gz")) {
new_suffix += ".gz";
}
if (!new_suffix.isEmpty() && old_suffix != new_suffix) {
filename.chop(old_suffix.size());
if (old_suffix.isEmpty()) {
filename += '.';
}
filename += new_suffix;
selectFile(filename);
}
}
void CaptureFileDialog::addPreview(QVBoxLayout &v_box) {
QGridLayout *preview_grid = new QGridLayout();
QLabel *lbl;
@ -516,6 +576,7 @@ void CaptureFileDialog::addGzipControls(QVBoxLayout &v_box) {
compress_.setChecked(false);
}
v_box.addWidget(&compress_, 0, Qt::AlignTop);
connect(&compress_, &QCheckBox::stateChanged, this, &CaptureFileDialog::fixFilenameExtension);
}
@ -587,6 +648,7 @@ check_savability_t CaptureFileDialog::saveAs(QString &file_name, bool must_suppo
if (!file_name.isEmpty()) {
selectFile(file_name);
}
connect(this, &QFileDialog::filterSelected, this, &CaptureFileDialog::fixFilenameExtension);
if (QFileDialog::exec() && selectedFiles().length() > 0) {
file_name = selectedFiles()[0];
@ -622,6 +684,7 @@ check_savability_t CaptureFileDialog::exportSelectedPackets(QString &file_name,
if (!file_name.isEmpty()) {
selectFile(file_name);
}
connect(this, &QFileDialog::filterSelected, this, &CaptureFileDialog::fixFilenameExtension);
if (QFileDialog::exec() && selectedFiles().length() > 0) {
file_name = selectedFiles()[0];
@ -663,6 +726,7 @@ QStringList CaptureFileDialog::buildFileSaveAsTypeList(bool must_support_all_com
guint i;
type_hash_.clear();
type_suffixes_.clear();
/* What types of comments do we have to support? */
if (must_support_all_comments)
@ -676,8 +740,6 @@ QStringList CaptureFileDialog::buildFileSaveAsTypeList(bool must_support_all_com
required_comment_types);
if (savable_file_types_subtypes != NULL) {
QString file_type;
QString hash_file_type;
int ft;
/* OK, we have at least one file type we can save this file as.
(If we didn't, we shouldn't have gotten here in the first
@ -686,10 +748,9 @@ QStringList CaptureFileDialog::buildFileSaveAsTypeList(bool must_support_all_com
ft = g_array_index(savable_file_types_subtypes, int, i);
if (default_ft_ < 1)
default_ft_ = ft; /* first file type is the default */
file_type = fileType(ft);
hash_file_type = fileType(ft, false);
filters << file_type;
type_hash_[hash_file_type] = ft;
QString type_name(wtap_file_type_subtype_string(ft));
filters << type_name + fileType(ft, type_suffixes_[type_name]);
type_hash_[type_name] = ft;
}
g_array_free(savable_file_types_subtypes, TRUE);
}
@ -840,7 +901,7 @@ void CaptureFileDialog::on_buttonBox_helpRequested()
if (help_topic_ != TOPIC_ACTION_NONE) wsApp->helpTopicAction(help_topic_);
}
#endif // Q_OS_WINDOWS
#endif // ! Q_OS_WIN
/*
* Editor modelines

View File

@ -80,7 +80,7 @@ private:
void addDisplayFilterEdit();
void addPreview(QVBoxLayout &v_box);
QString fileExtensionType(int et, bool extension_globs = true);
QString fileType(int ft, bool extension_globs = true);
QString fileType(int ft, QStringList &suffixes);
QStringList buildFileOpenTypeList(void);
QVBoxLayout left_v_box_;
@ -99,7 +99,8 @@ private:
QRadioButton merge_append_;
QComboBox format_type_;
QHash<QString, int>type_hash_;
QHash<QString, int> type_hash_;
QHash<QString, QStringList> type_suffixes_;
void addGzipControls(QVBoxLayout &v_box);
void addRangeControls(QVBoxLayout &v_box, packet_range_t *range);
@ -133,6 +134,7 @@ public slots:
private slots:
#if !defined(Q_OS_WIN)
void fixFilenameExtension();
void preview(const QString & path);
void on_buttonBox_helpRequested();
#endif // Q_OS_WIN