From 5894019410c6b30771bc696ead33807058c1d5a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B8rn=20Lindeijer?= Date: Wed, 27 Nov 2019 22:32:25 +0100 Subject: [PATCH] Added initial support for projects The initial project is simply a list of folders. Each folder is scanned recursively for files that seem to be one of the supported formats (based on their file extension). Since there's no automatic refresh at the moment, the Project menu contains an action to trigger the refresh manually. The last open project is automatically loaded on startup. There's no way yet to remove folders from a project. Need to edit the file by hand for now. Issue #1665 --- src/tiled/mainwindow.cpp | 28 ++++- src/tiled/mainwindow.h | 2 + src/tiled/mainwindow.ui | 42 ++++++++ src/tiled/mapsdock.cpp | 2 - src/tiled/mapsdock.h | 8 +- src/tiled/project.cpp | 178 +++++++++++++++++++++++++++++++ src/tiled/project.h | 81 ++++++++++++++ src/tiled/projectdock.cpp | 209 +++++++++++++++++++++++++++++++++++++ src/tiled/projectdock.h | 97 +++++++++++++++++ src/tiled/projectmodel.cpp | 115 ++++++++++++++++++++ src/tiled/projectmodel.h | 57 ++++++++++ src/tiled/tiled.pro | 6 ++ src/tiled/tiled.qbs | 6 ++ 13 files changed, 821 insertions(+), 10 deletions(-) create mode 100644 src/tiled/project.cpp create mode 100644 src/tiled/project.h create mode 100644 src/tiled/projectdock.cpp create mode 100644 src/tiled/projectdock.h create mode 100644 src/tiled/projectmodel.cpp create mode 100644 src/tiled/projectmodel.h diff --git a/src/tiled/mainwindow.cpp b/src/tiled/mainwindow.cpp index b6c29ac22a0..578045d9586 100644 --- a/src/tiled/mainwindow.cpp +++ b/src/tiled/mainwindow.cpp @@ -98,6 +98,7 @@ #include #endif +#include "projectdock.h" #include "qtcompat_p.h" using namespace Tiled; @@ -198,6 +199,7 @@ MainWindow::MainWindow(QWidget *parent, Qt::WindowFlags flags) , 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)) @@ -339,6 +341,7 @@ MainWindow::MainWindow(QWidget *parent, Qt::WindowFlags flags) ActionManager::registerAction(undoAction, "Undo"); ActionManager::registerAction(redoAction, "Redo"); + addDockWidget(Qt::LeftDockWidgetArea, mProjectDock); addDockWidget(Qt::BottomDockWidgetArea, mConsoleDock); addDockWidget(Qt::BottomDockWidgetArea, mIssuesDock); tabifyDockWidget(mConsoleDock, mIssuesDock); @@ -461,7 +464,7 @@ MainWindow::MainWindow(QWidget *parent, Qt::WindowFlags flags) mLayerMenu->addSeparator(); mLayerMenu->addAction(mActionHandler->actionLayerProperties()); - menuBar()->insertMenu(mUi->menuHelp->menuAction(), mLayerMenu); + menuBar()->insertMenu(mUi->menuProject->menuAction(), mLayerMenu); ActionManager::registerMenu(mLayerMenu, "Layer"); ActionManager::registerMenu(mNewLayerMenu, "NewLayer"); @@ -564,6 +567,12 @@ MainWindow::MainWindow(QWidget *parent, Qt::WindowFlags flags) connect(mUi->actionTilesetProperties, &QAction::triggered, this, &MainWindow::editTilesetProperties); + connect(mUi->actionOpenProject, &QAction::triggered, mProjectDock, &ProjectDock::openProject); + connect(mUi->actionSaveProjectAs, &QAction::triggered, mProjectDock, &ProjectDock::saveProjectAs); + 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->actionDocumentation, &QAction::triggered, this, &MainWindow::openDocumentation); connect(mUi->actionForum, &QAction::triggered, this, &MainWindow::openForum); connect(mUi->actionDonate, &QAction::triggered, this, &MainWindow::showDonationDialog); @@ -584,6 +593,8 @@ MainWindow::MainWindow(QWidget *parent, Qt::WindowFlags flags) mUi->menuRecentFiles->insertSeparator(mUi->actionClearRecentFiles); mUi->menuRecentFiles->setToolTipsVisible(true); + connect(mProjectDock, &ProjectDock::projectFileNameChanged, this, &MainWindow::updateWindowTitle); + setThemeIcon(mUi->menuNew, "document-new"); setThemeIcon(mUi->actionOpen, "document-open"); setThemeIcon(mUi->menuRecentFiles, "document-open-recent"); @@ -604,6 +615,11 @@ MainWindow::MainWindow(QWidget *parent, Qt::WindowFlags flags) setThemeIcon(mUi->actionFitInView, "zoom-fit-best"); setThemeIcon(mUi->actionResizeMap, "document-page-setup"); setThemeIcon(mUi->actionMapProperties, "document-properties"); + setThemeIcon(mUi->actionOpenProject, "document-open"); + setThemeIcon(mUi->actionSaveProjectAs, "document-save-as"); + setThemeIcon(mUi->actionCloseProject, "window-close"); + setThemeIcon(mUi->actionAddFolderToProject, "folder-new"); + setThemeIcon(mUi->actionRefreshProjectFolders, "view-refresh"); setThemeIcon(mUi->actionDocumentation, "help-contents"); setThemeIcon(mUi->actionAbout, "help-about"); @@ -1674,12 +1690,18 @@ void MainWindow::readSettings() void MainWindow::updateWindowTitle() { + QString projectName = mProjectDock->projectFileName(); + if (!projectName.isEmpty()) { + projectName = QFileInfo(projectName).completeBaseName(); + projectName = QString(QLatin1String(" (%1)")).arg(projectName); + } + if (Document *document = mDocumentManager->currentDocument()) { - setWindowTitle(tr("[*]%1").arg(document->displayName())); + setWindowTitle(tr("[*]%1%2").arg(document->displayName(), projectName)); setWindowFilePath(document->fileName()); setWindowModified(document->isModified()); } else { - setWindowTitle(QString()); + setWindowTitle(projectName); setWindowFilePath(QString()); setWindowModified(false); } diff --git a/src/tiled/mainwindow.h b/src/tiled/mainwindow.h index 5e4dabbd2c3..196d23ac94c 100644 --- a/src/tiled/mainwindow.h +++ b/src/tiled/mainwindow.h @@ -57,6 +57,7 @@ class MapEditor; class MapScene; class MapView; class ObjectTypesEditor; +class ProjectDock; class TilesetDocument; class TilesetEditor; class Zoomable; @@ -205,6 +206,7 @@ class MainWindow : public QMainWindow Zoomable *mZoomable = nullptr; MapDocumentActionHandler *mActionHandler; ConsoleDock *mConsoleDock; + ProjectDock *mProjectDock; IssuesDock *mIssuesDock; ObjectTypesEditor *mObjectTypesEditor; QSettings mSettings; diff --git a/src/tiled/mainwindow.ui b/src/tiled/mainwindow.ui index 68c0acbab14..26f72efce76 100644 --- a/src/tiled/mainwindow.ui +++ b/src/tiled/mainwindow.ui @@ -177,11 +177,23 @@ + + + &Project + + + + + + + + + @@ -655,6 +667,36 @@ Community Forum ↗ + + + &Open Project... + + + + + &Close Project + + + + + Clear Recent Projects + + + + + Add Folder to Project... + + + + + Save Project As... + + + + + Refresh Folders + + diff --git a/src/tiled/mapsdock.cpp b/src/tiled/mapsdock.cpp index 6ae2093ef41..0f2049c9a3a 100644 --- a/src/tiled/mapsdock.cpp +++ b/src/tiled/mapsdock.cpp @@ -29,11 +29,9 @@ #include #include #include -#include #include #include #include -#include #include #include #include diff --git a/src/tiled/mapsdock.h b/src/tiled/mapsdock.h index aea7debc4c5..a54fbcb8723 100644 --- a/src/tiled/mapsdock.h +++ b/src/tiled/mapsdock.h @@ -24,10 +24,7 @@ #include class QFileSystemModel; -class QLabel; class QLineEdit; -class QModelIndex; -class QTreeView; namespace Tiled { @@ -69,10 +66,11 @@ class MapsView : public QTreeView */ QSize sizeHint() const override; - void mousePressEvent(QMouseEvent *event) override; - QFileSystemModel *model() const { return mFileSystemModel; } +protected: + void mousePressEvent(QMouseEvent *event) override; + private: void onMapsDirectoryChanged(); void onActivated(const QModelIndex &index); diff --git a/src/tiled/project.cpp b/src/tiled/project.cpp new file mode 100644 index 00000000000..93367d0b521 --- /dev/null +++ b/src/tiled/project.cpp @@ -0,0 +1,178 @@ +/* + * project.cpp + * Copyright 2019, 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 "project.h" + +#include "fileformat.h" +#include "pluginmanager.h" +#include "savefile.h" +#include "utils.h" + +#include +#include +#include +#include + +namespace Tiled { + +static QString relative(const QDir &dir, const QString &fileName) +{ + QString rel = dir.relativeFilePath(fileName); + return rel.isEmpty() ? QString(QLatin1String(".")) : rel; +} + +Project::Project() +{ + updateNameFilters(); +} + +bool Project::save(const QString &fileName) +{ + QJsonObject project; + + const QDir dir = QFileInfo(fileName).dir(); + + QJsonArray folders; + + for (auto &folder : mFolders) + folders.append(relative(dir, folder->filePath)); + + project.insert(QLatin1String("folders"), folders); + + QJsonDocument document(project); + + SaveFile file(fileName); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) + return false; + + file.device()->write(document.toJson()); + if (!file.commit()) + return false; + + mFileName = fileName; + return true; +} + +bool Project::load(const QString &fileName) +{ + QFile file(fileName); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) + return false; + + QJsonParseError error; + QByteArray json = file.readAll(); + QJsonDocument document(QJsonDocument::fromJson(json, &error)); + if (error.error != QJsonParseError::NoError) + return false; + + mFolders.clear(); + mFileName = fileName; + + const QDir dir = QFileInfo(fileName).dir(); + + QJsonObject project = document.object(); + + const QJsonArray folders = project.value(QLatin1String("folders")).toArray(); + + for (const QJsonValue &folderValue : folders) { + const QString filePath = QDir::cleanPath(dir.absoluteFilePath(folderValue.toString())); + mFolders.push_back(std::make_unique(filePath)); + } + + refreshFolders(); + + return true; +} + +void Project::clear() +{ + mFileName.clear(); + mFolders.clear(); +} + +void Project::addFolder(const QString &folder) +{ + auto entry = std::make_unique(folder); + mVisitedFolders.clear(); + refreshFolder(*entry); + mFolders.push_back(std::move(entry)); +} + +void Project::refreshFolders() +{ + // TODO: This process should run in a thread (potentially one job for each folder) + + for (auto &folder : mFolders) { + // same child folders are allowed in each top-level folder + mVisitedFolders.clear(); + refreshFolder(*folder); + } +} + +void Project::updateNameFilters() +{ + QStringList nameFilters; + + const auto fileFormats = PluginManager::objects(); + for (FileFormat *format : fileFormats) { + if (!(format->capabilities() & FileFormat::Read)) + continue; + + const QString filter = format->nameFilter(); + nameFilters.append(Utils::cleanFilterList(filter)); + } + + if (mNameFilters != nameFilters) { + mNameFilters = nameFilters; + refreshFolders(); + } +} + +void Project::refreshFolder(FolderEntry &folder) +{ + // erase previously found entries + folder.entries.clear(); + + const auto list = QDir(folder.filePath).entryInfoList(mNameFilters, + QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot, + QDir::Name | QDir::LocaleAware | QDir::DirsFirst); + + for (const auto &fileInfo : list) { + auto entry = std::make_unique(fileInfo.filePath(), &folder); + + if (fileInfo.isDir()) { + const QString canonicalPath = fileInfo.canonicalFilePath(); + + // prevent potential endless symlink loop + if (!mVisitedFolders.contains(canonicalPath)) { + mVisitedFolders.insert(canonicalPath); + refreshFolder(*entry); + } + + // Leave out empty directories + if (entry->entries.empty()) + continue; + } + + folder.entries.push_back(std::move(entry)); + } +} + +} // namespace Tiled diff --git a/src/tiled/project.h b/src/tiled/project.h new file mode 100644 index 00000000000..e6d0bb7299c --- /dev/null +++ b/src/tiled/project.h @@ -0,0 +1,81 @@ +/* + * project.h + * Copyright 2019, 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 +#include + +#include +#include + +namespace Tiled { + +struct FolderEntry +{ + explicit FolderEntry(const QString &filePath, FolderEntry *parent = nullptr) + : filePath(filePath) + , parent(parent) + {} + + QString filePath; + std::vector> entries; + FolderEntry *parent = nullptr; +}; + +class Project +{ +public: + Project(); + + QString fileName() const; + bool save(const QString &fileName); + bool load(const QString &fileName); + void clear(); + + void addFolder(const QString &folder); + void refreshFolders(); + + const std::vector>* folders() const; + +private: + void updateNameFilters(); + + void refreshFolder(FolderEntry &folder); + + QString mFileName; + std::vector> mFolders; + + QStringList mNameFilters; + QSet mVisitedFolders; +}; + + +inline QString Project::fileName() const +{ + return mFileName; +} + +inline const std::vector > *Project::folders() const +{ + return &mFolders; +} + +} // namespace Tiled diff --git a/src/tiled/projectdock.cpp b/src/tiled/projectdock.cpp new file mode 100644 index 00000000000..426bca89e98 --- /dev/null +++ b/src/tiled/projectdock.cpp @@ -0,0 +1,209 @@ +/* + * projectdock.cpp + * Copyright 2019, 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 "projectdock.h" + +#include "documentmanager.h" +#include "preferences.h" +#include "projectmodel.h" +#include "utils.h" + +#include +#include +#include +#include +#include +#include + +using namespace Tiled; + +static const char * const LAST_PROJECT_KEY = "Project/LastProject"; + +ProjectDock::ProjectDock(QWidget *parent) + : QDockWidget(parent) + , mProjectView(new ProjectView) +{ + setObjectName(QLatin1String("ProjectDock")); + + auto widget = new QWidget(this); + auto layout = new QVBoxLayout(widget); + layout->setMargin(0); + + // 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); + + layout->addWidget(mProjectView); + + setWidget(widget); + retranslateUi(); + + connect(this, &ProjectDock::projectFileNameChanged, [this] { + Preferences::instance()->settings()->setValue(QLatin1String(LAST_PROJECT_KEY), projectFileName()); + }); +} + +void ProjectDock::openProject() +{ + const QString projectFilesFilter = tr("Tiled Projects (*.tiled-project)"); + const QString fileName = QFileDialog::getOpenFileName(window(), + tr("Open Project"), + mProject.fileName(), + projectFilesFilter, + nullptr); + if (fileName.isEmpty()) + return; + + Project project; + + if (!project.load(fileName)) { + QMessageBox::critical(window(), + tr("Error Opening Project"), + tr("An error occurred while opening the project.")); + return; + } + + mProjectView->model()->setFolders(nullptr); + std::swap(mProject, project); + mProjectView->model()->setFolders(mProject.folders()); + + emit projectFileNameChanged(); +} + +void ProjectDock::saveProjectAs() +{ + const QString projectFilesFilter = tr("Tiled Projects (*.tiled-project)"); + const QString fileName = QFileDialog::getSaveFileName(window(), + tr("Save Project As"), + mProject.fileName(), + projectFilesFilter, + nullptr); + if (fileName.isEmpty()) + return; + + if (!mProject.save(fileName)) { + QMessageBox::critical(window(), + tr("Error Saving Project"), + tr("An error occurred while saving the project.")); + } + + emit projectFileNameChanged(); +} + +void ProjectDock::closeProject() +{ + mProjectView->model()->setFolders(nullptr); + mProject.clear(); + + emit projectFileNameChanged(); +} + +void ProjectDock::addFolderToProject() +{ + const QString folder = QFileDialog::getExistingDirectory(window(), + tr("Choose Folder"), + QFileInfo(mProject.fileName()).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()); + + if (!mProject.fileName().isEmpty()) + mProject.save(mProject.fileName()); +} + +void ProjectDock::refreshProjectFolders() +{ + mProjectView->model()->setFolders(nullptr); + mProject.refreshFolders(); + mProjectView->model()->setFolders(mProject.folders()); +} + +void ProjectDock::changeEvent(QEvent *e) +{ + QDockWidget::changeEvent(e); + switch (e->type()) { + case QEvent::LanguageChange: + retranslateUi(); + break; + default: + break; + } +} + +void ProjectDock::retranslateUi() +{ + setWindowTitle(tr("Project")); +} + +///// ///// ///// ///// ///// + +ProjectView::ProjectView(QWidget *parent) + : QTreeView(parent) +{ + setHeaderHidden(true); + setUniformRowHeights(true); + setDragEnabled(true); + setDefaultDropAction(Qt::MoveAction); + + connect(this, &QAbstractItemView::activated, + this, &ProjectView::onActivated); +} + +QSize ProjectView::sizeHint() const +{ + return Utils::dpiScaled(QSize(130, 100)); +} + +void ProjectView::setModel(QAbstractItemModel *model) +{ + mProjectModel = qobject_cast(model); + Q_ASSERT(mProjectModel); + QTreeView::setModel(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)); + } + + QTreeView::mousePressEvent(event); +} + +void ProjectView::onActivated(const QModelIndex &index) +{ + const QString path = model()->filePath(index); + if (!QFileInfo(path).isDir()) + DocumentManager::instance()->openFile(path); +} diff --git a/src/tiled/projectdock.h b/src/tiled/projectdock.h new file mode 100644 index 00000000000..b051034aafc --- /dev/null +++ b/src/tiled/projectdock.h @@ -0,0 +1,97 @@ +/* + * projectdock.h + * Copyright 2019, 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 "project.h" + +#include +#include + +class QFileSystemModel; + +namespace Tiled { + +class ProjectView; +class ProjectModel; + +class ProjectDock : public QDockWidget +{ + Q_OBJECT + +public: + ProjectDock(QWidget *parent = nullptr); + + QString projectFileName() const; + + void openProject(); + void saveProjectAs(); + void closeProject(); + void addFolderToProject(); + void refreshProjectFolders(); + +signals: + void projectFileNameChanged(); + +protected: + void changeEvent(QEvent *e) override; + +private: + 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(); +} + +} // namespace Tiled diff --git a/src/tiled/projectmodel.cpp b/src/tiled/projectmodel.cpp new file mode 100644 index 00000000000..4bebace70ef --- /dev/null +++ b/src/tiled/projectmodel.cpp @@ -0,0 +1,115 @@ +/* + * projectmodel.cpp + * Copyright 2019, 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 "projectmodel.h" + +#include "containerhelpers.h" + +#include + +namespace Tiled { + +ProjectModel::ProjectModel(QObject *parent) + : QAbstractItemModel(parent) +{ + mFileIconProvider.setOptions(QFileIconProvider::DontUseCustomDirectoryIcons); +} + +void ProjectModel::setFolders(const std::vector > *folders) +{ + beginResetModel(); + mFolders = folders; + endResetModel(); +} + +QString ProjectModel::filePath(const QModelIndex &index) const +{ + if (!index.isValid()) + return QString(); + + FolderEntry *entry = static_cast(index.internalPointer()); + return entry->filePath; +} + +QModelIndex ProjectModel::index(int row, int column, const QModelIndex &parent) const +{ + if (parent.isValid()) { + FolderEntry *entry = static_cast(parent.internalPointer()); + 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()); + } + + return QModelIndex(); +} + +QModelIndex ProjectModel::parent(const QModelIndex &index) const +{ + FolderEntry *entry = static_cast(index.internalPointer()); + return indexForEntry(entry->parent); +} + +int ProjectModel::rowCount(const QModelIndex &parent) const +{ + if (!parent.isValid()) + return mFolders ? mFolders->size() : 0; + + FolderEntry *entry = static_cast(parent.internalPointer()); + return entry->entries.size(); +} + +int ProjectModel::columnCount(const QModelIndex &) const +{ + return 1; +} + +QVariant ProjectModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + FolderEntry *entry = static_cast(index.internalPointer()); + switch (role) { + case Qt::DisplayRole: + return QFileInfo(entry->filePath).fileName(); + case Qt::DecorationRole: + return mFileIconProvider.icon(QFileInfo(entry->filePath)); + case Qt::ToolTipRole: + return entry->filePath; + } + + return QVariant(); +} + +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; }); + + 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 new file mode 100644 index 00000000000..f189d7288fb --- /dev/null +++ b/src/tiled/projectmodel.h @@ -0,0 +1,57 @@ +/* + * projectmodel.h + * Copyright 2019, 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 "project.h" + +#include +#include + +namespace Tiled { + +class ProjectModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + explicit ProjectModel(QObject *parent = nullptr); + + void setFolders(const std::vector> *folders); + + QString filePath(const QModelIndex &index) const; + + QModelIndex index(int row, int column, + const QModelIndex &parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex &index) const override; + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + +private: + QModelIndex indexForEntry(FolderEntry *entry) const; + + const std::vector> *mFolders = nullptr; + QFileIconProvider mFileIconProvider; +}; + +} // namespace Tiled diff --git a/src/tiled/tiled.pro b/src/tiled/tiled.pro index dfa97a0da90..1581d4ee1c1 100644 --- a/src/tiled/tiled.pro +++ b/src/tiled/tiled.pro @@ -185,6 +185,9 @@ SOURCES += aboutdialog.cpp \ pluginlistmodel.cpp \ pointhandle.cpp \ preferences.cpp \ + project.cpp \ + projectdock.cpp \ + projectmodel.cpp \ preferencesdialog.cpp \ propertiesdock.cpp \ propertybrowser.cpp \ @@ -412,6 +415,9 @@ HEADERS += aboutdialog.h \ pointhandle.h \ preferencesdialog.h \ preferences.h \ + project.h \ + projectdock.h \ + projectmodel.h \ propertiesdock.h \ propertybrowser.h \ raiselowerhelper.h \ diff --git a/src/tiled/tiled.qbs b/src/tiled/tiled.qbs index 011b6add355..439ea2848a2 100644 --- a/src/tiled/tiled.qbs +++ b/src/tiled/tiled.qbs @@ -369,6 +369,12 @@ QtGuiApplication { "preferencesdialog.h", "preferencesdialog.ui", "preferences.h", + "project.cpp", + "project.h", + "projectdock.cpp", + "projectdock.h", + "projectmodel.cpp", + "projectmodel.h", "propertiesdock.cpp", "propertiesdock.h", "propertybrowser.cpp",