Skip to content

Commit 9156705

Browse files
committed
improve robustness of stage widget children specification and handling
1 parent c97486a commit 9156705

File tree

4 files changed

+95
-92
lines changed

4 files changed

+95
-92
lines changed

ProConPy/stage.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,7 @@ def _proceed(self):
353353

354354
# Display the child stage and its siblings by appending them to the current stage's widget
355355
if self.has_children():
356-
self._widget.append_child_stages(first_child=next_stage)
356+
self._widget.add_child_stages(first_child=next_stage)
357357

358358
# Proceed the csp solver before enabling the next stage
359359
csp.proceed()
@@ -578,8 +578,6 @@ def revert(self, b=None):
578578
self._disable()
579579
csp.revert()
580580
# If the stage to enable has guards as children, remove them from the widget
581-
if previous_stage.children_have_conditions():
582-
previous_stage._widget.children = [
583-
var.widget for var in previous_stage._varlist
584-
]
581+
if previous_stage.has_children():
582+
previous_stage._widget.remove_child_stages()
585583
previous_stage._enable()

visualCaseGen/custom_widget_types/stage_widget.py

Lines changed: 60 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,17 @@
1717
class StageWidget(VBox):
1818
"""A specialized VBox widget for a stage in the case configurator."""
1919

20-
def __init__(self, main_body_type=VBox, title="", layout={}, **kwargs):
20+
def __init__(self, main_body_type=VBox, supplementary_widgets=[], add_ok_button=False, title="", layout={}, **kwargs):
2121
"""Initialize the StageWidget.
2222
2323
Parameters
2424
----------
2525
main_body_type : VBox, HBox, or Tab
2626
The type of the main body of the StageWidget.
27+
supplementary_widgets : list
28+
A list of supplementary widgets to be added to the StageWidget.
29+
add_ok_button : bool
30+
Whether to add an OK button to the StageWidget. Defaults to False.
2731
title : str
2832
The title of the StageWidget.
2933
layout : dict
@@ -41,10 +45,13 @@ def __init__(self, main_body_type=VBox, title="", layout={}, **kwargs):
4145
HBox,
4246
Tab,
4347
], "StageWidget main_body_type must be VBox, HBox, or Tab"
48+
assert isinstance(
49+
supplementary_widgets, (list, tuple)
50+
), "StageWidget supplementary_widgets must be a list"
4451
self._main_body_type = main_body_type
4552
self._title = title
46-
self._main_body = None
47-
self._main_body = self._gen_main_body(children=())
53+
self._main_body = self._main_body_type()
54+
self._supplementary_widgets = supplementary_widgets
4855
self._top_bar = HBox([])
4956
self._stage = None # Reference to the stage object that this widget represents. To be set by the stage object.
5057
super().__init__(
@@ -55,6 +62,21 @@ def __init__(self, main_body_type=VBox, title="", layout={}, **kwargs):
5562
},
5663
**kwargs,
5764
)
65+
self.children = (
66+
self._top_bar,
67+
self._main_body,
68+
)
69+
70+
self._ok_button = None # OK button, if added
71+
if add_ok_button:
72+
self._ok_button = Button(
73+
description="OK",
74+
icon="check",
75+
tooltip="Confirm the selections in this stage.",
76+
layout={"width": "100px", "align_self": "center"},
77+
style={"button_color": bg_color_dark, "text_color": font_color_dark},
78+
)
79+
self._ok_button.on_click(self.attempt_to_proceed)
5880

5981
def _update_top_bar_title(
6082
self, title_prefix="", font_color="gray", background_color=bg_color_light
@@ -153,59 +175,52 @@ def _on_btn_info_click(b):
153175
self._btn_proceed.on_click(self.attempt_to_proceed)
154176
top_bar_buttons.append(self._btn_proceed)
155177

156-
self._top_bar = HBox([self._top_bar_title] + top_bar_buttons)
178+
self._top_bar.children = [self._top_bar_title] + top_bar_buttons
157179

158-
def _gen_main_body(self, children):
159-
"""Generate the main body of the StageWidget. This method is called whenever the children attribute is set.
180+
def _set_main_body_children(self, first_child=None):
181+
"""Set the children of the main body of the StageWidget. If a first_child is provided,
182+
it will be added to the main body, followed by all its siblings to the right."""
160183

161-
Parameters
162-
----------
163-
children : list
164-
The new list of children of the main body.
184+
main_body_children = [var.widget for var in self._stage._varlist]
185+
if self._supplementary_widgets:
186+
main_body_children.extend(self._supplementary_widgets)
187+
if self._ok_button:
188+
main_body_children.append(self._ok_button)
189+
if first_child:
190+
main_body_children.append(first_child._widget)
191+
main_body_children.extend(stage._widget for stage in first_child.siblings_to_right())
192+
self._main_body.children = tuple(main_body_children)
193+
194+
def _refresh_main_body(self):
195+
"""Generate the main body of the StageWidget. This method is called whenever the children attribute is set.
165196
"""
166197

