Skip to content

Commit 01d97a2

Browse files
Merge pull request #892 from annie-xd-wang/stage-event-bindings
Stage event bindings
2 parents 5c70589 + 8bbbb5b commit 01d97a2

File tree

3 files changed

+78
-45
lines changed

3 files changed

+78
-45
lines changed

src/navigate/controller/sub_controllers/stage_controller.py

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -284,13 +284,15 @@ def disable_synthetic_stages(self, config):
284284
def bind_position_callbacks(self):
285285
"""Binds position_callback() to each axis, records the trace name so we can
286286
unbind later."""
287+
widgets = self.view.get_widgets()
287288
if not self.position_callbacks_bound:
288289
for axis in ["x", "y", "z", "theta", "f"]:
289290
# add event bind to position entry variables
290-
cbname = self.widget_vals[axis].trace_add(
291-
"write", self.position_callback(axis)
292-
)
293-
self.position_callback_traces[axis] = cbname
291+
widgets[axis].widget.bind("<FocusOut>", self.position_callback(axis))
292+
# cbname = self.widget_vals[axis].trace_add(
293+
# "write", self.position_callback(axis)
294+
# )
295+
# self.position_callback_traces[axis] = cbname
294296
self.position_callbacks_bound = True
295297

296298
def unbind_position_callbacks(self):
@@ -318,13 +320,11 @@ def set_position(self, position):
318320
position : dict
319321
{'x': value, 'y': value, 'z': value, 'theta': value, 'f': value}
320322
"""
321-
widgets = self.view.get_widgets()
322323
for axis in ["x", "y", "z", "theta", "f"]:
323-
self.widget_vals[axis].set(position.get(axis, 0))
324-
# validate position value if set through variable
325-
if self.stage_limits:
326-
widgets[axis].widget.trigger_focusout_validation()
327-
self.stage_setting_dict[axis] = position.get(axis, 0)
324+
if axis not in position:
325+
continue
326+
self.widget_vals[axis].set(position[axis])
327+
self.position_callback(axis)()
328328
self.show_verbose_info("Set stage position")
329329

330330
def set_position_silent(self, position):
@@ -335,11 +335,17 @@ def set_position_silent(self, position):
335335
position : dict
336336
{'x': value, 'y': value, 'z': value, 'theta': value, 'f': value}
337337
"""
338-
self.unbind_position_callbacks()
339-
340-
self.set_position(position)
338+
widgets = self.view.get_widgets()
339+
for axis in ["x", "y", "z", "theta", "f"]:
340+
if axis not in position:
341+
continue
342+
self.widget_vals[axis].set(position[axis])
343+
# validate position value if set through variable
344+
if self.stage_limits:
345+
widgets[axis].widget.trigger_focusout_validation()
346+
self.stage_setting_dict[axis] = position.get(axis, 0)
347+
self.show_verbose_info("Set stage position")
341348

342-
self.bind_position_callbacks()
343349

344350
def get_position(self):
345351
"""This function returns current position from the view.
@@ -392,7 +398,7 @@ def handler():
392398
to move."""
393399
stage_direction = -1 if self.flip_flags[axis] else 1
394400
try:
395-
temp = position_val.get() + step_val.get() * stage_direction
401+
temp = float(position_val.get()) + step_val.get() * stage_direction
396402
except tk._tkinter.TclError:
397403
return
398404
if self.stage_limits is True:
@@ -401,8 +407,9 @@ def handler():
401407
elif temp < self.position_min[axis]:
402408
temp = self.position_min[axis]
403409
# guarantee stage won't move out of limits
404-
if position_val.get() != temp:
410+
if float(position_val.get()) != temp:
405411
position_val.set(temp)
412+
self.position_callback(axis)()
406413

407414
return handler
408415

