git repos / SagoImageBrowser

blame: src/mainwindow.cpp

normal view · raw

032aa1e1 Poul Sander 2026-02-19 18:55 1
#include "mainwindow.h"
032aa1e1 Poul Sander 2026-02-19 18:55 2
#include "imagemodel.h"
61136b77 Poul Sander 2026-02-20 22:45 3
#include "imageviewwidget.h"
f61bf886 Poul Sander 2026-02-23 19:27 4
#include "preferencesdialog.h"
83f0e0bc Poul Sander 2026-05-30 17:59 5
#include "shortcutmanager.h"
ac476ca8 Poul Sander 2026-05-18 16:47 6
#include "thumbnailcache.h"
53fdab93 Poul Sander 2026-05-18 18:45 7
#include "exifreader.h"
40efdfde Poul Sander 2026-05-19 21:24 8
#include "fsdirmodel.h"
fd7f51cd Poul Sander 2026-05-22 15:30 9
#include "pathcompletermodel.h"
1dcea855 Poul Sander 2026-05-26 21:48 10
#include "thumbnaildelegate.h"
032aa1e1 Poul Sander 2026-02-19 18:55 11
40efdfde Poul Sander 2026-05-19 21:24 12
#include <QTreeView>
032aa1e1 Poul Sander 2026-02-19 18:55 13
#include <QLabel>
61136b77 Poul Sander 2026-02-20 22:45 14
#include <QStackedWidget>
032aa1e1 Poul Sander 2026-02-19 18:55 15
#include <QDir>
3bee3e3e Poul Sander 2026-03-05 17:07 16
#include <QListWidget>
fc5f2e27 Poul Sander 2026-02-22 22:06 17
#include <QStandardPaths>
032aa1e1 Poul Sander 2026-02-19 18:55 18
#include <QImageReader>
032aa1e1 Poul Sander 2026-02-19 18:55 19
#include <QHeaderView>
6fcd6c90 Poul Sander 2026-02-19 21:12 20
#include <QScrollBar>
6fcd6c90 Poul Sander 2026-02-19 21:12 21
#include <QTimer>
6fcd6c90 Poul Sander 2026-02-19 21:12 22
#include <QEvent>
cfe4a67b Poul Sander 2026-02-19 23:06 23
#include <QItemSelectionModel>
cbc7d22b Poul Sander 2026-02-24 19:23 24
#include <QDockWidget>
f61bf886 Poul Sander 2026-02-23 19:27 25
#include <QMenuBar>
f61bf886 Poul Sander 2026-02-23 19:27 26
#include <QMenu>
f61bf886 Poul Sander 2026-02-23 19:27 27
#include <QAction>
f61bf886 Poul Sander 2026-02-23 19:27 28
#include <QSettings>
53fdab93 Poul Sander 2026-05-18 18:45 29
#include <QTableWidget>
beb4a823 Poul Sander 2026-05-19 21:53 30
#include <QDialog>
fd7f51cd Poul Sander 2026-05-22 15:30 31
#include <QHBoxLayout>
beb4a823 Poul Sander 2026-05-19 21:53 32
#include <QPlainTextEdit>
835a6bea Poul Sander 2026-05-19 22:03 33
#include <QMessageBox>
fd7f51cd Poul Sander 2026-05-22 15:30 34
#include <QLineEdit>
fd7f51cd Poul Sander 2026-05-22 15:30 35
#include <QPushButton>
fd7f51cd Poul Sander 2026-05-22 15:30 36
#include <QCompleter>
237d2c81 Poul Sander 2026-06-06 14:53 37
#include <QClipboard>
237d2c81 Poul Sander 2026-06-06 14:53 38
#include <QDialogButtonBox>
700d6f2d Poul Sander 2026-06-06 15:44 39
#include <QFile>
237d2c81 Poul Sander 2026-06-06 14:53 40
#include <QFileInfo>
237d2c81 Poul Sander 2026-06-06 14:53 41
#include <QGuiApplication>
237d2c81 Poul Sander 2026-06-06 14:53 42
#include <QVBoxLayout>
6fcd6c90 Poul Sander 2026-02-19 21:12 43
#include <iostream>
032aa1e1 Poul Sander 2026-02-19 18:55 44
80a9926d Poul Sander 2026-05-19 20:28 45
#include <fcntl.h>
bf525669 Poul Sander 2026-05-30 23:18 46
#include <filesystem>
80a9926d Poul Sander 2026-05-19 20:28 47
#include <sys/stat.h>
80a9926d Poul Sander 2026-05-19 20:28 48
#include <unistd.h>
80a9926d Poul Sander 2026-05-19 20:28 49
ac476ca8 Poul Sander 2026-05-18 16:47 50
namespace
ac476ca8 Poul Sander 2026-05-18 16:47 51
{
ac476ca8 Poul Sander 2026-05-18 16:47 52
ThumbnailCache::Size thumbnailSizeFromString(const QString &s)
ac476ca8 Poul Sander 2026-05-18 16:47 53
{
ac476ca8 Poul Sander 2026-05-18 16:47 54
    return (s == "large") ? ThumbnailCache::Size::Large
ac476ca8 Poul Sander 2026-05-18 16:47 55
                          : ThumbnailCache::Size::Normal;
ac476ca8 Poul Sander 2026-05-18 16:47 56
}
237d2c81 Poul Sander 2026-06-06 14:53 57
237d2c81 Poul Sander 2026-06-06 14:53 58
QString displayPath(const QByteArray &path)
237d2c81 Poul Sander 2026-06-06 14:53 59
{
237d2c81 Poul Sander 2026-06-06 14:53 60
    return QString::fromLocal8Bit(path.constData(), path.size());
237d2c81 Poul Sander 2026-06-06 14:53 61
}
237d2c81 Poul Sander 2026-06-06 14:53 62
237d2c81 Poul Sander 2026-06-06 14:53 63
QString filenameFromPath(const QByteArray &path)
237d2c81 Poul Sander 2026-06-06 14:53 64
{
237d2c81 Poul Sander 2026-06-06 14:53 65
    const std::filesystem::path fsPath(path.toStdString());
237d2c81 Poul Sander 2026-06-06 14:53 66
    const std::string nativeName = fsPath.filename().native();
237d2c81 Poul Sander 2026-06-06 14:53 67
    return QString::fromLocal8Bit(nativeName.data(),
237d2c81 Poul Sander 2026-06-06 14:53 68
                                  static_cast<qsizetype>(nativeName.size()));
237d2c81 Poul Sander 2026-06-06 14:53 69
}
ac476ca8 Poul Sander 2026-05-18 16:47 70
} // namespace
ac476ca8 Poul Sander 2026-05-18 16:47 71
032aa1e1 Poul Sander 2026-02-19 18:55 72
MainWindow::MainWindow(QWidget *parent)
032aa1e1 Poul Sander 2026-02-19 18:55 73
    : QMainWindow(parent)
