Coverage for /home/pi/Software/model-railway-signalling/model_railway_signals/editor/configure_signal_tab1.py: 95%

551 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2024-04-10 15:08 +0100

1#------------------------------------------------------------------------------------ 

2# Functions and sub Classes for the Edit Signal "Configuration" Tab 

3# 

4# Makes the following external API calls to library modules: 

5# signals_common.sig_exists(id) - To see if the signal exists (local) 

6# dcc_control.dcc_address_mapping(address) - To see if an address is already mapped 

7# 

8# Inherits the following common editor base classes (from common): 

9# common.dcc_entry_box 

10# common.state_box 

11# common.check_box 

12# common.entry_box 

13# common.object_id_selection 

14# common.selection_buttons 

15# 

16#------------------------------------------------------------------------------------ 

17 

18import tkinter as Tk 

19 

20from . import common 

21from ..library import signals_common 

22from ..library import dcc_control 

23 

24#------------------------------------------------------------------------------------ 

25# Class for a signal_dcc_entry_box - builds on the common DCC Entry Box class 

26# Class instance methods inherited from the parent class are: 

27# "get_value" - will return the last valid entry box value (dcc address) 

28# Public class instance methods provided by this child class are 

29# "set_value" - set the initial value of the dcc_entry_box (int) - Also 

30# sets the current item ID (int) for validation purposes 

31# "validate" - Validates the DCC address is not mapped to another item 

32#------------------------------------------------------------------------------------ 

33 

34class signal_dcc_entry_box(common.dcc_entry_box): 

35 def __init__(self, parent_frame, callback=None): 

36 # We need the current Signal ID to validate the DCC Address entry 

37 self.current_item_id = 0 

38 super().__init__(parent_frame, callback=callback) 

39 

40 def validate(self): 

41 # Do the basic item validation first (exists and not current item ID) 

42 valid = super().validate(update_validation_status=False) 

43 if valid and self.entry.get() != "": 

44 # Ensure the address is not mapped to another signal or point. Note that to cater for Semaphore 

45 # Signals with secondary distant arms we also need to check for Signal IDs + 100 

46 dcc_address = int(self.entry.get()) 

47 dcc_mapping = dcc_control.dcc_address_mapping(dcc_address) 

48 if dcc_mapping is not None and (dcc_mapping[0] != "Signal" or 48 ↛ 51line 48 didn't jump to line 51, because the condition on line 48 was never true

49 (dcc_mapping[1] != self.current_item_id and dcc_mapping[1] != self.current_item_id + 100)): 

50 # We need to correct the mapped signal ID for secondary distants 

51 if dcc_mapping[0] == "Signal" and dcc_mapping[1] > 99: dcc_mapping[1] = dcc_mapping[1] - 100 

52 self.TT.text = ("DCC address is already mapped to "+dcc_mapping[0]+" "+str(dcc_mapping[1])) 

53 valid = False 

54 self.set_validation_status(valid) 

55 return(valid) 

56 

57 def set_value(self, value:int, item_id:int): 

58 self.current_item_id = item_id 

59 super().set_value(value) 

60 

61#------------------------------------------------------------------------------------ 

62# Compound UI element for a signal_dcc_command_entry (address + command logic). 

63# Uses the signal_dcc_entry_box and state_box classes. Note that the state_box 

64# is only enabled when a valid DCC address has been entered into the entry_box. 

65# Note the responsibility of the instantiating func/class to 'pack' the Frame of 

66# the UI element - i.e. '<class_instance>.frame.pack()' 

67# 

68# Public class instance methods provided by this class are 

69# "validate" - validate the current entry_box value and return True/false 

70# "set_value" - will set the current value [address:int, state:bool] - Also 

71# sets the current item ID (int) for validation purposes 

72# "get_value" - will return the last "valid" value [address:int, state:bool] 

73# "disable" - disables/blanks the entry_box (and associated state button) 

74# "enable" enables/loads the entry_box (and associated state button) 

75#------------------------------------------------------------------------------------ 

76 

77class signal_dcc_command_entry(): 

78 def __init__(self, parent_frame): 

79 # create a frame to pack the two elements into 

80 self.frame = Tk.Frame(parent_frame) 

81 # Create the address entry box and the associated dcc state box 

82 self.EB = signal_dcc_entry_box(self.frame, callback=self.eb_updated) 

83 self.EB.pack(side=Tk.LEFT) 

84 self.CB = common.state_box(self.frame, label_off="OFF", label_on="ON", 

85 width=4, tool_tip="Set the DCC logic for the command") 

86 self.CB.pack(side=Tk.LEFT) 

87 

88 def eb_updated(self): 

89 if self.EB.entry.get() == "": 

90 self.CB.disable() 

91 else: self.CB.enable() 

92 

93 def validate(self): 

94 return (self.EB.validate()) 

95 

96 def enable(self): 

97 self.EB.enable() 

98 self.eb_updated() 

99 

100 def disable(self): 

101 self.EB.disable() 

102 self.eb_updated() 

103 

104 def set_value(self, dcc_command:[int, bool], item_id:int): 

105 # A DCC Command comprises a 2 element list of [DCC_Address, DCC_State] 

106 self.EB.set_value(dcc_command[0], item_id) 

107 self.CB.set_value(dcc_command[1]) 

108 self.eb_updated() 

109 

110 def get_value(self): 

111 # Returns a 2 element list of [DCC_Address, DCC_State] 

112 # When disabled (or empty) will always return [0, False] 

113 # When invalid will return [last valid address, current state] 

114 return([self.EB.get_value(), self.CB.get_value()]) 

115 

116#------------------------------------------------------------------------------------ 

117# Class for the General Settings UI Element - Builds on the common checkbox class 

118# Public class instance methods inherited from the base check box class are: 

119# "set_value" - set the initial value of the Rotate checkbutton (int)  

120# "get_value" - get the last "validated" value of the Rotate checkbutton (int)  

121#------------------------------------------------------------------------------------ 

122 

123class general_settings(common.check_box): 

124 def __init__(self, parent_frame): 

125 # Create a Label frame to hold the general settings UI element 

126 # Packed onto the parent frame by the creating function/class 

127 self.frame = Tk.LabelFrame(parent_frame,text="General Config") 

128 # Create the "rotate" checkbutton and tool Tip 

129 super().__init__(self.frame, label="Rotated", 

130 tool_tip = "Select to rotate signal by 180 degrees") 

131 self.pack() 

132 

133#------------------------------------------------------------------------------------ 

134# Class for a semaphore route arm element (comprising checkbox and DCC address Box) 

135# Class instance methods provided by this class are: 

136# "validate" - validate the DCC entry box value and returns True/false 

137# "enable" - disables/blanks the checkbox and entry box 

138# "disable" - enables/loads the checkbox and entry box 

139# "set_element" - will set the element [enabled/disabled, address] 

140# Also sets the current item ID (int) for validation purposes 

141# "get_element" - returns the last "valid" value [enabled/disabled, address] 

142#------------------------------------------------------------------------------------ 

143 

144class semaphore_route_element(): 

145 def __init__(self, parent_frame, label:str, tool_tip:str, callback=None): 

146 # Callback for select/deselect of the checkbox 

147 self.callback = callback 

148 # Create a frame for the UI element (always packed into the parent frame) 

149 self.frame = Tk.Frame(parent_frame) 

150 self.frame.pack() 

151 # Create the checkbox and DCC entry Box (default tool tip for DCC Entry Box) 

152 self.CB = common.check_box(parent_frame, label=label, 

153 tool_tip=tool_tip, callback=self.cb_updated) 

