Skip to content

Commit

Permalink
feat: use QRadioButtons to select z_plan (#385)
Browse files Browse the repository at this point in the history
* feat: use radiobuttons

* tweak

* fix: fix setMode

---------

Co-authored-by: Talley Lambert <[email protected]>
  • Loading branch information
fdrgsp and tlambert03 authored Nov 12, 2024
1 parent 33c1350 commit 1cda2b7
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 65 deletions.
2 changes: 1 addition & 1 deletion src/pymmcore_widgets/mda/_core_z.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def __init__(

def setMode(
self,
mode: Mode | Literal["top_bottom", "range_around", "above_below"] | None = None,
mode: Mode | Literal["top_bottom", "range_around", "above_below"],
) -> None:
super().setMode(mode)
self.bottom_btn.setVisible(self._mode == Mode.TOP_BOTTOM)
Expand Down
117 changes: 55 additions & 62 deletions src/pymmcore_widgets/useq_widgets/_z.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
from __future__ import annotations

import enum
from typing import TYPE_CHECKING, Final, Literal, cast
from typing import Final, Literal

import useq
from fonticon_mdi6 import MDI6
from qtpy.QtCore import Qt, Signal
from qtpy.QtWidgets import (
QAbstractButton,
QButtonGroup,
QDoubleSpinBox,
QGridLayout,
Expand All @@ -15,7 +16,6 @@
QPushButton,
QRadioButton,
QSpinBox,
QToolButton,
QVBoxLayout,
QWidget,
)
Expand All @@ -24,11 +24,6 @@

from pymmcore_widgets._util import SeparatorWidget

if TYPE_CHECKING:
from PyQt6.QtGui import QAction, QActionGroup
else:
from qtpy.QtGui import QAction, QActionGroup


class Mode(enum.Enum):
"""Recognized ZPlanWidget modes."""
Expand Down Expand Up @@ -67,52 +62,44 @@ def __init__(self, parent: QWidget | None = None) -> None:
# to store a "suggested" step size
self._suggested: float | None = None

# #################### Mode Buttons ####################

# ------------------- actions ----------
self._mode: Mode = Mode.TOP_BOTTOM

self._mode_top_bot = QAction(
icon(MDI6.arrow_expand_vertical, scale_factor=1), "Mark top and bottom."
)
self._mode_top_bot.setCheckable(True)
self._mode_top_bot.setData(Mode.TOP_BOTTOM)
self._mode_top_bot.triggered.connect(self.setMode)
# #################### Mode Buttons ####################

self._mode_range = QAction(
icon(MDI6.arrow_split_horizontal, scale_factor=1),
"Range symmetric around reference.",
self._btn_top_bot = QRadioButton("Top/Bottom")
self._btn_top_bot.setFocusPolicy(Qt.FocusPolicy.NoFocus)
self._btn_top_bot.setIcon(icon(MDI6.arrow_expand_vertical))
self._btn_top_bot.setToolTip("Mark top and bottom.")
self._btn_range = QRadioButton("Range Around (Symmetric)")
self._btn_range.setFocusPolicy(Qt.FocusPolicy.NoFocus)
self._btn_range.setIcon(icon(MDI6.arrow_split_horizontal))
self._btn_range.setToolTip("Range symmetric around reference.")
self._button_above_below = QRadioButton("Range Asymmetric")
self._button_above_below.setFocusPolicy(Qt.FocusPolicy.NoFocus)
self._button_above_below.setIcon(icon(MDI6.arrow_expand_up))
self._button_above_below.setToolTip(
"Range asymmetrically above/below reference."
)
self._mode_range.setCheckable(True)
self._mode_range.setData(Mode.RANGE_AROUND)
self._mode_range.triggered.connect(self.setMode)

self._mode_above_below = QAction(
icon(MDI6.arrow_expand_up, scale_factor=1),
"Range asymmetrically above/below reference.",
)
self._mode_above_below.setCheckable(True)
self._mode_above_below.setData(Mode.ABOVE_BELOW)
self._mode_above_below.triggered.connect(self.setMode)

self._mode_group = QActionGroup(self)
self._mode_group.addAction(self._mode_top_bot)
self._mode_group.addAction(self._mode_range)
self._mode_group.addAction(self._mode_above_below)

# -------------------

btn_top_bot = QToolButton()
btn_top_bot.setDefaultAction(self._mode_top_bot)
btn_range = QToolButton()
btn_range.setDefaultAction(self._mode_range)
button_above_below = QToolButton()
button_above_below.setDefaultAction(self._mode_above_below)

btn_layout = QHBoxLayout()
btn_layout.addWidget(btn_top_bot)
btn_layout.addWidget(btn_range)
btn_layout.addWidget(button_above_below)
btn_layout.addStretch()
self._mode_btn_group = QButtonGroup()
self._mode_btn_group.addButton(self._btn_top_bot)
self._mode_btn_group.addButton(self._btn_range)
self._mode_btn_group.addButton(self._button_above_below)
self._mode_btn_group.buttonToggled.connect(self.setMode)

# radio buttons on the top row
btn_wdg = QWidget()
btn_layout = QHBoxLayout(btn_wdg)
btn_layout.setContentsMargins(0, 0, 0, 0)
btn_layout.addWidget(self._btn_top_bot, 0)
btn_layout.addWidget(self._btn_range, 0)
btn_layout.addWidget(self._button_above_below, 1)

# FIXME: On Windows 11, buttons within an inner widget of a ScrollArea
# are filled in with the accent color, making it very difficult to see
# which radio button is checked. This HACK solves the issue. It's
# likely future Qt versions will fix this.
btn_wdg.setStyleSheet("QRadioButton {color: none}")

# #################### Value Widgets ####################

Expand Down Expand Up @@ -248,7 +235,6 @@ def __init__(self, parent: QWidget | None = None) -> None:

left_half = QVBoxLayout()
left_half.addWidget(self._range_readout)
# left_half.addWidget(self.leave_shutter_open)

right_half = QVBoxLayout()
right_half.addWidget(self._bottom_to_top)
Expand All @@ -261,7 +247,7 @@ def __init__(self, parent: QWidget | None = None) -> None:
below_grid.addLayout(right_half)

layout = QVBoxLayout(self)
layout.addLayout(btn_layout)
layout.addWidget(btn_wdg)
layout.addWidget(SeparatorWidget())
layout.addLayout(self._grid_layout)
layout.addStretch()
Expand All @@ -270,13 +256,12 @@ def __init__(self, parent: QWidget | None = None) -> None:
# #################### Defaults ####################

self.setMode(Mode.TOP_BOTTOM)
# self.setSuggestedStep(1)

# ------------------------- Public API -------------------------

def setMode(
self,
mode: Mode | Literal["top_bottom", "range_around", "above_below", None] = None,
mode: Mode | Literal["top_bottom", "range_around", "above_below"],
) -> None:
"""Set the current mode.
Expand All @@ -288,27 +273,35 @@ def setMode(
The mode to set. By default, None.
If None, the mode is determined by the sender().data(), for internal usage.
"""
if isinstance(mode, str):
mode = Mode(mode)
elif isinstance(mode, (bool, type(None))):
mode = cast("QAction", self.sender()).data()

self._mode = cast(Mode, mode)
if isinstance(mode, QRadioButton):
btn_map: dict[QAbstractButton, Mode] = {
self._btn_top_bot: Mode.TOP_BOTTOM,
self._btn_range: Mode.RANGE_AROUND,
self._button_above_below: Mode.ABOVE_BELOW,
}
self._mode = btn_map[mode]
elif isinstance(mode, str):
self._mode = Mode(mode)
else:
self._mode = mode

if self._mode is Mode.TOP_BOTTOM:
self._mode_top_bot.setChecked(True)
with signals_blocked(self._mode_btn_group):
self._btn_top_bot.setChecked(True)
self._set_row_visible(ROW_RANGE_AROUND, False)
self._set_row_visible(ROW_ABOVE_BELOW, False)
self._set_row_visible(ROW_TOP_BOTTOM, True)

elif self._mode is Mode.RANGE_AROUND:
self._mode_range.setChecked(True)
with signals_blocked(self._mode_btn_group):
self._btn_range.setChecked(True)
self._set_row_visible(ROW_TOP_BOTTOM, False)
self._set_row_visible(ROW_ABOVE_BELOW, False)
self._set_row_visible(ROW_RANGE_AROUND, True)

elif self._mode is Mode.ABOVE_BELOW:
self._mode_above_below.setChecked(True)
with signals_blocked(self._mode_btn_group):
self._button_above_below.setChecked(True)
self._set_row_visible(ROW_RANGE_AROUND, False)
self._set_row_visible(ROW_TOP_BOTTOM, False)
self._set_row_visible(ROW_ABOVE_BELOW, True)
Expand Down
7 changes: 5 additions & 2 deletions tests/useq_widgets/test_useq_widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -358,12 +358,15 @@ def test_z_plan_widget(qtbot: QtBot) -> None:
assert wdg.mode() == _z.Mode.TOP_BOTTOM
assert wdg.top.isVisible()
assert not wdg.above.isVisible()
wdg._mode_range.trigger()
assert wdg._btn_top_bot.isChecked()
wdg.setMode(_z.Mode.RANGE_AROUND)
assert wdg.range.isVisible()
assert not wdg.top.isVisible()
wdg._mode_above_below.trigger()
assert wdg._btn_range.isChecked()
wdg.setMode(_z.Mode.ABOVE_BELOW)
assert wdg.above.isVisible()
assert not wdg.range.isVisible()
assert wdg._button_above_below.isChecked()

assert wdg.step.value() == 1
wdg.setSuggestedStep(0.5)
Expand Down

0 comments on commit 1cda2b7

Please sign in to comment.