Skip to content

Commit

Permalink
Added initial support for projects
Browse files Browse the repository at this point in the history
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
  • Loading branch information
bjorn committed Nov 27, 2019
1 parent 2e53f96 commit 5894019
Show file tree
Hide file tree
Showing 13 changed files with 821 additions and 10 deletions.
28 changes: 25 additions & 3 deletions src/tiled/mainwindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@
#include <QtPlatformHeaders\QWindowsWindowFunctions>
#endif

#include "projectdock.h"
#include "qtcompat_p.h"

using namespace Tiled;
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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);
Expand All @@ -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");
Expand All @@ -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");

Expand Down Expand Up @@ -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);
}
Expand Down
2 changes: 2 additions & 0 deletions src/tiled/mainwindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ class MapEditor;
class MapScene;
class MapView;
class ObjectTypesEditor;
class ProjectDock;
class TilesetDocument;
class TilesetEditor;
class Zoomable;
Expand Down Expand Up @@ -205,6 +206,7 @@ class MainWindow : public QMainWindow
Zoomable *mZoomable = nullptr;
MapDocumentActionHandler *mActionHandler;
ConsoleDock *mConsoleDock;
ProjectDock *mProjectDock;
IssuesDock *mIssuesDock;
ObjectTypesEditor *mObjectTypesEditor;
QSettings mSettings;
Expand Down
42 changes: 42 additions & 0 deletions src/tiled/mainwindow.ui
Original file line number Diff line number Diff line change
Expand Up @@ -177,11 +177,23 @@
</property>
<addaction name="actionTilesetProperties"/>
</widget>
<widget class="QMenu" name="menuProject">
<property name="title">
<string>&amp;Project</string>
</property>
<addaction name="actionOpenProject"/>
<addaction name="actionSaveProjectAs"/>
<addaction name="actionCloseProject"/>
<addaction name="separator"/>
<addaction name="actionAddFolderToProject"/>
<addaction name="actionRefreshProjectFolders"/>
</widget>
<addaction name="menuFile"/>
<addaction name="menuEdit"/>
<addaction name="menuView"/>
<addaction name="menuMap"/>
<addaction name="menuTileset"/>
<addaction name="menuProject"/>
<addaction name="menuHelp"/>
</widget>
<action name="actionOpen">
Expand Down Expand Up @@ -655,6 +667,36 @@
<string>Community Forum ↗</string>
</property>
</action>
<action name="actionOpenProject">
<property name="text">
<string>&amp;Open Project...</string>
</property>
</action>
<action name="actionCloseProject">
<property name="text">
<string>&amp;Close Project</string>
</property>
</action>
<action name="actionClearRecentProjects">
<property name="text">
<string>Clear Recent Projects</string>
</property>
</action>
<action name="actionAddFolderToProject">
<property name="text">
<string>Add Folder to Project...</string>
</property>
</action>
<action name="actionSaveProjectAs">
<property name="text">
<string>Save Project As...</string>
</property>
</action>
<action name="actionRefreshProjectFolders">
<property name="text">
<string>Refresh Folders</string>
</property>
</action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<resources>
Expand Down
2 changes: 0 additions & 2 deletions src/tiled/mapsdock.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,9 @@
#include <QBoxLayout>
#include <QCompleter>
#include <QDirModel>
#include <QEvent>
#include <QFileDialog>
#include <QFileSystemModel>
#include <QHeaderView>
#include <QLabel>
#include <QLineEdit>
#include <QMouseEvent>
#include <QPushButton>
Expand Down
8 changes: 3 additions & 5 deletions src/tiled/mapsdock.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,7 @@
#include <QTreeView>

class QFileSystemModel;
class QLabel;
class QLineEdit;
class QModelIndex;
class QTreeView;

namespace Tiled {

Expand Down Expand Up @@ -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);
Expand Down
178 changes: 178 additions & 0 deletions src/tiled/project.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
/*
* project.cpp
* Copyright 2019, Thorbjørn Lindeijer <[email protected]>
*
* 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 <http://www.gnu.org/licenses/>.
*/

#include "project.h"

#include "fileformat.h"
#include "pluginmanager.h"
#include "savefile.h"
#include "utils.h"

#include <QDir>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>

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<FolderEntry>(filePath));
}

refreshFolders();

return true;
}

void Project::clear()
{
mFileName.clear();
mFolders.clear();
}

void Project::addFolder(const QString &folder)
{
auto entry = std::make_unique<FolderEntry>(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<FileFormat>();
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<FolderEntry>(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
Loading

0 comments on commit 5894019

Please sign in to comment.