From e8e83d69efacacd02760598c93800b66a33f43d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B8rn=20Lindeijer?= Date: Wed, 4 Dec 2019 11:28:56 +0100 Subject: [PATCH] Projects: Added various missing pieces * Added Recent Projects menu * Added action to remove a folder from the project * Added small tool bar at bottom of Project view * Don't reset project model when adding/removing folders * Improved suggested file name and location for first project Issue #1665 --- src/tiled/mainwindow.cpp | 54 ++++++++++-- src/tiled/mainwindow.h | 2 + src/tiled/mainwindow.ui | 21 +++++ src/tiled/mapsdock.cpp | 47 ++++++++-- src/tiled/mapsdock.h | 33 ------- src/tiled/preferences.cpp | 33 +++++-- src/tiled/preferences.h | 7 ++ src/tiled/project.cpp | 6 ++ src/tiled/project.h | 7 +- src/tiled/projectdock.cpp | 171 ++++++++++++++++++++++++++++--------- src/tiled/projectdock.h | 39 +-------- src/tiled/projectmodel.cpp | 45 +++++++--- src/tiled/projectmodel.h | 17 +++- 13 files changed, 337 insertions(+), 145 deletions(-) diff --git a/src/tiled/mainwindow.cpp b/src/tiled/mainwindow.cpp index 578045d9586..657c64e067a 100644 --- a/src/tiled/mainwindow.cpp +++ b/src/tiled/mainwindow.cpp @@ -198,9 +198,6 @@ MainWindow::MainWindow(QWidget *parent, Qt::WindowFlags flags) , mActionManager(new ActionManager(this)) , mUi(new Ui::MainWindow) , mActionHandler(new MapDocumentActionHandler(this)) - , mConsoleDock(new ConsoleDock(this)) - , mProjectDock(new ProjectDock(this)) - , mIssuesDock(new IssuesDock(this)) , mObjectTypesEditor(new ObjectTypesEditor(this)) , mAutomappingManager(new AutomappingManager(this)) , mDocumentManager(DocumentManager::instance()) @@ -226,21 +223,25 @@ MainWindow::MainWindow(QWidget *parent, Qt::WindowFlags flags) ActionManager::registerAction(mUi->actionAbout, "About"); ActionManager::registerAction(mUi->actionAboutQt, "AboutQt"); ActionManager::registerAction(mUi->actionAddExternalTileset, "AddExternalTileset"); + ActionManager::registerAction(mUi->actionAddFolderToProject, "AddFolderToProject"); ActionManager::registerAction(mUi->actionAutoMap, "AutoMap"); ActionManager::registerAction(mUi->actionAutoMapWhileDrawing, "AutoMapWhileDrawing"); - ActionManager::registerAction(mUi->actionDonate, "Donate"); ActionManager::registerAction(mUi->actionClearRecentFiles, "ClearRecentFiles"); + ActionManager::registerAction(mUi->actionClearRecentProjects, "ClearRecentProjects"); ActionManager::registerAction(mUi->actionClearView, "ClearView"); ActionManager::registerAction(mUi->actionClose, "Close"); ActionManager::registerAction(mUi->actionCloseAll, "CloseAll"); + ActionManager::registerAction(mUi->actionCloseProject, "CloseProject"); ActionManager::registerAction(mUi->actionCopy, "Copy"); ActionManager::registerAction(mUi->actionCut, "Cut"); ActionManager::registerAction(mUi->actionDelete, "Delete"); ActionManager::registerAction(mUi->actionDocumentation, "Documentation"); + ActionManager::registerAction(mUi->actionDonate, "Donate"); ActionManager::registerAction(mUi->actionEditCommands, "EditCommands"); ActionManager::registerAction(mUi->actionExport, "Export"); ActionManager::registerAction(mUi->actionExportAs, "ExportAs"); ActionManager::registerAction(mUi->actionExportAsImage, "ExportAsImage"); + ActionManager::registerAction(mUi->actionFitInView, "FitInView"); ActionManager::registerAction(mUi->actionFullScreen, "FullScreen"); ActionManager::registerAction(mUi->actionHighlightCurrentLayer, "HighlightCurrentLayer"); ActionManager::registerAction(mUi->actionHighlightHoveredObject, "HighlightHoveredObject"); @@ -254,15 +255,18 @@ MainWindow::MainWindow(QWidget *parent, Qt::WindowFlags flags) ActionManager::registerAction(mUi->actionNoLabels, "NoLabels"); ActionManager::registerAction(mUi->actionOffsetMap, "OffsetMap"); ActionManager::registerAction(mUi->actionOpen, "Open"); + ActionManager::registerAction(mUi->actionOpenProject, "OpenProject"); ActionManager::registerAction(mUi->actionPaste, "Paste"); ActionManager::registerAction(mUi->actionPasteInPlace, "PasteInPlace"); ActionManager::registerAction(mUi->actionPreferences, "Preferences"); ActionManager::registerAction(mUi->actionQuit, "Quit"); + ActionManager::registerAction(mUi->actionRefreshProjectFolders, "RefreshProjectFolders"); ActionManager::registerAction(mUi->actionReload, "Reload"); ActionManager::registerAction(mUi->actionResizeMap, "ResizeMap"); ActionManager::registerAction(mUi->actionSave, "Save"); ActionManager::registerAction(mUi->actionSaveAll, "SaveAll"); ActionManager::registerAction(mUi->actionSaveAs, "SaveAs"); + ActionManager::registerAction(mUi->actionSaveProjectAs, "SaveProjectAs"); ActionManager::registerAction(mUi->actionShowGrid, "ShowGrid"); ActionManager::registerAction(mUi->actionShowTileAnimations, "ShowTileAnimations"); ActionManager::registerAction(mUi->actionShowTileCollisionShapes, "ShowTileCollisionShapes"); @@ -273,9 +277,8 @@ MainWindow::MainWindow(QWidget *parent, Qt::WindowFlags flags) ActionManager::registerAction(mUi->actionSnapToPixels, "SnapToPixels"); ActionManager::registerAction(mUi->actionTilesetProperties, "TilesetProperties"); ActionManager::registerAction(mUi->actionZoomIn, "ZoomIn"); - ActionManager::registerAction(mUi->actionZoomOut, "ZoomOut"); ActionManager::registerAction(mUi->actionZoomNormal, "ZoomNormal"); - ActionManager::registerAction(mUi->actionFitInView, "FitInView"); + ActionManager::registerAction(mUi->actionZoomOut, "ZoomOut"); #ifdef Q_OS_MAC MacSupport::addFullscreen(this); @@ -341,6 +344,10 @@ MainWindow::MainWindow(QWidget *parent, Qt::WindowFlags flags) ActionManager::registerAction(undoAction, "Undo"); ActionManager::registerAction(redoAction, "Redo"); + mProjectDock = new ProjectDock(this); // uses some actions registered above + mConsoleDock = new ConsoleDock(this); + mIssuesDock = new IssuesDock(this); + addDockWidget(Qt::LeftDockWidgetArea, mProjectDock); addDockWidget(Qt::BottomDockWidgetArea, mConsoleDock); addDockWidget(Qt::BottomDockWidgetArea, mIssuesDock); @@ -572,6 +579,7 @@ MainWindow::MainWindow(QWidget *parent, Qt::WindowFlags flags) connect(mUi->actionCloseProject, &QAction::triggered, mProjectDock, &ProjectDock::closeProject); 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->actionDocumentation, &QAction::triggered, this, &MainWindow::openDocumentation); connect(mUi->actionForum, &QAction::triggered, this, &MainWindow::openForum); @@ -591,7 +599,6 @@ MainWindow::MainWindow(QWidget *parent, Qt::WindowFlags flags) this, &MainWindow::openRecentFile); } mUi->menuRecentFiles->insertSeparator(mUi->actionClearRecentFiles); - mUi->menuRecentFiles->setToolTipsVisible(true); connect(mProjectDock, &ProjectDock::projectFileNameChanged, this, &MainWindow::updateWindowTitle); @@ -599,6 +606,7 @@ MainWindow::MainWindow(QWidget *parent, Qt::WindowFlags flags) setThemeIcon(mUi->actionOpen, "document-open"); setThemeIcon(mUi->menuRecentFiles, "document-open-recent"); setThemeIcon(mUi->actionClearRecentFiles, "edit-clear"); + setThemeIcon(mUi->actionClearRecentProjects, "edit-clear"); setThemeIcon(mUi->actionSave, "document-save"); setThemeIcon(mUi->actionSaveAs, "document-save-as"); setThemeIcon(mUi->actionClose, "window-close"); @@ -616,6 +624,7 @@ MainWindow::MainWindow(QWidget *parent, Qt::WindowFlags flags) setThemeIcon(mUi->actionResizeMap, "document-page-setup"); setThemeIcon(mUi->actionMapProperties, "document-properties"); setThemeIcon(mUi->actionOpenProject, "document-open"); + setThemeIcon(mUi->menuRecentProjects, "document-open-recent"); setThemeIcon(mUi->actionSaveProjectAs, "document-save-as"); setThemeIcon(mUi->actionCloseProject, "window-close"); setThemeIcon(mUi->actionAddFolderToProject, "folder-new"); @@ -715,6 +724,7 @@ MainWindow::MainWindow(QWidget *parent, Qt::WindowFlags flags) #endif connect(preferences, &Preferences::recentFilesChanged, this, &MainWindow::updateRecentFilesMenu); + connect(preferences, &Preferences::recentProjectsChanged, this, &MainWindow::updateRecentProjectsMenu); QTimer::singleShot(500, this, [this,preferences] { if (preferences->shouldShowDonationDialog()) @@ -1489,6 +1499,13 @@ void MainWindow::openRecentFile() openFile(action->data().toString()); } +void MainWindow::openRecentProject() +{ + QAction *action = qobject_cast(sender()); + if (action) + mProjectDock->openProjectFile(action->data().toString()); +} + /** * Updates the recent files menu. */ @@ -1511,14 +1528,35 @@ void MainWindow::updateRecentFilesMenu() mUi->menuRecentFiles->setEnabled(numRecentFiles > 0); } +void MainWindow::updateRecentProjectsMenu() +{ + auto menu = mUi->menuRecentProjects; + menu->clear(); + + const QStringList files = Preferences::instance()->recentProjects(); + + for (const QString &file : files) { + const QFileInfo fileInfo(file); + auto action = menu->addAction(fileInfo.fileName(), this, &MainWindow::openRecentProject); + action->setData(file); + action->setToolTip(fileInfo.filePath()); + } + + menu->addSeparator(); + menu->addAction(mUi->actionClearRecentProjects); + menu->setEnabled(!files.isEmpty()); +} + void MainWindow::resetToDefaultLayout() { // Make sure we're not in Clear View mode mUi->actionClearView->setChecked(false); // Reset the Console and Issues dock + addDockWidget(Qt::LeftDockWidgetArea, mProjectDock); addDockWidget(Qt::BottomDockWidgetArea, mConsoleDock); addDockWidget(Qt::BottomDockWidgetArea, mIssuesDock); + mProjectDock->setVisible(true); mConsoleDock->setVisible(false); mIssuesDock->setVisible(false); tabifyDockWidget(mConsoleDock, mIssuesDock); @@ -1531,6 +1569,7 @@ void MainWindow::updateViewsAndToolbarsMenu() { mViewsAndToolbarsMenu->clear(); + mViewsAndToolbarsMenu->addAction(mProjectDock->toggleViewAction()); mViewsAndToolbarsMenu->addAction(mConsoleDock->toggleViewAction()); mViewsAndToolbarsMenu->addAction(mIssuesDock->toggleViewAction()); @@ -1678,6 +1717,7 @@ void MainWindow::readSettings() QByteArray()).toByteArray()); mSettings.endGroup(); updateRecentFilesMenu(); + updateRecentProjectsMenu(); auto &worldManager = WorldManager::instance(); const QStringList worldFiles = mSettings.value(QLatin1String("LoadedWorlds")).toStringList(); diff --git a/src/tiled/mainwindow.h b/src/tiled/mainwindow.h index 196d23ac94c..fd06744a45c 100644 --- a/src/tiled/mainwindow.h +++ b/src/tiled/mainwindow.h @@ -156,6 +156,7 @@ class MainWindow : public QMainWindow void showDonationDialog(); void aboutTiled(); void openRecentFile(); + void openRecentProject(); void documentChanged(Document *document); void documentSaved(Document *document); @@ -193,6 +194,7 @@ class MainWindow : public QMainWindow void readSettings(); void updateRecentFilesMenu(); + void updateRecentProjectsMenu(); void updateViewsAndToolbarsMenu(); void retranslateUi(); diff --git a/src/tiled/mainwindow.ui b/src/tiled/mainwindow.ui index 26f72efce76..eb998a22482 100644 --- a/src/tiled/mainwindow.ui +++ b/src/tiled/mainwindow.ui @@ -53,6 +53,9 @@ :/images/16/document-open-recent.png:/images/16/document-open-recent.png + + true + @@ -181,7 +184,21 @@ &Project + + + &Recent Projects + + + + :/images/16/document-open-recent.png:/images/16/document-open-recent.png + + + true + + + + @@ -678,6 +695,10 @@ + + + :/images/16/edit-clear.png:/images/16/edit-clear.png + Clear Recent Projects diff --git a/src/tiled/mapsdock.cpp b/src/tiled/mapsdock.cpp index 0f2049c9a3a..0da10e003c0 100644 --- a/src/tiled/mapsdock.cpp +++ b/src/tiled/mapsdock.cpp @@ -35,8 +35,9 @@ #include #include #include +#include -using namespace Tiled; +namespace Tiled { /** * Class represents the file system model with disabled dragging of directories. @@ -58,6 +59,38 @@ class FileSystemModel : public QFileSystemModel } }; +/** + * Shows the list of files and directories. + */ +class MapsView : public QTreeView +{ + Q_OBJECT + +public: + MapsView(QWidget *parent = nullptr); + + /** + * Returns a sensible size hint. + */ + QSize sizeHint() const override; + + QFileSystemModel *model() const { return mFileSystemModel; } + +protected: + void mousePressEvent(QMouseEvent *event) override; + +private: + void onMapsDirectoryChanged(); + void onActivated(const QModelIndex &index); + + void pluginObjectAddedOrRemoved(QObject *object); + + void updateNameFilters(); + + QFileSystemModel *mFileSystemModel; +}; + + MapsDock::MapsDock(QWidget *parent) : QDockWidget(parent) , mDirectoryEdit(new QLineEdit) @@ -152,7 +185,6 @@ MapsView::MapsView(QWidget *parent) setHeaderHidden(true); setItemsExpandable(false); setUniformRowHeights(true); - setDragEnabled(true); setDefaultDropAction(Qt::MoveAction); Preferences *prefs = Preferences::instance(); @@ -198,11 +230,8 @@ QSize MapsView::sizeHint() const void MapsView::mousePressEvent(QMouseEvent *event) { - QModelIndex index = indexAt(event->pos()); - if (index.isValid()) { - // Prevent drag-and-drop starting when clicking on an unselected item. - setDragEnabled(selectionModel()->isSelected(index)); - } + // Prevent drag-and-drop starting when clicking on an unselected item. + setDragEnabled(selectionModel()->isSelected(indexAt(event->pos()))); QTreeView::mousePressEvent(event); } @@ -252,3 +281,7 @@ void MapsView::updateNameFilters() mFileSystemModel->setNameFilters(nameFilters); } + +} // namespace Tiled + +#include "mapsdock.moc" diff --git a/src/tiled/mapsdock.h b/src/tiled/mapsdock.h index a54fbcb8723..d21833feba3 100644 --- a/src/tiled/mapsdock.h +++ b/src/tiled/mapsdock.h @@ -21,9 +21,7 @@ #pragma once #include -#include -class QFileSystemModel; class QLineEdit; namespace Tiled { @@ -51,35 +49,4 @@ class MapsDock : public QDockWidget MapsView *mMapsView; }; -/** - * Shows the list of files and directories. - */ -class MapsView : public QTreeView -{ - Q_OBJECT - -public: - MapsView(QWidget *parent = nullptr); - - /** - * Returns a sensible size hint. - */ - QSize sizeHint() const override; - - QFileSystemModel *model() const { return mFileSystemModel; } - -protected: - void mousePressEvent(QMouseEvent *event) override; - -private: - void onMapsDirectoryChanged(); - void onActivated(const QModelIndex &index); - - void pluginObjectAddedOrRemoved(QObject *object); - - void updateNameFilters(); - - QFileSystemModel *mFileSystemModel; -}; - } // namespace Tiled diff --git a/src/tiled/preferences.cpp b/src/tiled/preferences.cpp index 2956518e371..b02aa62b2e4 100644 --- a/src/tiled/preferences.cpp +++ b/src/tiled/preferences.cpp @@ -624,25 +624,38 @@ QString Preferences::fileDialogStartLocation() const * Adds the given file to the recent files list. */ void Preferences::addRecentFile(const QString &fileName) +{ + addToRecentFileList(fileName, "recentFiles/fileNames"); + emit recentFilesChanged(); +} + +QStringList Preferences::recentProjects() const +{ + QVariant v = mSettings->value(QLatin1String("Project/RecentProjects")); + return v.toStringList(); +} + +void Preferences::addRecentProject(const QString &fileName) +{ + addToRecentFileList(fileName, "Project/RecentProjects"); + emit recentProjectsChanged(); +} + +void Preferences::addToRecentFileList(const QString &fileName, const char *key) { // Remember the file by its absolute file path (not the canonical one, // which avoids unexpected paths when symlinks are involved). const QString absoluteFilePath = QDir::cleanPath(QFileInfo(fileName).absoluteFilePath()); - if (absoluteFilePath.isEmpty()) return; - QStringList files = recentFiles(); + QStringList files = mSettings->value(QLatin1String(key)).toStringList(); files.removeAll(absoluteFilePath); files.prepend(absoluteFilePath); while (files.size() > MaxRecentFiles) files.removeLast(); - mSettings->beginGroup(QLatin1String("recentFiles")); - mSettings->setValue(QLatin1String("fileNames"), files); - mSettings->endGroup(); - - emit recentFilesChanged(); + mSettings->setValue(QLatin1String(key), files); } void Preferences::clearRecentFiles() @@ -651,6 +664,12 @@ void Preferences::clearRecentFiles() emit recentFilesChanged(); } +void Preferences::clearRecentProjects() +{ + mSettings->remove(QLatin1String("Project/RecentProjects")); + emit recentProjectsChanged(); +} + void Preferences::setCheckForUpdates(bool on) { if (mCheckForUpdates == on) diff --git a/src/tiled/preferences.h b/src/tiled/preferences.h index f842295a0b1..3bbd75dadd8 100644 --- a/src/tiled/preferences.h +++ b/src/tiled/preferences.h @@ -162,6 +162,9 @@ class Preferences : public QObject QString fileDialogStartLocation() const; void addRecentFile(const QString &fileName); + QStringList recentProjects() const; + void addRecentProject(const QString &fileName); + bool openLastFilesOnStartup() const; bool checkForUpdates() const; @@ -198,6 +201,7 @@ public slots: void setWheelZoomsByDefault(bool mode); void clearRecentFiles(); + void clearRecentProjects(); signals: void showGridChanged(bool showGrid); @@ -233,6 +237,7 @@ public slots: void isPatronChanged(); void recentFilesChanged(); + void recentProjectsChanged(); void checkForUpdatesChanged(bool on); void displayNewsChanged(bool on); @@ -247,6 +252,8 @@ public slots: int intValue(const char *key, int defaultValue) const; qreal realValue(const char *key, qreal defaultValue) const; + void addToRecentFileList(const QString &fileName, const char *key); + void objectTypesFileChangedOnDisk(); FileSystemWatcher mWatcher; diff --git a/src/tiled/project.cpp b/src/tiled/project.cpp index 93367d0b521..403d4af0545 100644 --- a/src/tiled/project.cpp +++ b/src/tiled/project.cpp @@ -115,6 +115,12 @@ void Project::addFolder(const QString &folder) mFolders.push_back(std::move(entry)); } +void Project::removeFolder(int index) +{ + Q_ASSERT(index >= 0 && index < mFolders.size()); + mFolders.erase(mFolders.begin() + index); +} + void Project::refreshFolders() { // TODO: This process should run in a thread (potentially one job for each folder) diff --git a/src/tiled/project.h b/src/tiled/project.h index e6d0bb7299c..5a91f0c87f5 100644 --- a/src/tiled/project.h +++ b/src/tiled/project.h @@ -51,9 +51,10 @@ class Project void clear(); void addFolder(const QString &folder); + void removeFolder(int index); void refreshFolders(); - const std::vector>* folders() const; + const std::vector > &folders() const; private: void updateNameFilters(); @@ -73,9 +74,9 @@ inline QString Project::fileName() const return mFileName; } -inline const std::vector > *Project::folders() const +inline const std::vector > &Project::folders() const { - return &mFolders; + return mFolders; } } // namespace Tiled diff --git a/src/tiled/projectdock.cpp b/src/tiled/projectdock.cpp index 426bca89e98..a53f22fe37d 100644 --- a/src/tiled/projectdock.cpp +++ b/src/tiled/projectdock.cpp @@ -20,22 +20,58 @@ #include "projectdock.h" +#include "actionmanager.h" #include "documentmanager.h" #include "preferences.h" #include "projectmodel.h" #include "utils.h" #include +#include #include #include +#include #include #include #include - -using namespace Tiled; +#include +#include +#include static const char * const LAST_PROJECT_KEY = "Project/LastProject"; +namespace Tiled { + +/** + * Shows the list of files in a project. + */ +class ProjectView final : public QTreeView +{ + Q_OBJECT + +public: + ProjectView(QWidget *parent = nullptr); + + /** + * Returns a sensible size hint. + */ + QSize sizeHint() const override; + + void setModel(QAbstractItemModel *model) override; + + ProjectModel *model() const { return mProjectModel; } + +protected: + void mousePressEvent(QMouseEvent *event) override; + void contextMenuEvent(QContextMenuEvent *event) override; + +private: + void onActivated(const QModelIndex &index); + + ProjectModel *mProjectModel; +}; + + ProjectDock::ProjectDock(QWidget *parent) : QDockWidget(parent) , mProjectView(new ProjectView) @@ -45,20 +81,25 @@ ProjectDock::ProjectDock(QWidget *parent) auto widget = new QWidget(this); auto layout = new QVBoxLayout(widget); layout->setMargin(0); + layout->setSpacing(0); + + QToolBar *toolBar = new QToolBar; + toolBar->setFloatable(false); + toolBar->setMovable(false); + toolBar->setIconSize(Utils::smallIconSize()); + + toolBar->addAction(ActionManager::action("AddFolderToProject")); + toolBar->addAction(ActionManager::action("RefreshProjectFolders")); // Reopen last used project const auto prefs = Preferences::instance(); const auto settings = prefs->settings(); const auto lastProjectFileName = settings->value(QLatin1String(LAST_PROJECT_KEY)).toString(); if (prefs->openLastFilesOnStartup() && !lastProjectFileName.isEmpty()) - mProject.load(lastProjectFileName); - - auto projectModel = new ProjectModel(this); - projectModel->setFolders(mProject.folders()); - - mProjectView->setModel(projectModel); + openProjectFile(lastProjectFileName); layout->addWidget(mProjectView); + layout->addWidget(toolBar); setWidget(widget); retranslateUi(); @@ -73,12 +114,15 @@ void ProjectDock::openProject() const QString projectFilesFilter = tr("Tiled Projects (*.tiled-project)"); const QString fileName = QFileDialog::getOpenFileName(window(), tr("Open Project"), - mProject.fileName(), + projectFileName(), projectFilesFilter, nullptr); - if (fileName.isEmpty()) - return; + if (!fileName.isEmpty()) + openProjectFile(fileName); +} +void ProjectDock::openProjectFile(const QString &fileName) +{ Project project; if (!project.load(fileName)) { @@ -88,38 +132,58 @@ void ProjectDock::openProject() return; } - mProjectView->model()->setFolders(nullptr); - std::swap(mProject, project); - mProjectView->model()->setFolders(mProject.folders()); + mProjectView->model()->setProject(std::move(project)); + + Preferences::instance()->addRecentProject(fileName); emit projectFileNameChanged(); } void ProjectDock::saveProjectAs() { + QString fileName = projectFileName(); + if (fileName.isEmpty()) { + const auto recents = Preferences::instance()->recentProjects(); + if (!recents.isEmpty()) + fileName = QFileInfo(recents.first()).path(); + if (fileName.isEmpty()) + fileName = QStandardPaths::writableLocation(QStandardPaths::HomeLocation); + + fileName.append(QLatin1Char('/')); + fileName.append(QCoreApplication::translate("Tiled::MainWindow", "untitled")); + fileName.append(QLatin1String(".tiled-project")); + } + const QString projectFilesFilter = tr("Tiled Projects (*.tiled-project)"); - const QString fileName = QFileDialog::getSaveFileName(window(), - tr("Save Project As"), - mProject.fileName(), - projectFilesFilter, - nullptr); + fileName = QFileDialog::getSaveFileName(window(), + tr("Save Project As"), + fileName, + projectFilesFilter, + nullptr); if (fileName.isEmpty()) return; - if (!mProject.save(fileName)) { + if (!fileName.endsWith(QLatin1String(".tiled-project"))) { + while (fileName.endsWith(QLatin1String("."))) + fileName.chop(1); + + fileName.append(QLatin1String(".tiled-project")); + } + + if (!project().save(fileName)) { QMessageBox::critical(window(), tr("Error Saving Project"), tr("An error occurred while saving the project.")); } + Preferences::instance()->addRecentProject(fileName); + emit projectFileNameChanged(); } void ProjectDock::closeProject() { - mProjectView->model()->setFolders(nullptr); - mProject.clear(); - + mProjectView->model()->setProject(Project()); emit projectFileNameChanged(); } @@ -127,24 +191,21 @@ void ProjectDock::addFolderToProject() { const QString folder = QFileDialog::getExistingDirectory(window(), tr("Choose Folder"), - QFileInfo(mProject.fileName()).path()); + QFileInfo(projectFileName()).path()); if (folder.isEmpty()) return; - mProject.addFolder(folder); - // FIXME: Should just add the new top-level row, not trigger complete reset - mProjectView->model()->setFolders(mProject.folders()); + mProjectView->model()->addFolder(folder); - if (!mProject.fileName().isEmpty()) - mProject.save(mProject.fileName()); + auto &p = project(); + if (!p.fileName().isEmpty()) + p.save(p.fileName()); } void ProjectDock::refreshProjectFolders() { - mProjectView->model()->setFolders(nullptr); - mProject.refreshFolders(); - mProjectView->model()->setFolders(mProject.folders()); + mProjectView->model()->refreshFolders(); } void ProjectDock::changeEvent(QEvent *e) @@ -159,28 +220,34 @@ void ProjectDock::changeEvent(QEvent *e) } } +Project &ProjectDock::project() const +{ + return mProjectView->model()->project(); +} + void ProjectDock::retranslateUi() { setWindowTitle(tr("Project")); } -///// ///// ///// ///// ///// +/////////////////////////////////////////////////////////////////////////////// ProjectView::ProjectView(QWidget *parent) : QTreeView(parent) { setHeaderHidden(true); setUniformRowHeights(true); - setDragEnabled(true); setDefaultDropAction(Qt::MoveAction); + setModel(new ProjectModel(Project())); + connect(this, &QAbstractItemView::activated, this, &ProjectView::onActivated); } QSize ProjectView::sizeHint() const { - return Utils::dpiScaled(QSize(130, 100)); + return Utils::dpiScaled(QSize(130, 200)); } void ProjectView::setModel(QAbstractItemModel *model) @@ -192,18 +259,42 @@ void ProjectView::setModel(QAbstractItemModel *model) void ProjectView::mousePressEvent(QMouseEvent *event) { - QModelIndex index = indexAt(event->pos()); - if (index.isValid()) { - // Prevent drag-and-drop starting when clicking on an unselected item. - setDragEnabled(selectionModel()->isSelected(index)); - } + // Prevent drag-and-drop starting when clicking on an unselected item. + setDragEnabled(selectionModel()->isSelected(indexAt(event->pos()))); QTreeView::mousePressEvent(event); } +void ProjectView::contextMenuEvent(QContextMenuEvent *event) +{ + const QModelIndex index = indexAt(event->pos()); + + QMenu menu; + + if (index.isValid() && !index.parent().isValid()) { + auto removeFolder = menu.addAction(tr("Remove Folder from Project"), [=] { + model()->removeFolder(index.row()); + + auto &p = model()->project(); + if (!p.fileName().isEmpty()) + p.save(p.fileName()); + }); + Utils::setThemeIcon(removeFolder, "list-remove"); + } else { + menu.addAction(ActionManager::action("AddFolderToProject")); + menu.addAction(ActionManager::action("RefreshProjectFolders")); + } + + menu.exec(event->globalPos()); +} + void ProjectView::onActivated(const QModelIndex &index) { const QString path = model()->filePath(index); if (!QFileInfo(path).isDir()) DocumentManager::instance()->openFile(path); } + +} // namespace Tiled + +#include "projectdock.moc" diff --git a/src/tiled/projectdock.h b/src/tiled/projectdock.h index b051034aafc..b1d931f8fb0 100644 --- a/src/tiled/projectdock.h +++ b/src/tiled/projectdock.h @@ -23,16 +23,12 @@ #include "project.h" #include -#include - -class QFileSystemModel; namespace Tiled { class ProjectView; -class ProjectModel; -class ProjectDock : public QDockWidget +class ProjectDock final : public QDockWidget { Q_OBJECT @@ -42,6 +38,7 @@ class ProjectDock : public QDockWidget QString projectFileName() const; void openProject(); + void openProjectFile(const QString &fileName); void saveProjectAs(); void closeProject(); void addFolderToProject(); @@ -54,44 +51,16 @@ class ProjectDock : public QDockWidget void changeEvent(QEvent *e) override; private: + Project &project() const; void retranslateUi(); - Project mProject; ProjectView *mProjectView; }; -/** - * Shows the list of files in a project. - */ -class ProjectView : public QTreeView -{ - Q_OBJECT - -public: - ProjectView(QWidget *parent = nullptr); - - /** - * Returns a sensible size hint. - */ - QSize sizeHint() const override; - - void setModel(QAbstractItemModel *model) override; - - ProjectModel *model() const { return mProjectModel; } - -protected: - void mousePressEvent(QMouseEvent *event) override; - -private: - void onActivated(const QModelIndex &index); - - ProjectModel *mProjectModel; -}; - inline QString ProjectDock::projectFileName() const { - return mProject.fileName(); + return project().fileName(); } } // namespace Tiled diff --git a/src/tiled/projectmodel.cpp b/src/tiled/projectmodel.cpp index 4bebace70ef..267f2766e99 100644 --- a/src/tiled/projectmodel.cpp +++ b/src/tiled/projectmodel.cpp @@ -26,16 +26,40 @@ namespace Tiled { -ProjectModel::ProjectModel(QObject *parent) +ProjectModel::ProjectModel(Project project, QObject *parent) : QAbstractItemModel(parent) + , mProject(std::move(project)) { mFileIconProvider.setOptions(QFileIconProvider::DontUseCustomDirectoryIcons); } -void ProjectModel::setFolders(const std::vector > *folders) +void ProjectModel::setProject(Project project) { beginResetModel(); - mFolders = folders; + mProject = std::move(project); + endResetModel(); +} + +void ProjectModel::addFolder(const QString &folder) +{ + const int row = int(mProject.folders().size()); + + beginInsertRows(QModelIndex(), row, row); + mProject.addFolder(folder); + endInsertRows(); +} + +void ProjectModel::removeFolder(int row) +{ + beginRemoveRows(QModelIndex(), row, row); + mProject.removeFolder(row); + endRemoveRows(); +} + +void ProjectModel::refreshFolders() +{ + beginResetModel(); + mProject.refreshFolders(); endResetModel(); } @@ -55,8 +79,8 @@ QModelIndex ProjectModel::index(int row, int column, const QModelIndex &parent) if (row < int(entry->entries.size())) return createIndex(row, column, entry->entries.at(row).get()); } else { - if (mFolders && row < int(mFolders->size())) - return createIndex(row, column, mFolders->at(row).get()); + if (row < int(mProject.folders().size())) + return createIndex(row, column, mProject.folders().at(row).get()); } return QModelIndex(); @@ -71,7 +95,7 @@ QModelIndex ProjectModel::parent(const QModelIndex &index) const int ProjectModel::rowCount(const QModelIndex &parent) const { if (!parent.isValid()) - return mFolders ? mFolders->size() : 0; + return mProject.folders().size(); FolderEntry *entry = static_cast(parent.internalPointer()); return entry->entries.size(); @@ -105,11 +129,12 @@ QModelIndex ProjectModel::indexForEntry(FolderEntry *entry) const if (!entry) return QModelIndex(); - const std::vector> *container = entry->parent ? &entry->parent->entries : mFolders; - auto it = std::find_if(container->begin(), container->end(), [entry] (const std::unique_ptr &value) { return value.get() == entry; }); + const auto &container = entry->parent ? entry->parent->entries : mProject.folders(); + const auto it = std::find_if(container.begin(), container.end(), + [entry] (auto &value) { return value.get() == entry; }); - Q_ASSERT(it != container->end()); - return createIndex(std::distance(container->begin(), it), 0, entry); + Q_ASSERT(it != container.end()); + return createIndex(std::distance(container.begin(), it), 0, entry); } } // namespace Tiled diff --git a/src/tiled/projectmodel.h b/src/tiled/projectmodel.h index f189d7288fb..2a788e89188 100644 --- a/src/tiled/projectmodel.h +++ b/src/tiled/projectmodel.h @@ -32,9 +32,14 @@ class ProjectModel : public QAbstractItemModel Q_OBJECT public: - explicit ProjectModel(QObject *parent = nullptr); + explicit ProjectModel(Project project, QObject *parent = nullptr); - void setFolders(const std::vector> *folders); + void setProject(Project project); + Project &project(); + + void addFolder(const QString &folder); + void removeFolder(int row); + void refreshFolders(); QString filePath(const QModelIndex &index) const; @@ -50,8 +55,14 @@ class ProjectModel : public QAbstractItemModel private: QModelIndex indexForEntry(FolderEntry *entry) const; - const std::vector> *mFolders = nullptr; + Project mProject; QFileIconProvider mFileIconProvider; }; + +inline Project &ProjectModel::project() +{ + return mProject; +} + } // namespace Tiled