Coverage for /home/pi/Software/model-railway-signalling/model_railway_signals/editor/configure_signal.py: 88%
635 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# This module contains all the ui functions for configuring Signal objects
3#------------------------------------------------------------------------------------
4#
5# External API functions intended for use by other editor modules:
6# edit_signal - Open the edit point top level window
7#
8# Makes the following external API calls to other editor modules:
9# objects.update_object(obj_id,new_obj) - Update the configuration of the signal object
10#
11# Accesses the following external editor objects directly:
12# objects.schematic_objects - To load/save the object configuration
13#
14# Accesses the following types directly from the library modules:
15# signals_common.sig_type - The sygnal type
16# signals_colour_lights.signal_sub_type - colour light signal sub-type
17# signals_semaphores.semaphore_sub_type - semaphore signal sub-type
18#
19# Uses the classes from the following modules for each configuration tab:
20# configure_signal_tab1 - General signal configuration
21# configure_signal_tab2 - Point and signal interlocking
22# configure_signal_tab3 - signal automation
23# common.window_controls - the common load/save/cancel/OK controls
24#
25#------------------------------------------------------------------------------------
27import copy
29import tkinter as Tk
30from tkinter import ttk
32from . import common
33from . import objects
34from . import configure_signal_tab1
35from . import configure_signal_tab2
36from . import configure_signal_tab3
38from ..library import signals_common
39from ..library import signals_colour_lights
40from ..library import signals_semaphores
42#------------------------------------------------------------------------------------
43# We maintain a global dictionary of open edit windows (where the key is the UUID
44# of the object being edited) to prevent duplicate windows being opened. If the user
45# tries to edit an object which is already being edited, then we just bring the
46# existing edit window to the front (expanding if necessary) and set focus on it
47#------------------------------------------------------------------------------------
49open_windows={}
51#------------------------------------------------------------------------------------
52# Helper function to find out if the signal has a subsidary (colour light or semaphore)
53#------------------------------------------------------------------------------------
55def has_subsidary(signal):
56 return ( ( signal.config.sigtype.get_value() == signals_common.sig_type.semaphore.value and
57 ( signal.config.semaphores.main.sub.get_element()[0] or
58 signal.config.semaphores.lh1.sub.get_element()[0] or
59 signal.config.semaphores.lh2.sub.get_element()[0] or
60 signal.config.semaphores.rh1.sub.get_element()[0] or
61 signal.config.semaphores.rh2.sub.get_element()[0] ) ) or
62 (signal.config.sigtype.get_value() == signals_common.sig_type.colour_light.value and
63 signal.config.aspects.get_subsidary()[0] ) )
65#------------------------------------------------------------------------------------
66# Helper functions to find out if the signal has distant arms (semaphore
67#------------------------------------------------------------------------------------
69def has_secondary_distant(signal):
70 return ( signal.config.sigtype.get_value() == signals_common.sig_type.semaphore.value and
71 ( signal.config.semaphores.main.dist.get_element()[0] or
72 signal.config.semaphores.lh1.dist.get_element()[0] or
73 signal.config.semaphores.lh2.dist.get_element()[0] or
74 signal.config.semaphores.rh1.dist.get_element()[0] or
75 signal.config.semaphores.rh2.dist.get_element()[0] ) )
77#------------------------------------------------------------------------------------
78# Helper functions to find out if the signal has route arms (semaphore)
79#------------------------------------------------------------------------------------
81def has_route_arms(signal):
82 return ( signal.config.sigtype.get_value() == signals_common.sig_type.semaphore.value and
83 (signal.config.semaphores.lh1.sig.get_element()[0] or
84 signal.config.semaphores.lh2.sig.get_element()[0] or
85 signal.config.semaphores.rh1.sig.get_element()[0] or
86 signal.config.semaphores.rh2.sig.get_element()[0] or
87 signal.config.semaphores.lh1.dist.get_element()[0] or
88 signal.config.semaphores.lh2.dist.get_element()[0] or
89 signal.config.semaphores.rh1.dist.get_element()[0] or
90 signal.config.semaphores.rh2.dist.get_element()[0] or
91 signal.config.semaphores.lh1.sub.get_element()[0] or
92 signal.config.semaphores.lh2.sub.get_element()[0] or
93 signal.config.semaphores.rh1.sub.get_element()[0] or
94 signal.config.semaphores.rh2.sub.get_element()[0] ) )
96#------------------------------------------------------------------------------------
97# Helper functions to return a list of the selected signal, distant and subsidary
98# routes epending on the route indication type that has been selected
99#------------------------------------------------------------------------------------
101def get_sig_routes(signal):
102 # Get the route selections from the appropriate UI element
103 if signal.config.routetype.get_value() == 1:
104 # MAIN route is always enabled (and greyed out)
105 routes = signal.config.sig_routes.get_values()
106 elif signal.config.routetype.get_value() == 2:
107 # MAIN route is enabled even if a feather hasn't been selected
108 routes = signal.config.feathers.get_feathers()
109 routes[0] = True
110 elif signal.config.routetype.get_value() == 3:
111 # The Theatre route list comprises: [dark, main, lh1, lh2, rh1, rh2]
112 # Each route element comprises: [character, DCC_command_sequence]
113 # MAIN route is enabled even if a theatre character hasn't been selected
114 theatre_routes = signal.config.theatre.get_theatre()
115 routes = [True, False, False, False, False]
116 if theatre_routes[2][0] != "": routes[1] = True
117 if theatre_routes[3][0] != "": routes[2] = True
118 if theatre_routes[4][0] != "": routes[3] = True
119 if theatre_routes[5][0] != "": routes[4] = True
120 elif signal.config.routetype.get_value() == 4: 120 ↛ 133line 120 didn't jump to line 133, because the condition on line 120 was never false
121 # Signal arm list comprises:[main, LH1, LH2, RH1, RH2]
122 # Each Route element comprises: [signal, subsidary, distant]
123 # Each signal element comprises [enabled/disabled, address]
124 # MAIN route should always be enabled for a semaphore
125 semaphore_routes = signal.config.semaphores.get_arms()
126 routes = [True, False, False, False, False]
127 routes[1] = semaphore_routes[1][0][0]
128 routes[2] = semaphore_routes[2][0][0]
129 routes[3] = semaphore_routes[3][0][0]
130 routes[4] = semaphore_routes[4][0][0]
131 else:
132 # Defensive programming (MAIN route always enabled)
133 routes = [True, False, False, False, False]
134 return(routes)
136def get_sub_routes(signal):
137 # Get the route selections from the appropriate UI element
138 if ( signal.config.sigtype.get_value() == signals_common.sig_type.ground_position.value or
139 signal.config.sigtype.get_value() == signals_common.sig_type.ground_disc.value):
140 routes = [False, False, False, False, False]
141 elif signal.config.sigtype.get_value() == signals_common.sig_type.colour_light.value:
142 routes = signal.config.sub_routes.get_values()
143 elif signal.config.sigtype.get_value() == signals_common.sig_type.semaphore.value: 143 ↛ 159line 143 didn't jump to line 159, because the condition on line 143 was never false
144 if signal.config.routetype.get_value() == 4:
145 # Signal arm list comprises:[main, LH1, LH2, RH1, RH2]
146 # Each Route element comprises: [signal, subsidary, distant]
147 # Each signal element comprises [enabled/disabled, address]
148 semaphore_routes = signal.config.semaphores.get_arms()
149 routes = [False, False, False, False, False]
150 routes[0] = semaphore_routes[0][1][0]
151 routes[1] = semaphore_routes[1][1][0]
152 routes[2] = semaphore_routes[2][1][0]
153 routes[3] = semaphore_routes[3][1][0]
154 routes[4] = semaphore_routes[4][1][0]
155 else:
156 routes = signal.config.sub_routes.get_values()
157 else:
158 # Defensive programming (no subsidary routes)
159 routes = [False, False, False, False, False]
160 return(routes)
162def get_dist_routes(signal):
163 # Get the route selections from the appropriate UI element
164 # Note this is only applicable to semaphore signals
165 semaphore_routes = signal.config.semaphores.get_arms()
166 if signal.config.routetype.get_value() == 1 and semaphore_routes[0][2][0]:
167 # MAIN route is always enabled (and greyed out)
168 routes = signal.config.sig_routes.get_values()
169 elif signal.config.routetype.get_value() == 3 and semaphore_routes[0][2][0]:
170 # The Theatre route list comprises: [dark, main, lh1, lh2, rh1, rh2]
171 # Each route element comprises: [character, DCC_command_sequence]
172 # MAIN route is enabled even if a theatre character hasn't been selected
173 theatre_routes = signal.config.theatre.get_theatre()
174 routes = [True, False, False, False, False]
175 if theatre_routes[2][0] != "": routes[1] = True
176 if theatre_routes[3][0] != "": routes[2] = True
177 if theatre_routes[4][0] != "": routes[3] = True
178 if theatre_routes[5][0] != "": routes[4] = True
179 elif signal.config.routetype.get_value() == 4:
180 # Signal arm list comprises:[main, LH1, LH2, RH1, RH2]
181 # Each Route element comprises: [signal, subsidary, distant]
182 # Each signal element comprises [enabled/disabled, address]
183 # MAIN route should always be enabled for a semaphore
184 routes = [False, False, False, False, False]
185 routes[0] = semaphore_routes[0][2][0]
186 routes[1] = semaphore_routes[1][2][0]
187 routes[2] = semaphore_routes[2][2][0]
188 routes[3] = semaphore_routes[3][2][0]
189 routes[4] = semaphore_routes[4][2][0]
190 else:
191 # All other signal types do not support secondary distant arms
192 routes = [False, False, False, False, False]
193 return(routes)
195#------------------------------------------------------------------------------------
196# Hide/show the various route indication UI elements depending on what is selected
197# Also update the available route selections depending on signal type / syb-type
198#------------------------------------------------------------------------------------
200def update_tab1_signal_ui_elements(signal):
201 # Unpack all the optional elements first
202 signal.config.aspects.frame.pack_forget()
203 signal.config.semaphores.frame.pack_forget()
204 signal.config.theatre.frame.pack_forget()
205 signal.config.feathers.frame.pack_forget()
206 signal.config.sig_routes.frame.pack_forget()
207 signal.config.sub_routes.frame.pack_forget()
208 # Only pack those elements relevant to the signal type and route type
209 if signal.config.sigtype.get_value() == signals_common.sig_type.colour_light.value:
210 signal.config.aspects.frame.pack(padx=2, pady=2, fill='x')
211 elif signal.config.sigtype.get_value() == signals_common.sig_type.ground_position.value:
212 signal.config.aspects.frame.pack(padx=2, pady=2, fill='x')
213 elif signal.config.sigtype.get_value() == signals_common.sig_type.semaphore.value:
214 signal.config.semaphores.frame.pack(padx=2, pady=2, fill='x')
215 elif signal.config.sigtype.get_value() == signals_common.sig_type.ground_disc.value: 215 ↛ 218line 215 didn't jump to line 218, because the condition on line 215 was never false
216 signal.config.semaphores.frame.pack(padx=2, pady=2, fill='x')
217 # Pack the Route selections according to type
218 if signal.config.routetype.get_value() == 1:
219 signal.config.sig_routes.frame.pack(padx=2, pady=2, fill='x')
220 if has_subsidary(signal):
221 signal.config.sub_routes.frame.pack(padx=2, pady=2, fill='x')
222 if signal.config.routetype.get_value() == 2:
223 signal.config.feathers.frame.pack(padx=2, pady=2, fill='x')
224 if has_subsidary(signal):
225 signal.config.sub_routes.frame.pack(padx=2, pady=2, fill='x')
226 elif signal.config.routetype.get_value() == 3:
227 signal.config.theatre.frame.pack(padx=2, pady=2, fill='x')
228 if has_subsidary(signal):
229 signal.config.sub_routes.frame.pack(padx=2, pady=2, fill='x')
230 return()
232#------------------------------------------------------------------------------------
233# Update the available signal subtype selections based on the signal type
234#------------------------------------------------------------------------------------
236def update_tab1_signal_subtype_selections(signal):
237 if signal.config.sigtype.get_value() == signals_common.sig_type.colour_light.value:
238 signal.config.subtype.B1.configure(text="2 Asp G/R")
239 signal.config.subtype.B2.configure(text="2 Asp G/Y")
240 signal.config.subtype.B3.configure(text="2 Asp Y/R")
241 signal.config.subtype.B4.configure(text="3 Aspect")
242 signal.config.subtype.B5.configure(text="4 Aspect")
243 signal.config.subtype.B3.pack(side=Tk.LEFT)
244 signal.config.subtype.B4.pack(side=Tk.LEFT)
245 signal.config.subtype.B5.pack(side=Tk.LEFT)
246 elif signal.config.sigtype.get_value() == signals_common.sig_type.semaphore.value:
247 signal.config.subtype.B1.configure(text="Home")
248 signal.config.subtype.B2.configure(text="Distant")
249 signal.config.subtype.B3.pack_forget()
250 signal.config.subtype.B4.pack_forget()
251 signal.config.subtype.B5.pack_forget()
252 elif signal.config.sigtype.get_value() == signals_common.sig_type.ground_position.value:
253 signal.config.subtype.B1.configure(text="Norm (post'96)")
254 signal.config.subtype.B2.configure(text="Shunt (post'96)")
255 signal.config.subtype.B3.configure(text="Norm (early)")
256 signal.config.subtype.B4.configure(text="Shunt (early)")
257 signal.config.subtype.B3.pack(side=Tk.LEFT)
258 signal.config.subtype.B4.pack(side=Tk.LEFT)
259 signal.config.subtype.B5.pack_forget()
260 elif signal.config.sigtype.get_value() == signals_common.sig_type.ground_disc.value: 260 ↛ 266line 260 didn't jump to line 266, because the condition on line 260 was never false
261 signal.config.subtype.B1.configure(text="Normal")
262 signal.config.subtype.B2.configure(text="Shunt Ahead")
263 signal.config.subtype.B3.pack_forget()
264 signal.config.subtype.B4.pack_forget()
265 signal.config.subtype.B5.pack_forget()
266 return()
268#------------------------------------------------------------------------------------
269# Update the available aspect selections based on signal type and subtype
270#------------------------------------------------------------------------------------
272def update_tab1_signal_aspect_selections(signal):
273 if signal.config.sigtype.get_value() == signals_common.sig_type.colour_light.value:
274 if signal.config.subtype.get_value() == signals_colour_lights.signal_sub_type.home.value:
275 signal.config.aspects.red.enable_addresses()
276 signal.config.aspects.grn.enable_addresses()
277 signal.config.aspects.ylw.disable_addresses()
278 signal.config.aspects.dylw.disable_addresses()
279 signal.config.aspects.fylw.disable_addresses()
280 signal.config.aspects.fdylw.disable_addresses()
281 elif signal.config.subtype.get_value() == signals_colour_lights.signal_sub_type.distant.value:
282 signal.config.aspects.red.disable_addresses()
283 signal.config.aspects.grn.enable_addresses()
284 signal.config.aspects.ylw.enable_addresses()
285 signal.config.aspects.dylw.disable_addresses()
286 signal.config.aspects.fylw.enable_addresses()
287 signal.config.aspects.fdylw.disable_addresses()
288 elif signal.config.subtype.get_value() == signals_colour_lights.signal_sub_type.red_ylw.value:
289 signal.config.aspects.red.enable_addresses()
290 signal.config.aspects.grn.disable_addresses()
291 signal.config.aspects.ylw.enable_addresses()
292 signal.config.aspects.dylw.disable_addresses()
293 signal.config.aspects.fylw.disable_addresses()
294 signal.config.aspects.fdylw.disable_addresses()
295 elif signal.config.subtype.get_value() == signals_colour_lights.signal_sub_type.three_aspect.value:
296 signal.config.aspects.red.enable_addresses()
297 signal.config.aspects.grn.enable_addresses()
298 signal.config.aspects.ylw.enable_addresses()
299 signal.config.aspects.dylw.disable_addresses()
300 signal.config.aspects.fylw.enable_addresses()
301 signal.config.aspects.fdylw.disable_addresses()
302 elif signal.config.subtype.get_value() == signals_colour_lights.signal_sub_type.four_aspect.value: 302 ↛ 321line 302 didn't jump to line 321, because the condition on line 302 was never false
303 signal.config.aspects.red.enable_addresses()
304 signal.config.aspects.grn.enable_addresses()
305 signal.config.aspects.ylw.enable_addresses()
306 signal.config.aspects.dylw.enable_addresses()
307 signal.config.aspects.fylw.enable_addresses()
308 signal.config.aspects.fdylw.enable_addresses()
309 elif signal.config.sigtype.get_value() == signals_common.sig_type.ground_position.value:
310 signal.config.aspects.red.enable_addresses()
311 signal.config.aspects.grn.enable_addresses()
312 signal.config.aspects.ylw.disable_addresses()
313 signal.config.aspects.dylw.disable_addresses()
314 signal.config.aspects.fylw.disable_addresses()
315 signal.config.aspects.fdylw.disable_addresses()
316 else:
317 # Signal is a semaphore or ground disc - disable the entire UI element as this
318 # will be hidden and the semaphore signals arm UI element will be displayed
319 signal.config.aspects.disable_aspects()
320 # Enable/Disable the Colour Light subsidary selection (disabled for 2 aspect GRN/YLW)
321 if ( signal.config.sigtype.get_value() == signals_common.sig_type.colour_light.value and
322 signal.config.subtype.get_value() != signals_colour_lights.signal_sub_type.distant.value ):
323 signal.config.aspects.enable_subsidary()
324 else:
325 signal.config.aspects.disable_subsidary()
326 return()
328#------------------------------------------------------------------------------------
329# Update the Route selections based on signal type
330#------------------------------------------------------------------------------------
332def update_tab1_route_selection_elements(signal):
333 if signal.config.sigtype.get_value() == signals_common.sig_type.colour_light.value:
334 # Disable the Semaphore-specific route selections (this UI element will be hidden)
335 signal.config.semaphores.disable_main_route()
336 signal.config.semaphores.disable_diverging_routes()
337 # Enable the available route type selections depending on the type of colour light signal
338 if signal.config.subtype.get_value() == signals_colour_lights.signal_sub_type.distant.value:
339 # 2 aspect distant signals do not support route indications so set the route indications
340 # to 'None' and disable all associated route selections (these UI elements will be hidden)
341 signal.config.routetype.set_value(1)
342 signal.config.routetype.B2.configure(state="disabled")
343 signal.config.routetype.B3.configure(state="disabled")
344 signal.config.routetype.B4.configure(state="disabled")
345 # Disable all route selections (main signal and subsidary)
346 signal.config.feathers.disable_selection()
347 signal.config.theatre.disable_selection()
348 signal.config.sub_routes.disable_selection()
349 signal.config.sig_routes.disable_selection()
350 else:
351 # If 'Route Arms' are selected (semaphore only) then change to 'Feathers'
352 if signal.config.routetype.get_value() == 4: signal.config.routetype.set_value(2)
353 # Non-distant signals can support 'None', 'Feathers' or 'Theatre' route indications
354 signal.config.routetype.B2.configure(state="normal")
355 signal.config.routetype.B3.configure(state="normal")
356 signal.config.routetype.B4.configure(state="disabled")
357 # Enable/disable the appropriate UI elements based on the selected indication type
358 if signal.config.routetype.get_value() == 1:
359 signal.config.feathers.disable_selection()
360 signal.config.theatre.disable_selection()
361 signal.config.sig_routes.enable_selection()
362 elif signal.config.routetype.get_value() == 2:
363 signal.config.feathers.enable_selection()
364 signal.config.theatre.disable_selection()
365 signal.config.sig_routes.disable_selection()
366 elif signal.config.routetype.get_value() == 3: 366 ↛ 371line 366 didn't jump to line 371, because the condition on line 366 was never false
367 signal.config.theatre.enable_selection()
368 signal.config.feathers.disable_selection()
369 signal.config.sig_routes.disable_selection()
370 # If the signal has a subsidary then enable the subsidary route selections
371 if has_subsidary(signal): signal.config.sub_routes.enable_selection()
372 else: signal.config.sub_routes.disable_selection()
374 elif signal.config.sigtype.get_value() == signals_common.sig_type.semaphore.value:
375 # If Feathers are selected (Colour light signals only) then change to Route Arms
376 if signal.config.routetype.get_value() == 2: signal.config.routetype.set_value(4)
377 # Disable the Colour-light-specific 'feathers' selection (this UI element will be hidden)
378 signal.config.feathers.disable_selection()
379 # Enable the main route selections for the semaphore signal (main, subsidary, dist arms)
380 # Note that the distant and subsidary selections will be disabled for distant signals
381 signal.config.semaphores.enable_main_route()
382 # Enable the diverging route selections depending on the type of Semaphore signal
383 if signal.config.subtype.get_value() == signals_semaphores.semaphore_sub_type.distant.value:
384 # Distant signals only support 'Route Arms' or 'None' so disable all other selections
385 # If 'Theatre' is selected (not valid for a distant signal then change to 'Route arms'
386 if signal.config.routetype.get_value() == 3: signal.config.routetype.set_value(4)
387 signal.config.routetype.B2.configure(state="disabled")
388 signal.config.routetype.B3.configure(state="disabled")
389 signal.config.routetype.B4.configure(state="normal")
390 # Enable/disable the appropriate UI elements based on the selected indication type
391 if signal.config.routetype.get_value() == 1:
392 signal.config.semaphores.disable_diverging_routes()
393 elif signal.config.routetype.get_value() == 4: 393 ↛ 397line 393 didn't jump to line 397, because the condition on line 393 was never false
394 signal.config.semaphores.enable_diverging_routes()
395 # Disable all selections for subsidaries, secondary distants and theatre indicators
396 # Also disable the generic 'sig_routes' (only one signal in front of the distant)
397 signal.config.theatre.disable_selection()
398 signal.config.semaphores.disable_subsidaries()
399 signal.config.semaphores.disable_distants()
400 signal.config.sig_routes.disable_selection()
401 signal.config.sub_routes.disable_selection()
402 else:
403 # Home signals can support 'None', 'Route Arms', or 'Theatre'
404 signal.config.routetype.B2.configure(state="disabled")
405 signal.config.routetype.B3.configure(state="normal")
406 signal.config.routetype.B4.configure(state="normal")
407 # Home signals can support subsidaries and secondary distant arms
408 signal.config.semaphores.enable_subsidaries()
409 signal.config.semaphores.enable_distants()
410 # Enable/disable the appropriate UI elements based on the selected indication type
411 if signal.config.routetype.get_value() == 1:
412 signal.config.semaphores.disable_diverging_routes()
413 signal.config.sig_routes.enable_selection()
414 signal.config.theatre.disable_selection()
415 # If the MAIN subsidary is selected then enable the subsidary route selections
416 # i.e. we still allow the single subsidary arm to control multiple routes
417 if has_subsidary(signal): signal.config.sub_routes.enable_selection()
418 else: signal.config.sub_routes.disable_selection()
419 elif signal.config.routetype.get_value() == 3:
420 signal.config.semaphores.disable_diverging_routes()
421 signal.config.theatre.enable_selection()
422 signal.config.sig_routes.disable_selection()
423 # If the MAIN subsidary is selected then enable the subsidary route selections
424 # i.e. we still allow the single subsidary arm to control multiple routes
425 if has_subsidary(signal): signal.config.sub_routes.enable_selection()
426 else: signal.config.sub_routes.disable_selection()
427 elif signal.config.routetype.get_value() == 4: 427 ↛ 467line 427 didn't jump to line 467, because the condition on line 427 was never false
428 signal.config.semaphores.enable_diverging_routes()
429 signal.config.theatre.disable_selection()
430 signal.config.sig_routes.disable_selection()
431 signal.config.sub_routes.disable_selection()
433 elif signal.config.sigtype.get_value() == signals_common.sig_type.ground_disc.value:
434 # No route indications supported for ground signals
435 signal.config.routetype.set_value(1)
436 signal.config.routetype.B2.configure(state="disabled")
437 signal.config.routetype.B3.configure(state="disabled")
438 signal.config.routetype.B4.configure(state="disabled")
439 # Only the main signal arm is supported but this can support multiple routes
440 signal.config.semaphores.enable_main_route()
441 signal.config.sig_routes.enable_selection()
442 # All other subsidary, secondary distant and route selections are sisabled
443 signal.config.semaphores.disable_diverging_routes()
444 signal.config.semaphores.disable_subsidaries()
445 signal.config.semaphores.disable_distants()
446 signal.config.feathers.disable_selection()
447 signal.config.theatre.disable_selection()
448 signal.config.sub_routes.disable_selection()
450 elif signal.config.sigtype.get_value() == signals_common.sig_type.ground_position.value: 450 ↛ 467line 450 didn't jump to line 467, because the condition on line 450 was never false
451 # No route indications supported for ground signals
452 signal.config.routetype.set_value(1)
453 signal.config.routetype.B2.configure(state="disabled")
454 signal.config.routetype.B3.configure(state="disabled")
455 signal.config.routetype.B4.configure(state="disabled")
456 # A ground signal can also support multiple routes
457 signal.config.sig_routes.enable_selection()
458 # All other subsidary, secondary distant and route selections are sisabled
459 signal.config.semaphores.disable_diverging_routes()
460 signal.config.semaphores.disable_main_route()
461 signal.config.semaphores.disable_subsidaries()
462 signal.config.semaphores.disable_distants()
463 signal.config.feathers.disable_selection()
464 signal.config.theatre.disable_selection()
465 signal.config.sub_routes.disable_selection()
467 return()
469#------------------------------------------------------------------------------------
470# Enable/disable the various route selection elements depending on what is selected
471# I've kept it simple and not coupled it too tightly to the signal configuration tab
472#------------------------------------------------------------------------------------
474def update_tab2_available_signal_routes(signal):
475 # Hide (pack.forget) all the Conflicting signal elements for diverging routes
476 # The ones that need to be enabled get re-packed (in the right order) below
477 signal.locking.conflicting_sigs.lh1.frame.pack_forget()
478 signal.locking.conflicting_sigs.lh2.frame.pack_forget()
479 signal.locking.conflicting_sigs.rh1.frame.pack_forget()
480 signal.locking.conflicting_sigs.rh2.frame.pack_forget()
481 # Get the current route selections
482 sig_routes = get_sig_routes(signal)
483 sub_routes = get_sub_routes(signal)
484 # Note that the MAIN route is always enabled for all signal types
485 signal.locking.interlocking.main.enable_route()
486 signal.locking.interlocked_sections.main.enable_route()
487 signal.locking.conflicting_sigs.main.enable_route()
488 # Other routes are enabled if either the main signal or subsidary signal supports them
489 if sig_routes[1] or sub_routes[1]:
490 signal.locking.interlocking.lh1.enable_route()
491 signal.locking.interlocked_sections.lh1.enable_route()
492 signal.locking.conflicting_sigs.lh1.enable_route()
493 signal.locking.conflicting_sigs.lh1.frame.pack(padx=2, pady=2, fill='x')
494 else:
495 signal.locking.interlocking.lh1.disable_route()
496 signal.locking.interlocked_sections.lh1.disable_route()
497 signal.locking.conflicting_sigs.lh1.disable_route()
498 if sig_routes[2] or sub_routes[2]:
499 signal.locking.interlocking.lh2.enable_route()
500 signal.locking.interlocked_sections.lh2.enable_route()
501 signal.locking.conflicting_sigs.lh2.enable_route()
502 signal.locking.conflicting_sigs.lh2.frame.pack(padx=2, pady=2, fill='x')
503 else:
504 signal.locking.interlocking.lh2.disable_route()
505 signal.locking.interlocked_sections.lh2.disable_route()
506 signal.locking.conflicting_sigs.lh2.disable_route()
507 if sig_routes[3] or sub_routes[3]:
508 signal.locking.interlocking.rh1.enable_route()
509 signal.locking.interlocked_sections.rh1.enable_route()
510 signal.locking.conflicting_sigs.rh1.enable_route()
511 signal.locking.conflicting_sigs.rh1.frame.pack(padx=2, pady=2, fill='x')
512 else:
513 signal.locking.interlocking.rh1.disable_route()
514 signal.locking.interlocked_sections.rh1.disable_route()
515 signal.locking.conflicting_sigs.rh1.disable_route()
516 if sig_routes[4] or sub_routes[4]:
517 signal.locking.interlocking.rh2.enable_route()
518 signal.locking.interlocked_sections.rh2.enable_route()
519 signal.locking.conflicting_sigs.rh2.enable_route()
520 signal.locking.conflicting_sigs.rh2.frame.pack(padx=2, pady=2, fill='x')
521 else:
522 signal.locking.interlocking.rh2.disable_route()
523 signal.locking.interlocked_sections.rh2.disable_route()
524 signal.locking.conflicting_sigs.rh2.disable_route()
525 # Enable/disable the signal / block instrument ahead selections on signal type
526 # Signal Ahead selection is enabled for all Main Semaphore and Colour Light signal types
527 # Block Ahead selection is only enabled for Semaphore or Colour Light Home signals
528 # both are disabled for Ground Position and Ground disc signal types
529 if signal.config.sigtype.get_value() == signals_common.sig_type.semaphore.value:
530 signal.locking.interlocking.enable_sig_ahead()
531 if signal.config.subtype.get_value() == signals_semaphores.semaphore_sub_type.distant.value:
532 signal.locking.interlocking.disable_block_ahead()
533 else:
534 signal.locking.interlocking.enable_block_ahead()
535 elif signal.config.sigtype.get_value() == signals_common.sig_type.colour_light.value:
536 if signal.config.subtype.get_value() == signals_colour_lights.signal_sub_type.home.value:
537 signal.locking.interlocking.enable_block_ahead()
538 signal.locking.interlocking.enable_sig_ahead()
539 else:
540 signal.locking.interlocking.disable_block_ahead()
541 signal.locking.interlocking.enable_sig_ahead()
542 else:
543 signal.locking.interlocking.disable_block_ahead()
544 signal.locking.interlocking.disable_sig_ahead()
545 return()
547#------------------------------------------------------------------------------------
548# Enable/disable the Distant Signal interlocking UI Element - this is only avaliable
549# for selection for Colour light or Semaphore distant signal types
550#------------------------------------------------------------------------------------
552def update_tab2_interlock_ahead_selection(signal):
553 if ( ( signal.config.sigtype.get_value() == signals_common.sig_type.semaphore.value and
554 signal.config.subtype.get_value() == signals_semaphores.semaphore_sub_type.distant.value) or
555 ( signal.config.sigtype.get_value() == signals_common.sig_type.colour_light.value and
556 signal.config.subtype.get_value() == signals_colour_lights.signal_sub_type.distant.value) or
557 ( signal.config.sigtype.get_value() == signals_common.sig_type.semaphore.value and
558 signal.config.subtype.get_value() == signals_semaphores.semaphore_sub_type.home.value and
559 has_secondary_distant(signal) ) ):
560 signal.locking.interlock_ahead.frame.pack(padx=2, pady=2, fill='x')
561 signal.locking.interlock_ahead.enable()
562 else:
563 signal.locking.interlock_ahead.frame.pack_forget()
564 signal.locking.interlock_ahead.disable()
565 return()
567#------------------------------------------------------------------------------------
568# Hide/show the various route indication UI elements depending on what is selected
569#------------------------------------------------------------------------------------
571def update_tab3_signal_ui_elements(signal):
572 # Unpack all the optional elements first
573 signal.automation.timed_signal.frame.pack_forget()
574 signal.automation.approach_control.frame.pack_forget()
575 # Only pack those elements relevant to the signal type and route type
576 if ( signal.config.sigtype.get_value() == signals_common.sig_type.colour_light.value or
577 signal.config.sigtype.get_value() == signals_common.sig_type.semaphore.value ):
578 signal.automation.timed_signal.frame.pack(padx=2, pady=2, fill='x')
579 rel_on_red = ( ( signal.config.sigtype.get_value() == signals_common.sig_type.colour_light.value and
580 signal.config.subtype.get_value() != signals_colour_lights.signal_sub_type.distant.value) or
581 ( signal.config.sigtype.get_value() == signals_common.sig_type.semaphore.value and
582 signal.config.subtype.get_value() != signals_semaphores.semaphore_sub_type.distant.value ) )
583 rel_on_yel = ( signal.config.sigtype.get_value() == signals_common.sig_type.colour_light.value and
584 signal.config.subtype.get_value() != signals_colour_lights.signal_sub_type.home.value and
585 signal.config.subtype.get_value() != signals_colour_lights.signal_sub_type.distant.value and
586 signal.config.subtype.get_value() != signals_colour_lights.signal_sub_type.red_ylw.value )
587 if rel_on_red or rel_on_yel:
588 signal.automation.approach_control.frame.pack(padx=2, pady=2, fill='x')
589 return()
591#------------------------------------------------------------------------------------
592# Enable/disable the Tab3 general settings depending on what is selected
593#------------------------------------------------------------------------------------
595def update_tab3_general_settings_selections(signal):
596 # Enable/disable the "Fully Automatic"(no signal button) and "Override" selections
597 if ( signal.config.sigtype.get_value() == signals_common.sig_type.semaphore.value or
598 signal.config.sigtype.get_value() == signals_common.sig_type.colour_light.value):
599 signal.automation.general_settings.automatic.enable()
600 signal.automation.general_settings.override.enable()
601 else:
602 signal.automation.general_settings.automatic.disable()
603 signal.automation.general_settings.override.disable()
604 # Enable/disable the "Dustant Automatic"(no distant button) selection
605 if ( signal.config.sigtype.get_value() == signals_common.sig_type.semaphore.value and
606 has_secondary_distant(signal) ):
607 signal.automation.general_settings.distant_automatic.enable()
608 else:
609 signal.automation.general_settings.distant_automatic.disable()
610 # Enable/disable the "Override Ahead" selection (can be selected for all main signal types
611 # apart from colour light Home signals and Semnaphore Home signals without secondary distant arms
612 if ( ( signal.config.sigtype.get_value() == signals_common.sig_type.colour_light.value and
613 signal.config.subtype.get_value() != signals_colour_lights.signal_sub_type.home.value) or
614 ( signal.config.sigtype.get_value() == signals_common.sig_type.semaphore.value and
615 signal.config.subtype.get_value() != signals_semaphores.semaphore_sub_type.home.value ) or
616 ( signal.config.sigtype.get_value() == signals_common.sig_type.semaphore.value and
617 has_secondary_distant(signal) ) ):
618 signal.automation.general_settings.override_ahead.enable()
619 else:
620 signal.automation.general_settings.override_ahead.disable()
621 return()
623#------------------------------------------------------------------------------------
624# Enable/disable the Tab3 track occupancy route selection elements
625#------------------------------------------------------------------------------------
627def update_tab3_track_section_ahead_routes(signal):
628 # Get the current route selections
629 sig_routes = get_sig_routes(signal)
630 sub_routes = get_sub_routes(signal)
631 # MAIN Route (sig or sub)
632 signal.automation.track_occupancy.section_ahead.main.enable()
633 # LH1 Route (sig or sub)
634 if sig_routes[1] or sub_routes[1]:
635 signal.automation.track_occupancy.section_ahead.lh1.enable()
636 else:
637 signal.automation.track_occupancy.section_ahead.lh1.disable()
638 # LH2 Route (sig or sub)
639 if sig_routes[2] or sub_routes[2]:
640 signal.automation.track_occupancy.section_ahead.lh2.enable()
641 else:
642 signal.automation.track_occupancy.section_ahead.lh2.disable()
643 # RH1 Route (sig or sub)
644 if sig_routes[3] or sub_routes[3]:
645 signal.automation.track_occupancy.section_ahead.rh1.enable()
646 else:
647 signal.automation.track_occupancy.section_ahead.rh1.disable()
648 # RH2 Route (sig or sub)
649 if sig_routes[4] or sub_routes[4]:
650 signal.automation.track_occupancy.section_ahead.rh2.enable()
651 else:
652 signal.automation.track_occupancy.section_ahead.rh2.disable()
653 return()
655#------------------------------------------------------------------------------------
656# Enable/disable the Tab3 Timed Signal and approach control route selection elements
657#------------------------------------------------------------------------------------
659def update_tab3_timed_signal_selections(signal):
660 # Get the current route selections
661 sig_routes = get_sig_routes(signal)
662 # Enable/disable the UI element depending on whether the signal supports timed signal sequences
663 timed_signal = (signal.config.sigtype.get_value() == signals_common.sig_type.semaphore.value or
664 signal.config.sigtype.get_value() == signals_common.sig_type.colour_light.value )
665 if timed_signal: signal.automation.timed_signal.main.enable()
666 else: signal.automation.timed_signal.main.disable()
667 # LH1 Route (sig or sub)
668 if sig_routes[1] and timed_signal: signal.automation.timed_signal.lh1.enable()
669 else: signal.automation.timed_signal.lh1.disable()
670 # LH2 Route (sig or sub)
671 if sig_routes[2] and timed_signal: signal.automation.timed_signal.lh2.enable()
672 else: signal.automation.timed_signal.lh2.disable()
673 # RH1 Route (sig or sub)
674 if sig_routes[3] and timed_signal: signal.automation.timed_signal.rh1.enable()
675 else: signal.automation.timed_signal.rh1.disable()
676 # RH2 Route (sig or sub)
677 if sig_routes[4] and timed_signal: signal.automation.timed_signal.rh2.enable()
678 else: signal.automation.timed_signal.rh2.disable()
679 return()
681#------------------------------------------------------------------------------------
682# Enable/disable the Tab3 Timed Signal and approach control route selection elements
683#------------------------------------------------------------------------------------
685def update_tab3_approach_control_selections(signal):
686 # Get the current route selections
687 sig_routes = get_sig_routes(signal)
688 # Work out if the signal type supports approach control:
689 rel_on_red = ( ( signal.config.sigtype.get_value() == signals_common.sig_type.colour_light.value and
690 signal.config.subtype.get_value() != signals_colour_lights.signal_sub_type.distant.value ) or
691 ( signal.config.sigtype.get_value() == signals_common.sig_type.semaphore.value and
692 signal.config.subtype.get_value() != signals_semaphores.semaphore_sub_type.distant.value ) )
693 rel_on_yel = ( signal.config.sigtype.get_value() == signals_common.sig_type.colour_light.value and
694 signal.config.subtype.get_value() != signals_colour_lights.signal_sub_type.home.value and
695 signal.config.subtype.get_value() != signals_colour_lights.signal_sub_type.distant.value and
696 signal.config.subtype.get_value() != signals_colour_lights.signal_sub_type.red_ylw.value )
697 rel_on_sig = ( ( signal.config.sigtype.get_value() == signals_common.sig_type.colour_light.value and
698 signal.config.subtype.get_value() == signals_colour_lights.signal_sub_type.home.value )or
699 ( signal.config.sigtype.get_value() == signals_common.sig_type.semaphore.value and
700 signal.config.subtype.get_value() == signals_semaphores.semaphore_sub_type.home.value ) )
701 approach_control = rel_on_red or rel_on_yel or rel_on_sig
702 if approach_control:
703 # Deal with the approach control selections first
704 if rel_on_yel: signal.automation.approach_control.enable_release_on_yel()
705 else: signal.automation.approach_control.disable_release_on_yel()
706 if rel_on_red: signal.automation.approach_control.enable_release_on_red() 706 ↛ 707line 706 didn't jump to line 707, because the condition on line 706 was never false
707 else: signal.automation.approach_control.disable_release_on_red()
708 if rel_on_sig: signal.automation.approach_control.enable_release_on_red_sig_ahead()
709 else: signal.automation.approach_control.disable_release_on_red_sig_ahead()
710 # MAIN Route (sig or sub)
711 signal.automation.approach_control.main.enable_route()
712 # LH1 Route (sig or sub)
713 if sig_routes[1] and approach_control: signal.automation.approach_control.lh1.enable_route()
714 else: signal.automation.approach_control.lh1.disable_route()
715 # LH2 Route (sig or sub)
716 if sig_routes[2] and approach_control: signal.automation.approach_control.lh2.enable_route()
717 else: signal.automation.approach_control.lh2.disable_route()
718 # RH1 Route (sig or sub)
719 if sig_routes[3] and approach_control: signal.automation.approach_control.rh1.enable_route()
720 else: signal.automation.approach_control.rh1.disable_route()
721 # RH2 Route (sig or sub)
722 if sig_routes[4] and approach_control: signal.automation.approach_control.rh2.enable_route()
723 else: signal.automation.approach_control.rh2.disable_route()
724 # Enable the Approach sensor entry box
725 signal.automation.gpio_sensors.approach.enable()
726 else:
727 signal.automation.approach_control.main.disable_route()
728 signal.automation.approach_control.lh1.disable_route()
729 signal.automation.approach_control.lh2.disable_route()
730 signal.automation.approach_control.rh1.disable_route()
731 signal.automation.approach_control.rh2.disable_route()
732 signal.automation.approach_control.disable_release_on_yel()
733 signal.automation.approach_control.disable_release_on_red()
734 signal.automation.approach_control.disable_release_on_red_sig_ahead()
735 # Disable the Approach sensor entry box
736 signal.automation.gpio_sensors.approach.disable()
737 return()
739#------------------------------------------------------------------------------------
740# Top level Edit signal class (Has 3 tabs - Configuration, Interlocking and Automation
741#------------------------------------------------------------------------------------
743class edit_signal:
744 def __init__(self, root, object_id):
745 global open_windows
746 # If there is already a window open then we just make it jump to the top and exit
747 if object_id in open_windows.keys(): 747 ↛ 748line 747 didn't jump to line 748, because the condition on line 747 was never true
748 open_windows[object_id].lift()
749 open_windows[object_id].state('normal')
750 open_windows[object_id].focus_force()
751 else:
752 # This is the UUID for the object being edited
753 self.object_id = object_id
754 # Creatre the basic Top Level window
755 self.window = Tk.Toplevel(root)
756 self.window.protocol("WM_DELETE_WINDOW", self.close_window)
757 self.window.resizable(False, False)
758 open_windows[object_id] = self.window
759 # Create the common Apply/OK/Reset/Cancel buttons for the window (packed first to remain visible)
760 self.controls = common.window_controls(self.window, self.load_state, self.save_state, self.close_window)
761 self.controls.frame.pack(side=Tk.BOTTOM, padx=2, pady=2)
762 # Create the Validation error message (this gets packed/unpacked on apply/save)
763 self.validation_error = Tk.Label(self.window, text="Errors on Form need correcting", fg="red")
764 # Create the Notebook (for the tabs)
765 self.tabs = ttk.Notebook(self.window)
766 # Create the Window tabs
767 self.tab1 = Tk.Frame(self.tabs)
768 self.tabs.add(self.tab1, text="Configuration")
769 self.tab2 = Tk.Frame(self.tabs)
770 self.tabs.add(self.tab2, text="Interlocking")
771 self.tabs.pack()
772 self.tab3 = Tk.Frame(self.tabs)
773 self.tabs.add(self.tab3, text="Automation")
774 self.tabs.pack()
775 # The config tab needs references to all the 'config changed' callback functions
776 self.config = configure_signal_tab1.signal_configuration_tab(self.tab1,
777 self.sig_type_updated, self.sub_type_updated, self.route_type_updated,
778 self.route_selections_updated, self.sig_routes_updated,
779 self.sub_routes_updated, self.dist_routes_updated)
780 # The interlocking tab needs the parent object so the sig_id can be accessed for validation
781 self.locking = configure_signal_tab2.signal_interlocking_tab(self.tab2, self)
782 # The automation tab needs the parent object so the sig_id can be accessed for validation
783 self.automation = configure_signal_tab3.signal_automation_tab(self.tab3, self)
784 # load the initial UI state
785 self.load_state()
787 def sig_type_updated(self):
788 # The signal type has been changed (colour-light/semaphore/ground-pos-ground-disc)
789 self.config.subtype.set_value(1)
790 update_tab1_signal_subtype_selections(self)
791 update_tab1_signal_aspect_selections(self)
792 update_tab1_route_selection_elements(self)
793 update_tab1_signal_ui_elements(self)
794 update_tab2_available_signal_routes(self)
795 update_tab2_interlock_ahead_selection(self)
796 update_tab3_track_section_ahead_routes(self)
797 update_tab3_general_settings_selections(self)
798 update_tab3_timed_signal_selections(self)
799 update_tab3_approach_control_selections(self)
800 update_tab3_signal_ui_elements(self)
802 def sub_type_updated(self):
803 # The signal subtype has been changed (choices dependant on signal type)
804 update_tab1_signal_aspect_selections(self)
805 update_tab1_route_selection_elements(self)
806 update_tab1_signal_ui_elements(self)
807 update_tab2_available_signal_routes(self)
808 update_tab2_interlock_ahead_selection(self)
809 update_tab3_track_section_ahead_routes(self)
810 update_tab3_general_settings_selections(self)
811 update_tab3_approach_control_selections(self)
812 update_tab3_signal_ui_elements(self)
814 def route_type_updated(self):
815 # The route indication type has changed (none/theatre/feather/semaphore-arms)
816 update_tab1_route_selection_elements(self)
817 update_tab1_signal_ui_elements(self)
818 update_tab2_available_signal_routes(self)
819 update_tab2_interlock_ahead_selection(self)
820 update_tab3_track_section_ahead_routes(self)
821 update_tab3_timed_signal_selections(self)
822 update_tab3_approach_control_selections(self)
824 def route_selections_updated(self):
825 # A Theatre route has been enabled/disabled on Tab1
826 # A Feather route has been enabled/disabled on Tab1
827 # A signal route (no route indications) has been enabled/disabled on Tab1
828 # A subsidary route (no route indications) has been enabled/disabled on Tab1
829 update_tab2_available_signal_routes(self)
830 update_tab2_interlock_ahead_selection(self)
831 update_tab3_track_section_ahead_routes(self)
832 update_tab3_timed_signal_selections(self)
833 update_tab3_approach_control_selections(self)
835 def sig_routes_updated(self):
836 # A semaphore main signal arm has been enabled/disabled on Tab 1
837 # This means any secondary distant arm will also be enabled/disabled)
838 update_tab2_available_signal_routes(self)
839 update_tab2_interlock_ahead_selection(self)
840 update_tab3_track_section_ahead_routes(self)
841 update_tab3_timed_signal_selections(self)
842 update_tab3_approach_control_selections(self)
844 def sub_routes_updated(self):
845 # A semaphore subsidary arm has been enabled/disabled on Tab1
846 # A colour light subsidary has been enabled/disabled on Tab1
847 update_tab1_route_selection_elements(self)
848 update_tab1_signal_ui_elements(self)
849 update_tab2_available_signal_routes(self)
850 update_tab2_interlock_ahead_selection(self)
851 update_tab3_track_section_ahead_routes(self)
852 update_tab3_timed_signal_selections(self)
853 update_tab3_approach_control_selections(self)
855 def dist_routes_updated(self):
856 # A secondary semaphore distant arm has been enabled/disabled on Tab1
857 update_tab2_interlock_ahead_selection(self)
858 update_tab3_general_settings_selections(self)
860#------------------------------------------------------------------------------------
861# Load save and close class functions
862#------------------------------------------------------------------------------------
864 def load_state(self):
865 # Check the object we are editing still exists (hasn't been deleted from the schematic)
866 # If it no longer exists then we just destroy the window and exit without saving
867 if self.object_id not in objects.schematic_objects.keys(): 867 ↛ 868line 867 didn't jump to line 868, because the condition on line 867 was never true
868 self.close_window()
869 else:
870 item_id = objects.schematic_objects[self.object_id]["itemid"]
871 # Label the edit window with the Signal ID
872 self.window.title("Signal "+str(item_id))
873 # Set the Initial UI state from the current object settings. Note that several
874 # of the elements need the current signal ID to validate the DCC addresses
875 self.config.sigid.set_value(item_id)
876 self.config.sigtype.set_value(objects.schematic_objects[self.object_id]["itemtype"])
877 self.config.subtype.set_value(objects.schematic_objects[self.object_id]["itemsubtype"])
878 self.config.aspects.set_subsidary(objects.schematic_objects[self.object_id]["subsidary"], item_id)
879 self.config.aspects.set_addresses(objects.schematic_objects[self.object_id]["dccaspects"], item_id)
880 self.config.feathers.set_feathers(objects.schematic_objects[self.object_id]["feathers"])
881 self.config.feathers.set_addresses(objects.schematic_objects[self.object_id]["dccfeathers"], item_id)
882 self.config.feathers.set_auto_inhibit(objects.schematic_objects[self.object_id]["dccautoinhibit"])
883 self.config.theatre.set_theatre(objects.schematic_objects[self.object_id]["dcctheatre"], item_id)
884 self.config.semaphores.set_arms(objects.schematic_objects[self.object_id]["sigarms"], item_id)
885 self.config.sig_routes.set_values(objects.schematic_objects[self.object_id]["sigroutes"])
886 self.config.sub_routes.set_values(objects.schematic_objects[self.object_id]["subroutes"])
887 # These are the general settings for the signal
888 if objects.schematic_objects[self.object_id]["orientation"] == 180: rot = True
889 else:rot = False
890 self.config.settings.set_value(rot)
891 # These elements are for the signal intelocking tab. Note that several of
892 # the elements need the current signal ID to validate the signal entries
893 self.locking.interlocking.set_routes(objects.schematic_objects[self.object_id]["pointinterlock"], item_id)
894 self.locking.interlocked_sections.set_routes(objects.schematic_objects[self.object_id]["trackinterlock"])
895 self.locking.conflicting_sigs.set_values(objects.schematic_objects[self.object_id]["siginterlock"], item_id)
896 self.locking.interlock_ahead.set_value(objects.schematic_objects[self.object_id]["interlockahead"])
897 # These elements are for the Automation tab. Note that several elements
898 # need the current signal IDfor validation purposes
899 self.automation.gpio_sensors.approach.set_value(objects.schematic_objects[self.object_id]["approachsensor"][1], item_id)
900 self.automation.gpio_sensors.passed.set_value(objects.schematic_objects[self.object_id]["passedsensor"][1], item_id)
901 self.automation.track_occupancy.set_values(objects.schematic_objects[self.object_id]["tracksections"])
902 override = objects.schematic_objects[self.object_id]["overridesignal"]
903 main_auto = objects.schematic_objects[self.object_id]["fullyautomatic"]
904 dist_auto = objects.schematic_objects[self.object_id]["distautomatic"]
905 override_ahead = objects.schematic_objects[self.object_id]["overrideahead"]
906 self.automation.general_settings.set_values(override, main_auto, override_ahead, dist_auto)
907 self.automation.timed_signal.set_values(objects.schematic_objects[self.object_id]["timedsequences"], item_id)
908 self.automation.approach_control.set_values(objects.schematic_objects[self.object_id]["approachcontrol"])
909 # Configure the initial Route indication selection
910 feathers = objects.schematic_objects[self.object_id]["feathers"]
911 if objects.schematic_objects[self.object_id]["itemtype"] == signals_common.sig_type.colour_light.value:
912 if objects.schematic_objects[self.object_id]["theatreroute"]:
913 self.config.routetype.set_value(3)
914 elif feathers[0] or feathers[1] or feathers[2] or feathers[3] or feathers[4]:
915 self.config.routetype.set_value(2)
916 else:
917 self.config.routetype.set_value(1)
918 elif objects.schematic_objects[self.object_id]["itemtype"] == signals_common.sig_type.semaphore.value:
919 if objects.schematic_objects[self.object_id]["theatreroute"]:
920 self.config.routetype.set_value(3)
921 elif has_route_arms(self):
922 self.config.routetype.set_value(4)
923 else:
924 self.config.routetype.set_value(1)
925 else:
926 self.config.routetype.set_value(1)
927 # Set the initial UI selections
928 update_tab1_signal_subtype_selections(self)
929 update_tab1_signal_aspect_selections(self)
930 update_tab1_route_selection_elements(self)
931 update_tab1_signal_ui_elements(self)
932 update_tab2_available_signal_routes(self)
933 update_tab2_interlock_ahead_selection(self)
934 update_tab3_track_section_ahead_routes(self)
935 update_tab3_general_settings_selections(self)
936 update_tab3_timed_signal_selections(self)
937 update_tab3_approach_control_selections(self)
938 update_tab3_signal_ui_elements(self)
939 # Hide the validation error message
940 self.validation_error.pack_forget()
941 return()
943 def save_state(self, close_window):
944 # Check the object we are editing still exists (hasn't been deleted from the schematic)
945 # If it no longer exists then we just destroy the window and exit without saving
946 if self.object_id not in objects.schematic_objects.keys(): 946 ↛ 947line 946 didn't jump to line 947, because the condition on line 946 was never true
947 self.close_window()
948 else:
949 # Validate all user entries prior to applying the changes. Each of these would have
950 # been validated on entry, but changes to other objects may have been made since then
951 # Note that we validate ALL elements to ensure all UI elements are updated accordingly
952 valid = True
953 if not self.config.sigid.validate(): valid = False
954 if not self.config.aspects.validate(): valid = False
955 if not self.config.theatre.validate(): valid = False
956 if not self.config.feathers.validate(): valid = False
957 if not self.config.semaphores.validate(): valid = False
958 if not self.locking.interlocking.validate(): valid = False
959 if not self.locking.interlocked_sections.validate(): valid = False
960 if not self.locking.conflicting_sigs.validate(): valid = False
961 if not self.automation.gpio_sensors.validate(): valid = False
962 if not self.automation.track_occupancy.validate(): valid = False
963 if not self.automation.timed_signal.validate(): valid = False
964 if valid: 964 ↛ 1015line 964 didn't jump to line 1015, because the condition on line 964 was never false
965 # Copy the original signal Configuration (elements get overwritten as required)
966 new_object_configuration = copy.deepcopy(objects.schematic_objects[self.object_id])
967 # Update the signal coniguration elements from the current user selections
968 new_object_configuration["itemid"] = self.config.sigid.get_value()
969 new_object_configuration["itemtype"] = self.config.sigtype.get_value()
970 new_object_configuration["itemsubtype"] = self.config.subtype.get_value()
971 new_object_configuration["subsidary"] = self.config.aspects.get_subsidary()
972 new_object_configuration["feathers"] = self.config.feathers.get_feathers()
973 new_object_configuration["dccaspects"] = self.config.aspects.get_addresses()
974 new_object_configuration["dccfeathers"] = self.config.feathers.get_addresses()
975 new_object_configuration["dcctheatre"] = self.config.theatre.get_theatre()
976 new_object_configuration["sigarms"] = self.config.semaphores.get_arms()
977 new_object_configuration["sigroutes"] = get_sig_routes(self)
978 new_object_configuration["subroutes"] = get_sub_routes(self)
979 # These are the general settings for the signal
980 rot = self.config.settings.get_value()
981 if rot: new_object_configuration["orientation"] = 180
982 else: new_object_configuration["orientation"] = 0
983 # Set the Theatre route indicator flag if that particular radio button is selected
984 if self.config.routetype.get_value() == 3:
985 new_object_configuration["theatreroute"] = True
986 new_object_configuration["dccautoinhibit"] = self.config.theatre.get_auto_inhibit()
987 else:
988 new_object_configuration["dccautoinhibit"] = self.config.feathers.get_auto_inhibit()
989 new_object_configuration["theatreroute"] = False
990 # These elements are for the signal intelocking tab
991 new_object_configuration["pointinterlock"] = self.locking.interlocking.get_routes()
992 new_object_configuration["trackinterlock"] = self.locking.interlocked_sections.get_routes()
993 new_object_configuration["siginterlock"] = self.locking.conflicting_sigs.get_values()
994 new_object_configuration["interlockahead"] = self.locking.interlock_ahead.get_value()
995 # These elements are for the Automation tab
996 new_object_configuration["passedsensor"][0] = True
997 new_object_configuration["passedsensor"][1] = self.automation.gpio_sensors.passed.get_value()
998 new_object_configuration["approachsensor"][0] = self.automation.approach_control.is_selected()
999 new_object_configuration["approachsensor"][1] = self.automation.gpio_sensors.approach.get_value()
1000 new_object_configuration["tracksections"] = self.automation.track_occupancy.get_values()
1001 override, main_auto, override_ahead, dist_auto = self.automation.general_settings.get_values()
1002 new_object_configuration["fullyautomatic"] = main_auto
1003 new_object_configuration["distautomatic"] = dist_auto
1004 new_object_configuration["overridesignal"] = override
1005 new_object_configuration["overrideahead"] = override_ahead
1006 new_object_configuration["timedsequences"] = self.automation.timed_signal.get_values()
1007 new_object_configuration["approachcontrol"] = self.automation.approach_control.get_values()
1008 # Save the updated configuration (and re-draw the object)
1009 objects.update_object(self.object_id, new_object_configuration)
1010 # Close window on "OK" or re-load UI for "apply"
1011 if close_window: self.close_window()
1012 else: self.load_state()
1013 else:
1014 # Display the validation error message
1015 self.validation_error.pack(side=Tk.BOTTOM, before=self.controls.frame)
1016 return()
1018 def close_window(self):
1019 self.window.destroy()
1020 del open_windows[self.object_id]
1022#############################################################################################