Coverage for /home/pi/Software/model-railway-signalling/model_railway_signals/editor/configure_signal_tab3.py: 95%
392 statements
« prev ^ index » next coverage.py v7.2.7, created at 2024-04-10 15:08 +0100
« prev ^ index » next coverage.py v7.2.7, created at 2024-04-10 15:08 +0100
1#------------------------------------------------------------------------------------
2# Functions and sub Classes for the Edit Signal "Automation" Tab
3#
4# Makes the following external API calls to other editor modules:
5#########################################################################################################
6# Note that we need to use the 'objects.section_exists' function as the the library 'section_exists'
7# function will not work in edit mode as the Track Section library objects don't exist in edit mode
8# To be addressed in a future software update when the Track Sections functionality is re-factored
9#########################################################################################################
10# objects.section_exists(id) - To see if the section exists (local) ##################################
11#
12# Makes the following external API calls to library modules:
13# gpio_sensors.gpio_sensor_exists(id) - To see if the GPIO sensor exists (local or remote)
14# gpio_sensors.get_gpio_sensor_callback - To see if a GPIO sensor is already mapped
15# signals_common.sig_exists(id) - To see if the signal exists (local)
16# track_sections.section_exists(id) - To see if the track section exists ####################
17#
18# Inherits the following common editor base classes (from common):
19# common.str_int_item_id_entry_box
20# common.int_item_id_entry_box
21# common.check_box
22# common.integer_entry_box
23# common.CreateToolTip
24#
25#------------------------------------------------------------------------------------
27import tkinter as Tk
29from . import common
30from . import objects ############################################################################
32from ..library import gpio_sensors
33from ..library import signals_common
34from ..library import track_sections
36#------------------------------------------------------------------------------------
37# Class for a Signal Sensor Entry Box - based on the str_int_item_id_entry_box class
38# Public Class instance methods (inherited from the integer_entry_box) are
39# "set_value" - will set the current value for the GPIO Sensor ID (integer)
40# - Also sets the current item ID (int) for validation purposes
41# "get_value" - will return the last "valid" value (integer)
42# Overridden Public Class instance methods provided by this class:
43# "validate" - Must be a valid Sensor ID and not already assigned
44# Note that we use the current_item_id variable (from the base class) for validation.
45#------------------------------------------------------------------------------------
47class signal_sensor(common.str_int_item_id_entry_box):
48 def __init__(self, parent_frame, callback, label:str, tool_tip:str):
49 # We need to hold the current signal_id for validation purposes but we don't pass this
50 # into the parent class as the entered ID for the gpio sensor can be the same as the current
51 # item_id (for the signal object) - so we don't want the parent class to validate this.
52 self.signal_id = 0
53 # The this function will return true if the GPIO sensor exists
54 exists_function = gpio_sensors.gpio_sensor_exists
55 # Create the label and entry box UI elements
56 self.label = Tk.Label(parent_frame, text=label)
57 self.label.pack(side=Tk.LEFT, padx=2, pady=2)
58 super().__init__(parent_frame, callback = callback, tool_tip=tool_tip, exists_function=exists_function)
59 self.pack(side=Tk.LEFT, padx=2, pady=2)
61 def validate(self, update_validation_status=True):
62 # Do the basic integer validation first (is it a valid ID and does it exist (or has been subscribed to)
63 valid = super().validate(update_validation_status=False)
64 # Next we need to validate it isn't already assigned to another signal appropach or passed event
65 if valid and self.entry.get() != "":
66 sensor_id = self.entry.get()
67 event_mappings = gpio_sensors.get_gpio_sensor_callback(sensor_id)
68 if event_mappings[0] > 0 and event_mappings[0] != self.signal_id: 68 ↛ 69line 68 didn't jump to line 69, because the condition on line 68 was never true
69 self.TT.text = ("GPIO Sensor "+sensor_id+" is already mapped to Signal "+str(event_mappings[0]))
70 valid = False
71 elif event_mappings[1] > 0 and event_mappings[1] != self.signal_id: 71 ↛ 72line 71 didn't jump to line 72, because the condition on line 71 was never true
72 self.TT.text = ("GPIO Sensor "+sensor_id+" is already mapped to Signal "+str(event_mappings[1]))
73 valid = False
74 elif event_mappings[2] > 0: 74 ↛ 75line 74 didn't jump to line 75, because the condition on line 74 was never true
75 self.TT.text = ("GPIO Sensor "+sensor_id+" is already mapped to Track Sensor "+str(event_mappings[2]))
76 valid = False
77 if update_validation_status: self.set_validation_status(valid)
78 return(valid)
80 # We need to hold the current signal_id for validation purposes but we don't pass this
81 # into the parent class as the entered ID for the gpio sensor can be the same as the current
82 # item_id (for the signal object) - so we don't want the parent class to validate this.
83 def set_value(self, value:str, signal_id:int):
84 self.signal_id = signal_id
85 super().set_value(value)
87#------------------------------------------------------------------------------------
88# Class for the Signal Passed Sensor Frame - uses the Signal Sensor Entry Box class
89# Public Class instance methods used from the base classes are
90# "approach.enable" - disables/blanks the checkbox and entry box
91# "approach.disable" - enables/loads the checkbox and entry box
92# "approach.set_value" - will set the current value (int)
93# - Also sets the current item ID (int) for validation purposes
94# "approach.get_value" - returns the last "valid" value (int)
95# "passed.set_value" - will set the current value (int)
96# - Also sets the current item ID (int) for validation purposes
97# "passed.get_value" - returns the last "valid" value (int)
98# Public Class instance methods provided by this class:
99# "validate" - validate both entry box values and return True/false
100#------------------------------------------------------------------------------------
102class signal_passed_sensor_frame:
103 def __init__(self, parent_frame, parent_object):
104 # The child class instances need the reference to the parent object so they can call
105 # the sibling class method to get the current value of the Signal ID for validation
106 self.frame = Tk.LabelFrame(parent_frame, text="GPIO sensor events")
107 # Create the elements in a subframe so they are centered
108 self.subframe = Tk.Frame(self.frame)
109 self.subframe.pack()
110 tool_tip = ("Specify the ID of a GPIO Sensor (or leave blank) - This "+
111 "can be a local sensor ID or a remote sensor ID (in the form 'Node-ID') "+
112 "which has been subscribed to via MQTT networking")
113 self.passed = signal_sensor(self.subframe, callback=self.validate,
114 label=" Signal 'passed' sensor:", tool_tip = tool_tip)
115 self.approach = signal_sensor(self.subframe, callback=self.validate,
116 label=" Signal 'approached' sensor:", tool_tip = tool_tip)
118 def validate(self):
119 if self.passed.entry.get() != "" and self.passed.entry.get() == self.approach.entry.get(): 119 ↛ 120line 119 didn't jump to line 120, because the condition on line 119 was never true
120 error_text = "Cannot assign the same GPIO sensor for both signal 'passed' and signal 'approached' events"
121 self.passed.TT.text = error_text
122 self.approach.TT.text = error_text
123 self.passed.set_validation_status(False)
124 self.approach.set_validation_status(False)
125 return(False)
126 else:
127 # As both validation calls are made before the return statement
128 # all UI eelements will be updated to show their validation status
129 self.passed.set_validation_status(self.passed.validate())
130 self.approach.set_validation_status(self.approach.validate())
131 return(self.passed.validate() and self.approach.validate())
133#------------------------------------------------------------------------------------
134# Sub Classes for the Track Occupancy automation subframe
135# Public Class instance methods (inherited from the base class) are
136# "disable" - disables/blanks the entry box
137# "enable" enables/loads the entry box
138# "set_value" - will set the current value (integer)
139# "get_value" - will return the last "valid" value (integer)
140# Public Class instance methods provided by the section_ahead_frame class:
141# "validate" - validate all 'section ahead' entry box values and return True/false
142#
143# Note that the software only supports automation of track sections on the local schematic
144# (local sections should be created to 'mirror' remote sections for networked layouts)
145# so we use the section_exists function from the objects module for entry validation
146#------------------------------------------------------------------------------------
148class section_behind_element(common.int_item_id_entry_box):
149 def __init__(self, parent_frame):
150 self.frame = Tk.Frame(parent_frame)
151 self.frame.pack()
152 tool_tip = "Sepecify the track section 'in the rear of' this signal to be cleared when the signal is passed"
153 super().__init__(self.frame, tool_tip=tool_tip, exists_function=objects.section_exists)
154 self.pack(side=Tk.LEFT)
155 self.label = Tk.Label(self.frame, text=" "+u"\u2192")
156 self.label.pack(side=Tk.LEFT)
158class section_ahead_element():
159 def __init__(self, parent_frame, label):
160 self.frame = Tk.Frame(parent_frame)
161 self.frame.pack()
162 self.label1 = Tk.Label(self.frame, text=label, width=8)
163 self.label1.pack(side=Tk.LEFT)
164 tool_tip1 = ("Specify the track section on the route 'ahead of' the signal "+
165 "to be occupied when the signal is passed")
166 tool_tip2 = ("Specify any other track sections on the route that will also override "+
167 "this signal to ON if occupied (if enabled on the right)")
168 self.t1 = common.int_item_id_entry_box(self.frame, exists_function=objects.section_exists, tool_tip=tool_tip1)
169 self.t1.pack(side = Tk.LEFT)
170 self.t2 = common.int_item_id_entry_box(self.frame, exists_function=objects.section_exists, tool_tip=tool_tip2)
171 self.t2.pack(side = Tk.LEFT)
172 self.t3 = common.int_item_id_entry_box(self.frame, exists_function=objects.section_exists, tool_tip=tool_tip2)
173 self.t3.pack(side = Tk.LEFT)
175 def validate(self):
176 # Validate everything - to highlight ALL validation failures in the UI
177 valid = True
178 if not self.t1.validate(): valid = False
179 if not self.t2.validate(): valid = False
180 if not self.t3.validate(): valid = False
181 return(valid)
183 def enable(self):
184 self.t1.enable()
185 self.t2.enable()
186 self.t3.enable()
188 def disable(self):
189 self.t1.disable()
190 self.t2.disable()
191 self.t3.disable()
193 def set_values(self, list_of_sections:[int,int,int]):
194 # The list_of_sections comprises: [t1,t2,t3] Where each element is
195 # the ID of a track section on the route ahead
196 self.t1.set_value(list_of_sections[0])
197 self.t2.set_value(list_of_sections[1])
198 self.t3.set_value(list_of_sections[2])
200 def get_values(self):
201 # The list_of_sections comprises: [t1,t2,t3] Where each element is
202 # the ID of a track section on the route ahead
203 interlocked_route = [ self.t1.get_value(), self.t2.get_value(), self.t3.get_value() ]
204 return(interlocked_route)
206class section_ahead_frame():
207 def __init__(self, parent_frame):
208 self.main = section_ahead_element(parent_frame, label="MAIN "+u"\u2192")
209 self.lh1 = section_ahead_element(parent_frame, label="LH1 "+u"\u2192")
210 self.lh2 = section_ahead_element(parent_frame, label="LH2 "+u"\u2192")
211 self.rh1 = section_ahead_element(parent_frame, label="RH1 "+u"\u2192")
212 self.rh2 = section_ahead_element(parent_frame, label="RH2 "+u"\u2192")
214 def validate(self):
215 # Validate everything - to highlight ALL validation errors in the UI
216 valid = True
217 if not self.main.validate(): valid = False
218 if not self.lh1.validate(): valid = False
219 if not self.lh2.validate(): valid = False
220 if not self.rh1.validate(): valid = False
221 if not self.rh2.validate(): valid = False
222 return (valid)
224#------------------------------------------------------------------------------------
225# Class for the Track Occupancy Frame - inherits from the sub-classes above
226# Public Class instance methods provided by this class:
227# "set_values" - will set the current values [behind,[MAIN,LH1,LH2,RH1,RH2]]
228# "get_values" - will return the "valid" values [behind,[MAIN,LH1,LH2,RH1,RH2]]
229# "validate" - validate all entry box values and return True/false
230# Individual routes are enabled/disabled by calling the sub-class methods:
231# "section_ahead.<route>.disable" - disables/blanks the entry box
232# "section_ahead.<route>.enable" enables/loads the entry box
233#------------------------------------------------------------------------------------
235class track_occupancy_frame():
236 def __init__(self, parent_frame):
237 # Create the Label Frame for the UI element (packed by the creating function/class)
238 self.frame = Tk.LabelFrame(parent_frame, text="Track occupancy changes")
239 self.subframe1 = Tk.Frame(self.frame)
240 self.subframe1.pack(side=Tk.LEFT)
241 self.section_behind = section_behind_element(self.subframe1)
242 self.subframe2 = Tk.Frame(self.frame)
243 self.subframe2.pack(side=Tk.LEFT)
244 self.section_ahead = section_ahead_frame(self.subframe2)
246 def set_values(self, sections):
247 # sections is a list of [section_behind, sections_ahead]
248 # where sections_ahead is a list of routes [MAIN,LH1,LH2,RH1,RH2]
249 # And each route element is a list of track sections [t1,t2,t3]
250 self.section_behind.set_value(sections[0])
251 self.section_ahead.main.set_values(sections[1][0])
252 self.section_ahead.lh1.set_values(sections[1][1])
253 self.section_ahead.lh2.set_values(sections[1][2])
254 self.section_ahead.rh1.set_values(sections[1][3])
255 self.section_ahead.rh2.set_values(sections[1][4])
257 def get_values(self):
258 # sections is a list of [section_behind, sections_ahead]
259 # where sections_ahead is a list of routes [MAIN,LH1,LH2,RH1,RH2]
260 # And each route element is a list of track sections [t1,t2,t3]
261 return ( [ self.section_behind.get_value(),
262 [ self.section_ahead.main.get_values(),
263 self.section_ahead.lh1.get_values(),
264 self.section_ahead.lh2.get_values(),
265 self.section_ahead.rh1.get_values(),
266 self.section_ahead.rh2.get_values() ] ])
268 def validate(self):
269 # Validate everything - to highlight ALL validation errors in the UI
270 valid = True
271 if not self.section_behind.validate(): valid = False
272 if not self.section_ahead.validate(): valid = False
273 return (valid)
275#------------------------------------------------------------------------------------
276# Class for the General automation settings subframe
277# Public Class instance methods provided by this class:
278# "override.enable" - enable the override checkbox
279# "override.disable"- disable the override checkbox
280# "automatic.enable" - enable the main auto checkbox
281# "automatic.disable"- disable the main auto checkbox
282# "distant_automatic.enable" - enable the distant auto checkbox
283# "distant_automatic.disable"- disable the distant auto checkbox
284# "override_ahead.enable" - enable the override ahead checkbox
285# "override_ahead.disable"- disable the override ahead checkbox
286# "set_values" - will set the current values (override, auto)
287# "get_values" - will return the "valid" values (override, auto)
288#------------------------------------------------------------------------------------
290class general_settings_frame():
291 def __init__(self, parent_frame):
292 # Create the Label Frame for the UI element (packed by the creating function/class)
293 self.frame = Tk.LabelFrame(parent_frame, text="General settings")
294 self.automatic = common.check_box(self.frame, width=39,
295 label="Fully automatic signal (no control button)",
296 tool_tip="Select to create without a main signal button "+
297 "(signal will have a default signal state of OFF, but can be "+
298 "overridden to ON via the selections below)")
299 self.automatic.pack()
300 self.distant_automatic = common.check_box(self.frame, width=39,
301 label="Fully automatic distant arms (no control button)",
302 tool_tip="Select to create without a distant signal button "+
303 "(distant arms will have a default signal state of OFF, but can "+
304 "be overridden to CAUTION via the selections below)")
305 self.distant_automatic.pack()
306 self.override = common.check_box(self.frame, width=39,
307 label="Override signal to ON if section(s) ahead occupied",
308 tool_tip="Select to override the signal to ON if the track "+
309 "sections ahead of the signal (specified on the left) are occupied")
310 self.override.pack()
311 self.override_ahead = common.check_box(self.frame, width=39,
312 label="Override to CAUTION to reflect home signals ahead",
313 tool_tip="Select to override distant signal to CAUTION if "+
314 "any home signals on the route ahead are at DANGER")
315 self.override_ahead.pack()
317 def set_values(self, override:bool, main_auto:bool, override_ahead:bool, dist_auto:bool):
318 self.override.set_value(override)
319 self.automatic.set_value(main_auto)
320 self.override_ahead.set_value(override_ahead)
321 self.distant_automatic.set_value(dist_auto)
323 def get_values(self):
324 return ( self.override.get_value(),
325 self.automatic.get_value(),
326 self.override_ahead.get_value(),
327 self.distant_automatic.get_value() )
329#------------------------------------------------------------------------------------
330# Class for a Timed signal route element comprising a route selection checkbox, a
331# signal ID entry box and two integer entry boxes for specifying the timed sequence
332# Public class instance methods provided by this class are
333# "disable" - disables/blanks all checkboxes and selection boxes
334# "enable" enables/loads all checkboxes and selection boxes
335# "set_values" - set the initial values for the check box and entry boxes)
336# Note this class also needs the current signal ID for validation
337# "get_values" - get the last "validated" values of the check box and entry boxes
338#
339# ote that although the signals.sig_exists function will match both local and remote
340# Signal IDs, the int_item_id_entry_box only allows integers to be selected - so we
341# can safely use this function here for consistency.
342#------------------------------------------------------------------------------------
344class timed_signal_route_element():
345 def __init__(self, parent_frame, parent_object, label:str):
346 # We need to know the current Signal ID for validation purposes
347 self.current_item_id = 0
348 # This is the parent object (the signal instance)
349 self.parent_object = parent_object
350 # Create a frame for the route element
351 self.frame = Tk.Frame(parent_frame)
352 self.frame.pack()
353 # Create the route element (selection, sig ID, start delay, aspect change delay)
354 self.label1 = Tk.Label(self.frame, width=5, text=label, anchor='w')
355 self.label1.pack(side=Tk.LEFT)
356 self.route = common.check_box(self.frame, label="", callback=self.route_updated,
357 tool_tip="Select to trigger a timed sequence (for this route) when the current signal is passed")
358 self.route.pack(side=Tk.LEFT)
359 self.label2 = Tk.Label(self.frame, text=" Signal to trigger:")
360 self.label2.pack(side=Tk.LEFT)
361 self.sig = common.int_item_id_entry_box(self.frame, allow_empty=False, callback=self.signal_updated,
362 exists_function=signals_common.sig_exists, tool_tip="Enter the ID of the signal to "+
363 "trigger. This can be the current signal or another semaphore / colour light "+
364 "signal (on the route ahead of the current signal)")
365 self.sig.pack(side=Tk.LEFT)
366 self.label3 = Tk.Label(self.frame, text=" Start delay:")
367 self.label3.pack(side=Tk.LEFT)
368 self.start = common.integer_entry_box(self.frame, width=3, min_value=0, max_value=60,
369 allow_empty=False, tool_tip="Specify the time delay (in seconds) "+
370 "before triggering the timed sequence (if triggering the same " +
371 "signal then this will be zero)")
372 self.start.pack(side=Tk.LEFT)
373 self.label4 = Tk.Label(self.frame, text=" Time delay:")
374 self.label4.pack(side=Tk.LEFT)
375 self.delay = common.integer_entry_box(self.frame, width=3, min_value=1, max_value=60,
376 allow_empty=False, tool_tip="Specify the time period (in seconds) "+
377 "between signal aspect changes")
378 self.delay.pack(side=Tk.LEFT)
380 def signal_updated(self):
381 # Only enable the start delay if the current signal ID is not selected
382 if self.sig.get() == str(self.current_item_id):
383 self.start.disable2()
384 self.start.TT.text = "Start delay will be zero when triggering the current signal"
385 else:
386 self.start.enable2()
388 def route_updated(self):
389 if self.route.get_value():
390 self.sig.enable1()
391 self.start.enable1()
392 self.delay.enable1()
393 else:
394 self.sig.disable1()
395 self.start.disable1()
396 self.delay.disable1()
397 self.signal_updated()
399 def enable(self):
400 self.route.enable()
401 self.sig.enable()
402 self.start.enable()
403 self.delay.enable()
405 def disable(self):
406 self.route.disable()
407 self.sig.disable()
408 self.start.disable()
409 self.delay.disable()
411 def set_values(self, route:[bool,int,int,int], item_id:int):
412 # A route comprises a list of [selected, sig_id, start_delay, time_delay)
413 # If signal to trigger is '0' (no selection) then we set the current signal ID
414 # to give us a valid default configuration (for the user to edit as required)
415 # Similarly, we set a default of 5 seconds for the time delay
416 self.current_item_id = item_id
417 self.route.set_value(route[0])
418 if route[1] == 0: self.sig.set_value(item_id)
419 else:self.sig.set_value(route[1])
420 self.start.set_value(route[2])
421 if route[3] == 0: self.delay.set_value(5)
422 else: self.delay.set_value(route[3])
423 # Enable/disable the various route elements as required
424 self.route_updated()
426 def get_values(self):
427 # A route comprises a list of [selected, sig_id,start_delay, time_delay)
428 return ( [ self.route.get_value(),
429 self.sig.get_value(),
430 self.start.get_value(),
431 self.delay.get_value() ] )
433 def validate(self):
434 # Validate everything - to highlight ALL validation errors in the UI
435 valid = True
436 if not self.sig.validate(): valid = False
437 if not self.start.validate(): valid = False
438 if not self.delay.validate(): valid = False
439 return (valid)
441#------------------------------------------------------------------------------------
442# Class for a Timed signal route frame (comprising selections for each route)
443# Public class instance methods provided by this class are:
444# "set_values" - set the initial values for the check box and entry boxes
445# "get_values" - get the last "validated" values of the check box and entry boxes
446# Note this class also needs the current signal ID for validation
447# Note that no overall enable/disable functions are provided - External functions
448# should call the individual enable/disable functions for each route element
449#------------------------------------------------------------------------------------
451class timed_signal_frame():
452 def __init__(self, parent_frame, parent_object):
453 # Create a label frame for the UI element
454 self.frame = Tk.LabelFrame(parent_frame, text="Trigger timed signal sequence")
455 # Create a subframe for the context label
456 self.subframe1 = Tk.Frame(self.frame)
457 self.subframe1.pack(side=Tk.LEFT, padx=2, pady=2, fill='both')
458 self.label = Tk.Label(self.frame, text="Routes to\ntrigger", anchor='w')
459 self.label.pack(side=Tk.LEFT)
460 # Create a subframe for the route elements
461 self.subframe2 = Tk.Frame(self.frame)
462 self.subframe2.pack(side=Tk.LEFT, padx=2, pady=2, fill='x', expand=True)
463 self.main=timed_signal_route_element(self.subframe2, parent_object, label="MAIN")
464 self.lh1=timed_signal_route_element(self.subframe2, parent_object, label="LH1")
465 self.lh2=timed_signal_route_element(self.subframe2, parent_object, label="LH2")
466 self.rh1=timed_signal_route_element(self.subframe2, parent_object, label="RH1")
467 self.rh2=timed_signal_route_element(self.subframe2, parent_object, label="RH2")
469 def set_values(self, timed_sequence:[[bool,int,int,int],], item_id:int):
470 # A timed_sequence comprises a list of routes [MAIN, LH1, LH2, RH1, RH2]
471 # Each route comprises a list of [selected, sig_id,start_delay, time_delay)
472 self.main.set_values(timed_sequence[0], item_id)
473 self.lh1.set_values(timed_sequence[1], item_id)
474 self.lh2.set_values(timed_sequence[2], item_id)
475 self.rh1.set_values(timed_sequence[3], item_id)
476 self.rh2.set_values(timed_sequence[4], item_id)
478 def get_values(self):
479 # A timed_sequence comprises a list of routes [MAIN, LH1, LH2, RH1, RH2]
480 # Each route comprises a list of [selected, sig_id,start_delay, time_delay)
481 return ( [ self.main.get_values(),
482 self.lh1.get_values(),
483 self.lh2.get_values(),
484 self.rh1.get_values(),
485 self.rh2.get_values() ] )
487 def validate(self):
488 # Validate everything - to highlight ALL validation errors in the UI
489 valid = True
490 if not self.main.validate(): valid = False
491 if not self.lh1.validate(): valid = False
492 if not self.lh2.validate(): valid = False
493 if not self.rh1.validate(): valid = False
494 if not self.rh2.validate(): valid = False
495 return (valid)
497#------------------------------------------------------------------------------------
498# Class for a approach control route element comprising a route selection checkbox,
499# And radio buttons to select the approach control mode
500# "disable_route" - disables/blanks all checkboxes and radio buttons
501# "enable_route" enables/loads all checkboxes and radio buttons
502# "disable_red" - disables/blanks the "Release on Red" radio button
503# "enable_red" enables/loads the "Release on Red" radio button
504# "disable_yel" - disables/blanks the "Release on yellow" radio button
505# "enable_yel" enables/loads the "Release on yellow" radio button
506# "set_values" - set the initial values for the check box and radio buttons
507# "get_values" - get the current values of the check box and radio buttons
508#------------------------------------------------------------------------------------
510class approach_control_route_element():
511 def __init__(self, parent_frame, label:str):
512 # Create a frame for the route element
513 self.frame = Tk.Frame(parent_frame)
514 self.frame.pack()
515 # Create the route element (selection, sig ID, start delay, aspect change delay)
516 self.label1 = Tk.Label(self.frame, width=5, text=label, anchor='w')
517 self.label1.pack(side=Tk.LEFT)
518 self.route = common.check_box(self.frame, label="", callback=self.route_selected,
519 tool_tip="Select to enable 'Approach Control' for this route")
520 self.route.pack(side=Tk.LEFT)
521 # Add a bit of white space
522 self.label2 = Tk.Label(self.frame, text=" Release on:")
523 self.label2.pack(side=Tk.LEFT)
524 # Create the approach control mode selection radiobuttons
525 self.selection = Tk.IntVar(self.frame, 0)
526 self.approach_mode = 0
527 self.red_enabled = True
528 self.yel_enabled = True
529 self.sig_enabled = True
530 self.B1 = Tk.Radiobutton(self.frame, text="Red", anchor='w',
531 command=self.mode_selected, variable=self.selection, value=1)
532 self.B1.pack(side=Tk.LEFT)
533 self.B1TT = common.CreateToolTip(self.B1, "Signal will remain at DANGER until the train approaches")
534 self.B2 = Tk.Radiobutton(self.frame, text="Yellow", anchor='w',
535 command=self.mode_selected, variable=self.selection, value=2)
536 self.B2.pack(side=Tk.LEFT)
537 self.B2TT = common.CreateToolTip(self.B2, "Signal will remain at CAUTION until the train approaches")
538 self.B3 = Tk.Radiobutton(self.frame, text="Red (on signals ahead)", anchor='w',
539 command=self.mode_selected, variable=self.selection, value=3)
540 self.B3.pack(side=Tk.LEFT)
541 self.B3TT = common.CreateToolTip(self.B3, "Signal will remain at DANGER until the train approaches "+
542 "(approach control will only be applied if there is a home signal ahead at danger)")
544 def mode_selected(self):
545 self.approach_mode = self.selection.get()
547 def route_selected(self):
548 if self.route.get_value():
549 if self.red_enabled: self.B1.configure(state="normal") 549 ↛ 550line 549 didn't jump to line 550, because the condition on line 549 was never false
550 else: self.B1.configure(state="disabled")
551 if self.yel_enabled: self.B2.configure(state="normal")
552 else: self.B2.configure(state="disabled")
553 if self.sig_enabled: self.B3.configure(state="normal")
554 else: self.B3.configure(state="disabled")
555 # Ensure the selection is valid
556 if self.approach_mode == 0: self.approach_mode = 1
557 if not self.red_enabled and self.approach_mode == 1: self.approach_mode = 2
558 if not self.yel_enabled and self.approach_mode == 2: self.approach_mode = 1
559 if not self.sig_enabled and self.approach_mode == 3: self.approach_mode = 1
560 self.selection.set(self.approach_mode)
561 else:
562 self.B1.configure(state="disabled")
563 self.B2.configure(state="disabled")
564 self.B3.configure(state="disabled")
565 self.selection.set(0)
567 def enable_route(self):
568 self.route.enable()
569 self.route_selected()
571 def disable_route(self):
572 self.route.disable()
573 self.route_selected()
575 def enable_red(self):
576 self.red_enabled = True
577 self.route_selected()
579 def disable_red(self):
580 self.red_enabled = False
581 self.route_selected()
583 def enable_yel(self):
584 self.yel_enabled = True
585 self.route_selected()
587 def disable_yel(self):
588 self.yel_enabled = False
589 self.route_selected()
591 def enable_sig_ahead(self):
592 self.sig_enabled = True
593 self.route_selected()
595 def disable_sig_ahead(self):
596 self.sig_enabled = False
597 self.route_selected()
599 def set_values(self, mode:int):
600 # The 'Mode' value represents the approach control mode that has been set
601 # release_on_red=1, release_on_yel=2, released_on_red_home_ahead=3
602 self.route.set_value(mode != 0)
603 self.approach_mode = mode
604 self.route_selected()
606 def get_values(self):
607 # The 'Mode' value represents the approach control mode that has been set
608 # release_on_red=1, release_on_yel=2, released_on_red_home_ahead=3
609 return (self.selection.get())
611 def approach_control_selected(self):
612 return self.route.get_value()
614#------------------------------------------------------------------------------------
615# Class for a Approach Control route frame (comprising selections for each route)
616# Public class instance methods provided by this class are:
617# "enable_release_on_red" - disables/blanks the "Release on Red" radio button
618# "disable_release_on_red" enables/loads the "Release on Red" radio button
619# "enable_release_on_yel" - disables/blanks the "Release on yellow" radio button
620# "disable_release_on_yel" enables/loads the "Release on yellow" radio button
621# "enable_release_on_red_sig_ahead" - disables/blanks the "Release on sig ahead" radio button
622# "disable_release_on_red_sig_ahead" enables/loads the "Release on sig ahead" radio button
623# "set_values" - sets the initial values for the check boxes & radio buttons)
624# "get_values" - get current last values for the check boxes & radio buttons
625# "is_selected" - returns whether the signal has been configured for approach control
626# Note that no route enable/disable functions are provided - External functions
627# should call the individal route_enable/disable functions for each element
628#------------------------------------------------------------------------------------
630class approach_control_frame():
631 def __init__(self, parent_frame):
632 # Create a label frame for the UI element
633 self.frame = Tk.LabelFrame(parent_frame, text="Approach control selections")
634 # Create a subframe for the context label
635 self.subframe1 = Tk.Frame(self.frame)
636 self.subframe1.pack(side=Tk.LEFT, padx=2, pady=2, fill='both')
637 self.label = Tk.Label(self.frame, text="Routes\nsubject to\napproach\ncontrol", anchor='w')
638 self.label.pack(side=Tk.LEFT)
639 # Create a subframe for the route elements
640 self.subframe2 = Tk.Frame(self.frame)
641 self.subframe2.pack(side=Tk.LEFT, padx=2, pady=2, fill='x', expand=True)
642 self.main=approach_control_route_element(self.subframe2, label="MAIN")
643 self.lh1=approach_control_route_element(self.subframe2, label="LH1")
644 self.lh2=approach_control_route_element(self.subframe2, label="LH2")
645 self.rh1=approach_control_route_element(self.subframe2, label="RH1")
646 self.rh2=approach_control_route_element(self.subframe2, label="RH2")
648 def enable_release_on_red(self):
649 self.main.enable_red()
650 self.lh1.enable_red()
651 self.lh2.enable_red()
652 self.rh1.enable_red()
653 self.rh2.enable_red()
655 def disable_release_on_red(self):
656 self.main.disable_red()
657 self.lh1.disable_red()
658 self.lh2.disable_red()
659 self.rh1.disable_red()
660 self.rh2.disable_red()
662 def enable_release_on_yel(self):
663 self.main.enable_yel()
664 self.lh1.enable_yel()
665 self.lh2.enable_yel()
666 self.rh1.enable_yel()
667 self.rh2.enable_yel()
669 def disable_release_on_yel(self):
670 self.main.disable_yel()
671 self.lh1.disable_yel()
672 self.lh2.disable_yel()
673 self.rh1.disable_yel()
674 self.rh2.disable_yel()
676 def enable_release_on_red_sig_ahead(self):
677 self.main.enable_sig_ahead()
678 self.lh1.enable_sig_ahead()
679 self.lh2.enable_sig_ahead()
680 self.rh1.enable_sig_ahead()
681 self.rh2.enable_sig_ahead()
683 def disable_release_on_red_sig_ahead(self):
684 self.main.disable_sig_ahead()
685 self.lh1.disable_sig_ahead()
686 self.lh2.disable_sig_ahead()
687 self.rh1.disable_sig_ahead()
688 self.rh2.disable_sig_ahead()
690 def set_values(self, approach_control:[int,]):
691 # Approach_Control comprises a list of routes [MAIN, LH1, LH2, RH1, RH2]
692 # Each element represents the approach control mode that has been set
693 # release_on_red=1, release_on_yel=2, released_on_red_home_ahead=3
694 self.main.set_values(approach_control[0])
695 self.lh1.set_values(approach_control[1])
696 self.lh2.set_values(approach_control[2])
697 self.rh1.set_values(approach_control[3])
698 self.rh2.set_values(approach_control[4])
700 def get_values(self):
701 # Approach_Control comprises a list of routes [MAIN, LH1, LH2, RH1, RH2]
702 # Each element represents the approach control mode that has been set
703 # release_on_red=1, release_on_yel=2, released_on_red_home_ahead=3
704 return ( [ self.main.get_values(),
705 self.lh1.get_values(),
706 self.lh2.get_values(),
707 self.rh1.get_values(),
708 self.rh2.get_values() ] )
710 def is_selected(self):
711 return ( self.main.approach_control_selected() or
712 self.lh1.approach_control_selected() or
713 self.lh2.approach_control_selected() or
714 self.rh1.approach_control_selected() or
715 self.rh2.approach_control_selected() )
717#------------------------------------------------------------------------------------
718# Top level Class for the Edit Signal Window Automation Tab
719#------------------------------------------------------------------------------------
721class signal_automation_tab():
722 def __init__(self, parent_tab, parent_object):
723 # Create the signal sensor frame (always packed)
724 self.gpio_sensors = signal_passed_sensor_frame(parent_tab, parent_object)
725 self.gpio_sensors.frame.pack(padx=2, pady=2, fill='x')
726 # Create a Frame for the track occupancy and general settings (always packed)
727 self.frame1 = Tk.Frame(parent_tab)
728 self.frame1.pack(padx=2, pady=2, fill='x')
729 self.track_occupancy = track_occupancy_frame(self.frame1)
730 self.track_occupancy.frame.pack(side=Tk.LEFT, padx=2, pady=2)
731 self.general_settings = general_settings_frame(self.frame1)
732 self.general_settings.frame.pack(side=Tk.LEFT, padx=2, pady=2, fill='both', expand=True)
733 # Create a Frame for the timed signal configuration (packed according to signal type)
734 self.timed_signal = timed_signal_frame(parent_tab, parent_object)
735 # Create a Frame for the Signal Approach control (packed according to signal type)
736 self.approach_control = approach_control_frame(parent_tab)
738######################################################################################