Coverage for /home/pi/Software/model-railway-signalling/model_railway_signals/editor/configure_signal_tab1.py: 95%
551 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 "Configuration" Tab
3#
4# Makes the following external API calls to library modules:
5# signals_common.sig_exists(id) - To see if the signal exists (local)
6# dcc_control.dcc_address_mapping(address) - To see if an address is already mapped
7#
8# Inherits the following common editor base classes (from common):
9# common.dcc_entry_box
10# common.state_box
11# common.check_box
12# common.entry_box
13# common.object_id_selection
14# common.selection_buttons
15#
16#------------------------------------------------------------------------------------
18import tkinter as Tk
20from . import common
21from ..library import signals_common
22from ..library import dcc_control
24#------------------------------------------------------------------------------------
25# Class for a signal_dcc_entry_box - builds on the common DCC Entry Box class
26# Class instance methods inherited from the parent class are:
27# "get_value" - will return the last valid entry box value (dcc address)
28# Public class instance methods provided by this child class are
29# "set_value" - set the initial value of the dcc_entry_box (int) - Also
30# sets the current item ID (int) for validation purposes
31# "validate" - Validates the DCC address is not mapped to another item
32#------------------------------------------------------------------------------------
34class signal_dcc_entry_box(common.dcc_entry_box):
35 def __init__(self, parent_frame, callback=None):
36 # We need the current Signal ID to validate the DCC Address entry
37 self.current_item_id = 0
38 super().__init__(parent_frame, callback=callback)
40 def validate(self):
41 # Do the basic item validation first (exists and not current item ID)
42 valid = super().validate(update_validation_status=False)
43 if valid and self.entry.get() != "":
44 # Ensure the address is not mapped to another signal or point. Note that to cater for Semaphore
45 # Signals with secondary distant arms we also need to check for Signal IDs + 100
46 dcc_address = int(self.entry.get())
47 dcc_mapping = dcc_control.dcc_address_mapping(dcc_address)
48 if dcc_mapping is not None and (dcc_mapping[0] != "Signal" or 48 ↛ 51line 48 didn't jump to line 51, because the condition on line 48 was never true
49 (dcc_mapping[1] != self.current_item_id and dcc_mapping[1] != self.current_item_id + 100)):
50 # We need to correct the mapped signal ID for secondary distants
51 if dcc_mapping[0] == "Signal" and dcc_mapping[1] > 99: dcc_mapping[1] = dcc_mapping[1] - 100
52 self.TT.text = ("DCC address is already mapped to "+dcc_mapping[0]+" "+str(dcc_mapping[1]))
53 valid = False
54 self.set_validation_status(valid)
55 return(valid)
57 def set_value(self, value:int, item_id:int):
58 self.current_item_id = item_id
59 super().set_value(value)
61#------------------------------------------------------------------------------------
62# Compound UI element for a signal_dcc_command_entry (address + command logic).
63# Uses the signal_dcc_entry_box and state_box classes. Note that the state_box
64# is only enabled when a valid DCC address has been entered into the entry_box.
65# Note the responsibility of the instantiating func/class to 'pack' the Frame of
66# the UI element - i.e. '<class_instance>.frame.pack()'
67#
68# Public class instance methods provided by this class are
69# "validate" - validate the current entry_box value and return True/false
70# "set_value" - will set the current value [address:int, state:bool] - Also
71# sets the current item ID (int) for validation purposes
72# "get_value" - will return the last "valid" value [address:int, state:bool]
73# "disable" - disables/blanks the entry_box (and associated state button)
74# "enable" enables/loads the entry_box (and associated state button)
75#------------------------------------------------------------------------------------
77class signal_dcc_command_entry():
78 def __init__(self, parent_frame):
79 # create a frame to pack the two elements into
80 self.frame = Tk.Frame(parent_frame)
81 # Create the address entry box and the associated dcc state box
82 self.EB = signal_dcc_entry_box(self.frame, callback=self.eb_updated)
83 self.EB.pack(side=Tk.LEFT)
84 self.CB = common.state_box(self.frame, label_off="OFF", label_on="ON",
85 width=4, tool_tip="Set the DCC logic for the command")
86 self.CB.pack(side=Tk.LEFT)
88 def eb_updated(self):
89 if self.EB.entry.get() == "":
90 self.CB.disable()
91 else: self.CB.enable()
93 def validate(self):
94 return (self.EB.validate())
96 def enable(self):
97 self.EB.enable()
98 self.eb_updated()
100 def disable(self):
101 self.EB.disable()
102 self.eb_updated()
104 def set_value(self, dcc_command:[int, bool], item_id:int):
105 # A DCC Command comprises a 2 element list of [DCC_Address, DCC_State]
106 self.EB.set_value(dcc_command[0], item_id)
107 self.CB.set_value(dcc_command[1])
108 self.eb_updated()
110 def get_value(self):
111 # Returns a 2 element list of [DCC_Address, DCC_State]
112 # When disabled (or empty) will always return [0, False]
113 # When invalid will return [last valid address, current state]
114 return([self.EB.get_value(), self.CB.get_value()])
116#------------------------------------------------------------------------------------
117# Class for the General Settings UI Element - Builds on the common checkbox class
118# Public class instance methods inherited from the base check box class are:
119# "set_value" - set the initial value of the Rotate checkbutton (int)
120# "get_value" - get the last "validated" value of the Rotate checkbutton (int)
121#------------------------------------------------------------------------------------
123class general_settings(common.check_box):
124 def __init__(self, parent_frame):
125 # Create a Label frame to hold the general settings UI element
126 # Packed onto the parent frame by the creating function/class
127 self.frame = Tk.LabelFrame(parent_frame,text="General Config")
128 # Create the "rotate" checkbutton and tool Tip
129 super().__init__(self.frame, label="Rotated",
130 tool_tip = "Select to rotate signal by 180 degrees")
131 self.pack()
133#------------------------------------------------------------------------------------
134# Class for a semaphore route arm element (comprising checkbox and DCC address Box)
135# Class instance methods provided by this class are:
136# "validate" - validate the DCC entry box value and returns True/false
137# "enable" - disables/blanks the checkbox and entry box
138# "disable" - enables/loads the checkbox and entry box
139# "set_element" - will set the element [enabled/disabled, address]
140# Also sets the current item ID (int) for validation purposes
141# "get_element" - returns the last "valid" value [enabled/disabled, address]
142#------------------------------------------------------------------------------------
144class semaphore_route_element():
145 def __init__(self, parent_frame, label:str, tool_tip:str, callback=None):
146 # Callback for select/deselect of the checkbox
147 self.callback = callback
148 # Create a frame for the UI element (always packed into the parent frame)
149 self.frame = Tk.Frame(parent_frame)
150 self.frame.pack()
151 # Create the checkbox and DCC entry Box (default tool tip for DCC Entry Box)
152 self.CB = common.check_box(parent_frame, label=label,
153 tool_tip=tool_tip, callback=self.cb_updated)
154 self.CB.pack(side=Tk.LEFT)
155 self.EB = signal_dcc_entry_box(parent_frame)
156 self.EB.pack(side=Tk.LEFT)
158 def cb_updated(self):
159 self.update_eb_state()
160 if self.callback is not None: self.callback()
162 def update_eb_state(self):
163 if self.CB.get_value(): self.EB.enable()
164 else: self.EB.disable()
166 def validate(self):
167 # Validate the DCC Address
168 return(self.EB.validate())
170 def enable0(self):
171 self.CB.enable()
172 self.update_eb_state()
174 def enable1(self):
175 self.CB.enable1()
176 self.update_eb_state()
178 def enable2(self):
179 self.CB.enable2()
180 self.update_eb_state()
182 def disable0(self):
183 self.CB.disable()
184 self.update_eb_state()
186 def disable1(self):
187 self.CB.disable1()
188 self.update_eb_state()
190 def disable2(self):
191 self.CB.disable2()
192 self.update_eb_state()
194 def set_element(self, signal_arm:[bool,int], item_id:int):
195 # Each signal element comprises [enabled/disabled, address]
196 self.CB.set_value(signal_arm[0])
197 self.EB.set_value(signal_arm[1], item_id)
198 self.update_eb_state()
200 def get_element(self):
201 # Each signal element comprises [enabled/disabled, address]
202 return( [self.CB.get_value(), self.EB.get_value()] )
204#------------------------------------------------------------------------------------
205# Class for a semaphore route arm group (comprising main, subsidary, and distant arms)
206# Uses the base semaphore_route_element class from above
207# Public Class instance methods are:
208# "validate" - validate the current entry box values and return True/false
209# "enable_route" - disables/blanks all checkboxes and entry boxes
210# "disable_route" - enables/loads all checkboxes and entry boxes
211# "enable_distant" - enables/loads the distant checkbox and entry box
212# "set_route" - will set the element [enabled/disabled, address]
213# Also sets the current item ID (int) for validation purposes
214# "get_route" - returns the last "valid" value [enabled/disabled, address]
215# The callbacks are made when the signal arms are selected or deselected
216#------------------------------------------------------------------------------------
218class semaphore_route_group():
219 def __init__(self, parent_frame, label:str,sig_arms_updated_callback=None,
220 sub_arms_updated_callback=None, dist_arms_updated_callback=None):
221 # Callback for change in signal arm selections
222 self.sig_arms_callback = sig_arms_updated_callback
223 self.sub_arms_callback = sub_arms_updated_callback
224 self.dist_arms_callback = dist_arms_updated_callback
225 # Create a frame for the UI element (always packed into the parent frame)
226 self.frame = Tk.Frame(parent_frame)
227 self.frame.pack()
228 # Create the lable and route elements (these are packed by the class instances)
229 self.label = Tk.Label(self.frame, anchor='w', width=5, text=label)
230 self.label.pack(side=Tk.LEFT)
231 self.sig = semaphore_route_element(self.frame, label="Main (home) arm ",
232 tool_tip= "Select to add a home signal arm for this route",
233 callback=self.sig_arms_updated)
234 self.sub = semaphore_route_element(self.frame, label="Subsidary arm ",
235 tool_tip="Select to add a subsidary signal arm for this route",
236 callback=self.sub_arms_updated)
237 self.dist = semaphore_route_element(self.frame, label="Distant arm ",
238 tool_tip="Select to add a distant signal arm for this route",
239 callback=self.dist_arms_updated)
241 def sig_arms_updated(self):
242 self.enable_disable_distant_arms()
243 if self.sig_arms_callback is not None: self.sig_arms_callback()
245 def sub_arms_updated(self):
246 if self.sub_arms_callback is not None: self.sub_arms_callback()
248 def dist_arms_updated(self):
249 if self.sig_arms_callback is not None: self.dist_arms_callback()
251 def enable_disable_distant_arms(self):
252 # A route can only have a secondary distant arm if there is a main home arm
253 # Use the 'enable0/disable0' functions ('enable1/disable1' is used to to enable/disable
254 # the entire route and 'enable2/disable2' is used to enable/disable individual sig arms)
255 if self.sig.get_element()[0]: self.dist.enable0()
256 else: self.dist.disable0()
258 def validate(self):
259 return(self.sig.validate() and self.sub.validate() and self.dist.validate())
261 def enable_route(self):
262 self.sig.enable1()
263 self.sub.enable1()
264 self.dist.enable1()
266 def disable_route(self):
267 self.sig.disable1()
268 self.sub.disable1()
269 self.dist.disable1()
271 def enable_signal(self):
272 self.sig.enable2()
274 def disable_signal(self):
275 self.sig.disable2()
277 def enable_subsidary(self):
278 self.sub.enable2()
280 def disable_subsidary(self):
281 self.sub.disable2()
283 def enable_distant(self):
284 self.dist.enable2()
286 def disable_distant(self):
287 self.dist.disable2()
289 def set_route(self, signal_elements:[[bool,int],], item_id:int):
290 # Signal Group comprises: [signal, subsidary, distant]
291 # Each signal element comprises [enabled/disabled, address]
292 self.sig.set_element(signal_elements[0], item_id)
293 self.sub.set_element(signal_elements[1], item_id)
294 self.dist.set_element(signal_elements[2], item_id)
295 self.enable_disable_distant_arms()
297 def get_route(self):
298 # Signal Group comprises: [signal, subsidary, distant]
299 # Each signal element comprises [enabled/disabled, address]
300 return ( [ self.sig.get_element(),
301 self.sub.get_element(),
302 self.dist.get_element() ] )
304#------------------------------------------------------------------------------------
305# Class for the semaphore signal arms (comprising all possible signal arm combinations)
306# Uses the base semaphore_route_group class from above
307# Public Class instance methods are:
308# "validate" - validate the current entry box values and return True/false
309# "disable_routes" - disables/blanks all checkboxes and entry boxes apart from MAIN
310# "enable_routes" - enables/loads all checkboxes and entry boxes apart from MAIN
311# "disable_distants" - disables/blanks all distant checkboxes and entry boxes
312# "enable_distants" - enables/loads all distant checkboxes and entry boxes
313# "disable_subsidaries" - disables/blanks all subsidary checkboxes and entry boxes
314# "enable_subsidaries" - enables/loads all subsidary checkboxes and entry boxes
315# "set_arms" - will set all ui elements (enabled/disabled, addresses)
316# Also sets the current item ID (int) for validation purposes
317# "get_arms" - returns the last "valid" values (enabled/disabled, addresses)
318# The callbacks are made when the signal arms are selected or deselected
319#------------------------------------------------------------------------------------
321class semaphore_signal_arms():
322 def __init__(self, parent_frame, sig_arms_updated, subs_arms_updated, dist_arms_updated):
323 # Create a frame for this UI element (packed by the creating function/class)
324 self.frame = Tk.LabelFrame(parent_frame, text="Semaphore Signal Arms and DCC Addresses")
325 # Create the route group for each route (packed into the frame by the class instances)
326 self.main = semaphore_route_group(self.frame, label="Main",
327 sig_arms_updated_callback=sig_arms_updated,
328 sub_arms_updated_callback=subs_arms_updated,
329 dist_arms_updated_callback=dist_arms_updated)
330 self.lh1 = semaphore_route_group(self.frame, label="LH1",
331 sig_arms_updated_callback=sig_arms_updated,
332 sub_arms_updated_callback=subs_arms_updated,
333 dist_arms_updated_callback=dist_arms_updated)
334 self.lh2 = semaphore_route_group(self.frame, label="LH2",
335 sig_arms_updated_callback=sig_arms_updated,
336 sub_arms_updated_callback=subs_arms_updated,
337 dist_arms_updated_callback=dist_arms_updated)
338 self.rh1 = semaphore_route_group(self.frame, label="RH1",
339 sig_arms_updated_callback=sig_arms_updated,
340 sub_arms_updated_callback=subs_arms_updated,
341 dist_arms_updated_callback=dist_arms_updated)
342 self.rh2 = semaphore_route_group(self.frame, label="RH2",
343 sig_arms_updated_callback=sig_arms_updated,
344 sub_arms_updated_callback=subs_arms_updated,
345 dist_arms_updated_callback=dist_arms_updated)
346 # The signal arm for the main route cannot be deselected so we need to
347 # set the value and then disable the base tkinter widget (we can't use
348 # the disable function as this would also 'blank' the checkbox)
349 self.main.sig.CB.set_value(True)
350 self.main.sig.CB.config(state="disabled")
352 def validate(self):
353 return(self.main.validate() and self.lh1.validate() and self.lh2.validate()
354 and self.rh1.validate() and self.rh2.validate())
356 def enable_diverging_routes(self):
357 self.lh1.enable_route()
358 self.lh2.enable_route()
359 self.rh1.enable_route()
360 self.rh2.enable_route()
362 def disable_diverging_routes(self):
363 self.lh1.disable_route()
364 self.lh2.disable_route()
365 self.rh1.disable_route()
366 self.rh2.disable_route()
368 def enable_main_route(self):
369 # Enable the main signal route. Note that when the route is enabled
370 # the main signal arm is always selected (and cannot be de-selected)
371 self.main.sig.CB.set_value(True)
372 self.main.enable_route()
373 self.main.sig.CB.config(state="disabled")
375 def disable_main_route(self):
376 self.main.disable_route()
378 def enable_subsidaries(self):
379 self.main.enable_subsidary()
380 self.lh1.enable_subsidary()
381 self.lh2.enable_subsidary()
382 self.rh1.enable_subsidary()
383 self.rh2.enable_subsidary()
385 def disable_subsidaries(self):
386 self.main.disable_subsidary()
387 self.lh1.disable_subsidary()
388 self.lh2.disable_subsidary()
389 self.rh1.disable_subsidary()
390 self.rh2.disable_subsidary()
392 def enable_distants(self):
393 self.main.enable_distant()
394 self.lh1.enable_distant()
395 self.lh2.enable_distant()
396 self.rh1.enable_distant()
397 self.rh2.enable_distant()
399 def disable_distants(self):
400 self.main.disable_distant()
401 self.lh1.disable_distant()
402 self.lh2.disable_distant()
403 self.rh1.disable_distant()
404 self.rh2.disable_distant()
406 def set_arms(self, signal_arms:[[[bool,int],],], item_id:int):
407 # Signal arm list comprises:[main, LH1, LH2, RH1, RH2]
408 # Each Route element comprises: [signal, subsidary, distant]
409 # Each signal element comprises [enabled/disabled, address]
410 self.main.set_route(signal_arms[0], item_id)
411 self.lh1.set_route(signal_arms[1], item_id)
412 self.lh2.set_route(signal_arms[2], item_id)
413 self.rh1.set_route(signal_arms[3], item_id)
414 self.rh2.set_route(signal_arms[4], item_id)
416 def get_arms(self):
417 # Signal arm list comprises:[main, LH1, LH2, RH1, RH2]
418 # Each Route element comprises: [signal, subsidary, distant]
419 # Each signal element comprises [enabled/disabled, address]
420 # Note that the MAIN signal arm is always enabled (for semaphores)
421 main_route_with_signal_arm_enabled = self.main.get_route()
422 main_route_with_signal_arm_enabled[0][0] = True
423 return ( [ main_route_with_signal_arm_enabled,
424 self.lh1.get_route(),
425 self.lh2.get_route(),
426 self.rh1.get_route(),
427 self.rh2.get_route() ] )
429#------------------------------------------------------------------------------------
430# Class to create a sequence of DCC selection boxes - for colour light signal aspects,
431# feather route indications and theatre route indications
432# Public Class instance methods are:
433# "validate_addresses" - validate the current entry box values and return True/false
434# "enable_addresses" - disables/blanks all entry boxes (and state buttons)
435# "disable_addresses" enables/loads all entry box (and state buttona)
436# "set_addresses" - will set the values of the entry boxes (pass in a list)
437# Also sets the current item ID (int) for validation purposes
438# "get_addresses" - will return a list of the last "valid" entries
439#------------------------------------------------------------------------------------
441class dcc_entry_boxes:
442 def __init__(self, parent_frame):
443 # Create the DCC command entry elements (packed directly into parent frame)
444 self.dcc1 = signal_dcc_command_entry(parent_frame)
445 self.dcc1.frame.pack(side=Tk.LEFT)
446 self.dcc2 = signal_dcc_command_entry(parent_frame)
447 self.dcc2.frame.pack(side=Tk.LEFT)
448 self.dcc3 = signal_dcc_command_entry(parent_frame)
449 self.dcc3.frame.pack(side=Tk.LEFT)
450 self.dcc4 = signal_dcc_command_entry(parent_frame)
451 self.dcc4.frame.pack(side=Tk.LEFT)
452 self.dcc5 = signal_dcc_command_entry(parent_frame)
453 self.dcc5.frame.pack(side=Tk.LEFT)
454 self.dcc6 = signal_dcc_command_entry(parent_frame)
455 self.dcc6.frame.pack(side=Tk.LEFT)
457 def validate_addresses(self):
458 return ( self.dcc1.validate() and
459 self.dcc2.validate() and
460 self.dcc3.validate() and
461 self.dcc4.validate() and
462 self.dcc5.validate() and
463 self.dcc6.validate() )
465 def set_addresses(self, address_list:[[int,bool],], item_id:int):
466 # DCC command sequence comprises [dcc1, dcc2, dcc3, dcc4, dcc5, dcc6]
467 # Each dcc command element comprises: [dcc_address, dcc_state]
468 self.dcc1.set_value(address_list[0], item_id)
469 self.dcc2.set_value(address_list[1], item_id)
470 self.dcc3.set_value(address_list[2], item_id)
471 self.dcc4.set_value(address_list[3], item_id)
472 self.dcc5.set_value(address_list[4], item_id)
473 self.dcc6.set_value(address_list[5], item_id)
475 def get_addresses(self):
476 # DCC command sequence comprises [dcc1, dcc2, dcc3, dcc4, dcc5, dcc6]
477 # Each dcc command element comprises: [dcc_address, dcc_state]
478 return( [ self.dcc1.get_value(),
479 self.dcc2.get_value(),
480 self.dcc3.get_value(),
481 self.dcc4.get_value(),
482 self.dcc5.get_value(),
483 self.dcc6.get_value() ] )
485 def enable_addresses(self):
486 self.dcc1.enable()
487 self.dcc2.enable()
488 self.dcc3.enable()
489 self.dcc4.enable()
490 self.dcc5.enable()
491 self.dcc6.enable()
493 def disable_addresses(self):
494 self.dcc1.disable()
495 self.dcc2.disable()
496 self.dcc3.disable()
497 self.dcc4.disable()
498 self.dcc5.disable()
499 self.dcc6.disable()
501#------------------------------------------------------------------------------------
502# Classes to create the DCC Entry boxes for a colour light signal aspect
503# Builds on the common dcc_entry_boxes class above.
504# Inherited Class instance methods are:
505# "validate_addresses" - validate the current entry box values and return True/false
506# "enable_addresses" - disables/blanks all entry boxes (and state buttons)
507# "disable_addresses" enables/loads all entry box (and state buttona)
508# "set_addresses" - will set the values of the entry boxes (pass in a list)
509# Also sets the current item ID (int) for validation purposes
510# "get_addresses" - will return a list of the last "valid" entries
511#------------------------------------------------------------------------------------
513class colour_light_aspect(dcc_entry_boxes):
514 def __init__(self, parent_frame, label:str):
515 # Create a frame for this UI element (always packed)
516 self.frame = Tk.Frame(parent_frame)
517 self.frame.pack()
518 # Create the label for the DCC command sequence
519 self.label = Tk.Label(self.frame, width=12, text=label, anchor='w')
520 self.label.pack(side=Tk.LEFT)
521 # Call the init function of the class we are inheriting from
522 # The DCC entry boxes get packed into the frame by the parent class
523 super().__init__(self.frame)
525#------------------------------------------------------------------------------------
526# Classes to create the DCC entry UI element for colour light signal aspects
527# Class instance methods to use externally are:
528# "validate" - validate all current DCC entry box values
529# "set_addresses" - set the DCC command sequences for the aspects (pass in a list)
530# Also sets the current item ID (int) for validation purposes
531# "get_addresses" - return a list of the "validated" DCC command sequences
532# "set_subsidary" - set the subsidary signal status [has_subsidary, dcc_address]
533# Also sets the current item ID (int) for validation purposes
534# "get_subsidary" - return the subsidary signal status [has_subsidary, dcc_address]
535# "enable_subsidary" - enables/loads the subsidary signal selection (CB/address)
536# "disable_subsidary" - disables/clears the subsidary signal selection (CB/address)
537# "enable_aspects" - enables/loads the dcc command sequences for all aspects
538# "disable_aspects" - disables/clears the dcc command sequences for all aspects
539# The callback is made when the subsidary signal selection is updated
540#------------------------------------------------------------------------------------
542class colour_light_aspects():
543 def __init__(self, parent_frame, callback=None):
544 # Callback for select/deselect of the subsidary signal
545 self.callback = callback
546 # Create a label frame (packed by the creating function/class)
547 self.frame = Tk.LabelFrame(parent_frame,
548 text="DCC command sequences for Colour Light signal aspects")
549 # Create the DCC Entry Elements (packed into the frame by the parent class)
550 self.red = colour_light_aspect(self.frame, label="Danger")
551 self.grn = colour_light_aspect(self.frame, label="Proceed")
552 self.ylw = colour_light_aspect(self.frame, label="Caution")
553 self.dylw = colour_light_aspect(self.frame, label="Prelim Caution")
554 self.fylw = colour_light_aspect(self.frame, label="Flash Caution")
555 self.fdylw = colour_light_aspect(self.frame, label="Flash Prelim")
556 # Create a subframe to hold the subsidary signal entry box (always packed)
557 self.subframe = Tk.Frame(self.frame)
558 self.subframe.pack()
559 self.CB = common.check_box(self.subframe, label="Subsidary signal",
560 tool_tip="Select to add a seperate calling on aspect",callback=self.sub_updated)
561 self.CB.pack(side=Tk.LEFT, padx=2, pady=2)
562 self.EB = signal_dcc_entry_box(self.subframe)
563 self.EB.pack(side=Tk.LEFT, padx=2, pady=2)
565 def sub_updated(self):
566 self.update_eb_state()
567 if self.callback is not None: self.callback()
569 def update_eb_state(self):
570 if self.CB.get_value(): self.EB.enable()
571 else: self.EB.disable()
573 def validate(self):
574 return ( self.grn.validate_addresses() and
575 self.red.validate_addresses() and
576 self.ylw.validate_addresses() and
577 self.dylw.validate_addresses() and
578 self.fylw.validate_addresses() and
579 self.fdylw.validate_addresses() and
580 self.EB.validate() )
582 def set_addresses(self, addresses:[[[int,bool],],], item_id:int):
583 # The Colour Light Aspects command sequences are: [grn, red, ylw, dylw, fylw, fdylw]
584 # Each DCC command sequence comprises [dcc1, dcc2, dcc3, dcc4, dcc5, dcc6]
585 # Each DCC command comprises: [dcc_address, dcc_state]
586 self.grn.set_addresses(addresses[0], item_id)
587 self.red.set_addresses(addresses[1], item_id)
588 self.ylw.set_addresses(addresses[2], item_id)
589 self.dylw.set_addresses(addresses[3], item_id)
590 self.fylw.set_addresses(addresses[4], item_id)
591 self.fdylw.set_addresses(addresses[5], item_id)
593 def get_addresses(self):
594 # The Colour Light Aspects command sequences are: [grn, red, ylw, dylw, fylw, fdylw]
595 # Each DCC command sequence comprises [dcc1, dcc2, dcc3, dcc4, dcc5, dcc6]
596 # Each DCC command comprises: [dcc_address, dcc_state]
597 return( [self.grn.get_addresses(),
598 self.red.get_addresses(),
599 self.ylw.get_addresses(),
600 self.dylw.get_addresses(),
601 self.fylw.get_addresses(),
602 self.fdylw.get_addresses() ] )
604 def set_subsidary(self, subsidary:[bool,int], item_id:int):
605 # Subsidary is defined as [has_subsidary, dcc_address]
606 self.CB.set_value(subsidary[0])
607 self.EB.set_value(subsidary[1], item_id)
608 self.update_eb_state()
610 def get_subsidary(self):
611 # Subsidary is defined as [has_subsidary, dcc_address]
612 return([self.CB.get_value(), self.EB.get_value()])
614 def enable_subsidary(self):
615 self.CB.enable()
616 self.update_eb_state()
618 def disable_subsidary(self):
619 self.CB.disable()
620 self.update_eb_state()
622 def enable_aspects(self):
623 self.grn.enable_addresses()
624 self.red.enable_addresses()
625 self.ylw.enable_addresses()
626 self.dylw.enable_addresses()
627 self.fylw.enable_addresses()
628 self.fdylw.enable_addresses()
630 def disable_aspects(self):
631 self.grn.disable_addresses()
632 self.red.disable_addresses()
633 self.ylw.disable_addresses()
634 self.dylw.disable_addresses()
635 self.fylw.disable_addresses()
636 self.fdylw.disable_addresses()
638#------------------------------------------------------------------------------------
639# Class for a Theatre Route character entry Box - uses base common.entry_box class
640# Public class instance methods inherited from the base Entry Box class are:
641# "disable" - disables/blanks the entry box
642# "enable" enables/loads the entry box (with the last value)
643# "set_value" - set the initial value of the entry box (string)
644# "get_value" - get the last "validated" value of the entry box (string)
645# Public class instance methods overridden by this class are
646# "validate" - Validates either blank or a single character
647#------------------------------------------------------------------------------------
649class theatre_route_entry_box(common.entry_box):
650 def __init__(self, parent_frame, callback=None):
651 # Call the parent class init function to create the EB
652 super().__init__(parent_frame, width=2, callback=callback,
653 tool_tip = "Specify the character to be displayed for this route")
655 def validate(self):
656 # Ensure only one character has been entered
657 if len(self.entry.get()) <= 1: 657 ↛ 660line 657 didn't jump to line 660, because the condition on line 657 was never false
658 valid = True
659 else:
660 self.TT.text = "More than one theatre character has been entered"
661 valid = False
662 self.set_validation_status(valid)
663 return (valid)
665#------------------------------------------------------------------------------------
666# Class to create a Theatre route element with an entry box for the displayed character
667# and the associated DCC command sequence. Inherits from the dcc_entry_boxes class (above)
668# Inherited Class instance methods are:
669# "enable_addresses" - disables/blanks all entry boxes (and state buttons)
670# "disable_addresses" enables/loads all entry box (and state buttona)
671# Additional Class instance functions are:
672# "validate" - validate all current entry boxes (theatre character and dcc addresses)
673# "enable_selection" - disables/blanks the theatre entry box & DCC command list
674# "disable_selection" enables/loads the theatre entry box & DCC command list
675# "set_theatre" - set the values (character & dcc commands) for the theatre
676# Also sets the current item ID (int) for validation purposes
677# "get_theatre" - return the values (character & dcc commands) for the theatre
678#------------------------------------------------------------------------------------
680class theatre_route_element(dcc_entry_boxes):
681 def __init__(self, parent_frame, label:str, width:int, callback=None,
682 enable_addresses_on_selection:bool=False):
683 # Create a frame for this UI element (always packed in the parent frame)
684 self.frame = Tk.Frame(parent_frame)
685 self.frame.pack()
686 # Callback to make when the route selections change (Theatre Char EB changes)
687 self.callback = callback
688 # If the enable_addresses_on_selection flag is set to TRUE then the DCC address EBs
689 # will be enabled/disabled when the Theatre character is changed. If false then the current
690 # state of the EBs (enabled or disabled) remains unchanged. This is to support the MAIN
691 # route which will always need a DCC address sequence even if there is no Theartre character
692 self.enable_addresses_on_selection = enable_addresses_on_selection
693 # Create the label and entry box for the theatre character
694 self.label = Tk.Label(self.frame, width=width, text=label, anchor='w')
695 self.label.pack(side=Tk.LEFT)
696 self.EB = theatre_route_entry_box(self.frame, callback=self.selection_updated)
697 self.EB.pack(side=Tk.LEFT)
698 # Call the init function of the class we are inheriting from
699 # The DCC entry boxes get packed into the frame by the parent class
700 super().__init__(self.frame)
702 def selection_updated(self):
703 self.update_addresses()
704 if self.callback is not None: self.callback()
706 def update_addresses(self):
707 # Enable/disable the DCC entry boxes if the route is enabled
708 if self.enable_addresses_on_selection:
709 if self.EB.entry.get() != "": self.enable_addresses()
710 else: self.disable_addresses()
712 def validate(self):
713 # Validate the Theatre character EB and all DCC Address EBs
714 return (self.EB.validate() and self.validate_addresses())
716 def set_theatre(self,theatre:[str,[[int,bool],]], item_id:int):
717 # Each route element comprises: [character, DCC_command_sequence]
718 # Each DCC command sequence comprises: [dcc1, dcc2, dcc3, dcc4, dcc5, dcc6]
719 # Each DCC command element comprises: [dcc_address, dcc_state]
720 self.EB.set_value(theatre[0])
721 self.set_addresses(theatre[1], item_id)
722 self.update_addresses()
724 def get_theatre(self):
725 # Each route element comprises: [character, DCC_command_sequence]
726 # Each DCC command sequence comprises: [dcc1, dcc2, dcc3, dcc4, dcc5, dcc6]
727 # Each DCC command element comprises: [dcc_address, dcc_state]
728 return([self.EB.get_value(), self.get_addresses()])
730 def enable_selection(self):
731 self.EB.enable()
732 self.update_addresses()
734 def disable_selection(self):
735 self.EB.disable()
736 self.disable_addresses()
738#------------------------------------------------------------------------------------
739# Class to create the DCC entry UI element for a Theatre Route Indicator
740# Class instance functions to use externally are:
741# "validate" - validate the entry box values (theatre character and dcc addresses)
742# "set_theatre" - set the characters/addresses for the theatre [main,lh1,lh2,rh1,rh2]
743# Also sets the current item ID (int) for validation purposes
744# "get_theatre" - get the characters/addresses for the theatre [main,lh1,lh2,rh1,rh2]
745# "set_auto_inhibit" - set the "auto inhibit on DANGER" flag for the DCC route indications
746# "get_auto_inhibit" - get the "auto inhibit on DANGER" flag for the DCC route indications
747# "enable_selection" - enables all entries
748# "disable_selection" - disables all entries
749# The Callback will be made on route selection change (theatre character EB change)
750#------------------------------------------------------------------------------------
752class theatre_route_indications:
753 def __init__(self, parent_frame, callback=None):
754 # Create a label frame for the route selections. We don't pack this element
755 # as the frame gets packed/unpacked depending on UI selections
756 self.frame = Tk.LabelFrame(parent_frame, text="Theatre route indications "+
757 "and associated DCC command sequences")
758 # Create the individual route selection elements.
759 # The MAIN route DCC address EBs remain enabled even if there is no theatre route
760 # The MAIN element is therefore created with enable_addresses_on_selection=False
761 self.dark = theatre_route_element(self.frame, label="(Dark)", width=5,
762 callback=callback, enable_addresses_on_selection=True)
763 self.main = theatre_route_element(self.frame, label="MAIN", width=5,
764 callback=callback, enable_addresses_on_selection=False)
765 self.lh1 = theatre_route_element(self.frame, label="LH1", width=5,
766 callback=callback, enable_addresses_on_selection=True)
767 self.lh2 = theatre_route_element(self.frame, label="LH2", width=5,
768 callback=callback, enable_addresses_on_selection=True)
769 self.rh1 = theatre_route_element(self.frame, label="RH1", width=5,
770 callback=callback, enable_addresses_on_selection=True)
771 self.rh2 = theatre_route_element(self.frame, label="RH2", width=5,
772 callback=callback, enable_addresses_on_selection=True)
773 # The EB for DARK (signal at red - no route indications displyed) is always
774 # disabled so it can never be selected (not really a route indication as such)
775 self.dark.disable_selection()
776 # Create the checkbox and tool tip for auto route inhibit selection
777 self.CB = common.check_box(self.frame, label="Auto inhibit route indications on DANGER",
778 callback=self.auto_inhibit_update, tool_tip = "Select if the DCC signal automatically " +
779 "inhibits route indications if the signal is at DANGER - If not then the DCC " +
780 "commands to inhibit all route indications (dark) must be specified")
781 self.CB.pack(padx=2, pady=2)
783 def auto_inhibit_update(self):
784 if self.CB.get_value(): self.dark.disable_addresses() 784 ↛ exitline 784 didn't return from function 'auto_inhibit_update'
785 else: self.dark.enable_addresses()
787 def validate(self):
788 # Validate all the Theatre EBs and DCC Address entry boxes for all routes and DARK
789 return ( self.dark.validate() and
790 self.main.validate() and
791 self.lh1.validate() and
792 self.lh2.validate() and
793 self.rh1.validate() and
794 self.rh2.validate() )
796 def set_theatre(self, theatre:[[str,[[int,bool],],],], item_id:int):
797 # The Theatre route list comprises: [dark, main, lh1, lh2, rh1, rh2]
798 # Each route element comprises: [character, DCC_command_sequence]
799 # Each DCC command sequence comprises [dcc1, dcc2, dcc3, dcc4, dcc5, dcc6]
800 # Each DCC command comprises: [dcc_address, dcc_state]
801 self.dark.set_theatre(theatre[0], item_id)
802 self.main.set_theatre(theatre[1], item_id)
803 self.lh1.set_theatre(theatre[2], item_id)
804 self.lh2.set_theatre(theatre[3], item_id)
805 self.rh1.set_theatre(theatre[4], item_id)
806 self.rh2.set_theatre(theatre[5], item_id)
807 self.auto_inhibit_update()
809 def get_theatre(self):
810 # The Theatre route list comprises: [dark, main, lh1, lh2, rh1, rh2]
811 # Each route element comprises: [character, DCC_command_sequence]
812 # Each DCC command sequence comprises [dcc1, dcc2, dcc3, dcc4, dcc5, dcc6]
813 # Each DCC command comprises: [dcc_address, dcc_state]
814 # Note that the DARK aspect character is always present in the configuration
815 dark_theatre_configuration_enabled = self.dark.get_theatre()
816 dark_theatre_configuration_enabled[0]="#"
817 return( [dark_theatre_configuration_enabled,
818 self.main.get_theatre(),
819 self.lh1.get_theatre(),
820 self.lh2.get_theatre(),
821 self.rh1.get_theatre(),
822 self.rh2.get_theatre() ] )
824 def enable_selection(self):
825 # Enable the Theatre EBs for diverging routes (will also enable the address EBs)
826 self.lh1.enable_selection()
827 self.lh2.enable_selection()
828 self.rh1.enable_selection()
829 self.rh2.enable_selection()
830 # The DCC Address EBs for MAIN are enabled even if no theatre character is selected
831 self.main.enable_addresses()
832 self.main.enable_selection()
833 # Enable the "auto inhibit route" CB
834 self.CB.enable()
835 # Enabling of the "dark" DCC address EBs will depend on the state of the
836 # auto inhibit checkbox (the "dark" Theatre EB remains disabled and blank)
837 self.auto_inhibit_update()
839 def disable_selection(self):
840 # Only disable the "dark" DCC address EBs (the CB is always disabled)
841 self.dark.disable_addresses()
842 # Disable the CBs for all routes (will also disable the address EBs)
843 self.main.disable_selection()
844 self.lh1.disable_selection()
845 self.lh2.disable_selection()
846 self.rh1.disable_selection()
847 self.rh2.disable_selection()
848 # Disable the "auto inhibit route" CB
849 self.CB.disable()
851 def set_auto_inhibit(self, auto_inhibit:bool):
852 self.CB.set_value(auto_inhibit)
853 self.auto_inhibit_update()
855 def get_auto_inhibit(self):
856 return(self.CB.get_value())
858#------------------------------------------------------------------------------------
859# Class to create Feather route indication with a check box to enable the route indication
860# and the associated DCC command sequence. Inherits from the dcc_entry_boxes class (above)
861# Classes inherited from the parent class are:
862# "set_addresses" - will set the values of the entry boxes (pass in a list)
863# Also sets the current item ID (int) for validation purposes
864# "get_addresses" - will return a list of the last "valid" entries
865# "enable_addresses" - disables/blanks all entry boxes (and state buttons)
866# "disable_addresses" enables/loads all entry box (and state buttona)
867# Additional Class instance functions are:
868# "validate" - validate all current entry box values and return True/false
869# "enable_selection" - disables/blanks the route selection check box
870# "disable_selection" enables/loads the route selection check box
871# "set_feather" - set the state of the "Feather" checkbox
872# "get_feather" - return the state of the "Feather" checkbox
873#------------------------------------------------------------------------------------
875class feather_route_element(dcc_entry_boxes):
876 def __init__(self, parent_frame, label:str, width:int, callback=None,
877 enable_addresses_on_selection=False):
878 # Create a frame for this UI element (always packed in the parent frame)
879 self.frame = Tk.Frame(parent_frame)
880 self.frame.pack()
881 # Callback to make when the route selections change (enabled/disabled)
882 self.callback = callback
883 # If the enable_addresses_on_selection flag is set to TRUE then the DCC address EBs
884 # will be enabled/disabled when the route checkbox is changed. If false then the current
885 # state of the EBs (enabled or disabled) remains unchanged. This is to support the MAIN
886 # route which will always need a DCC address sequence even if there is no feather
887 self.enable_addresses_on_selection = enable_addresses_on_selection
888 # Create the label and checkbox for the feather route selection
889 self.label = Tk.Label(self.frame, width=width, text=label, anchor='w')
890 self.label.pack(side=Tk.LEFT)
891 self.CB = common.check_box(self.frame, callback=self.selection_updated, label="",
892 tool_tip="Select to add a feather indication for this route")
893 self.CB.pack(side=Tk.LEFT)
894 # Call the init function of the class we are inheriting from
895 # The DCC entry boxes get packed into the frame by the parent class
896 super().__init__(self.frame)
898 def selection_updated(self):
899 self.update_addresses()
900 if self.callback is not None: self.callback()
902 def update_addresses(self):
903 # Enable/disable the DCC entry boxes if enabled for this DCC entry element
904 if self.enable_addresses_on_selection:
905 if self.CB.get_value(): self.enable_addresses()
906 else: self.disable_addresses()
908 def validate(self):
909 return (self.validate_addresses())
911 def set_feather(self, state:bool):
912 self.CB.set_value(state)
913 self.update_addresses()
915 def get_feather(self):
916 return(self.CB.get_value())
918 def enable_selection(self):
919 self.CB.enable()
920 self.update_addresses()
922 def disable_selection(self):
923 self.CB.disable()
924 self.disable_addresses()
926#------------------------------------------------------------------------------------
927# Class to create the DCC entry UI element for Feather Route Indications
928# Class instance functions to use externally are:
929# "validate" - validate the current entry box values and return True/false
930# "set_addresses" - set the values of the DCC addresses/states (pass in a list)
931# Also sets the current item ID (int) for validation purposes
932# "get_addresses" - return a list of the "validated" DCC addresses/states
933# "set_feathers" - set the state of the feathers [main,lh1,lh2,rh1,rh2]
934# "get_feathers" - get the state of the feathers [main,lh1,lh2,rh1,rh2]
935# "set_auto_inhibit" - set the "auto inhibit on DANGER" selection
936# "get_auto_inhibit" - get the "auto inhibit on DANGER" selection
937# "enable_feathers" - enables all entries
938# "disable_feathers" - disables all entries
939# "disable_addresses" enables/loads all entry box (and state buttona)
940# The Callback will be made on route selection change (enabled/disabled)
941#------------------------------------------------------------------------------------
943class feather_route_indications:
944 def __init__(self, parent_frame, callback):
945 # Create a label frame for the route selections. We don't pack this element
946 # as the frame gets packed/unpacked depending on UI selections
947 self.frame = Tk.LabelFrame(parent_frame, text="Feather Route Indications "+
948 "and associated DCC command sequences")
949 # Create the individual route selection elements.
950 # The MAIN route DCC address EBs remain enabled even if there is no route feather
951 # The MAIN element is therefore created with enable_addresses_on_selection=False
952 self.dark = feather_route_element(self.frame, label="(Dark)", width=5,
953 callback=callback, enable_addresses_on_selection=True)
954 self.main = feather_route_element(self.frame, label="MAIN", width=5,
955 callback=callback, enable_addresses_on_selection=False)
956 self.lh1 = feather_route_element(self.frame, label="LH1", width=5,
957 callback=callback, enable_addresses_on_selection=True)
958 self.lh2 = feather_route_element(self.frame, label="LH2", width=5,
959 callback=callback, enable_addresses_on_selection=True)
960 self.rh1 = feather_route_element(self.frame, label="RH1", width=5,
961 callback=callback, enable_addresses_on_selection=True)
962 self.rh2 = feather_route_element(self.frame, label="RH2", width=5,
963 callback=callback, enable_addresses_on_selection=True)
964 # The CB for DARK (signal at red - no route indications displyed) is always
965 # disabled so it can never be selected (not really a route indication as such)
966 self.dark.disable_selection()
967 # Create the checkbox and tool tip for auto route inhibit
968 self.CB = common.check_box(self.frame, label="Auto inhibit route indications on DANGER",
969 callback=self.auto_inhibit_update, tool_tip = "Select if the DCC signal automatically " +
970 "inhibits route indications if the signal is at DANGER - If not then the DCC " +
971 "commands to inhibit all route indications (dark) must be specified")
972 self.CB.pack(padx=2, pady=2)
974 def auto_inhibit_update(self):
975 if self.CB.get_value(): self.dark.disable_addresses() 975 ↛ exitline 975 didn't return from function 'auto_inhibit_update'
976 else: self.dark.enable_addresses()
978 def validate(self):
979 # Validate all the DCC Address entry boxes for all routes and DARK
980 return ( self.dark.validate() and
981 self.main.validate() and
982 self.lh1.validate() and
983 self.lh2.validate() and
984 self.rh1.validate() and
985 self.rh2.validate() )
987 def set_addresses(self, addresses:[[[int,bool],],], item_id:int):
988 # The Feather Route address list comprises: [dark, main, lh1, lh2, rh1, rh2]
989 # Each route element comprises: [DCC_command_sequence]
990 # Each DCC command sequence comprises [dcc1, dcc2, dcc3, dcc4, dcc5, dcc6]
991 # Each DCC command comprises: [dcc_address, dcc_state]
992 self.dark.set_addresses(addresses[0], item_id)
993 self.main.set_addresses(addresses[1], item_id)
994 self.lh1.set_addresses(addresses[2], item_id)
995 self.lh2.set_addresses(addresses[3], item_id)
996 self.rh1.set_addresses(addresses[4], item_id)
997 self.rh2.set_addresses(addresses[5], item_id)
999 def get_addresses(self):
1000 # The Feather Route address list comprises: [dark, main, lh1, lh2, rh1, rh2]
1001 # Each route element comprises: [DCC_command_sequence]
1002 # Each DCC command sequence comprises [dcc1, dcc2, dcc3, dcc4, dcc5, dcc6]
1003 # Each DCC command comprises: [dcc_address, dcc_state]
1004 return( [self.dark.get_addresses(),
1005 self.main.get_addresses(),
1006 self.lh1.get_addresses(),
1007 self.lh2.get_addresses(),
1008 self.rh1.get_addresses(),
1009 self.rh2.get_addresses() ] )
1011 def set_feathers(self,feathers:[bool,bool,bool,bool,bool]):
1012 # Feather Route list comprises: [main, lh1, lh2, rh1, rh2]
1013 # Each element comprises a single boolean value
1014 self.main.set_feather(feathers[0])
1015 self.lh1.set_feather(feathers[1])
1016 self.lh2.set_feather(feathers[2])
1017 self.rh1.set_feather(feathers[3])
1018 self.rh2.set_feather(feathers[4])
1019 self.auto_inhibit_update()
1021 def get_feathers(self):
1022 # Feather Route list comprises: [main, lh1, lh2, rh1, rh2]
1023 # Each element comprises a single boolean value
1024 return( [ self.main.get_feather(),
1025 self.lh1.get_feather(),
1026 self.lh2.get_feather(),
1027 self.rh1.get_feather(),
1028 self.rh2.get_feather() ] )
1030 def enable_selection(self):
1031 # Enable the CBs for diverging routes (will also enable the address EBs)
1032 self.lh1.enable_selection()
1033 self.lh2.enable_selection()
1034 self.rh1.enable_selection()
1035 self.rh2.enable_selection()
1036 # The DCC Address EBs for MAIN are enabled even if no feather is selected
1037 self.main.enable_selection()
1038 self.main.enable_addresses()
1039 # Enable the "auto inhibit route" CB
1040 self.CB.enable()
1041 # Enabling of the "dark" DCC address EBs will depend on the state of the
1042 # auto inhibit checkbox (the "dark" CB remains disabled and unselected)
1043 self.auto_inhibit_update()
1045 def disable_selection(self):
1046 # Only disable the "dark" DCC address EBs (the CB is always disabled)
1047 self.dark.disable_addresses()
1048 # Disable the CBs for all diverging routes (will also disable the address EBs)
1049 self.main.disable_selection()
1050 self.lh1.disable_selection()
1051 self.lh2.disable_selection()
1052 self.rh1.disable_selection()
1053 self.rh2.disable_selection()
1054 # Disable the "auto inhibit route" CB
1055 self.CB.disable()
1057 def set_auto_inhibit(self, auto_inhibit:bool):
1058 self.CB.set_value(auto_inhibit)
1059 self.auto_inhibit_update()
1061 def get_auto_inhibit(self):
1062 return(self.CB.get_value())
1064#------------------------------------------------------------------------------------
1065# Class for the 'basic' route selections UI Element for the main signal (if no specific
1066# route indications are selected) and the subsidary signal (if one exists). If the class
1067# is created for a main signal (or ground signal) then the main route is always selected.
1068# Class instance functions to use externally are:
1069# "enable" - disables/blanks the route selection check boxes
1070# "disable" enables/loads the route selection check boxes
1071# "set_values" - sets the Route Selection Checkboxes
1072# "get_values" - return the states of the Route Selection Checkboxes
1073# The Callback will be made on route selection change (enabled/disabled)
1074#------------------------------------------------------------------------------------
1076class route_selections():
1077 def __init__(self, parent_frame, label:str, tool_tip:str, callback=None, main_signal=False):
1078 self.main_signal = main_signal
1079 # Create a label frame for the selections (packed by the calling function/class
1080 self.frame = Tk.LabelFrame(parent_frame, text=label)
1081 # We use a subframe to center the selections boxes
1082 self.subframe = Tk.Frame(self.frame)
1083 self.subframe.pack(padx=2, pady=2)
1084 # Create the required selection elements (always packed in the subframe)
1085 self.main = common.check_box(self.subframe, label="MAIN", tool_tip=tool_tip, callback=callback)
1086 self.main.pack(side=Tk.LEFT)
1087 self.lh1 = common.check_box(self.subframe, label="LH1", tool_tip=tool_tip, callback=callback)
1088 self.lh1.pack(side=Tk.LEFT)
1089 self.lh2 = common.check_box(self.subframe, label="LH2", tool_tip=tool_tip, callback=callback)
1090 self.lh2.pack(side=Tk.LEFT)
1091 self.rh1 = common.check_box(self.subframe, label="RH1", tool_tip=tool_tip, callback=callback)
1092 self.rh1.pack(side=Tk.LEFT)
1093 self.rh2 = common.check_box(self.subframe, label="RH2", tool_tip=tool_tip, callback=callback)
1094 self.rh2.pack(side=Tk.LEFT)
1095 if self.main_signal: self.main.config(state="disabled")
1097 def enable_selection(self):
1098 if not self.main_signal: self.main.enable()
1099 self.lh1.enable()
1100 self.lh2.enable()
1101 self.rh1.enable()
1102 self.rh2.enable()
1104 def disable_selection(self):
1105 if not self.main_signal: self.main.disable()
1106 self.lh1.disable()
1107 self.lh2.disable()
1108 self.rh1.disable()
1109 self.rh2.disable()
1111 def set_values(self, routes:[bool,bool,bool,bool,bool]):
1112 # Route list comprises: [main, lh1, lh2, rh1, rh2]
1113 # Each element comprises a single boolean value
1114 self.main.set_value(routes[0])
1115 self.lh1.set_value(routes[1])
1116 self.lh2.set_value(routes[2])
1117 self.rh1.set_value(routes[3])
1118 self.rh2.set_value(routes[4])
1120 def get_values(self):
1121 # Route list comprises: [main, lh1, lh2, rh1, rh2]
1122 # Each element comprises a single boolean value
1123 return ([ self.main.get_value(),
1124 self.lh1.get_value(),
1125 self.lh2.get_value(),
1126 self.rh1.get_value(),
1127 self.rh2.get_value() ] )
1129#------------------------------------------------------------------------------------
1130# Class for the Edit Signal Window Configuration Tab
1131# sig_type_updated, sub_type_updated, route_type_updated, route_selections_updated,
1132# sig_routes_updated, sub_routes_updated, dist_routes_updated are callback functions
1133#------------------------------------------------------------------------------------
1135class signal_configuration_tab:
1136 def __init__(self, parent_tab, sig_type_updated, sub_type_updated,
1137 route_type_updated, route_selections_updated, sig_routes_updated,
1138 sub_routes_updated, dist_routes_updated):
1139 # Create a Frame to hold the Signal ID and Signal Type Selections
1140 self.frame1 = Tk.Frame(parent_tab)
1141 self.frame1.pack(padx=2, pady=2, fill='x')
1142 # Create the UI Element for Item ID selection. Note that although the signals_common.sig_exists
1143 # function will match both local and remote Signal IDs, the object_id_selection only allows integers to
1144 # be selected - so we can safely use this function here for consistency.
1145 self.sigid = common.object_id_selection(self.frame1,"Signal ID",
1146 exists_function = signals_common.sig_exists)
1147 self.sigid.frame.pack(side=Tk.LEFT, padx=2, pady=2, fill='both')
1148 self.sigtype = common.selection_buttons(self.frame1,"Signal Type",
1149 "Select signal type",sig_type_updated,"Colour Light",
1150 "Ground Pos","Semaphore","Ground Disc")
1151 self.sigtype.frame.pack(side=Tk.LEFT, padx=2, pady=2, fill='x', expand=True)
1152 # Create the UI Element for Signal subtype selection (always packed)
1153 self.subtype = common.selection_buttons(parent_tab,"Signal Subtype",
1154 "Select signal subtype",sub_type_updated,"-","-","-","-","-")
1155 self.subtype.frame.pack(padx=2, pady=2, fill='x')
1156 # Create a Frame to hold the Gen settings and Route type Selections (always packed)
1157 self.frame2 = Tk.Frame(parent_tab)
1158 self.frame2.pack(padx=2, pady=2, fill='x')
1159 self.settings = general_settings(self.frame2)
1160 self.settings.frame.pack(side=Tk.LEFT, padx=2, pady=2, fill='both')
1161 self.routetype = common.selection_buttons(self.frame2, "Route Indications",
1162 "Select the route indications for the main signal", route_type_updated,
1163 "None", "Route feathers", "Theatre indicator", "Route arms")
1164 self.routetype.frame.pack(side=Tk.LEFT, padx=2, pady=2, fill='x', expand=True)
1165 # Create the Checkboxes and DCC Entry Box frames for the type-specific selections
1166 # These frames are packed / hidden depending on the signal type and route
1167 # indication type selections by the callback functions in "configure_signal.py"
1168 self.aspects = colour_light_aspects(parent_tab, sub_routes_updated)
1169 self.theatre = theatre_route_indications(parent_tab, route_selections_updated)
1170 self.feathers = feather_route_indications(parent_tab, route_selections_updated)
1171 self.semaphores = semaphore_signal_arms(parent_tab, sig_routes_updated,
1172 sub_routes_updated, dist_routes_updated)
1173 self.sig_routes = route_selections(parent_tab,
1174 "Routes to be controlled by the Main Signal",
1175 "Select one or more routes to be controlled by the main signal",
1176 callback=route_selections_updated, main_signal=True)
1177 self.sub_routes = route_selections(parent_tab,
1178 "Routes to be controlled by the Subsidary Signal",
1179 "Select one or more routes to be controlled by the subsidary signal",
1180 callback=route_selections_updated, main_signal=False)
1182#############################################################################################