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