154 self.CB.pack(side=Tk.LEFT) 

155 self.EB = signal_dcc_entry_box(parent_frame) 

156 self.EB.pack(side=Tk.LEFT) 

157 

158 def cb_updated(self): 

159 self.update_eb_state() 

160 if self.callback is not None: self.callback() 

161 

162 def update_eb_state(self): 

163 if self.CB.get_value(): self.EB.enable() 

164 else: self.EB.disable() 

165 

166 def validate(self): 

167 # Validate the DCC Address 

168 return(self.EB.validate()) 

169 

170 def enable0(self): 

171 self.CB.enable() 

172 self.update_eb_state() 

173 

174 def enable1(self): 

175 self.CB.enable1() 

176 self.update_eb_state() 

177 

178 def enable2(self): 

179 self.CB.enable2() 

180 self.update_eb_state() 

181 

182 def disable0(self): 

183 self.CB.disable() 

184 self.update_eb_state() 

185 

186 def disable1(self): 

187 self.CB.disable1() 

188 self.update_eb_state() 

189 

190 def disable2(self): 

191 self.CB.disable2() 

192 self.update_eb_state() 

193 

194 def set_element(self, signal_arm:[bool,int], item_id:int): 

195 # Each signal element comprises [enabled/disabled, address] 

196 self.CB.set_value(signal_arm[0]) 

197 self.EB.set_value(signal_arm[1], item_id) 

198 self.update_eb_state() 

199 

200 def get_element(self): 

201 # Each signal element comprises [enabled/disabled, address] 

202 return( [self.CB.get_value(), self.EB.get_value()] ) 

203 

204#------------------------------------------------------------------------------------ 

205# Class for a semaphore route arm group (comprising main, subsidary, and distant arms) 

206# Uses the base semaphore_route_element class from above 

207# Public Class instance methods are: 

208# "validate" - validate the current entry box values and return True/false 

209# "enable_route" - disables/blanks all checkboxes and entry boxes 

210# "disable_route" - enables/loads all checkboxes and entry boxes 

211# "enable_distant" - enables/loads the distant checkbox and entry box 

212# "set_route" - will set the element [enabled/disabled, address] 

213# Also sets the current item ID (int) for validation purposes 

214# "get_route" - returns the last "valid" value [enabled/disabled, address] 

215# The callbacks are made when the signal arms are selected or deselected 

216#------------------------------------------------------------------------------------ 

217 

218class semaphore_route_group(): 

219 def __init__(self, parent_frame, label:str,sig_arms_updated_callback=None, 

220 sub_arms_updated_callback=None, dist_arms_updated_callback=None): 

221 # Callback for change in signal arm selections 

222 self.sig_arms_callback = sig_arms_updated_callback 

223 self.sub_arms_callback = sub_arms_updated_callback 

224 self.dist_arms_callback = dist_arms_updated_callback 

225 # Create a frame for the UI element (always packed into the parent frame) 

226 self.frame = Tk.Frame(parent_frame) 

227 self.frame.pack() 

228 # Create the lable and route elements (these are packed by the class instances) 

229 self.label = Tk.Label(self.frame, anchor='w', width=5, text=label) 

230 self.label.pack(side=Tk.LEFT) 

231 self.sig = semaphore_route_element(self.frame, label="Main (home) arm ", 

232 tool_tip= "Select to add a home signal arm for this route", 

233 callback=self.sig_arms_updated) 

234 self.sub = semaphore_route_element(self.frame, label="Subsidary arm ", 

235 tool_tip="Select to add a subsidary signal arm for this route", 

236 callback=self.sub_arms_updated) 

237 self.dist = semaphore_route_element(self.frame, label="Distant arm ", 

238 tool_tip="Select to add a distant signal arm for this route", 

239 callback=self.dist_arms_updated) 

240 

241 def sig_arms_updated(self): 

242 self.enable_disable_distant_arms() 

243 if self.sig_arms_callback is not None: self.sig_arms_callback() 

244 

245 def sub_arms_updated(self): 

246 if self.sub_arms_callback is not None: self.sub_arms_callback() 

247 

248 def dist_arms_updated(self): 

249 if self.sig_arms_callback is not None: self.dist_arms_callback() 

250 

251 def enable_disable_distant_arms(self): 

252 # A route can only have a secondary distant arm if there is a main home arm 

253 # Use the 'enable0/disable0' functions ('enable1/disable1' is used to to enable/disable 

254 # the entire route and 'enable2/disable2' is used to enable/disable individual sig arms) 

255 if self.sig.get_element()[0]: self.dist.enable0() 

256 else: self.dist.disable0() 

257 

258 def validate(self): 

259 return(self.sig.validate() and self.sub.validate() and self.dist.validate()) 

260 

261 def enable_route(self): 

262 self.sig.enable1() 

263 self.sub.enable1() 

264 self.dist.enable1() 

265 

266 def disable_route(self): 

267 self.sig.disable1() 

268 self.sub.disable1() 

269 self.dist.disable1() 

270 

271 def enable_signal(self): 

272 self.sig.enable2() 

273 

274 def disable_signal(self): 

275 self.sig.disable2() 

276 

277 def enable_subsidary(self): 

278 self.sub.enable2() 

279 

280 def disable_subsidary(self): 

281 self.sub.disable2() 

282 

283 def enable_distant(self): 

284 self.dist.enable2() 

285 

286 def disable_distant(self): 

287 self.dist.disable2() 

288 

289 def set_route(self, signal_elements:[[bool,int],], item_id:int): 

290 # Signal Group comprises: [signal, subsidary, distant] 

291 # Each signal element comprises [enabled/disabled, address] 

292 self.sig.set_element(signal_elements[0], item_id) 

293 self.sub.set_element(signal_elements[1], item_id) 

294 self.dist.set_element(signal_elements[2], item_id) 

295 self.enable_disable_distant_arms() 

296 

297 def get_route(self): 

298 # Signal Group comprises: [signal, subsidary, distant] 

299 # Each signal element comprises [enabled/disabled, address] 

300 return ( [ self.sig.get_element(), 

301 self.sub.get_element(), 

302 self.dist.get_element() ] ) 

303 

304#------------------------------------------------------------------------------------ 

305# Class for the semaphore signal arms (comprising all possible signal arm combinations) 

306# Uses the base semaphore_route_group class from above 

307# Public Class instance methods are: 

308# "validate" - validate the current entry box values and return True/false 

309# "disable_routes" - disables/blanks all checkboxes and entry boxes apart from MAIN 

310# "enable_routes" - enables/loads all checkboxes and entry boxes apart from MAIN 

311# "disable_distants" - disables/blanks all distant checkboxes and entry boxes 

312# "enable_distants" - enables/loads all distant checkboxes and entry boxes 

313# "disable_subsidaries" - disables/blanks all subsidary checkboxes and entry boxes 

314# "enable_subsidaries" - enables/loads all subsidary checkboxes and entry boxes 

315# "set_arms" - will set all ui elements (enabled/disabled, addresses) 

316# Also sets the current item ID (int) for validation purposes 

317# "get_arms" - returns the last "valid" values (enabled/disabled, addresses) 

318# The callbacks are made when the signal arms are selected or deselected 

319#------------------------------------------------------------------------------------ 

320 

321class semaphore_signal_arms(): 

322 def __init__(self, parent_frame, sig_arms_updated, subs_arms_updated, dist_arms_updated): 

323 # Create a frame for this UI element (packed by the creating function/class) 

324 self.frame = Tk.LabelFrame(parent_frame, text="Semaphore Signal Arms and DCC Addresses") 

