diff options
| -rw-r--r-- | src/createinstancedialog.ui | 21 | ||||
| -rw-r--r-- | src/createinstancedialogpages.cpp | 62 | ||||
| -rw-r--r-- | src/instancemanager.cpp | 9 | ||||
| -rw-r--r-- | src/instancemanager.h | 1 | ||||
| -rw-r--r-- | src/instancemanagerdialog.cpp | 154 | ||||
| -rw-r--r-- | src/instancemanagerdialog.h | 12 | ||||
| -rw-r--r-- | src/instancemanagerdialog.ui | 27 |
7 files changed, 221 insertions, 65 deletions
diff --git a/src/createinstancedialog.ui b/src/createinstancedialog.ui index a3c3db92..b7f4f502 100644 --- a/src/createinstancedialog.ui +++ b/src/createinstancedialog.ui @@ -77,9 +77,12 @@ </widget> </item> <item> - <widget class="QCheckBox" name="hideIntro"> + <widget class="LinkLabel" name="label_19"> <property name="text"> - <string>Never show this again</string> + <string><html><head/><body><p><a href="https://github.com/ModOrganizer2/modorganizer/wiki/Instance">More information</a></p></body></html></string> + </property> + <property name="openExternalLinks"> + <bool>true</bool> </property> </widget> </item> @@ -96,6 +99,13 @@ </property> </spacer> </item> + <item> + <widget class="QCheckBox" name="hideIntro"> + <property name="text"> + <string>Never show this page again</string> + </property> + </widget> + </item> </layout> </widget> <widget class="QWidget" name="page"> @@ -1202,6 +1212,13 @@ </item> </layout> </widget> + <customwidgets> + <customwidget> + <class>LinkLabel</class> + <extends>QLabel</extends> + <header location="global">linklabel.h</header> + </customwidget> + </customwidgets> <resources> <include location="resources.qrc"/> </resources> diff --git a/src/createinstancedialogpages.cpp b/src/createinstancedialogpages.cpp index 127243c6..8d304635 100644 --- a/src/createinstancedialogpages.cpp +++ b/src/createinstancedialogpages.cpp @@ -21,50 +21,6 @@ QString makeDefaultPath(const std::wstring& dir) QString::fromStdWString(dir))); } -QString sanitizeFileName(const QString& name) -{ - QString new_name = name; - - // Restrict the allowed characters - new_name = new_name.remove(QRegExp("[^A-Za-z0-9 _=+;!@#$%^'\\-\\.\\[\\]\\{\\}\\(\\)]")); - - // Don't end in spaces and periods - new_name = new_name.remove(QRegExp("\\.*$")); - new_name = new_name.remove(QRegExp(" *$")); - - // Recurse until stuff stops changing - if (new_name != name) { - return sanitizeFileName(new_name); - } - - return new_name; -} - -// same thing as above, but allows path separators and colons -// -QString sanitizePath(const QString& path) -{ - QString new_name = path; - - // Restrict the allowed characters - new_name = new_name.remove(QRegExp("[^\\\\\\/A-Za-z0-9 _=+;!@#$%^:'\\-\\.\\[\\]\\{\\}\\(\\)]")); - - // Don't end in spaces and periods - new_name = new_name.remove(QRegExp("\\.*$")); - new_name = new_name.remove(QRegExp(" *$")); - - // Recurse until stuff stops changing - if (new_name != path) { - return sanitizeFileName(new_name); - } - - return new_name; -} - -void setPossiblePlaceholder( - QLabel* label, const QString& s, const QString& arg) -{ -} PlaceholderLabel::PlaceholderLabel(QLabel* label) @@ -762,7 +718,7 @@ QString NamePage::selectedInstanceName() const } const auto text = ui->instanceName->text().trimmed(); - return sanitizeFileName(text); + return InstanceManager::instance().sanitizeInstanceName(text); } void NamePage::onChanged() @@ -790,12 +746,10 @@ bool NamePage::checkName(QString parentDir, QString name) if (name.isEmpty()) { empty = true; } else { - const QString sanitized = sanitizeFileName(name); - - if (name != sanitized) { - invalid = true; - } else { + if (InstanceManager::instance().validInstanceName(name)) { exists = QDir(parentDir).exists(name); + } else { + invalid = true; } } @@ -963,11 +917,9 @@ bool PathsPage::checkPath( if (path.isEmpty()) { empty = true; } else { - const QString sanitized = sanitizePath(path); + const QDir d(path); - if (path != sanitized) { - invalid = true; - } else { + if (InstanceManager::instance().validInstanceName(d.dirName())) { if (m_dlg.instanceType() == CreateInstanceDialog::Portable) { // the default data path for a portable instance is the application // directory, so it's not an error if it exists @@ -977,6 +929,8 @@ bool PathsPage::checkPath( } else { exists = QDir(path).exists(); } + } else { + invalid = true; } } diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp index b6798fa9..111f948b 100644 --- a/src/instancemanager.cpp +++ b/src/instancemanager.cpp @@ -676,3 +676,12 @@ QString InstanceManager::sanitizeInstanceName(const QString &name) const } return new_name; } + +bool InstanceManager::validInstanceName(const QString& instanceName) const +{ + if (instanceName.isEmpty()) { + return false; + } + + return (instanceName == sanitizeInstanceName(instanceName)); +} diff --git a/src/instancemanager.h b/src/instancemanager.h index 9250ffe9..33e115a7 100644 --- a/src/instancemanager.h +++ b/src/instancemanager.h @@ -63,6 +63,7 @@ public: QString sanitizeInstanceName(const QString &name) const; QString makeUniqueName(const QString& instanceName) const; bool instanceExists(const QString& instanceName) const; + bool validInstanceName(const QString& instanceName) const; QString instancePath(const QString& instanceName) const; private: diff --git a/src/instancemanagerdialog.cpp b/src/instancemanagerdialog.cpp index a038d3c2..aa6bdb04 100644 --- a/src/instancemanagerdialog.cpp +++ b/src/instancemanagerdialog.cpp @@ -8,8 +8,12 @@ #include "shared/appconfig.h" #include <iplugingame.h> +namespace shell = MOBase::shell; + void openInstanceManager(PluginContainer& pc, QWidget* parent) { + //CreateInstanceDialog dlg(pc, parent); + //dlg.exec(); InstanceManagerDialog dlg(pc, parent); dlg.exec(); } @@ -77,12 +81,20 @@ InstanceManagerDialog::InstanceManagerDialog( ui->splitter->setStretchFactor(0, 0); ui->splitter->setStretchFactor(1, 1); + auto* model = new QStandardItemModel; + ui->list->setModel(model); + + m_filter.setEdit(ui->filter); + m_filter.setList(ui->list); + m_filter.setUpdateDelay(false); + m_filter.setFilteredBorder(false); + auto& m = InstanceManager::instance(); for (auto&& d : m.instancePaths()) { auto ii = std::make_unique<InstanceInfo>(d); - ui->list->addItem(ii->name()); + model->appendRow(new QStandardItem(ii->name())); m_instances.push_back(std::move(ii)); } @@ -91,8 +103,12 @@ InstanceManagerDialog::InstanceManagerDialog( } connect(ui->createNew, &QPushButton::clicked, [&]{ createNew(); }); - connect(ui->list, &QListWidget::itemSelectionChanged, [&]{ onSelection(); }); + connect(ui->list->selectionModel(), &QItemSelectionModel::selectionChanged, [&]{ onSelection(); }); //connect(ui->list, &QListWidget::itemActivated, [&]{ openSelectedInstance(); }); + connect(ui->rename, &QPushButton::clicked, [&]{ rename(); }); + connect(ui->exploreLocation, &QPushButton::clicked, [&]{ exploreLocation(); }); + connect(ui->exploreBaseDirectory, &QPushButton::clicked, [&]{ exploreBaseDirectory(); }); + connect(ui->exploreGame, &QPushButton::clicked, [&]{ exploreGame(); }); } InstanceManagerDialog::~InstanceManagerDialog() = default; @@ -105,11 +121,15 @@ void InstanceManagerDialog::select(std::size_t i) const auto& ii = m_instances[i]; fill(*ii); + + ui->list->selectionModel()->select( + m_filter.mapFromSource(m_filter.sourceModel()->index(i, 0)), + QItemSelectionModel::ClearAndSelect); } void InstanceManagerDialog::openSelectedInstance() { - const auto i = singleSelection(); + const auto i = singleSelectionIndex(); if (i == NoSelection) { return; } @@ -117,9 +137,107 @@ void InstanceManagerDialog::openSelectedInstance() InstanceManager::instance().switchToInstance(m_instances[i]->name()); } +void InstanceManagerDialog::rename() +{ + auto* i = singleSelection(); + if (!i) { + return; + } + + auto& m = InstanceManager::instance(); + if (m.currentInstance() == i->name()) { + QMessageBox::information(this, + tr("Rename instance"), tr("The active instance cannot be renamed")); + return; + } + + QDialog dlg(this); + dlg.setWindowTitle(tr("Rename instance")); + + auto* ly = new QVBoxLayout(&dlg); + + auto* bb = new QDialogButtonBox( + QDialogButtonBox::Cancel | QDialogButtonBox::Ok); + + auto* text = new QLineEdit(i->name()); + text->selectAll(); + + auto* error = new QLabel; + + ly->addWidget(new QLabel(tr("Instance name"))); + ly->addWidget(text); + ly->addWidget(error); + ly->addStretch(); + ly->addWidget(bb); + + connect(text, &QLineEdit::textChanged, [&] { + bool okay = false; + + if (!m.validInstanceName(text->text())) { + error->setText(tr("The instance name must be a valid folder name.")); + } else { + const auto name = m.sanitizeInstanceName(text->text()); + + if ((name != i->name()) && m.instanceExists(text->text())) { + error->setText(tr("An instance with this name already exists.")); + } else { + okay = true; + } + } + + error->setVisible(!okay); + bb->button(QDialogButtonBox::Ok)->setEnabled(okay); + }); + + connect(bb, &QDialogButtonBox::accepted, [&]{ dlg.accept(); }); + connect(bb, &QDialogButtonBox::rejected, [&]{ dlg.reject(); }); + + dlg.resize({400, 120}); + if (dlg.exec() != QDialog::Accepted) { + return; + } + + + const QString newName = m.sanitizeInstanceName(text->text()); + const QString src = QDir::toNativeSeparators(i->location()); + const QString dest = QDir::toNativeSeparators( + QFileInfo(i->location()).dir().path() + "/" + newName); + + const auto r = shell::Rename(src, dest, false); + if (!r) { + QMessageBox::critical( + this, tr("Error"), + tr("Failed to rename \"%1\" to \"%2\": %3") + .arg(src).arg(dest).arg(r.toString())); + + return; + } +} + +void InstanceManagerDialog::exploreLocation() +{ + if (const auto* i=singleSelection()) { + shell::Explore(i->location()); + } +} + +void InstanceManagerDialog::exploreBaseDirectory() +{ + if (const auto* i=singleSelection()) { + shell::Explore(i->baseDirectory()); + } +} + +void InstanceManagerDialog::exploreGame() +{ + if (const auto* i=singleSelection()) { + shell::Explore(i->gamePath()); + } +} + void InstanceManagerDialog::onSelection() { - const auto i = singleSelection(); + const auto i = singleSelectionIndex(); if (i == NoSelection) { return; } @@ -133,14 +251,36 @@ void InstanceManagerDialog::createNew() dlg.exec(); } -std::size_t InstanceManagerDialog::singleSelection() const +std::size_t InstanceManagerDialog::singleSelectionIndex() const { - const auto sel = ui->list->selectionModel()->selectedIndexes(); + const auto sel = m_filter.mapSelectionToSource( + ui->list->selectionModel()->selection()); + if (sel.size() != 1) { return NoSelection; } - return static_cast<std::size_t>(sel[0].row()); + return static_cast<std::size_t>(sel.indexes()[0].row()); +} + +InstanceInfo* InstanceManagerDialog::singleSelection() +{ + const auto i = singleSelectionIndex(); + if (i == NoSelection) { + return nullptr; + } + + return m_instances[i].get(); +} + +const InstanceInfo* InstanceManagerDialog::singleSelection() const +{ + const auto i = singleSelectionIndex(); + if (i == NoSelection) { + return nullptr; + } + + return m_instances[i].get(); } void InstanceManagerDialog::fill(const InstanceInfo& ii) diff --git a/src/instancemanagerdialog.h b/src/instancemanagerdialog.h index 6dbf015f..529a99b8 100644 --- a/src/instancemanagerdialog.h +++ b/src/instancemanagerdialog.h @@ -1,6 +1,7 @@ #ifndef MODORGANIZER_INSTANCEMANAGERDIALOG_INCLUDED #define MODORGANIZER_INSTANCEMANAGERDIALOG_INCLUDED +#include <filterwidget.h> #include <QDialog> namespace Ui { class InstanceManagerDialog; }; @@ -21,17 +22,26 @@ public: void select(std::size_t i); void openSelectedInstance(); + void rename(); + void exploreLocation(); + void exploreBaseDirectory(); + void exploreGame(); + private: static const std::size_t NoSelection = -1; std::unique_ptr<Ui::InstanceManagerDialog> ui; const PluginContainer& m_pc; std::vector<std::unique_ptr<InstanceInfo>> m_instances; + MOBase::FilterWidget m_filter; void onSelection(); void createNew(); - std::size_t singleSelection() const; + std::size_t singleSelectionIndex() const; + InstanceInfo* singleSelection(); + const InstanceInfo* singleSelection() const; + void fill(const InstanceInfo& ii); }; diff --git a/src/instancemanagerdialog.ui b/src/instancemanagerdialog.ui index 146b5fb5..08894ec1 100644 --- a/src/instancemanagerdialog.ui +++ b/src/instancemanagerdialog.ui @@ -103,7 +103,32 @@ <number>0</number> </property> <item> - <widget class="QListWidget" name="list"/> + <widget class="QListView" name="list"/> + </item> + <item> + <widget class="QWidget" name="widget_5" native="true"> + <layout class="QHBoxLayout" name="horizontalLayout_4"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QLineEdit" name="filter"> + <property name="placeholderText"> + <string>Filter</string> + </property> + </widget> + </item> + </layout> + </widget> </item> </layout> </widget> |
