From 03ac43da69cf6df0a53a6df77d89ca9fbf735617 Mon Sep 17 00:00:00 2001 From: leonarski_f Date: Tue, 23 Jun 2026 10:19:56 +0200 Subject: [PATCH] =?UTF-8?q?viewer:=20panel=20polish=20=E2=80=94=20accordio?= =?UTF-8?q?ns,=20shared=20geometry,=20detector=20tilt,=20SG=20name?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Declutter the side and settings panels (review items a–f): - New CollapsibleSection widget (slim navy header + coral rule + chevron). - Inspector: Image features / Resolution rings / ROI are now collapsible and start folded (ROI auto-expands when ROIs change); drop the Data-analysis and Powder-calibration sections (they live in the hero buttons and settings dock). - Settings dock: move Geometry to a shared section above the MX/AzInt toggle (both communities need it); add Detector tilt (PONI rot1/rot2, deg) and rename "Beam center" -> "Beam origin" with PONI/XDS tooltips; show the space-group number and resolved Hermann–Mauguin symbol on one line; wrap sections in accordions, anchored top with a bottom stretch so expanding one does not shift the others. - Toolbar: drop the redundant "Image number" label. Co-Authored-By: Claude Opus 4.8 --- viewer/CMakeLists.txt | 2 + viewer/JFJochViewerSidePanel.cpp | 51 +++---- viewer/JFJochViewerSidePanel.h | 3 + viewer/toolbar/JFJochViewerToolbarImage.cpp | 3 - viewer/widgets/CollapsibleSection.cpp | 44 ++++++ viewer/widgets/CollapsibleSection.h | 23 +++ viewer/widgets/JFJochViewerSettingsDock.cpp | 146 ++++++++++++++------ viewer/widgets/JFJochViewerSettingsDock.h | 9 +- 8 files changed, 209 insertions(+), 72 deletions(-) create mode 100644 viewer/widgets/CollapsibleSection.cpp create mode 100644 viewer/widgets/CollapsibleSection.h diff --git a/viewer/CMakeLists.txt b/viewer/CMakeLists.txt index 105671d3..85ed9d89 100644 --- a/viewer/CMakeLists.txt +++ b/viewer/CMakeLists.txt @@ -62,6 +62,8 @@ ADD_EXECUTABLE(jfjoch_viewer jfjoch_viewer.cpp JFJochViewerWindow.cpp JFJochView widgets/JFJochViewerSidePanelChart.h widgets/JFJochViewerSettingsDock.cpp widgets/JFJochViewerSettingsDock.h + widgets/CollapsibleSection.cpp + widgets/CollapsibleSection.h windows/JFJochViewerSpotListWindow.cpp windows/JFJochViewerSpotListWindow.h image_viewer/JFJochAzIntImage.cpp diff --git a/viewer/JFJochViewerSidePanel.cpp b/viewer/JFJochViewerSidePanel.cpp index 30ce9694..8e36d32f 100644 --- a/viewer/JFJochViewerSidePanel.cpp +++ b/viewer/JFJochViewerSidePanel.cpp @@ -8,8 +8,8 @@ #include "JFJochViewerSidePanel.h" #include "widgets/TitleLabel.h" +#include "widgets/CollapsibleSection.h" #include "widgets/JFJochViewerImageStatistics.h" -#include "widgets/PowderCalibrationWidget.h" JFJochViewerSidePanel::JFJochViewerSidePanel(QWidget *parent) : QWidget(parent) { @@ -21,7 +21,7 @@ JFJochViewerSidePanel::JFJochViewerSidePanel(QWidget *parent) : QWidget(parent) layout->addWidget(stats); connect(this, &JFJochViewerSidePanel::imageLoaded, stats, &JFJochViewerImageStatistics::loadImage); - layout->addWidget(new TitleLabel("Image features", this)); + auto *featuresSection = new CollapsibleSection("Image features", this); auto spotToggleCheckBox = new QCheckBox("Show spots", this); spotToggleCheckBox->setCheckState(Qt::CheckState::Unchecked); @@ -84,9 +84,13 @@ JFJochViewerSidePanel::JFJochViewerSidePanel(QWidget *parent) : QWidget(parent) image_feature_grid->addWidget(colorSelectButton, 3, 0); image_feature_grid->addWidget(spotColorSelectButton, 3, 1); - layout->addLayout(image_feature_grid); + featuresSection->setContentLayout(image_feature_grid); + featuresSection->setExpanded(false); + layout->addWidget(featuresSection); - layout->addWidget(new TitleLabel("Resolution rings", this)); + auto *ringsSection = new CollapsibleSection("Resolution rings", this); + auto *ringsLayout = new QVBoxLayout(); + ringsLayout->setContentsMargins(0, 0, 0, 0); res_rings = new ResolutionRingWidget(this); connect(res_rings, &ResolutionRingWidget::resRingsSet, [this] (const QVector &v) { @@ -97,12 +101,18 @@ JFJochViewerSidePanel::JFJochViewerSidePanel(QWidget *parent) : QWidget(parent) emit ringModeSet(mode); }); - layout->addWidget(res_rings); + ringsLayout->addWidget(res_rings); + ringsSection->setContentLayout(ringsLayout); + ringsSection->setExpanded(false); + layout->addWidget(ringsSection); // The per-image statistics plot now lives in the dataset-info dock (its "Per-image" toggle), - // so it is no longer duplicated here. + // and powder calibration / full analysis live in the settings dock and the hero buttons, + // so they are no longer duplicated here. - layout->addWidget(new TitleLabel("ROI", this)); + roiSection = new CollapsibleSection("ROI", this); + auto *roiLayout = new QVBoxLayout(); + roiLayout->setContentsMargins(0, 0, 0, 0); auto roiLabelsCheckBox = new QCheckBox("Show labels", this); roiLabelsCheckBox->setCheckState(Qt::CheckState::Unchecked); @@ -115,35 +125,20 @@ JFJochViewerSidePanel::JFJochViewerSidePanel(QWidget *parent) : QWidget(parent) auto roiToggleRow = new QHBoxLayout(); roiToggleRow->addWidget(roiLabelsCheckBox); roiToggleRow->addWidget(roiFillCheckBox); - layout->addLayout(roiToggleRow); + roiLayout->addLayout(roiToggleRow); roi_list = new JFJochViewerROIList(this); - layout->addWidget(roi_list); + roiLayout->addWidget(roi_list); connect(this, &JFJochViewerSidePanel::imageLoaded, roi_list, &JFJochViewerROIList::loadImage); connect(roi_list, &JFJochViewerROIList::roisChanged, this, &JFJochViewerSidePanel::roisChanged); connect(roi_list, &JFJochViewerROIList::selectedROIChanged, this, &JFJochViewerSidePanel::selectedROIChanged); connect(roi_list, &JFJochViewerROIList::downloadROIs, this, &JFJochViewerSidePanel::downloadROIs); connect(roi_list, &JFJochViewerROIList::uploadROIs, this, &JFJochViewerSidePanel::uploadROIs); connect(roi_list, &JFJochViewerROIList::maskFromROI, this, &JFJochViewerSidePanel::maskFromROI); - - layout->addWidget(new TitleLabel("Data analysis", this)); - - auto analyzeButton = new QPushButton("Full analysis", this); - connect(analyzeButton, &QPushButton::clicked,[this] {emit analyze();}); - layout->addWidget(analyzeButton); - - // Calibrant selection combo box - layout->addWidget(new TitleLabel("Powder geometry calibration", this)); - - auto power_calibration = new PowderCalibrationWidget(this); - layout->addWidget(power_calibration); - - connect(this, &JFJochViewerSidePanel::imageLoaded, power_calibration, &PowderCalibrationWidget::loadImage); - connect(power_calibration, &PowderCalibrationWidget::ringsFromCalibration, res_rings, &ResolutionRingWidget::setRings); - connect(power_calibration, &PowderCalibrationWidget::findBeamCenter, - [this] (const UnitCell& input, bool guess) { - emit findBeamCenter(input, guess); - }); + connect(roi_list, &JFJochViewerROIList::roisChanged, this, [this] { roiSection->setExpanded(true); }); + roiSection->setContentLayout(roiLayout); + roiSection->setExpanded(false); + layout->addWidget(roiSection); layout->addStretch(); setLayout(layout); // Set the layout to the widget diff --git a/viewer/JFJochViewerSidePanel.h b/viewer/JFJochViewerSidePanel.h index 69c98279..4db1e4e7 100644 --- a/viewer/JFJochViewerSidePanel.h +++ b/viewer/JFJochViewerSidePanel.h @@ -13,11 +13,14 @@ #include "charts/JFJochSimpleChartView.h" #include "widgets/ResolutionRingWidget.h" +class CollapsibleSection; + class JFJochViewerSidePanel : public QWidget { Q_OBJECT JFJochViewerROIList *roi_list = nullptr; ResolutionRingWidget *res_rings = nullptr; + CollapsibleSection *roiSection = nullptr; // auto-expands when ROIs change signals: void showSpots(bool input); diff --git a/viewer/toolbar/JFJochViewerToolbarImage.cpp b/viewer/toolbar/JFJochViewerToolbarImage.cpp index 74244ad6..497550d9 100644 --- a/viewer/toolbar/JFJochViewerToolbarImage.cpp +++ b/viewer/toolbar/JFJochViewerToolbarImage.cpp @@ -8,9 +8,6 @@ JFJochViewerToolbarImage::JFJochViewerToolbarImage(QWidget *parent) : QToolBar(parent) { image_count_in_dataset = 0; - auto title = new QLabel("Image number ", this); - addWidget(title); - leftmost_button = new QPushButton(this); leftmost_button->setIcon(style()->standardIcon(QStyle::SP_MediaSkipBackward)); leftmost_button->setToolTip("First image"); diff --git a/viewer/widgets/CollapsibleSection.cpp b/viewer/widgets/CollapsibleSection.cpp new file mode 100644 index 00000000..86748d00 --- /dev/null +++ b/viewer/widgets/CollapsibleSection.cpp @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute +// SPDX-License-Identifier: GPL-3.0-only + +#include "CollapsibleSection.h" + +#include +#include + +CollapsibleSection::CollapsibleSection(const QString &title, QWidget *parent) : QWidget(parent) { + auto *v = new QVBoxLayout(this); + v->setContentsMargins(0, 0, 0, 0); + v->setSpacing(0); + + header_ = new QToolButton(this); + header_->setText(title); + header_->setCheckable(true); + header_->setChecked(true); + header_->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + header_->setArrowType(Qt::DownArrow); + header_->setCursor(Qt::PointingHandCursor); + header_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + header_->setStyleSheet( + "QToolButton { color:#1F3A5F; font-weight:bold; text-align:left;" + " padding:3px 6px 3px 6px; border:none;" + " border-left:3px solid #FA7268; border-bottom:1px solid #FA7268; background:transparent; }"); + + content_ = new QWidget(this); + + v->addWidget(header_); + v->addWidget(content_); + + connect(header_, &QToolButton::toggled, this, [this](bool on) { + content_->setVisible(on); + header_->setArrowType(on ? Qt::DownArrow : Qt::RightArrow); + }); +} + +void CollapsibleSection::setContentLayout(QLayout *layout) { + content_->setLayout(layout); +} + +void CollapsibleSection::setExpanded(bool on) { + header_->setChecked(on); +} diff --git a/viewer/widgets/CollapsibleSection.h b/viewer/widgets/CollapsibleSection.h new file mode 100644 index 00000000..5bbf746a --- /dev/null +++ b/viewer/widgets/CollapsibleSection.h @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute +// SPDX-License-Identifier: GPL-3.0-only + +#pragma once + +#include + +class QToolButton; +class QLayout; + +// A slim collapsible section: a clickable navy header with a coral accent rule and a chevron, +// over a content area that folds away. Used to keep the side and settings panels compact. +class CollapsibleSection : public QWidget { + Q_OBJECT +public: + explicit CollapsibleSection(const QString &title, QWidget *parent = nullptr); + void setContentLayout(QLayout *layout); + void setExpanded(bool on); + +private: + QToolButton *header_; + QWidget *content_; +}; diff --git a/viewer/widgets/JFJochViewerSettingsDock.cpp b/viewer/widgets/JFJochViewerSettingsDock.cpp index f824bb00..f130c717 100644 --- a/viewer/widgets/JFJochViewerSettingsDock.cpp +++ b/viewer/widgets/JFJochViewerSettingsDock.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include #include #include @@ -19,7 +18,9 @@ #include "SliderPlusBox.h" #include "NumberLineEdit.h" #include "PowderCalibrationWidget.h" -#include "TitleLabel.h" +#include "CollapsibleSection.h" +#include "../../common/JFJochMath.h" +#include "../../gemmi_gph/gemmi/symmetry.hpp" JFJochViewerSettingsDock::JFJochViewerSettingsDock(const SpotFindingSettings &spot, const IndexingSettings &indexing, @@ -50,39 +51,63 @@ JFJochViewerSettingsDock::JFJochViewerSettingsDock(const SpotFindingSettings &sp stack->addWidget(BuildAzIntPage()); connect(group, &QButtonGroup::idClicked, stack, &QStackedWidget::setCurrentIndex); + // Geometry is common to both communities, so it lives above the toggle rather than per page. auto *layout = new QVBoxLayout(this); layout->addLayout(toggleRow); + layout->addWidget(BuildGeometrySection()); layout->addWidget(stack); layout->addStretch(); } +QWidget *JFJochViewerSettingsDock::BuildGeometrySection() { + auto *section = new CollapsibleSection("Geometry", this); + auto *geom = new QFormLayout(); + + energy_ = new NumberLineEdit(1.0, 200.0, 12.4, 4, "keV", this); + distance_ = new NumberLineEdit(10.0, 5000.0, 100.0, 2, "mm", this); + beamX_ = new NumberLineEdit(-20000.0, 20000.0, 0.0, 1, "px", this); + beamY_ = new NumberLineEdit(-20000.0, 20000.0, 0.0, 1, "px", this); + rot1_ = new NumberLineEdit(-180.0, 180.0, 0.0, 3, "°", this); + rot2_ = new NumberLineEdit(-180.0, 180.0, 0.0, 3, "°", this); + + // The detector origin is the PONI (PyFAI) point; in MX (XDS-style) terms it is the beam origin, + // which coincides with the beam center only when the detector is untilted. + const QString beamTip = + "Beam origin (XDS convention): the PONI point where the un-tilted beam meets the detector. " + "Equals the beam center only when the detector tilt is zero."; + beamX_->setToolTip(beamTip); + beamY_->setToolTip(beamTip); + const QString tiltTip = "Detector tilt about the two in-plane axes (PyFAI PONI rot1 / rot2), in degrees."; + rot1_->setToolTip(tiltTip); + rot2_->setToolTip(tiltTip); + + auto *beam = new QHBoxLayout(); + beam->addWidget(beamX_); + beam->addWidget(beamY_); + auto *tilt = new QHBoxLayout(); + tilt->addWidget(rot1_); + tilt->addWidget(rot2_); + + geom->addRow("Photon energy", energy_); + geom->addRow("Detector distance", distance_); + geom->addRow("Beam origin", beam); + geom->addRow("Detector tilt", tilt); + + for (auto *f : {energy_, distance_, beamX_, beamY_, rot1_, rot2_}) + connect(f, &NumberLineEdit::newValue, this, [this] { EmitExperiment(); }); + + section->setContentLayout(geom); + return section; +} + QWidget *JFJochViewerSettingsDock::BuildMXPage() { auto *page = new QWidget(this); auto *layout = new QVBoxLayout(page); layout->setContentsMargins(0, 0, 0, 0); - // --- Geometry --- - layout->addWidget(new TitleLabel("Geometry", page)); - auto *geomGroup = new QGroupBox(page); - auto *geom = new QFormLayout(geomGroup); - energy_ = new NumberLineEdit(1.0, 200.0, 12.4, 4, "keV", page); - distance_ = new NumberLineEdit(10.0, 5000.0, 100.0, 2, "mm", page); - beamX_ = new NumberLineEdit(0.0, 20000.0, 0.0, 1, "px", page); - beamY_ = new NumberLineEdit(0.0, 20000.0, 0.0, 1, "px", page); - geom->addRow("Photon energy", energy_); - geom->addRow("Detector distance", distance_); - auto *beam = new QHBoxLayout(); - beam->addWidget(beamX_); - beam->addWidget(beamY_); - geom->addRow("Beam center", beam); - layout->addWidget(geomGroup); - for (auto *f : {energy_, distance_, beamX_, beamY_}) - connect(f, &NumberLineEdit::newValue, this, [this] { EmitExperiment(); }); - // --- Unit cell + space group (new: no input existed before) --- - layout->addWidget(new TitleLabel("Unit cell", page)); - auto *cellGroup = new QGroupBox(page); - auto *cell = new QFormLayout(cellGroup); + auto *cellSection = new CollapsibleSection("Unit cell", page); + auto *cell = new QFormLayout(); cellKnown_ = new QCheckBox("Known unit cell", page); cell->addRow("", cellKnown_); cellA_ = new NumberLineEdit(1.0, 2000.0, 79.0, 3, "Å", page); @@ -92,14 +117,21 @@ QWidget *JFJochViewerSettingsDock::BuildMXPage() { cellBeta_ = new NumberLineEdit(1.0, 179.0, 90.0, 2, "°", page); cellGamma_ = new NumberLineEdit(1.0, 179.0, 90.0, 2, "°", page); spaceGroup_ = new NumberLineEdit(0.0, 230.0, 0.0, 0, "", page); + spaceGroup_->setToolTip("Space group number (1–230); 0 = unset."); + spaceGroupName_ = new QLabel("—", page); auto *abc = new QHBoxLayout(); abc->addWidget(cellA_); abc->addWidget(cellB_); abc->addWidget(cellC_); auto *angles = new QHBoxLayout(); angles->addWidget(cellAlpha_); angles->addWidget(cellBeta_); angles->addWidget(cellGamma_); + // Number and symbol split the line evenly, like the beam-origin row. + auto *sgRow = new QHBoxLayout(); + sgRow->addWidget(spaceGroup_, 1); + sgRow->addWidget(spaceGroupName_, 1); cell->addRow("a, b, c", abc); cell->addRow("α, β, γ", angles); - cell->addRow("Space group", spaceGroup_); - layout->addWidget(cellGroup); + cell->addRow("Space group", sgRow); + cellSection->setContentLayout(cell); + layout->addWidget(cellSection); auto enableCellFields = [this](bool on) { for (auto *f : {cellA_, cellB_, cellC_, cellAlpha_, cellBeta_, cellGamma_, spaceGroup_}) @@ -108,15 +140,19 @@ QWidget *JFJochViewerSettingsDock::BuildMXPage() { enableCellFields(false); connect(cellKnown_, &QCheckBox::toggled, this, [this, enableCellFields](bool on) { enableCellFields(on); + UpdateSpaceGroupName(); EmitExperiment(); }); - for (auto *f : {cellA_, cellB_, cellC_, cellAlpha_, cellBeta_, cellGamma_, spaceGroup_}) + for (auto *f : {cellA_, cellB_, cellC_, cellAlpha_, cellBeta_, cellGamma_}) connect(f, &NumberLineEdit::newValue, this, [this] { EmitExperiment(); }); + connect(spaceGroup_, &NumberLineEdit::newValue, this, [this] { + UpdateSpaceGroupName(); + EmitExperiment(); + }); // --- Spot finding --- - layout->addWidget(new TitleLabel("Spot finding", page)); - auto *spotGroup = new QGroupBox(page); - auto *spot = new QFormLayout(spotGroup); + auto *spotSection = new CollapsibleSection("Spot finding", page); + auto *spot = new QFormLayout(); auto *snr = new SliderPlusBox(1.0, 10.0, 0.1, 1, page); snr->setValue(spot_.signal_to_noise_threshold); auto *count = new SliderPlusBox(0.0, 100.0, 1.0, 0, page); @@ -126,7 +162,8 @@ QWidget *JFJochViewerSettingsDock::BuildMXPage() { spot->addRow("Signal/noise", snr); spot->addRow("Photon count", count); spot->addRow("Min pixels/spot", minPix); - layout->addWidget(spotGroup); + spotSection->setContentLayout(spot); + layout->addWidget(spotSection); connect(snr, &SliderPlusBox::valueChanged, this, [this](double v) { spot_.signal_to_noise_threshold = static_cast(v); EmitSpotFinding(); }); connect(count, &SliderPlusBox::valueChanged, this, [this](double v) { @@ -135,9 +172,8 @@ QWidget *JFJochViewerSettingsDock::BuildMXPage() { spot_.min_pix_per_spot = std::lround(v); EmitSpotFinding(); }); // --- Indexing --- - layout->addWidget(new TitleLabel("Indexing", page)); - auto *idxGroup = new QGroupBox(page); - auto *idx = new QFormLayout(idxGroup); + auto *idxSection = new CollapsibleSection("Indexing", page); + auto *idx = new QFormLayout(); auto *algo = new QComboBox(page); algo->addItem("Auto", static_cast(IndexingAlgorithmEnum::Auto)); algo->addItem("FFBIDX (GPU, known cell)", static_cast(IndexingAlgorithmEnum::FFBIDX)); @@ -153,7 +189,8 @@ QWidget *JFJochViewerSettingsDock::BuildMXPage() { refine->setCurrentIndex(refine->findData(static_cast(indexing_.GetGeomRefinementAlgorithm()))); idx->addRow("Algorithm", algo); idx->addRow("Refinement", refine); - layout->addWidget(idxGroup); + idxSection->setContentLayout(idx); + layout->addWidget(idxSection); connect(algo, &QComboBox::currentIndexChanged, this, [this, algo] { indexing_.Algorithm(static_cast(algo->currentData().toInt())); EmitSpotFinding(); @@ -163,6 +200,7 @@ QWidget *JFJochViewerSettingsDock::BuildMXPage() { EmitSpotFinding(); }); + layout->addStretch(); // anchor sections to the top so expanding an accordion grows downward return page; } @@ -171,9 +209,8 @@ QWidget *JFJochViewerSettingsDock::BuildAzIntPage() { auto *layout = new QVBoxLayout(page); layout->setContentsMargins(0, 0, 0, 0); - layout->addWidget(new TitleLabel("Azimuthal integration", page)); - auto *azGroup = new QGroupBox(page); - auto *az = new QFormLayout(azGroup); + auto *azSection = new CollapsibleSection("Azimuthal integration", page); + auto *az = new QFormLayout(); auto *lowQ = new SliderPlusBox(1e-5, 10.0, 0.001, 4, page); lowQ->setValue(azint_.GetLowQ_recipA()); auto *highQ = new SliderPlusBox(2e-5, 10.0, 0.001, 4, page); @@ -188,7 +225,8 @@ QWidget *JFJochViewerSettingsDock::BuildAzIntPage() { az->addRow("High Q [Å⁻¹]", highQ); az->addRow("Q spacing [Å⁻¹]", spacing); az->addRow("Azimuthal bins", azimBins); - layout->addWidget(azGroup); + azSection->setContentLayout(az); + layout->addWidget(azSection); auto emitAz = [=, this] { azint_.QRange_recipA(static_cast(lowQ->value()), static_cast(highQ->value())); @@ -202,12 +240,17 @@ QWidget *JFJochViewerSettingsDock::BuildAzIntPage() { connect(azimBins, &QComboBox::currentIndexChanged, this, [emitAz] { emitAz(); }); // Powder calibration (calibrant rings + geometry refinement) - reuse the existing widget. - layout->addWidget(new TitleLabel("Powder calibration", page)); + auto *powderSection = new CollapsibleSection("Powder calibration", page); + auto *powderLayout = new QVBoxLayout(); + powderLayout->setContentsMargins(0, 0, 0, 0); powder_ = new PowderCalibrationWidget(page); connect(powder_, &PowderCalibrationWidget::findBeamCenter, this, &JFJochViewerSettingsDock::findBeamCenter); connect(powder_, &PowderCalibrationWidget::ringsFromCalibration, this, &JFJochViewerSettingsDock::ringsFromCalibration); - layout->addWidget(powder_); + powderLayout->addWidget(powder_); + powderSection->setContentLayout(powderLayout); + layout->addWidget(powderSection); + layout->addStretch(); // anchor sections to the top so expanding an accordion grows downward return page; } @@ -222,6 +265,8 @@ void JFJochViewerSettingsDock::EmitExperiment() { experiment_.DetectorDistance_mm(static_cast(distance_->value())); experiment_.BeamX_pxl(static_cast(beamX_->value())); experiment_.BeamY_pxl(static_cast(beamY_->value())); + experiment_.PoniRot1_rad(static_cast(rot1_->value() * PI / 180.0)); + experiment_.PoniRot2_rad(static_cast(rot2_->value() * PI / 180.0)); if (cellKnown_->isChecked()) { experiment_.SetUnitCell(UnitCell{ static_cast(cellA_->value()), static_cast(cellB_->value()), @@ -243,6 +288,8 @@ void JFJochViewerSettingsDock::RefreshGeometryFields() { distance_->setValue(experiment_.GetDetectorDistance_mm()); beamX_->setValue(experiment_.GetBeamX_pxl()); beamY_->setValue(experiment_.GetBeamY_pxl()); + rot1_->setValue(experiment_.GetPoniRot1_rad() * 180.0 / PI); + rot2_->setValue(experiment_.GetPoniRot2_rad() * 180.0 / PI); const auto cell = experiment_.GetUnitCell(); QSignalBlocker blockKnown(cellKnown_); @@ -254,6 +301,25 @@ void JFJochViewerSettingsDock::RefreshGeometryFields() { cellAlpha_->setValue(cell->alpha); cellBeta_->setValue(cell->beta); cellGamma_->setValue(cell->gamma); spaceGroup_->setValue(experiment_.GetSpaceGroupNumber().value_or(0)); } + UpdateSpaceGroupName(); +} + +void JFJochViewerSettingsDock::UpdateSpaceGroupName() { + if (!cellKnown_->isChecked()) { + spaceGroupName_->setText("—"); + return; + } + const int n = static_cast(std::lround(spaceGroup_->value())); + if (n >= 1 && n <= 230) { + try { + const auto &sg = gemmi::get_spacegroup_by_number(n); // Hermann–Mauguin short symbol + spaceGroupName_->setText(QString::fromStdString(sg.short_name())); + } catch (...) { + spaceGroupName_->setText("invalid"); + } + } else { + spaceGroupName_->setText(n == 0 ? "—" : "invalid"); + } } void JFJochViewerSettingsDock::datasetLoaded(std::shared_ptr dataset) { diff --git a/viewer/widgets/JFJochViewerSettingsDock.h b/viewer/widgets/JFJochViewerSettingsDock.h index 4794df21..c41a67bf 100644 --- a/viewer/widgets/JFJochViewerSettingsDock.h +++ b/viewer/widgets/JFJochViewerSettingsDock.h @@ -16,6 +16,7 @@ class QStackedWidget; class QCheckBox; class QComboBox; +class QLabel; class SliderPlusBox; class NumberLineEdit; class PowderCalibrationWidget; @@ -48,21 +49,27 @@ private: bool have_experiment_ = false; // geometry edits only take effect once a dataset is loaded int64_t max_spots_ = 1000; - // MX geometry + crystal fields + // Geometry fields (shared by both MX and AzInt, since both need the same geometry) NumberLineEdit *energy_ = nullptr; NumberLineEdit *distance_ = nullptr; NumberLineEdit *beamX_ = nullptr; NumberLineEdit *beamY_ = nullptr; + NumberLineEdit *rot1_ = nullptr; // detector tilt (PONI rot1), deg + NumberLineEdit *rot2_ = nullptr; // detector tilt (PONI rot2), deg + // Crystal fields QCheckBox *cellKnown_ = nullptr; NumberLineEdit *cellA_ = nullptr, *cellB_ = nullptr, *cellC_ = nullptr; NumberLineEdit *cellAlpha_ = nullptr, *cellBeta_ = nullptr, *cellGamma_ = nullptr; NumberLineEdit *spaceGroup_ = nullptr; + QLabel *spaceGroupName_ = nullptr; // resolved Hermann–Mauguin symbol PowderCalibrationWidget *powder_ = nullptr; + QWidget *BuildGeometrySection(); QWidget *BuildMXPage(); QWidget *BuildAzIntPage(); void EmitSpotFinding(); void EmitExperiment(); void RefreshGeometryFields(); + void UpdateSpaceGroupName(); };