diff --git a/src/tiled/documentmanager.cpp b/src/tiled/documentmanager.cpp index 1e17e41a7a..05c425c7ef 100644 --- a/src/tiled/documentmanager.cpp +++ b/src/tiled/documentmanager.cpp @@ -1062,20 +1062,17 @@ void DocumentManager::tabContextMenuRequested(const QPoint &pos) menu.addSeparator(); - QAction *closeTab = menu.addAction(tr("Close")); - closeTab->setIcon(QIcon(QStringLiteral(":/images/16/window-close.png"))); - Utils::setThemeIcon(closeTab, "window-close"); - connect(closeTab, &QAction::triggered, [this, index] { + QAction *closeTab = menu.addAction(tr("Close"), [this, index] { documentCloseRequested(index); }); + closeTab->setIcon(QIcon(QStringLiteral(":/images/16/window-close.png"))); + Utils::setThemeIcon(closeTab, "window-close"); - QAction *closeOtherTabs = menu.addAction(tr("Close Other Tabs")); - connect(closeOtherTabs, &QAction::triggered, [this, index] { + menu.addAction(tr("Close Other Tabs"), [this, index] { closeOtherDocuments(index); }); - QAction *closeTabsToRight = menu.addAction(tr("Close Tabs to the Right")); - connect(closeTabsToRight, &QAction::triggered, [this, index] { + menu.addAction(tr("Close Tabs to the Right"), [this, index] { closeDocumentsToRight(index); }); diff --git a/src/tiled/fileedit.cpp b/src/tiled/fileedit.cpp index 171af383e4..e8f65daffa 100644 --- a/src/tiled/fileedit.cpp +++ b/src/tiled/fileedit.cpp @@ -45,6 +45,7 @@ FileEdit::FileEdit(QWidget *parent) QToolButton *button = new QToolButton(this); button->setText(QLatin1String("...")); button->setAutoRaise(true); + button->setToolTip(tr("Choose")); layout->addWidget(mLineEdit); layout->addWidget(button); @@ -111,23 +112,40 @@ void FileEdit::validate() const QUrl url(fileUrl()); QColor textColor = mOkTextColor; - if (url.isLocalFile() && !QFile::exists(url.toLocalFile())) - textColor = mErrorTextColor; + if (url.isLocalFile()) { + const QString localFile = url.toLocalFile(); + if (!QFile::exists(localFile) || (mIsDirectory && !QFileInfo(localFile).isDir())) + textColor = mErrorTextColor; + } QPalette palette = mLineEdit->palette(); - palette.setColor(QPalette::Active, QPalette::Text, textColor); + palette.setColor(QPalette::Text, textColor); mLineEdit->setPalette(palette); } void FileEdit::buttonClicked() { - QUrl url = QFileDialog::getOpenFileUrl(window(), - tr("Choose a File"), - fileUrl(), - mFilter); - if (url.isEmpty()) + QUrl url; + + if (mIsDirectory) { + url = QFileDialog::getExistingDirectoryUrl(window(), + tr("Choose a Folder"), + fileUrl()); + } else { + url = QFileDialog::getOpenFileUrl(window(), + tr("Choose a File"), + fileUrl(), + mFilter); + } + + if (url.isEmpty()) { + validate(); return; + } + setFileUrl(url); + validate(); // validate even if url didn't change, since directory may have been created + emit fileUrlChanged(url); } diff --git a/src/tiled/fileedit.h b/src/tiled/fileedit.h index 7044677c07..44565e2c0b 100644 --- a/src/tiled/fileedit.h +++ b/src/tiled/fileedit.h @@ -44,6 +44,9 @@ class FileEdit : public QWidget void setFilter(const QString &filter) { mFilter = filter; } QString filter() const { return mFilter; } + void setIsDirectory(bool isDirectory); + bool isDirectory() const; + signals: void fileUrlChanged(const QUrl &url); @@ -60,8 +63,20 @@ class FileEdit : public QWidget QLineEdit *mLineEdit; QString mFilter; + bool mIsDirectory = false; QColor mOkTextColor; QColor mErrorTextColor; }; + +inline void FileEdit::setIsDirectory(bool isDirectory) +{ + mIsDirectory = isDirectory; +} + +inline bool FileEdit::isDirectory() const +{ + return mIsDirectory; +} + } // namespace Tiled diff --git a/src/tiled/main.cpp b/src/tiled/main.cpp index 8504ee16bb..dcf56dfafc 100644 --- a/src/tiled/main.cpp +++ b/src/tiled/main.cpp @@ -494,7 +494,7 @@ int main(int argc, char *argv[]) QObject::connect(&a, &TiledApplication::fileOpenRequest, &w, [&] (const QString &file) { w.openFile(file); }); - initializePluginsAndExtensions(); + PluginManager::instance()->loadPlugins(); w.initializeSession(); diff --git a/src/tiled/mainwindow.cpp b/src/tiled/mainwindow.cpp index 5279ec2266..7647fd8942 100644 --- a/src/tiled/mainwindow.cpp +++ b/src/tiled/mainwindow.cpp @@ -60,7 +60,9 @@ #include "objecttypeseditor.h" #include "offsetmapdialog.h" #include "projectdock.h" +#include "projectpropertiesdialog.h" #include "resizedialog.h" +#include "scriptmanager.h" #include "templatesdock.h" #include "terrain.h" #include "tile.h" @@ -274,10 +276,10 @@ MainWindow::MainWindow(QWidget *parent, Qt::WindowFlags flags) ActionManager::registerAction(mUi->actionLabelsForAllObjects, "LabelsForAllObjects"); ActionManager::registerAction(mUi->actionLabelsForSelectedObjects, "LabelsForSelectedObjects"); ActionManager::registerAction(mUi->actionLoadWorld, "LoadWorld"); - ActionManager::registerAction(mUi->actionNewWorld, "NewWorld"); ActionManager::registerAction(mUi->actionMapProperties, "MapProperties"); ActionManager::registerAction(mUi->actionNewMap, "NewMap"); ActionManager::registerAction(mUi->actionNewTileset, "NewTileset"); + ActionManager::registerAction(mUi->actionNewWorld, "NewWorld"); ActionManager::registerAction(mUi->actionNoLabels, "NoLabels"); ActionManager::registerAction(mUi->actionOffsetMap, "OffsetMap"); ActionManager::registerAction(mUi->actionOpen, "Open"); @@ -285,6 +287,7 @@ MainWindow::MainWindow(QWidget *parent, Qt::WindowFlags flags) ActionManager::registerAction(mUi->actionPaste, "Paste"); ActionManager::registerAction(mUi->actionPasteInPlace, "PasteInPlace"); ActionManager::registerAction(mUi->actionPreferences, "Preferences"); + ActionManager::registerAction(mUi->actionProjectProperties, "ProjectProperties"); ActionManager::registerAction(mUi->actionQuit, "Quit"); ActionManager::registerAction(mUi->actionRefreshProjectFolders, "RefreshProjectFolders"); ActionManager::registerAction(mUi->actionReload, "Reload"); @@ -655,6 +658,7 @@ MainWindow::MainWindow(QWidget *parent, Qt::WindowFlags flags) connect(mUi->actionAddFolderToProject, &QAction::triggered, mProjectDock, &ProjectDock::addFolderToProject); connect(mUi->actionRefreshProjectFolders, &QAction::triggered, mProjectDock, &ProjectDock::refreshProjectFolders); connect(mUi->actionClearRecentProjects, &QAction::triggered, preferences, &Preferences::clearRecentProjects); + connect(mUi->actionProjectProperties, &QAction::triggered, this, &MainWindow::projectProperties); connect(mUi->actionDocumentation, &QAction::triggered, this, &MainWindow::openDocumentation); connect(mUi->actionForum, &QAction::triggered, this, &MainWindow::openForum); @@ -700,6 +704,7 @@ MainWindow::MainWindow(QWidget *parent, Qt::WindowFlags flags) setThemeIcon(mUi->actionCloseProject, "window-close"); setThemeIcon(mUi->actionAddFolderToProject, "folder-new"); setThemeIcon(mUi->actionRefreshProjectFolders, "view-refresh"); + setThemeIcon(mUi->actionProjectProperties, "document-properties"); setThemeIcon(mUi->actionDocumentation, "help-contents"); setThemeIcon(mUi->actionAbout, "help-about"); @@ -909,10 +914,7 @@ void MainWindow::newMap() void MainWindow::initializeSession() { - auto prefs = Preferences::instance(); - if (!prefs->restoreSessionOnStartup()) - return; - + const auto prefs = Preferences::instance(); const auto &session = prefs->session(); // Restore associated project if applicable @@ -922,7 +924,13 @@ void MainWindow::initializeSession() updateWindowTitle(); } - restoreSession(); + // Script manager initialization is delayed until after the project has + // been loaded, to avoid immediately having to reset the engine again after + // adding the project's extension path. + ScriptManager::instance().initialize(); + + if (prefs->restoreSessionOnStartup()) + restoreSession(); } bool MainWindow::openFile(const QString &fileName, FileFormat *fileFormat) @@ -956,6 +964,11 @@ bool MainWindow::openFile(const QString &fileName, FileFormat *fileFormat) return true; } +Project &MainWindow::project() const +{ + return mProjectDock->project(); +} + void MainWindow::openFileDialog() { SessionOption lastUsedOpenFilter { "file.lastUsedOpenFilter" }; @@ -1333,6 +1346,8 @@ void MainWindow::switchProject(Project project) mProjectDock->setProject(std::move(project)); prefs->switchSession(std::move(session)); + ScriptManager::instance().refreshExtensionsPaths(); + restoreSession(); updateWindowTitle(); updateActions(); @@ -1355,6 +1370,12 @@ void MainWindow::restoreSession() mProjectDock->setExpandedPaths(session.expandedProjectPaths); } +void MainWindow::projectProperties() +{ + if (ProjectPropertiesDialog(mProjectDock->project(), this).exec() == QDialog::Accepted) + ScriptManager::instance().refreshExtensionsPaths(); +} + void MainWindow::cut() { if (auto editor = mDocumentManager->currentEditor()) @@ -1965,14 +1986,12 @@ void MainWindow::updateWindowTitle() void MainWindow::showDonationDialog() { - DonationDialog donationDialog(this); - donationDialog.exec(); + DonationDialog(this).exec(); } void MainWindow::aboutTiled() { - AboutDialog aboutDialog(this); - aboutDialog.exec(); + AboutDialog(this).exec(); } void MainWindow::retranslateUi() diff --git a/src/tiled/mainwindow.h b/src/tiled/mainwindow.h index 09993d21c0..a17732e60c 100644 --- a/src/tiled/mainwindow.h +++ b/src/tiled/mainwindow.h @@ -91,6 +91,8 @@ class MainWindow : public QMainWindow */ bool openFile(const QString &fileName, FileFormat *fileFormat = nullptr); + Project &project() const; + static MainWindow *instance(); protected: @@ -125,6 +127,7 @@ class MainWindow : public QMainWindow void closeProject(); void switchProject(Project project); void restoreSession(); + void projectProperties(); void cut(); void copy(); diff --git a/src/tiled/mainwindow.ui b/src/tiled/mainwindow.ui index e062b188b6..c272777382 100644 --- a/src/tiled/mainwindow.ui +++ b/src/tiled/mainwindow.ui @@ -129,7 +129,7 @@ - + @@ -214,6 +214,8 @@ + + @@ -760,6 +762,15 @@ Ctrl+Shift+M + + + + :/images/16/document-properties.png:/images/16/document-properties.png + + + Project &Properties... + + diff --git a/src/tiled/newsbutton.cpp b/src/tiled/newsbutton.cpp index 56f49a7aac..72a2a1fe32 100644 --- a/src/tiled/newsbutton.cpp +++ b/src/tiled/newsbutton.cpp @@ -106,7 +106,10 @@ void NewsButton::showNewsMenu() auto &feed = NewsFeed::instance(); for (const NewsItem &newsItem : feed.newsItems()) { - QAction *action = newsFeedMenu->addAction(newsItem.title); + QAction *action = newsFeedMenu->addAction(newsItem.title, [=] { + QDesktopServices::openUrl(newsItem.link); + NewsFeed::instance().markRead(newsItem); + }); if (feed.isUnread(newsItem)) { QFont f = action->font(); @@ -116,11 +119,6 @@ void NewsButton::showNewsMenu() } else { action->setIcon(mReadIcon); } - - connect(action, &QAction::triggered, [=] { - QDesktopServices::openUrl(newsItem.link); - NewsFeed::instance().markRead(newsItem); - }); } newsFeedMenu->addSeparator(); diff --git a/src/tiled/project.cpp b/src/tiled/project.cpp index 22af3b7887..6e899e7769 100644 --- a/src/tiled/project.cpp +++ b/src/tiled/project.cpp @@ -41,6 +41,12 @@ Project::Project() bool Project::save(const QString &fileName) { + QString extensionsPath = mExtensionsPath; + + // Initialize extensions path to its default value + if (mFileName.isEmpty() && extensionsPath.isEmpty()) + extensionsPath = QFileInfo(fileName).dir().filePath(QLatin1String("extensions")); + QJsonObject project; const QDir dir = QFileInfo(fileName).dir(); @@ -51,6 +57,7 @@ bool Project::save(const QString &fileName) folders.append(relative(dir, folder)); project.insert(QLatin1String("folders"), folders); + project.insert(QLatin1String("extensionsPath"), relative(dir, extensionsPath)); QJsonDocument document(project); @@ -63,6 +70,7 @@ bool Project::save(const QString &fileName) return false; mFileName = fileName; + mExtensionsPath = extensionsPath; return true; } @@ -85,6 +93,8 @@ bool Project::load(const QString &fileName) QJsonObject project = document.object(); + mExtensionsPath = QDir::cleanPath(dir.absoluteFilePath(project.value(QLatin1String("extensionsFolder")).toString(QLatin1String("extensions")))); + const QJsonArray folders = project.value(QLatin1String("folders")).toArray(); for (const QJsonValue &folderValue : folders) mFolders.append(QDir::cleanPath(dir.absoluteFilePath(folderValue.toString()))); diff --git a/src/tiled/project.h b/src/tiled/project.h index 9cf08433ef..4e5e727ab4 100644 --- a/src/tiled/project.h +++ b/src/tiled/project.h @@ -35,10 +35,10 @@ class Project void addFolder(const QString &folder); void removeFolder(int index); - void refreshFolders(); - const QStringList &folders() const; + QString mExtensionsPath; + private: QString mFileName; QStringList mFolders; diff --git a/src/tiled/projectpropertiesdialog.cpp b/src/tiled/projectpropertiesdialog.cpp new file mode 100644 index 0000000000..c28183c4c7 --- /dev/null +++ b/src/tiled/projectpropertiesdialog.cpp @@ -0,0 +1,69 @@ +/* + * projectpropertiesdialog.cpp + * Copyright 2020, Thorbjørn Lindeijer + * + * This file is part of Tiled. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include "projectpropertiesdialog.h" +#include "ui_projectpropertiesdialog.h" + +#include "project.h" +#include "utils.h" +#include "varianteditorfactory.h" +#include "variantpropertymanager.h" + +#include + +namespace Tiled { + +ProjectPropertiesDialog::ProjectPropertiesDialog(Project &project, QWidget *parent) + : QDialog(parent) + , ui(new Ui::ProjectPropertiesDialog) + , mProject(project) +{ + ui->setupUi(this); + + auto variantPropertyManager = new VariantPropertyManager(this); + auto variantEditorFactory = new VariantEditorFactory(this); + auto groupPropertyManager = new QtGroupPropertyManager(this); + + ui->propertyBrowser->setFactoryForManager(variantPropertyManager, + variantEditorFactory); + + auto groupProperty = groupPropertyManager->addProperty(tr("Extensions")); + mExtensionPathProperty = variantPropertyManager->addProperty(filePathTypeId(), tr("Directory")); + mExtensionPathProperty->setValue(project.mExtensionsPath); + mExtensionPathProperty->setAttribute(QStringLiteral("directory"), true); + + groupProperty->addSubProperty(mExtensionPathProperty); + + ui->propertyBrowser->addProperty(groupProperty); +} + +ProjectPropertiesDialog::~ProjectPropertiesDialog() +{ + delete ui; +} + +void ProjectPropertiesDialog::accept() +{ + mProject.mExtensionsPath = mExtensionPathProperty->value().toString(); + + QDialog::accept(); +} + +} // namespace Tiled diff --git a/src/tiled/projectpropertiesdialog.h b/src/tiled/projectpropertiesdialog.h new file mode 100644 index 0000000000..756997853d --- /dev/null +++ b/src/tiled/projectpropertiesdialog.h @@ -0,0 +1,52 @@ +/* + * projectpropertiesdialog.h + * Copyright 2020, Thorbjørn Lindeijer + * + * This file is part of Tiled. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#pragma once + +#include + +class QtVariantProperty; + +namespace Ui { +class ProjectPropertiesDialog; +} + +namespace Tiled { + +class Project; + +class ProjectPropertiesDialog : public QDialog +{ + Q_OBJECT + +public: + explicit ProjectPropertiesDialog(Project &project, QWidget *parent = nullptr); + ~ProjectPropertiesDialog() override; + + void accept() override; + +private: + Ui::ProjectPropertiesDialog *ui; + + Project &mProject; + QtVariantProperty *mExtensionPathProperty; +}; + +} // namespace Tiled diff --git a/src/tiled/projectpropertiesdialog.ui b/src/tiled/projectpropertiesdialog.ui new file mode 100644 index 0000000000..5194942dc4 --- /dev/null +++ b/src/tiled/projectpropertiesdialog.ui @@ -0,0 +1,75 @@ + + + ProjectPropertiesDialog + + + + 0 + 0 + 575 + 168 + + + + Project Properties + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + QtGroupBoxPropertyBrowser + QWidget +
QtGroupBoxPropertyBrowser
+ 1 +
+
+ + + + buttonBox + accepted() + ProjectPropertiesDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + ProjectPropertiesDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + +
diff --git a/src/tiled/scriptmanager.cpp b/src/tiled/scriptmanager.cpp index b826d325ed..840fd05819 100644 --- a/src/tiled/scriptmanager.cpp +++ b/src/tiled/scriptmanager.cpp @@ -30,8 +30,11 @@ #include "editabletilelayer.h" #include "editabletileset.h" #include "logginginterface.h" +#include "mainwindow.h" #include "mapeditor.h" #include "mapview.h" +#include "preferences.h" +#include "project.h" #include "regionvaluetype.h" #include "scriptedaction.h" #include "scriptedfileformat.h" @@ -71,7 +74,7 @@ void ScriptManager::deleteInstance() } /* - * mJSEngine needs to be QQmlEngine for the "Qt" module to be available, which + * mEngine needs to be QQmlEngine for the "Qt" module to be available, which * is necessary to pass things like QSize or QPoint to some API functions * (using Qt.size and Qt.point). * @@ -81,9 +84,6 @@ void ScriptManager::deleteInstance() ScriptManager::ScriptManager(QObject *parent) : QObject(parent) - , mEngine(new QQmlEngine(this)) - , mModule(new ScriptModule(this)) - , mTempCount(0) { qRegisterMetaType(); qRegisterMetaType(); @@ -114,34 +114,21 @@ ScriptManager::ScriptManager(QObject *parent) connect(&mWatcher, &FileSystemWatcher::pathsChanged, this, &ScriptManager::scriptFilesChanged); - const QString configLocation { QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation) }; + const QString configLocation { Preferences::configLocation() }; if (!configLocation.isEmpty()) { mExtensionsPath = QDir{configLocation}.filePath(QStringLiteral("extensions")); if (!QFile::exists(mExtensionsPath)) QDir().mkpath(mExtensionsPath); - - mExtensionsPaths.append(mExtensionsPath); } } void ScriptManager::initialize() { - QJSValue globalObject = mEngine->globalObject(); - globalObject.setProperty(QStringLiteral("tiled"), mEngine->newQObject(mModule)); -#if QT_VERSION >= 0x050800 - globalObject.setProperty(QStringLiteral("TextFile"), mEngine->newQMetaObject()); - globalObject.setProperty(QStringLiteral("BinaryFile"), mEngine->newQMetaObject()); - globalObject.setProperty(QStringLiteral("Layer"), mEngine->newQMetaObject()); - globalObject.setProperty(QStringLiteral("MapObject"), mEngine->newQMetaObject()); - globalObject.setProperty(QStringLiteral("ObjectGroup"), mEngine->newQMetaObject()); - globalObject.setProperty(QStringLiteral("Terrain"), mEngine->newQMetaObject()); - globalObject.setProperty(QStringLiteral("Tile"), mEngine->newQMetaObject()); - globalObject.setProperty(QStringLiteral("TileLayer"), mEngine->newQMetaObject()); - globalObject.setProperty(QStringLiteral("TileMap"), mEngine->newQMetaObject()); - globalObject.setProperty(QStringLiteral("Tileset"), mEngine->newQMetaObject()); -#endif + Q_ASSERT(!mEngine && !mModule); + refreshExtensionsPaths(); + setupEngine(); loadExtensions(); } @@ -275,15 +262,37 @@ void ScriptManager::reset() Tiled::INFO(tr("Resetting script engine")); mWatcher.clear(); + delete mEngine; delete mModule; + mEngine = nullptr; + mModule = nullptr; mTempCount = 0; + setupEngine(); + loadExtensions(); +} + +void ScriptManager::setupEngine() +{ mEngine = new QQmlEngine(this); mModule = new ScriptModule(this); - initialize(); + QJSValue globalObject = mEngine->globalObject(); + globalObject.setProperty(QStringLiteral("tiled"), mEngine->newQObject(mModule)); +#if QT_VERSION >= 0x050800 + globalObject.setProperty(QStringLiteral("TextFile"), mEngine->newQMetaObject()); + globalObject.setProperty(QStringLiteral("BinaryFile"), mEngine->newQMetaObject()); + globalObject.setProperty(QStringLiteral("Layer"), mEngine->newQMetaObject()); + globalObject.setProperty(QStringLiteral("MapObject"), mEngine->newQMetaObject()); + globalObject.setProperty(QStringLiteral("ObjectGroup"), mEngine->newQMetaObject()); + globalObject.setProperty(QStringLiteral("Terrain"), mEngine->newQMetaObject()); + globalObject.setProperty(QStringLiteral("Tile"), mEngine->newQMetaObject()); + globalObject.setProperty(QStringLiteral("TileLayer"), mEngine->newQMetaObject()); + globalObject.setProperty(QStringLiteral("TileMap"), mEngine->newQMetaObject()); + globalObject.setProperty(QStringLiteral("Tileset"), mEngine->newQMetaObject()); +#endif } void ScriptManager::scriptFilesChanged(const QStringList &scriptFiles) @@ -292,4 +301,33 @@ void ScriptManager::scriptFilesChanged(const QStringList &scriptFiles) reset(); } +void ScriptManager::refreshExtensionsPaths() +{ + QStringList extensionsPaths; + + if (!mExtensionsPath.isEmpty()) + extensionsPaths.append(mExtensionsPath); + + // Add extensions path from project + auto &projectExtensionsPath = MainWindow::instance()->project().mExtensionsPath; + if (!projectExtensionsPath.isEmpty()) { + const QFileInfo info(projectExtensionsPath); + if (info.exists() && info.isDir()) + extensionsPaths.append(projectExtensionsPath); + } + + extensionsPaths.sort(); + extensionsPaths.removeDuplicates(); + + if (extensionsPaths == mExtensionsPaths) + return; + + mExtensionsPaths.swap(extensionsPaths); + + if (mEngine) { + Tiled::INFO(tr("Extensions paths changed: %1").arg(mExtensionsPaths.join(QLatin1String(", ")))); + reset(); + } +} + } // namespace Tiled diff --git a/src/tiled/scriptmanager.h b/src/tiled/scriptmanager.h index 63d8ed9cf6..fa81ee3a0d 100644 --- a/src/tiled/scriptmanager.h +++ b/src/tiled/scriptmanager.h @@ -62,23 +62,25 @@ class ScriptManager : public QObject void throwError(const QString &message); void throwNullArgError(int argNumber); - void reset(); + void refreshExtensionsPaths(); private: explicit ScriptManager(QObject *parent = nullptr); ~ScriptManager() = default; + void reset(); + void setupEngine(); void scriptFilesChanged(const QStringList &scriptFiles); void loadExtensions(); void loadExtension(const QString &path); - QJSEngine *mEngine; - ScriptModule *mModule; + QJSEngine *mEngine = nullptr; + ScriptModule *mModule = nullptr; FileSystemWatcher mWatcher; QString mExtensionsPath; QStringList mExtensionsPaths; - int mTempCount; + int mTempCount = 0; static ScriptManager *mInstance; }; diff --git a/src/tiled/tiled.pro b/src/tiled/tiled.pro index 53e79930d6..e743cae28a 100644 --- a/src/tiled/tiled.pro +++ b/src/tiled/tiled.pro @@ -190,6 +190,7 @@ SOURCES += aboutdialog.cpp \ project.cpp \ projectdock.cpp \ projectmodel.cpp \ + projectpropertiesdialog.cpp \ preferencesdialog.cpp \ propertiesdock.cpp \ propertybrowser.cpp \ @@ -427,6 +428,7 @@ HEADERS += aboutdialog.h \ project.h \ projectdock.h \ projectmodel.h \ + projectpropertiesdialog.h \ propertiesdock.h \ propertybrowser.h \ raiselowerhelper.h \ @@ -529,6 +531,7 @@ FORMS += aboutdialog.ui \ objecttypeseditor.ui \ offsetmapdialog.ui \ preferencesdialog.ui \ + projectpropertiesdialog.ui \ resizedialog.ui \ shortcutsettingspage.ui \ texteditordialog.ui \ diff --git a/src/tiled/tiled.qbs b/src/tiled/tiled.qbs index e6cde3c584..615372b01f 100644 --- a/src/tiled/tiled.qbs +++ b/src/tiled/tiled.qbs @@ -380,6 +380,9 @@ QtGuiApplication { "projectdock.h", "projectmodel.cpp", "projectmodel.h", + "projectpropertiesdialog.cpp", + "projectpropertiesdialog.h", + "projectpropertiesdialog.ui", "propertiesdock.cpp", "propertiesdock.h", "propertybrowser.cpp", diff --git a/src/tiled/utils.cpp b/src/tiled/utils.cpp index 94a1b97d5e..9f5c8d0337 100644 --- a/src/tiled/utils.cpp +++ b/src/tiled/utils.cpp @@ -291,14 +291,12 @@ void addFileManagerActions(QMenu &menu, const QString &fileName) if (fileName.isEmpty()) return; - QAction *copyPath = menu.addAction(QCoreApplication::translate("Utils", "Copy File Path")); - QObject::connect(copyPath, &QAction::triggered, [fileName] { + menu.addAction(QCoreApplication::translate("Utils", "Copy File Path"), [fileName] { QClipboard *clipboard = QApplication::clipboard(); clipboard->setText(QDir::toNativeSeparators(fileName)); }); - QAction *openFolder = menu.addAction(QCoreApplication::translate("Utils", "Open Containing Folder...")); - QObject::connect(openFolder, &QAction::triggered, [fileName] { + menu.addAction(QCoreApplication::translate("Utils", "Open Containing Folder..."), [fileName] { showInFileManager(fileName); }); } diff --git a/src/tiled/varianteditorfactory.cpp b/src/tiled/varianteditorfactory.cpp index 38f103f834..fb8d0d5df8 100644 --- a/src/tiled/varianteditorfactory.cpp +++ b/src/tiled/varianteditorfactory.cpp @@ -111,6 +111,7 @@ QWidget *VariantEditorFactory::createEditor(QtVariantPropertyManager *manager, FilePath filePath = manager->value(property).value(); editor->setFileUrl(filePath.url); editor->setFilter(manager->attributeValue(property, QLatin1String("filter")).toString()); + editor->setIsDirectory(manager->attributeValue(property, QLatin1String("directory")).toBool()); mCreatedFileEdits[property].append(editor); mFileEditToProperty[editor] = property; @@ -245,6 +246,9 @@ void VariantEditorFactory::slotPropertyAttributeChanged(QtProperty *property, if (attribute == QLatin1String("filter")) { for (FileEdit *edit : qAsConst(mCreatedFileEdits)[property]) edit->setFilter(value.toString()); + } else if (attribute == QLatin1String("directory")) { + for (FileEdit *edit : qAsConst(mCreatedFileEdits)[property]) + edit->setIsDirectory(value.toBool()); } } else if (mCreatedComboBoxes.contains(property)) { diff --git a/src/tiled/variantpropertymanager.cpp b/src/tiled/variantpropertymanager.cpp index 4c5f0c4f02..8761ad3fca 100644 --- a/src/tiled/variantpropertymanager.cpp +++ b/src/tiled/variantpropertymanager.cpp @@ -53,6 +53,8 @@ MapObject *DisplayObjectRef::object() const VariantPropertyManager::VariantPropertyManager(QObject *parent) : QtVariantPropertyManager(parent) + , mFilterAttribute(QStringLiteral("filter")) + , mDirectoryAttribute(QStringLiteral("directory")) , mSuggestionsAttribute(QStringLiteral("suggestions")) , mMultilineAttribute(QStringLiteral("multiline")) , mImageMissingIcon(QStringLiteral("://images/16/image-missing.png")) @@ -68,7 +70,7 @@ VariantPropertyManager::VariantPropertyManager(QObject *parent) QVariant VariantPropertyManager::value(const QtProperty *property) const { if (mValues.contains(property)) - return mValues[property].value; + return mValues[property]; if (m_alignValues.contains(property)) return QVariant::fromValue(m_alignValues.value(property)); return QtVariantPropertyManager::value(property); @@ -100,8 +102,9 @@ int VariantPropertyManager::valueType(int propertyType) const QStringList VariantPropertyManager::attributes(int propertyType) const { if (propertyType == filePathTypeId()) { - return QStringList { - QStringLiteral("filter") + return { + mFilterAttribute, + mDirectoryAttribute }; } return QtVariantPropertyManager::attributes(propertyType); @@ -111,8 +114,10 @@ int VariantPropertyManager::attributeType(int propertyType, const QString &attribute) const { if (propertyType == filePathTypeId()) { - if (attribute == QLatin1String("filter")) + if (attribute == mFilterAttribute) return QVariant::String; + if (attribute == mDirectoryAttribute) + return QVariant::Bool; return 0; } return QtVariantPropertyManager::attributeType(propertyType, attribute); @@ -121,9 +126,11 @@ int VariantPropertyManager::attributeType(int propertyType, QVariant VariantPropertyManager::attributeValue(const QtProperty *property, const QString &attribute) const { - if (mValues.contains(property)) { - if (attribute == QLatin1String("filter")) - return mValues[property].filter; + if (mFilePathAttributes.contains(property)) { + if (attribute == mFilterAttribute) + return mFilePathAttributes[property].filter; + if (attribute == mDirectoryAttribute) + return mFilePathAttributes[property].directory; return QVariant(); } if (mStringAttributes.contains(property)) { @@ -169,7 +176,7 @@ QString VariantPropertyManager::objectRefLabel(const MapObject *object) const QString VariantPropertyManager::valueText(const QtProperty *property) const { if (mValues.contains(property)) { - QVariant value = mValues[property].value; + QVariant value = mValues[property]; int typeId = propertyType(property); if (typeId == displayObjectRefTypeId()) { @@ -222,7 +229,7 @@ QString VariantPropertyManager::valueText(const QtProperty *property) const QIcon VariantPropertyManager::valueIcon(const QtProperty *property) const { if (mValues.contains(property)) { - QVariant value = mValues[property].value; + QVariant value = mValues[property]; QString filePath; int typeId = propertyType(property); @@ -257,11 +264,10 @@ QIcon VariantPropertyManager::valueIcon(const QtProperty *property) const void VariantPropertyManager::setValue(QtProperty *property, const QVariant &value) { if (mValues.contains(property)) { - Data d = mValues[property]; - if (d.value == value) + QVariant &storedValue = mValues[property]; + if (storedValue == value) return; - d.value = value; - mValues[property] = d; + storedValue = value; emit propertyChanged(property); emit valueChanged(property, value); return; @@ -297,17 +303,22 @@ void VariantPropertyManager::setAttribute(QtProperty *property, const QString &attribute, const QVariant &val) { - if (mValues.contains(property)) { - if (attribute == QLatin1String("filter")) { + if (mFilePathAttributes.contains(property)) { + FilePathAttributes &attributes = mFilePathAttributes[property]; + if (attribute == mFilterAttribute) { if (val.type() != QVariant::String && !val.canConvert(QVariant::String)) return; - QString str = val.toString(); - Data d = mValues[property]; - if (d.filter == str) + QString filter = val.toString(); + if (attributes.filter == filter) + return; + attributes.filter = filter; + emit attributeChanged(property, attribute, filter); + } else if (attribute == mDirectoryAttribute) { + bool directory = val.toBool(); + if (attributes.directory == directory) return; - d.filter = str; - mValues[property] = d; - emit attributeChanged(property, attribute, str); + attributes.directory = directory; + emit attributeChanged(property, attribute, directory); } return; } @@ -332,7 +343,9 @@ void VariantPropertyManager::initializeProperty(QtProperty *property) if (type == filePathTypeId() || type == displayObjectRefTypeId() || type == tilesetParametersTypeId()) { - mValues[property] = Data(); + mValues[property] = QVariant(); + if (type == filePathTypeId()) + mFilePathAttributes[property] = FilePathAttributes(); } else if (type == QVariant::String) { mStringAttributes[property] = StringAttributes(); } else if (type == alignmentTypeId()) { @@ -365,6 +378,7 @@ void VariantPropertyManager::initializeProperty(QtProperty *property) void VariantPropertyManager::uninitializeProperty(QtProperty *property) { mValues.remove(property); + mFilePathAttributes.remove(property); mStringAttributes.remove(property); m_alignValues.remove(property); diff --git a/src/tiled/variantpropertymanager.h b/src/tiled/variantpropertymanager.h index 9e6fc8c733..2c800e143f 100644 --- a/src/tiled/variantpropertymanager.h +++ b/src/tiled/variantpropertymanager.h @@ -91,11 +91,13 @@ public slots: void slotPropertyDestroyed(QtProperty *property); QString objectRefLabel(const MapObject *object) const; - struct Data { - QVariant value; + QMap mValues; + + struct FilePathAttributes { QString filter; + bool directory = false; }; - QMap mValues; + QMap mFilePathAttributes; struct StringAttributes { QStringList suggestions; @@ -116,6 +118,8 @@ public slots: PropertyToPropertyMap m_alignHToProperty; PropertyToPropertyMap m_alignVToProperty; + const QString mFilterAttribute; + const QString mDirectoryAttribute; const QString mSuggestionsAttribute; const QString mMultilineAttribute; QIcon mImageMissingIcon;