325 # Create the route group for each route (packed into the frame by the class instances) 

326 self.main = semaphore_route_group(self.frame, label="Main", 

327 sig_arms_updated_callback=sig_arms_updated, 

328 sub_arms_updated_callback=subs_arms_updated, 

329 dist_arms_updated_callback=dist_arms_updated) 

330 self.lh1 = semaphore_route_group(self.frame, label="LH1", 

331 sig_arms_updated_callback=sig_arms_updated, 

332 sub_arms_updated_callback=subs_arms_updated, 

333 dist_arms_updated_callback=dist_arms_updated) 

334 self.lh2 = semaphore_route_group(self.frame, label="LH2", 

335 sig_arms_updated_callback=sig_arms_updated, 

336 sub_arms_updated_callback=subs_arms_updated, 

337 dist_arms_updated_callback=dist_arms_updated) 

338 self.rh1 = semaphore_route_group(self.frame, label="RH1", 

339 sig_arms_updated_callback=sig_arms_updated, 

340 sub_arms_updated_callback=subs_arms_updated, 

341 dist_arms_updated_callback=dist_arms_updated) 

342 self.rh2 = semaphore_route_group(self.frame, label="RH2", 

343 sig_arms_updated_callback=sig_arms_updated, 

344 sub_arms_updated_callback=subs_arms_updated, 

345 dist_arms_updated_callback=dist_arms_updated) 

346 # The signal arm for the main route cannot be deselected so we need to 

347 # set the value and then disable the base tkinter widget (we can't use 

348 # the disable function as this would also 'blank' the checkbox) 

349 self.main.sig.CB.set_value(True) 

350 self.main.sig.CB.config(state="disabled") 

351 

352 def validate(self): 

353 return(self.main.validate() and self.lh1.validate() and self.lh2.validate() 

354 and self.rh1.validate() and self.rh2.validate()) 

355 

356 def enable_diverging_routes(self): 

357 self.lh1.enable_route() 

358 self.lh2.enable_route() 

359 self.rh1.enable_route() 

360 self.rh2.enable_route() 

361 

362 def disable_diverging_routes(self): 

363 self.lh1.disable_route() 

364 self.lh2.disable_route() 

365 self.rh1.disable_route() 

366 self.rh2.disable_route() 

367 

368 def enable_main_route(self): 

369 # Enable the main signal route. Note that when the route is enabled 

370 # the main signal arm is always selected (and cannot be de-selected) 

371 self.main.sig.CB.set_value(True) 

372 self.main.enable_route() 

373 self.main.sig.CB.config(state="disabled") 

374 

375 def disable_main_route(self): 

376 self.main.disable_route() 

377 

378 def enable_subsidaries(self): 

379 self.main.enable_subsidary() 

380 self.lh1.enable_subsidary() 

381 self.lh2.enable_subsidary() 

382 self.rh1.enable_subsidary() 

383 self.rh2.enable_subsidary() 

384 

385 def disable_subsidaries(self): 

386 self.main.disable_subsidary() 

387 self.lh1.disable_subsidary() 

388 self.lh2.disable_subsidary() 

389 self.rh1.disable_subsidary() 

390 self.rh2.disable_subsidary() 

391 

392 def enable_distants(self): 

393 self.main.enable_distant() 

394 self.lh1.enable_distant() 

395 self.lh2.enable_distant() 

396 self.rh1.enable_distant() 

397 self.rh2.enable_distant() 

398 

399 def disable_distants(self): 

400 self.main.disable_distant() 

401 self.lh1.disable_distant() 

402 self.lh2.disable_distant() 

403 self.rh1.disable_distant() 

404 self.rh2.disable_distant() 

405 

406 def set_arms(self, signal_arms:[[[bool,int],],], item_id:int): 

407 # Signal arm list comprises:[main, LH1, LH2, RH1, RH2] 

408 # Each Route element comprises: [signal, subsidary, distant] 

409 # Each signal element comprises [enabled/disabled, address] 

410 self.main.set_route(signal_arms[0], item_id) 

411 self.lh1.set_route(signal_arms[1], item_id) 

412 self.lh2.set_route(signal_arms[2], item_id) 

413 self.rh1.set_route(signal_arms[3], item_id) 

414 self.rh2.set_route(signal_arms[4], item_id) 

415 

416 def get_arms(self): 

417 # Signal arm list comprises:[main, LH1, LH2, RH1, RH2] 

418 # Each Route element comprises: [signal, subsidary, distant] 

419 # Each signal element comprises [enabled/disabled, address] 

420 # Note that the MAIN signal arm is always enabled (for semaphores) 

421 main_route_with_signal_arm_enabled = self.main.get_route() 

422 main_route_with_signal_arm_enabled[0][0] = True 

423 return ( [ main_route_with_signal_arm_enabled, 

424 self.lh1.get_route(), 

425 self.lh2.get_route(), 

426 self.rh1.get_route(), 

427 self.rh2.get_route() ] ) 

428 

429#------------------------------------------------------------------------------------ 

430# Class to create a sequence of DCC selection boxes - for colour light signal aspects, 

431# feather route indications and theatre route indications 

432# Public Class instance methods are: 

433# "validate_addresses" - validate the current entry box values and return True/false 

434# "enable_addresses" - disables/blanks all entry boxes (and state buttons) 

435# "disable_addresses" enables/loads all entry box (and state buttona) 

436# "set_addresses" - will set the values of the entry boxes (pass in a list) 

437# Also sets the current item ID (int) for validation purposes 

438# "get_addresses" - will return a list of the last "valid" entries 

439#------------------------------------------------------------------------------------ 

440 

441class dcc_entry_boxes: 

442 def __init__(self, parent_frame): 

443 # Create the DCC command entry elements (packed directly into parent frame) 

444 self.dcc1 = signal_dcc_command_entry(parent_frame) 

445 self.dcc1.frame.pack(side=Tk.LEFT) 

446 self.dcc2 = signal_dcc_command_entry(parent_frame) 

447 self.dcc2.frame.pack(side=Tk.LEFT) 

448 self.dcc3 = signal_dcc_command_entry(parent_frame) 

449 self.dcc3.frame.pack(side=Tk.LEFT) 

450 self.dcc4 = signal_dcc_command_entry(parent_frame) 

451 self.dcc4.frame.pack(side=Tk.LEFT) 

452 self.dcc5 = signal_dcc_command_entry(parent_frame) 

453 self.dcc5.frame.pack(side=Tk.LEFT) 

454 self.dcc6 = signal_dcc_command_entry(parent_frame) 

455 self.dcc6.frame.pack(side=Tk.LEFT) 

456 

457 def validate_addresses(self): 

458 return ( self.dcc1.validate() and 

459 self.dcc2.validate() and 

460 self.dcc3.validate() and 

461 self.dcc4.validate() and 

462 self.dcc5.validate() and 

463 self.dcc6.validate() ) 

464 

465 def set_addresses(self, address_list:[[int,bool],], item_id:int): 

466 # DCC command sequence comprises [dcc1, dcc2, dcc3, dcc4, dcc5, dcc6] 

467 # Each dcc command element comprises: [dcc_address, dcc_state] 

468 self.dcc1.set_value(address_list[0], item_id) 

469 self.dcc2.set_value(address_list[1], item_id) 

470 self.dcc3.set_value(address_list[2], item_id) 

471 self.dcc4.set_value(address_list[3], item_id) 

472 self.dcc5.set_value(address_list[4], item_id) 

473 self.dcc6.set_value(address_list[5], item_id) 

474 

475 def get_addresses(self): 

