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