1 /* profile_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 #include <glib.h>
12
13 #include "wsutil/filesystem.h"
14 #include "wsutil/utf8_entities.h"
15 #include "epan/prefs.h"
16
17 #include <ui/qt/utils/qt_ui_utils.h>
18
19 #include "ui/profile.h"
20 #include "ui/recent.h"
21 #include "ui/last_open_dir.h"
22
23 #include <ui/qt/utils/variant_pointer.h>
24 #include <ui/qt/models/profile_model.h>
25
26 #include "profile_dialog.h"
27 #include <ui_profile_dialog.h>
28 #include "wireshark_application.h"
29 #include <ui/qt/utils/color_utils.h>
30 #include <ui/qt/simple_dialog.h>
31
32 #include <QBrush>
33 #include <QDir>
34 #include <QFont>
35 #include <QMessageBox>
36 #include <QPushButton>
37 #include <QTreeWidgetItem>
38 #include <QUrl>
39 #include <QComboBox>
40 #include <QLineEdit>
41 #include <QFileDialog>
42 #include <QStandardPaths>
43 #include <QKeyEvent>
44 #include <QMenu>
45 #include <QMessageBox>
46
47 #define PROFILE_EXPORT_PROPERTY "export"
48 #define PROFILE_EXPORT_ALL "all"
49 #define PROFILE_EXPORT_SELECTED "selected"
50
ProfileDialog(QWidget * parent)51 ProfileDialog::ProfileDialog(QWidget *parent) :
52 GeometryStateDialog(parent),
53 pd_ui_(new Ui::ProfileDialog),
54 ok_button_(Q_NULLPTR),
55 import_button_(Q_NULLPTR),
56 model_(Q_NULLPTR),
57 sort_model_(Q_NULLPTR)
58 {
59 pd_ui_->setupUi(this);
60 loadGeometry();
61 setWindowTitle(wsApp->windowTitleString(tr("Configuration Profiles")));
62
63 ok_button_ = pd_ui_->buttonBox->button(QDialogButtonBox::Ok);
64
65 // XXX - Use NSImageNameAddTemplate and NSImageNameRemoveTemplate to set stock
66 // icons on macOS.
67 // Are there equivalent stock icons on Windows?
68 pd_ui_->newToolButton->setStockIcon("list-add");
69 pd_ui_->deleteToolButton->setStockIcon("list-remove");
70 pd_ui_->copyToolButton->setStockIcon("list-copy");
71 #ifdef Q_OS_MAC
72 pd_ui_->newToolButton->setAttribute(Qt::WA_MacSmallSize, true);
73 pd_ui_->deleteToolButton->setAttribute(Qt::WA_MacSmallSize, true);
74 pd_ui_->copyToolButton->setAttribute(Qt::WA_MacSmallSize, true);
75 pd_ui_->hintLabel->setAttribute(Qt::WA_MacSmallSize, true);
76 #endif
77
78 import_button_ = pd_ui_->buttonBox->addButton(tr("Import", "noun"), QDialogButtonBox::ActionRole);
79
80 #ifdef HAVE_MINIZIP
81 export_button_ = pd_ui_->buttonBox->addButton(tr("Export", "noun"), QDialogButtonBox::ActionRole);
82
83 QMenu * importMenu = new QMenu(import_button_);
84 QAction * entry = importMenu->addAction(tr("from zip file"));
85 connect(entry, &QAction::triggered, this, &ProfileDialog::importFromZip);
86 entry = importMenu->addAction(tr("from directory"));
87 connect(entry, &QAction::triggered, this, &ProfileDialog::importFromDirectory);
88 import_button_->setMenu(importMenu);
89
90 QMenu * exportMenu = new QMenu(export_button_);
91 export_selected_entry_ = exportMenu->addAction(tr("%Ln selected personal profile(s)", "", 0));
92 export_selected_entry_->setProperty(PROFILE_EXPORT_PROPERTY, PROFILE_EXPORT_SELECTED);
93 connect(export_selected_entry_, &QAction::triggered, this, &ProfileDialog::exportProfiles);
94 entry = exportMenu->addAction(tr("all personal profiles"));
95 entry->setProperty(PROFILE_EXPORT_PROPERTY, PROFILE_EXPORT_ALL);
96 connect(entry, &QAction::triggered, this, &ProfileDialog::exportProfiles);
97 export_button_->setMenu(exportMenu);
98 #else
99 connect(import_button_, &QPushButton::clicked, this, &ProfileDialog::importFromDirectory);
100 #endif
101
102 resetTreeView();
103
104 /* Select the row for the currently selected profile or the first row if non is selected*/
105 selectProfile();
106
107 pd_ui_->cmbProfileTypes->addItems(ProfileSortModel::filterTypes());
108
109 connect (pd_ui_->cmbProfileTypes, SIGNAL(currentTextChanged(const QString &)),
110 this, SLOT(filterChanged(const QString &)));
111 connect (pd_ui_->lineProfileFilter, SIGNAL(textChanged(const QString &)),
112 this, SLOT(filterChanged(const QString &)));
113
114 currentItemChanged();
115
116 pd_ui_->profileTreeView->setFocus();
117 }
118
~ProfileDialog()119 ProfileDialog::~ProfileDialog()
120 {
121 delete pd_ui_;
122 empty_profile_list (TRUE);
123 }
124
keyPressEvent(QKeyEvent * evt)125 void ProfileDialog::keyPressEvent(QKeyEvent *evt)
126 {
127 if (pd_ui_->lineProfileFilter->hasFocus() && (evt->key() == Qt::Key_Enter || evt->key() == Qt::Key_Return))
128 return;
129 QDialog::keyPressEvent(evt);
130 }
131
selectProfile(QString profile)132 void ProfileDialog::selectProfile(QString profile)
133 {
134 if (profile.isEmpty())
135 profile = QString(get_profile_name());
136
137 int row = model_->findByName(profile);
138 QModelIndex idx = sort_model_->mapFromSource(model_->index(row, ProfileModel::COL_NAME));
139 if (idx.isValid())
140 pd_ui_->profileTreeView->selectRow(idx.row());
141 }
142
execAction(ProfileDialog::ProfileAction profile_action)143 int ProfileDialog::execAction(ProfileDialog::ProfileAction profile_action)
144 {
145 int ret = QDialog::Accepted;
146 QModelIndex item;
147
148 switch (profile_action) {
149 case ShowProfiles:
150 ret = exec();
151 break;
152 case NewProfile:
153 on_newToolButton_clicked();
154 ret = exec();
155 break;
156 case ImportZipProfile:
157 #ifdef HAVE_MINIZIP
158 importFromZip();
159 #endif
160 break;
161 case ImportDirProfile:
162 importFromDirectory();
163 break;
164 case ExportSingleProfile:
165 #ifdef HAVE_MINIZIP
166 exportProfiles();
167 #endif
168 break;
169 case ExportAllProfiles:
170 #ifdef HAVE_MINIZIP
171 exportProfiles(true);
172 #endif
173 break;
174 case EditCurrentProfile:
175 item = pd_ui_->profileTreeView->currentIndex();
176 if (item.isValid()) {
177 pd_ui_->profileTreeView->edit(item);
178 }
179 ret = exec();
180 break;
181 case DeleteCurrentProfile:
182 if (delete_current_profile()) {
183 wsApp->setConfigurationProfile (Q_NULLPTR);
184 }
185 break;
186 }
187 return ret;
188 }
189
selectedProfiles()190 QModelIndexList ProfileDialog::selectedProfiles()
191 {
192 QModelIndexList profiles;
193
194 foreach (QModelIndex idx, pd_ui_->profileTreeView->selectionModel()->selectedIndexes())
195 {
196 QModelIndex temp = sort_model_->mapToSource(idx);
197 if (! temp.isValid() || profiles.contains(temp) || temp.column() != ProfileModel::COL_NAME)
198 continue;
199
200 profiles << temp;
201 }
202
203 return profiles;
204 }
205
selectionChanged()206 void ProfileDialog::selectionChanged()
207 {
208 if (selectedProfiles().count() == 0)
209 pd_ui_->profileTreeView->selectRow(0);
210
211 updateWidgets();
212 }
213
updateWidgets()214 void ProfileDialog::updateWidgets()
215 {
216 bool enable_del = true;
217 bool enable_ok = true;
218 bool multiple = false;
219 bool enable_import = true;
220 int user_profiles = 0;
221
222 QString msg;
223 QModelIndex index = sort_model_->mapToSource(pd_ui_->profileTreeView->currentIndex());
224 QModelIndexList profiles = selectedProfiles();
225
226 /* Ensure that the index is always the name column */
227 if (index.column() != ProfileModel::COL_NAME)
228 index = index.sibling(index.row(), ProfileModel::COL_NAME);
229
230 /* check if more than one viable profile is selected, and inform the sorting model */
231 if (profiles.count() > 1)
232 multiple = true;
233
234 /* Check if user profiles have been selected and allow export if it is so */
235 for (int cnt = 0; cnt < profiles.count(); cnt++)
236 {
237 if (! profiles[cnt].data(ProfileModel::DATA_IS_GLOBAL).toBool() && ! profiles[cnt].data(ProfileModel::DATA_IS_DEFAULT).toBool())
238 user_profiles++;
239 }
240 if (model_->changesPending())
241 {
242 enable_import = false;
243 msg = tr("An import of profiles is not allowed, while changes are pending");
244 }
245 else if (model_->importPending())
246 {
247 enable_import = false;
248 msg = tr("An import is pending to be saved. Additional imports are not allowed");
249 }
250 import_button_->setToolTip(msg);
251 import_button_->setEnabled(enable_import);
252
253 #ifdef HAVE_MINIZIP
254 bool contains_user = false;
255 bool enable_export = false;
256
257 if (user_profiles > 0)
258 contains_user = true;
259
260 /* enable export if no changes are pending */
261 if (! model_->changesPending())
262 enable_export = true;
263
264 export_button_->setEnabled(enable_export);
265 if (! enable_export)
266 {
267 if (! contains_user)
268 export_button_->setToolTip(tr("An export of profiles is only allowed for personal profiles"));
269 else
270 export_button_->setToolTip(tr("An export of profiles is not allowed, while changes are pending"));
271 }
272 export_selected_entry_->setVisible(contains_user);
273 #endif
274
275 /* if the current profile is default with reset pending or a global one, deactivate delete */
276 if (! multiple)
277 {
278 if (index.isValid())
279 {
280 if (index.data(ProfileModel::DATA_IS_GLOBAL).toBool())
281 enable_del = false;
282 else if (index.data(ProfileModel::DATA_IS_DEFAULT).toBool() && model_->resetDefault())
283 enable_del = false;
284 }
285 else if (! index.isValid())
286 enable_del = false;
287 }
288
289 QString hintUrl;
290 msg.clear();
291 if (multiple)
292 {
293 /* multiple profiles are being selected, copy is no longer allowed */
294 pd_ui_->copyToolButton->setEnabled(false);
295
296 msg = tr("%Ln selected personal profile(s)", "", user_profiles);
297 pd_ui_->hintLabel->setText(msg);
298 #ifdef HAVE_MINIZIP
299 export_selected_entry_->setText(msg);
300 #endif
301 }
302 else
303 {
304 /* if only one profile is selected, display it's path in the hint label and activate link (if allowed) */
305 if (index.isValid())
306 {
307 QString temp = index.data(ProfileModel::DATA_PATH).toString();
308 if (index.data(ProfileModel::DATA_PATH_IS_NOT_DESCRIPTION).toBool() && QFileInfo(temp).isDir())
309 hintUrl = QUrl::fromLocalFile(temp).toString();
310 pd_ui_->hintLabel->setText(temp);
311 pd_ui_->hintLabel->setToolTip(index.data(Qt::ToolTipRole).toString());
312
313 if (! index.data(ProfileModel::DATA_IS_GLOBAL).toBool() && ! index.data(ProfileModel::DATA_IS_DEFAULT).toBool())
314 msg = tr("%Ln selected personal profile(s)", "", 1);
315 }
316
317 pd_ui_->copyToolButton->setEnabled(true);
318 #ifdef HAVE_MINIZIP
319 export_selected_entry_->setText(msg);
320 #endif
321 }
322
323 /* Ensure, that the ok button is disabled, if an invalid name is used or if duplicate global profiles exist */
324 if (model_ && model_->rowCount() > 0)
325 {
326 msg.clear();
327 for (int row = 0; row < model_->rowCount() && enable_ok; row++)
328 {
329 QModelIndex idx = model_->index(row, ProfileModel::COL_NAME);
330 QString name = idx.data().toString();
331
332 if (! ProfileModel::checkNameValidity(name, &msg))
333 {
334 if (idx == index || selectedProfiles().contains(idx))
335 {
336 hintUrl.clear();
337 pd_ui_->hintLabel->setText(msg);
338 }
339
340 enable_ok = false;
341 continue;
342 }
343
344 if (model_->checkInvalid(idx) || (! idx.data(ProfileModel::DATA_IS_GLOBAL).toBool() && model_->checkIfDeleted(idx)) )
345 {
346 if (idx == index)
347 hintUrl.clear();
348 enable_ok = false;
349 continue;
350 }
351
352 if (idx != index && idx.data().toString().compare(index.data().toString()) == 0)
353 {
354 if (idx.data(ProfileModel::DATA_IS_GLOBAL).toBool() == index.data(ProfileModel::DATA_IS_GLOBAL).toBool())
355 enable_ok = false;
356 }
357
358 QList<int> rows = model_->findAllByNameAndVisibility(name, idx.data(ProfileModel::DATA_IS_GLOBAL).toBool());
359 if (rows.count() > 1)
360 enable_ok = false;
361 }
362
363 if (enable_ok && ! model_->checkIfDeleted(index) && index.data(ProfileModel::DATA_STATUS).toInt() == PROF_STAT_CHANGED)
364 hintUrl.clear();
365 }
366
367 pd_ui_->hintLabel->setUrl(hintUrl);
368
369 /* ensure the name column is resized to it's content */
370 pd_ui_->profileTreeView->resizeColumnToContents(ProfileModel::COL_NAME);
371
372 pd_ui_->deleteToolButton->setEnabled(enable_del);
373 ok_button_->setEnabled(enable_ok);
374 }
375
currentItemChanged(const QModelIndex &,const QModelIndex &)376 void ProfileDialog::currentItemChanged(const QModelIndex &, const QModelIndex &)
377 {
378 updateWidgets();
379 }
380
on_newToolButton_clicked()381 void ProfileDialog::on_newToolButton_clicked()
382 {
383 pd_ui_->lineProfileFilter->setText("");
384 pd_ui_->cmbProfileTypes->setCurrentIndex(ProfileSortModel::AllProfiles);
385 sort_model_->setFilterString();
386
387 QModelIndex ridx = sort_model_->mapFromSource(model_->addNewProfile(tr("New profile")));
388 if (ridx.isValid())
389 {
390 pd_ui_->profileTreeView->setCurrentIndex(ridx);
391 pd_ui_->profileTreeView->scrollTo(ridx);
392 pd_ui_->profileTreeView->edit(ridx);
393 currentItemChanged();
394 }
395 else
396 updateWidgets();
397 }
398
on_deleteToolButton_clicked()399 void ProfileDialog::on_deleteToolButton_clicked()
400 {
401 QModelIndexList profiles = selectedProfiles();
402 if (profiles.count() <= 0)
403 return;
404
405 model_->deleteEntries(profiles);
406
407 bool isGlobal = model_->activeProfile().data(ProfileModel::DATA_IS_GLOBAL).toBool();
408 int row = model_->findByName(model_->activeProfile().data().toString());
409 /* If the active profile is deleted, the default is selected next */
410 if (row < 0)
411 row = 0;
412 QModelIndex newIdx = sort_model_->mapFromSource(model_->index(row, 0));
413 if (newIdx.data(ProfileModel::DATA_IS_GLOBAL).toBool() != isGlobal)
414 newIdx = sort_model_->mapFromSource(model_->index(0, 0));
415
416 pd_ui_->profileTreeView->setCurrentIndex(newIdx);
417
418 updateWidgets();
419 }
420
on_copyToolButton_clicked()421 void ProfileDialog::on_copyToolButton_clicked()
422 {
423 QModelIndexList profiles = selectedProfiles();
424 if (profiles.count() > 1)
425 return;
426
427 pd_ui_->lineProfileFilter->setText("");
428 pd_ui_->cmbProfileTypes->setCurrentIndex(ProfileSortModel::AllProfiles);
429 sort_model_->setFilterString();
430
431 QModelIndex current = pd_ui_->profileTreeView->currentIndex();
432 if (current.column() != ProfileModel::COL_NAME)
433 current = current.sibling(current.row(), ProfileModel::COL_NAME);
434
435 QModelIndex source = sort_model_->mapToSource(current);
436 QModelIndex ridx = model_->duplicateEntry(source);
437 if (ridx.isValid())
438 {
439 pd_ui_->profileTreeView->setCurrentIndex(sort_model_->mapFromSource(ridx));
440 pd_ui_->profileTreeView->scrollTo(sort_model_->mapFromSource(ridx));
441 pd_ui_->profileTreeView->edit(sort_model_->mapFromSource(ridx));
442 currentItemChanged();
443 }
444 else
445 updateWidgets();
446 }
447
on_buttonBox_accepted()448 void ProfileDialog::on_buttonBox_accepted()
449 {
450 bool write_recent = true;
451 bool item_data_removed = false;
452
453 QModelIndex index = sort_model_->mapToSource(pd_ui_->profileTreeView->currentIndex());
454
455 pd_ui_->buttonBox->setFocus();
456
457 QModelIndexList profiles = selectedProfiles();
458 if (profiles.count() <= 0)
459 index = QModelIndex();
460
461 QModelIndex default_item = sort_model_->mapFromSource(model_->index(0, ProfileModel::COL_NAME));
462 if (index.isValid() && index.column() != ProfileModel::COL_NAME)
463 index = index.sibling(index.row(), ProfileModel::COL_NAME);
464
465 if (default_item.data(ProfileModel::DATA_STATUS).toInt() == PROF_STAT_DEFAULT && model_->resetDefault())
466 {
467 // Reset Default profile.
468 GList *fl_entry = model_->at(0);
469 remove_from_profile_list(fl_entry);
470
471 // Don't write recent file if leaving the Default profile after this has been reset.
472 write_recent = !is_default_profile();
473
474 // Don't fetch profile data if removed.
475 item_data_removed = (index.row() == 0);
476 }
477
478 if (write_recent) {
479 /* Get the current geometry, before writing it to disk */
480 wsApp->emitAppSignal(WiresharkApplication::ProfileChanging);
481
482 /* Write recent file for current profile now because
483 * the profile may be renamed in apply_profile_changes() */
484 write_profile_recent();
485 }
486
487 gchar * err_msg = Q_NULLPTR;
488 if ((err_msg = apply_profile_changes()) != Q_NULLPTR) {
489 QMessageBox::critical(this, tr("Profile Error"),
490 err_msg,
491 QMessageBox::Ok);
492 g_free(err_msg);
493
494 model_->doResetModel();
495 return;
496 }
497
498 model_->doResetModel();
499
500 QString profileName;
501
502 if (! index.isValid() && model_->lastSetRow() >= 0)
503 {
504 QModelIndex original = model_->index(model_->lastSetRow(), ProfileModel::COL_NAME);
505 index = sort_model_->mapFromSource(original);
506 }
507
508 /* If multiple profiles are selected, do not change the selected profile */
509 if (index.isValid() && ! item_data_removed && profiles.count() <= 1)
510 {
511 profileName = model_->data(index).toString();
512 }
513
514 if (profileName.length() > 0 && model_->findByName(profileName) >= 0) {
515 // The new profile exists, change.
516 wsApp->setConfigurationProfile (profileName.toUtf8().constData(), FALSE);
517 } else if (!model_->activeProfile().isValid()) {
518 // The new profile does not exist, and the previous profile has
519 // been deleted. Change to the default profile.
520 wsApp->setConfigurationProfile (Q_NULLPTR, FALSE);
521 }
522 }
523
on_buttonBox_rejected()524 void ProfileDialog::on_buttonBox_rejected()
525 {
526 QString msg;
527 if (! model_->clearImported(&msg))
528 QMessageBox::critical(this, tr("Error"), msg);
529 }
530
on_buttonBox_helpRequested()531 void ProfileDialog::on_buttonBox_helpRequested()
532 {
533 wsApp->helpTopicAction(HELP_CONFIG_PROFILES_DIALOG);
534 }
535
dataChanged(const QModelIndex &)536 void ProfileDialog::dataChanged(const QModelIndex &)
537 {
538 pd_ui_->lineProfileFilter->setText("");
539 pd_ui_->cmbProfileTypes->setCurrentIndex(ProfileSortModel::AllProfiles);
540
541 pd_ui_->profileTreeView->setFocus();
542 if (model_->lastSetRow() >= 0)
543 {
544 QModelIndex original = model_->index(model_->lastSetRow(), ProfileModel::COL_NAME);
545 pd_ui_->profileTreeView->setCurrentIndex(sort_model_->mapFromSource(original));
546 pd_ui_->profileTreeView->selectRow(sort_model_->mapFromSource(original).row());
547 }
548
549 updateWidgets();
550 }
551
filterChanged(const QString & text)552 void ProfileDialog::filterChanged(const QString &text)
553 {
554 if (qobject_cast<QComboBox *>(sender()))
555 {
556 QComboBox * cmb = qobject_cast<QComboBox *>(sender());
557 sort_model_->setFilterType(static_cast<ProfileSortModel::FilterType>(cmb->currentIndex()));
558 }
559 else if (qobject_cast<QLineEdit *>(sender()))
560 sort_model_->setFilterString(text);
561
562 pd_ui_->profileTreeView->resizeColumnToContents(ProfileModel::COL_NAME);
563
564 QModelIndex active = sort_model_->mapFromSource(model_->activeProfile());
565 if (active.isValid())
566 pd_ui_->profileTreeView->setCurrentIndex(active);
567 }
568
569 #ifdef HAVE_MINIZIP
exportProfiles(bool exportAllPersonalProfiles)570 void ProfileDialog::exportProfiles(bool exportAllPersonalProfiles)
571 {
572 QAction * action = qobject_cast<QAction *>(sender());
573 if (action && action->property(PROFILE_EXPORT_PROPERTY).isValid())
574 exportAllPersonalProfiles = action->property(PROFILE_EXPORT_PROPERTY).toString().compare(PROFILE_EXPORT_ALL) == 0;
575
576 QModelIndexList items;
577 int skipped = 0;
578
579 if (! exportAllPersonalProfiles)
580 {
581 foreach (QModelIndex idx, selectedProfiles())
582 {
583 if (! idx.data(ProfileModel::DATA_IS_GLOBAL).toBool() && ! idx.data(ProfileModel::DATA_IS_DEFAULT).toBool())
584 items << idx;
585 else
586 skipped++;
587 }
588 }
589 else if (exportAllPersonalProfiles)
590 {
591 for (int cnt = 0; cnt < sort_model_->rowCount(); cnt++)
592 {
593 QModelIndex idx = sort_model_->index(cnt, ProfileModel::COL_NAME);
594 if (! idx.data(ProfileModel::DATA_IS_GLOBAL).toBool() && ! idx.data(ProfileModel::DATA_IS_DEFAULT).toBool())
595 items << sort_model_->mapToSource(idx);
596 }
597 }
598 if (items.count() == 0)
599 {
600 QString msg = tr("No profiles found for export");
601 if (skipped > 0)
602 msg.append(tr(", %Ln profile(s) skipped", "", skipped));
603 QMessageBox::critical(this, tr("Exporting profiles"), msg);
604 return;
605 }
606
607 QString zipFile = QFileDialog::getSaveFileName(this, tr("Select zip file for export"), lastOpenDir(), tr("Zip File (*.zip)"));
608
609 if (zipFile.length() > 0)
610 {
611 QFileInfo fi(zipFile);
612 if (fi.suffix().length() == 0 || fi.suffix().toLower().compare("zip") != 0)
613 zipFile += ".zip";
614
615 QString err;
616 if (model_->exportProfiles(zipFile, items, &err))
617 {
618 QString msg = tr("%Ln profile(s) exported", "", items.count());
619 if (skipped > 0)
620 msg.append(tr(", %Ln profile(s) skipped", "", skipped));
621 QMessageBox::information(this, tr("Exporting profiles"), msg);
622
623 QFileInfo zip(zipFile);
624 storeLastDir(zip.absolutePath());
625 }
626 else
627 {
628 QString msg = tr("An error has occurred while exporting profiles");
629 if (err.length() > 0)
630 msg.append(QString("\n\n%1: %3").arg(tr("Error")).arg(err));
631 QMessageBox::critical(this, tr("Exporting profiles"), msg);
632 }
633 }
634 }
635
importFromZip()636 void ProfileDialog::importFromZip()
637 {
638 QString zipFile = QFileDialog::getOpenFileName(this, tr("Select zip file for import"), lastOpenDir(), tr("Zip File (*.zip)"));
639
640 QFileInfo fi(zipFile);
641 if (! fi.exists())
642 return;
643
644 int skipped = 0;
645 QStringList import;
646 int count = model_->importProfilesFromZip(zipFile, &skipped, &import);
647
648 finishImport(fi, count, skipped, import);
649 }
650 #endif
651
importFromDirectory()652 void ProfileDialog::importFromDirectory()
653 {
654 QString importDir = QFileDialog::getExistingDirectory(this, tr("Select directory for import"), lastOpenDir());
655
656 QFileInfo fi(importDir);
657 if (! fi.isDir())
658 return;
659
660 int skipped = 0;
661 QStringList import;
662 int count = model_->importProfilesFromDir(importDir, &skipped, false, &import);
663
664 finishImport(fi, count, skipped, import);
665 }
666
finishImport(QFileInfo fi,int count,int skipped,QStringList import)667 void ProfileDialog::finishImport(QFileInfo fi, int count, int skipped, QStringList import)
668 {
669 QString msg;
670 QMessageBox::Icon icon;
671
672 if (count == 0 && skipped == 0)
673 {
674 icon = QMessageBox::Warning;
675 msg = tr("No profiles found for import in %1").arg(fi.fileName());
676 }
677 else
678 {
679 icon = QMessageBox::Information;
680 msg = tr("%Ln profile(s) imported", "", count);
681 if (skipped > 0)
682 msg.append(tr(", %Ln profile(s) skipped", "", skipped));
683 }
684
685 storeLastDir(fi.absolutePath());
686
687 if (count > 0)
688 {
689 import.sort();
690 resetTreeView();
691 model_->markAsImported(import);
692 int rowFirstImported = model_->findByName(import.at(0));
693 QModelIndex idx = sort_model_->mapFromSource(model_->index(rowFirstImported, ProfileModel::COL_NAME));
694 pd_ui_->profileTreeView->selectRow(idx.isValid() ? idx.row() : 0);
695 }
696
697 QMessageBox msgBox(icon, tr("Importing profiles"), msg, QMessageBox::Ok, this);
698 msgBox.exec();
699
700 updateWidgets();
701 }
702
lastOpenDir()703 QString ProfileDialog::lastOpenDir()
704 {
705 QString result;
706
707 switch (prefs.gui_fileopen_style) {
708
709 case FO_STYLE_LAST_OPENED:
710 /* The user has specified that we should start out in the last directory
711 we looked in. If we've already opened a file, use its containing
712 directory, if we could determine it, as the directory, otherwise
713 use the "last opened" directory saved in the preferences file if
714 there was one. */
715 /* This is now the default behaviour in file_selection_new() */
716 result = QString(get_last_open_dir());
717 break;
718
719 case FO_STYLE_SPECIFIED:
720 /* The user has specified that we should always start out in a
721 specified directory; if they've specified that directory,
722 start out by showing the files in that dir. */
723 if (prefs.gui_fileopen_dir[0] != '\0')
724 result = QString(prefs.gui_fileopen_dir);
725 break;
726 }
727
728 QDir ld(result);
729 if (ld.exists())
730 return result;
731
732 return QString();
733 }
734
storeLastDir(QString dir)735 void ProfileDialog::storeLastDir(QString dir)
736 {
737 if (wsApp && dir.length() > 0)
738 wsApp->setLastOpenDir(qUtf8Printable(dir));
739 }
740
resetTreeView()741 void ProfileDialog::resetTreeView()
742 {
743 if (model_)
744 {
745 pd_ui_->profileTreeView->setModel(Q_NULLPTR);
746 sort_model_->setSourceModel(Q_NULLPTR);
747 model_->disconnect();
748 if (pd_ui_->profileTreeView->selectionModel())
749 pd_ui_->profileTreeView->selectionModel()->disconnect();
750 delete sort_model_;
751 delete model_;
752 }
753
754 model_ = new ProfileModel(pd_ui_->profileTreeView);
755 sort_model_ = new ProfileSortModel(pd_ui_->profileTreeView);
756 sort_model_->setSourceModel(model_);
757 pd_ui_->profileTreeView->setModel(sort_model_);
758
759 connect(model_, &ProfileModel::itemChanged, this, &ProfileDialog::dataChanged, Qt::QueuedConnection);
760 QItemSelectionModel *selModel = pd_ui_->profileTreeView->selectionModel();
761 connect(selModel, &QItemSelectionModel::currentChanged,
762 this, &ProfileDialog::currentItemChanged, Qt::QueuedConnection);
763 connect(selModel, SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)),
764 this, SLOT(selectionChanged()));
765
766 selectionChanged();
767
768 if (sort_model_->columnCount() <= 1)
769 pd_ui_->profileTreeView->header()->hide();
770 else
771 {
772 pd_ui_->profileTreeView->header()->setStretchLastSection(false);
773 pd_ui_->profileTreeView->header()->setSectionResizeMode(ProfileModel::COL_NAME, QHeaderView::Stretch);
774 }
775 }
776