Skip to content

Commit a43264f

Browse files
ej-sanmartinbjorn
andauthored
Added support for natural sorting of project files (#4284)
This change adds a natural / numeric sorting option affecting Projects view and the results in Open File in Project, which is enabled by default. It is implemented using a proxy model to enable adjusting the Project view quickly when the option is changed, without needing to rescan the folders. Also ensured `.vscode` config dirs are git ignored. Closes #4180 Co-authored-by: Thorbjørn Lindeijer <[email protected]>
1 parent e4fcb79 commit a43264f

File tree

11 files changed

+151
-47
lines changed

11 files changed

+151
-47
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ compile_commands.json
1515
build/
1616

1717
# Visual Studio
18+
.vscode/
1819
*.sln
1920
*.suo
2021
*.vcxproj

NEWS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* Allow filtering tilesets by name in the tileset dock (with dogboydog, #4239)
66
* Allow changing the values of number inputs using expressions (with dogboydog, #4234)
77
* Added support for SVG 1.2 / CSS blending modes to layers (#3932)
8+
* Added support for natural sorting of project files (by Edgar Jr. San Martin, #4284)
89
* Added button to toggle Terrain Brush to full tile mode (by Finlay Pearson, #3407)
910
* Added square selection and expand-from-center to Rectangular Select tool (#4201)
1011
* Added status info for various Stamp Brush, Terrain Brush and Eraser modes (#3092, #4201)

src/tiled/locatorwidget.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,12 @@
2222

2323
#include "documentmanager.h"
2424
#include "filteredit.h"
25+
#include "preferences.h"
2526
#include "projectmanager.h"
2627
#include "utils.h"
2728

2829
#include <QApplication>
30+
#include <QCollator>
2931
#include <QDir>
3032
#include <QKeyEvent>
3133
#include <QPainter>
@@ -376,13 +378,17 @@ void FileLocatorSource::setFilterWords(const QStringList &words)
376378
auto projectModel = ProjectManager::instance()->projectModel();
377379
auto matches = projectModel->findFiles(words);
378380

379-
std::stable_sort(matches.begin(), matches.end(), [] (const ProjectModel::Match &a, const ProjectModel::Match &b) {
381+
QCollator collator;
382+
collator.setCaseSensitivity(Qt::CaseInsensitive);
383+
collator.setNumericMode(Preferences::instance()->naturalSorting());
384+
385+
std::stable_sort(matches.begin(), matches.end(), [&] (const ProjectModel::Match &a, const ProjectModel::Match &b) {
380386
// Sort based on score first
381387
if (a.score != b.score)
382388
return a.score > b.score;
383389

384390
// If score is the same, sort alphabetically
385-
return a.relativePath().compare(b.relativePath(), Qt::CaseInsensitive) < 0;
391+
return collator.compare(a.relativePath(), b.relativePath()) < 0;
386392
});
387393

388394
mDelegate->setWords(words);

src/tiled/preferences.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -662,6 +662,17 @@ bool Preferences::restoreSessionOnStartup() const
662662
return get("Startup/RestorePreviousSession", true);
663663
}
664664

665+
bool Preferences::naturalSorting() const
666+
{
667+
return get("Project/NaturalSorting", true);
668+
}
669+
670+
void Preferences::setNaturalSorting(bool enabled)
671+
{
672+
setValue(QLatin1String("Project/NaturalSorting"), enabled);
673+
emit naturalSortingChanged(enabled);
674+
}
675+
665676
void Preferences::addToRecentFileList(const QString &fileName, QStringList& files)
666677
{
667678
// Remember the file by its absolute file path (not the canonical one,

src/tiled/preferences.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,9 @@ class TILED_EDITOR_EXPORT Preferences : public QSettings
164164
void setLastSession(const QString &fileName);
165165
bool restoreSessionOnStartup() const;
166166

167+
bool naturalSorting() const;
168+
void setNaturalSorting(bool enabled);
169+
167170
bool checkForUpdates() const;
168171
void setCheckForUpdates(bool on);
169172

@@ -252,6 +255,8 @@ public slots:
252255
void recentFilesChanged();
253256
void recentProjectsChanged();
254257

258+
void naturalSortingChanged(bool enabled);
259+
255260
void checkForUpdatesChanged(bool on);
256261
void displayNewsChanged(bool on);
257262

src/tiled/preferencesdialog.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ PreferencesDialog::PreferencesDialog(QWidget *parent)
9393
preferences, &Preferences::setSafeSavingEnabled);
9494
connect(mUi->exportOnSave, &QCheckBox::toggled,
9595
preferences, &Preferences::setExportOnSave);
96+
connect(mUi->naturalSorting, &QCheckBox::toggled,
97+
preferences, &Preferences::setNaturalSorting);
9698

9799
connect(mUi->embedTilesets, &QCheckBox::toggled, preferences, [preferences] (bool value) {
98100
preferences->setExportOption(Preferences::EmbedTilesets, value);
@@ -218,6 +220,7 @@ void PreferencesDialog::fromPreferences()
218220
mUi->restoreSession->setChecked(prefs->restoreSessionOnStartup());
219221
mUi->safeSaving->setChecked(prefs->safeSavingEnabled());
220222
mUi->exportOnSave->setChecked(prefs->exportOnSave());
223+
mUi->naturalSorting->setChecked(prefs->naturalSorting());
221224

222225
mUi->embedTilesets->setChecked(prefs->exportOption(Preferences::EmbedTilesets));
223226
mUi->detachTemplateInstances->setChecked(prefs->exportOption(Preferences::DetachTemplateInstances));

src/tiled/preferencesdialog.ui

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,16 @@
263263
</property>
264264
</widget>
265265
</item>
266+
<item row="7" column="0" colspan="2">
267+
<widget class="QCheckBox" name="naturalSorting">
268+
<property name="toolTip">
269+
<string>Sort files numerically (d3, d20) instead of alphabetically (d20, d3)</string>
270+
</property>
271+
<property name="text">
272+
<string>Use natural sorting in Projects view</string>
273+
</property>
274+
</widget>
275+
</item>
266276
<item row="4" column="1" colspan="2">
267277
<layout class="QHBoxLayout" name="horizontalLayout_3">
268278
<item>
@@ -692,6 +702,7 @@
692702
<tabstop>gridMajorY</tabstop>
693703
<tabstop>objectLineWidth</tabstop>
694704
<tabstop>openGL</tabstop>
705+
<tabstop>naturalSorting</tabstop>
695706
<tabstop>objectSelectionBehaviorCombo</tabstop>
696707
<tabstop>preciseTileObjectSelection</tabstop>
697708
<tabstop>wheelZoomsByDefault</tabstop>

src/tiled/projectdock.cpp

Lines changed: 41 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -62,17 +62,16 @@ class ProjectView final : public QTreeView
6262
*/
6363
QSize sizeHint() const override;
6464

65-
void setModel(QAbstractItemModel *model) override;
66-
ProjectModel *model() const { return mProjectModel; }
67-
68-
// TODO: Add 'select by file name'
65+
ProjectModel *projectModel() const { return mProjectModel; }
6966

7067
QStringList expandedPaths() const { return mExpandedPaths.values(); }
7168
void setExpandedPaths(const QStringList &paths);
7269
void addExpandedPath(const QString &path);
7370

7471
void selectPath(const QString &path);
7572

73+
QString filePath(const QModelIndex &index) const;
74+
7675
protected:
7776
void contextMenuEvent(QContextMenuEvent *event) override;
7877

@@ -83,6 +82,7 @@ class ProjectView final : public QTreeView
8382
void restoreExpanded(const QModelIndex &parent);
8483

8584
ProjectModel *mProjectModel;
85+
ProjectProxyModel *mProxyModel;
8686
QSet<QString> mExpandedPaths;
8787
QString mSelectedPath;
8888
int mScrollBarValue = 0;
@@ -111,8 +111,10 @@ ProjectDock::ProjectDock(QWidget *parent)
111111
connect(mProjectView->selectionModel(), &QItemSelectionModel::currentRowChanged,
112112
this, &ProjectDock::onCurrentRowChanged);
113113

114-
connect(mProjectView->model(), &ProjectModel::folderAdded, this, &ProjectDock::folderAdded);
115-
connect(mProjectView->model(), &ProjectModel::folderRemoved, this, &ProjectDock::folderRemoved);
114+
// Forwarding signals
115+
auto projectModel = mProjectView->projectModel();
116+
connect(projectModel, &ProjectModel::folderAdded, this, &ProjectDock::folderAdded);
117+
connect(projectModel, &ProjectModel::folderRemoved, this, &ProjectDock::folderRemoved);
116118
}
117119

118120
void ProjectDock::addFolderToProject()
@@ -134,15 +136,15 @@ void ProjectDock::addFolderToProject()
134136
if (folder.isEmpty())
135137
return;
136138

137-
mProjectView->model()->addFolder(folder);
139+
mProjectView->projectModel()->addFolder(folder);
138140
mProjectView->addExpandedPath(folder);
139141

140142
project.save();
141143
}
142144

143145
void ProjectDock::refreshProjectFolders()
144146
{
145-
mProjectView->model()->refreshFolders();
147+
mProjectView->projectModel()->refreshFolders();
146148
}
147149

148150
void ProjectDock::setExpandedPaths(const QStringList &expandedPaths)
@@ -167,7 +169,7 @@ void ProjectDock::onCurrentRowChanged(const QModelIndex &current)
167169
if (!current.isValid())
168170
return;
169171

170-
const auto filePath = mProjectView->model()->filePath(current);
172+
const auto filePath = mProjectView->filePath(current);
171173
if (QFileInfo { filePath }.isFile())
172174
emit fileSelected(filePath);
173175
}
@@ -193,27 +195,29 @@ ProjectView::ProjectView(QWidget *parent)
193195
setDefaultDropAction(Qt::MoveAction);
194196
setDragDropMode(QAbstractItemView::DragOnly);
195197

196-
auto model = ProjectManager::instance()->projectModel();
197-
setModel(model);
198+
mProjectModel = ProjectManager::instance()->projectModel();
199+
mProxyModel = new ProjectProxyModel(this);
200+
mProxyModel->setSourceModel(mProjectModel);
201+
setModel(mProxyModel);
198202

199203
connect(this, &QAbstractItemView::activated,
200204
this, &ProjectView::onActivated);
201205

202-
connect(model, &QAbstractItemModel::rowsInserted,
206+
connect(mProxyModel, &QAbstractItemModel::rowsInserted,
203207
this, &ProjectView::onRowsInserted);
204208

205209
connect(this, &QTreeView::expanded,
206-
this, [=] (const QModelIndex &index) { mExpandedPaths.insert(model->filePath(index)); });
210+
this, [=] (const QModelIndex &index) { mExpandedPaths.insert(filePath(index)); });
207211
connect(this, &QTreeView::collapsed,
208-
this, [=] (const QModelIndex &index) { mExpandedPaths.remove(model->filePath(index)); });
212+
this, [=] (const QModelIndex &index) { mExpandedPaths.remove(filePath(index)); });
209213

210214
// Reselect a previously selected path and restore scrollbar after refresh
211-
connect(model, &ProjectModel::aboutToRefresh,
215+
connect(mProjectModel, &ProjectModel::aboutToRefresh,
212216
this, [=] {
213-
mSelectedPath = model->filePath(currentIndex());
217+
mSelectedPath = filePath(currentIndex());
214218
mScrollBarValue = verticalScrollBar()->value();
215219
});
216-
connect(model, &ProjectModel::refreshed,
220+
connect(mProjectModel, &ProjectModel::refreshed,
217221
this, [=] {
218222
selectPath(mSelectedPath);
219223
verticalScrollBar()->setValue(mScrollBarValue);
@@ -225,13 +229,6 @@ QSize ProjectView::sizeHint() const
225229
return Utils::dpiScaled(QSize(250, 200));
226230
}
227231

228-
void ProjectView::setModel(QAbstractItemModel *model)
229-
{
230-
mProjectModel = qobject_cast<ProjectModel*>(model);
231-
Q_ASSERT(mProjectModel);
232-
QTreeView::setModel(model);
233-
}
234-
235232
void ProjectView::setExpandedPaths(const QStringList &paths)
236233
{
237234
mExpandedPaths = QSet<QString>(paths.begin(), paths.end());
@@ -244,38 +241,44 @@ void ProjectView::addExpandedPath(const QString &path)
244241

245242
void ProjectView::selectPath(const QString &path)
246243
{
247-
auto index = model()->index(path);
248-
if (index.isValid())
249-
setCurrentIndex(index);
244+
const auto sourceIndex = mProjectModel->index(path);
245+
const auto proxyIndex = mProxyModel->mapFromSource(sourceIndex);
246+
if (proxyIndex.isValid())
247+
setCurrentIndex(proxyIndex);
248+
}
249+
250+
QString ProjectView::filePath(const QModelIndex &index) const
251+
{
252+
return mProjectModel->filePath(mProxyModel->mapToSource(index));
250253
}
251254

252255
void ProjectView::contextMenuEvent(QContextMenuEvent *event)
253256
{
254-
const QModelIndex index = indexAt(event->pos());
257+
const auto index = indexAt(event->pos());
255258

256259
QMenu menu;
257260

258261
if (index.isValid()) {
259-
const auto filePath = model()->filePath(index);
262+
const auto path = filePath(index);
260263

261-
Utils::addFileManagerActions(menu, filePath);
264+
Utils::addFileManagerActions(menu, path);
262265

263-
if (QFileInfo { filePath }.isFile()) {
264-
Utils::addOpenWithSystemEditorAction(menu, filePath);
266+
if (QFileInfo { path }.isFile()) {
267+
Utils::addOpenWithSystemEditorAction(menu, path);
265268

266269
auto mapDocumentActionHandler = MapDocumentActionHandler::instance();
267270
auto mapDocument = mapDocumentActionHandler->mapDocument();
268271

269272
// Add template-specific actions
270-
auto objectTemplate = TemplateManager::instance()->loadObjectTemplate(filePath);
273+
auto objectTemplate = TemplateManager::instance()->loadObjectTemplate(path);
271274
if (objectTemplate->object()) {
272275
menu.addSeparator();
273276
menu.addAction(tr("Select Template Instances"), [=] {
274277
mapDocumentActionHandler->selectAllInstances(objectTemplate);
275278
})->setEnabled(mapDocument != nullptr);
276279
}
277280
// Add tileset-specific actions
278-
else if (auto tileset = TilesetManager::instance()->loadTileset(filePath)) {
281+
else if (auto tileset = TilesetManager::instance()->loadTileset(path)) {
279282
if (mapDocument) {
280283
auto documentManager = DocumentManager::instance();
281284
auto mapEditor = static_cast<MapEditor*>(documentManager->editor(Document::MapDocumentType));
@@ -301,8 +304,8 @@ void ProjectView::contextMenuEvent(QContextMenuEvent *event)
301304
if (!index.parent().isValid()) {
302305
menu.addSeparator();
303306
auto removeFolder = menu.addAction(tr("&Remove Folder from Project"), [=] {
304-
model()->removeFolder(index.row());
305-
model()->project().save();
307+
projectModel()->removeFolder(index.row());
308+
projectModel()->project().save();
306309
});
307310
Utils::setThemeIcon(removeFolder, "list-remove");
308311
}
@@ -319,7 +322,7 @@ void ProjectView::contextMenuEvent(QContextMenuEvent *event)
319322

320323
void ProjectView::onActivated(const QModelIndex &index)
321324
{
322-
const QString path = model()->filePath(index);
325+
const QString path = filePath(index);
323326
if (QFileInfo(path).isFile())
324327
DocumentManager::instance()->openFile(path);
325328
}
@@ -332,7 +335,7 @@ void ProjectView::onRowsInserted(const QModelIndex &parent)
332335

333336
void ProjectView::restoreExpanded(const QModelIndex &parent)
334337
{
335-
const QString path = model()->filePath(parent);
338+
const QString path = filePath(parent);
336339

337340
if (mExpandedPaths.contains(path)) {
338341
setExpanded(parent, true);

src/tiled/projectdock.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@
2020

2121
#pragma once
2222

23-
#include "project.h"
24-
2523
#include <QDockWidget>
2624

2725
namespace Tiled {

0 commit comments

Comments
 (0)