167198
old_display = ""
168199
if self._main_body:
169200
old_display = self._main_body.layout.display
170201

171-
return self._main_body_type(
172-
children=children,
173-
layout={
174-
"display": old_display,
175-
"margin": "0px",
176-
},
177-
)
202+
self._set_main_body_children()
203+
204+
self._main_body.layout = {
205+
"display": old_display,
206+
"margin": "0px",
207+
}
178208

179-
def __setattr__(self, name, value):
180-
"""Override the __setattr__ method to handle the children attribute so that the widget
181-
always has two children: the top bar and the main body. Any new children, unless they
182-
are the top bar or the main body, are added to the main body. If the children attribute
183-
is set, the top bar and the main body are updated accordingly.
209+
def add_child_stages(self, first_child):
210+
"""Append a child stage and all its siblings to the main body, which, by default,
211+
has the widgets of the varlist only, but may be extended to include the StageWidget
212+
instances of child stages.
184213
185214
Parameters
186215
----------
187-
name : str
188-
The name of the attribute to set.
189-
value : any
190-
The value to set the attribute to.
216+
first_child : Stage
217+
The first child stage to append.
191218
"""
192-
193-
if name == "children":
194-
if len(value) > 0 and value[0] is self._top_bar:
195-
value = value[1:]
196-
if len(value) > 0 and value[0] is self._main_body:
197-
value = value[0].children + value[1:]
198-
self._main_body = self._gen_main_body(children=value)
199-
super().__setattr__(
200-
name,
201-
(
202-
self._top_bar,
203-
self._main_body,
204-
),
205-
)
206-
else:
207-
# For all other attributes, use the default behavior
208-
super().__setattr__(name, value)
219+
self._set_main_body_children(first_child)
220+
221+
def remove_child_stages(self):
222+
"""Remove all child stages from the main body, leaving only the widgets of the varlist and supplementary widgets."""
223+
self._set_main_body_children()
209224

210225
@property
211226
def stage(self):
@@ -220,7 +235,7 @@ def stage(self, value):
220235
# Set the children attribute. This will actually set the children of the StageWidget
221236
# to the top bar and the main body, and the main body's children to the widgets of the
222237
# variables in the stage.
223-
self.children = [var.widget for var in self._stage._varlist]
238+
self._refresh_main_body()
224239
# Observe StageStat
225240
self._stage.observe(self._on_stage_status_change, names="status", type="change")
226241
self._on_stage_status_change(
@@ -241,24 +256,6 @@ def _on_stage_status_change(self, change):
241256
self._enable()
242257
self._update_btn_reset(old_state, new_state)
243258

244-
def append_child_stages(self, first_child):
245-
"""Append a child stage and all its siblings to the main body, which, by default,
246-
has the widgets of the varlist only, but may be extended to include the StageWidget
247-
instances of child stages.
248-
249-
Parameters
250-
----------
251-
first_child : Stage
252-
The first child stage to append.
253-
"""
254-
255-
self._main_body.children = tuple(
256-
itertools.chain(
257-
[var.widget for var in self._stage._varlist],
258-
[first_child._widget],
259-
[stage._widget for stage in first_child.siblings_to_right()],
260-
)
261-
)
262259

263260
def _disable(self):
264261
"""Disable the entire stage widget."""
@@ -345,4 +342,4 @@ def reset(self):
345342
"""Reset the stage."""
346343
logger.debug(f"Resetting stage {self._title}...")
347344
self._stage.reset()
348-
self._main_body.children = self._stage._gen_main_body(children=())
345+
self._stage._refresh_main_body()

visualCaseGen/stages/grid_stages.py

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,10 @@ def initialize_grid_stages(cime):
105105
"the button will launch a new notebook. Execute all the cells in the notebook to create the new "
106106
"grid. Once all the cells are executed, return to this tab and click the Confirm Completion "
107107
"button to proceed to the next stage.",
108-
widget=StageWidget(VBox),
108+
widget=StageWidget(
109+
VBox,
110+
supplementary_widgets=[MOM6BathyLauncher()]
111+
),
109112
parent=Guard(
110113
title="Custom Ocn Grid",
111114
parent=stg_custom_ocn_grid_mode,
@@ -141,7 +144,7 @@ def initialize_grid_stages(cime):
141144
title="Simple Initial Conditions",
142145
description="Set a uniform reference temperature for the new ocean grid. Salinity will be "
143146
"fit accordingly.",
144-
widget=StageWidget(VBox),
147+
widget=StageWidget(VBox, add_ok_button=True),
145148
parent=Guard(
146149
title="Std IC",
147150
parent=stg_new_ocn_grid_ic_mode,
@@ -166,8 +169,6 @@ def initialize_grid_stages(cime):
166169
],
167170
)
168171

169-
stg_new_ocn_grid._widget.children += (MOM6BathyLauncher(),)
170-
171172
stg_custom_lnd_grid_mode = Stage(
172173
title="Land Grid Mode",
173174
description="Determine whether to use a standard land grid or modify an existing land grid.",
@@ -203,13 +204,21 @@ def initialize_grid_stages(cime):
203204
auto_set_default_value=False,
204205
)
205206

207+
fsurdat_modifier_launcher = FsurdatModifierLauncher(cime.srcroot)
208+
206209
stg_fsurdat_modifier_w_mom = Stage(
207210
title="fsurdat",
208211
description= "At this stage, you will be prompted to configure are run the fsurdat modifier "
209212
"tool to modify the surface data of the selected CLM grid. The properties to configure and "
210213
"modify include soil properties, vegetation properties, urban areas, etc. See CLM documentation "
211214
"for more information.",
212-
widget=StageWidget(VBox),
215+
widget=StageWidget(
216+
VBox,
217+
supplementary_widgets=[
218+
cvars["FSURDAT_MATRIX"]._widget,
219+
fsurdat_modifier_launcher,
220+
],
221+
),
213222
parent=Guard(
214223
title="Custom w/ mom",
215224
parent=stg_base_lnd_grid,
@@ -227,11 +236,6 @@ def initialize_grid_stages(cime):
227236
cvars["FSURDAT_MOD_STATUS"]
228237
],
229238
)
230-
fsurdat_modifier_launcher = FsurdatModifierLauncher(cime.srcroot)
231-
stg_fsurdat_modifier_w_mom._widget.children += (
232-
cvars["FSURDAT_MATRIX"]._widget,
233-
fsurdat_modifier_launcher
234-
)
235239

236240
guard_custom_clm_grid_wo_mom = Guard(
237241
title="Custom w/o mom",
@@ -248,7 +252,12 @@ def initialize_grid_stages(cime):
248252
"mask file that contains the final land mask. This file must be created by the user beforehand. "
249253
"You may then specify the variable and dimension names of latitude and longitude in the mask file. "
250254
"Finally, specify the output file name to be generated by the mesh_mask_modifier tool.",
251-
widget=StageWidget(VBox),
255+
widget=StageWidget(
256+
VBox,
257+
supplementary_widgets=[
258+
MeshMaskModifierLauncher(cime.srcroot)
259+
]
260+
),
252261
parent=guard_custom_clm_grid_wo_mom,
253262
varlist=[
254263
cvars["INPUT_MASK_MESH"],
@@ -260,15 +269,20 @@ def initialize_grid_stages(cime):
260269
cvars["MESH_MASK_MOD_STATUS"],
261270
],
262271
)
263-
stg_mesh_mask_modifier._widget.children += (MeshMaskModifierLauncher(cime.srcroot),)
264272

265273
stg_fsurdat_modifier = Stage(
266274
title="fsurdat ",
267275
description= "At this stage, you will be prompted to configure are run the fsurdat modifier "
268276
"tool to modify the surface data of the selected CLM grid. The properties to configure and "
269277
"modify include soil properties, vegetation properties, urban areas, etc. See CLM documentation "
270278
"for more information.",
271-
widget=StageWidget(VBox),
279+
widget=StageWidget(
280+
VBox,
281+
supplementary_widgets=[
282+
cvars["FSURDAT_MATRIX"]._widget,
283+
fsurdat_modifier_launcher
284+
]
285+
),
272286
parent=guard_custom_clm_grid_wo_mom,
273287
varlist=[
274288
cvars["INPUT_FSURDAT"],
@@ -282,10 +296,3 @@ def initialize_grid_stages(cime):
282296
cvars["FSURDAT_MOD_STATUS"]
283297
],
284298
)
285-
stg_fsurdat_modifier._widget.children += (
286-
cvars["FSURDAT_MATRIX"]._widget,
287-
fsurdat_modifier_launcher
288-
)
289-
290-
291-

visualCaseGen/stages/launcher_stages.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,9 @@ def initialize_launcher_stages(cime):
3030
"If the machine requires a PROJECT id, you'll be prompted to provide it. When everything "
3131
"is set, click either the *Create Case* button or *Show Commands* to view the "
3232
"corresponding terminal commands.",
33-
widget=StageWidget(VBox),
33+
widget=StageWidget(
34+
VBox,
35+
supplementary_widgets=[CaseCreatorWidget(cime)]
36+
),
3437
varlist=launcher_vars,
35-
)
36-
37-
stg_launch._widget.children += (CaseCreatorWidget(cime),)
38+
)

0 commit comments

Comments
 (0)