1 /* about_dialog.cpp
2  *
3  * Wireshark - Network traffic analyzer
4  * By Gerald Combs <gerald@wireshark.org>
5  * Copyright 1998 Gerald Combs
6  *
7  * SPDX-License-Identifier: GPL-2.0-or-later
8  */
9 
10 #include "config.h"
11 
12 #include "about_dialog.h"
13 #include <ui_about_dialog.h>
14 
15 #include "wireshark_application.h"
16 #include <wsutil/filesystem.h>
17 
18 #include <QDesktopServices>
19 #include <QUrl>
20 
21 #ifdef HAVE_LIBSMI
22 #include <epan/oids.h>
23 #endif
24 
25 #include <epan/maxmind_db.h>
26 
27 #ifdef HAVE_LUA
28 #include <epan/wslua/init_wslua.h>
29 #endif
30 
31 #include "ui/alert_box.h"
32 #include "ui/last_open_dir.h"
33 #include "ui/help_url.h"
34 #include <wsutil/utf8_entities.h>
35 
36 #include "file.h"
37 #include "wsutil/file_util.h"
38 #include "wsutil/tempfile.h"
39 #include "wsutil/plugins.h"
40 #include "wsutil/copyright_info.h"
41 #include "ui/version_info.h"
42 
43 #include "extcap.h"
44 
45 #include <ui/qt/utils/color_utils.h>
46 #include <ui/qt/utils/qt_ui_utils.h>
47 #include <ui/qt/utils/variant_pointer.h>
48 
49 #include <ui/qt/models/astringlist_list_model.h>
50 #include <ui/qt/models/url_link_delegate.h>
51 
52 #include <QFontMetrics>
53 #include <QKeySequence>
54 #include <QTextStream>
55 #include <QUrl>
56 #include <QRegExp>
57 #include <QAbstractItemModel>
58 #include <QHash>
59 #include <QDesktopServices>
60 #include <QClipboard>
61 #include <QMenu>
62 #include <QFileInfo>
63 #include <QMessageBox>
64 
AuthorListModel(QObject * parent)65 AuthorListModel::AuthorListModel(QObject * parent) :
66 AStringListListModel(parent)
67 {
68     bool readAck = false;
69     QFile f_authors;
70 
71     f_authors.setFileName(get_datafile_path("AUTHORS-SHORT"));
72     f_authors.open(QFile::ReadOnly | QFile::Text);
73     QTextStream ReadFile_authors(&f_authors);
74     ReadFile_authors.setCodec("UTF-8");
75 
76     QRegExp rx("(.*)[<(]([\\s'a-zA-Z0-9._%+-]+(\\[[Aa][Tt]\\])?[a-zA-Z0-9._%+-]+)[>)]");
77     acknowledgement_.clear();
78     while (!ReadFile_authors.atEnd()) {
79         QString line = ReadFile_authors.readLine();
80 
81         if (! readAck && line.trimmed().length() == 0)
82                 continue;
83         if (line.startsWith("------"))
84             continue;
85 
86         if (line.contains("Acknowledgements"))
87         {
88             readAck = true;
89             continue;
90         }
91         else if (rx.indexIn(line) != -1)
92             appendRow(QStringList() << rx.cap(1).trimmed() << rx.cap(2).trimmed());
93 
94         if (readAck && (!line.isEmpty() || !acknowledgement_.isEmpty()))
95             acknowledgement_.append(QString("%1\n").arg(line));
96     }
97     f_authors.close();
98 
99 }
100 
~AuthorListModel()101 AuthorListModel::~AuthorListModel() { }
102 
acknowledgment() const103 QString AuthorListModel::acknowledgment() const
104 {
105     return acknowledgement_;
106 }
107 
headerColumns() const108 QStringList AuthorListModel::headerColumns() const
109 {
110     return QStringList() << tr("Name") << tr("Email");
111 }
112 
plugins_add_description(const char * name,const char * version,const char * types,const char * filename,void * user_data)113 static void plugins_add_description(const char *name, const char *version,
114                                     const char *types, const char *filename,
115                                     void *user_data)
116 {
117     QList<QStringList> *plugin_data = (QList<QStringList> *)user_data;
118     QStringList plugin_row = QStringList() << name << version << types << filename;
119     *plugin_data << plugin_row;
120 }
121 
PluginListModel(QObject * parent)122 PluginListModel::PluginListModel(QObject * parent) : AStringListListModel(parent)
123 {
124     QList<QStringList> plugin_data;
125 #ifdef HAVE_PLUGINS
126     plugins_get_descriptions(plugins_add_description, &plugin_data);
127 #endif
128 
129 #ifdef HAVE_LUA
130     wslua_plugins_get_descriptions(plugins_add_description, &plugin_data);
131 #endif
132 
133     extcap_get_descriptions(plugins_add_description, &plugin_data);
134 
135     typeNames_ << QString("");
136     foreach(QStringList row, plugin_data)
137     {
138         QString type_name = row.at(2);
139         typeNames_ << type_name;
140         appendRow(row);
141     }
142 
143     typeNames_.sort();
144     typeNames_.removeDuplicates();
145 }
146 
typeNames() const147 QStringList PluginListModel::typeNames() const
148 {
149     return typeNames_;
150 }
151 
headerColumns() const152 QStringList PluginListModel::headerColumns() const
153 {
154     return QStringList() << tr("Name") << tr("Version") << tr("Type") << tr("Path");
155 }
156 
ShortcutListModel(QObject * parent)157 ShortcutListModel::ShortcutListModel(QObject * parent):
158         AStringListListModel(parent)
159 {
160     QMap<QString, QPair<QString, QString> > shortcuts; // name -> (shortcut, description)
161     foreach (const QWidget *child, wsApp->mainWindow()->findChildren<QWidget *>()) {
162         // Recent items look funny here.
163         if (child->objectName().compare("menuOpenRecentCaptureFile") == 0) continue;
164         foreach (const QAction *action, child->actions()) {
165 
166             if (!action->shortcut().isEmpty()) {
167                 QString name = action->text();
168                 name.replace('&', "");
169                 shortcuts[name] = QPair<QString, QString>(action->shortcut().toString(QKeySequence::NativeText), action->toolTip());
170             }
171         }
172     }
173 
174     QStringList names = shortcuts.keys();
175     names.sort();
176     foreach (const QString &name, names) {
177         QStringList row;
178         row << shortcuts[name].first << name << shortcuts[name].second;
179         appendRow(row);
180     }
181 }
182 
headerColumns() const183 QStringList ShortcutListModel::headerColumns() const
184 {
185     return QStringList() << tr("Shortcut") << tr("Name") << tr("Description");
186 }
187 
FolderListModel(QObject * parent)188 FolderListModel::FolderListModel(QObject * parent):
189         AStringListListModel(parent)
190 {
191     /* "file open" */
192     appendRow(QStringList() << tr("\"File\" dialogs") << get_last_open_dir() << tr("capture files"));
193 
194     /* temp */
195     appendRow(QStringList() << tr("Temp") << g_get_tmp_dir() << tr("untitled capture files"));
196 
197     /* pers conf */
198     appendRow(QStringList() << tr("Personal configuration")
199             << gchar_free_to_qstring(get_persconffile_path("", FALSE))
200             << tr("dfilters, preferences, ethers, …"));
201 
202     /* global conf */
203     QString dirPath = get_datafile_dir();
204     if (! dirPath.isEmpty()) {
205         appendRow (QStringList() << tr("Global configuration") << dirPath
206                 << tr("dfilters, preferences, manuf, …"));
207     }
208 
209     /* system */
210     appendRow(QStringList() << tr("System") << get_systemfile_dir() << tr("ethers, ipxnets"));
211 
212     /* program */
213     appendRow(QStringList() << tr("Program") << get_progfile_dir() << tr("program files"));
214 
215 #ifdef HAVE_PLUGINS
216     /* pers plugins */
217     appendRow(QStringList() << tr("Personal Plugins") << get_plugins_pers_dir_with_version() << tr("binary plugins"));
218 
219     /* global plugins */
220     appendRow(QStringList() << tr("Global Plugins") << get_plugins_dir_with_version() << tr("binary plugins"));
221 #endif
222 
223 #ifdef HAVE_LUA
224     /* pers plugins */
225     appendRow(QStringList() << tr("Personal Lua Plugins") << get_plugins_pers_dir() << tr("lua scripts"));
226 
227     /* global plugins */
228     appendRow(QStringList() << tr("Global Lua Plugins") << get_plugins_dir() << tr("lua scripts"));
229 #endif
230 
231     /* Extcap */
232     appendRow(QStringList() << tr("Personal Extcap path") << QString(get_persconffile_path("extcap", FALSE)).trimmed() << tr("Extcap Plugins search path"));
233     appendRow(QStringList() << tr("Global Extcap path") << QString(get_extcap_dir()).trimmed() << tr("Extcap Plugins search path"));
234 
235 #ifdef HAVE_MAXMINDDB
236     /* MaxMind DB */
237     QStringList maxMindDbPaths = QString(maxmind_db_get_paths()).split(G_SEARCHPATH_SEPARATOR_S);
238     foreach(QString path, maxMindDbPaths)
239         appendRow(QStringList() << tr("MaxMind DB path") << path.trimmed() << tr("MaxMind DB database search path"));
240 #endif
241 
242 #ifdef HAVE_LIBSMI
243     /* SMI MIBs/PIBs */
244     char *default_mib_path = oid_get_default_mib_path();
245     QStringList smiPaths = QString(default_mib_path).split(G_SEARCHPATH_SEPARATOR_S);
246     g_free(default_mib_path);
247     foreach(QString path, smiPaths)
248         appendRow(QStringList() << tr("MIB/PIB path") << path.trimmed() << tr("SMI MIB/PIB search path"));
249 #endif
250 
251 #ifdef Q_OS_MAC
252     /* Mac Extras */
253     QString extras_path = wsApp->applicationDirPath() + "/../Resources/Extras";
254     appendRow(QStringList() << tr("macOS Extras") << QDir::cleanPath(extras_path) << tr("Extra macOS packages"));
255 
256 #endif
257 }
258 
headerColumns() const259 QStringList FolderListModel::headerColumns() const
260 {
261     return QStringList() << tr("Name") << tr("Location") << tr("Typical Files");
262 }
263 
264 // To do:
265 // - Tweak and enhance ui...
266 
AboutDialog(QWidget * parent)267 AboutDialog::AboutDialog(QWidget *parent) :
268     QDialog(parent),
269     ui(new Ui::AboutDialog)
270 {
271     ui->setupUi(this);
272     setAttribute(Qt::WA_DeleteOnClose, true);
273     QFile f_license;
274 
275     AuthorListModel * authorModel = new AuthorListModel(this);
276     AStringListListSortFilterProxyModel * proxyAuthorModel = new AStringListListSortFilterProxyModel(this);
277     proxyAuthorModel->setSourceModel(authorModel);
278     proxyAuthorModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
279     proxyAuthorModel->setColumnToFilter(0);
280     proxyAuthorModel->setColumnToFilter(1);
281     ui->tblAuthors->setModel(proxyAuthorModel);
282     ui->tblAuthors->setRootIsDecorated(false);
283     ui->pte_Authors->clear();
284     ui->pte_Authors->appendPlainText(authorModel->acknowledgment());
285     ui->pte_Authors->moveCursor(QTextCursor::Start);
286 
287     ui->tblAuthors->setContextMenuPolicy(Qt::CustomContextMenu);
288     connect(ui->tblAuthors, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(handleCopyMenu(QPoint)));
289     connect(ui->searchAuthors, SIGNAL(textChanged(QString)), proxyAuthorModel, SLOT(setFilter(QString)));
290 
291     /* Wireshark tab */
292     updateWiresharkText();
293 
294     ui->pte_wireshark->setFrameStyle(QFrame::NoFrame);
295     ui->pte_wireshark->viewport()->setAutoFillBackground(false);
296 
297 /* Check if it is a dev release... (VERSION_MINOR is odd in dev release) */
298 #if VERSION_MINOR & 1
299         ui->label_logo->setPixmap(QPixmap(":/about/wssplash_dev.png"));
300 #endif
301 
302     /* Folders */
303     FolderListModel * folderModel = new FolderListModel(this);
304     AStringListListSortFilterProxyModel * folderProxyModel = new AStringListListSortFilterProxyModel(this);
305     folderProxyModel->setSourceModel(folderModel);
306     folderProxyModel->setColumnToFilter(1);
307     folderProxyModel->setFilterType(AStringListListSortFilterProxyModel::FilterByStart);
308     AStringListListUrlProxyModel * folderDisplayModel = new AStringListListUrlProxyModel(this);
309     folderDisplayModel->setSourceModel(folderProxyModel);
310     folderDisplayModel->setUrlColumn(1);
311     ui->tblFolders->setModel(folderDisplayModel);
312     ui->tblFolders->setRootIsDecorated(false);
313     ui->tblFolders->setItemDelegateForColumn(1, new UrlLinkDelegate(this));
314     ui->tblFolders->setContextMenuPolicy(Qt::CustomContextMenu);
315     ui->tblFolders->setTextElideMode(Qt::ElideMiddle);
316     ui->tblFolders->setSortingEnabled(true);
317     ui->tblFolders->sortByColumn(0, Qt::AscendingOrder);
318     connect(ui->tblFolders, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(handleCopyMenu(QPoint)));
319     connect(ui->searchFolders, SIGNAL(textChanged(QString)), folderProxyModel, SLOT(setFilter(QString)));
320     connect(ui->tblFolders, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(urlDoubleClicked(QModelIndex)));
321 
322 
323     /* Plugins */
324     ui->label_no_plugins->hide();
325     PluginListModel * pluginModel = new PluginListModel(this);
326     AStringListListSortFilterProxyModel * pluginFilterModel = new AStringListListSortFilterProxyModel(this);
327     pluginFilterModel->setSourceModel(pluginModel);
328     pluginFilterModel->setColumnToFilter(0);
329     AStringListListSortFilterProxyModel * pluginTypeModel = new AStringListListSortFilterProxyModel(this);
330     pluginTypeModel->setSourceModel(pluginFilterModel);
331     pluginTypeModel->setColumnToFilter(2);
332     ui->tblPlugins->setModel(pluginTypeModel);
333     ui->tblPlugins->setRootIsDecorated(false);
334     UrlLinkDelegate *plugin_delegate = new UrlLinkDelegate(this);
335     script_pattern = QString("\\.(lua|py)$");
336     plugin_delegate->setColCheck(3, script_pattern);
337     ui->tblPlugins->setItemDelegateForColumn(3, plugin_delegate);
338     ui->cmbType->addItems(pluginModel->typeNames());
339     ui->tblPlugins->setContextMenuPolicy(Qt::CustomContextMenu);
340     ui->tblPlugins->setTextElideMode(Qt::ElideMiddle);
341     ui->tblPlugins->setSortingEnabled(true);
342     ui->tblPlugins->sortByColumn(0, Qt::AscendingOrder);
343     connect(ui->tblPlugins, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(handleCopyMenu(QPoint)));
344     connect(ui->searchPlugins, SIGNAL(textChanged(QString)), pluginFilterModel, SLOT(setFilter(QString)));
345     connect(ui->cmbType, SIGNAL(currentIndexChanged(QString)), pluginTypeModel, SLOT(setFilter(QString)));
346     if (ui->tblPlugins->model()->rowCount() < 1) {
347         foreach (QWidget *w, ui->tab_plugins->findChildren<QWidget *>()) {
348             w->hide();
349         }
350         ui->label_no_plugins->setAlignment(Qt::AlignVCenter | Qt::AlignHCenter);
351         ui->label_no_plugins->setEnabled(false);
352         ui->label_no_plugins->show();
353     }
354 
355     /* Shortcuts */
356     ShortcutListModel * shortcutModel = new ShortcutListModel(this);
357     AStringListListSortFilterProxyModel * shortcutProxyModel = new AStringListListSortFilterProxyModel(this);
358     shortcutProxyModel->setSourceModel(shortcutModel);
359     shortcutProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
360     shortcutProxyModel->setColumnToFilter(1);
361     shortcutProxyModel->setColumnToFilter(2);
362     ui->tblShortcuts->setModel(shortcutProxyModel);
363     ui->tblShortcuts->setRootIsDecorated(false);
364     ui->tblShortcuts->setContextMenuPolicy(Qt::CustomContextMenu);
365     ui->tblShortcuts->setSortingEnabled(true);
366     ui->tblShortcuts->sortByColumn(1, Qt::AscendingOrder);
367     connect(ui->tblShortcuts, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(handleCopyMenu(QPoint)));
368     connect(ui->searchShortcuts, SIGNAL(textChanged(QString)), shortcutProxyModel, SLOT(setFilter(QString)));
369 
370     /* License */
371 #if defined(_WIN32)
372     f_license.setFileName(get_datafile_path("COPYING.txt"));
373 #else
374     f_license.setFileName(get_datafile_path("COPYING"));
375 #endif
376 
377     f_license.open(QFile::ReadOnly | QFile::Text);
378     QTextStream ReadFile_license(&f_license);
379 
380     ui->pte_License->insertPlainText(ReadFile_license.readAll());
381     ui->pte_License->moveCursor(QTextCursor::Start);
382 }
383 
~AboutDialog()384 AboutDialog::~AboutDialog()
385 {
386     delete ui;
387 }
388 
event(QEvent * event)389 bool AboutDialog::event(QEvent *event)
390 {
391     switch (event->type()) {
392     case QEvent::ApplicationPaletteChange:
393         updateWiresharkText();
394         break;
395     default:
396         break;
397 
398     }
399     return QDialog::event(event);
400 }
401 
showEvent(QShowEvent * event)402 void AboutDialog::showEvent(QShowEvent * event)
403 {
404     int one_em = fontMetrics().height();
405 
406     // Authors: Names slightly narrower than emails.
407     QAbstractItemModel *model = ui->tblAuthors->model();
408     int column_count = model->columnCount();
409     if (column_count) {
410         ui->tblAuthors->setColumnWidth(0, (ui->tblAuthors->parentWidget()->width() / column_count) - one_em);
411     }
412 
413     // Folders: First and last to contents.
414     ui->tblFolders->resizeColumnToContents(0);
415     ui->tblFolders->resizeColumnToContents(2);
416     ui->tblFolders->setColumnWidth(1, ui->tblFolders->parentWidget()->width() -
417                                    (ui->tblFolders->columnWidth(0) + ui->tblFolders->columnWidth(2)));
418 
419     // Plugins: All but the last to contents.
420     model = ui->tblPlugins->model();
421     for (int col = 0; model && col < model->columnCount() - 1; col++) {
422         ui->tblPlugins->resizeColumnToContents(col);
423     }
424 
425     // Contents + 2 em-widths
426     ui->tblShortcuts->resizeColumnToContents(0);
427     ui->tblShortcuts->setColumnWidth(0, ui->tblShortcuts->columnWidth(0) + (one_em * 2));
428     ui->tblShortcuts->setColumnWidth(1, one_em * 12);
429     ui->tblShortcuts->resizeColumnToContents(2);
430 
431     QDialog::showEvent(event);
432 }
433 
updateWiresharkText()434 void AboutDialog::updateWiresharkText()
435 {
436     QString vcs_version_info_str = get_ws_vcs_version_info();
437     QString copyright_info_str = get_copyright_info();
438     QString comp_info_str = gstring_free_to_qbytearray(get_compiled_version_info(get_wireshark_qt_compiled_info,
439                                               get_gui_compiled_info));
440     QString runtime_info_str = gstring_free_to_qbytearray(get_runtime_version_info(get_wireshark_runtime_info));
441 
442     QString message = ColorUtils::themeLinkStyle();
443 
444     /* Construct the message string */
445     message += "<p>Version " + html_escape(vcs_version_info_str) + "</p>\n\n";
446     message += "<p>" + html_escape(copyright_info_str) + "</p>\n\n";
447     message += "<p>" + html_escape(comp_info_str) + "</p>\n\n";
448     message += "<p>" + html_escape(runtime_info_str) + "</p>\n\n";
449     message += "<p>Wireshark is Open Source Software released under the GNU General Public License.</p>\n\n";
450     message += "<p>Check the man page and ";
451     message += "<a href=https://www.wireshark.org>https://www.wireshark.org</a> ";
452     message += "for more information.</p>\n\n";
453     ui->pte_wireshark->setHtml(message);
454 
455     /* Save the info for the clipboard copy */
456     clipboardInfo = "";
457     clipboardInfo += vcs_version_info_str + "\n\n";
458     clipboardInfo += gstring_free_to_qbytearray(get_compiled_version_info(get_wireshark_qt_compiled_info,
459                                                                           get_gui_compiled_info)) + "\n";
460     clipboardInfo += gstring_free_to_qbytearray(get_runtime_version_info(get_wireshark_runtime_info));
461 }
462 
on_copyToClipboard_clicked()463 void AboutDialog::on_copyToClipboard_clicked()
464 {
465     QClipboard * clipBoard = QApplication::clipboard();
466     clipBoard->setText(clipboardInfo);
467 }
468 
urlDoubleClicked(const QModelIndex & idx)469 void AboutDialog::urlDoubleClicked(const QModelIndex &idx)
470 {
471     if (idx.column() != 1) {
472         return;
473     }
474     QTreeView * table = qobject_cast<QTreeView *>(sender());
475     if (! table)
476         return;
477 
478     QString urlText = table->model()->data(idx).toString();
479     if (urlText.isEmpty())
480         return;
481 
482     if (! QDir(urlText).exists())
483     {
484         if (QMessageBox::question(this, tr("The directory does not exist"),
485                           QString(tr("Should the directory %1 be created?").arg(urlText))) == QMessageBox::Yes)
486         {
487             if (! QDir().mkpath(urlText))
488             {
489                 QMessageBox::warning(this, tr("The directory could not be created"),
490                                      QString(tr("The directory %1 could not be created.").arg(urlText)));
491             }
492         }
493     }
494 
495     if (QDir(urlText).exists())
496     {
497         QUrl url = QUrl::fromLocalFile(urlText);
498         if (url.isValid())
499             QDesktopServices::openUrl(url);
500     }
501 }
502 
handleCopyMenu(QPoint pos)503 void AboutDialog::handleCopyMenu(QPoint pos)
504 {
505     QTreeView * tree = qobject_cast<QTreeView *>(sender());
506     if (! tree)
507         return;
508 
509     QModelIndex index = tree->indexAt(pos);
510     if (! index.isValid())
511         return;
512 
513     QMenu * menu = new QMenu(this);
514 
515     if (ui->tabWidget->currentWidget() == ui->tab_plugins)
516     {
517 #ifdef Q_OS_MAC
518         QString show_in_str = tr("Show in Finder");
519 #else
520         QString show_in_str = tr("Show in Folder");
521 #endif
522         QAction * showInFolderAction = menu->addAction(show_in_str);
523         showInFolderAction->setData(VariantPointer<QTreeView>::asQVariant(tree));
524         connect(showInFolderAction, SIGNAL(triggered()), this, SLOT(showInFolderActionTriggered()));
525     }
526 
527     QAction * copyColumnAction = menu->addAction(tr("Copy"));
528     copyColumnAction->setData(VariantPointer<QTreeView>::asQVariant(tree));
529     connect(copyColumnAction, SIGNAL(triggered()), this, SLOT(copyActionTriggered()));
530 
531     QModelIndexList selectedRows = tree->selectionModel()->selectedRows();
532     QAction * copyRowAction = menu->addAction(tr("Copy Row(s)", "", selectedRows.count()));
533     copyRowAction->setData(VariantPointer<QTreeView>::asQVariant(tree));
534     connect(copyRowAction, SIGNAL(triggered()), this, SLOT(copyRowActionTriggered()));
535 
536     menu->popup(tree->viewport()->mapToGlobal(pos));
537 }
538 
showInFolderActionTriggered()539 void AboutDialog::showInFolderActionTriggered()
540 {
541     QAction * sendingAction = qobject_cast<QAction *>(sender());
542     if (!sendingAction)
543         return;
544 
545     QTreeView * tree = VariantPointer<QTreeView>::asPtr(sendingAction->data());
546     QModelIndexList selectedRows = tree->selectionModel()->selectedRows();
547 
548     foreach (QModelIndex index, selectedRows)
549     {
550         QString cf_path = tree->model()->index(index.row(), 3).data().toString();
551         desktop_show_in_folder(cf_path);
552     }
553 }
554 
copyRowActionTriggered()555 void AboutDialog::copyRowActionTriggered()
556 {
557     copyActionTriggered(true);
558 }
559 
copyActionTriggered(bool copyRow)560 void AboutDialog::copyActionTriggered(bool copyRow)
561 {
562     QAction * sendingAction = qobject_cast<QAction *>(sender());
563     if (! sendingAction)
564         return;
565 
566     QTreeView * tree = VariantPointer<QTreeView>::asPtr(sendingAction->data());
567 
568     QModelIndexList selIndeces = tree->selectionModel()->selectedIndexes();
569 
570     int copyColumn = -1;
571     if (! copyRow)
572     {
573         QMenu * menu = qobject_cast<QMenu *>(sendingAction->parentWidget());
574         if (menu)
575         {
576             QPoint menuPosOnTable = tree->mapFromGlobal(menu->pos());
577             QModelIndex clickedIndex = tree->indexAt(menuPosOnTable);
578             if (clickedIndex.isValid())
579                 copyColumn = clickedIndex.column();
580         }
581     }
582 
583     QString clipdata;
584     if (selIndeces.count() > 0)
585     {
586         int columnCount = tree->model()->columnCount();
587         QList<int> visitedRows;
588 
589         foreach(QModelIndex index, selIndeces)
590         {
591             if (visitedRows.contains(index.row()))
592                 continue;
593 
594             QStringList row;
595             if (copyRow)
596             {
597                 for (int cnt = 0; cnt < columnCount; cnt++)
598                 {
599                     QModelIndex dataIdx = tree->model()->index(index.row(), cnt);
600                     row << tree->model()->data(dataIdx).toString();
601                 }
602             }
603             else
604             {
605                 if (copyColumn < 0)
606                     copyColumn = index.column();
607 
608                 QModelIndex dataIdx = tree->model()->index(index.row(), copyColumn);
609                 row << tree->model()->data(dataIdx).toString();
610             }
611 
612             clipdata.append(row.join("\t\t").append("\n"));
613 
614             visitedRows << index.row();
615         }
616     }
617     QClipboard * clipBoard = QApplication::clipboard();
618     clipBoard->setText(clipdata);
619 }
620 
on_tblPlugins_doubleClicked(const QModelIndex & index)621 void AboutDialog::on_tblPlugins_doubleClicked(const QModelIndex &index)
622 {
623     const int path_col = 3;
624     if (index.column() != path_col) {
625         return;
626     }
627     const int row = index.row();
628     const QAbstractItemModel *model = index.model();
629     if (model->index(row, path_col).data().toString().contains(QRegExp(script_pattern))) {
630         QDesktopServices::openUrl(QUrl::fromLocalFile(model->index(row, path_col).data().toString()));
631     }
632 }
633