#include "mainwindow.h" #include "imagemodel.h" #include "imageviewwidget.h" #include "preferencesdialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { setupUi(); setupMenuBar(); setupConnections(); loadPreferences(); } void MainWindow::setupUi() { // Folder model (left pane) m_dirModel = new QFileSystemModel(this); m_dirModel->setFilter(QDir::AllDirs | QDir::NoDotAndDotDot); m_dirModel->setRootPath(QDir::homePath()); // Root chooser combo box m_rootCombo = new QComboBox; m_rootCombo->addItem("Home", QDir::homePath()); m_rootCombo->addItem("Pictures", QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); m_rootCombo->addItem("File System", QDir::rootPath()); m_treeView = new QTreeView; m_treeView->setModel(m_dirModel); m_treeView->setRootIndex(m_dirModel->index(QDir::homePath())); m_treeView->header()->hide(); m_treeView->setColumnHidden(1, true); m_treeView->setColumnHidden(2, true); m_treeView->setColumnHidden(3, true); // Left pane: combo + tree inside a container widget QWidget *leftPane = new QWidget; QVBoxLayout *leftLayout = new QVBoxLayout(leftPane); leftLayout->setContentsMargins(0, 0, 0, 0); leftLayout->addWidget(m_rootCombo); leftLayout->addWidget(m_treeView); // Thumbnail model (center pane) m_imageModel = new ImageModel(this); m_listView = new QListView; m_listView->setModel(m_imageModel); m_listView->setViewMode(QListView::IconMode); m_listView->setIconSize(QSize(128, 128)); m_listView->setGridSize(QSize(148, 168)); m_listView->setResizeMode(QListView::Adjust); m_listView->setSelectionMode(QAbstractItemView::SingleSelection); m_listView->setSpacing(4); m_listView->setWordWrap(true); m_listView->setTextElideMode(Qt::ElideRight); // Preview pane (right) m_previewLabel = new QLabel; m_previewLabel->setAlignment(Qt::AlignCenter); m_previewLabel->setMinimumWidth(300); // Layout QSplitter *rightSplitter = new QSplitter(Qt::Horizontal); rightSplitter->addWidget(m_listView); rightSplitter->addWidget(m_previewLabel); rightSplitter->setStretchFactor(0, 3); rightSplitter->setStretchFactor(1, 2); QSplitter *mainSplitter = new QSplitter(Qt::Horizontal); mainSplitter->addWidget(leftPane); mainSplitter->addWidget(rightSplitter); mainSplitter->setStretchFactor(1, 1); m_browserPage = mainSplitter; // Single image view m_imageView = new ImageViewWidget; // Stack to switch between browser and single image m_stack = new QStackedWidget; m_stack->addWidget(m_browserPage); m_stack->addWidget(m_imageView); setCentralWidget(m_stack); } void MainWindow::setupMenuBar() { m_menuBar = menuBar(); m_menuBar->setStyleSheet( "QMenuBar { padding: 1px 2px; }" "QMenuBar::item { padding: 2px 6px; }" ); QMenu *editMenu = m_menuBar->addMenu("&Edit"); QAction *preferencesAction = editMenu->addAction("&Preferences"); connect(preferencesAction, &QAction::triggered, this, &MainWindow::onPreferencesTriggered); } void MainWindow::navigateToFolder(const QString &path) { m_previousFolderPath = m_imageModel->currentDirectory(); m_imageModel->setDirectory(path); QModelIndex dirIdx = m_dirModel->index(path); if (dirIdx.isValid()) m_treeView->setCurrentIndex(dirIdx); QTimer::singleShot(0, this, [this]() { selectPreviousFolderIfExists(); loadVisibleThumbnails(); }); } void MainWindow::selectPreviousFolderIfExists() { if (m_previousFolderPath.isEmpty()) return; // Find the previous folder in the current directory's contents for (int i = 0; i < m_imageModel->rowCount(); ++i) { QModelIndex idx = m_imageModel->index(i); if (m_imageModel->isFolder(idx)) { QString folderPath = m_imageModel->filePath(idx); if (folderPath == m_previousFolderPath) { m_listView->setCurrentIndex(idx); m_listView->scrollTo(idx); return; } } } } void MainWindow::setupConnections() { connect(m_rootCombo, QOverload::of(&QComboBox::currentIndexChanged), this, [this](int index) { QString rootPath = m_rootCombo->itemData(index).toString(); m_dirModel->setRootPath(rootPath); m_treeView->setRootIndex(m_dirModel->index(rootPath)); }); connect(m_treeView, &QTreeView::clicked, this, [this](const QModelIndex &index) { QString path = m_dirModel->filePath(index); navigateToFolder(path); }); connect(m_listView, &QListView::clicked, this, [this](const QModelIndex &index) { showPreview(index); }); connect(m_listView->selectionModel(), &QItemSelectionModel::currentChanged, this, [this](const QModelIndex ¤t) { showPreview(current); }); connect(m_listView, &QListView::doubleClicked, this, [this](const QModelIndex &index) { if (m_imageModel->isFolder(index)) { QString path = m_imageModel->filePath(index); navigateToFolder(path); } else { enterSingleImageMode(index); } }); connect(m_imageView, &ImageViewWidget::closeRequested, this, &MainWindow::leaveSingleImageMode); connect(m_imageView, &ImageViewWidget::nextRequested, this, [this]() { QModelIndex cur = m_listView->currentIndex(); int next = cur.isValid() ? cur.row() + 1 : 0; while (next < m_imageModel->rowCount() && m_imageModel->isFolder(m_imageModel->index(next))) ++next; if (next < m_imageModel->rowCount()) { QModelIndex idx = m_imageModel->index(next); m_listView->setCurrentIndex(idx); enterSingleImageMode(idx); } }); connect(m_imageView, &ImageViewWidget::previousRequested, this, [this]() { QModelIndex cur = m_listView->currentIndex(); int prev = cur.isValid() ? cur.row() - 1 : 0; while (prev >= 0 && m_imageModel->isFolder(m_imageModel->index(prev))) --prev; if (prev >= 0) { QModelIndex idx = m_imageModel->index(prev); m_listView->setCurrentIndex(idx); enterSingleImageMode(idx); } }); connect(m_listView->verticalScrollBar(), &QScrollBar::valueChanged, this, [this]() { loadVisibleThumbnails(); }); // Also catch resize — visible range changes when the viewport is resized m_listView->viewport()->installEventFilter(this); } bool MainWindow::eventFilter(QObject *obj, QEvent *event) { if (obj == m_listView->viewport() && event->type() == QEvent::Resize) QTimer::singleShot(0, this, &MainWindow::loadVisibleThumbnails); return QMainWindow::eventFilter(obj, event); } void MainWindow::loadVisibleThumbnails() { QModelIndex topLeft = m_listView->indexAt(QPoint(0, 0)); QModelIndex bottomRight = m_listView->indexAt( QPoint(m_listView->viewport()->width() - 1, m_listView->viewport()->height() - 1)); int first = topLeft.isValid() ? topLeft.row() : 0; int last = bottomRight.isValid() ? bottomRight.row() : m_imageModel->rowCount() - 1; if (last < 0) return; std::cout << "loadVisibleThumbnails: " << first << " - " << last << "\n"; m_imageModel->requestThumbnails(first, last); } void MainWindow::showPreview(const QModelIndex &index) { if (!index.isValid()) return; QString path = m_imageModel->filePath(index); QImageReader reader(path); reader.setAutoTransform(true); QImage image = reader.read(); m_previewLabel->setPixmap( QPixmap::fromImage(image).scaled( m_previewLabel->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); } void MainWindow::enterSingleImageMode(const QModelIndex &index) { if (!index.isValid()) return; m_imageView->setBackgroundColor(m_backgroundColorPreference); QString path = m_imageModel->filePath(index); m_imageView->setImage(path); m_stack->setCurrentWidget(m_imageView); m_imageView->setFocus(); if (m_imageViewWasFullScreen) { setFullScreenMode(true); m_imageViewWasFullScreen = false; } } void MainWindow::leaveSingleImageMode() { if (isFullScreen()) { m_imageViewWasFullScreen = true; setFullScreenMode(false); } m_stack->setCurrentWidget(m_browserPage); m_listView->setFocus(); } void MainWindow::setFullScreenMode(bool fullScreen) { if (fullScreen == isFullScreen()) return; if (fullScreen) { m_wasMaximized = isMaximized(); m_menuBar->hide(); showFullScreen(); } else { m_menuBar->show(); if (m_wasMaximized) showMaximized(); else showNormal(); } } void MainWindow::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_F11 && m_stack->currentWidget() == m_imageView) { setFullScreenMode(!isFullScreen()); event->accept(); return; } if (event->key() == Qt::Key_Return && m_stack->currentWidget() == m_browserPage) { QModelIndex current = m_listView->currentIndex(); if (current.isValid()) { if (m_imageModel->isFolder(current)) { QString path = m_imageModel->filePath(current); navigateToFolder(path); } else { enterSingleImageMode(current); } event->accept(); return; } } QMainWindow::keyPressEvent(event); } void MainWindow::closeEvent(QCloseEvent *event) { if (m_stack->currentWidget() == m_imageView) { leaveSingleImageMode(); event->ignore(); return; } savePreferences(); QMainWindow::closeEvent(event); } void MainWindow::loadPreferences() { QSettings settings("SagoImageBrowser", "SagoImageBrowser"); m_backgroundColorPreference = settings.value("backgroundColor", "black").toString(); m_imageView->setBackgroundColor(m_backgroundColorPreference); } void MainWindow::savePreferences() { QSettings settings("SagoImageBrowser", "SagoImageBrowser"); settings.setValue("backgroundColor", m_backgroundColorPreference); } void MainWindow::onPreferencesTriggered() { PreferencesDialog dialog(this); dialog.setBackgroundColor(m_backgroundColorPreference); if (dialog.exec() == QDialog::Accepted) { m_backgroundColorPreference = dialog.getBackgroundColor(); m_imageView->setBackgroundColor(m_backgroundColorPreference); savePreferences(); } }