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

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#------------------------------------------------------------------------------------ 

26 

27import copy 

28 

29import tkinter as Tk 

30from tkinter import ttk 

31 

32from . import common 

33from . import objects 

34from . import configure_signal_tab1 

35from . import configure_signal_tab2 

36from . import configure_signal_tab3 

37 

38from ..library import signals_common 

39from ..library import signals_colour_lights 

40from ..library import signals_semaphores 

41 

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#------------------------------------------------------------------------------------ 

48 

49open_windows={} 

50 

51#------------------------------------------------------------------------------------ 

52# Helper function to find out if the signal has a subsidary (colour light or semaphore) 

53#------------------------------------------------------------------------------------ 

54 

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] ) ) 

64 

65#------------------------------------------------------------------------------------ 

66# Helper functions to find out if the signal has distant arms (semaphore 

67#------------------------------------------------------------------------------------ 

68 

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] ) ) 

76 

77#------------------------------------------------------------------------------------ 

78# Helper functions to find out if the signal has route arms (semaphore) 

79#------------------------------------------------------------------------------------ 

80 

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] ) ) 

95 

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#------------------------------------------------------------------------------------ 

100 

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) 

135 

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) 

161 

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) 

194 

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#------------------------------------------------------------------------------------ 

199 

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() 

231 

232#------------------------------------------------------------------------------------ 

233# Update the available signal subtype selections based on the signal type 

234#------------------------------------------------------------------------------------ 

235 

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() 

267 

268#------------------------------------------------------------------------------------ 

269# Update the available aspect selections based on signal type and subtype  

270#------------------------------------------------------------------------------------ 

271 

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() 

327 

328#------------------------------------------------------------------------------------ 

329# Update the Route selections based on signal type  

330#------------------------------------------------------------------------------------ 

331 

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() 

373 

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() 

432 

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() 

449 

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() 

466 

467 return() 

468 

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#------------------------------------------------------------------------------------ 

473 

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() 

546 

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#------------------------------------------------------------------------------------ 

551 

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() 

566 

567#------------------------------------------------------------------------------------ 

568# Hide/show the various route indication UI elements depending on what is selected 

569#------------------------------------------------------------------------------------ 

570 

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() 

590 

591#------------------------------------------------------------------------------------ 

592# Enable/disable the Tab3 general settings depending on what is selected 

593#------------------------------------------------------------------------------------ 

594 

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() 

622 

623#------------------------------------------------------------------------------------ 

624# Enable/disable the Tab3 track occupancy route selection elements 

625#------------------------------------------------------------------------------------ 

626 

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() 

654 

655#------------------------------------------------------------------------------------ 

656# Enable/disable the Tab3 Timed Signal and approach control route selection elements 

657#------------------------------------------------------------------------------------ 

658 

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() 

680 

681#------------------------------------------------------------------------------------ 

682# Enable/disable the Tab3 Timed Signal and approach control route selection elements 

683#------------------------------------------------------------------------------------ 

684 

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() 

738 

739#------------------------------------------------------------------------------------ 

740# Top level Edit signal class (Has 3 tabs - Configuration, Interlocking and Automation  

741#------------------------------------------------------------------------------------ 

742 

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() 

786 

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) 

801 

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) 

813 

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) 

823 

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) 

834 

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) 

843 

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) 

854 

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) 

859 

860#------------------------------------------------------------------------------------ 

861# Load save and close class functions 

862#------------------------------------------------------------------------------------ 

863 

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() 

942 

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() 

1017 

1018 def close_window(self): 

1019 self.window.destroy() 

1020 del open_windows[self.object_id] 

1021 

1022#############################################################################################