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>
This commit is contained in:
Roland Knall 2019-07-29 14:06:58 +02:00
parent 66747a982b
commit f259187803
12 changed files with 744 additions and 233 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 118 KiB

View File

@ -784,6 +784,16 @@ are common to all profiles.
.The configuration profiles dialog box
image::wsug_graphics/ws-gui-config-profiles.png[{medium-screenshot-attrs}]
Search for profile ...::
The list of profiles can be filtered by entering part of the profile's name
into the search box.
Type selection::
Profiles can be filtered between displaying "All profiles", "Personal profiles"
and "Global profiles"
* Personal profiles - these are profiles stored in the user's configuration dirctory
* Global profiles - these are profiles provided with Wireshark
New (+)::
Create a new profile. The name of the created profile is “New profile”
and is highlighted so that you can more easily change it.
@ -791,7 +801,9 @@ and is highlighted so that you can more easily change it.
Delete (-)::
Deletes the selected profile. This includes all configuration files used
in this profile. It is not possible to delete the “Default” profile or global
profiles.
profiles. Multiple profiles can be selected and deleted at the same time, if global
profiles are selected they will be skipped, a selected "Default" profile will be
resetted.
Copy::
Copies the selected profile. This copies the configuration of the
@ -799,6 +811,16 @@ profile currently selected in the list. The name of the created profile
is the same as the copied profile, with the text “(copy)” and is
highlighted so that you can more easily change it.
btn:[Import]::
Profiles can be imported from zip-archives as well as directly from directory
structures. Profiles, which already exist by name will be skipped, as well as
profiles named "Default".
btn:[Export]::
Profiles can be exported to a zip-archive. Global profiles, as well as the default
profile will be skipped during export. Profiles can be selected in the list individually
and only the selected profiles will be exported
btn:[OK]::
This button saves all changes, applies the selected profile and closes the
dialog.

View File

@ -26,6 +26,7 @@ extern "C" {
#define PROF_STAT_NEW 3
#define PROF_STAT_CHANGED 4
#define PROF_STAT_COPY 5
#define PROF_STAT_IMPORT 6
typedef struct {
char *name; /* profile name */

View File

@ -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,46 +591,46 @@ 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()));
#ifdef HAVE_MINIZIP
QMenu * importMenu = new QMenu(tr("Import"));
action = importMenu->addAction(tr(UTF8_HORIZONTAL_ELLIPSIS" from Zip"));
action->setProperty("dialog_action_", (int)ProfileDialog::ImportZipProfile);
connect(action, SIGNAL(triggered()), this, SLOT(manageProfile()));
action = importMenu->addAction(tr(UTF8_HORIZONTAL_ELLIPSIS" from Directory"));
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);
#else
action = ctx_menu_.addAction(tr("Import" UTF8_HORIZONTAL_ELLIPSIS));
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 = ctx_menu_.addAction(tr("New" UTF8_HORIZONTAL_ELLIPSIS), this, SLOT(manageProfile()));
action->setProperty("dialog_action_", (int)ProfileDialog::NewProfile);
connect(action, SIGNAL(triggered()), this, SLOT(manageProfile()));
action = ctx_menu_.addAction(tr("Edit" UTF8_HORIZONTAL_ELLIPSIS));
action = ctx_menu_.addAction(tr("Edit" UTF8_HORIZONTAL_ELLIPSIS), this, SLOT(manageProfile()));
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 = ctx_menu_.addAction(tr("Delete"), this, SLOT(manageProfile()));
action->setProperty("dialog_action_", (int)ProfileDialog::DeleteCurrentProfile);
action->setEnabled(enable_edit);
connect(action, SIGNAL(triggered()), this, SLOT(manageProfile()));
ctx_menu_.addSeparator();
#ifdef HAVE_MINIZIP
QMenu * importMenu = new QMenu(tr("Import"));
action = importMenu->addAction(tr("from zip file"), this, SLOT(manageProfile()));
action->setProperty("dialog_action_", (int)ProfileDialog::ImportZipProfile);
action = importMenu->addAction(tr("from directory"), this, SLOT(manageProfile()));
action->setProperty("dialog_action_", (int)ProfileDialog::ImportDirProfile);
ctx_menu_.addMenu(importMenu);
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"), this, SLOT(manageProfile()));
action->setProperty("dialog_action_", (int)ProfileDialog::ImportDirProfile);
#endif
ctx_menu_.addSeparator();
ctx_menu_.addMenu(&profile_menu_);

View File

@ -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 )
{
if ( ! indeces.contains(idx.row()) && ! idx.data(ProfileModel::DATA_IS_GLOBAL).toBool() )
indeces << idx.row();
}
/* 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 )
{
profile_def * prof = guard(row);
if ( ! prof )
continue;
if ( prof->is_global )
continue;
if ( prof->status == PROF_STAT_DEFAULT )
{
reset_default_ = ! reset_default_;
}
else
{
GList * fl_entry = entry(prof);
if ( fl_entry )
{
changes = true;
remove_from_profile_list(fl_entry);
}
}
}
if ( changes )
loadProfiles();
if ( reset_default_ )
{
emit layoutAboutToBeChanged();
reset_default_ = ! reset_default_;
emit dataChanged(index(0, 0), index(rowCount(), columnCount()));
emit layoutChanged();
}
else
{
GList * fl_entry = entry(prof);
if ( fl_entry )
{
remove_from_profile_list(fl_entry);
loadProfiles();
}
}
}
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)

View File

@ -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

View File

@ -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_->deleteEntry(index);
model_->deleteEntries(profiles);
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());
}
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();
if ( count > 0 )
resetTreeView();
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

View File

@ -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
};

View File

@ -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>

View File

@ -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 )
{
/* 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)
{
QModelIndexList selIndex = selectionModel()->selectedIndexes();
scrollTo(selIndex.at(0));
}
}
else
QTreeView::selectionChanged(selected, deselected);
}
QTreeView::selectionChanged(selected, deselected);
void ProfileTreeView::currentChanged(const QModelIndex &current, const QModelIndex &previous)
{
emit currentItemChanged();
QTreeView::currentChanged(current, previous);
if ( model() )
{
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 )
{
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);
}
}
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 );
}

View File

@ -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

View File

@ -8553,6 +8553,20 @@ For example, use 1 hour to have a new file created every hour on the hour.</sour
<source>Select zip file for export</source>
<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>
@ -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>