476 # DCC command sequence comprises [dcc1, dcc2, dcc3, dcc4, dcc5, dcc6] 

477 # Each dcc command element comprises: [dcc_address, dcc_state] 

478 return( [ self.dcc1.get_value(), 

479 self.dcc2.get_value(), 

480 self.dcc3.get_value(), 

481 self.dcc4.get_value(), 

482 self.dcc5.get_value(), 

483 self.dcc6.get_value() ] ) 

484 

485 def enable_addresses(self): 

486 self.dcc1.enable() 

487 self.dcc2.enable() 

488 self.dcc3.enable() 

489 self.dcc4.enable() 

490 self.dcc5.enable() 

491 self.dcc6.enable() 

492 

493 def disable_addresses(self): 

494 self.dcc1.disable() 

495 self.dcc2.disable() 

496 self.dcc3.disable() 

497 self.dcc4.disable() 

498 self.dcc5.disable() 

499 self.dcc6.disable() 

500 

501#------------------------------------------------------------------------------------ 

502# Classes to create the DCC Entry boxes for a colour light signal aspect 

503# Builds on the common dcc_entry_boxes class above. 

504# Inherited Class instance methods are: 

505# "validate_addresses" - validate the current entry box values and return True/false 

506# "enable_addresses" - disables/blanks all entry boxes (and state buttons) 

507# "disable_addresses" enables/loads all entry box (and state buttona) 

508# "set_addresses" - will set the values of the entry boxes (pass in a list) 

509# Also sets the current item ID (int) for validation purposes 

510# "get_addresses" - will return a list of the last "valid" entries 

511#------------------------------------------------------------------------------------ 

512 

513class colour_light_aspect(dcc_entry_boxes): 

514 def __init__(self, parent_frame, label:str): 

515 # Create a frame for this UI element (always packed) 

516 self.frame = Tk.Frame(parent_frame) 

517 self.frame.pack() 

518 # Create the label for the DCC command sequence 

519 self.label = Tk.Label(self.frame, width=12, text=label, anchor='w') 

520 self.label.pack(side=Tk.LEFT) 

521 # Call the init function of the class we are inheriting from 

522 # The DCC entry boxes get packed into the frame by the parent class 

523 super().__init__(self.frame) 

524 

525#------------------------------------------------------------------------------------ 

526# Classes to create the DCC entry UI element for colour light signal aspects 

527# Class instance methods to use externally are: 

528# "validate" - validate all current DCC entry box values 

529# "set_addresses" - set the DCC command sequences for the aspects (pass in a list) 

530# Also sets the current item ID (int) for validation purposes 

531# "get_addresses" - return a list of the "validated" DCC command sequences 

532# "set_subsidary" - set the subsidary signal status [has_subsidary, dcc_address] 

533# Also sets the current item ID (int) for validation purposes 

534# "get_subsidary" - return the subsidary signal status [has_subsidary, dcc_address] 

535# "enable_subsidary" - enables/loads the subsidary signal selection (CB/address) 

536# "disable_subsidary" - disables/clears the subsidary signal selection (CB/address) 

537# "enable_aspects" - enables/loads the dcc command sequences for all aspects 

538# "disable_aspects" - disables/clears the dcc command sequences for all aspects 

539# The callback is made when the subsidary signal selection is updated 

540#------------------------------------------------------------------------------------ 

541 

542class colour_light_aspects(): 

543 def __init__(self, parent_frame, callback=None): 

544 # Callback for select/deselect of the subsidary signal 

545 self.callback = callback 

546 # Create a label frame (packed by the creating function/class) 

547 self.frame = Tk.LabelFrame(parent_frame, 

548 text="DCC command sequences for Colour Light signal aspects") 

549 # Create the DCC Entry Elements (packed into the frame by the parent class) 

550 self.red = colour_light_aspect(self.frame, label="Danger") 

551 self.grn = colour_light_aspect(self.frame, label="Proceed") 

552 self.ylw = colour_light_aspect(self.frame, label="Caution") 

553 self.dylw = colour_light_aspect(self.frame, label="Prelim Caution") 

554 self.fylw = colour_light_aspect(self.frame, label="Flash Caution") 

555 self.fdylw = colour_light_aspect(self.frame, label="Flash Prelim") 

556 # Create a subframe to hold the subsidary signal entry box (always packed) 

557 self.subframe = Tk.Frame(self.frame) 

558 self.subframe.pack() 

559 self.CB = common.check_box(self.subframe, label="Subsidary signal", 

560 tool_tip="Select to add a seperate calling on aspect",callback=self.sub_updated) 

561 self.CB.pack(side=Tk.LEFT, padx=2, pady=2) 

562 self.EB = signal_dcc_entry_box(self.subframe) 

563 self.EB.pack(side=Tk.LEFT, padx=2, pady=2) 

564 

565 def sub_updated(self): 

566 self.update_eb_state() 

567 if self.callback is not None: self.callback() 

568 

569 def update_eb_state(self): 

570 if self.CB.get_value(): self.EB.enable() 

571 else: self.EB.disable() 

572 

573 def validate(self): 

574 return ( self.grn.validate_addresses() and 

575 self.red.validate_addresses() and 

576 self.ylw.validate_addresses() and 

577 self.dylw.validate_addresses() and 

578 self.fylw.validate_addresses() and 

579 self.fdylw.validate_addresses() and 

580 self.EB.validate() ) 

581 

582 def set_addresses(self, addresses:[[[int,bool],],], item_id:int): 

583 # The Colour Light Aspects command sequences are: [grn, red, ylw, dylw, fylw, fdylw] 

584 # Each DCC command sequence comprises [dcc1, dcc2, dcc3, dcc4, dcc5, dcc6] 

585 # Each DCC command comprises: [dcc_address, dcc_state] 

586 self.grn.set_addresses(addresses[0], item_id) 

587 self.red.set_addresses(addresses[1], item_id) 

588 self.ylw.set_addresses(addresses[2], item_id) 

589 self.dylw.set_addresses(addresses[3], item_id) 

590 self.fylw.set_addresses(addresses[4], item_id) 

591 self.fdylw.set_addresses(addresses[5], item_id) 

592 

593 def get_addresses(self): 

594 # The Colour Light Aspects command sequences are: [grn, red, ylw, dylw, fylw, fdylw] 

595 # Each DCC command sequence comprises [dcc1, dcc2, dcc3, dcc4, dcc5, dcc6] 

596 # Each DCC command comprises: [dcc_address, dcc_state] 

597 return( [self.grn.get_addresses(), 

598 self.red.get_addresses(), 

599 self.ylw.get_addresses(), 

600 self.dylw.get_addresses(), 

601 self.fylw.get_addresses(), 

602 self.fdylw.get_addresses() ] ) 

603 

604 def set_subsidary(self, subsidary:[bool,int], item_id:int): 

605 # Subsidary is defined as [has_subsidary, dcc_address] 

606 self.CB.set_value(subsidary[0]) 

607 self.EB.set_value(subsidary[1], item_id) 

608 self.update_eb_state() 

609 

610 def get_subsidary(self): 

611 # Subsidary is defined as [has_subsidary, dcc_address] 

612 return([self.CB.get_value(), self.EB.get_value()]) 

613 

614 def enable_subsidary(self): 

615 self.CB.enable() 

616 self.update_eb_state() 

617 

618 def disable_subsidary(self): 

619 self.CB.disable() 

620 self.update_eb_state() 

621 

622 def enable_aspects(self): 

623 self.grn.enable_addresses() 

624 self.red.enable_addresses() 

625 self.ylw.enable_addresses() 

