aboutsummaryrefslogtreecommitdiffstats
path: root/ui/qt
diff options
context:
space:
mode:
authorRoland Knall <rknall@gmail.com>2019-07-29 14:06:58 +0200
committerRoland Knall <rknall@gmail.com>2019-08-02 21:38:02 +0000
commitf2591878034f5380de4cef5ed9532942ea16469c (patch)
treeaacb9d6bf7ece9b7291f3f1f0df8d871b21acff7 /ui/qt
parent66747a982b93ac6332260b23119d11072a51c5bb (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.cpp59
-rw-r--r--ui/qt/models/profile_model.cpp318
-rw-r--r--ui/qt/models/profile_model.h46
-rw-r--r--ui/qt/profile_dialog.cpp401
-rw-r--r--ui/qt/profile_dialog.h14
-rw-r--r--ui/qt/profile_dialog.ui5
-rw-r--r--ui/qt/widgets/profile_tree_view.cpp60
-rw-r--r--ui/qt/widgets/profile_tree_view.h14
-rw-r--r--ui/qt/wireshark_en.ts21
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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Remove this profile. System provided profiles cannot be removed. The default profile will be resetted upon deletion.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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 &current, 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 &current, 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>