@@ -432,7 +439,7 @@ def handler():
432439
to move."""
433440
stage_direction = -1 if self.flip_flags[axis] else 1
434441
try:
435-
temp = position_val.get() - step_val.get() * stage_direction
442+
temp = float(position_val.get()) - step_val.get() * stage_direction
436443
except tk._tkinter.TclError:
437444
return
438445
if self.stage_limits is True:
@@ -441,8 +448,9 @@ def handler():
441448
elif temp > self.position_max[axis]:
442449
temp = self.position_max[axis]
443450
# guarantee stage won't move out of limits
444-
if position_val.get() != temp:
451+
if float(position_val.get()) != temp:
445452
position_val.set(temp)
453+
self.position_callback(axis)()
446454

447455
return handler
448456

@@ -465,6 +473,7 @@ def zero_btn_handler(self, axis):
465473

466474
def handler():
467475
position_val.set(0)
476+
self.position_callback(axis)()
468477

469478
return handler
470479

@@ -482,6 +491,8 @@ def xy_zero_btn_handler(self):
482491
def handler():
483492
x_val.set(0)
484493
y_val.set(0)
494+
self.position_callback("x")()
495+
self.position_callback("y")()
485496

486497
return handler
487498

@@ -547,7 +558,7 @@ def handler(*args):
547558
self.view.after_cancel(self.event_id[axis])
548559
# if position is not a number, then do not move stage
549560
try:
550-
position = position_var.get()
561+
position = float(position_var.get())
551562
if self.stage_limits:
552563
widget.trigger_focusout_validation()
553564
# if position is not inside limits do not move stage

src/navigate/view/main_window_content/stage_tab.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -521,7 +521,7 @@ def __init__(self, stage_control_tab, *args, **kwargs):
521521
parent=self,
522522
label=entry_labels[i],
523523
input_class=ValidatedEntry,
524-
input_var=tk.DoubleVar(),
524+
input_var=tk.StringVar(),
525525
input_args={
526526
"required": True,
527527
"precision": 0.1,

test/controller/sub_controllers/test_stage_controller.py

Lines changed: 47 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
#
3232

3333
import pytest
34-
from unittest.mock import MagicMock
34+
from unittest.mock import MagicMock, call
3535
import numpy as np
3636

3737
AXES = ["x", "y", "z", "theta", "f"]
@@ -55,6 +55,52 @@ def stage_controller(dummy_controller):
5555
dummy_controller,
5656
)
5757

58+
# test before set position variables to MagicMock()
59+
def test_set_position(stage_controller):
60+
61+
widgets = stage_controller.view.get_widgets()
62+
vals = {}
63+
for axis in AXES:
64+
widgets[axis].widget.trigger_focusout_validation = MagicMock()
65+
vals[axis] = np.random.randint(0, 9)
66+
67+
stage_controller.view.get_widgets = MagicMock(return_value=widgets)
68+
stage_controller.show_verbose_info = MagicMock()
69+
position = {
70+
"x": np.random.random(),
71+
"y": np.random.random(),
72+
"z": np.random.random(),
73+
}
74+
stage_controller.set_position(position)
75+
for axis in position.keys():
76+
assert float(stage_controller.widget_vals[axis].get()) == position[axis]
77+
assert widgets[axis].widget.trigger_focusout_validation.called
78+
assert stage_controller.stage_setting_dict[axis] == position.get(axis, 0)
79+
stage_controller.show_verbose_info.assert_has_calls([call("Stage position changed"), call("Set stage position")])
80+
81+
def test_set_position_silent(stage_controller):
82+
83+
widgets = stage_controller.view.get_widgets()
84+
vals = {}
85+
for axis in AXES:
86+
widgets[axis].widget.trigger_focusout_validation = MagicMock()
87+
vals[axis] = np.random.randint(0, 9)
88+
89+
stage_controller.view.get_widgets = MagicMock(return_value=widgets)
90+
stage_controller.show_verbose_info = MagicMock()
91+
position = {
92+
"x": np.random.random(),
93+
"y": np.random.random(),
94+
"z": np.random.random(),
95+
}
96+
stage_controller.set_position_silent(position)
97+
for axis in position.keys():
98+
assert float(stage_controller.widget_vals[axis].get()) == position[axis]
99+
widgets[axis].widget.trigger_focusout_validation.assert_called_once()
100+
assert stage_controller.stage_setting_dict[axis] == position.get(axis, 0)
101+
stage_controller.show_verbose_info.assert_has_calls([call("Set stage position")])
102+
assert call("Stage position changed") not in stage_controller.show_verbose_info.mock_calls
103+
58104

59105
@pytest.mark.parametrize(
60106
"flip_x, flip_y",
@@ -105,30 +151,6 @@ def test_stage_key_press(stage_controller, flip_x, flip_y):
105151
stage_config["flip_y"] = False
106152

107153

108-
def test_set_position(stage_controller):
109-
110-
widgets = stage_controller.view.get_widgets()
111-
vals = {}
112-
for axis in AXES:
113-
widgets[axis].widget.trigger_focusout_validation = MagicMock()
114-
vals[axis] = np.random.randint(0, 9)
115-
stage_controller.widget_vals[axis].set = MagicMock()
116-
117-
stage_controller.view.get_widgets = MagicMock(return_value=widgets)
118-
stage_controller.show_verbose_info = MagicMock()
119-
position = {
120-
"x": np.random.random(),
121-
"y": np.random.random(),
122-
"z": np.random.random(),
123-
}
124-
stage_controller.set_position(position)
125-
for axis in AXES:
126-
assert stage_controller.widget_vals[axis].set.called
127-
widgets[axis].widget.trigger_focusout_validation.assert_called_once()
128-
assert stage_controller.stage_setting_dict[axis] == position.get(axis, 0)
129-
stage_controller.show_verbose_info.assert_called_once_with("Set stage position")
130-
131-
132154
def test_get_position(stage_controller):
133155
import tkinter as tk
134156

0 commit comments

Comments
 (0)