626 self.dylw.enable_addresses() 

627 self.fylw.enable_addresses() 

628 self.fdylw.enable_addresses() 

629 

630 def disable_aspects(self): 

631 self.grn.disable_addresses() 

632 self.red.disable_addresses() 

633 self.ylw.disable_addresses() 

634 self.dylw.disable_addresses() 

635 self.fylw.disable_addresses() 

636 self.fdylw.disable_addresses() 

637 

638#------------------------------------------------------------------------------------ 

639# Class for a Theatre Route character entry Box - uses base common.entry_box class 

640# Public class instance methods inherited from the base Entry Box class are: 

641# "disable" - disables/blanks the entry box 

642# "enable" enables/loads the entry box (with the last value) 

643# "set_value" - set the initial value of the entry box (string)  

644# "get_value" - get the last "validated" value of the entry box (string)  

645# Public class instance methods overridden by this class are 

646# "validate" - Validates either blank or a single character 

647#------------------------------------------------------------------------------------ 

648 

649class theatre_route_entry_box(common.entry_box): 

650 def __init__(self, parent_frame, callback=None): 

651 # Call the parent class init function to create the EB 

652 super().__init__(parent_frame, width=2, callback=callback, 

653 tool_tip = "Specify the character to be displayed for this route") 

654 

655 def validate(self): 

656 # Ensure only one character has been entered 

657 if len(self.entry.get()) <= 1: 657 ↛ 660line 657 didn't jump to line 660, because the condition on line 657 was never false

658 valid = True 

659 else: 

660 self.TT.text = "More than one theatre character has been entered" 

661 valid = False 

662 self.set_validation_status(valid) 

663 return (valid) 

664 

665#------------------------------------------------------------------------------------ 

666# Class to create a Theatre route element with an entry box for the displayed character 

667# and the associated DCC command sequence. Inherits from the dcc_entry_boxes class (above) 

668# Inherited Class instance methods are: 

669# "enable_addresses" - disables/blanks all entry boxes (and state buttons) 

670# "disable_addresses" enables/loads all entry box (and state buttona) 

671# Additional Class instance functions are: 

672# "validate" - validate all current entry boxes (theatre character and dcc addresses) 

673# "enable_selection" - disables/blanks the theatre entry box & DCC command list 

674# "disable_selection" enables/loads the theatre entry box & DCC command list 

675# "set_theatre" - set the values (character & dcc commands) for the theatre 

676# Also sets the current item ID (int) for validation purposes 

677# "get_theatre" - return the values (character & dcc commands) for the theatre 

678#------------------------------------------------------------------------------------ 

679 

680class theatre_route_element(dcc_entry_boxes): 

681 def __init__(self, parent_frame, label:str, width:int, callback=None, 

682 enable_addresses_on_selection:bool=False): 

683 # Create a frame for this UI element (always packed in the parent frame) 

684 self.frame = Tk.Frame(parent_frame) 

685 self.frame.pack() 

686 # Callback to make when the route selections change (Theatre Char EB changes) 

687 self.callback = callback 

688 # If the enable_addresses_on_selection flag is set to TRUE then the DCC address EBs 

689 # will be enabled/disabled when the Theatre character is changed. If false then the current 

690 # state of the EBs (enabled or disabled) remains unchanged. This is to support the MAIN 

691 # route which will always need a DCC address sequence even if there is no Theartre character 

692 self.enable_addresses_on_selection = enable_addresses_on_selection 

693 # Create the label and entry box for the theatre character 

694 self.label = Tk.Label(self.frame, width=width, text=label, anchor='w') 

695 self.label.pack(side=Tk.LEFT) 

696 self.EB = theatre_route_entry_box(self.frame, callback=self.selection_updated) 

697 self.EB.pack(side=Tk.LEFT) 

698 # Call the init function of the class we are inheriting from 

699 # The DCC entry boxes get packed into the frame by the parent class 

700 super().__init__(self.frame) 

701 

702 def selection_updated(self): 

703 self.update_addresses() 

704 if self.callback is not None: self.callback() 

705 

706 def update_addresses(self): 

707 # Enable/disable the DCC entry boxes if the route is enabled 

708 if self.enable_addresses_on_selection: 

709 if self.EB.entry.get() != "": self.enable_addresses() 

710 else: self.disable_addresses() 

711 

712 def validate(self): 

713 # Validate the Theatre character EB and all DCC Address EBs 

714 return (self.EB.validate() and self.validate_addresses()) 

715 

716 def set_theatre(self,theatre:[str,[[int,bool],]], item_id:int): 

717 # Each route element comprises: [character, DCC_command_sequence] 

718 # Each DCC command sequence comprises: [dcc1, dcc2, dcc3, dcc4, dcc5, dcc6] 

719 # Each DCC command element comprises: [dcc_address, dcc_state] 

720 self.EB.set_value(theatre[0]) 

721 self.set_addresses(theatre[1], item_id) 

722 self.update_addresses() 

723 

724 def get_theatre(self): 

725 # Each route element comprises: [character, DCC_command_sequence] 

726 # Each DCC command sequence comprises: [dcc1, dcc2, dcc3, dcc4, dcc5, dcc6] 

727 # Each DCC command element comprises: [dcc_address, dcc_state] 

728 return([self.EB.get_value(), self.get_addresses()]) 

729 

730 def enable_selection(self): 

731 self.EB.enable() 

732 self.update_addresses() 

733 

734 def disable_selection(self): 

735 self.EB.disable() 

736 self.disable_addresses() 

737 

738#------------------------------------------------------------------------------------ 

739# Class to create the DCC entry UI element for a Theatre Route Indicator 

740# Class instance functions to use externally are: 

741# "validate" - validate the entry box values (theatre character and dcc addresses) 

742# "set_theatre" - set the characters/addresses for the theatre [main,lh1,lh2,rh1,rh2] 

743# Also sets the current item ID (int) for validation purposes 

744# "get_theatre" - get the characters/addresses for the theatre [main,lh1,lh2,rh1,rh2] 

745# "set_auto_inhibit" - set the "auto inhibit on DANGER" flag for the DCC route indications 

746# "get_auto_inhibit" - get the "auto inhibit on DANGER" flag for the DCC route indications 

747# "enable_selection" - enables all entries 

748# "disable_selection" - disables all entries 

749# The Callback will be made on route selection change (theatre character EB change) 

750#------------------------------------------------------------------------------------ 

751 

752class theatre_route_indications: 

753 def __init__(self, parent_frame, callback=None): 

754 # Create a label frame for the route selections. We don't pack this element 

755 # as the frame gets packed/unpacked depending on UI selections 

756 self.frame = Tk.LabelFrame(parent_frame, text="Theatre route indications "+ 

757 "and associated DCC command sequences") 

758 # Create the individual route selection elements. 

759 # The MAIN route DCC address EBs remain enabled even if there is no theatre route 

760 # The MAIN element is therefore created with enable_addresses_on_selection=False 

761 self.dark = theatre_route_element(self.frame, label="(Dark)", width=5, 

762 callback=callback, enable_addresses_on_selection=True) 

763 self.main = theatre_route_element(self.frame, label="MAIN", width=5, 

764 callback=callback, enable_addresses_on_selection=False) 

765 self.lh1 = theatre_route_element(self.frame, label="LH1", width=5, 

766 callback=callback, enable_addresses_on_selection=True) 

767 self.lh2 = theatre_route_element(self.frame, label="LH2", width=5, 

768 callback=callback, enable_addresses_on_selection=True) 

