From abcb7ec8750a263d0307bc41d86b798e5ae02966 Mon Sep 17 00:00:00 2001 From: Gerald Combs Date: Wed, 29 Nov 2017 17:25:53 -0800 Subject: [PATCH] Convert the file set dialog treewidget to a treeview+model. Add a FilesetEntryModel and use it in FileSetDialog. This should be faster than using a QTreeWidget. Move dialog updates and date calculations out of the "add file" loop. Bug: 11280 Bug: 14242 Change-Id: I702cef4fe91e739695fe805dc5e496bf3db411f1 Reviewed-on: https://code.wireshark.org/review/24708 Reviewed-by: Gerald Combs --- fileset.c | 13 +- fileset.h | 26 +++- ui/gtk/fileset_dlg.c | 16 +-- ui/qt/CMakeLists.txt | 2 + ui/qt/Makefile.am | 2 + ui/qt/file_set_dialog.cpp | 176 +++++++++++--------------- ui/qt/file_set_dialog.h | 26 ++-- ui/qt/file_set_dialog.ui | 26 +--- ui/qt/models/fileset_entry_model.cpp | 177 +++++++++++++++++++++++++++ ui/qt/models/fileset_entry_model.h | 64 ++++++++++ 10 files changed, 360 insertions(+), 168 deletions(-) create mode 100644 ui/qt/models/fileset_entry_model.cpp create mode 100644 ui/qt/models/fileset_entry_model.h diff --git a/fileset.c b/fileset.c index f9c2afad27..88d6996682 100644 --- a/fileset.c +++ b/fileset.c @@ -27,14 +27,16 @@ #include "fileset.h" - - typedef struct _fileset { GList *entries; char *dirname; } fileset; -/* this is the fileset's global data */ +/* + * This is the fileset's global data. + * + * XXX This should probably be per-main-window instead of global. + */ static fileset set = { NULL, NULL}; /* @@ -258,13 +260,14 @@ void fileset_update_dlg(void *window) { GList *le; - - /* add all entries to the dialog */ + /* Add all entries to the dialog. */ + fileset_dlg_begin_add_file(window); le = g_list_first(set.entries); while(le) { fileset_dlg_add_file((fileset_entry *)le->data, window); le = g_list_next(le); } + fileset_dlg_end_add_file(window); } diff --git a/fileset.h b/fileset.h index 7b1103255d..776df31952 100644 --- a/fileset.h +++ b/fileset.h @@ -38,11 +38,31 @@ extern const char *fileset_get_dirname(void); extern fileset_entry *fileset_get_next(void); extern fileset_entry *fileset_get_previous(void); - - -/* this file is a part of the current file set */ +/** + * Add an entry to our dialog / window. Called by fileset_update_dlg. + * Must be implemented in the UI. + * + * @param entry The new fileset entry. + * @param window Window / dialog reference provided by the UI code. + */ extern void fileset_dlg_add_file(fileset_entry *entry, void *window); +/** + * Notify our dialog / window that we're about to add files. Called by fileset_update_dlg. + * Must be implemented in the UI. + * + * @param window Window / dialog reference provided by the UI code. + */ +extern void fileset_dlg_begin_add_file(void *window); + +/** + * Notify our dialog / window that we're done adding files. Called by fileset_update_dlg. + * Must be implemented in the UI. + * + * @param window Window / dialog reference provided by the UI code. + */ +extern void fileset_dlg_end_add_file(void *window); + extern void fileset_update_dlg(void *window); extern void fileset_update_file(const char *path); diff --git a/ui/gtk/fileset_dlg.c b/ui/gtk/fileset_dlg.c index 540da82fdd..aa2945edfc 100644 --- a/ui/gtk/fileset_dlg.c +++ b/ui/gtk/fileset_dlg.c @@ -5,19 +5,7 @@ * By Gerald Combs * Copyright 1998 Gerald Combs * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * SPDX-License-Identifier: GPL-2.0+ */ #include "config.h" @@ -249,6 +237,8 @@ fileset_dlg_add_file(fileset_entry *entry, void *window _U_) { g_free(size); } +void fileset_dlg_begin_add_file(void *window _U_) { } // Qt only +void fileset_dlg_end_add_file(void *window _U_) { } // Qt only /* init the fileset table */ static void diff --git a/ui/qt/CMakeLists.txt b/ui/qt/CMakeLists.txt index 19496e154a..7be83ddbe4 100644 --- a/ui/qt/CMakeLists.txt +++ b/ui/qt/CMakeLists.txt @@ -64,6 +64,7 @@ set(WIRESHARK_MODEL_HEADERS models/cache_proxy_model.h models/decode_as_delegate.h models/decode_as_model.h + models/fileset_entry_model.h models/html_text_delegate.h models/interface_sort_filter_model.h models/interface_tree_cache_model.h @@ -270,6 +271,7 @@ set(WIRESHARK_MODEL_SRCS models/cache_proxy_model.cpp models/decode_as_delegate.cpp models/decode_as_model.cpp + models/fileset_entry_model.cpp models/html_text_delegate.cpp models/interface_sort_filter_model.cpp models/interface_tree_cache_model.cpp diff --git a/ui/qt/Makefile.am b/ui/qt/Makefile.am index beba9cc8ae..04b014bafe 100644 --- a/ui/qt/Makefile.am +++ b/ui/qt/Makefile.am @@ -193,6 +193,7 @@ MOC_MODELS_HDRS = \ models/cache_proxy_model.h \ models/decode_as_delegate.h \ models/decode_as_model.h \ + models/fileset_entry_model.h \ models/html_text_delegate.h \ models/interface_sort_filter_model.h \ models/interface_tree_cache_model.h \ @@ -512,6 +513,7 @@ WIRESHARK_QT_MODELS_SRCS = \ models/cache_proxy_model.cpp \ models/decode_as_delegate.cpp \ models/decode_as_model.cpp \ + models/fileset_entry_model.cpp \ models/html_text_delegate.cpp \ models/interface_sort_filter_model.cpp \ models/interface_tree_cache_model.cpp \ diff --git a/ui/qt/file_set_dialog.cpp b/ui/qt/file_set_dialog.cpp index 808163a1b0..042e24d843 100644 --- a/ui/qt/file_set_dialog.cpp +++ b/ui/qt/file_set_dialog.cpp @@ -4,19 +4,7 @@ * By Gerald Combs * Copyright 1998 Gerald Combs * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * SPDX-License-Identifier: GPL-2.0+ */ #include "config.h" @@ -28,12 +16,11 @@ #include "ui/help_url.h" -#include - #include #include "file_set_dialog.h" #include +#include "models/fileset_entry_model.h" #include "wireshark_application.h" #include @@ -44,7 +31,17 @@ #include #include -/* this file is a part of the current file set, add it to the dialog */ +// To do: +// - We might want to rename this to FilesetDialog / fileset_dialog.{cpp,h}. + +void +fileset_dlg_begin_add_file(void *window) { + FileSetDialog *fs_dlg = static_cast(window); + + if (fs_dlg) fs_dlg->beginAddFile(); +} + +/* This file is a part of the current file set. Add it to our model. */ void fileset_dlg_add_file(fileset_entry *entry, void *window) { FileSetDialog *fs_dlg = static_cast(window); @@ -52,147 +49,112 @@ fileset_dlg_add_file(fileset_entry *entry, void *window) { if (fs_dlg) fs_dlg->addFile(entry); } +void +fileset_dlg_end_add_file(void *window) { + FileSetDialog *fs_dlg = static_cast(window); + + if (fs_dlg) fs_dlg->endAddFile(); +} + FileSetDialog::FileSetDialog(QWidget *parent) : GeometryStateDialog(parent), fs_ui_(new Ui::FileSetDialog), + fileset_entry_model_(new FilesetEntryModel(this)), close_button_(NULL) { fs_ui_->setupUi(this); loadGeometry (); - fs_ui_->fileSetTree->headerItem(); + fs_ui_->fileSetTree->setModel(fileset_entry_model_); + + fs_ui_->fileSetTree->setFocus(); close_button_ = fs_ui_->buttonBox->button(QDialogButtonBox::Close); + + connect(fs_ui_->fileSetTree->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), + this, SLOT(selectionChanged(QItemSelection,QItemSelection))); + + beginAddFile(); addFile(); + endAddFile(); } FileSetDialog::~FileSetDialog() { + fileset_entry_model_->clear(); delete fs_ui_; } /* a new capture file was opened, browse the dir and look for files matching the given file set */ void FileSetDialog::fileOpened(const capture_file *cf) { if (!cf) return; - fs_ui_->fileSetTree->clear(); + fileset_entry_model_->clear(); fileset_add_dir(cf->filename, this); } /* the capture file was closed */ void FileSetDialog::fileClosed() { - fileset_delete(); - fs_ui_->fileSetTree->clear(); + fileset_entry_model_->clear(); } -#include void FileSetDialog::addFile(fileset_entry *entry) { - QString created; - QString modified; - QString dir_name; - QString elided_dir_name; - QTreeWidgetItem *entry_item; - gchar *size_str; + if (!entry) return; - if (!entry) { - setWindowTitle(wsApp->windowTitleString(tr("No files in Set"))); - fs_ui_->directoryLabel->setText(tr("No capture loaded")); - fs_ui_->directoryLabel->setEnabled(false); - return; + if (entry->current) { + cur_idx_ = fileset_entry_model_->entryCount(); + } + fileset_entry_model_->appendEntry(entry); +} + +void FileSetDialog::beginAddFile() +{ + cur_idx_ = -1; + setWindowTitle(wsApp->windowTitleString(tr("No files in Set"))); + fs_ui_->directoryLabel->setText(tr("No capture loaded")); + fs_ui_->directoryLabel->setEnabled(false); +} + +void FileSetDialog::endAddFile() +{ + if (fileset_entry_model_->entryCount() > 0) { + setWindowTitle(wsApp->windowTitleString(tr("%Ln File(s) in Set", "", + fileset_entry_model_->entryCount()))); } - created = nameToDate(entry->name); - if(created.length() < 1) { - /* if this file doesn't follow the file set pattern, */ - /* use the creation time of that file if available */ - /* http://en.wikipedia.org/wiki/ISO_8601 */ - /* - * macOS provides 0 if the file system doesn't support the - * creation time; FreeBSD provides -1. - * - * If this OS doesn't provide the creation time with stat(), - * it will be 0. - */ - if (entry->ctime > 0) - created = QDateTime::fromTime_t(uint(entry->ctime)).toLocalTime().toString("yyyy-MM-dd HH:mm:ss"); - else - created = "Not available"; - } - - modified = QDateTime::fromTime_t(uint(entry->mtime)).toLocalTime().toString("yyyy-MM-dd HH:mm:ss"); - - size_str = format_size(entry->size, format_size_unit_bytes|format_size_prefix_si); - - entry_item = new QTreeWidgetItem(fs_ui_->fileSetTree); - entry_item->setToolTip(0, QString(tr("Open this capture file"))); - entry_item->setData(0, Qt::UserRole, VariantPointer::asQVariant(entry)); - - entry_item->setText(0, entry->name); - entry_item->setText(1, created); - entry_item->setText(2, modified); - entry_item->setText(3, size_str); - g_free(size_str); - // Not perfect but better than nothing. - entry_item->setTextAlignment(3, Qt::AlignRight); - - setWindowTitle(wsApp->windowTitleString(tr("%Ln File(s) in Set", "", - fs_ui_->fileSetTree->topLevelItemCount()))); - - dir_name = fileset_get_dirname(); + QString dir_name = fileset_get_dirname(); fs_ui_->directoryLabel->setText(dir_name); fs_ui_->directoryLabel->setUrl(QUrl::fromLocalFile(dir_name).toString()); fs_ui_->directoryLabel->setEnabled(true); - if(entry->current) { - fs_ui_->fileSetTree->setCurrentItem(entry_item); + if(cur_idx_ >= 0) { + fs_ui_->fileSetTree->setCurrentIndex(fileset_entry_model_->index(cur_idx_, 0)); + } + + for (int col = 0; col < 4; col++) { + fs_ui_->fileSetTree->resizeColumnToContents(col); } if (close_button_) close_button_->setEnabled(true); - - fs_ui_->fileSetTree->addTopLevelItem(entry_item); - for (int i = 0; i < fs_ui_->fileSetTree->columnCount(); i++) - fs_ui_->fileSetTree->resizeColumnToContents(i); - fs_ui_->fileSetTree->setFocus(); } -QString FileSetDialog::nameToDate(const char *name) { - QString dn; - - if (!fileset_filename_match_pattern(name)) - return NULL; - - dn = name; - dn.remove(QRegExp(".*_")); - dn.truncate(14); - dn.insert(4, '-'); - dn.insert(7, '-'); - dn.insert(10, ' '); - dn.insert(13, ':'); - dn.insert(16, ':'); - return dn; -} - -void FileSetDialog::on_buttonBox_helpRequested() +void FileSetDialog::selectionChanged(const QItemSelection &selected, const QItemSelection &) { - wsApp->helpTopicAction(HELP_FILESET_DIALOG); -} - -void FileSetDialog::on_fileSetTree_currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *) -{ - fileset_entry *entry; - - if (!current) - return; - - entry = VariantPointer::asPtr(current->data(0, Qt::UserRole)); + const fileset_entry *entry = fileset_entry_model_->getRowEntry(selected.first().top()); if (!entry || entry->current) return; QString new_cf_path = entry->fullname; - if (new_cf_path.length() > 0) + if (new_cf_path.length() > 0) { emit fileSetOpenCaptureFile(new_cf_path); + } +} + +void FileSetDialog::on_buttonBox_helpRequested() +{ + wsApp->helpTopicAction(HELP_FILESET_DIALOG); } /* diff --git a/ui/qt/file_set_dialog.h b/ui/qt/file_set_dialog.h index 7d8ec5f95a..a619f9154e 100644 --- a/ui/qt/file_set_dialog.h +++ b/ui/qt/file_set_dialog.h @@ -4,19 +4,7 @@ * By Gerald Combs * Copyright 1998 Gerald Combs * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * SPDX-License-Identifier: GPL-2.0+ */ #ifndef FILE_SET_DIALOG_H @@ -31,12 +19,16 @@ #include "geometry_state_dialog.h" +#include + class QTreeWidgetItem; namespace Ui { class FileSetDialog; } +class FilesetEntryModel; + class FileSetDialog : public GeometryStateDialog { Q_OBJECT @@ -48,19 +40,21 @@ public: void fileOpened(const capture_file *cf); void fileClosed(); void addFile(fileset_entry *entry = NULL); + void beginAddFile(); + void endAddFile(); signals: void fileSetOpenCaptureFile(QString); private slots: + void selectionChanged(const QItemSelection &selected, const QItemSelection &); void on_buttonBox_helpRequested(); - void on_fileSetTree_currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *previous); private: - QString nameToDate(const char *name); - Ui::FileSetDialog *fs_ui_; + FilesetEntryModel *fileset_entry_model_; QPushButton *close_button_; + int cur_idx_; }; #endif // FILE_SET_DIALOG_H diff --git a/ui/qt/file_set_dialog.ui b/ui/qt/file_set_dialog.ui index 901e7ac4d0..3c61433227 100644 --- a/ui/qt/file_set_dialog.ui +++ b/ui/qt/file_set_dialog.ui @@ -63,7 +63,7 @@ - + 0 @@ -85,29 +85,6 @@ false - - 4 - - - - Filename - - - - - Created - - - - - Modified - - - - - Size - - @@ -121,6 +98,7 @@
widgets/elided_label.h
+ buttonBox diff --git a/ui/qt/models/fileset_entry_model.cpp b/ui/qt/models/fileset_entry_model.cpp new file mode 100644 index 0000000000..c613a5cc35 --- /dev/null +++ b/ui/qt/models/fileset_entry_model.cpp @@ -0,0 +1,177 @@ +/* fileset_entry_model.cpp + * + * Wireshark - Network traffic analyzer + * By Gerald Combs + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include + +#include "wsutil/utf8_entities.h" + +#include + +FilesetEntryModel::FilesetEntryModel(QObject * parent) : + QAbstractItemModel(parent) +{} + +QModelIndex FilesetEntryModel::index(int row, int column, const QModelIndex &) const +{ + if (row >= entries_.count() || row < 0 || column > ColumnCount) { + return QModelIndex(); + } + +DIAG_OFF(cast-qual) + return createIndex(row, column, (void *)entries_.at(row)); +DIAG_ON(cast-qual) +} + +int FilesetEntryModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + return entries_.count(); +} + +QVariant FilesetEntryModel::data(const QModelIndex &index, int role) const +{ + if ( ! index.isValid() || index.row() >= rowCount() ) + return QVariant(); + + fileset_entry *entry = static_cast(index.internalPointer()); + if (role == Qt::DisplayRole && entry) { + switch (index.column()) { + case Name: + return QString(entry->name); + break; + case Created: + { + QString created = nameToDate(entry->name); + if(created.length() < 1) { + /* if this file doesn't follow the file set pattern, */ + /* use the creation time of that file if available */ + /* http://en.wikipedia.org/wiki/ISO_8601 */ + /* + * macOS provides 0 if the file system doesn't support the + * creation time; FreeBSD provides -1. + * + * If this OS doesn't provide the creation time with stat(), + * it will be 0. + */ + if (entry->ctime > 0) { + created = time_tToString(entry->ctime); + } else { + created = UTF8_EM_DASH; + } + } + return created; + break; + } + case Modified: + return time_tToString(entry->mtime); + break; + case Size: + return file_size_to_qstring(entry->size); + break; + default: + break; + } + } else if (role == Qt::ToolTipRole) { + return QString(tr("Open this capture file")); + } else if (role == Qt::TextAlignmentRole) { + switch (index.column()) { + case Size: + // Not perfect but better than nothing. + return Qt::AlignRight; + default: + return Qt::AlignLeft; + } + } + return QVariant(); +} + +QVariant FilesetEntryModel::headerData(int section, Qt::Orientation, int role) const +{ + if (role != Qt::DisplayRole) return QVariant(); + + switch (section) { + case Name: + return tr("Filename"); + break; + case Created: + return tr("Created"); + break; + case Modified: + return tr("Modified"); + break; + case Size: + return tr("Size"); + break; + default: + break; + } + return QVariant(); +} + +void FilesetEntryModel::appendEntry(const fileset_entry *entry) +{ + emit beginInsertRows(QModelIndex(), rowCount(), rowCount()); + entries_ << entry; + emit endInsertRows(); +} + +void FilesetEntryModel::clear() +{ + fileset_delete(); + beginResetModel(); + entries_.clear(); + endResetModel(); +} + +QString FilesetEntryModel::nameToDate(const char *name) const { + QString dn; + + if (!fileset_filename_match_pattern(name)) + return NULL; + + dn = name; + dn.remove(QRegExp(".*_")); + dn.truncate(14); + dn.insert(4, '-'); + dn.insert(7, '-'); + dn.insert(10, ' '); + dn.insert(13, ':'); + dn.insert(16, ':'); + return dn; +} + +QString FilesetEntryModel::time_tToString(time_t clock) const +{ + struct tm *local = localtime(&clock); + if (!local) return UTF8_EM_DASH; + + // yyyy-MM-dd HH:mm:ss + // The equivalent QDateTime call is pretty slow here, possibly related to QTBUG-21678 + // and/or QTBUG-41714. + return QString("%1-%2-%3 %4:%5:%6") + .arg(local->tm_year + 1900, 4, 10, QChar('0')) + .arg(local->tm_mon+1, 2, 10, QChar('0')) + .arg(local->tm_mday, 2, 10, QChar('0')) + .arg(local->tm_hour, 2, 10, QChar('0')) + .arg(local->tm_min, 2, 10, QChar('0')) + .arg(local->tm_sec, 2, 10, QChar('0')); +} + +/* + * Editor modelines + * + * Local Variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * ex: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/ui/qt/models/fileset_entry_model.h b/ui/qt/models/fileset_entry_model.h new file mode 100644 index 0000000000..ad740680b3 --- /dev/null +++ b/ui/qt/models/fileset_entry_model.h @@ -0,0 +1,64 @@ +/* fileset_entry_model.h + * + * Wireshark - Network traffic analyzer + * By Gerald Combs + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#ifndef FILESET_ENTRY_MODEL_H +#define FILESET_ENTRY_MODEL_H + +#include + +#include + +#include + +#include +#include + +class FilesetEntryModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + explicit FilesetEntryModel(QObject * parent = 0); + + QModelIndex index(int row, int column, const QModelIndex & = QModelIndex()) const; + // Everything is under the root. + virtual QModelIndex parent(const QModelIndex &) const { return QModelIndex(); } + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; + virtual int columnCount(const QModelIndex &) const { return ColumnCount; } + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + virtual QVariant headerData(int section, Qt::Orientation, int role = Qt::DisplayRole) const; + + virtual void appendEntry(const fileset_entry *entry); + const fileset_entry *getRowEntry(int row) const { return entries_.value(row, NULL); } + int entryCount() const { return entries_.count(); } + // Calls fileset_delete and clears our model data. + void clear(); + +private: + QVector entries_; + enum Column { Name, Created, Modified, Size, ColumnCount }; + + QString nameToDate(const char *name) const ; + QString time_tToString(time_t clock) const; +}; + +#endif // FILESET_ENTRY_MODEL_H + +/* + * Editor modelines + * + * Local Variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * ex: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */