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"
ac476ca8 Poul Sander 2026-05-18 16:47 5
#include "thumbnailcache.h"
53fdab93 Poul Sander 2026-05-18 18:45 6
#include "exifreader.h"
40efdfde Poul Sander 2026-05-19 21:24 7
#include "fsdirmodel.h"
fd7f51cd Poul Sander 2026-05-22 15:30 8
#include "pathcompletermodel.h"
1dcea855 Poul Sander 2026-05-26 21:48 9
#include "thumbnaildelegate.h"
032aa1e1 Poul Sander 2026-02-19 18:55 10
80a9926d Poul Sander 2026-05-19 20:28 11
#include <QFile>
40efdfde Poul Sander 2026-05-19 21:24 12
#include <QTreeView>
032aa1e1 Poul Sander 2026-02-19 18:55 13
#include <QTreeView>
032aa1e1 Poul Sander 2026-02-19 18:55 14
#include <QListView>
032aa1e1 Poul Sander 2026-02-19 18:55 15
#include <QLabel>
61136b77 Poul Sander 2026-02-20 22:45 16
#include <QStackedWidget>
032aa1e1 Poul Sander 2026-02-19 18:55 17
#include <QDir>
3bee3e3e Poul Sander 2026-03-05 17:07 18
#include <QListWidget>
fc5f2e27 Poul Sander 2026-02-22 22:06 19
#include <QStandardPaths>
032aa1e1 Poul Sander 2026-02-19 18:55 20
#include <QImageReader>
032aa1e1 Poul Sander 2026-02-19 18:55 21
#include <QHeaderView>
6fcd6c90 Poul Sander 2026-02-19 21:12 22
#include <QScrollBar>
6fcd6c90 Poul Sander 2026-02-19 21:12 23
#include <QTimer>
6fcd6c90 Poul Sander 2026-02-19 21:12 24
#include <QEvent>
61136b77 Poul Sander 2026-02-20 22:45 25
#include <QKeyEvent>
c9e5684a Poul Sander 2026-02-23 19:18 26
#include <QCloseEvent>
cfe4a67b Poul Sander 2026-02-19 23:06 27
#include <QItemSelectionModel>
cbc7d22b Poul Sander 2026-02-24 19:23 28
#include <QDockWidget>
f61bf886 Poul Sander 2026-02-23 19:27 29
#include <QMenuBar>
f61bf886 Poul Sander 2026-02-23 19:27 30
#include <QMenu>
f61bf886 Poul Sander 2026-02-23 19:27 31
#include <QAction>
f61bf886 Poul Sander 2026-02-23 19:27 32
#include <QSettings>
53fdab93 Poul Sander 2026-05-18 18:45 33
#include <QTableWidget>
beb4a823 Poul Sander 2026-05-19 21:53 34
#include <QDialog>
beb4a823 Poul Sander 2026-05-19 21:53 35
#include <QVBoxLayout>
fd7f51cd Poul Sander 2026-05-22 15:30 36
#include <QHBoxLayout>
beb4a823 Poul Sander 2026-05-19 21:53 37
#include <QDialogButtonBox>
beb4a823 Poul Sander 2026-05-19 21:53 38
#include <QPlainTextEdit>
835a6bea Poul Sander 2026-05-19 22:03 39
#include <QMessageBox>
fd7f51cd Poul Sander 2026-05-22 15:30 40
#include <QLineEdit>
fd7f51cd Poul Sander 2026-05-22 15:30 41
#include <QPushButton>
fd7f51cd Poul Sander 2026-05-22 15:30 42
#include <QCompleter>
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>
80a9926d Poul Sander 2026-05-19 20:28 46
#include <sys/stat.h>
80a9926d Poul Sander 2026-05-19 20:28 47
#include <unistd.h>
80a9926d Poul Sander 2026-05-19 20:28 48
ac476ca8 Poul Sander 2026-05-18 16:47 49
namespace
ac476ca8 Poul Sander 2026-05-18 16:47 50
{
ac476ca8 Poul Sander 2026-05-18 16:47 51
ThumbnailCache::Size thumbnailSizeFromString(const QString &s)
ac476ca8 Poul Sander 2026-05-18 16:47 52
{
ac476ca8 Poul Sander 2026-05-18 16:47 53
    return (s == "large") ? ThumbnailCache::Size::Large
ac476ca8 Poul Sander 2026-05-18 16:47 54
                          : ThumbnailCache::Size::Normal;
ac476ca8 Poul Sander 2026-05-18 16:47 55
}
ac476ca8 Poul Sander 2026-05-18 16:47 56
} // namespace
ac476ca8 Poul Sander 2026-05-18 16:47 57
032aa1e1 Poul Sander 2026-02-19 18:55 58
MainWindow::MainWindow(QWidget *parent)
032aa1e1 Poul Sander 2026-02-19 18:55 59
    : QMainWindow(parent)