769 self.rh1 = theatre_route_element(self.frame, label="RH1", width=5, 

770 callback=callback, enable_addresses_on_selection=True) 

771 self.rh2 = theatre_route_element(self.frame, label="RH2", width=5, 

772 callback=callback, enable_addresses_on_selection=True) 

773 # The EB for DARK (signal at red - no route indications displyed) is always 

774 # disabled so it can never be selected (not really a route indication as such) 

775 self.dark.disable_selection() 

776 # Create the checkbox and tool tip for auto route inhibit selection 

777 self.CB = common.check_box(self.frame, label="Auto inhibit route indications on DANGER", 

778 callback=self.auto_inhibit_update, tool_tip = "Select if the DCC signal automatically " + 

779 "inhibits route indications if the signal is at DANGER - If not then the DCC " + 

780 "commands to inhibit all route indications (dark) must be specified") 

781 self.CB.pack(padx=2, pady=2) 

782 

783 def auto_inhibit_update(self): 

784 if self.CB.get_value(): self.dark.disable_addresses() 784 ↛ exitline 784 didn't return from function 'auto_inhibit_update'

785 else: self.dark.enable_addresses() 

786 

787 def validate(self): 

788 # Validate all the Theatre EBs and DCC Address entry boxes for all routes and DARK 

789 return ( self.dark.validate() and 

790 self.main.validate() and 

791 self.lh1.validate() and 

792 self.lh2.validate() and 

793 self.rh1.validate() and 

794 self.rh2.validate() ) 

795 

796 def set_theatre(self, theatre:[[str,[[int,bool],],],], item_id:int): 

797 # The Theatre route list comprises: [dark, main, lh1, lh2, rh1, rh2] 

798 # Each route element comprises: [character, DCC_command_sequence] 

799 # Each DCC command sequence comprises [dcc1, dcc2, dcc3, dcc4, dcc5, dcc6] 

800 # Each DCC command comprises: [dcc_address, dcc_state] 

801 self.dark.set_theatre(theatre[0], item_id) 

802 self.main.set_theatre(theatre[1], item_id) 

803 self.lh1.set_theatre(theatre[2], item_id) 

804 self.lh2.set_theatre(theatre[3], item_id) 

805 self.rh1.set_theatre(theatre[4], item_id) 

806 self.rh2.set_theatre(theatre[5], item_id) 

807 self.auto_inhibit_update() 

808 

809 def get_theatre(self): 

810 # The Theatre route list comprises: [dark, main, lh1, lh2, rh1, rh2] 

811 # Each route element comprises: [character, DCC_command_sequence] 

812 # Each DCC command sequence comprises [dcc1, dcc2, dcc3, dcc4, dcc5, dcc6] 

813 # Each DCC command comprises: [dcc_address, dcc_state] 

814 # Note that the DARK aspect character is always present in the configuration 

815 dark_theatre_configuration_enabled = self.dark.get_theatre() 

816 dark_theatre_configuration_enabled[0]="#" 

817 return( [dark_theatre_configuration_enabled, 

818 self.main.get_theatre(), 

819 self.lh1.get_theatre(), 

820 self.lh2.get_theatre(), 

821 self.rh1.get_theatre(), 

822 self.rh2.get_theatre() ] ) 

823 

824 def enable_selection(self): 

825 # Enable the Theatre EBs for diverging routes (will also enable the address EBs)  

826 self.lh1.enable_selection() 

827 self.lh2.enable_selection() 

828 self.rh1.enable_selection() 

829 self.rh2.enable_selection() 

830 # The DCC Address EBs for MAIN are enabled even if no theatre character is selected 

831 self.main.enable_addresses() 

832 self.main.enable_selection() 

833 # Enable the "auto inhibit route" CB 

834 self.CB.enable() 

835 # Enabling of the "dark" DCC address EBs will depend on the state of the 

836 # auto inhibit checkbox (the "dark" Theatre EB remains disabled and blank) 

837 self.auto_inhibit_update() 

838 

839 def disable_selection(self): 

840 # Only disable the "dark" DCC address EBs (the CB is always disabled) 

841 self.dark.disable_addresses() 

842 # Disable the CBs for all routes (will also disable the address EBs) 

843 self.main.disable_selection() 

844 self.lh1.disable_selection() 

845 self.lh2.disable_selection() 

846 self.rh1.disable_selection() 

847 self.rh2.disable_selection() 

848 # Disable the "auto inhibit route" CB 

849 self.CB.disable() 

850 

851 def set_auto_inhibit(self, auto_inhibit:bool): 

852 self.CB.set_value(auto_inhibit) 

853 self.auto_inhibit_update() 

854 

855 def get_auto_inhibit(self): 

856 return(self.CB.get_value()) 

857 

858#------------------------------------------------------------------------------------ 

859# Class to create Feather route indication with a check box to enable the route indication 

860# and the associated DCC command sequence. Inherits from the dcc_entry_boxes class (above) 

861# Classes inherited from the parent class are: 

862# "set_addresses" - will set the values of the entry boxes (pass in a list) 

863# Also sets the current item ID (int) for validation purposes 

864# "get_addresses" - will return a list of the last "valid" entries 

865# "enable_addresses" - disables/blanks all entry boxes (and state buttons) 

866# "disable_addresses" enables/loads all entry box (and state buttona) 

867# Additional Class instance functions are: 

868# "validate" - validate all current entry box values and return True/false 

869# "enable_selection" - disables/blanks the route selection check box 

870# "disable_selection" enables/loads the route selection check box 

871# "set_feather" - set the state of the "Feather" checkbox 

872# "get_feather" - return the state of the "Feather" checkbox 

873#------------------------------------------------------------------------------------ 

874 

875class feather_route_element(dcc_entry_boxes): 

876 def __init__(self, parent_frame, label:str, width:int, callback=None, 

877 enable_addresses_on_selection=False): 

878 # Create a frame for this UI element (always packed in the parent frame) 

879 self.frame = Tk.Frame(parent_frame) 

880 self.frame.pack() 

881 # Callback to make when the route selections change (enabled/disabled) 

882 self.callback = callback 

883 # If the enable_addresses_on_selection flag is set to TRUE then the DCC address EBs 

884 # will be enabled/disabled when the route checkbox is changed. If false then the current 

885 # state of the EBs (enabled or disabled) remains unchanged. This is to support the MAIN 

886 # route which will always need a DCC address sequence even if there is no feather 

887 self.enable_addresses_on_selection = enable_addresses_on_selection 

888 # Create the label and checkbox for the feather route selection 

889 self.label = Tk.Label(self.frame, width=width, text=label, anchor='w') 

890 self.label.pack(side=Tk.LEFT) 

891 self.CB = common.check_box(self.frame, callback=self.selection_updated, label="", 

892 tool_tip="Select to add a feather indication for this route") 

893 self.CB.pack(side=Tk.LEFT) 

894 # Call the init function of the class we are inheriting from 

895 # The DCC entry boxes get packed into the frame by the parent class 

896 super().__init__(self.frame) 

897 

898 def selection_updated(self): 

899 self.update_addresses() 

900 if self.callback is not None: self.callback() 

901 

902 def update_addresses(self): 

903 # Enable/disable the DCC entry boxes if enabled for this DCC entry element 

904 if self.enable_addresses_on_selection: 

905 if self.CB.get_value(): self.enable_addresses() 

906 else: self.disable_addresses() 

907 

908 def validate(self): 

909 return (self.validate_addresses()) 

910 

911 def set_feather(self, state:bool): 

912 self.CB.set_value(state) 

913 self.update_addresses() 

914 

915 def get_feather(self): 