032aa1e1 Poul Sander 2026-02-19 18:55 74
{
032aa1e1 Poul Sander 2026-02-19 18:55 75
    setupUi();
f61bf886 Poul Sander 2026-02-23 19:27 76
    setupMenuBar();
032aa1e1 Poul Sander 2026-02-19 18:55 77
    setupConnections();
f61bf886 Poul Sander 2026-02-23 19:27 78
    loadPreferences();
032aa1e1 Poul Sander 2026-02-19 18:55 79
}
032aa1e1 Poul Sander 2026-02-19 18:55 80
032aa1e1 Poul Sander 2026-02-19 18:55 81
void MainWindow::setupUi()
032aa1e1 Poul Sander 2026-02-19 18:55 82
{
40efdfde Poul Sander 2026-05-19 21:24 83
    // Folder model (left pane) — backed by std::filesystem so directories with
40efdfde Poul Sander 2026-05-19 21:24 84
    // non-UTF-8 filenames are listed correctly.
40efdfde Poul Sander 2026-05-19 21:24 85
    m_dirModel = new FsDirModel(this);
032aa1e1 Poul Sander 2026-02-19 18:55 86
    m_dirModel->setRootPath(QDir::homePath());
032aa1e1 Poul Sander 2026-02-19 18:55 87
3bee3e3e Poul Sander 2026-03-05 17:07 88
    // Root chooser list
3bee3e3e Poul Sander 2026-03-05 17:07 89
    m_rootList = new QListWidget;
3bee3e3e Poul Sander 2026-03-05 17:07 90
    m_rootList->setSelectionMode(QAbstractItemView::SingleSelection);
3bee3e3e Poul Sander 2026-03-05 17:07 91
3bee3e3e Poul Sander 2026-03-05 17:07 92
    auto addRootItem = [this](const QString &label, const QString &path) {
3bee3e3e Poul Sander 2026-03-05 17:07 93
        QListWidgetItem *item = new QListWidgetItem(label, m_rootList);
3bee3e3e Poul Sander 2026-03-05 17:07 94
        item->setData(Qt::UserRole, path);
3bee3e3e Poul Sander 2026-03-05 17:07 95
    };
4c41f5ff Poul Sander 2026-05-31 00:01 96
    addRootItem(tr("Home"), QDir::homePath());
4c41f5ff Poul Sander 2026-05-31 00:01 97
    addRootItem(tr("Pictures"), QStandardPaths::writableLocation(QStandardPaths::PicturesLocation));
4c41f5ff Poul Sander 2026-05-31 00:01 98
    addRootItem(tr("File System"), QDir::rootPath());
3bee3e3e Poul Sander 2026-03-05 17:07 99
    m_rootList->setCurrentRow(0);
fc5f2e27 Poul Sander 2026-02-22 22:06 100
032aa1e1 Poul Sander 2026-02-19 18:55 101
    m_treeView = new QTreeView;
032aa1e1 Poul Sander 2026-02-19 18:55 102
    m_treeView->setModel(m_dirModel);
40efdfde Poul Sander 2026-05-19 21:24 103
    m_treeView->setRootIndex(m_dirModel->rootIndex());
032aa1e1 Poul Sander 2026-02-19 18:55 104
    m_treeView->header()->hide();
032aa1e1 Poul Sander 2026-02-19 18:55 105
    m_treeView->setColumnHidden(1, true);
032aa1e1 Poul Sander 2026-02-19 18:55 106
    m_treeView->setColumnHidden(2, true);
032aa1e1 Poul Sander 2026-02-19 18:55 107
    m_treeView->setColumnHidden(3, true);
032aa1e1 Poul Sander 2026-02-19 18:55 108
032aa1e1 Poul Sander 2026-02-19 18:55 109
    // Thumbnail model (center pane)
032aa1e1 Poul Sander 2026-02-19 18:55 110
    m_imageModel = new ImageModel(this);
032aa1e1 Poul Sander 2026-02-19 18:55 111
032aa1e1 Poul Sander 2026-02-19 18:55 112
    m_listView = new QListView;
032aa1e1 Poul Sander 2026-02-19 18:55 113
    m_listView->setModel(m_imageModel);
032aa1e1 Poul Sander 2026-02-19 18:55 114
    m_listView->setViewMode(QListView::IconMode);
032aa1e1 Poul Sander 2026-02-19 18:55 115
    m_listView->setIconSize(QSize(128, 128));
b614a581 Poul Sander 2026-02-19 21:18 116
    m_listView->setGridSize(QSize(148, 168));
032aa1e1 Poul Sander 2026-02-19 18:55 117
    m_listView->setResizeMode(QListView::Adjust);
032aa1e1 Poul Sander 2026-02-19 18:55 118
    m_listView->setSelectionMode(QAbstractItemView::SingleSelection);
b614a581 Poul Sander 2026-02-19 21:18 119
    m_listView->setSpacing(4);
b614a581 Poul Sander 2026-02-19 21:18 120
    m_listView->setWordWrap(true);
b614a581 Poul Sander 2026-02-19 21:18 121
    m_listView->setTextElideMode(Qt::ElideRight);
1dcea855 Poul Sander 2026-05-26 21:48 122
    m_listView->setItemDelegate(new ThumbnailDelegate(this));
237d2c81 Poul Sander 2026-06-06 14:53 123
    m_listView->setContextMenuPolicy(Qt::CustomContextMenu);
032aa1e1 Poul Sander 2026-02-19 18:55 124
032aa1e1 Poul Sander 2026-02-19 18:55 125
    // Preview pane (right)
032aa1e1 Poul Sander 2026-02-19 18:55 126
    m_previewLabel = new QLabel;
032aa1e1 Poul Sander 2026-02-19 18:55 127
    m_previewLabel->setAlignment(Qt::AlignCenter);
cbc7d22b Poul Sander 2026-02-24 19:23 128
    m_previewLabel->setMinimumWidth(200);
61136b77 Poul Sander 2026-02-20 22:45 129
61136b77 Poul Sander 2026-02-20 22:45 130
    // Single image view
61136b77 Poul Sander 2026-02-20 22:45 131
    m_imageView = new ImageViewWidget;
d93c1ab0 Poul Sander 2026-06-08 21:44 132
    m_imageView->setContextMenuPolicy(Qt::CustomContextMenu);
61136b77 Poul Sander 2026-02-20 22:45 133
fd7f51cd Poul Sander 2026-05-22 15:30 134
    // Path bar: editable absolute path with ajax-style folder completion and a Go button
fd7f51cd Poul Sander 2026-05-22 15:30 135
    m_pathEdit = new QLineEdit;
fd7f51cd Poul Sander 2026-05-22 15:30 136
    m_goButton = new QPushButton(tr("Go"));
bf525669 Poul Sander 2026-05-30 23:18 137
    m_prevFolderButton = new QPushButton(tr("Prev"));
bf525669 Poul Sander 2026-05-30 23:18 138
    m_nextFolderButton = new QPushButton(tr("Next"));
fd7f51cd Poul Sander 2026-05-22 15:30 139
    m_pathCompleterModel = new PathCompleterModel(this);
fd7f51cd Poul Sander 2026-05-22 15:30 140
    m_pathCompleter = new QCompleter(m_pathCompleterModel, this);
fd7f51cd Poul Sander 2026-05-22 15:30 141
    m_pathCompleter->setCaseSensitivity(Qt::CaseSensitive);
fd7f51cd Poul Sander 2026-05-22 15:30 142
    m_pathCompleter->setCompletionMode(QCompleter::PopupCompletion);
fd7f51cd Poul Sander 2026-05-22 15:30 143
    m_pathCompleter->setFilterMode(Qt::MatchStartsWith);
fd7f51cd Poul Sander 2026-05-22 15:30 144
    m_pathEdit->setCompleter(m_pathCompleter);
fd7f51cd Poul Sander 2026-05-22 15:30 145
fd7f51cd Poul Sander 2026-05-22 15:30 146
    auto *pathRow = new QHBoxLayout;
fd7f51cd Poul Sander 2026-05-22 15:30 147
    pathRow->setContentsMargins(4, 4, 4, 0);
fd7f51cd Poul Sander 2026-05-22 15:30 148
    pathRow->addWidget(m_pathEdit, 1);
fd7f51cd Poul Sander 2026-05-22 15:30 149
    pathRow->addWidget(m_goButton);
bf525669 Poul Sander 2026-05-30 23:18 150
    pathRow->addWidget(m_prevFolderButton);
bf525669 Poul Sander 2026-05-30 23:18 151
    pathRow->addWidget(m_nextFolderButton);
fd7f51cd Poul Sander 2026-05-22 15:30 152
fd7f51cd Poul Sander 2026-05-22 15:30 153
    auto *browserPage = new QWidget;
fd7f51cd Poul Sander 2026-05-22 15:30 154
    auto *vbox = new QVBoxLayout(browserPage);
fd7f51cd Poul Sander 2026-05-22 15:30 155
    vbox->setContentsMargins(0, 0, 0, 0);
fd7f51cd Poul Sander 2026-05-22 15:30 156
    vbox->setSpacing(0);
fd7f51cd Poul Sander 2026-05-22 15:30 157
    vbox->addLayout(pathRow);
fd7f51cd Poul Sander 2026-05-22 15:30 158
    vbox->addWidget(m_listView, 1);
fd7f51cd Poul Sander 2026-05-22 15:30 159
61136b77 Poul Sander 2026-02-20 22:45 160
    // Stack to switch between browser and single image
61136b77 Poul Sander 2026-02-20 22:45 161
    m_stack = new QStackedWidget;
fd7f51cd Poul Sander 2026-05-22 15:30 162
    m_stack->addWidget(browserPage);
61136b77 Poul Sander 2026-02-20 22:45 163
    m_stack->addWidget(m_imageView);
fd7f51cd Poul Sander 2026-05-22 15:30 164
    m_browserPage = browserPage;
61136b77 Poul Sander 2026-02-20 22:45 165
61136b77 Poul Sander 2026-02-20 22:45 166
    setCentralWidget(m_stack);
cbc7d22b Poul Sander 2026-02-24 19:23 167
fd7f51cd Poul Sander 2026-05-22 15:30 168
    updatePathField(QFile::encodeName(QDir::homePath()));
fd7f51cd Poul Sander 2026-05-22 15:30 169
cbc7d22b Poul Sander 2026-02-24 19:23 170
    // Dock widgets — can be dragged, floated and stacked by the user
3bee3e3e Poul Sander 2026-03-05 17:07 171
    m_rootDock = new QDockWidget(tr("Roots"), this);
3bee3e3e Poul Sander 2026-03-05 17:07 172
    m_rootDock->setObjectName("RootDock");
3bee3e3e Poul Sander 2026-03-05 17:07 173
    m_rootDock->setWidget(m_rootList);
3bee3e3e Poul Sander 2026-03-05 17:07 174
    addDockWidget(Qt::LeftDockWidgetArea, m_rootDock);
3bee3e3e Poul Sander 2026-03-05 17:07 175
cbc7d22b Poul Sander 2026-02-24 19:23 176
    m_folderDock = new QDockWidget(tr("Folders"), this);
cbc7d22b Poul Sander 2026-02-24 19:23 177
    m_folderDock->setObjectName("FolderDock");
3bee3e3e Poul Sander 2026-03-05 17:07 178
    m_folderDock->setWidget(m_treeView);
cbc7d22b Poul Sander 2026-02-24 19:23 179
    addDockWidget(Qt::LeftDockWidgetArea, m_folderDock);
3bee3e3e Poul Sander 2026-03-05 17:07 180
    splitDockWidget(m_rootDock, m_folderDock, Qt::Vertical);
cbc7d22b Poul Sander 2026-02-24 19:23 181
cbc7d22b Poul Sander 2026-02-24 19:23 182
    m_previewDock = new QDockWidget(tr("Preview"), this);
cbc7d22b Poul Sander 2026-02-24 19:23 183
    m_previewDock->setObjectName("PreviewDock");
cbc7d22b Poul Sander 2026-02-24 19:23 184
    m_previewDock->setWidget(m_previewLabel);
cbc7d22b Poul Sander 2026-02-24 19:23 185
    addDockWidget(Qt::RightDockWidgetArea, m_previewDock);
53fdab93 Poul Sander 2026-05-18 18:45 186
53fdab93 Poul Sander 2026-05-18 18:45 187
    // EXIF info dock — placed below the preview dock by default
53fdab93 Poul Sander 2026-05-18 18:45 188
    m_exifTable = new QTableWidget(0, 2);
53fdab93 Poul Sander 2026-05-18 18:45 189
    m_exifTable->setHorizontalHeaderLabels({tr("Field"), tr("Value")});
53fdab93 Poul Sander 2026-05-18 18:45 190
    m_exifTable->horizontalHeader()->setStretchLastSection(true);
53fdab93 Poul Sander 2026-05-18 18:45 191
    m_exifTable->verticalHeader()->hide();
53fdab93 Poul Sander 2026-05-18 18:45 192
    m_exifTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
53fdab93 Poul Sander 2026-05-18 18:45 193
    m_exifTable->setSelectionMode(QAbstractItemView::NoSelection);
53fdab93 Poul Sander 2026-05-18 18:45 194
    m_exifTable->setShowGrid(false);
53fdab93 Poul Sander 2026-05-18 18:45 195
    m_exifTable->setAlternatingRowColors(true);
53fdab93 Poul Sander 2026-05-18 18:45 196
    m_exifTable->setMinimumWidth(200);
53fdab93 Poul Sander 2026-05-18 18:45 197
53fdab93 Poul Sander 2026-05-18 18:45 198
    m_exifDock = new QDockWidget(tr("EXIF Info"), this);
53fdab93 Poul Sander 2026-05-18 18:45 199
    m_exifDock->setObjectName("ExifDock");
53fdab93 Poul Sander 2026-05-18 18:45 200
    m_exifDock->setWidget(m_exifTable);
53fdab93 Poul Sander 2026-05-18 18:45 201
    addDockWidget(Qt::RightDockWidgetArea, m_exifDock);
53fdab93 Poul Sander 2026-05-18 18:45 202
    splitDockWidget(m_previewDock, m_exifDock, Qt::Vertical);
032aa1e1 Poul Sander 2026-02-19 18:55 203
}
032aa1e1 Poul Sander 2026-02-19 18:55 204
f61bf886 Poul Sander 2026-02-23 19:27 205
void MainWindow::setupMenuBar()
f61bf886 Poul Sander 2026-02-23 19:27 206
{
ccb726b8 Poul Sander 2026-02-23 22:43 207
    m_menuBar = menuBar();
0df01dc1 Poul Sander 2026-02-24 18:51 208
    m_menuBar->setStyleSheet(
0df01dc1 Poul Sander 2026-02-24 18:51 209
        "QMenuBar { padding: 1px 2px; }"
0df01dc1 Poul Sander 2026-02-24 18:51 210
        "QMenuBar::item { padding: 2px 6px; }"
0df01dc1 Poul Sander 2026-02-24 18:51 211
    );
4c41f5ff Poul Sander 2026-05-31 00:01 212
    QMenu *viewMenu = m_menuBar->addMenu(tr("&View"));
3bee3e3e Poul Sander 2026-03-05 17:07 213
    viewMenu->addAction(m_rootDock->toggleViewAction());
cbc7d22b Poul Sander 2026-02-24 19:23 214
    viewMenu->addAction(m_folderDock->toggleViewAction());
cbc7d22b Poul Sander 2026-02-24 19:23 215
    viewMenu->addAction(m_previewDock->toggleViewAction());
53fdab93 Poul Sander 2026-05-18 18:45 216
    viewMenu->addAction(m_exifDock->toggleViewAction());
cbc7d22b Poul Sander 2026-02-24 19:23 217
4c41f5ff Poul Sander 2026-05-31 00:01 218
    QMenu *editMenu = m_menuBar->addMenu(tr("&Edit"));
4c41f5ff Poul Sander 2026-05-31 00:01 219
    QAction *preferencesAction = editMenu->addAction(tr("&Preferences"));
f61bf886 Poul Sander 2026-02-23 19:27 220
    connect(preferencesAction, &QAction::triggered, this, &MainWindow::onPreferencesTriggered);
f61bf886 Poul Sander 2026-02-23 19:27 221
}
f61bf886 Poul Sander 2026-02-23 19:27 222
665ac308 Poul Sander 2026-02-23 19:34 223
void MainWindow::navigateToFolder(const QString &path)
665ac308 Poul Sander 2026-02-23 19:34 224
{
80a9926d Poul Sander 2026-05-19 20:28 225
    navigateToFolder(QFile::encodeName(path));
80a9926d Poul Sander 2026-05-19 20:28 226
}
80a9926d Poul Sander 2026-05-19 20:28 227
bf525669 Poul Sander 2026-05-30 23:18 228
QByteArray MainWindow::siblingFolder(int delta) const
bf525669 Poul Sander 2026-05-30 23:18 229
{
bf525669 Poul Sander 2026-05-30 23:18 230
    QByteArray currentDir = m_imageModel->currentDirectory();
bf525669 Poul Sander 2026-05-30 23:18 231
    if (currentDir.isEmpty())
bf525669 Poul Sander 2026-05-30 23:18 232
        return {};
bf525669 Poul Sander 2026-05-30 23:18 233
bf525669 Poul Sander 2026-05-30 23:18 234
    namespace fs = std::filesystem;
bf525669 Poul Sander 2026-05-30 23:18 235
    fs::path current(currentDir.toStdString());
bf525669 Poul Sander 2026-05-30 23:18 236
    fs::path parent = current.parent_path();
bf525669 Poul Sander 2026-05-30 23:18 237
    if (parent.empty() || parent == current)
bf525669 Poul Sander 2026-05-30 23:18 238
        return {};
bf525669 Poul Sander 2026-05-30 23:18 239
bf525669 Poul Sander 2026-05-30 23:18 240
    QByteArray parentPath = QByteArray::fromStdString(parent.native());
bf525669 Poul Sander 2026-05-30 23:18 241
    QModelIndex parentIdx = m_dirModel->indexForPath(parentPath);
bf525669 Poul Sander 2026-05-30 23:18 242
    if (!parentIdx.isValid())
bf525669 Poul Sander 2026-05-30 23:18 243
        return {};
bf525669 Poul Sander 2026-05-30 23:18 244
bf525669 Poul Sander 2026-05-30 23:18 245
    int count = m_dirModel->rowCount(parentIdx);
bf525669 Poul Sander 2026-05-30 23:18 246
    int currentRow = -1;
bf525669 Poul Sander 2026-05-30 23:18 247
    for (int i = 0; i < count; ++i) {
bf525669 Poul Sander 2026-05-30 23:18 248
        QModelIndex idx = m_dirModel->index(i, 0, parentIdx);
bf525669 Poul Sander 2026-05-30 23:18 249
        if (m_dirModel->filePath(idx) == currentDir) {
bf525669 Poul Sander 2026-05-30 23:18 250
            currentRow = i;
bf525669 Poul Sander 2026-05-30 23:18 251
            break;
bf525669 Poul Sander 2026-05-30 23:18 252
        }
bf525669 Poul Sander 2026-05-30 23:18 253
    }
bf525669 Poul Sander 2026-05-30 23:18 254
bf525669 Poul Sander 2026-05-30 23:18 255
    if (currentRow < 0)
bf525669 Poul Sander 2026-05-30 23:18 256
        return {};
bf525669 Poul Sander 2026-05-30 23:18 257
bf525669 Poul Sander 2026-05-30 23:18 258
    int targetRow = currentRow + delta;
bf525669 Poul Sander 2026-05-30 23:18 259
    if (targetRow < 0 || targetRow >= count)
bf525669 Poul Sander 2026-05-30 23:18 260
        return {};
bf525669 Poul Sander 2026-05-30 23:18 261
bf525669 Poul Sander 2026-05-30 23:18 262
    QModelIndex targetIdx = m_dirModel->index(targetRow, 0, parentIdx);
bf525669 Poul Sander 2026-05-30 23:18 263
    return m_dirModel->filePath(targetIdx);
bf525669 Poul Sander 2026-05-30 23:18 264
}
bf525669 Poul Sander 2026-05-30 23:18 265
bf525669 Poul Sander 2026-05-30 23:18 266
void MainWindow::navigateToPrevFolder()
bf525669 Poul Sander 2026-05-30 23:18 267
{
bf525669 Poul Sander 2026-05-30 23:18 268
    QByteArray path = siblingFolder(-1);
bf525669 Poul Sander 2026-05-30 23:18 269
    if (!path.isEmpty())
bf525669 Poul Sander 2026-05-30 23:18 270
        navigateToFolder(path);
bf525669 Poul Sander 2026-05-30 23:18 271
}
bf525669 Poul Sander 2026-05-30 23:18 272
bf525669 Poul Sander 2026-05-30 23:18 273
void MainWindow::navigateToNextFolder()
bf525669 Poul Sander 2026-05-30 23:18 274
{
bf525669 Poul Sander 2026-05-30 23:18 275
    QByteArray path = siblingFolder(+1);
bf525669 Poul Sander 2026-05-30 23:18 276
    if (!path.isEmpty())
bf525669 Poul Sander 2026-05-30 23:18 277
        navigateToFolder(path);
bf525669 Poul Sander 2026-05-30 23:18 278
}
bf525669 Poul Sander 2026-05-30 23:18 279
80a9926d Poul Sander 2026-05-19 20:28 280
void MainWindow::navigateToFolder(const QByteArray &path)
80a9926d Poul Sander 2026-05-19 20:28 281
{
665ac308 Poul Sander 2026-02-23 19:34 282
    m_previousFolderPath = m_imageModel->currentDirectory();
665ac308 Poul Sander 2026-02-23 19:34 283
    m_imageModel->setDirectory(path);
40efdfde Poul Sander 2026-05-19 21:24 284
    QModelIndex dirIdx = m_dirModel->indexForPath(path);
665ac308 Poul Sander 2026-02-23 19:34 285
    if (dirIdx.isValid())
665ac308 Poul Sander 2026-02-23 19:34 286
        m_treeView->setCurrentIndex(dirIdx);
fd7f51cd Poul Sander 2026-05-22 15:30 287
    updatePathField(path);
665ac308 Poul Sander 2026-02-23 19:34 288
    QTimer::singleShot(0, this, [this]() {
665ac308 Poul Sander 2026-02-23 19:34 289
        selectPreviousFolderIfExists();
665ac308 Poul Sander 2026-02-23 19:34 290
        loadVisibleThumbnails();
665ac308 Poul Sander 2026-02-23 19:34 291
    });
665ac308 Poul Sander 2026-02-23 19:34 292
}
665ac308 Poul Sander 2026-02-23 19:34 293
fd7f51cd Poul Sander 2026-05-22 15:30 294
void MainWindow::updatePathField(const QByteArray &path)
fd7f51cd Poul Sander 2026-05-22 15:30 295
{
fd7f51cd Poul Sander 2026-05-22 15:30 296
    if (!m_pathEdit)
fd7f51cd Poul Sander 2026-05-22 15:30 297
        return;
fd7f51cd Poul Sander 2026-05-22 15:30 298
    const QString text = QFile::decodeName(path);
fd7f51cd Poul Sander 2026-05-22 15:30 299
    if (m_pathEdit->text() != text)
fd7f51cd Poul Sander 2026-05-22 15:30 300
        m_pathEdit->setText(text);
fd7f51cd Poul Sander 2026-05-22 15:30 301
}
fd7f51cd Poul Sander 2026-05-22 15:30 302
fd7f51cd Poul Sander 2026-05-22 15:30 303
void MainWindow::onPathEntered()
fd7f51cd Poul Sander 2026-05-22 15:30 304
{
fd7f51cd Poul Sander 2026-05-22 15:30 305
    const QString text = m_pathEdit->text().trimmed();
fd7f51cd Poul Sander 2026-05-22 15:30 306
    if (text.isEmpty())
fd7f51cd Poul Sander 2026-05-22 15:30 307
        return;
fd7f51cd Poul Sander 2026-05-22 15:30 308
    openPath(text);
fd7f51cd Poul Sander 2026-05-22 15:30 309
}
fd7f51cd Poul Sander 2026-05-22 15:30 310
48607848 Poul Sander 2026-05-19 19:20 311
void MainWindow::openPath(const QString &path)
48607848 Poul Sander 2026-05-19 19:20 312
{
48607848 Poul Sander 2026-05-19 19:20 313
    QFileInfo info(path);
48607848 Poul Sander 2026-05-19 19:20 314
    if (!info.exists())
48607848 Poul Sander 2026-05-19 19:20 315
        return;
48607848 Poul Sander 2026-05-19 19:20 316
48607848 Poul Sander 2026-05-19 19:20 317
    if (info.isDir()) {
80a9926d Poul Sander 2026-05-19 20:28 318
        navigateToFolder(QFile::encodeName(info.absoluteFilePath()));
48607848 Poul Sander 2026-05-19 19:20 319
    } else if (info.isFile()) {
80a9926d Poul Sander 2026-05-19 20:28 320
        navigateToFolder(QFile::encodeName(info.absolutePath()));
48607848 Poul Sander 2026-05-19 19:20 321
48607848 Poul Sander 2026-05-19 19:20 322
        // ImageModel::setDirectory is synchronous, so items are ready now.
48607848 Poul Sander 2026-05-19 19:20 323
        // Find the file in the model and open it.
80a9926d Poul Sander 2026-05-19 20:28 324
        QByteArray target = QFile::encodeName(info.absoluteFilePath());
48607848 Poul Sander 2026-05-19 19:20 325
        for (int i = 0; i < m_imageModel->rowCount(); ++i) {
48607848 Poul Sander 2026-05-19 19:20 326
            QModelIndex idx = m_imageModel->index(i);
80a9926d Poul Sander 2026-05-19 20:28 327
            if (m_imageModel->filePath(idx) == target) {
48607848 Poul Sander 2026-05-19 19:20 328
                m_listView->setCurrentIndex(idx);
48607848 Poul Sander 2026-05-19 19:20 329
                m_listView->scrollTo(idx);
48607848 Poul Sander 2026-05-19 19:20 330
                showPreview(idx);
48607848 Poul Sander 2026-05-19 19:20 331
                if (!m_imageModel->isFolder(idx))
48607848 Poul Sander 2026-05-19 19:20 332
                    enterSingleImageMode(idx);
48607848 Poul Sander 2026-05-19 19:20 333
                break;
48607848 Poul Sander 2026-05-19 19:20 334
            }
48607848 Poul Sander 2026-05-19 19:20 335
        }
48607848 Poul Sander 2026-05-19 19:20 336
    }
48607848 Poul Sander 2026-05-19 19:20 337
}
48607848 Poul Sander 2026-05-19 19:20 338
665ac308 Poul Sander 2026-02-23 19:34 339
void MainWindow::selectPreviousFolderIfExists()
665ac308 Poul Sander 2026-02-23 19:34 340
{
665ac308 Poul Sander 2026-02-23 19:34 341
    if (m_previousFolderPath.isEmpty())
665ac308 Poul Sander 2026-02-23 19:34 342
        return;
665ac308 Poul Sander 2026-02-23 19:34 343
665ac308 Poul Sander 2026-02-23 19:34 344
    // Find the previous folder in the current directory's contents
665ac308 Poul Sander 2026-02-23 19:34 345
    for (int i = 0; i < m_imageModel->rowCount(); ++i) {
665ac308 Poul Sander 2026-02-23 19:34 346
        QModelIndex idx = m_imageModel->index(i);
665ac308 Poul Sander 2026-02-23 19:34 347
        if (m_imageModel->isFolder(idx)) {
80a9926d Poul Sander 2026-05-19 20:28 348
            QByteArray folderPath = m_imageModel->filePath(idx);
665ac308 Poul Sander 2026-02-23 19:34 349
            if (folderPath == m_previousFolderPath) {
665ac308 Poul Sander 2026-02-23 19:34 350
                m_listView->setCurrentIndex(idx);
665ac308 Poul Sander 2026-02-23 19:34 351
                m_listView->scrollTo(idx);
665ac308 Poul Sander 2026-02-23 19:34 352
                return;
665ac308 Poul Sander 2026-02-23 19:34 353
            }
665ac308 Poul Sander 2026-02-23 19:34 354
        }
665ac308 Poul Sander 2026-02-23 19:34 355
    }
665ac308 Poul Sander 2026-02-23 19:34 356
}
665ac308 Poul Sander 2026-02-23 19:34 357
032aa1e1 Poul Sander 2026-02-19 18:55 358
void MainWindow::setupConnections()
032aa1e1 Poul Sander 2026-02-19 18:55 359
{
fd7f51cd Poul Sander 2026-05-22 15:30 360
    connect(m_pathEdit, &QLineEdit::returnPressed,
fd7f51cd Poul Sander 2026-05-22 15:30 361
            this, &MainWindow::onPathEntered);
fd7f51cd Poul Sander 2026-05-22 15:30 362
    connect(m_goButton, &QPushButton::clicked,
fd7f51cd Poul Sander 2026-05-22 15:30 363
            this, &MainWindow::onPathEntered);
bf525669 Poul Sander 2026-05-30 23:18 364
    connect(m_prevFolderButton, &QPushButton::clicked,
bf525669 Poul Sander 2026-05-30 23:18 365
            this, &MainWindow::navigateToPrevFolder);
bf525669 Poul Sander 2026-05-30 23:18 366
    connect(m_nextFolderButton, &QPushButton::clicked,
bf525669 Poul Sander 2026-05-30 23:18 367
            this, &MainWindow::navigateToNextFolder);
fd7f51cd Poul Sander 2026-05-22 15:30 368
    connect(m_pathEdit, &QLineEdit::textEdited,
fd7f51cd Poul Sander 2026-05-22 15:30 369
            m_pathCompleterModel, &PathCompleterModel::setPrefix);
fd7f51cd Poul Sander 2026-05-22 15:30 370
3bee3e3e Poul Sander 2026-03-05 17:07 371
    connect(m_rootList, &QListWidget::currentItemChanged, this,
3bee3e3e Poul Sander 2026-03-05 17:07 372
            [this](QListWidgetItem *current, QListWidgetItem *) {
3bee3e3e Poul Sander 2026-03-05 17:07 373
                if (current == nullptr)
3bee3e3e Poul Sander 2026-03-05 17:07 374
                    return;
3bee3e3e Poul Sander 2026-03-05 17:07 375
3bee3e3e Poul Sander 2026-03-05 17:07 376
                QString rootPath = current->data(Qt::UserRole).toString();
fc5f2e27 Poul Sander 2026-02-22 22:06 377
                m_dirModel->setRootPath(rootPath);
40efdfde Poul Sander 2026-05-19 21:24 378
                m_treeView->setRootIndex(m_dirModel->rootIndex());
fc5f2e27 Poul Sander 2026-02-22 22:06 379
            });
fc5f2e27 Poul Sander 2026-02-22 22:06 380
032aa1e1 Poul Sander 2026-02-19 18:55 381
    connect(m_treeView, &QTreeView::clicked, this,
032aa1e1 Poul Sander 2026-02-19 18:55 382
            [this](const QModelIndex &index)
6fcd6c90 Poul Sander 2026-02-19 21:12 383
            {
40efdfde Poul Sander 2026-05-19 21:24 384
                QByteArray path = m_dirModel->filePath(index);
665ac308 Poul Sander 2026-02-23 19:34 385
                navigateToFolder(path);
6fcd6c90 Poul Sander 2026-02-19 21:12 386
            });
032aa1e1 Poul Sander 2026-02-19 18:55 387
032aa1e1 Poul Sander 2026-02-19 18:55 388
    connect(m_listView, &QListView::clicked, this,
cfe4a67b Poul Sander 2026-02-19 23:06 389
            [this](const QModelIndex &index) { showPreview(index); });
6fcd6c90 Poul Sander 2026-02-19 21:12 390
cfe4a67b Poul Sander 2026-02-19 23:06 391
    connect(m_listView->selectionModel(), &QItemSelectionModel::currentChanged,
cfe4a67b Poul Sander 2026-02-19 23:06 392
            this, [this](const QModelIndex &current) { showPreview(current); });
6fcd6c90 Poul Sander 2026-02-19 21:12 393
61136b77 Poul Sander 2026-02-20 22:45 394
    connect(m_listView, &QListView::doubleClicked, this,
09130ab9 Poul Sander 2026-02-22 22:19 395
            [this](const QModelIndex &index) {
09130ab9 Poul Sander 2026-02-22 22:19 396
                if (m_imageModel->isFolder(index)) {
80a9926d Poul Sander 2026-05-19 20:28 397
                    QByteArray path = m_imageModel->filePath(index);
665ac308 Poul Sander 2026-02-23 19:34 398
                    navigateToFolder(path);
09130ab9 Poul Sander 2026-02-22 22:19 399
                } else {
09130ab9 Poul Sander 2026-02-22 22:19 400
                    enterSingleImageMode(index);
09130ab9 Poul Sander 2026-02-22 22:19 401
                }
09130ab9 Poul Sander 2026-02-22 22:19 402
            });
61136b77 Poul Sander 2026-02-20 22:45 403
237d2c81 Poul Sander 2026-06-06 14:53 404
    connect(m_listView, &QListView::customContextMenuRequested,
237d2c81 Poul Sander 2026-06-06 14:53 405
            this, &MainWindow::showImageContextMenu);
237d2c81 Poul Sander 2026-06-06 14:53 406
d93c1ab0 Poul Sander 2026-06-08 21:44 407
    connect(m_imageView, &ImageViewWidget::customContextMenuRequested,
d93c1ab0 Poul Sander 2026-06-08 21:44 408
            this, &MainWindow::showImageViewContextMenu);
d93c1ab0 Poul Sander 2026-06-08 21:44 409
61136b77 Poul Sander 2026-02-20 22:45 410
    connect(m_imageView, &ImageViewWidget::closeRequested,
61136b77 Poul Sander 2026-02-20 22:45 411
            this, &MainWindow::leaveSingleImageMode);
beb4a823 Poul Sander 2026-05-19 21:53 412
    connect(m_imageView, &ImageViewWidget::editCaptionRequested,
beb4a823 Poul Sander 2026-05-19 21:53 413
        this, &MainWindow::onEditCaption);
1ffc1962 Poul Sander 2026-02-20 23:41 414
    connect(m_imageView, &ImageViewWidget::nextRequested, this, [this]() {
1ffc1962 Poul Sander 2026-02-20 23:41 415
        QModelIndex cur = m_listView->currentIndex();
1ffc1962 Poul Sander 2026-02-20 23:41 416
        int next = cur.isValid() ? cur.row() + 1 : 0;
09130ab9 Poul Sander 2026-02-22 22:19 417
        while (next < m_imageModel->rowCount() && m_imageModel->isFolder(m_imageModel->index(next)))
09130ab9 Poul Sander 2026-02-22 22:19 418
            ++next;
1ffc1962 Poul Sander 2026-02-20 23:41 419
        if (next < m_imageModel->rowCount())
1ffc1962 Poul Sander 2026-02-20 23:41 420
        {
1ffc1962 Poul Sander 2026-02-20 23:41 421
            QModelIndex idx = m_imageModel->index(next);
1ffc1962 Poul Sander 2026-02-20 23:41 422
            m_listView->setCurrentIndex(idx);
1ffc1962 Poul Sander 2026-02-20 23:41 423
            enterSingleImageMode(idx);
1ffc1962 Poul Sander 2026-02-20 23:41 424
        }
1ffc1962 Poul Sander 2026-02-20 23:41 425
    });
1ffc1962 Poul Sander 2026-02-20 23:41 426
1ffc1962 Poul Sander 2026-02-20 23:41 427
    connect(m_imageView, &ImageViewWidget::previousRequested, this, [this]() {
1ffc1962 Poul Sander 2026-02-20 23:41 428
        QModelIndex cur = m_listView->currentIndex();
1ffc1962 Poul Sander 2026-02-20 23:41 429
        int prev = cur.isValid() ? cur.row() - 1 : 0;
09130ab9 Poul Sander 2026-02-22 22:19 430
        while (prev >= 0 && m_imageModel->isFolder(m_imageModel->index(prev)))
09130ab9 Poul Sander 2026-02-22 22:19 431
            --prev;
1ffc1962 Poul Sander 2026-02-20 23:41 432
        if (prev >= 0)
1ffc1962 Poul Sander 2026-02-20 23:41 433
        {
1ffc1962 Poul Sander 2026-02-20 23:41 434
            QModelIndex idx = m_imageModel->index(prev);
1ffc1962 Poul Sander 2026-02-20 23:41 435
            m_listView->setCurrentIndex(idx);
1ffc1962 Poul Sander 2026-02-20 23:41 436
            enterSingleImageMode(idx);
1ffc1962 Poul Sander 2026-02-20 23:41 437
        }
1ffc1962 Poul Sander 2026-02-20 23:41 438
    });
1ffc1962 Poul Sander 2026-02-20 23:41 439
6fcd6c90 Poul Sander 2026-02-19 21:12 440
    connect(m_listView->verticalScrollBar(), &QScrollBar::valueChanged,
6fcd6c90 Poul Sander 2026-02-19 21:12 441
            this, [this]()
6fcd6c90 Poul Sander 2026-02-19 21:12 442
            { loadVisibleThumbnails(); });
6fcd6c90 Poul Sander 2026-02-19 21:12 443
6fcd6c90 Poul Sander 2026-02-19 21:12 444
    // Also catch resize — visible range changes when the viewport is resized
6fcd6c90 Poul Sander 2026-02-19 21:12 445
    m_listView->viewport()->installEventFilter(this);
ff6ac640 Poul Sander 2026-06-02 16:53 446
ff6ac640 Poul Sander 2026-06-02 16:53 447
    // The folder list and tree views consume Ctrl+Left/Ctrl+Right for their own
ff6ac640 Poul Sander 2026-06-02 16:53 448
    // item navigation, so those keys never reach MainWindow::keyPressEvent.
ff6ac640 Poul Sander 2026-06-02 16:53 449
    // Filter them here so PrevFolder/NextFolder shortcuts work while a view has focus.
ff6ac640 Poul Sander 2026-06-02 16:53 450
    m_listView->installEventFilter(this);
ff6ac640 Poul Sander 2026-06-02 16:53 451
    m_treeView->installEventFilter(this);
6fcd6c90 Poul Sander 2026-02-19 21:12 452
}
6fcd6c90 Poul Sander 2026-02-19 21:12 453
6fcd6c90 Poul Sander 2026-02-19 21:12 454
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
6fcd6c90 Poul Sander 2026-02-19 21:12 455
{
6fcd6c90 Poul Sander 2026-02-19 21:12 456
    if (obj == m_listView->viewport() && event->type() == QEvent::Resize)
6fcd6c90 Poul Sander 2026-02-19 21:12 457
        QTimer::singleShot(0, this, &MainWindow::loadVisibleThumbnails);
6fcd6c90 Poul Sander 2026-02-19 21:12 458
ff6ac640 Poul Sander 2026-06-02 16:53 459
    if ((obj == m_listView || obj == m_treeView)
ff6ac640 Poul Sander 2026-06-02 16:53 460
        && event->type() == QEvent::KeyPress
ff6ac640 Poul Sander 2026-06-02 16:53 461
        && m_stack->currentWidget() == m_browserPage)
ff6ac640 Poul Sander 2026-06-02 16:53 462
    {
ff6ac640 Poul Sander 2026-06-02 16:53 463
        const ShortcutManager &sc = ShortcutManager::instance();
ff6ac640 Poul Sander 2026-06-02 16:53 464
        QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
ff6ac640 Poul Sander 2026-06-02 16:53 465
ff6ac640 Poul Sander 2026-06-02 16:53 466
        if (sc.matches(keyEvent, ShortcutManager::PrevFolder)) {
ff6ac640 Poul Sander 2026-06-02 16:53 467
            navigateToPrevFolder();
ff6ac640 Poul Sander 2026-06-02 16:53 468
            return true;
ff6ac640 Poul Sander 2026-06-02 16:53 469
        }
ff6ac640 Poul Sander 2026-06-02 16:53 470
        if (sc.matches(keyEvent, ShortcutManager::NextFolder)) {
ff6ac640 Poul Sander 2026-06-02 16:53 471
            navigateToNextFolder();
ff6ac640 Poul Sander 2026-06-02 16:53 472
            return true;
ff6ac640 Poul Sander 2026-06-02 16:53 473
        }
a686d839 Poul Sander 2026-06-06 22:34 474
        if (obj == m_listView
a686d839 Poul Sander 2026-06-06 22:34 475
            && sc.matches(keyEvent, ShortcutManager::MoveToTrash)) {
a686d839 Poul Sander 2026-06-06 22:34 476
            moveCurrentImageToTrash();
a686d839 Poul Sander 2026-06-06 22:34 477
            return true;
a686d839 Poul Sander 2026-06-06 22:34 478
        }
ff6ac640 Poul Sander 2026-06-02 16:53 479
    }
ff6ac640 Poul Sander 2026-06-02 16:53 480
6fcd6c90 Poul Sander 2026-02-19 21:12 481
    return QMainWindow::eventFilter(obj, event);
6fcd6c90 Poul Sander 2026-02-19 21:12 482
}
6fcd6c90 Poul Sander 2026-02-19 21:12 483
6fcd6c90 Poul Sander 2026-02-19 21:12 484
void MainWindow::loadVisibleThumbnails()
6fcd6c90 Poul Sander 2026-02-19 21:12 485
{
6fcd6c90 Poul Sander 2026-02-19 21:12 486
    QModelIndex topLeft = m_listView->indexAt(QPoint(0, 0));
6fcd6c90 Poul Sander 2026-02-19 21:12 487
    QModelIndex bottomRight = m_listView->indexAt(
6fcd6c90 Poul Sander 2026-02-19 21:12 488
        QPoint(m_listView->viewport()->width() - 1,
6fcd6c90 Poul Sander 2026-02-19 21:12 489
               m_listView->viewport()->height() - 1));
6fcd6c90 Poul Sander 2026-02-19 21:12 490
6fcd6c90 Poul Sander 2026-02-19 21:12 491
    int first = topLeft.isValid() ? topLeft.row() : 0;
6fcd6c90 Poul Sander 2026-02-19 21:12 492
    int last = bottomRight.isValid() ? bottomRight.row()
6fcd6c90 Poul Sander 2026-02-19 21:12 493
                                     : m_imageModel->rowCount() - 1;
6fcd6c90 Poul Sander 2026-02-19 21:12 494
6fcd6c90 Poul Sander 2026-02-19 21:12 495
    if (last < 0)
6fcd6c90 Poul Sander 2026-02-19 21:12 496
        return;
6fcd6c90 Poul Sander 2026-02-19 21:12 497
6fcd6c90 Poul Sander 2026-02-19 21:12 498
    std::cout << "loadVisibleThumbnails: " << first << " - " << last << "\n";
6fcd6c90 Poul Sander 2026-02-19 21:12 499
    m_imageModel->requestThumbnails(first, last);
032aa1e1 Poul Sander 2026-02-19 18:55 500
}
cfe4a67b Poul Sander 2026-02-19 23:06 501
cfe4a67b Poul Sander 2026-02-19 23:06 502
void MainWindow::showPreview(const QModelIndex &index)
cfe4a67b Poul Sander 2026-02-19 23:06 503
{
cfe4a67b Poul Sander 2026-02-19 23:06 504
    if (!index.isValid())
cfe4a67b Poul Sander 2026-02-19 23:06 505
        return;
cfe4a67b Poul Sander 2026-02-19 23:06 506
80a9926d Poul Sander 2026-05-19 20:28 507
    QByteArray path = m_imageModel->filePath(index);
80a9926d Poul Sander 2026-05-19 20:28 508
80a9926d Poul Sander 2026-05-19 20:28 509
    int fd = ::open(path.constData(), O_RDONLY | O_CLOEXEC);
80a9926d Poul Sander 2026-05-19 20:28 510
    if (fd >= 0) {
80a9926d Poul Sander 2026-05-19 20:28 511
        QFile file;
80a9926d Poul Sander 2026-05-19 20:28 512
        if (file.open(fd, QIODevice::ReadOnly, QFileDevice::AutoCloseHandle)) {
80a9926d Poul Sander 2026-05-19 20:28 513
            QImageReader reader(&file);
80a9926d Poul Sander 2026-05-19 20:28 514
            reader.setAutoTransform(true);
80a9926d Poul Sander 2026-05-19 20:28 515
            QSize fullSize = reader.size();
80a9926d Poul Sander 2026-05-19 20:28 516
            if (fullSize.isValid()) {
80a9926d Poul Sander 2026-05-19 20:28 517
                QSize target = fullSize.scaled(m_previewLabel->size(), Qt::KeepAspectRatio);
80a9926d Poul Sander 2026-05-19 20:28 518
                reader.setScaledSize(target);
80a9926d Poul Sander 2026-05-19 20:28 519
            }
80a9926d Poul Sander 2026-05-19 20:28 520
            QImage image = reader.read();
80a9926d Poul Sander 2026-05-19 20:28 521
            m_previewLabel->setPixmap(
80a9926d Poul Sander 2026-05-19 20:28 522
                QPixmap::fromImage(image).scaled(
80a9926d Poul Sander 2026-05-19 20:28 523
                    m_previewLabel->size(),
80a9926d Poul Sander 2026-05-19 20:28 524
                    Qt::KeepAspectRatio,
80a9926d Poul Sander 2026-05-19 20:28 525
                    Qt::SmoothTransformation));
80a9926d Poul Sander 2026-05-19 20:28 526
        }
7889168c Poul Sander 2026-02-26 18:27 527
    }
53fdab93 Poul Sander 2026-05-18 18:45 528
53fdab93 Poul Sander 2026-05-18 18:45 529
    updateExifInfo(path);
53fdab93 Poul Sander 2026-05-18 18:45 530
}
53fdab93 Poul Sander 2026-05-18 18:45 531
80a9926d Poul Sander 2026-05-19 20:28 532
void MainWindow::updateExifInfo(const QByteArray &path)
53fdab93 Poul Sander 2026-05-18 18:45 533
{
80a9926d Poul Sander 2026-05-19 20:28 534
    // Check if this is a folder by attempting stat
80a9926d Poul Sander 2026-05-19 20:28 535
    struct ::stat st{};
80a9926d Poul Sander 2026-05-19 20:28 536
    if (::stat(path.constData(), &st) != 0 || S_ISDIR(st.st_mode)) {
53fdab93 Poul Sander 2026-05-18 18:45 537
        m_exifTable->setRowCount(0);
53fdab93 Poul Sander 2026-05-18 18:45 538
        return;
53fdab93 Poul Sander 2026-05-18 18:45 539
    }
53fdab93 Poul Sander 2026-05-18 18:45 540
    const ExifData data = ExifReader::read(path);
53fdab93 Poul Sander 2026-05-18 18:45 541
    const auto fields = data.toList();
53fdab93 Poul Sander 2026-05-18 18:45 542
    m_exifTable->setRowCount(fields.size());
53fdab93 Poul Sander 2026-05-18 18:45 543
    for (int i = 0; i < fields.size(); ++i) {
53fdab93 Poul Sander 2026-05-18 18:45 544
        m_exifTable->setItem(i, 0, new QTableWidgetItem(fields[i].first));
53fdab93 Poul Sander 2026-05-18 18:45 545
        m_exifTable->setItem(i, 1, new QTableWidgetItem(fields[i].second));
53fdab93 Poul Sander 2026-05-18 18:45 546
    }
53fdab93 Poul Sander 2026-05-18 18:45 547
    m_exifTable->resizeColumnToContents(0);
cfe4a67b Poul Sander 2026-02-19 23:06 548
}
61136b77 Poul Sander 2026-02-20 22:45 549
61136b77 Poul Sander 2026-02-20 22:45 550
void MainWindow::enterSingleImageMode(const QModelIndex &index)
61136b77 Poul Sander 2026-02-20 22:45 551
{
61136b77 Poul Sander 2026-02-20 22:45 552
    if (!index.isValid())
61136b77 Poul Sander 2026-02-20 22:45 553
        return;
61136b77 Poul Sander 2026-02-20 22:45 554
cbc7d22b Poul Sander 2026-02-24 19:23 555
    // Only save state and hide chrome when transitioning from browse mode
cbc7d22b Poul Sander 2026-02-24 19:23 556
    if (m_stack->currentWidget() != m_imageView) {
3bee3e3e Poul Sander 2026-03-05 17:07 557
        m_rootDockWasVisible = m_rootDock->isVisible();
cbc7d22b Poul Sander 2026-02-24 19:23 558
        m_folderDockWasVisible = m_folderDock->isVisible();
cbc7d22b Poul Sander 2026-02-24 19:23 559
        m_previewDockWasVisible = m_previewDock->isVisible();
53fdab93 Poul Sander 2026-05-18 18:45 560
        m_exifDockWasVisible = m_exifDock->isVisible();
3bee3e3e Poul Sander 2026-03-05 17:07 561
        m_rootDock->hide();
cbc7d22b Poul Sander 2026-02-24 19:23 562
        m_folderDock->hide();
cbc7d22b Poul Sander 2026-02-24 19:23 563
        m_previewDock->hide();
53fdab93 Poul Sander 2026-05-18 18:45 564
        m_exifDock->hide();
cbc7d22b Poul Sander 2026-02-24 19:23 565
        m_menuBar->hide();
cbc7d22b Poul Sander 2026-02-24 19:23 566
    }
cbc7d22b Poul Sander 2026-02-24 19:23 567
f61bf886 Poul Sander 2026-02-23 19:27 568
    m_imageView->setBackgroundColor(m_backgroundColorPreference);
80a9926d Poul Sander 2026-05-19 20:28 569
    QByteArray path = m_imageModel->filePath(index);
58103277 Poul Sander 2026-03-01 22:53 570
    m_imageView->setNeighborPaths(computeNeighborPaths(index));
61136b77 Poul Sander 2026-02-20 22:45 571
    m_imageView->setImage(path);
53fdab93 Poul Sander 2026-05-18 18:45 572
    m_imageView->setExifData(ExifReader::read(path));
53fdab93 Poul Sander 2026-05-18 18:45 573
    updateExifInfo(path);
61136b77 Poul Sander 2026-02-20 22:45 574
    m_stack->setCurrentWidget(m_imageView);
61136b77 Poul Sander 2026-02-20 22:45 575
    m_imageView->setFocus();
ccb726b8 Poul Sander 2026-02-23 22:43 576
ccb726b8 Poul Sander 2026-02-23 22:43 577
    if (m_imageViewWasFullScreen) {
ccb726b8 Poul Sander 2026-02-23 22:43 578
        setFullScreenMode(true);
ccb726b8 Poul Sander 2026-02-23 22:43 579
        m_imageViewWasFullScreen = false;
ccb726b8 Poul Sander 2026-02-23 22:43 580
    }
61136b77 Poul Sander 2026-02-20 22:45 581
}
61136b77 Poul Sander 2026-02-20 22:45 582
beb4a823 Poul Sander 2026-05-19 21:53 583
void MainWindow::onEditCaption()
beb4a823 Poul Sander 2026-05-19 21:53 584
{
237d2c81 Poul Sander 2026-06-06 14:53 585
    QByteArray path;
237d2c81 Poul Sander 2026-06-06 14:53 586
    if (m_stack->currentWidget() == m_imageView) {
237d2c81 Poul Sander 2026-06-06 14:53 587
        path = m_imageView->currentPath();
237d2c81 Poul Sander 2026-06-06 14:53 588
    } else {
237d2c81 Poul Sander 2026-06-06 14:53 589
        const QModelIndex index = m_listView->currentIndex();
237d2c81 Poul Sander 2026-06-06 14:53 590
        if (index.isValid() && !m_imageModel->isFolder(index))
237d2c81 Poul Sander 2026-06-06 14:53 591
            path = m_imageModel->filePath(index);
237d2c81 Poul Sander 2026-06-06 14:53 592
    }
237d2c81 Poul Sander 2026-06-06 14:53 593
beb4a823 Poul Sander 2026-05-19 21:53 594
    if (path.isEmpty())
beb4a823 Poul Sander 2026-05-19 21:53 595
        return;
beb4a823 Poul Sander 2026-05-19 21:53 596
835a6bea Poul Sander 2026-05-19 22:03 597
    if (::access(path.constData(), W_OK) != 0) {
835a6bea Poul Sander 2026-05-19 22:03 598
        QMessageBox::warning(this, tr("Cannot Edit Caption"),
835a6bea Poul Sander 2026-05-19 22:03 599
            tr("The file is write-protected and cannot be edited.\n\n%1")
237d2c81 Poul Sander 2026-06-06 14:53 600
                .arg(displayPath(path)));
835a6bea Poul Sander 2026-05-19 22:03 601
        return;
835a6bea Poul Sander 2026-05-19 22:03 602
    }
835a6bea Poul Sander 2026-05-19 22:03 603
beb4a823 Poul Sander 2026-05-19 21:53 604
    ExifData data = ExifReader::read(path);
beb4a823 Poul Sander 2026-05-19 21:53 605
beb4a823 Poul Sander 2026-05-19 21:53 606
    QDialog dlg(this);
beb4a823 Poul Sander 2026-05-19 21:53 607
    dlg.setWindowTitle(tr("Edit Caption"));
beb4a823 Poul Sander 2026-05-19 21:53 608
    auto *layout = new QVBoxLayout(&dlg);
beb4a823 Poul Sander 2026-05-19 21:53 609
    layout->addWidget(new QLabel(tr("Caption-Abstract:"), &dlg));
beb4a823 Poul Sander 2026-05-19 21:53 610
    auto *edit = new QPlainTextEdit(&dlg);
beb4a823 Poul Sander 2026-05-19 21:53 611
    edit->setPlainText(data.captionAbstract);
beb4a823 Poul Sander 2026-05-19 21:53 612
    edit->setMinimumWidth(400);
beb4a823 Poul Sander 2026-05-19 21:53 613
    layout->addWidget(edit);
beb4a823 Poul Sander 2026-05-19 21:53 614
    auto *buttons = new QDialogButtonBox(
beb4a823 Poul Sander 2026-05-19 21:53 615
        QDialogButtonBox::Ok | QDialogButtonBox::Cancel, &dlg);
beb4a823 Poul Sander 2026-05-19 21:53 616
    layout->addWidget(buttons);
beb4a823 Poul Sander 2026-05-19 21:53 617
    connect(buttons, &QDialogButtonBox::accepted, &dlg, &QDialog::accept);
beb4a823 Poul Sander 2026-05-19 21:53 618
    connect(buttons, &QDialogButtonBox::rejected, &dlg, &QDialog::reject);
beb4a823 Poul Sander 2026-05-19 21:53 619
beb4a823 Poul Sander 2026-05-19 21:53 620
    if (dlg.exec() != QDialog::Accepted)
beb4a823 Poul Sander 2026-05-19 21:53 621
        return;
beb4a823 Poul Sander 2026-05-19 21:53 622
beb4a823 Poul Sander 2026-05-19 21:53 623
    const QString newCaption = edit->toPlainText();
beb4a823 Poul Sander 2026-05-19 21:53 624
    if (ExifReader::saveCaption(path, newCaption, data)) {
beb4a823 Poul Sander 2026-05-19 21:53 625
        ExifData updated = ExifReader::read(path);
237d2c81 Poul Sander 2026-06-06 14:53 626
        if (m_imageView->currentPath() == path)
237d2c81 Poul Sander 2026-06-06 14:53 627
            m_imageView->setExifData(updated);
beb4a823 Poul Sander 2026-05-19 21:53 628
        updateExifInfo(path);
beb4a823 Poul Sander 2026-05-19 21:53 629
    }
beb4a823 Poul Sander 2026-05-19 21:53 630
}
beb4a823 Poul Sander 2026-05-19 21:53 631
237d2c81 Poul Sander 2026-06-06 14:53 632
void MainWindow::showImageContextMenu(const QPoint &pos)
237d2c81 Poul Sander 2026-06-06 14:53 633
{
237d2c81 Poul Sander 2026-06-06 14:53 634
    const QModelIndex index = m_listView->indexAt(pos);
237d2c81 Poul Sander 2026-06-06 14:53 635
    if (!index.isValid() || m_imageModel->isFolder(index))
237d2c81 Poul Sander 2026-06-06 14:53 636
        return;
237d2c81 Poul Sander 2026-06-06 14:53 637
237d2c81 Poul Sander 2026-06-06 14:53 638
    m_listView->setCurrentIndex(index);
237d2c81 Poul Sander 2026-06-06 14:53 639
    showPreview(index);
237d2c81 Poul Sander 2026-06-06 14:53 640
237d2c81 Poul Sander 2026-06-06 14:53 641
    const QByteArray path = m_imageModel->filePath(index);
d93c1ab0 Poul Sander 2026-06-08 21:44 642
    executeContextMenu(m_listView->viewport()->mapToGlobal(pos), path, index);
d93c1ab0 Poul Sander 2026-06-08 21:44 643
}
237d2c81 Poul Sander 2026-06-06 14:53 644
d93c1ab0 Poul Sander 2026-06-08 21:44 645
void MainWindow::showImageViewContextMenu(const QPoint &pos)
d93c1ab0 Poul Sander 2026-06-08 21:44 646
{
d93c1ab0 Poul Sander 2026-06-08 21:44 647
    const QByteArray path = m_imageView->currentPath();
d93c1ab0 Poul Sander 2026-06-08 21:44 648
    if (path.isEmpty())
d93c1ab0 Poul Sander 2026-06-08 21:44 649
        return;
d93c1ab0 Poul Sander 2026-06-08 21:44 650
d93c1ab0 Poul Sander 2026-06-08 21:44 651
    const QModelIndex index = m_listView->currentIndex();
d93c1ab0 Poul Sander 2026-06-08 21:44 652
    executeContextMenu(m_imageView->mapToGlobal(pos), path, index);
d93c1ab0 Poul Sander 2026-06-08 21:44 653
}
d93c1ab0 Poul Sander 2026-06-08 21:44 654
d93c1ab0 Poul Sander 2026-06-08 21:44 655
void MainWindow::executeContextMenu(const QPoint &globalPos, const QByteArray &path, const QModelIndex &index)
d93c1ab0 Poul Sander 2026-06-08 21:44 656
{
237d2c81 Poul Sander 2026-06-06 14:53 657
    QMenu menu(this);
237d2c81 Poul Sander 2026-06-06 14:53 658
    QAction *copyFilenameAction = menu.addAction(tr("Copy filename"));
237d2c81 Poul Sander 2026-06-06 14:53 659
    QAction *copyFullPathAction = menu.addAction(tr("Copy full path"));
237d2c81 Poul Sander 2026-06-06 14:53 660
    menu.addSeparator();
48534775 Poul Sander 2026-06-08 18:57 661
    QAction *rotateLeftAction = menu.addAction(tr("Rotate Left"));
48534775 Poul Sander 2026-06-08 18:57 662
    QAction *rotateRightAction = menu.addAction(tr("Rotate Right"));
48534775 Poul Sander 2026-06-08 18:57 663
    menu.addSeparator();
237d2c81 Poul Sander 2026-06-06 14:53 664
    QAction *editDescriptionAction = menu.addAction(tr("Edit description"));
700d6f2d Poul Sander 2026-06-06 15:44 665
    QAction *moveToTrashAction = menu.addAction(tr("Move to trash"));
a686d839 Poul Sander 2026-06-06 22:34 666
    moveToTrashAction->setShortcut(
a686d839 Poul Sander 2026-06-06 22:34 667
        ShortcutManager::instance().shortcut(ShortcutManager::MoveToTrash));
237d2c81 Poul Sander 2026-06-06 14:53 668
d93c1ab0 Poul Sander 2026-06-08 21:44 669
    QAction *chosen = menu.exec(globalPos);
237d2c81 Poul Sander 2026-06-06 14:53 670
    if (chosen == copyFilenameAction) {
237d2c81 Poul Sander 2026-06-06 14:53 671
        QGuiApplication::clipboard()->setText(filenameFromPath(path));
237d2c81 Poul Sander 2026-06-06 14:53 672
    } else if (chosen == copyFullPathAction) {
237d2c81 Poul Sander 2026-06-06 14:53 673
        QGuiApplication::clipboard()->setText(displayPath(path));
48534775 Poul Sander 2026-06-08 18:57 674
    } else if (chosen == rotateLeftAction) {
48534775 Poul Sander 2026-06-08 18:57 675
        rotateImage(index, false);
48534775 Poul Sander 2026-06-08 18:57 676
    } else if (chosen == rotateRightAction) {
48534775 Poul Sander 2026-06-08 18:57 677
        rotateImage(index, true);
237d2c81 Poul Sander 2026-06-06 14:53 678
    } else if (chosen == editDescriptionAction) {
237d2c81 Poul Sander 2026-06-06 14:53 679
        onEditCaption();
700d6f2d Poul Sander 2026-06-06 15:44 680
    } else if (chosen == moveToTrashAction) {
700d6f2d Poul Sander 2026-06-06 15:44 681
        moveImageToTrash(path);
237d2c81 Poul Sander 2026-06-06 14:53 682
    }
237d2c81 Poul Sander 2026-06-06 14:53 683
}
237d2c81 Poul Sander 2026-06-06 14:53 684
48534775 Poul Sander 2026-06-08 18:57 685
void MainWindow::rotateImage(const QModelIndex &index, bool clockwise)
48534775 Poul Sander 2026-06-08 18:57 686
{
48534775 Poul Sander 2026-06-08 18:57 687
    if (!index.isValid() || m_imageModel->isFolder(index))
48534775 Poul Sander 2026-06-08 18:57 688
        return;
48534775 Poul Sander 2026-06-08 18:57 689
48534775 Poul Sander 2026-06-08 18:57 690
    const QByteArray path = m_imageModel->filePath(index);
48534775 Poul Sander 2026-06-08 18:57 691
48534775 Poul Sander 2026-06-08 18:57 692
    if (::access(path.constData(), W_OK) != 0) {
48534775 Poul Sander 2026-06-08 18:57 693
        QMessageBox::warning(this, tr("Cannot Rotate Image"),
48534775 Poul Sander 2026-06-08 18:57 694
            tr("The file is write-protected and cannot be rotated.\n\n%1")
48534775 Poul Sander 2026-06-08 18:57 695
                .arg(displayPath(path)));
48534775 Poul Sander 2026-06-08 18:57 696
        return;
48534775 Poul Sander 2026-06-08 18:57 697
    }
48534775 Poul Sander 2026-06-08 18:57 698
48534775 Poul Sander 2026-06-08 18:57 699
    if (!ExifReader::rotate(path, clockwise)) {
48534775 Poul Sander 2026-06-08 18:57 700
        QMessageBox::warning(this, tr("Cannot Rotate Image"),
48534775 Poul Sander 2026-06-08 18:57 701
            tr("The image orientation could not be changed. The format may not "
48534775 Poul Sander 2026-06-08 18:57 702
               "support EXIF orientation.\n\n%1")
48534775 Poul Sander 2026-06-08 18:57 703
                .arg(displayPath(path)));
48534775 Poul Sander 2026-06-08 18:57 704
        return;
48534775 Poul Sander 2026-06-08 18:57 705
    }
48534775 Poul Sander 2026-06-08 18:57 706
48534775 Poul Sander 2026-06-08 18:57 707
    m_imageModel->refreshThumbnail(index);
48534775 Poul Sander 2026-06-08 18:57 708
    showPreview(index);
48534775 Poul Sander 2026-06-08 18:57 709
48534775 Poul Sander 2026-06-08 18:57 710
    if (m_imageView->currentPath() == path) {
48534775 Poul Sander 2026-06-08 18:57 711
        m_imageView->invalidateCache(path);
48534775 Poul Sander 2026-06-08 18:57 712
        m_imageView->setImage(path);
48534775 Poul Sander 2026-06-08 18:57 713
        m_imageView->setExifData(ExifReader::read(path));
48534775 Poul Sander 2026-06-08 18:57 714
    }
48534775 Poul Sander 2026-06-08 18:57 715
}
48534775 Poul Sander 2026-06-08 18:57 716
700d6f2d Poul Sander 2026-06-06 15:44 717
void MainWindow::moveImageToTrash(const QByteArray &path)
700d6f2d Poul Sander 2026-06-06 15:44 718
{
700d6f2d Poul Sander 2026-06-06 15:44 719
    if (path.isEmpty())
700d6f2d Poul Sander 2026-06-06 15:44 720
        return;
700d6f2d Poul Sander 2026-06-06 15:44 721
700d6f2d Poul Sander 2026-06-06 15:44 722
    const QString fileName = filenameFromPath(path);
700d6f2d Poul Sander 2026-06-06 15:44 723
    const QMessageBox::StandardButton answer = QMessageBox::question(
700d6f2d Poul Sander 2026-06-06 15:44 724
        this,
700d6f2d Poul Sander 2026-06-06 15:44 725
        tr("Move to Trash"),
700d6f2d Poul Sander 2026-06-06 15:44 726
        tr("Move \"%1\" to the trash?").arg(fileName),
700d6f2d Poul Sander 2026-06-06 15:44 727
        QMessageBox::Yes | QMessageBox::Cancel,
700d6f2d Poul Sander 2026-06-06 15:44 728
        QMessageBox::Cancel);
700d6f2d Poul Sander 2026-06-06 15:44 729
    if (answer != QMessageBox::Yes)
700d6f2d Poul Sander 2026-06-06 15:44 730
        return;
700d6f2d Poul Sander 2026-06-06 15:44 731
700d6f2d Poul Sander 2026-06-06 15:44 732
    if (!QFile::moveToTrash(displayPath(path))) {
700d6f2d Poul Sander 2026-06-06 15:44 733
        QMessageBox::warning(this, tr("Cannot Move to Trash"),
700d6f2d Poul Sander 2026-06-06 15:44 734
            tr("The file could not be moved to the trash.\n\n%1")
700d6f2d Poul Sander 2026-06-06 15:44 735
                .arg(displayPath(path)));
700d6f2d Poul Sander 2026-06-06 15:44 736
        return;
700d6f2d Poul Sander 2026-06-06 15:44 737
    }
700d6f2d Poul Sander 2026-06-06 15:44 738
700d6f2d Poul Sander 2026-06-06 15:44 739
    const QByteArray currentDir = m_imageModel->currentDirectory();
700d6f2d Poul Sander 2026-06-06 15:44 740
    m_previewLabel->clear();
700d6f2d Poul Sander 2026-06-06 15:44 741
    m_exifTable->setRowCount(0);
700d6f2d Poul Sander 2026-06-06 15:44 742
    m_imageModel->setDirectory(currentDir);
700d6f2d Poul Sander 2026-06-06 15:44 743
    QTimer::singleShot(0, this, &MainWindow::loadVisibleThumbnails);
700d6f2d Poul Sander 2026-06-06 15:44 744
}
700d6f2d Poul Sander 2026-06-06 15:44 745
a686d839 Poul Sander 2026-06-06 22:34 746
void MainWindow::moveCurrentImageToTrash()
a686d839 Poul Sander 2026-06-06 22:34 747
{
a686d839 Poul Sander 2026-06-06 22:34 748
    const QModelIndex index = m_listView->currentIndex();
a686d839 Poul Sander 2026-06-06 22:34 749
    if (!index.isValid() || m_imageModel->isFolder(index))
a686d839 Poul Sander 2026-06-06 22:34 750
        return;
a686d839 Poul Sander 2026-06-06 22:34 751
a686d839 Poul Sander 2026-06-06 22:34 752
    moveImageToTrash(m_imageModel->filePath(index));
a686d839 Poul Sander 2026-06-06 22:34 753
}
a686d839 Poul Sander 2026-06-06 22:34 754
61136b77 Poul Sander 2026-02-20 22:45 755
void MainWindow::leaveSingleImageMode()
61136b77 Poul Sander 2026-02-20 22:45 756
{
3968465e Poul Sander 2026-02-23 19:13 757
    if (isFullScreen()) {
ccb726b8 Poul Sander 2026-02-23 22:43 758
        m_imageViewWasFullScreen = true;
ccb726b8 Poul Sander 2026-02-23 22:43 759
        setFullScreenMode(false);
61136b77 Poul Sander 2026-02-20 22:45 760
    }
58103277 Poul Sander 2026-03-01 22:53 761
    m_imageView->clearCache();
61136b77 Poul Sander 2026-02-20 22:45 762
    m_stack->setCurrentWidget(m_browserPage);
cbc7d22b Poul Sander 2026-02-24 19:23 763
    m_menuBar->show();
3bee3e3e Poul Sander 2026-03-05 17:07 764
    if (m_rootDockWasVisible)
3bee3e3e Poul Sander 2026-03-05 17:07 765
        m_rootDock->show();
cbc7d22b Poul Sander 2026-02-24 19:23 766
    if (m_folderDockWasVisible)
cbc7d22b Poul Sander 2026-02-24 19:23 767
        m_folderDock->show();
cbc7d22b Poul Sander 2026-02-24 19:23 768
    if (m_previewDockWasVisible)
cbc7d22b Poul Sander 2026-02-24 19:23 769
        m_previewDock->show();
53fdab93 Poul Sander 2026-05-18 18:45 770
    if (m_exifDockWasVisible)
53fdab93 Poul Sander 2026-05-18 18:45 771
        m_exifDock->show();
61136b77 Poul Sander 2026-02-20 22:45 772
    m_listView->setFocus();
61136b77 Poul Sander 2026-02-20 22:45 773
}
61136b77 Poul Sander 2026-02-20 22:45 774
80a9926d Poul Sander 2026-05-19 20:28 775
QList<QByteArray> MainWindow::computeNeighborPaths(const QModelIndex &index) const
58103277 Poul Sander 2026-03-01 22:53 776
{
80a9926d Poul Sander 2026-05-19 20:28 777
    QList<QByteArray> paths;
58103277 Poul Sander 2026-03-01 22:53 778
    int row = index.row();
58103277 Poul Sander 2026-03-01 22:53 779
58103277 Poul Sander 2026-03-01 22:53 780
    // Collect up to CacheKeepBehind non-folder images before current
58103277 Poul Sander 2026-03-01 22:53 781
    int count = 0;
58103277 Poul Sander 2026-03-01 22:53 782
    for (int i = row - 1; i >= 0 && count < ImageViewWidget::CacheKeepBehind; --i) {
58103277 Poul Sander 2026-03-01 22:53 783
        QModelIndex idx = m_imageModel->index(i);
58103277 Poul Sander 2026-03-01 22:53 784
        if (!m_imageModel->isFolder(idx)) {
58103277 Poul Sander 2026-03-01 22:53 785
            paths.prepend(m_imageModel->filePath(idx));
58103277 Poul Sander 2026-03-01 22:53 786
            ++count;
58103277 Poul Sander 2026-03-01 22:53 787
        }
58103277 Poul Sander 2026-03-01 22:53 788
    }
58103277 Poul Sander 2026-03-01 22:53 789
58103277 Poul Sander 2026-03-01 22:53 790
    // Collect up to CacheReadAhead non-folder images after current
58103277 Poul Sander 2026-03-01 22:53 791
    count = 0;
58103277 Poul Sander 2026-03-01 22:53 792
    for (int i = row + 1; i < m_imageModel->rowCount() && count < ImageViewWidget::CacheReadAhead; ++i) {
58103277 Poul Sander 2026-03-01 22:53 793
        QModelIndex idx = m_imageModel->index(i);
58103277 Poul Sander 2026-03-01 22:53 794
        if (!m_imageModel->isFolder(idx)) {
58103277 Poul Sander 2026-03-01 22:53 795
            paths.append(m_imageModel->filePath(idx));
58103277 Poul Sander 2026-03-01 22:53 796
            ++count;
58103277 Poul Sander 2026-03-01 22:53 797
        }
58103277 Poul Sander 2026-03-01 22:53 798
    }
58103277 Poul Sander 2026-03-01 22:53 799
58103277 Poul Sander 2026-03-01 22:53 800
    return paths;
58103277 Poul Sander 2026-03-01 22:53 801
}
58103277 Poul Sander 2026-03-01 22:53 802
ccb726b8 Poul Sander 2026-02-23 22:43 803
void MainWindow::setFullScreenMode(bool fullScreen)
ccb726b8 Poul Sander 2026-02-23 22:43 804
{
ccb726b8 Poul Sander 2026-02-23 22:43 805
    if (fullScreen == isFullScreen())
ccb726b8 Poul Sander 2026-02-23 22:43 806
        return;
ccb726b8 Poul Sander 2026-02-23 22:43 807
ccb726b8 Poul Sander 2026-02-23 22:43 808
    if (fullScreen) {
ccb726b8 Poul Sander 2026-02-23 22:43 809
        m_wasMaximized = isMaximized();
ccb726b8 Poul Sander 2026-02-23 22:43 810
        m_menuBar->hide();
ccb726b8 Poul Sander 2026-02-23 22:43 811
        showFullScreen();
ccb726b8 Poul Sander 2026-02-23 22:43 812
    } else {
cbc7d22b Poul Sander 2026-02-24 19:23 813
        // Only restore menu bar when returning to browse mode
cbc7d22b Poul Sander 2026-02-24 19:23 814
        if (m_stack->currentWidget() != m_imageView)
cbc7d22b Poul Sander 2026-02-24 19:23 815
            m_menuBar->show();
ccb726b8 Poul Sander 2026-02-23 22:43 816
        if (m_wasMaximized)
ccb726b8 Poul Sander 2026-02-23 22:43 817
            showMaximized();
ccb726b8 Poul Sander 2026-02-23 22:43 818
        else
ccb726b8 Poul Sander 2026-02-23 22:43 819
            showNormal();
ccb726b8 Poul Sander 2026-02-23 22:43 820
    }
ccb726b8 Poul Sander 2026-02-23 22:43 821
}
ccb726b8 Poul Sander 2026-02-23 22:43 822
61136b77 Poul Sander 2026-02-20 22:45 823
void MainWindow::keyPressEvent(QKeyEvent *event)
61136b77 Poul Sander 2026-02-20 22:45 824
{
4d6b7476 Poul Sander 2026-05-30 16:39 825
    // THREADING: No race condition with F11 fullscreen toggle.  All cross-thread
4d6b7476 Poul Sander 2026-05-30 16:39 826
    // signal deliveries (thumbnailReady via explicit QueuedConnection,
4d6b7476 Poul Sander 2026-05-30 16:39 827
    // onImageLoaded via AutoConnection resolving to QueuedConnection) execute on
4d6b7476 Poul Sander 2026-05-30 16:39 828
    // the main thread's event loop.  The event loop processes events one at a
4d6b7476 Poul Sander 2026-05-30 16:39 829
    // time, so this handler and any signal slot can never interleave.  All widget
4d6b7476 Poul Sander 2026-05-30 16:39 830
    // state (m_wasMaximized, m_stack, fullscreen flag) is only accessed from the
4d6b7476 Poul Sander 2026-05-30 16:39 831
    // main thread.
83f0e0bc Poul Sander 2026-05-30 17:59 832
    const ShortcutManager &sc = ShortcutManager::instance();
83f0e0bc Poul Sander 2026-05-30 17:59 833
83f0e0bc Poul Sander 2026-05-30 17:59 834
    if (sc.matches(event, ShortcutManager::ToggleFullscreen)
83f0e0bc Poul Sander 2026-05-30 17:59 835
        && m_stack->currentWidget() == m_imageView)
61136b77 Poul Sander 2026-02-20 22:45 836
    {
ccb726b8 Poul Sander 2026-02-23 22:43 837
        setFullScreenMode(!isFullScreen());
61136b77 Poul Sander 2026-02-20 22:45 838
        event->accept();
61136b77 Poul Sander 2026-02-20 22:45 839
        return;
61136b77 Poul Sander 2026-02-20 22:45 840
    }
61136b77 Poul Sander 2026-02-20 22:45 841
83f0e0bc Poul Sander 2026-05-30 17:59 842
    if (sc.matches(event, ShortcutManager::ActivateEntry)
83f0e0bc Poul Sander 2026-05-30 17:59 843
        && m_stack->currentWidget() == m_browserPage)
61136b77 Poul Sander 2026-02-20 22:45 844
    {
61136b77 Poul Sander 2026-02-20 22:45 845
        QModelIndex current = m_listView->currentIndex();
61136b77 Poul Sander 2026-02-20 22:45 846
        if (current.isValid())
61136b77 Poul Sander 2026-02-20 22:45 847
        {
09130ab9 Poul Sander 2026-02-22 22:19 848
            if (m_imageModel->isFolder(current)) {
80a9926d Poul Sander 2026-05-19 20:28 849
                QByteArray path = m_imageModel->filePath(current);
665ac308 Poul Sander 2026-02-23 19:34 850
                navigateToFolder(path);
09130ab9 Poul Sander 2026-02-22 22:19 851
            } else {
09130ab9 Poul Sander 2026-02-22 22:19 852
                enterSingleImageMode(current);
09130ab9 Poul Sander 2026-02-22 22:19 853
            }
61136b77 Poul Sander 2026-02-20 22:45 854
            event->accept();
61136b77 Poul Sander 2026-02-20 22:45 855
            return;
61136b77 Poul Sander 2026-02-20 22:45 856
        }
61136b77 Poul Sander 2026-02-20 22:45 857
    }
61136b77 Poul Sander 2026-02-20 22:45 858
bf525669 Poul Sander 2026-05-30 23:18 859
    if (sc.matches(event, ShortcutManager::PrevFolder)
bf525669 Poul Sander 2026-05-30 23:18 860
        && m_stack->currentWidget() == m_browserPage)
bf525669 Poul Sander 2026-05-30 23:18 861
    {
bf525669 Poul Sander 2026-05-30 23:18 862
        navigateToPrevFolder();
bf525669 Poul Sander 2026-05-30 23:18 863
        event->accept();
bf525669 Poul Sander 2026-05-30 23:18 864
        return;
bf525669 Poul Sander 2026-05-30 23:18 865
    }
bf525669 Poul Sander 2026-05-30 23:18 866
bf525669 Poul Sander 2026-05-30 23:18 867
    if (sc.matches(event, ShortcutManager::NextFolder)
bf525669 Poul Sander 2026-05-30 23:18 868
        && m_stack->currentWidget() == m_browserPage)
bf525669 Poul Sander 2026-05-30 23:18 869
    {
bf525669 Poul Sander 2026-05-30 23:18 870
        navigateToNextFolder();
bf525669 Poul Sander 2026-05-30 23:18 871
        event->accept();
bf525669 Poul Sander 2026-05-30 23:18 872
        return;
bf525669 Poul Sander 2026-05-30 23:18 873
    }
bf525669 Poul Sander 2026-05-30 23:18 874
a686d839 Poul Sander 2026-06-06 22:34 875
    if (sc.matches(event, ShortcutManager::MoveToTrash)
a686d839 Poul Sander 2026-06-06 22:34 876
        && m_stack->currentWidget() == m_browserPage)
a686d839 Poul Sander 2026-06-06 22:34 877
    {
a686d839 Poul Sander 2026-06-06 22:34 878
        moveCurrentImageToTrash();
a686d839 Poul Sander 2026-06-06 22:34 879
        event->accept();
a686d839 Poul Sander 2026-06-06 22:34 880
        return;
a686d839 Poul Sander 2026-06-06 22:34 881
    }
a686d839 Poul Sander 2026-06-06 22:34 882
61136b77 Poul Sander 2026-02-20 22:45 883
    QMainWindow::keyPressEvent(event);
61136b77 Poul Sander 2026-02-20 22:45 884
}
c9e5684a Poul Sander 2026-02-23 19:18 885
c9e5684a Poul Sander 2026-02-23 19:18 886
void MainWindow::closeEvent(QCloseEvent *event)
c9e5684a Poul Sander 2026-02-23 19:18 887
{
c9e5684a Poul Sander 2026-02-23 19:18 888
    if (m_stack->currentWidget() == m_imageView)
c9e5684a Poul Sander 2026-02-23 19:18 889
    {
c9e5684a Poul Sander 2026-02-23 19:18 890
        leaveSingleImageMode();
c9e5684a Poul Sander 2026-02-23 19:18 891
        event->ignore();
c9e5684a Poul Sander 2026-02-23 19:18 892
        return;
c9e5684a Poul Sander 2026-02-23 19:18 893
    }
c9e5684a Poul Sander 2026-02-23 19:18 894
f61bf886 Poul Sander 2026-02-23 19:27 895
    savePreferences();
c9e5684a Poul Sander 2026-02-23 19:18 896
    QMainWindow::closeEvent(event);
c9e5684a Poul Sander 2026-02-23 19:18 897
}
f61bf886 Poul Sander 2026-02-23 19:27 898
4ae57f60 Poul Sander 2026-02-25 21:35 899
void MainWindow::applyDockLocking(bool lock)
4ae57f60 Poul Sander 2026-02-25 21:35 900
{
4ae57f60 Poul Sander 2026-02-25 21:35 901
    const QDockWidget::DockWidgetFeatures movableFeatures =
4ae57f60 Poul Sander 2026-02-25 21:35 902
        QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable;
4ae57f60 Poul Sander 2026-02-25 21:35 903
    const QDockWidget::DockWidgetFeatures baseFeatures =
4ae57f60 Poul Sander 2026-02-25 21:35 904
        QDockWidget::DockWidgetClosable;
4ae57f60 Poul Sander 2026-02-25 21:35 905
4ae57f60 Poul Sander 2026-02-25 21:35 906
    QDockWidget::DockWidgetFeatures features = lock ? baseFeatures
4ae57f60 Poul Sander 2026-02-25 21:35 907
                                                    : (baseFeatures | movableFeatures);
3bee3e3e Poul Sander 2026-03-05 17:07 908
    m_rootDock->setFeatures(features);
4ae57f60 Poul Sander 2026-02-25 21:35 909
    m_folderDock->setFeatures(features);
4ae57f60 Poul Sander 2026-02-25 21:35 910
    m_previewDock->setFeatures(features);
53fdab93 Poul Sander 2026-05-18 18:45 911
    m_exifDock->setFeatures(features);
4ae57f60 Poul Sander 2026-02-25 21:35 912
}
4ae57f60 Poul Sander 2026-02-25 21:35 913
f61bf886 Poul Sander 2026-02-23 19:27 914
void MainWindow::loadPreferences()
f61bf886 Poul Sander 2026-02-23 19:27 915
{
f61bf886 Poul Sander 2026-02-23 19:27 916
    QSettings settings("SagoImageBrowser", "SagoImageBrowser");
f61bf886 Poul Sander 2026-02-23 19:27 917
    m_backgroundColorPreference = settings.value("backgroundColor", "black").toString();
f61bf886 Poul Sander 2026-02-23 19:27 918
    m_imageView->setBackgroundColor(m_backgroundColorPreference);
4ae57f60 Poul Sander 2026-02-25 21:35 919
    m_lockDockingPreference = settings.value("lockDocking", false).toBool();
4ae57f60 Poul Sander 2026-02-25 21:35 920
    applyDockLocking(m_lockDockingPreference);
459bad0f Poul Sander 2026-05-17 14:11 921
    m_folderCacheSizePreference = settings.value("folderCacheSize", 4).toInt();
459bad0f Poul Sander 2026-05-17 14:11 922
    m_imageModel->setCacheMaxSize(m_folderCacheSizePreference);
ac476ca8 Poul Sander 2026-05-18 16:47 923
    m_thumbnailCacheSizePreference = settings.value("thumbnailCacheSize", "normal").toString();
ac476ca8 Poul Sander 2026-05-18 16:47 924
    m_imageModel->setThumbnailSize(thumbnailSizeFromString(m_thumbnailCacheSizePreference));
83f0e0bc Poul Sander 2026-05-30 17:59 925
    ShortcutManager::instance().load(settings);
cbc7d22b Poul Sander 2026-02-24 19:23 926
    if (settings.contains("windowGeometry"))
cbc7d22b Poul Sander 2026-02-24 19:23 927
        restoreGeometry(settings.value("windowGeometry").toByteArray());
cbc7d22b Poul Sander 2026-02-24 19:23 928
    if (settings.contains("windowState"))
cbc7d22b Poul Sander 2026-02-24 19:23 929
        restoreState(settings.value("windowState").toByteArray());
f61bf886 Poul Sander 2026-02-23 19:27 930
}
f61bf886 Poul Sander 2026-02-23 19:27 931
f61bf886 Poul Sander 2026-02-23 19:27 932
void MainWindow::savePreferences()
f61bf886 Poul Sander 2026-02-23 19:27 933
{
f61bf886 Poul Sander 2026-02-23 19:27 934
    QSettings settings("SagoImageBrowser", "SagoImageBrowser");
f61bf886 Poul Sander 2026-02-23 19:27 935
    settings.setValue("backgroundColor", m_backgroundColorPreference);
4ae57f60 Poul Sander 2026-02-25 21:35 936
    settings.setValue("lockDocking", m_lockDockingPreference);
459bad0f Poul Sander 2026-05-17 14:11 937
    settings.setValue("folderCacheSize", m_folderCacheSizePreference);
ac476ca8 Poul Sander 2026-05-18 16:47 938
    settings.setValue("thumbnailCacheSize", m_thumbnailCacheSizePreference);
83f0e0bc Poul Sander 2026-05-30 17:59 939
    ShortcutManager::instance().save(settings);
cbc7d22b Poul Sander 2026-02-24 19:23 940
    settings.setValue("windowGeometry", saveGeometry());
cbc7d22b Poul Sander 2026-02-24 19:23 941
    settings.setValue("windowState", saveState());
f61bf886 Poul Sander 2026-02-23 19:27 942
}
f61bf886 Poul Sander 2026-02-23 19:27 943
f61bf886 Poul Sander 2026-02-23 19:27 944
void MainWindow::onPreferencesTriggered()
f61bf886 Poul Sander 2026-02-23 19:27 945
{
f61bf886 Poul Sander 2026-02-23 19:27 946
    PreferencesDialog dialog(this);
f61bf886 Poul Sander 2026-02-23 19:27 947
    dialog.setBackgroundColor(m_backgroundColorPreference);
4ae57f60 Poul Sander 2026-02-25 21:35 948
    dialog.setLockDocking(m_lockDockingPreference);
459bad0f Poul Sander 2026-05-17 14:11 949
    dialog.setCacheSize(m_folderCacheSizePreference);
ac476ca8 Poul Sander 2026-05-18 16:47 950
    dialog.setThumbnailCacheSize(m_thumbnailCacheSizePreference);
f61bf886 Poul Sander 2026-02-23 19:27 951
83f0e0bc Poul Sander 2026-05-30 17:59 952
    // Populate shortcuts tab from current bindings
83f0e0bc Poul Sander 2026-05-30 17:59 953
    {
83f0e0bc Poul Sander 2026-05-30 17:59 954
        QMap<int, QKeySequence> scMap;
83f0e0bc Poul Sander 2026-05-30 17:59 955
        for (int i = 0; i < ShortcutManager::ActionCount; ++i)
83f0e0bc Poul Sander 2026-05-30 17:59 956
            scMap[i] = ShortcutManager::instance().shortcut(
83f0e0bc Poul Sander 2026-05-30 17:59 957
                static_cast<ShortcutManager::Action>(i));
83f0e0bc Poul Sander 2026-05-30 17:59 958
        dialog.setShortcuts(scMap);
83f0e0bc Poul Sander 2026-05-30 17:59 959
    }
83f0e0bc Poul Sander 2026-05-30 17:59 960
7370f1c0 Poul Sander 2026-03-01 15:15 961
    bool shouldResetLayout = false;
7370f1c0 Poul Sander 2026-03-01 15:15 962
    connect(&dialog, &PreferencesDialog::resetLayoutRequested, this, [&shouldResetLayout]() {
7370f1c0 Poul Sander 2026-03-01 15:15 963
        shouldResetLayout = true;
7370f1c0 Poul Sander 2026-03-01 15:15 964
    });
7370f1c0 Poul Sander 2026-03-01 15:15 965
f61bf886 Poul Sander 2026-02-23 19:27 966
    if (dialog.exec() == QDialog::Accepted)
f61bf886 Poul Sander 2026-02-23 19:27 967
    {
f61bf886 Poul Sander 2026-02-23 19:27 968
        m_backgroundColorPreference = dialog.getBackgroundColor();
f61bf886 Poul Sander 2026-02-23 19:27 969
        m_imageView->setBackgroundColor(m_backgroundColorPreference);
4ae57f60 Poul Sander 2026-02-25 21:35 970
        m_lockDockingPreference = dialog.getLockDocking();
4ae57f60 Poul Sander 2026-02-25 21:35 971
        applyDockLocking(m_lockDockingPreference);
459bad0f Poul Sander 2026-05-17 14:11 972
        m_folderCacheSizePreference = dialog.getCacheSize();
459bad0f Poul Sander 2026-05-17 14:11 973
        m_imageModel->setCacheMaxSize(m_folderCacheSizePreference);
ac476ca8 Poul Sander 2026-05-18 16:47 974
        QString newThumbSize = dialog.getThumbnailCacheSize();
ac476ca8 Poul Sander 2026-05-18 16:47 975
        if (newThumbSize != m_thumbnailCacheSizePreference)
ac476ca8 Poul Sander 2026-05-18 16:47 976
        {
ac476ca8 Poul Sander 2026-05-18 16:47 977
            m_thumbnailCacheSizePreference = newThumbSize;
ac476ca8 Poul Sander 2026-05-18 16:47 978
            m_imageModel->setThumbnailSize(thumbnailSizeFromString(m_thumbnailCacheSizePreference));
ac476ca8 Poul Sander 2026-05-18 16:47 979
            loadVisibleThumbnails();
ac476ca8 Poul Sander 2026-05-18 16:47 980
        }
83f0e0bc Poul Sander 2026-05-30 17:59 981
83f0e0bc Poul Sander 2026-05-30 17:59 982
        // Apply shortcut changes
83f0e0bc Poul Sander 2026-05-30 17:59 983
        QMap<int, QKeySequence> scMap = dialog.getShortcuts();
83f0e0bc Poul Sander 2026-05-30 17:59 984
        for (auto it = scMap.cbegin(); it != scMap.cend(); ++it)
83f0e0bc Poul Sander 2026-05-30 17:59 985
            ShortcutManager::instance().setShortcut(
83f0e0bc Poul Sander 2026-05-30 17:59 986
                static_cast<ShortcutManager::Action>(it.key()), it.value());
83f0e0bc Poul Sander 2026-05-30 17:59 987
f61bf886 Poul Sander 2026-02-23 19:27 988
        savePreferences();
f61bf886 Poul Sander 2026-02-23 19:27 989
    }
7370f1c0 Poul Sander 2026-03-01 15:15 990
    
7370f1c0 Poul Sander 2026-03-01 15:15 991
    // Apply layout reset immediately after dialog closes if requested
7370f1c0 Poul Sander 2026-03-01 15:15 992
    if (shouldResetLayout) {
7370f1c0 Poul Sander 2026-03-01 15:15 993
        resetLayoutToDefault();
7370f1c0 Poul Sander 2026-03-01 15:15 994
    }
7370f1c0 Poul Sander 2026-03-01 15:15 995
}
7370f1c0 Poul Sander 2026-03-01 15:15 996
7370f1c0 Poul Sander 2026-03-01 15:15 997
void MainWindow::resetLayoutToDefault()
7370f1c0 Poul Sander 2026-03-01 15:15 998
{
7370f1c0 Poul Sander 2026-03-01 15:15 999
    QSettings settings("SagoImageBrowser", "SagoImageBrowser");
7370f1c0 Poul Sander 2026-03-01 15:15 1000
    settings.remove("windowGeometry");
7370f1c0 Poul Sander 2026-03-01 15:15 1001
    settings.remove("windowState");
7370f1c0 Poul Sander 2026-03-01 15:15 1002
    
7370f1c0 Poul Sander 2026-03-01 15:15 1003
    // Hide and detach docks completely
3bee3e3e Poul Sander 2026-03-05 17:07 1004
    m_rootDock->hide();
7370f1c0 Poul Sander 2026-03-01 15:15 1005
    m_folderDock->hide();
7370f1c0 Poul Sander 2026-03-01 15:15 1006
    m_previewDock->hide();
7370f1c0 Poul Sander 2026-03-01 15:15 1007
    
3bee3e3e Poul Sander 2026-03-05 17:07 1008
    m_rootDock->setFloating(false);
7370f1c0 Poul Sander 2026-03-01 15:15 1009
    m_folderDock->setFloating(false);
7370f1c0 Poul Sander 2026-03-01 15:15 1010
    m_previewDock->setFloating(false);
7370f1c0 Poul Sander 2026-03-01 15:15 1011
    
3bee3e3e Poul Sander 2026-03-05 17:07 1012
    removeDockWidget(m_rootDock);
7370f1c0 Poul Sander 2026-03-01 15:15 1013
    removeDockWidget(m_folderDock);
7370f1c0 Poul Sander 2026-03-01 15:15 1014
    removeDockWidget(m_previewDock);
7370f1c0 Poul Sander 2026-03-01 15:15 1015
    
7370f1c0 Poul Sander 2026-03-01 15:15 1016
    // Re-add in default positions
3bee3e3e Poul Sander 2026-03-05 17:07 1017
    addDockWidget(Qt::LeftDockWidgetArea, m_rootDock);
7370f1c0 Poul Sander 2026-03-01 15:15 1018
    addDockWidget(Qt::LeftDockWidgetArea, m_folderDock);
7370f1c0 Poul Sander 2026-03-01 15:15 1019
    addDockWidget(Qt::RightDockWidgetArea, m_previewDock);
3bee3e3e Poul Sander 2026-03-05 17:07 1020
    splitDockWidget(m_rootDock, m_folderDock, Qt::Vertical);
7370f1c0 Poul Sander 2026-03-01 15:15 1021
    
3bee3e3e Poul Sander 2026-03-05 17:07 1022
    // Show all docks
3bee3e3e Poul Sander 2026-03-05 17:07 1023
    m_rootDock->show();
7370f1c0 Poul Sander 2026-03-01 15:15 1024
    m_folderDock->show();
7370f1c0 Poul Sander 2026-03-01 15:15 1025
    m_previewDock->show();
f61bf886 Poul Sander 2026-02-23 19:27 1026
}
1970-01-01 00:00 1027