032aa1e1 Poul Sander 2026-02-19 18:55 60
{
032aa1e1 Poul Sander 2026-02-19 18:55 61
    setupUi();
f61bf886 Poul Sander 2026-02-23 19:27 62
    setupMenuBar();
032aa1e1 Poul Sander 2026-02-19 18:55 63
    setupConnections();
f61bf886 Poul Sander 2026-02-23 19:27 64
    loadPreferences();
032aa1e1 Poul Sander 2026-02-19 18:55 65
}
032aa1e1 Poul Sander 2026-02-19 18:55 66
032aa1e1 Poul Sander 2026-02-19 18:55 67
void MainWindow::setupUi()
032aa1e1 Poul Sander 2026-02-19 18:55 68
{
40efdfde Poul Sander 2026-05-19 21:24 69
    // Folder model (left pane) — backed by std::filesystem so directories with
40efdfde Poul Sander 2026-05-19 21:24 70
    // non-UTF-8 filenames are listed correctly.
40efdfde Poul Sander 2026-05-19 21:24 71
    m_dirModel = new FsDirModel(this);
032aa1e1 Poul Sander 2026-02-19 18:55 72
    m_dirModel->setRootPath(QDir::homePath());
032aa1e1 Poul Sander 2026-02-19 18:55 73
3bee3e3e Poul Sander 2026-03-05 17:07 74
    // Root chooser list
3bee3e3e Poul Sander 2026-03-05 17:07 75
    m_rootList = new QListWidget;
3bee3e3e Poul Sander 2026-03-05 17:07 76
    m_rootList->setSelectionMode(QAbstractItemView::SingleSelection);
3bee3e3e Poul Sander 2026-03-05 17:07 77
3bee3e3e Poul Sander 2026-03-05 17:07 78
    auto addRootItem = [this](const QString &label, const QString &path) {
3bee3e3e Poul Sander 2026-03-05 17:07 79
        QListWidgetItem *item = new QListWidgetItem(label, m_rootList);
3bee3e3e Poul Sander 2026-03-05 17:07 80
        item->setData(Qt::UserRole, path);
3bee3e3e Poul Sander 2026-03-05 17:07 81
    };
3bee3e3e Poul Sander 2026-03-05 17:07 82
    addRootItem("Home", QDir::homePath());
3bee3e3e Poul Sander 2026-03-05 17:07 83
    addRootItem("Pictures", QStandardPaths::writableLocation(QStandardPaths::PicturesLocation));
3bee3e3e Poul Sander 2026-03-05 17:07 84
    addRootItem("File System", QDir::rootPath());
3bee3e3e Poul Sander 2026-03-05 17:07 85
    m_rootList->setCurrentRow(0);
fc5f2e27 Poul Sander 2026-02-22 22:06 86
032aa1e1 Poul Sander 2026-02-19 18:55 87
    m_treeView = new QTreeView;
032aa1e1 Poul Sander 2026-02-19 18:55 88
    m_treeView->setModel(m_dirModel);
40efdfde Poul Sander 2026-05-19 21:24 89
    m_treeView->setRootIndex(m_dirModel->rootIndex());
032aa1e1 Poul Sander 2026-02-19 18:55 90
    m_treeView->header()->hide();
032aa1e1 Poul Sander 2026-02-19 18:55 91
    m_treeView->setColumnHidden(1, true);
032aa1e1 Poul Sander 2026-02-19 18:55 92
    m_treeView->setColumnHidden(2, true);
032aa1e1 Poul Sander 2026-02-19 18:55 93
    m_treeView->setColumnHidden(3, true);
032aa1e1 Poul Sander 2026-02-19 18:55 94
032aa1e1 Poul Sander 2026-02-19 18:55 95
    // Thumbnail model (center pane)
032aa1e1 Poul Sander 2026-02-19 18:55 96
    m_imageModel = new ImageModel(this);
032aa1e1 Poul Sander 2026-02-19 18:55 97
032aa1e1 Poul Sander 2026-02-19 18:55 98
    m_listView = new QListView;
032aa1e1 Poul Sander 2026-02-19 18:55 99
    m_listView->setModel(m_imageModel);
032aa1e1 Poul Sander 2026-02-19 18:55 100
    m_listView->setViewMode(QListView::IconMode);
032aa1e1 Poul Sander 2026-02-19 18:55 101
    m_listView->setIconSize(QSize(128, 128));
b614a581 Poul Sander 2026-02-19 21:18 102
    m_listView->setGridSize(QSize(148, 168));
032aa1e1 Poul Sander 2026-02-19 18:55 103
    m_listView->setResizeMode(QListView::Adjust);
032aa1e1 Poul Sander 2026-02-19 18:55 104
    m_listView->setSelectionMode(QAbstractItemView::SingleSelection);
b614a581 Poul Sander 2026-02-19 21:18 105
    m_listView->setSpacing(4);
b614a581 Poul Sander 2026-02-19 21:18 106
    m_listView->setWordWrap(true);
b614a581 Poul Sander 2026-02-19 21:18 107
    m_listView->setTextElideMode(Qt::ElideRight);
1dcea855 Poul Sander 2026-05-26 21:48 108
    m_listView->setItemDelegate(new ThumbnailDelegate(this));
032aa1e1 Poul Sander 2026-02-19 18:55 109
032aa1e1 Poul Sander 2026-02-19 18:55 110
    // Preview pane (right)
032aa1e1 Poul Sander 2026-02-19 18:55 111
    m_previewLabel = new QLabel;
032aa1e1 Poul Sander 2026-02-19 18:55 112
    m_previewLabel->setAlignment(Qt::AlignCenter);
cbc7d22b Poul Sander 2026-02-24 19:23 113
    m_previewLabel->setMinimumWidth(200);
61136b77 Poul Sander 2026-02-20 22:45 114
61136b77 Poul Sander 2026-02-20 22:45 115
    // Single image view
61136b77 Poul Sander 2026-02-20 22:45 116
    m_imageView = new ImageViewWidget;
61136b77 Poul Sander 2026-02-20 22:45 117
fd7f51cd Poul Sander 2026-05-22 15:30 118
    // Path bar: editable absolute path with ajax-style folder completion and a Go button
fd7f51cd Poul Sander 2026-05-22 15:30 119
    m_pathEdit = new QLineEdit;
fd7f51cd Poul Sander 2026-05-22 15:30 120
    m_goButton = new QPushButton(tr("Go"));
fd7f51cd Poul Sander 2026-05-22 15:30 121
    m_pathCompleterModel = new PathCompleterModel(this);
fd7f51cd Poul Sander 2026-05-22 15:30 122
    m_pathCompleter = new QCompleter(m_pathCompleterModel, this);
fd7f51cd Poul Sander 2026-05-22 15:30 123
    m_pathCompleter->setCaseSensitivity(Qt::CaseSensitive);
fd7f51cd Poul Sander 2026-05-22 15:30 124
    m_pathCompleter->setCompletionMode(QCompleter::PopupCompletion);
fd7f51cd Poul Sander 2026-05-22 15:30 125
    m_pathCompleter->setFilterMode(Qt::MatchStartsWith);
fd7f51cd Poul Sander 2026-05-22 15:30 126
    m_pathEdit->setCompleter(m_pathCompleter);
fd7f51cd Poul Sander 2026-05-22 15:30 127
fd7f51cd Poul Sander 2026-05-22 15:30 128
    auto *pathRow = new QHBoxLayout;
fd7f51cd Poul Sander 2026-05-22 15:30 129
    pathRow->setContentsMargins(4, 4, 4, 0);
fd7f51cd Poul Sander 2026-05-22 15:30 130
    pathRow->addWidget(m_pathEdit, 1);
fd7f51cd Poul Sander 2026-05-22 15:30 131
    pathRow->addWidget(m_goButton);
fd7f51cd Poul Sander 2026-05-22 15:30 132
fd7f51cd Poul Sander 2026-05-22 15:30 133
    auto *browserPage = new QWidget;
fd7f51cd Poul Sander 2026-05-22 15:30 134
    auto *vbox = new QVBoxLayout(browserPage);
fd7f51cd Poul Sander 2026-05-22 15:30 135
    vbox->setContentsMargins(0, 0, 0, 0);
fd7f51cd Poul Sander 2026-05-22 15:30 136
    vbox->setSpacing(0);
fd7f51cd Poul Sander 2026-05-22 15:30 137
    vbox->addLayout(pathRow);
fd7f51cd Poul Sander 2026-05-22 15:30 138
    vbox->addWidget(m_listView, 1);
fd7f51cd Poul Sander 2026-05-22 15:30 139
61136b77 Poul Sander 2026-02-20 22:45 140
    // Stack to switch between browser and single image
61136b77 Poul Sander 2026-02-20 22:45 141
    m_stack = new QStackedWidget;
fd7f51cd Poul Sander 2026-05-22 15:30 142
    m_stack->addWidget(browserPage);
61136b77 Poul Sander 2026-02-20 22:45 143
    m_stack->addWidget(m_imageView);
fd7f51cd Poul Sander 2026-05-22 15:30 144
    m_browserPage = browserPage;
61136b77 Poul Sander 2026-02-20 22:45 145
61136b77 Poul Sander 2026-02-20 22:45 146
    setCentralWidget(m_stack);
cbc7d22b Poul Sander 2026-02-24 19:23 147
fd7f51cd Poul Sander 2026-05-22 15:30 148
    updatePathField(QFile::encodeName(QDir::homePath()));
fd7f51cd Poul Sander 2026-05-22 15:30 149
cbc7d22b Poul Sander 2026-02-24 19:23 150
    // Dock widgets — can be dragged, floated and stacked by the user
3bee3e3e Poul Sander 2026-03-05 17:07 151
    m_rootDock = new QDockWidget(tr("Roots"), this);
3bee3e3e Poul Sander 2026-03-05 17:07 152
    m_rootDock->setObjectName("RootDock");
3bee3e3e Poul Sander 2026-03-05 17:07 153
    m_rootDock->setWidget(m_rootList);
3bee3e3e Poul Sander 2026-03-05 17:07 154
    addDockWidget(Qt::LeftDockWidgetArea, m_rootDock);
3bee3e3e Poul Sander 2026-03-05 17:07 155
cbc7d22b Poul Sander 2026-02-24 19:23 156
    m_folderDock = new QDockWidget(tr("Folders"), this);
cbc7d22b Poul Sander 2026-02-24 19:23 157
    m_folderDock->setObjectName("FolderDock");
3bee3e3e Poul Sander 2026-03-05 17:07 158
    m_folderDock->setWidget(m_treeView);
cbc7d22b Poul Sander 2026-02-24 19:23 159
    addDockWidget(Qt::LeftDockWidgetArea, m_folderDock);
3bee3e3e Poul Sander 2026-03-05 17:07 160
    splitDockWidget(m_rootDock, m_folderDock, Qt::Vertical);
cbc7d22b Poul Sander 2026-02-24 19:23 161
cbc7d22b Poul Sander 2026-02-24 19:23 162
    m_previewDock = new QDockWidget(tr("Preview"), this);
cbc7d22b Poul Sander 2026-02-24 19:23 163
    m_previewDock->setObjectName("PreviewDock");
cbc7d22b Poul Sander 2026-02-24 19:23 164
    m_previewDock->setWidget(m_previewLabel);
cbc7d22b Poul Sander 2026-02-24 19:23 165
    addDockWidget(Qt::RightDockWidgetArea, m_previewDock);
53fdab93 Poul Sander 2026-05-18 18:45 166
53fdab93 Poul Sander 2026-05-18 18:45 167
    // EXIF info dock — placed below the preview dock by default
53fdab93 Poul Sander 2026-05-18 18:45 168
    m_exifTable = new QTableWidget(0, 2);
53fdab93 Poul Sander 2026-05-18 18:45 169
    m_exifTable->setHorizontalHeaderLabels({tr("Field"), tr("Value")});
53fdab93 Poul Sander 2026-05-18 18:45 170
    m_exifTable->horizontalHeader()->setStretchLastSection(true);
53fdab93 Poul Sander 2026-05-18 18:45 171
    m_exifTable->verticalHeader()->hide();
53fdab93 Poul Sander 2026-05-18 18:45 172
    m_exifTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
53fdab93 Poul Sander 2026-05-18 18:45 173
    m_exifTable->setSelectionMode(QAbstractItemView::NoSelection);
53fdab93 Poul Sander 2026-05-18 18:45 174
    m_exifTable->setShowGrid(false);
53fdab93 Poul Sander 2026-05-18 18:45 175
    m_exifTable->setAlternatingRowColors(true);
53fdab93 Poul Sander 2026-05-18 18:45 176
    m_exifTable->setMinimumWidth(200);
53fdab93 Poul Sander 2026-05-18 18:45 177
53fdab93 Poul Sander 2026-05-18 18:45 178
    m_exifDock = new QDockWidget(tr("EXIF Info"), this);
53fdab93 Poul Sander 2026-05-18 18:45 179
    m_exifDock->setObjectName("ExifDock");
53fdab93 Poul Sander 2026-05-18 18:45 180
    m_exifDock->setWidget(m_exifTable);
53fdab93 Poul Sander 2026-05-18 18:45 181
    addDockWidget(Qt::RightDockWidgetArea, m_exifDock);
53fdab93 Poul Sander 2026-05-18 18:45 182
    splitDockWidget(m_previewDock, m_exifDock, Qt::Vertical);
032aa1e1 Poul Sander 2026-02-19 18:55 183
}
032aa1e1 Poul Sander 2026-02-19 18:55 184
f61bf886 Poul Sander 2026-02-23 19:27 185
void MainWindow::setupMenuBar()
f61bf886 Poul Sander 2026-02-23 19:27 186
{
ccb726b8 Poul Sander 2026-02-23 22:43 187
    m_menuBar = menuBar();
0df01dc1 Poul Sander 2026-02-24 18:51 188
    m_menuBar->setStyleSheet(
0df01dc1 Poul Sander 2026-02-24 18:51 189
        "QMenuBar { padding: 1px 2px; }"
0df01dc1 Poul Sander 2026-02-24 18:51 190
        "QMenuBar::item { padding: 2px 6px; }"
0df01dc1 Poul Sander 2026-02-24 18:51 191
    );
cbc7d22b Poul Sander 2026-02-24 19:23 192
    QMenu *viewMenu = m_menuBar->addMenu("&View");
3bee3e3e Poul Sander 2026-03-05 17:07 193
    viewMenu->addAction(m_rootDock->toggleViewAction());
cbc7d22b Poul Sander 2026-02-24 19:23 194
    viewMenu->addAction(m_folderDock->toggleViewAction());
cbc7d22b Poul Sander 2026-02-24 19:23 195
    viewMenu->addAction(m_previewDock->toggleViewAction());
53fdab93 Poul Sander 2026-05-18 18:45 196
    viewMenu->addAction(m_exifDock->toggleViewAction());
cbc7d22b Poul Sander 2026-02-24 19:23 197
ccb726b8 Poul Sander 2026-02-23 22:43 198
    QMenu *editMenu = m_menuBar->addMenu("&Edit");
f61bf886 Poul Sander 2026-02-23 19:27 199
    QAction *preferencesAction = editMenu->addAction("&Preferences");
f61bf886 Poul Sander 2026-02-23 19:27 200
    connect(preferencesAction, &QAction::triggered, this, &MainWindow::onPreferencesTriggered);
f61bf886 Poul Sander 2026-02-23 19:27 201
}
f61bf886 Poul Sander 2026-02-23 19:27 202
665ac308 Poul Sander 2026-02-23 19:34 203
void MainWindow::navigateToFolder(const QString &path)
665ac308 Poul Sander 2026-02-23 19:34 204
{
80a9926d Poul Sander 2026-05-19 20:28 205
    navigateToFolder(QFile::encodeName(path));
80a9926d Poul Sander 2026-05-19 20:28 206
}
80a9926d Poul Sander 2026-05-19 20:28 207
80a9926d Poul Sander 2026-05-19 20:28 208
void MainWindow::navigateToFolder(const QByteArray &path)
80a9926d Poul Sander 2026-05-19 20:28 209
{
665ac308 Poul Sander 2026-02-23 19:34 210
    m_previousFolderPath = m_imageModel->currentDirectory();
665ac308 Poul Sander 2026-02-23 19:34 211
    m_imageModel->setDirectory(path);
40efdfde Poul Sander 2026-05-19 21:24 212
    QModelIndex dirIdx = m_dirModel->indexForPath(path);
665ac308 Poul Sander 2026-02-23 19:34 213
    if (dirIdx.isValid())
665ac308 Poul Sander 2026-02-23 19:34 214
        m_treeView->setCurrentIndex(dirIdx);
fd7f51cd Poul Sander 2026-05-22 15:30 215
    updatePathField(path);
665ac308 Poul Sander 2026-02-23 19:34 216
    QTimer::singleShot(0, this, [this]() {
665ac308 Poul Sander 2026-02-23 19:34 217
        selectPreviousFolderIfExists();
665ac308 Poul Sander 2026-02-23 19:34 218
        loadVisibleThumbnails();
665ac308 Poul Sander 2026-02-23 19:34 219
    });
665ac308 Poul Sander 2026-02-23 19:34 220
}
665ac308 Poul Sander 2026-02-23 19:34 221
fd7f51cd Poul Sander 2026-05-22 15:30 222
void MainWindow::updatePathField(const QByteArray &path)
fd7f51cd Poul Sander 2026-05-22 15:30 223
{
fd7f51cd Poul Sander 2026-05-22 15:30 224
    if (!m_pathEdit)
fd7f51cd Poul Sander 2026-05-22 15:30 225
        return;
fd7f51cd Poul Sander 2026-05-22 15:30 226
    const QString text = QFile::decodeName(path);
fd7f51cd Poul Sander 2026-05-22 15:30 227
    if (m_pathEdit->text() != text)
fd7f51cd Poul Sander 2026-05-22 15:30 228
        m_pathEdit->setText(text);
fd7f51cd Poul Sander 2026-05-22 15:30 229
}
fd7f51cd Poul Sander 2026-05-22 15:30 230
fd7f51cd Poul Sander 2026-05-22 15:30 231
void MainWindow::onPathEntered()
fd7f51cd Poul Sander 2026-05-22 15:30 232
{
fd7f51cd Poul Sander 2026-05-22 15:30 233
    const QString text = m_pathEdit->text().trimmed();
fd7f51cd Poul Sander 2026-05-22 15:30 234
    if (text.isEmpty())
fd7f51cd Poul Sander 2026-05-22 15:30 235
        return;
fd7f51cd Poul Sander 2026-05-22 15:30 236
    openPath(text);
fd7f51cd Poul Sander 2026-05-22 15:30 237
}
fd7f51cd Poul Sander 2026-05-22 15:30 238
48607848 Poul Sander 2026-05-19 19:20 239
void MainWindow::openPath(const QString &path)
48607848 Poul Sander 2026-05-19 19:20 240
{
48607848 Poul Sander 2026-05-19 19:20 241
    QFileInfo info(path);
48607848 Poul Sander 2026-05-19 19:20 242
    if (!info.exists())
48607848 Poul Sander 2026-05-19 19:20 243
        return;
48607848 Poul Sander 2026-05-19 19:20 244
48607848 Poul Sander 2026-05-19 19:20 245
    if (info.isDir()) {
80a9926d Poul Sander 2026-05-19 20:28 246
        navigateToFolder(QFile::encodeName(info.absoluteFilePath()));
48607848 Poul Sander 2026-05-19 19:20 247
    } else if (info.isFile()) {
80a9926d Poul Sander 2026-05-19 20:28 248
        navigateToFolder(QFile::encodeName(info.absolutePath()));
48607848 Poul Sander 2026-05-19 19:20 249
48607848 Poul Sander 2026-05-19 19:20 250
        // ImageModel::setDirectory is synchronous, so items are ready now.
48607848 Poul Sander 2026-05-19 19:20 251
        // Find the file in the model and open it.
80a9926d Poul Sander 2026-05-19 20:28 252
        QByteArray target = QFile::encodeName(info.absoluteFilePath());
48607848 Poul Sander 2026-05-19 19:20 253
        for (int i = 0; i < m_imageModel->rowCount(); ++i) {
48607848 Poul Sander 2026-05-19 19:20 254
            QModelIndex idx = m_imageModel->index(i);
80a9926d Poul Sander 2026-05-19 20:28 255
            if (m_imageModel->filePath(idx) == target) {
48607848 Poul Sander 2026-05-19 19:20 256
                m_listView->setCurrentIndex(idx);
48607848 Poul Sander 2026-05-19 19:20 257
                m_listView->scrollTo(idx);
48607848 Poul Sander 2026-05-19 19:20 258
                showPreview(idx);
48607848 Poul Sander 2026-05-19 19:20 259
                if (!m_imageModel->isFolder(idx))
48607848 Poul Sander 2026-05-19 19:20 260
                    enterSingleImageMode(idx);
48607848 Poul Sander 2026-05-19 19:20 261
                break;
48607848 Poul Sander 2026-05-19 19:20 262
            }
48607848 Poul Sander 2026-05-19 19:20 263
        }
48607848 Poul Sander 2026-05-19 19:20 264
    }
48607848 Poul Sander 2026-05-19 19:20 265
}
48607848 Poul Sander 2026-05-19 19:20 266
665ac308 Poul Sander 2026-02-23 19:34 267
void MainWindow::selectPreviousFolderIfExists()
665ac308 Poul Sander 2026-02-23 19:34 268
{
665ac308 Poul Sander 2026-02-23 19:34 269
    if (m_previousFolderPath.isEmpty())
665ac308 Poul Sander 2026-02-23 19:34 270
        return;
665ac308 Poul Sander 2026-02-23 19:34 271
665ac308 Poul Sander 2026-02-23 19:34 272
    // Find the previous folder in the current directory's contents
665ac308 Poul Sander 2026-02-23 19:34 273
    for (int i = 0; i < m_imageModel->rowCount(); ++i) {
665ac308 Poul Sander 2026-02-23 19:34 274
        QModelIndex idx = m_imageModel->index(i);
665ac308 Poul Sander 2026-02-23 19:34 275
        if (m_imageModel->isFolder(idx)) {
80a9926d Poul Sander 2026-05-19 20:28 276
            QByteArray folderPath = m_imageModel->filePath(idx);
665ac308 Poul Sander 2026-02-23 19:34 277
            if (folderPath == m_previousFolderPath) {
665ac308 Poul Sander 2026-02-23 19:34 278
                m_listView->setCurrentIndex(idx);
665ac308 Poul Sander 2026-02-23 19:34 279
                m_listView->scrollTo(idx);
665ac308 Poul Sander 2026-02-23 19:34 280
                return;
665ac308 Poul Sander 2026-02-23 19:34 281
            }
665ac308 Poul Sander 2026-02-23 19:34 282
        }
665ac308 Poul Sander 2026-02-23 19:34 283
    }
665ac308 Poul Sander 2026-02-23 19:34 284
}
665ac308 Poul Sander 2026-02-23 19:34 285
032aa1e1 Poul Sander 2026-02-19 18:55 286
void MainWindow::setupConnections()
032aa1e1 Poul Sander 2026-02-19 18:55 287
{
fd7f51cd Poul Sander 2026-05-22 15:30 288
    connect(m_pathEdit, &QLineEdit::returnPressed,
fd7f51cd Poul Sander 2026-05-22 15:30 289
            this, &MainWindow::onPathEntered);
fd7f51cd Poul Sander 2026-05-22 15:30 290
    connect(m_goButton, &QPushButton::clicked,
fd7f51cd Poul Sander 2026-05-22 15:30 291
            this, &MainWindow::onPathEntered);
fd7f51cd Poul Sander 2026-05-22 15:30 292
    connect(m_pathEdit, &QLineEdit::textEdited,
fd7f51cd Poul Sander 2026-05-22 15:30 293
            m_pathCompleterModel, &PathCompleterModel::setPrefix);
fd7f51cd Poul Sander 2026-05-22 15:30 294
3bee3e3e Poul Sander 2026-03-05 17:07 295
    connect(m_rootList, &QListWidget::currentItemChanged, this,
3bee3e3e Poul Sander 2026-03-05 17:07 296
            [this](QListWidgetItem *current, QListWidgetItem *) {
3bee3e3e Poul Sander 2026-03-05 17:07 297
                if (current == nullptr)
3bee3e3e Poul Sander 2026-03-05 17:07 298
                    return;
3bee3e3e Poul Sander 2026-03-05 17:07 299
3bee3e3e Poul Sander 2026-03-05 17:07 300
                QString rootPath = current->data(Qt::UserRole).toString();
fc5f2e27 Poul Sander 2026-02-22 22:06 301
                m_dirModel->setRootPath(rootPath);
40efdfde Poul Sander 2026-05-19 21:24 302
                m_treeView->setRootIndex(m_dirModel->rootIndex());
fc5f2e27 Poul Sander 2026-02-22 22:06 303
            });
fc5f2e27 Poul Sander 2026-02-22 22:06 304
032aa1e1 Poul Sander 2026-02-19 18:55 305
    connect(m_treeView, &QTreeView::clicked, this,
032aa1e1 Poul Sander 2026-02-19 18:55 306
            [this](const QModelIndex &index)
6fcd6c90 Poul Sander 2026-02-19 21:12 307
            {
40efdfde Poul Sander 2026-05-19 21:24 308
                QByteArray path = m_dirModel->filePath(index);
665ac308 Poul Sander 2026-02-23 19:34 309
                navigateToFolder(path);
6fcd6c90 Poul Sander 2026-02-19 21:12 310
            });
032aa1e1 Poul Sander 2026-02-19 18:55 311
032aa1e1 Poul Sander 2026-02-19 18:55 312
    connect(m_listView, &QListView::clicked, this,
cfe4a67b Poul Sander 2026-02-19 23:06 313
            [this](const QModelIndex &index) { showPreview(index); });
6fcd6c90 Poul Sander 2026-02-19 21:12 314
cfe4a67b Poul Sander 2026-02-19 23:06 315
    connect(m_listView->selectionModel(), &QItemSelectionModel::currentChanged,
cfe4a67b Poul Sander 2026-02-19 23:06 316
            this, [this](const QModelIndex &current) { showPreview(current); });
6fcd6c90 Poul Sander 2026-02-19 21:12 317
61136b77 Poul Sander 2026-02-20 22:45 318
    connect(m_listView, &QListView::doubleClicked, this,
09130ab9 Poul Sander 2026-02-22 22:19 319
            [this](const QModelIndex &index) {
09130ab9 Poul Sander 2026-02-22 22:19 320
                if (m_imageModel->isFolder(index)) {
80a9926d Poul Sander 2026-05-19 20:28 321
                    QByteArray path = m_imageModel->filePath(index);
665ac308 Poul Sander 2026-02-23 19:34 322
                    navigateToFolder(path);
09130ab9 Poul Sander 2026-02-22 22:19 323
                } else {
09130ab9 Poul Sander 2026-02-22 22:19 324
                    enterSingleImageMode(index);
09130ab9 Poul Sander 2026-02-22 22:19 325
                }
09130ab9 Poul Sander 2026-02-22 22:19 326
            });
61136b77 Poul Sander 2026-02-20 22:45 327
61136b77 Poul Sander 2026-02-20 22:45 328
    connect(m_imageView, &ImageViewWidget::closeRequested,
61136b77 Poul Sander 2026-02-20 22:45 329
            this, &MainWindow::leaveSingleImageMode);
beb4a823 Poul Sander 2026-05-19 21:53 330
    connect(m_imageView, &ImageViewWidget::editCaptionRequested,
beb4a823 Poul Sander 2026-05-19 21:53 331
        this, &MainWindow::onEditCaption);
1ffc1962 Poul Sander 2026-02-20 23:41 332
    connect(m_imageView, &ImageViewWidget::nextRequested, this, [this]() {
1ffc1962 Poul Sander 2026-02-20 23:41 333
        QModelIndex cur = m_listView->currentIndex();
1ffc1962 Poul Sander 2026-02-20 23:41 334
        int next = cur.isValid() ? cur.row() + 1 : 0;
09130ab9 Poul Sander 2026-02-22 22:19 335
        while (next < m_imageModel->rowCount() && m_imageModel->isFolder(m_imageModel->index(next)))
09130ab9 Poul Sander 2026-02-22 22:19 336
            ++next;
1ffc1962 Poul Sander 2026-02-20 23:41 337
        if (next < m_imageModel->rowCount())
1ffc1962 Poul Sander 2026-02-20 23:41 338
        {
1ffc1962 Poul Sander 2026-02-20 23:41 339
            QModelIndex idx = m_imageModel->index(next);
1ffc1962 Poul Sander 2026-02-20 23:41 340
            m_listView->setCurrentIndex(idx);
1ffc1962 Poul Sander 2026-02-20 23:41 341
            enterSingleImageMode(idx);
1ffc1962 Poul Sander 2026-02-20 23:41 342
        }
1ffc1962 Poul Sander 2026-02-20 23:41 343
    });
1ffc1962 Poul Sander 2026-02-20 23:41 344
1ffc1962 Poul Sander 2026-02-20 23:41 345
    connect(m_imageView, &ImageViewWidget::previousRequested, this, [this]() {
1ffc1962 Poul Sander 2026-02-20 23:41 346
        QModelIndex cur = m_listView->currentIndex();
1ffc1962 Poul Sander 2026-02-20 23:41 347
        int prev = cur.isValid() ? cur.row() - 1 : 0;
09130ab9 Poul Sander 2026-02-22 22:19 348
        while (prev >= 0 && m_imageModel->isFolder(m_imageModel->index(prev)))
09130ab9 Poul Sander 2026-02-22 22:19 349
            --prev;
1ffc1962 Poul Sander 2026-02-20 23:41 350
        if (prev >= 0)
1ffc1962 Poul Sander 2026-02-20 23:41 351
        {
1ffc1962 Poul Sander 2026-02-20 23:41 352
            QModelIndex idx = m_imageModel->index(prev);
1ffc1962 Poul Sander 2026-02-20 23:41 353
            m_listView->setCurrentIndex(idx);
1ffc1962 Poul Sander 2026-02-20 23:41 354
            enterSingleImageMode(idx);
1ffc1962 Poul Sander 2026-02-20 23:41 355
        }
1ffc1962 Poul Sander 2026-02-20 23:41 356
    });
1ffc1962 Poul Sander 2026-02-20 23:41 357
6fcd6c90 Poul Sander 2026-02-19 21:12 358
    connect(m_listView->verticalScrollBar(), &QScrollBar::valueChanged,
6fcd6c90 Poul Sander 2026-02-19 21:12 359
            this, [this]()
6fcd6c90 Poul Sander 2026-02-19 21:12 360
            { loadVisibleThumbnails(); });
6fcd6c90 Poul Sander 2026-02-19 21:12 361
6fcd6c90 Poul Sander 2026-02-19 21:12 362
    // Also catch resize — visible range changes when the viewport is resized
6fcd6c90 Poul Sander 2026-02-19 21:12 363
    m_listView->viewport()->installEventFilter(this);
6fcd6c90 Poul Sander 2026-02-19 21:12 364
}
6fcd6c90 Poul Sander 2026-02-19 21:12 365
6fcd6c90 Poul Sander 2026-02-19 21:12 366
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
6fcd6c90 Poul Sander 2026-02-19 21:12 367
{
6fcd6c90 Poul Sander 2026-02-19 21:12 368
    if (obj == m_listView->viewport() && event->type() == QEvent::Resize)
6fcd6c90 Poul Sander 2026-02-19 21:12 369
        QTimer::singleShot(0, this, &MainWindow::loadVisibleThumbnails);
6fcd6c90 Poul Sander 2026-02-19 21:12 370
6fcd6c90 Poul Sander 2026-02-19 21:12 371
    return QMainWindow::eventFilter(obj, event);
6fcd6c90 Poul Sander 2026-02-19 21:12 372
}
6fcd6c90 Poul Sander 2026-02-19 21:12 373
6fcd6c90 Poul Sander 2026-02-19 21:12 374
void MainWindow::loadVisibleThumbnails()
6fcd6c90 Poul Sander 2026-02-19 21:12 375
{
6fcd6c90 Poul Sander 2026-02-19 21:12 376
    QModelIndex topLeft = m_listView->indexAt(QPoint(0, 0));
6fcd6c90 Poul Sander 2026-02-19 21:12 377
    QModelIndex bottomRight = m_listView->indexAt(
6fcd6c90 Poul Sander 2026-02-19 21:12 378
        QPoint(m_listView->viewport()->width() - 1,
6fcd6c90 Poul Sander 2026-02-19 21:12 379
               m_listView->viewport()->height() - 1));
6fcd6c90 Poul Sander 2026-02-19 21:12 380
6fcd6c90 Poul Sander 2026-02-19 21:12 381
    int first = topLeft.isValid() ? topLeft.row() : 0;
6fcd6c90 Poul Sander 2026-02-19 21:12 382
    int last = bottomRight.isValid() ? bottomRight.row()
6fcd6c90 Poul Sander 2026-02-19 21:12 383
                                     : m_imageModel->rowCount() - 1;
6fcd6c90 Poul Sander 2026-02-19 21:12 384
6fcd6c90 Poul Sander 2026-02-19 21:12 385
    if (last < 0)
6fcd6c90 Poul Sander 2026-02-19 21:12 386
        return;
6fcd6c90 Poul Sander 2026-02-19 21:12 387
6fcd6c90 Poul Sander 2026-02-19 21:12 388
    std::cout << "loadVisibleThumbnails: " << first << " - " << last << "\n";
6fcd6c90 Poul Sander 2026-02-19 21:12 389
    m_imageModel->requestThumbnails(first, last);
032aa1e1 Poul Sander 2026-02-19 18:55 390
}
cfe4a67b Poul Sander 2026-02-19 23:06 391
cfe4a67b Poul Sander 2026-02-19 23:06 392
void MainWindow::showPreview(const QModelIndex &index)
cfe4a67b Poul Sander 2026-02-19 23:06 393
{
cfe4a67b Poul Sander 2026-02-19 23:06 394
    if (!index.isValid())
cfe4a67b Poul Sander 2026-02-19 23:06 395
        return;
cfe4a67b Poul Sander 2026-02-19 23:06 396
80a9926d Poul Sander 2026-05-19 20:28 397
    QByteArray path = m_imageModel->filePath(index);
80a9926d Poul Sander 2026-05-19 20:28 398
80a9926d Poul Sander 2026-05-19 20:28 399
    int fd = ::open(path.constData(), O_RDONLY | O_CLOEXEC);
80a9926d Poul Sander 2026-05-19 20:28 400
    if (fd >= 0) {
80a9926d Poul Sander 2026-05-19 20:28 401
        QFile file;
80a9926d Poul Sander 2026-05-19 20:28 402
        if (file.open(fd, QIODevice::ReadOnly, QFileDevice::AutoCloseHandle)) {
80a9926d Poul Sander 2026-05-19 20:28 403
            QImageReader reader(&file);
80a9926d Poul Sander 2026-05-19 20:28 404
            reader.setAutoTransform(true);
80a9926d Poul Sander 2026-05-19 20:28 405
            QSize fullSize = reader.size();
80a9926d Poul Sander 2026-05-19 20:28 406
            if (fullSize.isValid()) {
80a9926d Poul Sander 2026-05-19 20:28 407
                QSize target = fullSize.scaled(m_previewLabel->size(), Qt::KeepAspectRatio);
80a9926d Poul Sander 2026-05-19 20:28 408
                reader.setScaledSize(target);
80a9926d Poul Sander 2026-05-19 20:28 409
            }
80a9926d Poul Sander 2026-05-19 20:28 410
            QImage image = reader.read();
80a9926d Poul Sander 2026-05-19 20:28 411
            m_previewLabel->setPixmap(
80a9926d Poul Sander 2026-05-19 20:28 412
                QPixmap::fromImage(image).scaled(
80a9926d Poul Sander 2026-05-19 20:28 413
                    m_previewLabel->size(),
80a9926d Poul Sander 2026-05-19 20:28 414
                    Qt::KeepAspectRatio,
80a9926d Poul Sander 2026-05-19 20:28 415
                    Qt::SmoothTransformation));
80a9926d Poul Sander 2026-05-19 20:28 416
        }
7889168c Poul Sander 2026-02-26 18:27 417
    }
53fdab93 Poul Sander 2026-05-18 18:45 418
53fdab93 Poul Sander 2026-05-18 18:45 419
    updateExifInfo(path);
53fdab93 Poul Sander 2026-05-18 18:45 420
}
53fdab93 Poul Sander 2026-05-18 18:45 421
80a9926d Poul Sander 2026-05-19 20:28 422
void MainWindow::updateExifInfo(const QByteArray &path)
53fdab93 Poul Sander 2026-05-18 18:45 423
{
80a9926d Poul Sander 2026-05-19 20:28 424
    // Check if this is a folder by attempting stat
80a9926d Poul Sander 2026-05-19 20:28 425
    struct ::stat st{};
80a9926d Poul Sander 2026-05-19 20:28 426
    if (::stat(path.constData(), &st) != 0 || S_ISDIR(st.st_mode)) {
53fdab93 Poul Sander 2026-05-18 18:45 427
        m_exifTable->setRowCount(0);
53fdab93 Poul Sander 2026-05-18 18:45 428
        return;
53fdab93 Poul Sander 2026-05-18 18:45 429
    }
53fdab93 Poul Sander 2026-05-18 18:45 430
    const ExifData data = ExifReader::read(path);
53fdab93 Poul Sander 2026-05-18 18:45 431
    const auto fields = data.toList();
53fdab93 Poul Sander 2026-05-18 18:45 432
    m_exifTable->setRowCount(fields.size());
53fdab93 Poul Sander 2026-05-18 18:45 433
    for (int i = 0; i < fields.size(); ++i) {
53fdab93 Poul Sander 2026-05-18 18:45 434
        m_exifTable->setItem(i, 0, new QTableWidgetItem(fields[i].first));
53fdab93 Poul Sander 2026-05-18 18:45 435
        m_exifTable->setItem(i, 1, new QTableWidgetItem(fields[i].second));
53fdab93 Poul Sander 2026-05-18 18:45 436
    }
53fdab93 Poul Sander 2026-05-18 18:45 437
    m_exifTable->resizeColumnToContents(0);
cfe4a67b Poul Sander 2026-02-19 23:06 438
}
61136b77 Poul Sander 2026-02-20 22:45 439
61136b77 Poul Sander 2026-02-20 22:45 440
void MainWindow::enterSingleImageMode(const QModelIndex &index)
61136b77 Poul Sander 2026-02-20 22:45 441
{
61136b77 Poul Sander 2026-02-20 22:45 442
    if (!index.isValid())
61136b77 Poul Sander 2026-02-20 22:45 443
        return;
61136b77 Poul Sander 2026-02-20 22:45 444
cbc7d22b Poul Sander 2026-02-24 19:23 445
    // Only save state and hide chrome when transitioning from browse mode
cbc7d22b Poul Sander 2026-02-24 19:23 446
    if (m_stack->currentWidget() != m_imageView) {
3bee3e3e Poul Sander 2026-03-05 17:07 447
        m_rootDockWasVisible = m_rootDock->isVisible();
cbc7d22b Poul Sander 2026-02-24 19:23 448
        m_folderDockWasVisible = m_folderDock->isVisible();
cbc7d22b Poul Sander 2026-02-24 19:23 449
        m_previewDockWasVisible = m_previewDock->isVisible();
53fdab93 Poul Sander 2026-05-18 18:45 450
        m_exifDockWasVisible = m_exifDock->isVisible();
3bee3e3e Poul Sander 2026-03-05 17:07 451
        m_rootDock->hide();
cbc7d22b Poul Sander 2026-02-24 19:23 452
        m_folderDock->hide();
cbc7d22b Poul Sander 2026-02-24 19:23 453
        m_previewDock->hide();
53fdab93 Poul Sander 2026-05-18 18:45 454
        m_exifDock->hide();
cbc7d22b Poul Sander 2026-02-24 19:23 455
        m_menuBar->hide();
cbc7d22b Poul Sander 2026-02-24 19:23 456
    }
cbc7d22b Poul Sander 2026-02-24 19:23 457
f61bf886 Poul Sander 2026-02-23 19:27 458
    m_imageView->setBackgroundColor(m_backgroundColorPreference);
80a9926d Poul Sander 2026-05-19 20:28 459
    QByteArray path = m_imageModel->filePath(index);
58103277 Poul Sander 2026-03-01 22:53 460
    m_imageView->setNeighborPaths(computeNeighborPaths(index));
61136b77 Poul Sander 2026-02-20 22:45 461
    m_imageView->setImage(path);
53fdab93 Poul Sander 2026-05-18 18:45 462
    m_imageView->setExifData(ExifReader::read(path));
53fdab93 Poul Sander 2026-05-18 18:45 463
    updateExifInfo(path);
61136b77 Poul Sander 2026-02-20 22:45 464
    m_stack->setCurrentWidget(m_imageView);
61136b77 Poul Sander 2026-02-20 22:45 465
    m_imageView->setFocus();
ccb726b8 Poul Sander 2026-02-23 22:43 466
ccb726b8 Poul Sander 2026-02-23 22:43 467
    if (m_imageViewWasFullScreen) {
ccb726b8 Poul Sander 2026-02-23 22:43 468
        setFullScreenMode(true);
ccb726b8 Poul Sander 2026-02-23 22:43 469
        m_imageViewWasFullScreen = false;
ccb726b8 Poul Sander 2026-02-23 22:43 470
    }
61136b77 Poul Sander 2026-02-20 22:45 471
}
61136b77 Poul Sander 2026-02-20 22:45 472
beb4a823 Poul Sander 2026-05-19 21:53 473
void MainWindow::onEditCaption()
beb4a823 Poul Sander 2026-05-19 21:53 474
{
beb4a823 Poul Sander 2026-05-19 21:53 475
    const QByteArray path = m_imageView->currentPath();
beb4a823 Poul Sander 2026-05-19 21:53 476
    if (path.isEmpty())
beb4a823 Poul Sander 2026-05-19 21:53 477
        return;
beb4a823 Poul Sander 2026-05-19 21:53 478
835a6bea Poul Sander 2026-05-19 22:03 479
    if (::access(path.constData(), W_OK) != 0) {
835a6bea Poul Sander 2026-05-19 22:03 480
        QMessageBox::warning(this, tr("Cannot Edit Caption"),
835a6bea Poul Sander 2026-05-19 22:03 481
            tr("The file is write-protected and cannot be edited.\n\n%1")
835a6bea Poul Sander 2026-05-19 22:03 482
                .arg(QString::fromLocal8Bit(path)));
835a6bea Poul Sander 2026-05-19 22:03 483
        return;
835a6bea Poul Sander 2026-05-19 22:03 484
    }
835a6bea Poul Sander 2026-05-19 22:03 485
beb4a823 Poul Sander 2026-05-19 21:53 486
    ExifData data = ExifReader::read(path);
beb4a823 Poul Sander 2026-05-19 21:53 487
beb4a823 Poul Sander 2026-05-19 21:53 488
    QDialog dlg(this);
beb4a823 Poul Sander 2026-05-19 21:53 489
    dlg.setWindowTitle(tr("Edit Caption"));
beb4a823 Poul Sander 2026-05-19 21:53 490
    auto *layout = new QVBoxLayout(&dlg);
beb4a823 Poul Sander 2026-05-19 21:53 491
    layout->addWidget(new QLabel(tr("Caption-Abstract:"), &dlg));
beb4a823 Poul Sander 2026-05-19 21:53 492
    auto *edit = new QPlainTextEdit(&dlg);
beb4a823 Poul Sander 2026-05-19 21:53 493
    edit->setPlainText(data.captionAbstract);
beb4a823 Poul Sander 2026-05-19 21:53 494
    edit->setMinimumWidth(400);
beb4a823 Poul Sander 2026-05-19 21:53 495
    layout->addWidget(edit);
beb4a823 Poul Sander 2026-05-19 21:53 496
    auto *buttons = new QDialogButtonBox(
beb4a823 Poul Sander 2026-05-19 21:53 497
        QDialogButtonBox::Ok | QDialogButtonBox::Cancel, &dlg);
beb4a823 Poul Sander 2026-05-19 21:53 498
    layout->addWidget(buttons);
beb4a823 Poul Sander 2026-05-19 21:53 499
    connect(buttons, &QDialogButtonBox::accepted, &dlg, &QDialog::accept);
beb4a823 Poul Sander 2026-05-19 21:53 500
    connect(buttons, &QDialogButtonBox::rejected, &dlg, &QDialog::reject);
beb4a823 Poul Sander 2026-05-19 21:53 501
beb4a823 Poul Sander 2026-05-19 21:53 502
    if (dlg.exec() != QDialog::Accepted)
beb4a823 Poul Sander 2026-05-19 21:53 503
        return;
beb4a823 Poul Sander 2026-05-19 21:53 504
beb4a823 Poul Sander 2026-05-19 21:53 505
    const QString newCaption = edit->toPlainText();
beb4a823 Poul Sander 2026-05-19 21:53 506
    if (ExifReader::saveCaption(path, newCaption, data)) {
beb4a823 Poul Sander 2026-05-19 21:53 507
        ExifData updated = ExifReader::read(path);
beb4a823 Poul Sander 2026-05-19 21:53 508
        m_imageView->setExifData(updated);
beb4a823 Poul Sander 2026-05-19 21:53 509
        updateExifInfo(path);
beb4a823 Poul Sander 2026-05-19 21:53 510
    }
beb4a823 Poul Sander 2026-05-19 21:53 511
}
beb4a823 Poul Sander 2026-05-19 21:53 512
61136b77 Poul Sander 2026-02-20 22:45 513
void MainWindow::leaveSingleImageMode()
61136b77 Poul Sander 2026-02-20 22:45 514
{
3968465e Poul Sander 2026-02-23 19:13 515
    if (isFullScreen()) {
ccb726b8 Poul Sander 2026-02-23 22:43 516
        m_imageViewWasFullScreen = true;
ccb726b8 Poul Sander 2026-02-23 22:43 517
        setFullScreenMode(false);
61136b77 Poul Sander 2026-02-20 22:45 518
    }
58103277 Poul Sander 2026-03-01 22:53 519
    m_imageView->clearCache();
61136b77 Poul Sander 2026-02-20 22:45 520
    m_stack->setCurrentWidget(m_browserPage);
cbc7d22b Poul Sander 2026-02-24 19:23 521
    m_menuBar->show();
3bee3e3e Poul Sander 2026-03-05 17:07 522
    if (m_rootDockWasVisible)
3bee3e3e Poul Sander 2026-03-05 17:07 523
        m_rootDock->show();
cbc7d22b Poul Sander 2026-02-24 19:23 524
    if (m_folderDockWasVisible)
cbc7d22b Poul Sander 2026-02-24 19:23 525
        m_folderDock->show();
cbc7d22b Poul Sander 2026-02-24 19:23 526
    if (m_previewDockWasVisible)
cbc7d22b Poul Sander 2026-02-24 19:23 527
        m_previewDock->show();
53fdab93 Poul Sander 2026-05-18 18:45 528
    if (m_exifDockWasVisible)
53fdab93 Poul Sander 2026-05-18 18:45 529
        m_exifDock->show();
61136b77 Poul Sander 2026-02-20 22:45 530
    m_listView->setFocus();
61136b77 Poul Sander 2026-02-20 22:45 531
}
61136b77 Poul Sander 2026-02-20 22:45 532
80a9926d Poul Sander 2026-05-19 20:28 533
QList<QByteArray> MainWindow::computeNeighborPaths(const QModelIndex &index) const
58103277 Poul Sander 2026-03-01 22:53 534
{
80a9926d Poul Sander 2026-05-19 20:28 535
    QList<QByteArray> paths;
58103277 Poul Sander 2026-03-01 22:53 536
    int row = index.row();
58103277 Poul Sander 2026-03-01 22:53 537
58103277 Poul Sander 2026-03-01 22:53 538
    // Collect up to CacheKeepBehind non-folder images before current
58103277 Poul Sander 2026-03-01 22:53 539
    int count = 0;
58103277 Poul Sander 2026-03-01 22:53 540
    for (int i = row - 1; i >= 0 && count < ImageViewWidget::CacheKeepBehind; --i) {
58103277 Poul Sander 2026-03-01 22:53 541
        QModelIndex idx = m_imageModel->index(i);
58103277 Poul Sander 2026-03-01 22:53 542
        if (!m_imageModel->isFolder(idx)) {
58103277 Poul Sander 2026-03-01 22:53 543
            paths.prepend(m_imageModel->filePath(idx));
58103277 Poul Sander 2026-03-01 22:53 544
            ++count;
58103277 Poul Sander 2026-03-01 22:53 545
        }
58103277 Poul Sander 2026-03-01 22:53 546
    }
58103277 Poul Sander 2026-03-01 22:53 547
58103277 Poul Sander 2026-03-01 22:53 548
    // Collect up to CacheReadAhead non-folder images after current
58103277 Poul Sander 2026-03-01 22:53 549
    count = 0;
58103277 Poul Sander 2026-03-01 22:53 550
    for (int i = row + 1; i < m_imageModel->rowCount() && count < ImageViewWidget::CacheReadAhead; ++i) {
58103277 Poul Sander 2026-03-01 22:53 551
        QModelIndex idx = m_imageModel->index(i);
58103277 Poul Sander 2026-03-01 22:53 552
        if (!m_imageModel->isFolder(idx)) {
58103277 Poul Sander 2026-03-01 22:53 553
            paths.append(m_imageModel->filePath(idx));
58103277 Poul Sander 2026-03-01 22:53 554
            ++count;
58103277 Poul Sander 2026-03-01 22:53 555
        }
58103277 Poul Sander 2026-03-01 22:53 556
    }
58103277 Poul Sander 2026-03-01 22:53 557
58103277 Poul Sander 2026-03-01 22:53 558
    return paths;
58103277 Poul Sander 2026-03-01 22:53 559
}
58103277 Poul Sander 2026-03-01 22:53 560
ccb726b8 Poul Sander 2026-02-23 22:43 561
void MainWindow::setFullScreenMode(bool fullScreen)
ccb726b8 Poul Sander 2026-02-23 22:43 562
{
ccb726b8 Poul Sander 2026-02-23 22:43 563
    if (fullScreen == isFullScreen())
ccb726b8 Poul Sander 2026-02-23 22:43 564
        return;
ccb726b8 Poul Sander 2026-02-23 22:43 565
ccb726b8 Poul Sander 2026-02-23 22:43 566
    if (fullScreen) {
ccb726b8 Poul Sander 2026-02-23 22:43 567
        m_wasMaximized = isMaximized();
ccb726b8 Poul Sander 2026-02-23 22:43 568
        m_menuBar->hide();
ccb726b8 Poul Sander 2026-02-23 22:43 569
        showFullScreen();
ccb726b8 Poul Sander 2026-02-23 22:43 570
    } else {
cbc7d22b Poul Sander 2026-02-24 19:23 571
        // Only restore menu bar when returning to browse mode
cbc7d22b Poul Sander 2026-02-24 19:23 572
        if (m_stack->currentWidget() != m_imageView)
cbc7d22b Poul Sander 2026-02-24 19:23 573
            m_menuBar->show();
ccb726b8 Poul Sander 2026-02-23 22:43 574
        if (m_wasMaximized)
ccb726b8 Poul Sander 2026-02-23 22:43 575
            showMaximized();
ccb726b8 Poul Sander 2026-02-23 22:43 576
        else
ccb726b8 Poul Sander 2026-02-23 22:43 577
            showNormal();
ccb726b8 Poul Sander 2026-02-23 22:43 578
    }
ccb726b8 Poul Sander 2026-02-23 22:43 579
}
ccb726b8 Poul Sander 2026-02-23 22:43 580
61136b77 Poul Sander 2026-02-20 22:45 581
void MainWindow::keyPressEvent(QKeyEvent *event)
61136b77 Poul Sander 2026-02-20 22:45 582
{
4d6b7476 Poul Sander 2026-05-30 16:39 583
    // THREADING: No race condition with F11 fullscreen toggle.  All cross-thread
4d6b7476 Poul Sander 2026-05-30 16:39 584
    // signal deliveries (thumbnailReady via explicit QueuedConnection,
4d6b7476 Poul Sander 2026-05-30 16:39 585
    // onImageLoaded via AutoConnection resolving to QueuedConnection) execute on
4d6b7476 Poul Sander 2026-05-30 16:39 586
    // the main thread's event loop.  The event loop processes events one at a
4d6b7476 Poul Sander 2026-05-30 16:39 587
    // time, so this handler and any signal slot can never interleave.  All widget
4d6b7476 Poul Sander 2026-05-30 16:39 588
    // state (m_wasMaximized, m_stack, fullscreen flag) is only accessed from the
4d6b7476 Poul Sander 2026-05-30 16:39 589
    // main thread.
61136b77 Poul Sander 2026-02-20 22:45 590
    if (event->key() == Qt::Key_F11 && m_stack->currentWidget() == m_imageView)
61136b77 Poul Sander 2026-02-20 22:45 591
    {
ccb726b8 Poul Sander 2026-02-23 22:43 592
        setFullScreenMode(!isFullScreen());
61136b77 Poul Sander 2026-02-20 22:45 593
        event->accept();
61136b77 Poul Sander 2026-02-20 22:45 594
        return;
61136b77 Poul Sander 2026-02-20 22:45 595
    }
61136b77 Poul Sander 2026-02-20 22:45 596
61136b77 Poul Sander 2026-02-20 22:45 597
    if (event->key() == Qt::Key_Return && m_stack->currentWidget() == m_browserPage)
61136b77 Poul Sander 2026-02-20 22:45 598
    {
61136b77 Poul Sander 2026-02-20 22:45 599
        QModelIndex current = m_listView->currentIndex();
61136b77 Poul Sander 2026-02-20 22:45 600
        if (current.isValid())
61136b77 Poul Sander 2026-02-20 22:45 601
        {
09130ab9 Poul Sander 2026-02-22 22:19 602
            if (m_imageModel->isFolder(current)) {
80a9926d Poul Sander 2026-05-19 20:28 603
                QByteArray path = m_imageModel->filePath(current);
665ac308 Poul Sander 2026-02-23 19:34 604
                navigateToFolder(path);
09130ab9 Poul Sander 2026-02-22 22:19 605
            } else {
09130ab9 Poul Sander 2026-02-22 22:19 606
                enterSingleImageMode(current);
09130ab9 Poul Sander 2026-02-22 22:19 607
            }
61136b77 Poul Sander 2026-02-20 22:45 608
            event->accept();
61136b77 Poul Sander 2026-02-20 22:45 609
            return;
61136b77 Poul Sander 2026-02-20 22:45 610
        }
61136b77 Poul Sander 2026-02-20 22:45 611
    }
61136b77 Poul Sander 2026-02-20 22:45 612
61136b77 Poul Sander 2026-02-20 22:45 613
    QMainWindow::keyPressEvent(event);
61136b77 Poul Sander 2026-02-20 22:45 614
}
c9e5684a Poul Sander 2026-02-23 19:18 615
c9e5684a Poul Sander 2026-02-23 19:18 616
void MainWindow::closeEvent(QCloseEvent *event)
c9e5684a Poul Sander 2026-02-23 19:18 617
{
c9e5684a Poul Sander 2026-02-23 19:18 618
    if (m_stack->currentWidget() == m_imageView)
c9e5684a Poul Sander 2026-02-23 19:18 619
    {
c9e5684a Poul Sander 2026-02-23 19:18 620
        leaveSingleImageMode();
c9e5684a Poul Sander 2026-02-23 19:18 621
        event->ignore();
c9e5684a Poul Sander 2026-02-23 19:18 622
        return;
c9e5684a Poul Sander 2026-02-23 19:18 623
    }
c9e5684a Poul Sander 2026-02-23 19:18 624
f61bf886 Poul Sander 2026-02-23 19:27 625
    savePreferences();
c9e5684a Poul Sander 2026-02-23 19:18 626
    QMainWindow::closeEvent(event);
c9e5684a Poul Sander 2026-02-23 19:18 627
}
f61bf886 Poul Sander 2026-02-23 19:27 628
4ae57f60 Poul Sander 2026-02-25 21:35 629
void MainWindow::applyDockLocking(bool lock)
4ae57f60 Poul Sander 2026-02-25 21:35 630
{
4ae57f60 Poul Sander 2026-02-25 21:35 631
    const QDockWidget::DockWidgetFeatures movableFeatures =
4ae57f60 Poul Sander 2026-02-25 21:35 632
        QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable;
4ae57f60 Poul Sander 2026-02-25 21:35 633
    const QDockWidget::DockWidgetFeatures baseFeatures =
4ae57f60 Poul Sander 2026-02-25 21:35 634
        QDockWidget::DockWidgetClosable;
4ae57f60 Poul Sander 2026-02-25 21:35 635
4ae57f60 Poul Sander 2026-02-25 21:35 636
    QDockWidget::DockWidgetFeatures features = lock ? baseFeatures
4ae57f60 Poul Sander 2026-02-25 21:35 637
                                                    : (baseFeatures | movableFeatures);
3bee3e3e Poul Sander 2026-03-05 17:07 638
    m_rootDock->setFeatures(features);
4ae57f60 Poul Sander 2026-02-25 21:35 639
    m_folderDock->setFeatures(features);
4ae57f60 Poul Sander 2026-02-25 21:35 640
    m_previewDock->setFeatures(features);
53fdab93 Poul Sander 2026-05-18 18:45 641
    m_exifDock->setFeatures(features);
4ae57f60 Poul Sander 2026-02-25 21:35 642
}
4ae57f60 Poul Sander 2026-02-25 21:35 643
f61bf886 Poul Sander 2026-02-23 19:27 644
void MainWindow::loadPreferences()
f61bf886 Poul Sander 2026-02-23 19:27 645
{
f61bf886 Poul Sander 2026-02-23 19:27 646
    QSettings settings("SagoImageBrowser", "SagoImageBrowser");
f61bf886 Poul Sander 2026-02-23 19:27 647
    m_backgroundColorPreference = settings.value("backgroundColor", "black").toString();
f61bf886 Poul Sander 2026-02-23 19:27 648
    m_imageView->setBackgroundColor(m_backgroundColorPreference);
4ae57f60 Poul Sander 2026-02-25 21:35 649
    m_lockDockingPreference = settings.value("lockDocking", false).toBool();
4ae57f60 Poul Sander 2026-02-25 21:35 650
    applyDockLocking(m_lockDockingPreference);
459bad0f Poul Sander 2026-05-17 14:11 651
    m_folderCacheSizePreference = settings.value("folderCacheSize", 4).toInt();
459bad0f Poul Sander 2026-05-17 14:11 652
    m_imageModel->setCacheMaxSize(m_folderCacheSizePreference);
ac476ca8 Poul Sander 2026-05-18 16:47 653
    m_thumbnailCacheSizePreference = settings.value("thumbnailCacheSize", "normal").toString();
ac476ca8 Poul Sander 2026-05-18 16:47 654
    m_imageModel->setThumbnailSize(thumbnailSizeFromString(m_thumbnailCacheSizePreference));
cbc7d22b Poul Sander 2026-02-24 19:23 655
    if (settings.contains("windowGeometry"))
cbc7d22b Poul Sander 2026-02-24 19:23 656
        restoreGeometry(settings.value("windowGeometry").toByteArray());
cbc7d22b Poul Sander 2026-02-24 19:23 657
    if (settings.contains("windowState"))
cbc7d22b Poul Sander 2026-02-24 19:23 658
        restoreState(settings.value("windowState").toByteArray());
f61bf886 Poul Sander 2026-02-23 19:27 659
}
f61bf886 Poul Sander 2026-02-23 19:27 660
f61bf886 Poul Sander 2026-02-23 19:27 661
void MainWindow::savePreferences()
f61bf886 Poul Sander 2026-02-23 19:27 662
{
f61bf886 Poul Sander 2026-02-23 19:27 663
    QSettings settings("SagoImageBrowser", "SagoImageBrowser");
f61bf886 Poul Sander 2026-02-23 19:27 664
    settings.setValue("backgroundColor", m_backgroundColorPreference);
4ae57f60 Poul Sander 2026-02-25 21:35 665
    settings.setValue("lockDocking", m_lockDockingPreference);
459bad0f Poul Sander 2026-05-17 14:11 666
    settings.setValue("folderCacheSize", m_folderCacheSizePreference);
ac476ca8 Poul Sander 2026-05-18 16:47 667
    settings.setValue("thumbnailCacheSize", m_thumbnailCacheSizePreference);
cbc7d22b Poul Sander 2026-02-24 19:23 668
    settings.setValue("windowGeometry", saveGeometry());
cbc7d22b Poul Sander 2026-02-24 19:23 669
    settings.setValue("windowState", saveState());
f61bf886 Poul Sander 2026-02-23 19:27 670
}
f61bf886 Poul Sander 2026-02-23 19:27 671
f61bf886 Poul Sander 2026-02-23 19:27 672
void MainWindow::onPreferencesTriggered()
f61bf886 Poul Sander 2026-02-23 19:27 673
{
f61bf886 Poul Sander 2026-02-23 19:27 674
    PreferencesDialog dialog(this);
f61bf886 Poul Sander 2026-02-23 19:27 675
    dialog.setBackgroundColor(m_backgroundColorPreference);
4ae57f60 Poul Sander 2026-02-25 21:35 676
    dialog.setLockDocking(m_lockDockingPreference);
459bad0f Poul Sander 2026-05-17 14:11 677
    dialog.setCacheSize(m_folderCacheSizePreference);
ac476ca8 Poul Sander 2026-05-18 16:47 678
    dialog.setThumbnailCacheSize(m_thumbnailCacheSizePreference);
f61bf886 Poul Sander 2026-02-23 19:27 679
7370f1c0 Poul Sander 2026-03-01 15:15 680
    bool shouldResetLayout = false;
7370f1c0 Poul Sander 2026-03-01 15:15 681
    connect(&dialog, &PreferencesDialog::resetLayoutRequested, this, [&shouldResetLayout]() {
7370f1c0 Poul Sander 2026-03-01 15:15 682
        shouldResetLayout = true;
7370f1c0 Poul Sander 2026-03-01 15:15 683
    });
7370f1c0 Poul Sander 2026-03-01 15:15 684
f61bf886 Poul Sander 2026-02-23 19:27 685
    if (dialog.exec() == QDialog::Accepted)
f61bf886 Poul Sander 2026-02-23 19:27 686
    {
f61bf886 Poul Sander 2026-02-23 19:27 687
        m_backgroundColorPreference = dialog.getBackgroundColor();
f61bf886 Poul Sander 2026-02-23 19:27 688
        m_imageView->setBackgroundColor(m_backgroundColorPreference);
4ae57f60 Poul Sander 2026-02-25 21:35 689
        m_lockDockingPreference = dialog.getLockDocking();
4ae57f60 Poul Sander 2026-02-25 21:35 690
        applyDockLocking(m_lockDockingPreference);
459bad0f Poul Sander 2026-05-17 14:11 691
        m_folderCacheSizePreference = dialog.getCacheSize();
459bad0f Poul Sander 2026-05-17 14:11 692
        m_imageModel->setCacheMaxSize(m_folderCacheSizePreference);
ac476ca8 Poul Sander 2026-05-18 16:47 693
        QString newThumbSize = dialog.getThumbnailCacheSize();
ac476ca8 Poul Sander 2026-05-18 16:47 694
        if (newThumbSize != m_thumbnailCacheSizePreference)
ac476ca8 Poul Sander 2026-05-18 16:47 695
        {
ac476ca8 Poul Sander 2026-05-18 16:47 696
            m_thumbnailCacheSizePreference = newThumbSize;
ac476ca8 Poul Sander 2026-05-18 16:47 697
            m_imageModel->setThumbnailSize(thumbnailSizeFromString(m_thumbnailCacheSizePreference));
ac476ca8 Poul Sander 2026-05-18 16:47 698
            loadVisibleThumbnails();
ac476ca8 Poul Sander 2026-05-18 16:47 699
        }
f61bf886 Poul Sander 2026-02-23 19:27 700
        savePreferences();
f61bf886 Poul Sander 2026-02-23 19:27 701
    }
7370f1c0 Poul Sander 2026-03-01 15:15 702
    
7370f1c0 Poul Sander 2026-03-01 15:15 703
    // Apply layout reset immediately after dialog closes if requested
7370f1c0 Poul Sander 2026-03-01 15:15 704
    if (shouldResetLayout) {
7370f1c0 Poul Sander 2026-03-01 15:15 705
        resetLayoutToDefault();
7370f1c0 Poul Sander 2026-03-01 15:15 706
    }
7370f1c0 Poul Sander 2026-03-01 15:15 707
}
7370f1c0 Poul Sander 2026-03-01 15:15 708
7370f1c0 Poul Sander 2026-03-01 15:15 709
void MainWindow::resetLayoutToDefault()
7370f1c0 Poul Sander 2026-03-01 15:15 710
{
7370f1c0 Poul Sander 2026-03-01 15:15 711
    QSettings settings("SagoImageBrowser", "SagoImageBrowser");
7370f1c0 Poul Sander 2026-03-01 15:15 712
    settings.remove("windowGeometry");
7370f1c0 Poul Sander 2026-03-01 15:15 713
    settings.remove("windowState");
7370f1c0 Poul Sander 2026-03-01 15:15 714
    
7370f1c0 Poul Sander 2026-03-01 15:15 715
    // Hide and detach docks completely
3bee3e3e Poul Sander 2026-03-05 17:07 716
    m_rootDock->hide();
7370f1c0 Poul Sander 2026-03-01 15:15 717
    m_folderDock->hide();
7370f1c0 Poul Sander 2026-03-01 15:15 718
    m_previewDock->hide();
7370f1c0 Poul Sander 2026-03-01 15:15 719
    
3bee3e3e Poul Sander 2026-03-05 17:07 720
    m_rootDock->setFloating(false);
7370f1c0 Poul Sander 2026-03-01 15:15 721
    m_folderDock->setFloating(false);
7370f1c0 Poul Sander 2026-03-01 15:15 722
    m_previewDock->setFloating(false);
7370f1c0 Poul Sander 2026-03-01 15:15 723
    
3bee3e3e Poul Sander 2026-03-05 17:07 724
    removeDockWidget(m_rootDock);
7370f1c0 Poul Sander 2026-03-01 15:15 725
    removeDockWidget(m_folderDock);
7370f1c0 Poul Sander 2026-03-01 15:15 726
    removeDockWidget(m_previewDock);
7370f1c0 Poul Sander 2026-03-01 15:15 727
    
7370f1c0 Poul Sander 2026-03-01 15:15 728
    // Re-add in default positions
3bee3e3e Poul Sander 2026-03-05 17:07 729
    addDockWidget(Qt::LeftDockWidgetArea, m_rootDock);
7370f1c0 Poul Sander 2026-03-01 15:15 730
    addDockWidget(Qt::LeftDockWidgetArea, m_folderDock);
7370f1c0 Poul Sander 2026-03-01 15:15 731
    addDockWidget(Qt::RightDockWidgetArea, m_previewDock);
3bee3e3e Poul Sander 2026-03-05 17:07 732
    splitDockWidget(m_rootDock, m_folderDock, Qt::Vertical);
7370f1c0 Poul Sander 2026-03-01 15:15 733
    
3bee3e3e Poul Sander 2026-03-05 17:07 734
    // Show all docks
3bee3e3e Poul Sander 2026-03-05 17:07 735
    m_rootDock->show();
7370f1c0 Poul Sander 2026-03-01 15:15 736
    m_folderDock->show();
7370f1c0 Poul Sander 2026-03-01 15:15 737
    m_previewDock->show();
f61bf886 Poul Sander 2026-02-23 19:27 738
}
1970-01-01 00:00 739