916 return(self.CB.get_value()) 

917 

918 def enable_selection(self): 

919 self.CB.enable() 

920 self.update_addresses() 

921 

922 def disable_selection(self): 

923 self.CB.disable() 

924 self.disable_addresses() 

925 

926#------------------------------------------------------------------------------------ 

927# Class to create the DCC entry UI element for Feather Route Indications 

928# Class instance functions to use externally are: 

929# "validate" - validate the current entry box values and return True/false 

930# "set_addresses" - set the values of the DCC addresses/states (pass in a list) 

931# Also sets the current item ID (int) for validation purposes 

932# "get_addresses" - return a list of the "validated" DCC addresses/states 

933# "set_feathers" - set the state of the feathers [main,lh1,lh2,rh1,rh2] 

934# "get_feathers" - get the state of the feathers [main,lh1,lh2,rh1,rh2] 

935# "set_auto_inhibit" - set the "auto inhibit on DANGER" selection 

936# "get_auto_inhibit" - get the "auto inhibit on DANGER" selection 

937# "enable_feathers" - enables all entries 

938# "disable_feathers" - disables all entries 

939# "disable_addresses" enables/loads all entry box (and state buttona) 

940# The Callback will be made on route selection change (enabled/disabled) 

941#------------------------------------------------------------------------------------ 

942 

943class feather_route_indications: 

944 def __init__(self, parent_frame, callback): 

945 # Create a label frame for the route selections. We don't pack this element 

946 # as the frame gets packed/unpacked depending on UI selections 

947 self.frame = Tk.LabelFrame(parent_frame, text="Feather Route Indications "+ 

948 "and associated DCC command sequences") 

949 # Create the individual route selection elements. 

950 # The MAIN route DCC address EBs remain enabled even if there is no route feather 

951 # The MAIN element is therefore created with enable_addresses_on_selection=False 

952 self.dark = feather_route_element(self.frame, label="(Dark)", width=5, 

953 callback=callback, enable_addresses_on_selection=True) 

954 self.main = feather_route_element(self.frame, label="MAIN", width=5, 

955 callback=callback, enable_addresses_on_selection=False) 

956 self.lh1 = feather_route_element(self.frame, label="LH1", width=5, 

957 callback=callback, enable_addresses_on_selection=True) 

958 self.lh2 = feather_route_element(self.frame, label="LH2", width=5, 

959 callback=callback, enable_addresses_on_selection=True) 

960 self.rh1 = feather_route_element(self.frame, label="RH1", width=5, 

961 callback=callback, enable_addresses_on_selection=True) 

962 self.rh2 = feather_route_element(self.frame, label="RH2", width=5, 

963 callback=callback, enable_addresses_on_selection=True) 

964 # The CB for DARK (signal at red - no route indications displyed) is always 

965 # disabled so it can never be selected (not really a route indication as such) 

966 self.dark.disable_selection() 

967 # Create the checkbox and tool tip for auto route inhibit 

968 self.CB = common.check_box(self.frame, label="Auto inhibit route indications on DANGER", 

969 callback=self.auto_inhibit_update, tool_tip = "Select if the DCC signal automatically " + 

970 "inhibits route indications if the signal is at DANGER - If not then the DCC " + 

971 "commands to inhibit all route indications (dark) must be specified") 

972 self.CB.pack(padx=2, pady=2) 

973 

974 def auto_inhibit_update(self): 

975 if self.CB.get_value(): self.dark.disable_addresses() 975 ↛ exitline 975 didn't return from function 'auto_inhibit_update'

976 else: self.dark.enable_addresses() 

977 

978 def validate(self): 

979 # Validate all the DCC Address entry boxes for all routes and DARK 

980 return ( self.dark.validate() and 

981 self.main.validate() and 

982 self.lh1.validate() and 

983 self.lh2.validate() and 

984 self.rh1.validate() and 

985 self.rh2.validate() ) 

986 

987 def set_addresses(self, addresses:[[[int,bool],],], item_id:int): 

988 # The Feather Route address list comprises: [dark, main, lh1, lh2, rh1, rh2] 

989 # Each route element comprises: [DCC_command_sequence] 

990 # Each DCC command sequence comprises [dcc1, dcc2, dcc3, dcc4, dcc5, dcc6] 

991 # Each DCC command comprises: [dcc_address, dcc_state] 

992 self.dark.set_addresses(addresses[0], item_id) 

993 self.main.set_addresses(addresses[1], item_id) 

994 self.lh1.set_addresses(addresses[2], item_id) 

995 self.lh2.set_addresses(addresses[3], item_id) 

996 self.rh1.set_addresses(addresses[4], item_id) 

997 self.rh2.set_addresses(addresses[5], item_id) 

998 

999 def get_addresses(self): 

1000 # The Feather Route address list comprises: [dark, main, lh1, lh2, rh1, rh2] 

1001 # Each route element comprises: [DCC_command_sequence] 

1002 # Each DCC command sequence comprises [dcc1, dcc2, dcc3, dcc4, dcc5, dcc6] 

1003 # Each DCC command comprises: [dcc_address, dcc_state] 

1004 return( [self.dark.get_addresses(), 

1005 self.main.get_addresses(), 

1006 self.lh1.get_addresses(), 

1007 self.lh2.get_addresses(), 

1008 self.rh1.get_addresses(), 

1009 self.rh2.get_addresses() ] ) 

1010 

1011 def set_feathers(self,feathers:[bool,bool,bool,bool,bool]): 

1012 # Feather Route list comprises: [main, lh1, lh2, rh1, rh2] 

1013 # Each element comprises a single boolean value 

1014 self.main.set_feather(feathers[0]) 

1015 self.lh1.set_feather(feathers[1]) 

1016 self.lh2.set_feather(feathers[2]) 

1017 self.rh1.set_feather(feathers[3]) 

1018 self.rh2.set_feather(feathers[4]) 

1019 self.auto_inhibit_update() 

1020 

1021 def get_feathers(self): 

1022 # Feather Route list comprises: [main, lh1, lh2, rh1, rh2] 

1023 # Each element comprises a single boolean value 

1024 return( [ self.main.get_feather(), 

1025 self.lh1.get_feather(), 

1026 self.lh2.get_feather(), 

1027 self.rh1.get_feather(), 

1028 self.rh2.get_feather() ] ) 

1029 

1030 def enable_selection(self): 

1031 # Enable the CBs for diverging routes (will also enable the address EBs)  

1032 self.lh1.enable_selection() 

1033 self.lh2.enable_selection() 

1034 self.rh1.enable_selection() 

1035 self.rh2.enable_selection() 

1036 # The DCC Address EBs for MAIN are enabled even if no feather is selected 

1037 self.main.enable_selection() 

1038 self.main.enable_addresses() 

1039 # Enable the "auto inhibit route" CB 

1040 self.CB.enable() 

1041 # Enabling of the "dark" DCC address EBs will depend on the state of the 

1042 # auto inhibit checkbox (the "dark" CB remains disabled and unselected) 

1043 self.auto_inhibit_update() 

1044 

1045 def disable_selection(self): 

1046 # Only disable the "dark" DCC address EBs (the CB is always disabled) 

1047 self.dark.disable_addresses() 

1048 # Disable the CBs for all diverging routes (will also disable the address EBs) 

1049 self.main.disable_selection() 

1050 self.lh1.disable_selection() 

1051 self.lh2.disable_selection() 

1052 self.rh1.disable_selection() 

1053 self.rh2.disable_selection() 

1054 # Disable the "auto inhibit route" CB 

1055 self.CB.disable() 

1056 

