diff options
author | Roland Knall <rknall@gmail.com> | 2019-07-29 14:06:58 +0200 |
---|---|---|
committer | Roland Knall <rknall@gmail.com> | 2019-08-02 21:38:02 +0000 |
commit | f2591878034f5380de4cef5ed9532942ea16469c (patch) | |
tree | aacb9d6bf7ece9b7291f3f1f0df8d871b21acff7 /ui/qt | |
parent | 66747a982b93ac6332260b23119d11072a51c5bb (diff) |
Qt: Update UI for profiles and handle export/import properly
This patchset ensures a 1:1 replacement of the old 3.0 version of the profiles
dialog. It is a major bugfix for the new version in case of handling creating/
deleting and adding profiles.
Delete can be performed on multiple profiles now, by selecting the profiles
which need to be deleted.
Import/Export functionality has been overhauled to follow these rules:
* No imports while changes are pending, due to datamodel sanity
* Export for Default Profile and Global Profiles is not possible
* Either all personal profiles can be selected or individually choosen ones
* Use last directory and store it properly
* Imports can be cancelled
* Only one import is allowed at a time (but it can contain as many profiles as needed)
Change-Id: Ie2fccd397202ec06976d764734437284f464409a
Reviewed-on: https://code.wireshark.org/review/34123
Petri-Dish: Roland Knall <rknall@gmail.com>
Tested-by: Petri Dish Buildbot
Reviewed-by: Stig Bjørlykke <stig@bjorlykke.org>
Reviewed-by: Roland Knall <rknall@gmail.com>
Diffstat (limited to 'ui/qt')
-rw-r--r-- | ui/qt/main_status_bar.cpp | 59 | ||||
-rw-r--r-- | ui/qt/models/profile_model.cpp | 318 | ||||
-rw-r--r-- | ui/qt/models/profile_model.h | 46 | ||||
-rw-r--r-- | ui/qt/profile_dialog.cpp | 401 | ||||
-rw-r--r-- | ui/qt/profile_dialog.h | 14 | ||||
-rw-r--r-- | ui/qt/profile_dialog.ui | 5 | ||||
-rw-r--r-- | ui/qt/widgets/profile_tree_view.cpp | 60 | ||||
-rw-r--r-- | ui/qt/widgets/profile_tree_view.h | 14 | ||||
-rw-r--r-- | ui/qt/wireshark_en.ts | 21 |
9 files changed, 713 insertions, 225 deletions
diff --git a/ui/qt/main_status_bar.cpp b/ui/qt/main_status_bar.cpp index b658929921..1c5e08be0a 100644 --- a/ui/qt/main_status_bar.cpp +++ b/ui/qt/main_status_bar.cpp @@ -545,6 +545,7 @@ void MainStatusBar::showProfileMenu(const QPoint &global_pos, Qt::MouseButton bu if ( ! idx.isValid() ) continue; + QAction * pa = Q_NULLPTR; QString name = idx.data().toString(); if ( idx.data(ProfileModel::DATA_IS_DEFAULT).toBool() ) @@ -590,47 +591,47 @@ void MainStatusBar::showProfileMenu(const QPoint &global_pos, Qt::MouseButton bu profile_menu_.setTitle(tr("Switch to")); QMenu ctx_menu_; - QAction * action = ctx_menu_.addAction(tr("Manage Profiles" UTF8_HORIZONTAL_ELLIPSIS)); + QAction * action = ctx_menu_.addAction(tr("Manage Profiles" UTF8_HORIZONTAL_ELLIPSIS), this, SLOT(manageProfile())); action->setProperty("dialog_action_", (int)ProfileDialog::ShowProfiles); - connect(action, SIGNAL(triggered()), this, SLOT(manageProfile())); + + ctx_menu_.addSeparator(); + action = ctx_menu_.addAction(tr("New" UTF8_HORIZONTAL_ELLIPSIS), this, SLOT(manageProfile())); + action->setProperty("dialog_action_", (int)ProfileDialog::NewProfile); + action = ctx_menu_.addAction(tr("Edit" UTF8_HORIZONTAL_ELLIPSIS), this, SLOT(manageProfile())); + action->setProperty("dialog_action_", (int)ProfileDialog::EditCurrentProfile); + action->setEnabled(enable_edit); + action = ctx_menu_.addAction(tr("Delete"), this, SLOT(manageProfile())); + action->setProperty("dialog_action_", (int)ProfileDialog::DeleteCurrentProfile); + action->setEnabled(enable_edit); + ctx_menu_.addSeparator(); + #ifdef HAVE_MINIZIP QMenu * importMenu = new QMenu(tr("Import")); - action = importMenu->addAction(tr(UTF8_HORIZONTAL_ELLIPSIS" from Zip")); + action = importMenu->addAction(tr("from zip file"), this, SLOT(manageProfile())); action->setProperty("dialog_action_", (int)ProfileDialog::ImportZipProfile); - connect(action, SIGNAL(triggered()), this, SLOT(manageProfile())); - action = importMenu->addAction(tr(UTF8_HORIZONTAL_ELLIPSIS" from Directory")); + action = importMenu->addAction(tr("from directory"), this, SLOT(manageProfile())); action->setProperty("dialog_action_", (int)ProfileDialog::ImportDirProfile); - connect(action, SIGNAL(triggered()), this, SLOT(manageProfile())); ctx_menu_.addMenu(importMenu); - QMenu * exportMenu = new QMenu(tr("Export")); - action = exportMenu->addAction(tr(UTF8_HORIZONTAL_ELLIPSIS" selected entry")); - action->setProperty("dialog_action_", (int)ProfileDialog::ExportSingleProfile); - action->setEnabled(enable_edit); - connect(action, SIGNAL(triggered()), this, SLOT(manageProfile())); - action = exportMenu->addAction(tr(UTF8_HORIZONTAL_ELLIPSIS" all user profiles")); - action->setProperty("dialog_action_", (int)ProfileDialog::ExportAllProfiles); - connect(action, SIGNAL(triggered()), this, SLOT(manageProfile())); - ctx_menu_.addMenu(exportMenu); + if ( model.userProfilesExist() ) + { + QMenu * exportMenu = new QMenu(tr("Export")); + if ( enable_edit ) + { + action = exportMenu->addAction(tr("selected personal profile"), this, SLOT(manageProfile())); + action->setProperty("dialog_action_", (int)ProfileDialog::ExportSingleProfile); + action->setEnabled(enable_edit); + } + action = exportMenu->addAction(tr("all personal profiles"), this, SLOT(manageProfile())); + action->setProperty("dialog_action_", (int)ProfileDialog::ExportAllProfiles); + ctx_menu_.addMenu(exportMenu); + } #else - action = ctx_menu_.addAction(tr("Import" UTF8_HORIZONTAL_ELLIPSIS)); + action = ctx_menu_.addAction(tr("Import"), this, SLOT(manageProfile())); action->setProperty("dialog_action_", (int)ProfileDialog::ImportDirProfile); - connect(action, SIGNAL(triggered()), this, SLOT(manageProfile())); #endif ctx_menu_.addSeparator(); - action = ctx_menu_.addAction(tr("New" UTF8_HORIZONTAL_ELLIPSIS)); - action->setProperty("dialog_action_", (int)ProfileDialog::NewProfile); - connect(action, SIGNAL(triggered()), this, SLOT(manageProfile())); - action = ctx_menu_.addAction(tr("Edit" UTF8_HORIZONTAL_ELLIPSIS)); - action->setProperty("dialog_action_", (int)ProfileDialog::EditCurrentProfile); - action->setEnabled(enable_edit); - connect(action, SIGNAL(triggered()), this, SLOT(manageProfile())); - action = ctx_menu_.addAction(tr("Delete")); - action->setProperty("dialog_action_", (int)ProfileDialog::DeleteCurrentProfile); - action->setEnabled(enable_edit); - connect(action, SIGNAL(triggered()), this, SLOT(manageProfile())); - ctx_menu_.addSeparator(); ctx_menu_.addMenu(&profile_menu_); ctx_menu_.exec(global_pos); diff --git a/ui/qt/models/profile_model.cpp b/ui/qt/models/profile_model.cpp index 4e7b280016..cfc563d1c3 100644 --- a/ui/qt/models/profile_model.cpp +++ b/ui/qt/models/profile_model.cpp @@ -9,6 +9,8 @@ #include "config.h" +#include <errno.h> + #include "glib.h" #include "ui/profile.h" #include "wsutil/filesystem.h" @@ -80,6 +82,16 @@ void ProfileSortModel::setFilterString(QString txt) invalidateFilter(); } +QStringList ProfileSortModel::filterTypes() +{ + QMap<int, QString> filter_types_; + filter_types_.insert(ProfileSortModel::AllProfiles, tr("All profiles")); + filter_types_.insert(ProfileSortModel::PersonalProfiles, tr("Personal profiles")); + filter_types_.insert(ProfileSortModel::GlobalProfiles, tr("Global profiles")); + + return filter_types_.values(); +} + bool ProfileSortModel::filterAcceptsRow(int source_row, const QModelIndex &) const { bool accept = true; @@ -111,6 +123,9 @@ ProfileModel::ProfileModel(QObject * parent) : set_profile_ = get_profile_name(); reset_default_ = false; + profiles_imported_ = false; + + last_set_row_ = 0; loadProfiles(); } @@ -183,6 +198,24 @@ bool ProfileModel::changesPending() const return pending; } +bool ProfileModel::importPending() const +{ + return profiles_imported_; +} + +bool ProfileModel::userProfilesExist() const +{ + bool user_exists = false; + for ( int cnt = 0; cnt < rowCount() && ! user_exists; cnt++ ) + { + QModelIndex idx = index(cnt, ProfileModel::COL_NAME); + if ( ! idx.data(ProfileModel::DATA_IS_GLOBAL).toBool() && ! idx.data(ProfileModel::DATA_IS_DEFAULT).toBool() ) + user_exists = true; + } + + return user_exists; +} + int ProfileModel::rowCount(const QModelIndex &) const { return profiles_.count(); @@ -204,7 +237,7 @@ profile_def * ProfileModel::guard(int row) const return Q_NULLPTR; } - return profiles_.at(row); + return profiles_.value(row, Q_NULLPTR); } QVariant ProfileModel::dataDisplay(const QModelIndex &index) const @@ -312,6 +345,8 @@ QVariant ProfileModel::dataPath(const QModelIndex &index) const return gchar_free_to_qstring(get_persconffile_path("", FALSE)); else return tr("Resetting to default"); + case PROF_STAT_IMPORT: + return tr("Imported profile"); case PROF_STAT_EXISTS: { QString profile_path; @@ -336,23 +371,39 @@ QVariant ProfileModel::dataPath(const QModelIndex &index) const return tr("Created from default settings"); } case PROF_STAT_CHANGED: - if (prof->reference) - return QString("%1 %2").arg(tr("Renamed from: ")).arg(prof->reference); - break; + { + QString msg; + if ( ! ProfileModel::checkNameValidity(QString(prof->name), &msg) ) + return msg; + + if (prof->reference) + return QString("%1 %2").arg(tr("Renamed from: ")).arg(prof->reference); + + return QVariant(); + } case PROF_STAT_COPY: if (prof->reference) { + ProfileModel * nthis = const_cast<ProfileModel *>(this); + int row = nthis->findByNameAndVisibility(prof->reference, false, true); + profile_def * ref = Q_NULLPTR; + if ( row > 0 && row != index.row() ) + ref = guard(row); + else + row = -1; + + /* Security blanket. It should not happen with PROF_STAT_COPY, but just in case */ + if ( ! prof->reference ) + return tr("Created from default settings"); + QString msg = QString("%1 %2").arg(tr("Copied from: ")).arg(prof->reference); if ( profile_exists(prof->reference, TRUE) && prof->from_global ) msg.append(QString(" (%1)").arg(tr("system provided"))); - else - { - ProfileModel * nthis = const_cast<ProfileModel *>(this); - int row = nthis->findByNameAndVisibility(prof->reference); - if ( row < 0 ) - msg.append(QString(" (%1)").arg(tr("deleted"))); - } + else if ( row > 0 && ref && QString(ref->name).compare(prof->reference) != 0 ) + msg.append(QString(" (%1 %2)").arg(tr("renamed to")).arg(ref->name)); + else if ( row < 0 ) + msg.append(QString(" (%1)").arg(tr("deleted"))); return msg; } @@ -377,16 +428,12 @@ QVariant ProfileModel::data(const QModelIndex &index, int role) const { case Qt::DisplayRole: return dataDisplay(index); - break; case Qt::FontRole: return dataFontRole(index); - break; case Qt::BackgroundColorRole: return dataBackgroundRole(index); - break; case Qt::ToolTipRole: return dataToolTipRole(index); - break; case ProfileModel::DATA_STATUS: return qVariantFromValue(prof->status); case ProfileModel::DATA_IS_DEFAULT: @@ -407,15 +454,16 @@ QVariant ProfileModel::data(const QModelIndex &index, int role) const } return qVariantFromValue(false); } - break; case ProfileModel::DATA_PATH: return dataPath(index); - break; - + case ProfileModel::DATA_INDEX_VALUE_IS_URL: + if ( index.column() <= ProfileModel::COL_TYPE ) + return qVariantFromValue(false); + return qVariantFromValue(true); case ProfileModel::DATA_PATH_IS_NOT_DESCRIPTION: if ( prof->status == PROF_STAT_NEW || prof->status == PROF_STAT_COPY || ( prof->status == PROF_STAT_DEFAULT && reset_default_ ) - || prof->status == PROF_STAT_CHANGED ) + || prof->status == PROF_STAT_CHANGED || prof->status == PROF_STAT_IMPORT ) return qVariantFromValue(false); else return qVariantFromValue(true); @@ -471,21 +519,24 @@ int ProfileModel::findByName(QString name) return row; } -int ProfileModel::findByNameAndVisibility(QString name, bool isGlobal) +int ProfileModel::findByNameAndVisibility(QString name, bool isGlobal, bool searchReference) { - QList<int> result = findAllByNameAndVisibility(name, isGlobal); + QList<int> result = findAllByNameAndVisibility(name, isGlobal, searchReference); return result.count() == 0 ? -1 : result.at(0); } -QList<int> ProfileModel::findAllByNameAndVisibility(QString name, bool isGlobal) +QList<int> ProfileModel::findAllByNameAndVisibility(QString name, bool isGlobal, bool searchReference) { QList<int> result; for ( int cnt = 0; cnt < profiles_.count(); cnt++ ) { profile_def * prof = guard(cnt); - if ( prof && static_cast<bool>(prof->is_global) == isGlobal && name.compare(prof->name) == 0 ) - result << cnt; + if ( prof && static_cast<bool>(prof->is_global) == isGlobal ) + { + if ( name.compare(prof->name) == 0 || ( searchReference && name.compare(prof->reference) == 0 ) ) + result << cnt; + } } return result; @@ -508,7 +559,7 @@ QModelIndex ProfileModel::addNewProfile(QString name) return index(findByName(newName), COL_NAME); } -QModelIndex ProfileModel::duplicateEntry(QModelIndex idx) +QModelIndex ProfileModel::duplicateEntry(QModelIndex idx, int new_status) { if ( ! idx.isValid() ) return QModelIndex(); @@ -517,17 +568,42 @@ QModelIndex ProfileModel::duplicateEntry(QModelIndex idx) if ( ! prof ) return QModelIndex(); + if ( new_status < 0 || new_status > PROF_STAT_COPY ) + new_status = PROF_STAT_COPY; + + if ( prof->status == PROF_STAT_COPY && ! prof->from_global ) + { + int row = findByNameAndVisibility(prof->reference, false); + profile_def * copyParent = guard(row); + if ( copyParent && copyParent->status == PROF_STAT_NEW ) + return duplicateEntry(index(row, ProfileModel::COL_NAME), PROF_STAT_NEW); + } + QString parent = prof->name; - if ( ! prof->is_global && prof->status != PROF_STAT_CHANGED && prof->status != PROF_STAT_NEW ) + if ( prof->status == PROF_STAT_NEW ) + { + if ( QString(prof->reference).length() > 0 ) + parent = QString(prof->reference); + } + else if ( ! prof->is_global && prof->status != PROF_STAT_CHANGED ) parent = get_profile_parent (prof->name); + else if ( prof->status == PROF_STAT_CHANGED ) + parent = prof->reference; + + QString parentName = parent; + if ( prof->status == PROF_STAT_CHANGED ) + parentName = prof->name; + + if ( parent.length() == 0 ) + return QModelIndex(); QString new_name; if (prof->is_global && ! profile_exists (parent.toUtf8().constData(), FALSE)) new_name = QString(prof->name); else - new_name = QString("%1 (%2)").arg(parent).arg(tr("copy", "noun")); + new_name = QString("%1 (%2)").arg(parentName).arg(tr("copy", "noun")); - if ( findByNameAndVisibility(new_name) >= 0 ) + if ( findByNameAndVisibility(new_name) >= 0 && new_name.length() > 0 ) { int cnt = 1; QString copyName = new_name; @@ -543,7 +619,10 @@ QModelIndex ProfileModel::duplicateEntry(QModelIndex idx) if ( new_name.compare(QString(new_name.toUtf8().constData())) != 0 && !prof->is_global ) return QModelIndex(); - add_to_profile_list(new_name.toUtf8().constData(), parent.toUtf8().constData(), PROF_STAT_COPY, FALSE, prof->from_global); + if ( new_status == PROF_STAT_COPY && prof->status == PROF_STAT_NEW ) + new_status = PROF_STAT_NEW; + + add_to_profile_list(new_name.toUtf8().constData(), parent.toUtf8().constData(), new_status, FALSE, prof->from_global); loadProfiles(); int row = findByNameAndVisibility(new_name, false); @@ -558,29 +637,57 @@ void ProfileModel::deleteEntry(QModelIndex idx) if ( ! idx.isValid() ) return; - profile_def * prof = guard(idx.row()); - if ( ! prof ) - return; + QModelIndexList temp; + temp << idx; + deleteEntries(temp); +} - if ( prof->is_global ) - return; +void ProfileModel::deleteEntries(QModelIndexList idcs) +{ + bool changes = false; - if ( prof->status == PROF_STAT_DEFAULT ) + QList<int> indeces; + foreach ( QModelIndex idx, idcs ) { - emit layoutAboutToBeChanged(); - reset_default_ = ! reset_default_; - emit dataChanged(index(0, 0), index(rowCount(), columnCount())); - emit layoutChanged(); + if ( ! indeces.contains(idx.row()) && ! idx.data(ProfileModel::DATA_IS_GLOBAL).toBool() ) + indeces << idx.row(); } - else + /* Security blanket. This ensures, that we start deleting from the end and do not get any issues iterating the list */ + std::sort(indeces.begin(), indeces.end(), std::less<int>()); + + foreach ( int row, indeces ) { - GList * fl_entry = entry(prof); - if ( fl_entry ) + profile_def * prof = guard(row); + if ( ! prof ) + continue; + + if ( prof->is_global ) + continue; + + if ( prof->status == PROF_STAT_DEFAULT ) + { + reset_default_ = ! reset_default_; + } + else { - remove_from_profile_list(fl_entry); - loadProfiles(); + GList * fl_entry = entry(prof); + if ( fl_entry ) + { + changes = true; + remove_from_profile_list(fl_entry); + } } } + + if ( changes ) + loadProfiles(); + + if ( reset_default_ ) + { + emit layoutAboutToBeChanged(); + emit dataChanged(index(0, 0), index(rowCount(), columnCount())); + emit layoutChanged(); + } } bool ProfileModel::resetDefault() const @@ -588,9 +695,12 @@ bool ProfileModel::resetDefault() const return reset_default_; } -void ProfileModel::doResetModel() +void ProfileModel::doResetModel(bool reset_import) { reset_default_ = false; + if ( reset_import ) + profiles_imported_ = false; + loadProfiles(); } @@ -613,6 +723,8 @@ QModelIndex ProfileModel::activeProfile() const bool ProfileModel::setData(const QModelIndex &idx, const QVariant &value, int role) { + last_set_row_ = -1; + if ( role != Qt::EditRole || ! idx.isValid() ) return false; @@ -623,6 +735,8 @@ bool ProfileModel::setData(const QModelIndex &idx, const QVariant &value, int ro if ( ! prof || prof->status == PROF_STAT_DEFAULT ) return false; + last_set_row_ = idx.row(); + QString current(prof->name); if ( current.compare(value.toString()) != 0 ) { @@ -634,6 +748,7 @@ bool ProfileModel::setData(const QModelIndex &idx, const QVariant &value, int ro } else if (prof->status == PROF_STAT_EXISTS) { prof->status = PROF_STAT_CHANGED; } + emit itemChanged(idx); } loadProfiles(); @@ -641,6 +756,11 @@ bool ProfileModel::setData(const QModelIndex &idx, const QVariant &value, int ro return true; } +int ProfileModel::lastSetRow() const +{ + return last_set_row_; +} + bool ProfileModel::copyTempToProfile(QString tempPath, QString profilePath) { QDir profileDir(profilePath); @@ -672,10 +792,29 @@ bool ProfileModel::copyTempToProfile(QString tempPath, QString profilePath) return false; } +QFileInfoList ProfileModel::uniquePaths(QFileInfoList lst) +{ + QStringList files; + QFileInfoList newLst; + + foreach ( QFileInfo entry, lst ) + { + if ( ! files.contains(entry.absoluteFilePath()) ) + { + if ( entry.exists() && entry.isDir() ) + { + newLst << entry.absoluteFilePath(); + files << entry.absoluteFilePath(); + } + } + } + + return newLst; +} + QFileInfoList ProfileModel::filterProfilePath(QString path, QFileInfoList ent, bool fromZip) { QFileInfoList result = ent; - QDir temp(path); temp.setSorting(QDir::Name); temp.setFilter(QDir::Dirs | QDir::NoSymLinks | QDir::NoDotAndDotDot); @@ -696,9 +835,14 @@ QFileInfoList ProfileModel::filterProfilePath(QString path, QFileInfoList ent, b } if ( found ) + { result.append(entry); + } else - result.append(filterProfilePath(entry.absoluteFilePath(), result, fromZip)); + { + if ( path.compare(entry.absoluteFilePath()) != 0 ) + result.append(filterProfilePath(entry.absoluteFilePath(), result, fromZip)); + } } return result; @@ -756,36 +900,35 @@ bool ProfileModel::exportProfiles(QString filename, QModelIndexList items, QStri /* This check runs BEFORE the file has been unzipped! */ bool ProfileModel::acceptFile(QString fileName, int fileSize) { - if ( fileName.contains(".") || fileName.startsWith("_") ) + if ( fileName.toLower().endsWith(".zip") ) return false; if ( fileSize > 1024 * 512 ) return false; - /* config_file_exists_with_entries cannot be used, due to the fact, that the file has not been extracted yet */ - return true; } -int ProfileModel::importProfilesFromZip(QString filename, int * skippedCnt) +int ProfileModel::importProfilesFromZip(QString filename, int * skippedCnt, QStringList *result) { QTemporaryDir dir; #if 0 dir.setAutoRemove(false); + g_printerr("Temp dir for unzip: %s\n", dir.path().toUtf8().constData()); #endif int cnt = 0; if ( dir.isValid() ) { WireSharkZipHelper::unzip(filename, dir.path(), &ProfileModel::acceptFile); - cnt = importProfilesFromDir(dir.path(), skippedCnt, true); + cnt = importProfilesFromDir(dir.path(), skippedCnt, true, result); } return cnt; } #endif -int ProfileModel::importProfilesFromDir(QString dirname, int * skippedCnt, bool fromZip) +int ProfileModel::importProfilesFromDir(QString dirname, int * skippedCnt, bool fromZip, QStringList *result) { int count = 0; int skipped = 0; @@ -793,7 +936,8 @@ int ProfileModel::importProfilesFromDir(QString dirname, int * skippedCnt, bool QDir dir(dirname); if ( dir.exists() ) { - QFileInfoList entries = filterProfilePath(dirname, QFileInfoList(), fromZip); + QFileInfoList entries = uniquePaths(filterProfilePath(dirname, QFileInfoList(), fromZip)); + *skippedCnt = 0; int entryCount = 0; foreach ( QFileInfo fentry, entries ) @@ -809,6 +953,9 @@ int ProfileModel::importProfilesFromDir(QString dirname, int * skippedCnt, bool continue; } + if ( result ) + *result << fentry.fileName(); + if ( copyTempToProfile(tempPath, profilePath) ) { count++; @@ -818,7 +965,10 @@ int ProfileModel::importProfilesFromDir(QString dirname, int * skippedCnt, bool } if ( count > 0 ) + { + profiles_imported_ = true; loadProfiles(); + } if ( skippedCnt ) *skippedCnt = skipped; @@ -826,6 +976,57 @@ int ProfileModel::importProfilesFromDir(QString dirname, int * skippedCnt, bool return count; } +void ProfileModel::markAsImported(QStringList importedItems) +{ + if ( importedItems.count() <= 0 ) + return; + + profiles_imported_ = true; + + foreach ( QString item, importedItems ) + { + int row = findByNameAndVisibility(item, false); + profile_def * prof = guard(row); + if ( ! prof ) + continue; + + prof->status = PROF_STAT_IMPORT; + prof->from_global = true; + } +} + +bool ProfileModel::clearImported(QString *msg) +{ + QList<int> rows; + bool result = true; + for ( int cnt = 0; cnt < rowCount(); cnt++ ) + { + profile_def * prof = guard(cnt); + if ( prof && prof->status == PROF_STAT_IMPORT && ! rows.contains(cnt) ) + rows << cnt; + } + /* Security blanket. This ensures, that we start deleting from the end and do not get any issues iterating the list */ + std::sort(rows.begin(), rows.end(), std::less<int>()); + + char * ret_path = Q_NULLPTR; + for ( int cnt = 0; cnt < rows.count() && result; cnt++ ) + { + int row = rows.at(cnt); + if ( delete_persconffile_profile ( index(row, ProfileModel::COL_NAME).data().toString().toUtf8().constData(), &ret_path ) != 0 ) + { + if ( msg ) + { + QString errmsg = QString("%1\n\"%2\":\n%3.").arg(tr("Can't delete profile directory")).arg(ret_path).arg(g_strerror(errno)); + msg->append(errmsg); + } + + result = false; + } + } + + return result; +} + bool ProfileModel::checkNameValidity(QString name, QString *msg) { QString message; @@ -845,17 +1046,18 @@ bool ProfileModel::checkNameValidity(QString name, QString *msg) if ( name.contains(invalid_dir_chars[cnt]) ) invalid = true; } +#ifdef _WIN32 if ( invalid ) { -#ifdef _WIN32 message = tr("A profile name cannot contain the following characters: %1").arg(msgChars); -#else - message = tr("A profile name cannot contain the '/' character."); -#endif } if ( message.isEmpty() && ( name.startsWith('.') || name.endsWith('.') ) ) message = tr("A profile cannot start or end with a period (.)"); +#else + if ( invalid ) + message = tr("A profile name cannot contain the '/' character."); +#endif if (! message.isEmpty()) { if (msg) diff --git a/ui/qt/models/profile_model.h b/ui/qt/models/profile_model.h index 620f82fedf..a49a54c8b3 100644 --- a/ui/qt/models/profile_model.h +++ b/ui/qt/models/profile_model.h @@ -30,13 +30,15 @@ public: enum FilterType { AllProfiles = 0, - GlobalProfiles, - PersonalProfiles + PersonalProfiles, + GlobalProfiles }; void setFilterType(FilterType ft); void setFilterString(QString txt = QString()); + static QStringList filterTypes(); + protected: virtual bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const; virtual bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const; @@ -44,7 +46,6 @@ protected: private: FilterType ft_; QString ftext_; - }; class ProfileModel : public QAbstractTableModel @@ -66,7 +67,8 @@ public: DATA_IS_GLOBAL, DATA_IS_SELECTED, DATA_PATH, - DATA_PATH_IS_NOT_DESCRIPTION + DATA_PATH_IS_NOT_DESCRIPTION, + DATA_INDEX_VALUE_IS_URL } data_values_; // QAbstractItemModel interface @@ -78,12 +80,13 @@ public: virtual Qt::ItemFlags flags(const QModelIndex &index) const; void deleteEntry(QModelIndex idx); + void deleteEntries(QModelIndexList idcs); int findByName(QString name); QModelIndex addNewProfile(QString name); - QModelIndex duplicateEntry(QModelIndex idx); + QModelIndex duplicateEntry(QModelIndex idx, int new_status = PROF_STAT_COPY); - void doResetModel(); + void doResetModel(bool reset_import = false); bool resetDefault() const; QModelIndex activeProfile() const; @@ -91,29 +94,39 @@ public: GList * at(int row) const; bool changesPending() const; + bool importPending() const; + + bool userProfilesExist() const; #ifdef HAVE_MINIZIP - QStringList exportFileList(QModelIndexList items); bool exportProfiles(QString filename, QModelIndexList items, QString * err = Q_NULLPTR); - int importProfilesFromZip(QString filename, int *skippedCnt = Q_NULLPTR); + int importProfilesFromZip(QString filename, int *skippedCnt = Q_NULLPTR, QStringList *result = Q_NULLPTR); #endif - int importProfilesFromDir(QString filename, int *skippedCnt = Q_NULLPTR, bool fromZip = false); - bool copyTempToProfile(QString tempPath, QString profilePath); - QFileInfoList filterProfilePath(QString, QFileInfoList ent, bool fromZip); + int importProfilesFromDir(QString filename, int *skippedCnt = Q_NULLPTR, bool fromZip = false, QStringList *result = Q_NULLPTR); static bool checkNameValidity(QString name, QString *msg = Q_NULLPTR); - QList<int> findAllByNameAndVisibility(QString name, bool isGlobal = false); + QList<int> findAllByNameAndVisibility(QString name, bool isGlobal = false, bool searchReference = false); + void markAsImported(QStringList importedItems); + bool clearImported(QString *msg = Q_NULLPTR); + + int lastSetRow() const; + +Q_SIGNALS: + void itemChanged(const QModelIndex &idx); private: QList<profile_def *> profiles_; QString set_profile_; bool reset_default_; + bool profiles_imported_; + + int last_set_row_; void loadProfiles(); profile_def * guard(int row) const; GList * entry(profile_def *) const; - int findByNameAndVisibility(QString name, bool isGlobal = false); + int findByNameAndVisibility(QString name, bool isGlobal = false, bool searchReference = false); #ifdef HAVE_MINIZIP static bool acceptFile(QString fileName, int fileSize); @@ -125,6 +138,13 @@ private: QVariant dataToolTipRole(const QModelIndex & idx) const; QVariant dataPath(const QModelIndex & idx) const; +#ifdef HAVE_MINIZIP + QStringList exportFileList(QModelIndexList items); +#endif + bool copyTempToProfile(QString tempPath, QString profilePath); + QFileInfoList filterProfilePath(QString, QFileInfoList ent, bool fromZip); + QFileInfoList uniquePaths(QFileInfoList lst); + }; #endif diff --git a/ui/qt/profile_dialog.cpp b/ui/qt/profile_dialog.cpp index 54c1f9e9a1..a77eb98c4e 100644 --- a/ui/qt/profile_dialog.cpp +++ b/ui/qt/profile_dialog.cpp @@ -18,6 +18,7 @@ #include "ui/profile.h" #include "ui/recent.h" +#include "ui/last_open_dir.h" #include <ui/qt/utils/variant_pointer.h> #include <ui/qt/models/profile_model.h> @@ -80,17 +81,17 @@ ProfileDialog::ProfileDialog(QWidget *parent) : export_button_ = pd_ui_->buttonBox->addButton(tr("Export", "noun"), QDialogButtonBox::ActionRole); QMenu * importMenu = new QMenu(import_button_); - QAction * entry = importMenu->addAction(tr(UTF8_HORIZONTAL_ELLIPSIS " from Zip")); + QAction * entry = importMenu->addAction(tr("from zip file")); connect( entry, &QAction::triggered, this, &ProfileDialog::importFromZip); - entry = importMenu->addAction(tr(UTF8_HORIZONTAL_ELLIPSIS " from Directory")); + entry = importMenu->addAction(tr("from directory")); connect( entry, &QAction::triggered, this, &ProfileDialog::importFromDirectory); import_button_->setMenu(importMenu); QMenu * exportMenu = new QMenu(export_button_); - export_selected_entry_ = exportMenu->addAction(tr(UTF8_HORIZONTAL_ELLIPSIS " selected entry")); + export_selected_entry_ = exportMenu->addAction(tr("%Ln selected personal profile(s)", "", 0)); export_selected_entry_->setProperty(PROFILE_EXPORT_PROPERTY, PROFILE_EXPORT_SELECTED); connect( export_selected_entry_, &QAction::triggered, this, &ProfileDialog::exportProfiles); - entry = exportMenu->addAction(tr(UTF8_HORIZONTAL_ELLIPSIS " all personal profiles")); + entry = exportMenu->addAction(tr("all personal profiles")); entry->setProperty(PROFILE_EXPORT_PROPERTY, PROFILE_EXPORT_ALL); connect( entry, &QAction::triggered, this, &ProfileDialog::exportProfiles); export_button_->setMenu(exportMenu); @@ -100,18 +101,10 @@ ProfileDialog::ProfileDialog(QWidget *parent) : resetTreeView(); - connect(pd_ui_->profileTreeView, &ProfileTreeView::currentItemChanged, - this, &ProfileDialog::currentItemChanged); - - connect(pd_ui_->profileTreeView, &ProfileTreeView::itemUpdated, - this, &ProfileDialog::editingFinished); - /* Select the row for the currently selected profile or the first row if non is selected*/ selectProfile(); - QStringList items; - items << tr("All profiles") << tr("Personal profiles") << tr("Global profiles"); - pd_ui_->cmbProfileTypes->addItems(items); + pd_ui_->cmbProfileTypes->addItems(ProfileSortModel::filterTypes()); connect (pd_ui_->cmbProfileTypes, SIGNAL(currentTextChanged(const QString &)), this, SLOT(filterChanged(const QString &))); @@ -194,35 +187,150 @@ int ProfileDialog::execAction(ProfileDialog::ProfileAction profile_action) return ret; } +QModelIndexList ProfileDialog::selectedProfiles() +{ + QModelIndexList profiles; + + foreach (QModelIndex idx, pd_ui_->profileTreeView->selectionModel()->selectedIndexes()) + { + QModelIndex temp = sort_model_->mapToSource(idx); + if ( ! temp.isValid() || profiles.contains(temp) || temp.column() != ProfileModel::COL_NAME ) + continue; + + profiles << temp; + } + + return profiles; +} + +void ProfileDialog::selectionChanged() +{ + if ( selectedProfiles().count() == 0 ) + pd_ui_->profileTreeView->selectRow(0); + + updateWidgets(); +} + void ProfileDialog::updateWidgets() { - bool enable_del = false; + bool enable_del = true; bool enable_ok = true; + bool multiple = false; + bool contains_user = false; + bool enable_import = true; + int user_profiles = 0; - QString msg = ""; - if ( model_->changesPending() ) - msg = tr("An import of profiles is not allowed, while changes are pending."); - import_button_->setToolTip(msg); - import_button_->setEnabled( ! model_->changesPending() ); - + QString msg; QModelIndex index = sort_model_->mapToSource(pd_ui_->profileTreeView->currentIndex()); + QModelIndexList profiles = selectedProfiles(); + + /* Ensure that the index is always the name column */ if ( index.column() != ProfileModel::COL_NAME ) index = index.sibling(index.row(), ProfileModel::COL_NAME); - if (index.isValid()) { - if ( !index.data(ProfileModel::DATA_IS_GLOBAL).toBool() || ! model_->resetDefault()) - enable_del = true; + /* check if more than one viable profile is selected, and inform the sorting model */ + if ( profiles.count() > 1 ) + multiple = true; + + /* Check if user profiles have been selected and allow export if it is so */ + for ( int cnt = 0; cnt < profiles.count(); cnt++ ) + { + if ( ! profiles[cnt].data(ProfileModel::DATA_IS_GLOBAL).toBool() && ! profiles[cnt].data(ProfileModel::DATA_IS_DEFAULT).toBool() ) + user_profiles++; + } + if ( user_profiles > 0 ) + contains_user = true; + if ( model_->changesPending() ) + { + enable_import = false; + msg = tr("An import of profiles is not allowed, while changes are pending."); + } + else if ( model_->importPending() ) + { + enable_import = false; + msg = tr("An import is pending to be saved. Additional imports are not allowed."); + } + import_button_->setToolTip( msg ); + import_button_->setEnabled( enable_import ); + +#ifdef HAVE_MINIZIP + bool enable_export = false; + + /* enable export if no changes are pending */ + if ( ! model_->changesPending() ) + enable_export = true; + + export_button_->setEnabled( enable_export ); + if ( ! enable_export ) + { + if ( ! contains_user ) + export_button_->setToolTip(tr("An export of profiles is only allowed for personal profiles.")); + else + export_button_->setToolTip(tr("An export of profiles is not allowed, while changes are pending.")); + } + export_selected_entry_->setVisible(contains_user); +#endif + + /* if the current profile is default with reset pending or a global one, deactivate delete */ + if ( ! multiple ) + { + if ( index.isValid() ) + { + if ( index.data(ProfileModel::DATA_IS_GLOBAL).toBool() ) + enable_del = false; + else if ( index.data(ProfileModel::DATA_IS_DEFAULT).toBool() && model_->resetDefault()) + enable_del = false; + } + else if ( ! index.isValid() ) + enable_del = false; } + msg.clear(); + if ( multiple ) + { + /* multiple profiles are being selected, copy is no longer allowed */ + pd_ui_->copyToolButton->setEnabled(false); + + msg = tr("%Ln selected personal profile(s)", "", user_profiles); + pd_ui_->hintLabel->setText(msg); + pd_ui_->hintLabel->setUrl(QString()); + export_selected_entry_->setText(msg); + } + else + { + /* if only one profile is selected, display it's path in the hint label and activate link (if allowed) */ + if ( index.isValid() ) + { + QString temp = index.data(ProfileModel::DATA_PATH).toString(); + if ( index.data(ProfileModel::DATA_PATH_IS_NOT_DESCRIPTION).toBool() ) + pd_ui_->hintLabel->setUrl(QUrl::fromLocalFile(temp).toString()); + else + pd_ui_->hintLabel->setUrl(QString()); + pd_ui_->hintLabel->setText(temp); + pd_ui_->hintLabel->setToolTip(index.data(Qt::ToolTipRole).toString()); + + if ( ! index.data(ProfileModel::DATA_IS_GLOBAL).toBool() && ! index.data(ProfileModel::DATA_IS_DEFAULT).toBool() ) + msg = tr("%Ln selected personal profile(s)", "", 1); + } + + pd_ui_->copyToolButton->setEnabled(true); + export_selected_entry_->setText(msg); + } + + /* Ensure, that the ok button is disabled, if an invalid name is used or if duplicate global profiles exist */ if (model_ && model_->rowCount() > 0) { - for ( int row = 0; row < model_->rowCount(); row++ ) + msg.clear(); + for ( int row = 0; row < model_->rowCount() && enable_ok; row++ ) { QModelIndex idx = model_->index(row, ProfileModel::COL_NAME); QString name = idx.data().toString(); - if ( ! ProfileModel::checkNameValidity(name) ) + if ( ! ProfileModel::checkNameValidity(name, &msg) ) { + pd_ui_->hintLabel->setText(msg); + pd_ui_->hintLabel->setUrl(QString()); + enable_ok = false; continue; } @@ -239,25 +347,15 @@ void ProfileDialog::updateWidgets() } } - pd_ui_->profileTreeView->resizeColumnToContents(0); + /* ensure the name column is resized to it's content */ + pd_ui_->profileTreeView->resizeColumnToContents(ProfileModel::COL_NAME); + pd_ui_->deleteToolButton->setEnabled(enable_del); ok_button_->setEnabled(enable_ok); } -void ProfileDialog::currentItemChanged() +void ProfileDialog::currentItemChanged(const QModelIndex &, const QModelIndex &) { - QModelIndex idx = pd_ui_->profileTreeView->currentIndex(); - if ( idx.isValid() ) - { - QString temp = idx.data(ProfileModel::DATA_PATH).toString(); - if ( idx.data(ProfileModel::DATA_PATH_IS_NOT_DESCRIPTION).toBool() ) - pd_ui_->hintLabel->setUrl(QUrl::fromLocalFile(temp).toString()); - else - pd_ui_->hintLabel->setUrl(QString()); - pd_ui_->hintLabel->setText(temp); - pd_ui_->hintLabel->setToolTip(idx.data(Qt::ToolTipRole).toString()); - } - updateWidgets(); } @@ -281,11 +379,21 @@ void ProfileDialog::on_newToolButton_clicked() void ProfileDialog::on_deleteToolButton_clicked() { - QModelIndex index = sort_model_->mapToSource(pd_ui_->profileTreeView->currentIndex()); + QModelIndexList profiles = selectedProfiles(); + if ( profiles.count() <= 0 ) + return; + + model_->deleteEntries(profiles); - model_->deleteEntry(index); + bool isGlobal = model_->activeProfile().data(ProfileModel::DATA_IS_GLOBAL).toBool(); + int row = model_->findByName(model_->activeProfile().data().toString()); + /* If the active profile is deleted, the default is selected next */ + if ( row < 0 ) + row = 0; + QModelIndex newIdx = sort_model_->mapFromSource(model_->index(row, 0)); + if ( newIdx.data(ProfileModel::DATA_IS_GLOBAL).toBool() != isGlobal ) + newIdx = sort_model_->mapFromSource(model_->index(0, 0)); - QModelIndex newIdx = sort_model_->mapFromSource(model_->index(0, 0)); pd_ui_->profileTreeView->setCurrentIndex(newIdx); updateWidgets(); @@ -293,6 +401,10 @@ void ProfileDialog::on_deleteToolButton_clicked() void ProfileDialog::on_copyToolButton_clicked() { + QModelIndexList profiles = selectedProfiles(); + if ( profiles.count() > 1 ) + return; + pd_ui_->lineProfileFilter->setText(""); pd_ui_->cmbProfileTypes->setCurrentIndex(ProfileSortModel::AllProfiles); sort_model_->setFilterString(); @@ -320,8 +432,15 @@ void ProfileDialog::on_buttonBox_accepted() bool item_data_removed = false; QModelIndex index = sort_model_->mapToSource(pd_ui_->profileTreeView->currentIndex()); + + pd_ui_->buttonBox->setFocus(); + + QModelIndexList profiles = selectedProfiles(); + if ( profiles.count() <= 0 ) + index = QModelIndex(); + QModelIndex default_item = sort_model_->mapFromSource(model_->index(0, ProfileModel::COL_NAME)); - if (index.column() != ProfileModel::COL_NAME) + if (index.isValid() && index.column() != ProfileModel::COL_NAME) index = index.sibling(index.row(), ProfileModel::COL_NAME); if (default_item.data(ProfileModel::DATA_STATUS).toInt() == PROF_STAT_DEFAULT && model_->resetDefault()) @@ -360,7 +479,16 @@ void ProfileDialog::on_buttonBox_accepted() model_->doResetModel(); QString profileName; - if (index.isValid() && !item_data_removed) { + + if ( ! index.isValid() && model_->lastSetRow() >= 0 ) + { + QModelIndex original = model_->index(model_->lastSetRow(), ProfileModel::COL_NAME); + index = sort_model_->mapFromSource(original); + } + + /* If multiple profiles are selected, do not change the selected profile */ + if ( index.isValid() && ! item_data_removed && profiles.count() <= 1 ) + { profileName = model_->data(index).toString(); } @@ -374,16 +502,32 @@ void ProfileDialog::on_buttonBox_accepted() } } +void ProfileDialog::on_buttonBox_rejected() +{ + QString msg; + if ( ! model_->clearImported(&msg) ) + QMessageBox::critical(this, tr("Error"), msg); +} + void ProfileDialog::on_buttonBox_helpRequested() { wsApp->helpTopicAction(HELP_CONFIG_PROFILES_DIALOG); } -void ProfileDialog::editingFinished() +void ProfileDialog::dataChanged(const QModelIndex &idx) { pd_ui_->lineProfileFilter->setText(""); pd_ui_->cmbProfileTypes->setCurrentIndex(ProfileSortModel::AllProfiles); - currentItemChanged(); + + pd_ui_->profileTreeView->setFocus(); + if ( ! idx.isValid() && model_->lastSetRow() >= 0 ) + { + QModelIndex original = model_->index(model_->lastSetRow(), ProfileModel::COL_NAME); + pd_ui_->profileTreeView->setCurrentIndex(sort_model_->mapFromSource(original)); + pd_ui_->profileTreeView->selectRow(sort_model_->mapFromSource(original).row()); + } + + updateWidgets(); } void ProfileDialog::filterChanged(const QString &text) @@ -404,86 +548,105 @@ void ProfileDialog::filterChanged(const QString &text) } #ifdef HAVE_MINIZIP -void ProfileDialog::exportProfiles(bool exportAll) +void ProfileDialog::exportProfiles(bool exportAllPersonalProfiles) { QAction * action = qobject_cast<QAction *>(sender()); if ( action && action->property(PROFILE_EXPORT_PROPERTY).isValid() ) - exportAll = action->property(PROFILE_EXPORT_PROPERTY).toString().compare(PROFILE_EXPORT_ALL) == 0; + exportAllPersonalProfiles = action->property(PROFILE_EXPORT_PROPERTY).toString().compare(PROFILE_EXPORT_ALL) == 0; QModelIndexList items; + int skipped = 0; - if ( ! exportAll && pd_ui_->profileTreeView->currentIndex().isValid() ) - items << sort_model_->mapToSource(pd_ui_->profileTreeView->currentIndex()); - else if ( exportAll ) + if ( ! exportAllPersonalProfiles ) + { + foreach ( QModelIndex idx, selectedProfiles() ) + { + if ( ! idx.data(ProfileModel::DATA_IS_GLOBAL).toBool() && ! idx.data(ProfileModel::DATA_IS_DEFAULT).toBool() ) + items << idx; + else + skipped++; + } + } + else if ( exportAllPersonalProfiles ) { for ( int cnt = 0; cnt < sort_model_->rowCount(); cnt++ ) { QModelIndex idx = sort_model_->index(cnt, ProfileModel::COL_NAME); if ( ! idx.data(ProfileModel::DATA_IS_GLOBAL).toBool() && ! idx.data(ProfileModel::DATA_IS_DEFAULT).toBool() ) - { items << sort_model_->mapToSource(idx); - } } } if ( items.count() == 0 ) { - QMessageBox::warning(this, tr("Exporting profiles"), tr("No profiles found for export")); + QString msg = tr("No profiles found for export"); + if ( skipped > 0 ) + msg.append(tr(", %Ln profile(s) skipped", "", skipped)); + QMessageBox::critical(this, tr("Exporting profiles"), msg); return; } - QString zipFile = QFileDialog::getSaveFileName(this, tr("Select zip file for export"), QString(), tr("Zip File (*.zip)")); + QString zipFile = QFileDialog::getSaveFileName(this, tr("Select zip file for export"), lastOpenDir(), tr("Zip File (*.zip)")); - QString err; - if ( model_->exportProfiles(zipFile, items, &err) ) - QMessageBox::information(this, tr("Exporting profiles"), tr("%Ln profile(s) exported", "", items.count())); - else - QMessageBox::warning(this, tr("Exporting profiles"), QString("%1\n\n%2: %3").arg(tr("An error has occured while exporting profiles")).arg("Error").arg(err)); + if ( zipFile.length() > 0 ) + { + QFileInfo fi(zipFile); + if ( fi.suffix().length() == 0 || fi.suffix().toLower().compare("zip") != 0 ) + zipFile += ".zip"; + + QString err; + if ( model_->exportProfiles(zipFile, items, &err) ) + { + QString msg = tr("%Ln profile(s) exported", "", items.count()); + if ( skipped > 0 ) + msg.append(tr(", %Ln profile(s) skipped", "", skipped)); + QMessageBox::information(this, tr("Exporting profiles"), msg); + + QFileInfo zip(zipFile); + storeLastDir(zip.absolutePath()); + } + else + { + QString msg = tr("An error has occurred while exporting profiles"); + if ( err.length() > 0 ) + msg.append(QString("\n\n%1: %3").arg(tr("Error")).arg(err)); + QMessageBox::critical(this, tr("Exporting profiles"), msg); + } + } } void ProfileDialog::importFromZip() { - QString zipFile = QFileDialog::getOpenFileName(this, tr("Select zip file for import"), QString(), tr("Zip File (*.zip)")); + QString zipFile = QFileDialog::getOpenFileName(this, tr("Select zip file for import"), lastOpenDir(), tr("Zip File (*.zip)")); QFileInfo fi(zipFile); if ( ! fi.exists() ) return; int skipped = 0; - int count = model_->importProfilesFromZip(zipFile, &skipped); - QString msg; - QMessageBox::Icon icon; + QStringList import; + int count = model_->importProfilesFromZip(zipFile, &skipped, &import); - if ( count == 0 && skipped == 0 ) - { - icon = QMessageBox::Warning; - msg = tr("No profiles found for import in %1").arg(fi.fileName()); - } - else - { - icon = QMessageBox::Information; - msg = tr("%Ln profile(s) imported", "", count); - if ( skipped > 0 ) - msg.append(tr(", %Ln profile(s) skipped", "", skipped)); - } - - QMessageBox msgBox(icon, tr("Importing profiles"), msg, QMessageBox::Ok, this); - msgBox.exec(); - - if ( count > 0 ) - resetTreeView(); + finishImport(fi, count, skipped, import); } #endif void ProfileDialog::importFromDirectory() { - QString importDir = QFileDialog::getExistingDirectory(this, tr("Select directory for import"), QString()); + QString importDir = QFileDialog::getExistingDirectory(this, tr("Select directory for import"), lastOpenDir()); QFileInfo fi(importDir); if ( ! fi.isDir() ) return; int skipped = 0; - int count = model_->importProfilesFromDir(importDir, &skipped); + QStringList import; + int count = model_->importProfilesFromDir(importDir.append(QDir::separator()), &skipped, false, &import); + + finishImport(fi, count, skipped, import); +} + +void ProfileDialog::finishImport(QFileInfo fi, int count, int skipped, QStringList import) +{ QString msg; QMessageBox::Icon icon; @@ -498,12 +661,62 @@ void ProfileDialog::importFromDirectory() msg = tr("%Ln profile(s) imported", "", count); if ( skipped > 0 ) msg.append(tr(", %Ln profile(s) skipped", "", skipped)); + + storeLastDir(fi.absolutePath()); } - QMessageBox msgBox(icon, tr("Importing profiles"), msg, QMessageBox::Ok, this); - msgBox.exec(); if ( count > 0 ) + { + import.sort(); resetTreeView(); + model_->markAsImported(import); + int rowFirstImported = model_->findByName(import.at(0)); + QModelIndex idx = sort_model_->mapFromSource(model_->index(rowFirstImported, ProfileModel::COL_NAME)); + pd_ui_->profileTreeView->selectRow(idx.isValid() ? idx.row() : 0); + } + + QMessageBox msgBox(icon, tr("Importing profiles"), msg, QMessageBox::Ok, this); + msgBox.exec(); + + updateWidgets(); +} + +QString ProfileDialog::lastOpenDir() +{ + QString result; + + switch (prefs.gui_fileopen_style) { + + case FO_STYLE_LAST_OPENED: + /* The user has specified that we should start out in the last directory + we looked in. If we've already opened a file, use its containing + directory, if we could determine it, as the directory, otherwise + use the "last opened" directory saved in the preferences file if + there was one. */ + /* This is now the default behaviour in file_selection_new() */ + result = QString(get_last_open_dir()); + break; + + case FO_STYLE_SPECIFIED: + /* The user has specified that we should always start out in a + specified directory; if they've specified that directory, + start out by showing the files in that dir. */ + if (prefs.gui_fileopen_dir[0] != '\0') + result = QString(prefs.gui_fileopen_dir); + break; + } + + QDir ld(result); + if ( ld.exists() ) + return result; + + return QString(); +} + +void ProfileDialog::storeLastDir(QString dir) +{ + if (wsApp && dir.length() > 0) + wsApp->setLastOpenDir(dir.toUtf8().constData()); } void ProfileDialog::resetTreeView() @@ -512,15 +725,27 @@ void ProfileDialog::resetTreeView() { pd_ui_->profileTreeView->setModel(Q_NULLPTR); sort_model_->setSourceModel(Q_NULLPTR); + model_->disconnect(); + if ( pd_ui_->profileTreeView->selectionModel() ) + pd_ui_->profileTreeView->selectionModel()->disconnect(); delete sort_model_; delete model_; } - model_ = new ProfileModel(this); - sort_model_ = new ProfileSortModel(this); + model_ = new ProfileModel(pd_ui_->profileTreeView); + sort_model_ = new ProfileSortModel(pd_ui_->profileTreeView); sort_model_->setSourceModel(model_); pd_ui_->profileTreeView->setModel(sort_model_); + connect(model_, &ProfileModel::itemChanged, this, &ProfileDialog::dataChanged, Qt::QueuedConnection); + QItemSelectionModel *selModel = pd_ui_->profileTreeView->selectionModel(); + connect(selModel, &QItemSelectionModel::currentChanged, + this, &ProfileDialog::currentItemChanged, Qt::QueuedConnection); + connect(selModel, SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)), + this, SLOT(selectionChanged())); + + selectionChanged(); + if ( sort_model_->columnCount() <= 1 ) pd_ui_->profileTreeView->header()->hide(); else diff --git a/ui/qt/profile_dialog.h b/ui/qt/profile_dialog.h index a1afcbb02b..7fa91d8592 100644 --- a/ui/qt/profile_dialog.h +++ b/ui/qt/profile_dialog.h @@ -64,10 +64,14 @@ private: void updateWidgets(); void resetTreeView(); + QString lastOpenDir(); + void storeLastDir(QString dir); + void finishImport(QFileInfo fi, int count, int skipped, QStringList import); + private slots: - void currentItemChanged(); + void currentItemChanged(const QModelIndex & c = QModelIndex(), const QModelIndex & p = QModelIndex()); #ifdef HAVE_MINIZIP - void exportProfiles(bool exportAll = false); + void exportProfiles(bool exportAllPersonalProfiles = false); void importFromZip(); #endif void importFromDirectory(); @@ -76,11 +80,15 @@ private slots: void on_deleteToolButton_clicked(); void on_copyToolButton_clicked(); void on_buttonBox_accepted(); + void on_buttonBox_rejected(); void on_buttonBox_helpRequested(); - void editingFinished(); + void dataChanged(const QModelIndex &); void filterChanged(const QString &); + void selectionChanged(); + QModelIndexList selectedProfiles(); + // QWidget interface }; diff --git a/ui/qt/profile_dialog.ui b/ui/qt/profile_dialog.ui index fc475a2daf..d0e545b8d8 100644 --- a/ui/qt/profile_dialog.ui +++ b/ui/qt/profile_dialog.ui @@ -27,6 +27,9 @@ </item> <item> <widget class="ProfileTreeView" name="profileTreeView"> + <property name="selectionMode"> + <enum>QAbstractItemView::ExtendedSelection</enum> + </property> <property name="rootIsDecorated"> <bool>false</bool> </property> @@ -69,7 +72,7 @@ <item> <widget class="StockIconToolButton" name="deleteToolButton"> <property name="toolTip"> - <string>Remove this profile. System provided profiles cannot be removed.</string> + <string><html><head/><body><p>Remove this profile. System provided profiles cannot be removed. The default profile will be resetted upon deletion.</p></body></html></string> </property> <property name="icon"> <iconset> diff --git a/ui/qt/widgets/profile_tree_view.cpp b/ui/qt/widgets/profile_tree_view.cpp index ea3e25d609..3782314239 100644 --- a/ui/qt/widgets/profile_tree_view.cpp +++ b/ui/qt/widgets/profile_tree_view.cpp @@ -29,7 +29,7 @@ void ProfileUrlLinkDelegate::paint(QPainter *painter, const QStyleOptionViewItem } -ProfileTreeEditDelegate::ProfileTreeEditDelegate(QWidget *parent) : QItemDelegate(parent) {} +ProfileTreeEditDelegate::ProfileTreeEditDelegate(QWidget *parent) : QItemDelegate(parent), editor_(Q_NULLPTR) {} void ProfileTreeEditDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const { @@ -52,38 +52,26 @@ ProfileTreeView::ProfileTreeView(QWidget *parent) : void ProfileTreeView::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) { - if ( selected.count() == 0 && deselected.count() > 0 ) - { - QItemSelection newSelection; - newSelection << deselected.at(0); - selectionModel()->select(newSelection, QItemSelectionModel::ClearAndSelect); - if (newSelection.count() > 0) - { - QModelIndexList selIndex = selectionModel()->selectedIndexes(); - scrollTo(selIndex.at(0)); - } - } - else if ( selected.count() > 1 ) + QTreeView::selectionChanged(selected, deselected); + + if ( model() ) { - /* If more then one item is selected, only accept the new item, deselect everything else */ - QSet<QItemSelectionRange> intersection = selected.toSet().intersect(deselected.toSet()); - QItemSelection newSelection; - newSelection << intersection.toList().at(0); - selectionModel()->select(newSelection, QItemSelectionModel::ClearAndSelect); - if (newSelection.count() > 0) + int offColumn = model()->columnCount(); + int idxCount = selectedIndexes().count() / offColumn; + int dselCount = deselected.count() > 0 ? deselected.at(0).indexes().count() / offColumn : 0; + + /* Ensure, that the last selected row cannot be deselected */ + if ( idxCount == 0 && dselCount == 1 ) { - QModelIndexList selIndex = selectionModel()->selectedIndexes(); - scrollTo(selIndex.at(0)); + QModelIndex idx = deselected.at(0).indexes().at(0); + /* If the last item is no longer valid or the row is out of bounds, select default */ + if ( ! idx.isValid() || idx.row() >= model()->rowCount() ) + idx = model()->index(0, ProfileModel::COL_NAME); + selectRow(idx.row()); } + else if ( selectedIndexes().count() == 0 ) + selectRow(0); } - else - QTreeView::selectionChanged(selected, deselected); -} - -void ProfileTreeView::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) -{ - emit currentItemChanged(); - QTreeView::currentChanged(current, previous); } void ProfileTreeView::clicked(const QModelIndex &index) @@ -92,7 +80,7 @@ void ProfileTreeView::clicked(const QModelIndex &index) return; /* Only paint links for valid paths */ - if ( index.data(ProfileModel::DATA_PATH_IS_NOT_DESCRIPTION).toBool() ) + if ( index.data(ProfileModel::DATA_INDEX_VALUE_IS_URL).toBool() ) { QString path = QDir::toNativeSeparators(index.data().toString()); QDesktopServices::openUrl(QUrl::fromLocalFile(path)); @@ -111,3 +99,15 @@ void ProfileTreeView::selectRow(int row) QItemSelectionModel::ClearAndSelect); } + +void ProfileTreeView::mouseDoubleClickEvent(QMouseEvent *ev) +{ + /* due to the fact, that we allow only row selection, selected rows are always added with all columns */ + if ( selectedIndexes().count() <= model()->columnCount() ) + QTreeView::mouseDoubleClickEvent(ev); +} + +bool ProfileTreeView::activeEdit() +{ + return ( state() == QAbstractItemView::EditingState ); +} diff --git a/ui/qt/widgets/profile_tree_view.h b/ui/qt/widgets/profile_tree_view.h index a0c34c8743..0aed651b9e 100644 --- a/ui/qt/widgets/profile_tree_view.h +++ b/ui/qt/widgets/profile_tree_view.h @@ -31,7 +31,12 @@ class ProfileTreeEditDelegate : public QItemDelegate public: ProfileTreeEditDelegate(QWidget *parent = Q_NULLPTR); + // QAbstractItemDelegate interface virtual void setEditorData(QWidget *editor, const QModelIndex &index) const; + +private: + QWidget * editor_; + QModelIndex index_; }; class ProfileTreeView : public QTreeView @@ -41,20 +46,23 @@ public: ProfileTreeView(QWidget *parent = nullptr); void selectRow(int row); + bool activeEdit(); Q_SIGNALS: - void currentItemChanged(); void itemUpdated(); + // QWidget interface +protected: + virtual void mouseDoubleClickEvent(QMouseEvent *event); + // QAbstractItemView interface protected slots: virtual void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected); - virtual void currentChanged(const QModelIndex ¤t, const QModelIndex &previous); - virtual void clicked(const QModelIndex &index); private: ProfileTreeEditDelegate *delegate_; + }; #endif diff --git a/ui/qt/wireshark_en.ts b/ui/qt/wireshark_en.ts index e7a07178aa..d015a62203 100644 --- a/ui/qt/wireshark_en.ts +++ b/ui/qt/wireshark_en.ts @@ -8554,6 +8554,20 @@ For example, use 1 hour to have a new file created every hour on the hour.</sour <translation type="unfinished"></translation> </message> <message numerus="yes"> + <source>… %Ln selected personal profile(s)</source> + <translation> + <numerusform>… %Ln selected personal profile</numerusform> + <numerusform>… %Ln selected personal profiles</numerusform> + </translation> + </message> + <message numerus="yes"> + <source>%Ln selected personal profile(s)</source> + <translation> + <numerusform>%Ln selected personal profile</numerusform> + <numerusform>%Ln selected personal profiles</numerusform> + </translation> + </message> + <message numerus="yes"> <source>%Ln profile(s) exported</source> <translation> <numerusform>%Ln profile exported</numerusform> @@ -8598,6 +8612,13 @@ For example, use 1 hour to have a new file created every hour on the hour.</sour <source>Importing profiles</source> <translation type="unfinished"></translation> </message> + <message numerus="yes"> + <source>%Ln profile(s) selected</source> + <translation type="unfinished"> + <numerusform>%Ln profile selected</numerusform> + <numerusform>%Ln profiles selected</numerusform> + </translation> + </message> </context> <context> <name>ProfileModel</name> |