Viewer: inspector width from its content, fold it away on a narrow window

The inspector's width bounds were 75-100 average character widths, which on
macOS (8 px) came to 600 px for content that needs 397. They are now
measured: at least the panel's minimum size hint plus the scroll bar, at
most a third more. CollapsibleSection reports its folded content's width
in minimumSizeHint, so the panel keeps its width when a section opens. On a
font change the measurement waits a turn, until the children have the new
font.

When shrinking the window would leave the image narrower than the
inspector, the inspector folds away, with the magnifier if it shares the
inspector's column; they come back once the image would still be at least
as wide as the inspector (plus 40 px of hysteresis). Only a shrink folds, a
dock shown by anyone else is no longer the window's to restore, and folded
docks are saved as open. Measured on a 1920 px screen: folds at 1140 px,
returns at 1260 px; at 960 px the image gets 620 px instead of ~145.

Dark theme: disabled text is set explicitly (#858AAE, ~4.8:1 on the panels)
instead of the derived colour, which was nearly the panel colour and made
greyed-out fields look empty. The splash text is black in either theme:
its message is rich text, which takes the palette's text colour rather
than showMessage's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Filip LeonarskiandClaude Opus 5.5 committed 2026-09-26 18:04:42 +02:00
1 parent 9f369567f1
commit 22d4e94bd8
6 files changed
+74 -11

No files matched your search

+50 -10
View File
@@ -17,6 +17,9 @@
#include <QScreen>
#include <QApplication>
#include <QScrollArea>
#include <QScrollBar>
#include <QResizeEvent>
#include <utility>
#include <QSettings>
#include <QCloseEvent>
#include <QRandomGenerator>
@@ -163,6 +166,10 @@ JFJochViewerWindow::JFJochViewerWindow(QWidget *parent, bool dbus, const QString
splitDockWidget(inspectorDock, magnifierDock, Qt::Vertical);
resizeDocks({inspectorDock, magnifierDock}, {600, 400}, Qt::Vertical);
menuBar->AddDockEntry(magnifierDock, "Magnifier");
// Shown by anyone - the Window menu, a layout, a reset - it is no longer the window's to restore.
for (auto *dock: {inspectorDock, magnifierDock})
connect(dock, &QDockWidget::visibilityChanged, this,
[this, dock](bool visible) { if (visible) autoHiddenDocks.removeAll(dock); });
magnifierWindow = new JFJochHelperWindow(this);
magnifierWindow->setWindowTitle("Magnifier");
@@ -719,26 +726,59 @@ void JFJochViewerWindow::ResetAllSettings() {
menuBar->ResetFontZoom();
}
// The inspector's width bounds are a character count, not a pixel count, so the value column keeps
// up with the text - the View menu's font zoom or the desktop's scaling. At the default font this
// is the 450/600 px it has always been. The count comes from the application font rather than the
// scroll area's: on a font change the window is told before its children are.
// The inspector's width bounds are measured from its content: at least what its widest section needs
// (folded ones included, see CollapsibleSection) plus the scroll bar, so nothing is cut off, and a third
// more at most - wider only adds empty space next to the text, and takes it from the image. Measured
// rather than counted in characters, as they once were, because a character count that suits one
// platform's font is far too wide on another's (600 px for 400 px of content on macOS).
void JFJochViewerWindow::UpdateInspectorWidth() {
const int char_width = QFontMetrics(QApplication::font()).averageCharWidth();
inspectorScroll->setMinimumWidth(75 * char_width);
inspectorScroll->setMaximumWidth(100 * char_width);
const int need = inspectorScroll->widget()->minimumSizeHint().width()
+ inspectorScroll->verticalScrollBar()->sizeHint().width();
inspectorScroll->setMinimumWidth(need);
inspectorScroll->setMaximumWidth(need * 4 / 3);
}
void JFJochViewerWindow::changeEvent(QEvent *event) {
QMainWindow::changeEvent(event);
// The menu bar sets the application font while the window is still being built, so the inspector
// is not there yet on the first one.
// is not there yet on the first one. The window hears of a font change before its children do,
// so the content is measured once they have all taken the new font.
if (event->type() == QEvent::FontChange && inspectorScroll)
UpdateInspectorWidth();
QTimer::singleShot(0, this, &JFJochViewerWindow::UpdateInspectorWidth);
}
// The image comes first. When shrinking the window would leave it narrower than the inspector next
// to it, the inspector folds away, with the magnifier if it shares the inspector's column. They come
// back once the image would still be at least as wide as the inspector with it restored (plus a
// margin, so a window resized around the threshold does not flicker). Only a shrink folds: a panel
// the user reopens on a narrow window stays open, and one the user closed is never brought back.
void JFJochViewerWindow::resizeEvent(QResizeEvent *event) {
QMainWindow::resizeEvent(event);
if (!inspectorDock || !magnifierDock)
return;
const int image_width = centralWidget()->width();
const bool shrinking = event->oldSize().width() < 0 || event->size().width() < event->oldSize().width();
if (shrinking && inspectorDock->isVisible() && image_width < inspectorDock->width()) {
autoHiddenWidth = inspectorDock->width();
QList<QDockWidget *> docks = {inspectorDock};
if (magnifierDock->isVisible() && dockWidgetArea(magnifierDock) == dockWidgetArea(inspectorDock))
docks.append(magnifierDock);
for (auto *dock: docks) {
dock->hide();
if (!autoHiddenDocks.contains(dock))
autoHiddenDocks.append(dock);
}
} else if (!autoHiddenDocks.isEmpty() && image_width - autoHiddenWidth >= autoHiddenWidth + 40) {
for (auto *dock: std::exchange(autoHiddenDocks, {}))
dock->show();
}
}
void JFJochViewerWindow::closeEvent(QCloseEvent *event) {
// Persist the dock/toolbar arrangement and window geometry so the next launch resumes it.
// Persist the dock/toolbar arrangement and window geometry so the next launch resumes it. Docks
// folded away only for a narrow window are saved as open: the next start may have the room.
for (auto *dock: std::exchange(autoHiddenDocks, {}))
dock->show();
QSettings settings("PSI", "jfjoch_viewer");
settings.setValue("geometry", saveGeometry());
settings.setValue("windowState", saveState(kLayoutVersion));
+6
View File
@@ -68,6 +68,12 @@ private:
void ResetAllSettings();
void UpdateInspectorWidth();
// A narrow window folds the inspector away (with the magnifier, when it is docked in the same
// column) so the image keeps its room, and brings it back once the window is wide enough again.
QList<QDockWidget *> autoHiddenDocks; // hidden by the window, not by the user
int autoHiddenWidth = 0; // the inspector column's width when it was folded away
void resizeEvent(QResizeEvent *event) override;
// Both ways of opening a dataset from outside the file manager - the File menu and D-Bus -
// come through here, so the browser follows the same way for either.
void FollowInFileBrowser(const QString &filename);
+4
View File
@@ -66,6 +66,10 @@ namespace {
pal.setColor(QPalette::Link, QColor(0x8A, 0xB4, 0xF8));
pal.setColor(QPalette::PlaceholderText, QColor(0x80, 0x84, 0xA8));
SetTextColor(pal, DARK_TEXT);
// The derived disabled text is nearly the panel colour, so a greyed-out field looked empty.
// A muted lavender still reads (about 4.8:1 on the panels, against 13:1 for normal text).
for (auto role: {QPalette::WindowText, QPalette::Text, QPalette::ButtonText})
pal.setColor(QPalette::Disabled, role, QColor(0x85, 0x8A, 0xAE));
return pal;
}
+4 -1
View File
@@ -127,8 +127,11 @@ int main(int argc, char *argv[]) {
QSplashScreen *splash = new QSplashScreen(splash_pixmap);
splash->setWindowFlags(Qt::WindowStaysOnTopHint | Qt::SplashScreen);
QString version(QString::fromStdString(jfjoch_version()));
// The splash is white in either theme, so its text is black in either theme too. The message is
// rich text, and rich text takes its colour from the palette, not from showMessage's colour.
splash->showMessage(
"<b>Jungfrujoch image viewer</b><br/> Version " + version + "<br/>Paul Scherrer Institut",
"<span style=\"color: black;\"><b>Jungfrujoch image viewer</b><br/> Version " + version
+ "<br/>Paul Scherrer Institut</span>",
Qt::AlignBottom | Qt::AlignHCenter,
Qt::black
);
+8
View File
@@ -4,6 +4,7 @@
#include "CollapsibleSection.h"
#include "../ViewerTheme.h"
#include <algorithm>
#include <QToolButton>
#include <QVBoxLayout>
@@ -45,3 +46,10 @@ void CollapsibleSection::setContentLayout(QLayout *layout) {
void CollapsibleSection::setExpanded(bool on) {
header_->setChecked(on);
}
QSize CollapsibleSection::minimumSizeHint() const {
// A hidden widget does not count towards its parent's layout, so the folded content is measured here.
QSize s = QWidget::minimumSizeHint();
s.setWidth(std::max(s.width(), content_->minimumSizeHint().width()));
return s;
}
+2
View File
@@ -17,6 +17,8 @@ public:
explicit CollapsibleSection(const QString &title, QWidget *parent = nullptr);
void setContentLayout(QLayout *layout);
void setExpanded(bool on);
// As wide as the content needs even while folded, so a panel keeps its width when a section opens.
QSize minimumSizeHint() const override;
private:
QToolButton *header_;