1057 def set_auto_inhibit(self, auto_inhibit:bool): 

1058 self.CB.set_value(auto_inhibit) 

1059 self.auto_inhibit_update() 

1060 

1061 def get_auto_inhibit(self): 

1062 return(self.CB.get_value()) 

1063 

1064#------------------------------------------------------------------------------------ 

1065# Class for the 'basic' route selections UI Element for the main signal (if no specific 

1066# route indications are selected) and the subsidary signal (if one exists). If the class 

1067# is created for a main signal (or ground signal) then the main route is always selected. 

1068# Class instance functions to use externally are: 

1069# "enable" - disables/blanks the route selection check boxes 

1070# "disable" enables/loads the route selection check boxes 

1071# "set_values" - sets the Route Selection Checkboxes 

1072# "get_values" - return the states of the Route Selection Checkboxes 

1073# The Callback will be made on route selection change (enabled/disabled) 

1074#------------------------------------------------------------------------------------ 

1075 

1076class route_selections(): 

1077 def __init__(self, parent_frame, label:str, tool_tip:str, callback=None, main_signal=False): 

1078 self.main_signal = main_signal 

1079 # Create a label frame for the selections (packed by the calling function/class 

1080 self.frame = Tk.LabelFrame(parent_frame, text=label) 

1081 # We use a subframe to center the selections boxes 

1082 self.subframe = Tk.Frame(self.frame) 

1083 self.subframe.pack(padx=2, pady=2) 

1084 # Create the required selection elements (always packed in the subframe) 

1085 self.main = common.check_box(self.subframe, label="MAIN", tool_tip=tool_tip, callback=callback) 

1086 self.main.pack(side=Tk.LEFT) 

1087 self.lh1 = common.check_box(self.subframe, label="LH1", tool_tip=tool_tip, callback=callback) 

1088 self.lh1.pack(side=Tk.LEFT) 

1089 self.lh2 = common.check_box(self.subframe, label="LH2", tool_tip=tool_tip, callback=callback) 

1090 self.lh2.pack(side=Tk.LEFT) 

1091 self.rh1 = common.check_box(self.subframe, label="RH1", tool_tip=tool_tip, callback=callback) 

1092 self.rh1.pack(side=Tk.LEFT) 

1093 self.rh2 = common.check_box(self.subframe, label="RH2", tool_tip=tool_tip, callback=callback) 

1094 self.rh2.pack(side=Tk.LEFT) 

1095 if self.main_signal: self.main.config(state="disabled") 

1096 

1097 def enable_selection(self): 

1098 if not self.main_signal: self.main.enable() 

1099 self.lh1.enable() 

1100 self.lh2.enable() 

1101 self.rh1.enable() 

1102 self.rh2.enable() 

1103 

1104 def disable_selection(self): 

1105 if not self.main_signal: self.main.disable() 

1106 self.lh1.disable() 

1107 self.lh2.disable() 

1108 self.rh1.disable() 

1109 self.rh2.disable() 

1110 

1111 def set_values(self, routes:[bool,bool,bool,bool,bool]): 

1112 # Route list comprises: [main, lh1, lh2, rh1, rh2] 

1113 # Each element comprises a single boolean value 

1114 self.main.set_value(routes[0]) 

1115 self.lh1.set_value(routes[1]) 

1116 self.lh2.set_value(routes[2]) 

1117 self.rh1.set_value(routes[3]) 

1118 self.rh2.set_value(routes[4]) 

1119 

1120 def get_values(self): 

1121 # Route list comprises: [main, lh1, lh2, rh1, rh2] 

1122 # Each element comprises a single boolean value 

1123 return ([ self.main.get_value(), 

1124 self.lh1.get_value(), 

1125 self.lh2.get_value(), 

1126 self.rh1.get_value(), 

1127 self.rh2.get_value() ] ) 

1128 

1129#------------------------------------------------------------------------------------ 

1130# Class for the Edit Signal Window Configuration Tab 

1131# sig_type_updated, sub_type_updated, route_type_updated, route_selections_updated, 

1132# sig_routes_updated, sub_routes_updated, dist_routes_updated are callback functions 

1133#------------------------------------------------------------------------------------ 

1134 

1135class signal_configuration_tab: 

1136 def __init__(self, parent_tab, sig_type_updated, sub_type_updated, 

1137 route_type_updated, route_selections_updated, sig_routes_updated, 

1138 sub_routes_updated, dist_routes_updated): 

1139 # Create a Frame to hold the Signal ID and Signal Type Selections 

1140 self.frame1 = Tk.Frame(parent_tab) 

1141 self.frame1.pack(padx=2, pady=2, fill='x') 

1142 # Create the UI Element for Item ID selection. Note that although the signals_common.sig_exists 

1143 # function will match both local and remote Signal IDs, the object_id_selection only allows integers to 

1144 # be selected - so we can safely use this function here for consistency. 

1145 self.sigid = common.object_id_selection(self.frame1,"Signal ID", 

1146 exists_function = signals_common.sig_exists) 

1147 self.sigid.frame.pack(side=Tk.LEFT, padx=2, pady=2, fill='both') 

1148 self.sigtype = common.selection_buttons(self.frame1,"Signal Type", 

1149 "Select signal type",sig_type_updated,"Colour Light", 

1150 "Ground Pos","Semaphore","Ground Disc") 

1151 self.sigtype.frame.pack(side=Tk.LEFT, padx=2, pady=2, fill='x', expand=True) 

1152 # Create the UI Element for Signal subtype selection (always packed) 

1153 self.subtype = common.selection_buttons(parent_tab,"Signal Subtype", 

1154 "Select signal subtype",sub_type_updated,"-","-","-","-","-") 

1155 self.subtype.frame.pack(padx=2, pady=2, fill='x') 

1156 # Create a Frame to hold the Gen settings and Route type Selections (always packed) 

1157 self.frame2 = Tk.Frame(parent_tab) 

1158 self.frame2.pack(padx=2, pady=2, fill='x') 

1159 self.settings = general_settings(self.frame2) 

1160 self.settings.frame.pack(side=Tk.LEFT, padx=2, pady=2, fill='both') 

1161 self.routetype = common.selection_buttons(self.frame2, "Route Indications", 

1162 "Select the route indications for the main signal", route_type_updated, 

1163 "None", "Route feathers", "Theatre indicator", "Route arms") 

1164 self.routetype.frame.pack(side=Tk.LEFT, padx=2, pady=2, fill='x', expand=True) 

1165 # Create the Checkboxes and DCC Entry Box frames for the type-specific selections 

1166 # These frames are packed / hidden depending on the signal type and route  

1167 # indication type selections by the callback functions in "configure_signal.py" 

1168 self.aspects = colour_light_aspects(parent_tab, sub_routes_updated) 

1169 self.theatre = theatre_route_indications(parent_tab, route_selections_updated) 

1170 self.feathers = feather_route_indications(parent_tab, route_selections_updated) 

1171 self.semaphores = semaphore_signal_arms(parent_tab, sig_routes_updated, 

1172 sub_routes_updated, dist_routes_updated) 

1173 self.sig_routes = route_selections(parent_tab, 

1174 "Routes to be controlled by the Main Signal", 

1175 "Select one or more routes to be controlled by the main signal", 

1176 callback=route_selections_updated, main_signal=True) 

1177 self.sub_routes = route_selections(parent_tab, 

1178 "Routes to be controlled by the Subsidary Signal", 

1179 "Select one or more routes to be controlled by the subsidary signal", 

1180 callback=route_selections_updated, main_signal=False) 

1181 

1182#############################################################################################