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 <noreply@anthropic.com>
This commit is contained in:
@@ -231,15 +231,17 @@ void JFJochViewerDatasetInfo::UpdatePlot() {
|
||||
std::vector<float> 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<JFJochDatasetInfoChartView::NamedSeries> 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)});
|
||||
}
|
||||
|
||||
@@ -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<RunData> runs, QString active) {
|
||||
lastRuns = std::move(runs);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <QCommandLineOption>
|
||||
#include <QSplashScreen>
|
||||
#include <QTimer>
|
||||
#include <QPalette>
|
||||
|
||||
#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);
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <QComboBox>
|
||||
#include <QDateTime>
|
||||
#include <QDialog>
|
||||
#include <QFont>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QFileInfo>
|
||||
#include <QFormLayout>
|
||||
@@ -35,6 +36,12 @@ namespace {
|
||||
const unsigned hc = std::thread::hardware_concurrency();
|
||||
return hc == 0 ? 4 : static_cast<int>(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<int>(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 "<done> / <expected>" 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<int>(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<int>(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);
|
||||
|
||||
@@ -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_;
|
||||
|
||||
Reference in New Issue
Block a user