summaryrefslogtreecommitdiffstats
path: root/ui/qt/about_dialog.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 20:34:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 20:34:10 +0000
commite4ba6dbc3f1e76890b22773807ea37fe8fa2b1bc (patch)
tree68cb5ef9081156392f1dd62a00c6ccc1451b93df /ui/qt/about_dialog.cpp
parentInitial commit. (diff)
downloadwireshark-e4ba6dbc3f1e76890b22773807ea37fe8fa2b1bc.tar.xz
wireshark-e4ba6dbc3f1e76890b22773807ea37fe8fa2b1bc.zip
Adding upstream version 4.2.2.upstream/4.2.2
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ui/qt/about_dialog.cpp')
-rw-r--r--ui/qt/about_dialog.cpp646
1 files changed, 646 insertions, 0 deletions
diff --git a/ui/qt/about_dialog.cpp b/ui/qt/about_dialog.cpp
new file mode 100644
index 0000000..0151303
--- /dev/null
+++ b/ui/qt/about_dialog.cpp
@@ -0,0 +1,646 @@
+/* about_dialog.cpp
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "config.h"
+
+#include "about_dialog.h"
+#include <ui_about_dialog.h>
+
+#include "main_application.h"
+#include <wsutil/filesystem.h>
+
+#include <QDesktopServices>
+#include <QUrl>
+
+#ifdef HAVE_LIBSMI
+#include <epan/oids.h>
+#endif
+
+#include <epan/maxmind_db.h>
+#include <epan/prefs.h>
+
+#ifdef HAVE_LUA
+#include <epan/wslua/init_wslua.h>
+#endif
+
+#include "ui/alert_box.h"
+#include "ui/util.h"
+#include "ui/help_url.h"
+#include <wsutil/utf8_entities.h>
+
+#include "file.h"
+#include "wsutil/file_util.h"
+#include "wsutil/tempfile.h"
+#include "wsutil/plugins.h"
+#include "wsutil/version_info.h"
+#include "ui/capture_globals.h"
+
+#include "extcap.h"
+
+#include <ui/qt/utils/color_utils.h>
+#include <ui/qt/utils/qt_ui_utils.h>
+#include <ui/qt/utils/variant_pointer.h>
+
+#include <ui/qt/models/astringlist_list_model.h>
+#include <ui/qt/models/url_link_delegate.h>
+
+#include <QFontMetrics>
+#include <QKeySequence>
+#include <QTextStream>
+#include <QUrl>
+#include <QRegularExpression>
+#include <QAbstractItemModel>
+#include <QHash>
+#include <QDesktopServices>
+#include <QClipboard>
+#include <QMenu>
+#include <QFileInfo>
+#include <QMessageBox>
+#include <QPlainTextEdit>
+
+AuthorListModel::AuthorListModel(QObject * parent) :
+AStringListListModel(parent)
+{
+ QFile f_authors;
+
+ f_authors.setFileName(":/about/authors.csv");
+ f_authors.open(QFile::ReadOnly | QFile::Text);
+ QTextStream ReadFile_authors(&f_authors);
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ ReadFile_authors.setEncoding(QStringConverter::Utf8);
+#else
+ ReadFile_authors.setCodec("UTF-8");
+#endif
+
+ while (!ReadFile_authors.atEnd()) {
+ QString line = ReadFile_authors.readLine();
+#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
+ QStringList entry = line.split(",", Qt::SkipEmptyParts);
+#else
+ QStringList entry = QStringList() << line.section(',', 0, 0) << line.section(',', 1, 1);
+#endif
+ if (entry.size() == 2) {
+ appendRow(entry);
+ }
+ }
+ f_authors.close();
+
+}
+
+AuthorListModel::~AuthorListModel() { }
+
+QStringList AuthorListModel::headerColumns() const
+{
+ return QStringList() << tr("Name") << tr("Email");
+}
+
+static void plugins_add_description(const char *name, const char *version,
+ const char *types, const char *filename,
+ void *user_data)
+{
+ QList<QStringList> *plugin_data = (QList<QStringList> *)user_data;
+ QStringList plugin_row = QStringList() << name << version << types << filename;
+ *plugin_data << plugin_row;
+}
+
+PluginListModel::PluginListModel(QObject * parent) : AStringListListModel(parent)
+{
+ QList<QStringList> plugin_data;
+#ifdef HAVE_PLUGINS
+ plugins_get_descriptions(plugins_add_description, &plugin_data);
+#endif
+
+#ifdef HAVE_LUA
+ wslua_plugins_get_descriptions(plugins_add_description, &plugin_data);
+#endif
+
+ extcap_get_descriptions(plugins_add_description, &plugin_data);
+
+ typeNames_ << QString("");
+ foreach(QStringList row, plugin_data)
+ {
+ QString type_name = row.at(2);
+ typeNames_ << type_name;
+ appendRow(row);
+ }
+
+ typeNames_.sort();
+ typeNames_.removeDuplicates();
+}
+
+QStringList PluginListModel::typeNames() const
+{
+ return typeNames_;
+}
+
+QStringList PluginListModel::headerColumns() const
+{
+ return QStringList() << tr("Name") << tr("Version") << tr("Type") << tr("Path");
+}
+
+ShortcutListModel::ShortcutListModel(QObject * parent):
+ AStringListListModel(parent)
+{
+ QMap<QString, QPair<QString, QString> > shortcuts; // name -> (shortcut, description)
+ foreach (const QWidget *child, mainApp->mainWindow()->findChildren<QWidget *>()) {
+ // Recent items look funny here.
+ if (child->objectName().compare("menuOpenRecentCaptureFile") == 0) continue;
+ foreach (const QAction *action, child->actions()) {
+
+ if (!action->shortcut().isEmpty()) {
+ QString name = action->text();
+ name.replace('&', "");
+ shortcuts[name] = QPair<QString, QString>(action->shortcut().toString(QKeySequence::NativeText), action->toolTip());
+ }
+ }
+ }
+
+ QStringList names = shortcuts.keys();
+ names.sort();
+ foreach (const QString &name, names) {
+ QStringList row;
+ row << shortcuts[name].first << name << shortcuts[name].second;
+ appendRow(row);
+ }
+}
+
+QStringList ShortcutListModel::headerColumns() const
+{
+ return QStringList() << tr("Shortcut") << tr("Name") << tr("Description");
+}
+
+FolderListModel::FolderListModel(QObject * parent):
+ AStringListListModel(parent)
+{
+ /* "file open" */
+ appendRow(QStringList() << tr("\"File\" dialogs") << get_open_dialog_initial_dir() << tr("capture files"));
+
+ /* temp */
+ appendRow(QStringList() << tr("Temp") << (global_capture_opts.temp_dir && global_capture_opts.temp_dir[0] ? global_capture_opts.temp_dir : g_get_tmp_dir()) << tr("untitled capture files"));
+
+ /* pers conf */
+ appendRow(QStringList() << tr("Personal configuration")
+ << gchar_free_to_qstring(get_persconffile_path("", FALSE))
+ << tr("dfilters, preferences, ethers, …"));
+
+ /* global conf */
+ QString dirPath = get_datafile_dir();
+ if (! dirPath.isEmpty()) {
+ appendRow (QStringList() << tr("Global configuration") << dirPath
+ << tr("dfilters, preferences, manuf, …"));
+ }
+
+ /* system */
+ appendRow(QStringList() << tr("System") << get_systemfile_dir() << tr("ethers, ipxnets"));
+
+ /* program */
+ appendRow(QStringList() << tr("Program") << get_progfile_dir() << tr("program files"));
+
+#ifdef HAVE_PLUGINS
+ /* pers plugins */
+ appendRow(QStringList() << tr("Personal Plugins") << get_plugins_pers_dir_with_version() << tr("binary plugins"));
+
+ /* global plugins */
+ appendRow(QStringList() << tr("Global Plugins") << get_plugins_dir_with_version() << tr("binary plugins"));
+#endif
+
+#ifdef HAVE_LUA
+ /* pers plugins */
+ appendRow(QStringList() << tr("Personal Lua Plugins") << get_plugins_pers_dir() << tr("Lua scripts"));
+
+ /* global plugins */
+ appendRow(QStringList() << tr("Global Lua Plugins") << get_plugins_dir() << tr("Lua scripts"));
+#endif
+
+ /* Extcap */
+ appendRow(QStringList() << tr("Personal Extcap path") << QString(get_extcap_pers_dir()) << tr("external capture (extcap) plugins"));
+ appendRow(QStringList() << tr("Global Extcap path") << QString(get_extcap_dir()) << tr("external capture (extcap) plugins"));
+
+#ifdef HAVE_MAXMINDDB
+ /* MaxMind DB */
+ QStringList maxMindDbPaths = QString(maxmind_db_get_paths()).split(G_SEARCHPATH_SEPARATOR_S);
+ foreach(QString path, maxMindDbPaths)
+ appendRow(QStringList() << tr("MaxMind DB path") << path.trimmed() << tr("MaxMind DB database search path"));
+#endif
+
+#ifdef HAVE_LIBSMI
+ /* SMI MIBs/PIBs */
+ char *default_mib_path = oid_get_default_mib_path();
+ QStringList smiPaths = QString(default_mib_path).split(G_SEARCHPATH_SEPARATOR_S);
+ g_free(default_mib_path);
+ foreach(QString path, smiPaths)
+ appendRow(QStringList() << tr("MIB/PIB path") << path.trimmed() << tr("SMI MIB/PIB search path"));
+#endif
+
+#ifdef Q_OS_MAC
+ /* Mac Extras */
+ QString extras_path = mainApp->applicationDirPath() + "/../Resources/Extras";
+ appendRow(QStringList() << tr("macOS Extras") << QDir::cleanPath(extras_path) << tr("Extra macOS packages"));
+
+#endif
+}
+
+QStringList FolderListModel::headerColumns() const
+{
+ return QStringList() << tr("Name") << tr("Location") << tr("Typical Files");
+}
+
+// To do:
+// - Tweak and enhance ui...
+
+AboutDialog::AboutDialog(QWidget *parent) :
+ QDialog(parent),
+ ui(new Ui::AboutDialog)
+{
+ ui->setupUi(this);
+ setAttribute(Qt::WA_DeleteOnClose, true);
+ QFile f_acknowledgements;
+ QFile f_license;
+
+ AuthorListModel * authorModel = new AuthorListModel(this);
+ AStringListListSortFilterProxyModel * proxyAuthorModel = new AStringListListSortFilterProxyModel(this);
+ proxyAuthorModel->setSourceModel(authorModel);
+ proxyAuthorModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
+ proxyAuthorModel->setColumnToFilter(0);
+ proxyAuthorModel->setColumnToFilter(1);
+ ui->tblAuthors->setModel(proxyAuthorModel);
+ ui->tblAuthors->setRootIsDecorated(false);
+ ui->tblAuthors->setContextMenuPolicy(Qt::CustomContextMenu);
+ connect(ui->tblAuthors, &QTreeView::customContextMenuRequested, this, &AboutDialog::handleCopyMenu);
+ connect(ui->searchAuthors, &QLineEdit::textChanged, proxyAuthorModel, &AStringListListSortFilterProxyModel::setFilter);
+
+ /* Wireshark tab */
+ updateWiresharkText();
+
+ ui->pte_wireshark->setFrameStyle(QFrame::NoFrame);
+ ui->pte_wireshark->viewport()->setAutoFillBackground(false);
+
+/* Check if it is a dev release... (VERSION_MINOR is odd in dev release) */
+#if VERSION_MINOR & 1
+ ui->label_logo->setPixmap(QPixmap(":/about/wssplash_dev.png"));
+#endif
+
+ /* Folders */
+ FolderListModel * folderModel = new FolderListModel(this);
+ AStringListListSortFilterProxyModel * folderProxyModel = new AStringListListSortFilterProxyModel(this);
+ folderProxyModel->setSourceModel(folderModel);
+ folderProxyModel->setColumnToFilter(1);
+ folderProxyModel->setFilterType(AStringListListSortFilterProxyModel::FilterByStart);
+ AStringListListUrlProxyModel * folderDisplayModel = new AStringListListUrlProxyModel(this);
+ folderDisplayModel->setSourceModel(folderProxyModel);
+ folderDisplayModel->setUrlColumn(1);
+ ui->tblFolders->setModel(folderDisplayModel);
+ ui->tblFolders->setRootIsDecorated(false);
+ ui->tblFolders->setItemDelegateForColumn(1, new UrlLinkDelegate(this));
+ ui->tblFolders->setContextMenuPolicy(Qt::CustomContextMenu);
+ ui->tblFolders->setTextElideMode(Qt::ElideMiddle);
+ ui->tblFolders->setSortingEnabled(true);
+ ui->tblFolders->sortByColumn(0, Qt::AscendingOrder);
+ connect(ui->tblFolders, &QTreeView::customContextMenuRequested, this, &AboutDialog::handleCopyMenu);
+ connect(ui->searchFolders, &QLineEdit::textChanged, folderProxyModel, &AStringListListSortFilterProxyModel::setFilter);
+ connect(ui->tblFolders, &QTreeView::doubleClicked, this, &AboutDialog::urlDoubleClicked);
+
+
+ /* Plugins */
+ ui->label_no_plugins->hide();
+ PluginListModel * pluginModel = new PluginListModel(this);
+ AStringListListSortFilterProxyModel * pluginFilterModel = new AStringListListSortFilterProxyModel(this);
+ pluginFilterModel->setSourceModel(pluginModel);
+ pluginFilterModel->setColumnToFilter(0);
+ AStringListListSortFilterProxyModel * pluginTypeModel = new AStringListListSortFilterProxyModel(this);
+ pluginTypeModel->setSourceModel(pluginFilterModel);
+ pluginTypeModel->setColumnToFilter(2);
+ ui->tblPlugins->setModel(pluginTypeModel);
+ ui->tblPlugins->setRootIsDecorated(false);
+ UrlLinkDelegate *plugin_delegate = new UrlLinkDelegate(this);
+ script_pattern = QString("\\.(lua|py)$");
+ plugin_delegate->setColCheck(3, script_pattern);
+ ui->tblPlugins->setItemDelegateForColumn(3, plugin_delegate);
+ ui->cmbType->addItems(pluginModel->typeNames());
+ ui->tblPlugins->setContextMenuPolicy(Qt::CustomContextMenu);
+ ui->tblPlugins->setTextElideMode(Qt::ElideMiddle);
+ ui->tblPlugins->setSortingEnabled(true);
+ ui->tblPlugins->sortByColumn(0, Qt::AscendingOrder);
+ connect(ui->tblPlugins, &QTreeView::customContextMenuRequested, this, &AboutDialog::handleCopyMenu);
+ connect(ui->searchPlugins, &QLineEdit::textChanged, pluginFilterModel, &AStringListListSortFilterProxyModel::setFilter);
+ connect(ui->cmbType, &QComboBox::currentTextChanged, pluginTypeModel, &AStringListListSortFilterProxyModel::setFilter);
+ if (ui->tblPlugins->model()->rowCount() < 1) {
+ foreach (QWidget *w, ui->tab_plugins->findChildren<QWidget *>()) {
+ w->hide();
+ }
+ ui->label_no_plugins->setAlignment(Qt::AlignVCenter | Qt::AlignHCenter);
+ ui->label_no_plugins->setEnabled(false);
+ ui->label_no_plugins->show();
+ }
+
+ /* Shortcuts */
+ ShortcutListModel * shortcutModel = new ShortcutListModel(this);
+ AStringListListSortFilterProxyModel * shortcutProxyModel = new AStringListListSortFilterProxyModel(this);
+ shortcutProxyModel->setSourceModel(shortcutModel);
+ shortcutProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
+ shortcutProxyModel->setColumnToFilter(1);
+ shortcutProxyModel->setColumnToFilter(2);
+ ui->tblShortcuts->setModel(shortcutProxyModel);
+ ui->tblShortcuts->setRootIsDecorated(false);
+ ui->tblShortcuts->setContextMenuPolicy(Qt::CustomContextMenu);
+ ui->tblShortcuts->setSortingEnabled(true);
+ ui->tblShortcuts->sortByColumn(1, Qt::AscendingOrder);
+ connect(ui->tblShortcuts, &QTreeView::customContextMenuRequested, this, &AboutDialog::handleCopyMenu);
+ connect(ui->searchShortcuts, &QLineEdit::textChanged, shortcutProxyModel, &AStringListListSortFilterProxyModel::setFilter);
+
+ /* Acknowledgements */
+ f_acknowledgements.setFileName(":/about/Acknowledgements.md");
+
+ f_acknowledgements.open(QFile::ReadOnly | QFile::Text);
+ QTextStream ReadFile_acks(&f_acknowledgements);
+
+ /* QTextBrowser markdown support added in 5.14. */
+#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
+ QTextBrowser *textBrowserAcks = new QTextBrowser();
+ textBrowserAcks->setMarkdown(ReadFile_acks.readAll());
+ textBrowserAcks->setReadOnly(true);
+ textBrowserAcks->setOpenExternalLinks(true);
+ textBrowserAcks->moveCursor(QTextCursor::Start);
+ ui->ackVerticalLayout->addWidget(textBrowserAcks);
+#else
+ QPlainTextEdit *pte = new QPlainTextEdit();
+ pte->setPlainText(ReadFile_acks.readAll());
+ pte->setReadOnly(true);
+ pte->moveCursor(QTextCursor::Start);
+ ui->ackVerticalLayout->addWidget(pte);
+#endif
+
+ /* License */
+ f_license.setFileName(":/about/gpl-2.0-standalone.html");
+
+ f_license.open(QFile::ReadOnly | QFile::Text);
+ QTextStream ReadFile_license(&f_license);
+
+ ui->textBrowserLicense->setHtml(ReadFile_license.readAll());
+ ui->textBrowserLicense->moveCursor(QTextCursor::Start);
+}
+
+AboutDialog::~AboutDialog()
+{
+ delete ui;
+}
+
+bool AboutDialog::event(QEvent *event)
+{
+ switch (event->type()) {
+ case QEvent::ApplicationPaletteChange:
+ updateWiresharkText();
+ break;
+ default:
+ break;
+
+ }
+ return QDialog::event(event);
+}
+
+void AboutDialog::showEvent(QShowEvent * event)
+{
+ int one_em = fontMetrics().height();
+
+ // Authors: Names slightly narrower than emails.
+ QAbstractItemModel *model = ui->tblAuthors->model();
+ int column_count = model->columnCount();
+ if (column_count) {
+ ui->tblAuthors->setColumnWidth(0, (ui->tblAuthors->parentWidget()->width() / column_count) - one_em);
+ }
+
+ // Folders: First and last to contents.
+ ui->tblFolders->resizeColumnToContents(0);
+ ui->tblFolders->resizeColumnToContents(2);
+ ui->tblFolders->setColumnWidth(1, ui->tblFolders->parentWidget()->width() -
+ (ui->tblFolders->columnWidth(0) + ui->tblFolders->columnWidth(2)));
+
+ // Plugins: All but the last to contents.
+ model = ui->tblPlugins->model();
+ for (int col = 0; model && col < model->columnCount() - 1; col++) {
+ ui->tblPlugins->resizeColumnToContents(col);
+ }
+
+ // Contents + 2 em-widths
+ ui->tblShortcuts->resizeColumnToContents(0);
+ ui->tblShortcuts->setColumnWidth(0, ui->tblShortcuts->columnWidth(0) + (one_em * 2));
+ ui->tblShortcuts->setColumnWidth(1, one_em * 12);
+ ui->tblShortcuts->resizeColumnToContents(2);
+
+ QDialog::showEvent(event);
+}
+
+void AboutDialog::updateWiresharkText()
+{
+ QString vcs_version_info_str = get_ws_vcs_version_info();
+ QString copyright_info_str = get_copyright_info();
+ QString license_info_str = get_license_info();
+ QString comp_info_str = gstring_free_to_qbytearray(get_compiled_version_info(gather_wireshark_qt_compiled_info));
+ QString runtime_info_str = gstring_free_to_qbytearray(get_runtime_version_info(gather_wireshark_runtime_info));
+
+ QString message = ColorUtils::themeLinkStyle();
+
+ /* Construct the message string */
+ message += "<p>Version " + html_escape(vcs_version_info_str) + ".</p>\n";
+ message += "<p>" + html_escape(copyright_info_str) + "</p>\n";
+ message += "<p>" + html_escape(license_info_str) + "</p>\n";
+ message += "<p>" + html_escape(comp_info_str) + "</p>\n";
+ message += "<p>" + html_escape(runtime_info_str) + "</p>\n";
+ message += "<p>Check the man page and <a href=https://www.wireshark.org>www.wireshark.org</a> "
+ "for more information.</p>\n";
+ ui->pte_wireshark->setHtml(message);
+
+ /* Save the info for the clipboard copy */
+ clipboardInfo = "";
+ clipboardInfo += "Version " + vcs_version_info_str + ".\n\n";
+ /* XXX: GCC 12.1 has a bogus stringop-overread warning using the Qt
+ * conversions from QByteArray to QString at -O2 and higher due to
+ * computing a branch that will never be taken.
+ */
+#if WS_IS_AT_LEAST_GNUC_VERSION(12,1)
+DIAG_OFF(stringop-overread)
+#endif
+ clipboardInfo += gstring_free_to_qbytearray(get_compiled_version_info(gather_wireshark_qt_compiled_info)) + "\n";
+ clipboardInfo += gstring_free_to_qbytearray(get_runtime_version_info(gather_wireshark_runtime_info)) + "\n";
+#if WS_IS_AT_LEAST_GNUC_VERSION(12,1)
+DIAG_ON(stringop-overread)
+#endif
+}
+
+void AboutDialog::on_copyToClipboard_clicked()
+{
+ QClipboard * clipBoard = QApplication::clipboard();
+ clipBoard->setText(clipboardInfo);
+}
+
+void AboutDialog::urlDoubleClicked(const QModelIndex &idx)
+{
+ if (idx.column() != 1) {
+ return;
+ }
+ QTreeView * table = qobject_cast<QTreeView *>(sender());
+ if (! table)
+ return;
+
+ QString urlText = table->model()->data(idx).toString();
+ if (urlText.isEmpty())
+ return;
+
+ if (! QDir(urlText).exists())
+ {
+ if (QMessageBox::question(this, tr("The directory does not exist"),
+ QString(tr("Should the directory %1 be created?").arg(urlText))) == QMessageBox::Yes)
+ {
+ if (! QDir().mkpath(urlText))
+ {
+ QMessageBox::warning(this, tr("The directory could not be created"),
+ QString(tr("The directory %1 could not be created.").arg(urlText)));
+ }
+ }
+ }
+
+ if (QDir(urlText).exists())
+ {
+ QUrl url = QUrl::fromLocalFile(urlText);
+ if (url.isValid())
+ QDesktopServices::openUrl(url);
+ }
+}
+
+void AboutDialog::handleCopyMenu(QPoint pos)
+{
+ QTreeView * tree = qobject_cast<QTreeView *>(sender());
+ if (! tree)
+ return;
+
+ QModelIndex index = tree->indexAt(pos);
+ if (! index.isValid())
+ return;
+
+ QMenu * menu = new QMenu(this);
+ menu->setAttribute(Qt::WA_DeleteOnClose);
+
+ if (ui->tabWidget->currentWidget() == ui->tab_plugins)
+ {
+#ifdef Q_OS_MAC
+ QString show_in_str = tr("Show in Finder");
+#else
+ QString show_in_str = tr("Show in Folder");
+#endif
+ QAction * showInFolderAction = menu->addAction(show_in_str);
+ showInFolderAction->setData(VariantPointer<QTreeView>::asQVariant(tree));
+ connect(showInFolderAction, &QAction::triggered, this, &AboutDialog::showInFolderActionTriggered);
+ }
+
+ QAction * copyColumnAction = menu->addAction(tr("Copy"));
+ copyColumnAction->setData(VariantPointer<QTreeView>::asQVariant(tree));
+ connect(copyColumnAction, &QAction::triggered, this, &AboutDialog::copyActionTriggered);
+
+ QModelIndexList selectedRows = tree->selectionModel()->selectedRows();
+ QAction * copyRowAction = menu->addAction(tr("Copy Row(s)", "", static_cast<int>(selectedRows.count())));
+ copyRowAction->setData(VariantPointer<QTreeView>::asQVariant(tree));
+ connect(copyRowAction, &QAction::triggered, this, &AboutDialog::copyRowActionTriggered);
+
+ menu->popup(tree->viewport()->mapToGlobal(pos));
+}
+
+void AboutDialog::showInFolderActionTriggered()
+{
+ QAction * sendingAction = qobject_cast<QAction *>(sender());
+ if (!sendingAction)
+ return;
+
+ QTreeView * tree = VariantPointer<QTreeView>::asPtr(sendingAction->data());
+ QModelIndexList selectedRows = tree->selectionModel()->selectedRows();
+
+ foreach (QModelIndex index, selectedRows)
+ {
+ QString cf_path = tree->model()->index(index.row(), 3).data().toString();
+ desktop_show_in_folder(cf_path);
+ }
+}
+
+void AboutDialog::copyRowActionTriggered()
+{
+ copyActionTriggered(true);
+}
+
+void AboutDialog::copyActionTriggered(bool copyRow)
+{
+ QAction * sendingAction = qobject_cast<QAction *>(sender());
+ if (! sendingAction)
+ return;
+
+ QTreeView * tree = VariantPointer<QTreeView>::asPtr(sendingAction->data());
+
+ QModelIndexList selIndeces = tree->selectionModel()->selectedIndexes();
+
+ int copyColumn = -1;
+ if (! copyRow)
+ {
+ QMenu * menu = qobject_cast<QMenu *>(sendingAction->parent());
+ if (menu)
+ {
+ QPoint menuPosOnTable = tree->mapFromGlobal(menu->pos());
+ QModelIndex clickedIndex = tree->indexAt(menuPosOnTable);
+ if (clickedIndex.isValid())
+ copyColumn = clickedIndex.column();
+ }
+ }
+
+ QString clipdata;
+ if (selIndeces.count() > 0)
+ {
+ int columnCount = tree->model()->columnCount();
+ QList<int> visitedRows;
+
+ foreach(QModelIndex index, selIndeces)
+ {
+ if (visitedRows.contains(index.row()))
+ continue;
+
+ QStringList row;
+ if (copyRow)
+ {
+ for (int cnt = 0; cnt < columnCount; cnt++)
+ {
+ QModelIndex dataIdx = tree->model()->index(index.row(), cnt);
+ row << tree->model()->data(dataIdx).toString();
+ }
+ }
+ else
+ {
+ if (copyColumn < 0)
+ copyColumn = index.column();
+
+ QModelIndex dataIdx = tree->model()->index(index.row(), copyColumn);
+ row << tree->model()->data(dataIdx).toString();
+ }
+
+ clipdata.append(row.join("\t\t").append("\n"));
+
+ visitedRows << index.row();
+ }
+ }
+ QClipboard * clipBoard = QApplication::clipboard();
+ clipBoard->setText(clipdata);
+}
+
+void AboutDialog::on_tblPlugins_doubleClicked(const QModelIndex &index)
+{
+ const int path_col = 3;
+ if (index.column() != path_col) {
+ return;
+ }
+ const int row = index.row();
+ const QAbstractItemModel *model = index.model();
+ if (model->index(row, path_col).data().toString().contains(QRegularExpression(script_pattern))) {
+ QDesktopServices::openUrl(QUrl::fromLocalFile(model->index(row, path_col).data().toString()));
+ }
+}