From 67ad43a2b28eaba3185707fdbb6480f72141200d Mon Sep 17 00:00:00 2001 From: leonarski_f Date: Mon, 22 Jun 2026 15:34:06 +0200 Subject: [PATCH] viewer: palette theming, Original as a run row, stable plot colours Styling (now via the application palette in main(), not a stylesheet, so it applies to every widget incl. dialogs): - Entry fields and item views (line edits, spin boxes, combos, tables) are white when enabled and salmon (the GUI default) when disabled - so it's clear where you can type. Panels stay salmon (Window role). - Teal (#2a9d8f) accent via the Highlight role: selections and progress-bar chunks. Runs list / plots: - The original file is listed as a first "Original" run (Mode "HDF5"); double-click any run row to show it (replaces the "Show original" button). The currently shown run is bold (driven by snapshotsChanged). - Fix the colour "swap" when switching runs: the primary line is matched by the active-dataset pointer (not active_id_), so a run keeps its colour even while a datasetLoaded/runsChanged pair is mid-flight. Co-Authored-By: Claude Opus 4.8 --- viewer/JFJochViewerDatasetInfo.cpp | 14 ++-- viewer/JFJochViewerWindow.cpp | 3 +- viewer/JFJochViewerWindow.h | 27 +------ viewer/jfjoch_viewer.cpp | 14 ++++ viewer/windows/JFJochProcessingJobsWindow.cpp | 77 ++++++++++++++----- viewer/windows/JFJochProcessingJobsWindow.h | 5 +- 6 files changed, 87 insertions(+), 53 deletions(-) diff --git a/viewer/JFJochViewerDatasetInfo.cpp b/viewer/JFJochViewerDatasetInfo.cpp index 54657890..985c3f6f 100644 --- a/viewer/JFJochViewerDatasetInfo.cpp +++ b/viewer/JFJochViewerDatasetInfo.cpp @@ -231,15 +231,17 @@ void JFJochViewerDatasetInfo::UpdatePlot() { std::vector data = ExtractMetric(*dataset, val, one_over_d2); // One overlay line per other run, plus the in-progress live run. Each run keeps a stable colour - // tied to its position in the list, so colours don't swap when the active run changes. + // tied to its position in the list, so colours don't swap when the active run changes. The + // primary is matched by dataset pointer (not active_id_) so colour/name stay consistent even + // while a datasetLoaded/runsChanged pair is still arriving. std::vector overlays; QString primary_name; QColor primary_color; - int run_index = 0; - for (const auto &run: runs_) { - const QColor color = RunColor(run_index++); - if (run.id == active_id_) { primary_name = run.label; primary_color = color; continue; } - if (!run.dataset || run.dataset == dataset) continue; // never draw the active run twice + for (int i = 0; i < runs_.size(); i++) { + const auto &run = runs_[i]; + if (!run.dataset) continue; + const QColor color = RunColor(i); + if (run.dataset == dataset) { primary_name = run.label; primary_color = color; continue; } bool ignore = false; overlays.push_back({run.label, ExtractMetric(*run.dataset, val, ignore), color, XForRun(*run.dataset)}); } diff --git a/viewer/JFJochViewerWindow.cpp b/viewer/JFJochViewerWindow.cpp index e9667d93..d16a629e 100644 --- a/viewer/JFJochViewerWindow.cpp +++ b/viewer/JFJochViewerWindow.cpp @@ -54,7 +54,6 @@ JFJochViewerWindow::JFJochViewerWindow(QWidget *parent, bool dbus, const QString setDockOptions(dockOptions() & ~QMainWindow::DockOption::AllowTabbedDocks); - setStyleSheet(stylesheet); setWindowTitle("Jungfraujoch image viewer"); // Start large on a big display but fit within a laptop screen. const QRect avail = QGuiApplication::primaryScreen()->availableGeometry(); @@ -409,6 +408,8 @@ JFJochViewerWindow::JFJochViewerWindow(QWidget *parent, bool dbus, const QString processingJobsWindow, &JFJochProcessingJobsWindow::onHttpConnectionChanged); connect(reading_worker, &JFJochImageReadingWorker::fileOpened, processingJobsWindow, &JFJochProcessingJobsWindow::clearJobs); + connect(reading_worker, &JFJochImageReadingWorker::snapshotsChanged, processingJobsWindow, + [pw = processingJobsWindow](QStringList, QString active) { pw->setActiveRun(active); }); connect(reading_worker, &JFJochImageReadingWorker::runsChanged, this, [this](QVector runs, QString active) { lastRuns = std::move(runs); diff --git a/viewer/JFJochViewerWindow.h b/viewer/JFJochViewerWindow.h index 56537bca..e42b61cb 100644 --- a/viewer/JFJochViewerWindow.h +++ b/viewer/JFJochViewerWindow.h @@ -18,31 +18,8 @@ class QDockWidget; class JFJochViewerWindow : public QMainWindow { Q_OBJECT - // Salmon (GUI default) everywhere; entry widgets (line edits, spin boxes, combos, tables) turn - // white while focused/active. A teal accent (selection + progress chunks) adds some colour. - const QString stylesheet = R"( - background-color: rgb(255, 235, 230); - - QLineEdit, QPlainTextEdit, QTextEdit, QAbstractSpinBox, QComboBox, QAbstractItemView { - background-color: rgb(255, 235, 230); - selection-background-color: #2a9d8f; - selection-color: white; - } - QLineEdit:focus, QPlainTextEdit:focus, QTextEdit:focus, QAbstractSpinBox:focus, - QComboBox:focus, QAbstractItemView:focus { - background-color: white; - } - QProgressBar { - border: 1px solid #d9b3aa; - border-radius: 3px; - background-color: rgb(255, 235, 230); - text-align: center; - } - QProgressBar::chunk { - background-color: #2a9d8f; - border-radius: 2px; - } - )"; + // Theming is done via the application palette in main() (salmon panels, white entry fields, + // teal accent) so it applies consistently to every widget, including dialogs. public: explicit JFJochViewerWindow(QWidget *parent = nullptr, bool dbus = true, const QString &file = ""); ~JFJochViewerWindow() override; diff --git a/viewer/jfjoch_viewer.cpp b/viewer/jfjoch_viewer.cpp index f325822b..863a3e48 100644 --- a/viewer/jfjoch_viewer.cpp +++ b/viewer/jfjoch_viewer.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "JFJochViewerWindow.h" #include "../writer/HDF5Objects.h" @@ -16,6 +17,19 @@ int main(int argc, char *argv[]) { RegisterHDF5Filter(); QApplication app(argc, argv); + // Theme via the palette (robust across widgets): salmon panels, white entry fields that fall back + // to salmon when disabled, and a teal accent for selections and progress-bar chunks. + app.setStyle("Fusion"); + QPalette pal = app.palette(); + pal.setColor(QPalette::Window, QColor(255, 235, 230)); + pal.setColor(QPalette::Base, Qt::white); + pal.setColor(QPalette::AlternateBase, QColor(255, 245, 242)); + pal.setColor(QPalette::Button, QColor(255, 235, 230)); + pal.setColor(QPalette::Disabled, QPalette::Base, QColor(255, 235, 230)); + pal.setColor(QPalette::Highlight, QColor(0x2a, 0x9d, 0x8f)); + pal.setColor(QPalette::HighlightedText, Qt::white); + app.setPalette(pal); + QIcon appIcon(":/jfjoch.png"); app.setWindowIcon(appIcon); diff --git a/viewer/windows/JFJochProcessingJobsWindow.cpp b/viewer/windows/JFJochProcessingJobsWindow.cpp index d955f837..9e3ad20f 100644 --- a/viewer/windows/JFJochProcessingJobsWindow.cpp +++ b/viewer/windows/JFJochProcessingJobsWindow.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -35,6 +36,12 @@ namespace { const unsigned hc = std::thread::hardware_concurrency(); return hc == 0 ? 4 : static_cast(hc); } + + QTableWidgetItem *fixedItem(const QString &text) { // a non-editable cell + auto *cell = new QTableWidgetItem(text); + cell->setFlags(cell->flags() & ~Qt::ItemIsEditable); + return cell; + } } JFJochProcessingJobsWindow::JFJochProcessingJobsWindow(JFJochImageReadingWorker *worker, QWidget *parent) @@ -54,8 +61,7 @@ JFJochProcessingJobsWindow::JFJochProcessingJobsWindow(JFJochImageReadingWorker toolbar_->addAction("Cancel", this, &JFJochProcessingJobsWindow::cancelJob); toolbar_->addAction("Remove result", this, &JFJochProcessingJobsWindow::removeResult); toolbar_->addSeparator(); - toolbar_->addAction("View results", this, &JFJochProcessingJobsWindow::viewResults); - toolbar_->addAction("Show original", this, &JFJochProcessingJobsWindow::showOriginal); + toolbar_->addAction("Show selected", this, &JFJochProcessingJobsWindow::viewResults); table_ = new QTableWidget(0, COL_COUNT, this); table_->setMinimumHeight(60); // let the bottom dock shrink freely @@ -74,6 +80,13 @@ JFJochProcessingJobsWindow::JFJochProcessingJobsWindow(JFJochImageReadingWorker jobs_[row].label = item->text(); emit renameRun(jobs_[row].id, jobs_[row].label); }); + // Double-click a row (other than its editable Name) to show that run in the plots/image. + connect(table_, &QTableWidget::cellDoubleClicked, this, [this](int row, int col) { + if (col == COL_NAME) + return; // double-clicking Name edits the label + if (row >= 0 && row < static_cast(jobs_.size()) && jobs_[row].has_result) + emit activateSnapshot(jobs_[row].id); + }); auto *message = new QLabel("Dataset re-processing is currently available only in File mode.\n\n" "Open a stored HDF5 file to run processing jobs.", this); @@ -245,12 +258,6 @@ void JFJochProcessingJobsWindow::newJob() { table_->insertRow(row); jobs_.push_back(info); // jobs_[row] must exist before setItem(COL_NAME) fires itemChanged - auto fixed = [](const QString &text) { // a non-editable cell - auto *cell = new QTableWidgetItem(text); - cell->setFlags(cell->flags() & ~Qt::ItemIsEditable); - return cell; - }; - const QString range_text = (spec.start_image == 0 && spec.end_image == 0) ? QStringLiteral("all") : QStringLiteral("%1–%2").arg(spec.start_image) @@ -258,12 +265,12 @@ void JFJochProcessingJobsWindow::newJob() { const int expected = spec.end_image > spec.start_image ? spec.end_image - spec.start_image : 0; table_->setItem(row, COL_NAME, new QTableWidgetItem(label)); // editable (default flags) - table_->setItem(row, COL_STARTED, fixed(QDateTime::currentDateTime().toString("HH:mm:ss"))); - table_->setItem(row, COL_MODE, fixed(full ? "Full" : "AzInt")); - table_->setItem(row, COL_IMAGES, fixed(range_text)); - table_->setItem(row, COL_STATUS, fixed("queued")); - table_->setItem(row, COL_INDEX, fixed("-")); - table_->setItem(row, COL_CELL, fixed("-")); + table_->setItem(row, COL_STARTED, fixedItem(QDateTime::currentDateTime().toString("HH:mm:ss"))); + table_->setItem(row, COL_MODE, fixedItem(full ? "Full" : "AzInt")); + table_->setItem(row, COL_IMAGES, fixedItem(range_text)); + table_->setItem(row, COL_STATUS, fixedItem("queued")); + table_->setItem(row, COL_INDEX, fixedItem("-")); + table_->setItem(row, COL_CELL, fixedItem("-")); // A progress bar lives in the Status cell while the job runs; it shows the phase as text until // image processing starts, then " / " with the bar filling in the background. @@ -292,11 +299,13 @@ void JFJochProcessingJobsWindow::removeResult() { const int row = table_->currentRow(); if (row < 0 || row >= static_cast(jobs_.size())) return; + if (jobs_[row].id == "Original") + return; // the original file is always kept if (row == running_row_) { QMessageBox::information(this, "Processing", "Cancel the running job before removing it."); return; } - emit removeRun(jobs_[row].id); // drops the snapshot from the reader (no-op for Original) + emit removeRun(jobs_[row].id); // drops the snapshot from the reader QSignalBlocker block(table_); // row removal must not look like a rename table_->removeRow(row); @@ -316,6 +325,40 @@ void JFJochProcessingJobsWindow::clearJobs() { job_counter_ = 0; running_row_ = -1; running_bar_ = nullptr; + addOriginalRow(); +} + +void JFJochProcessingJobsWindow::addOriginalRow() { + // The file's own data, listed as the first run so it can be shown like any reprocessing run. + JobInfo info; + info.id = "Original"; + info.label = "Original"; + info.has_result = true; + + const int row = table_->rowCount(); + table_->insertRow(row); + jobs_.push_back(info); + + QSignalBlocker block(table_); + table_->setItem(row, COL_NAME, fixedItem("Original")); // reserved name, not editable + table_->setItem(row, COL_STATUS, fixedItem("")); + table_->setItem(row, COL_STARTED, fixedItem("—")); + table_->setItem(row, COL_MODE, fixedItem("HDF5")); + table_->setItem(row, COL_IMAGES, fixedItem("all")); + table_->setItem(row, COL_INDEX, fixedItem("-")); + table_->setItem(row, COL_CELL, fixedItem("-")); +} + +void JFJochProcessingJobsWindow::setActiveRun(QString active_id) { + QSignalBlocker block(table_); + for (int row = 0; row < static_cast(jobs_.size()); row++) { + auto *item = table_->item(row, COL_NAME); + if (!item) + continue; + QFont f = item->font(); + f.setBold(jobs_[row].id == active_id); + item->setFont(f); + } } void JFJochProcessingJobsWindow::viewResults() { @@ -329,10 +372,6 @@ void JFJochProcessingJobsWindow::viewResults() { emit activateSnapshot(jobs_[row].id); } -void JFJochProcessingJobsWindow::showOriginal() { - emit activateSnapshot("Original"); -} - void JFJochProcessingJobsWindow::setStatus(int row, const QString &text) { if (row >= 0 && row < table_->rowCount()) table_->item(row, COL_STATUS)->setText(text); diff --git a/viewer/windows/JFJochProcessingJobsWindow.h b/viewer/windows/JFJochProcessingJobsWindow.h index d47815e7..27aad0f7 100644 --- a/viewer/windows/JFJochProcessingJobsWindow.h +++ b/viewer/windows/JFJochProcessingJobsWindow.h @@ -37,14 +37,14 @@ signals: public slots: void onHttpConnectionChanged(bool connected, QString addr); - void clearJobs(); // reset the table on a new file + void clearJobs(); // reset the table on a new file (re-adds the Original row) + void setActiveRun(QString active_id); // bold the row of the run currently shown in the plots private slots: void newJob(); void cancelJob(); void removeResult(); void viewResults(); - void showOriginal(); void onPhase(QString phase); void onProgress(quint64 done, quint64 total); void onFinished(ProcessResult result); @@ -72,6 +72,7 @@ private: int askJob(const ReprocessingInputs &inputs, JobSpec &spec); ProcessConfig buildConfig(const JobSpec &spec, const ReprocessingInputs &inputs) const; void setStatus(int row, const QString &text); + void addOriginalRow(); // the file's own data as the first, always-present run JFJochImageReadingWorker *worker_; JFJochProcessController *controller_;