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

120 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2024-04-05 17:29 +0100

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

2# This module contains all the ui functions for configuring Textbox objects 

3#------------------------------------------------------------------------------------ 

4# 

5# External API functions intended for use by other editor modules: 

6# edit_textbox - Open the edit textbox 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 on save 

10# 

11# Accesses the following external editor objects directly: 

12# objects.schematic_objects - To load/save the object configuration 

13# 

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

15# common.integer_entry_box 

16# common.scrollable_text_frame 

17# common.window_controls 

18# common.colour_selection 

19# common.selection_buttons 

20# common.check_box 

21# 

22#------------------------------------------------------------------------------------ 

23 

24import copy 

25 

26import tkinter as Tk 

27 

28from . import common 

29from . import objects 

30 

31#------------------------------------------------------------------------------------ 

32# We maintain a global dictionary of open edit windows (where the key is the UUID 

33# of the object being edited) to prevent duplicate windows being opened. If the user 

34# tries to edit an object which is already being edited, then we just bring the 

35# existing edit window to the front (expanding if necessary) and set focus on it 

36#------------------------------------------------------------------------------------ 

37 

38open_windows={} 

39 

40##################################################################################### 

41# Classes for the Edit Text Box UI Elements 

42##################################################################################### 

43 

44class text_style_entry(): 

45 def __init__(self, parent_frame, callback): 

46 # Create a Frame for the Text Style Entry components 

47 self.frame = Tk.LabelFrame(parent_frame,text="Text Style") 

48 # Create a subframe to hold the font size and border configuration elements 

49 self.subframe1 = Tk.Frame(self.frame) 

50 self.subframe1.pack() 

51 self.label1 = Tk.Label(self.subframe1, text="Font size") 

52 self.label1.pack(padx=2, pady=2, fill='x', side=Tk.LEFT) 

53 self.fontsize = common.integer_entry_box(self.subframe1, width=3, min_value=4, max_value=24, 

54 tool_tip="Select font size (between 4 and 24)", callback=callback, allow_empty=False) 

55 self.fontsize.pack(padx=2, pady=2, fill='x', side=Tk.LEFT) 

56 self.label2 = Tk.Label(self.subframe1, text=" Border width") 

57 self.label2.pack(padx=2, pady=2, fill='x', side=Tk.LEFT) 

58 self.border = common.integer_entry_box(self.subframe1, width=3, min_value=0, max_value=5, allow_empty=False, 

59 tool_tip="Select border width between 0 and 5 (0 to disable border)", callback=callback) 

60 self.border.pack(padx=2, pady=2, fill='x', side=Tk.LEFT) 

61 # Create a subframe to hold the bold, italic and underline configuration elements 

62 self.subframe2 = Tk.Frame(self.frame) 

63 self.subframe2.pack() 

64 self.bold = common.check_box(self.subframe2,label="Bold",tool_tip="Bold text",callback=callback) 

65 self.bold.pack(padx=2, pady=2, fill='x', side=Tk.LEFT) 

66 self.italic = common.check_box(self.subframe2,label="Italic",tool_tip="Italic text",callback=callback) 

67 self.italic.pack(padx=2, pady=2, fill='x', side=Tk.LEFT) 

68 self.underline = common.check_box(self.subframe2,label="Underline",tool_tip="Underline text",callback=callback) 

69 self.underline.pack(padx=2, pady=2, fill='x', side=Tk.LEFT) 

70 

71 def set_values(self, font:str, font_size:int, font_style:str, border:int): 

72 self.font = font 

73 self.fontsize.set_value(font_size) 

74 self.bold.set_value("bold" in font_style) 

75 self.italic.set_value("italic" in font_style) 

76 self.underline.set_value("underline" in font_style) 

77 self.border.set_value(border) 

78 

79 def get_values(self): 

80 font_style = "" 

81 if self.bold.get_value(): font_style=font_style + "bold " 

82 if self.italic.get_value(): font_style=font_style + "italic " 

83 if self.underline.get_value(): font_style=font_style + "underline " 

84 return (self.font, self.fontsize.get_value(),font_style, self.border.get_value()) 

85 

86 def validate(self): 

87 return (self.fontsize.validate() and self.border.validate() ) 

88 

89##################################################################################### 

90# Top level Class for the Edit Textbox window 

91# This window doesn't have any tabs (unlike other object configuration windows) 

92##################################################################################### 

93 

94class edit_textbox(): 

95 def __init__(self, root, object_id): 

96 global open_windows 

97 # If there is already a window open then we just make it jump to the top and exit 

98 if object_id in open_windows.keys(): 98 ↛ 99line 98 didn't jump to line 99, because the condition on line 98 was never true

99 open_windows[object_id].lift() 

100 open_windows[object_id].state('normal') 

101 open_windows[object_id].focus_force() 

102 else: 

103 # This is the UUID for the object being edited 

104 self.object_id = object_id 

105 # Creatre the basic Top Level window 

106 self.window = Tk.Toplevel(root) 

107 self.window.protocol("WM_DELETE_WINDOW", self.close_window) 

108 self.window.resizable(False, False) 

109 open_windows[object_id] = self.window 

110 # Create the common Apply/OK/Reset/Cancel buttons for the window (packed first to remain visible) 

111 self.controls = common.window_controls(self.window, self.load_state, self.save_state, self.close_window) 

112 self.controls.frame.pack(side=Tk.BOTTOM, padx=2, pady=2) 

113 # Create the Validation error message (this gets packed/unpacked on apply/save) 

114 self.validation_error = Tk.Label(self.window, text="Errors on Form need correcting", fg="red") 

115 # Create a frame to hold all UI elements (so they don't expand on window resize 

116 # to provide consistent behavior with the other configure object popup windows) 

117 self.main_frame = Tk.Frame(self.window) 

118 self.main_frame.pack(fill='both', expand=True) 

119 self.frame1 = Tk.Frame(self.main_frame) 

120 self.frame1.pack(padx=2, pady=2, fill='both', expand=True) 

121 self.text = common.scrollable_text_frame(self.frame1, max_height=15,max_width=50, 

122 min_height=1, min_width=10, editable=True, auto_resize=True) 

123 self.text.pack(padx=2, pady=2, fill='both', expand=True) 

124 # Create a Frame for the colour selections 

125 self.frame2 = Tk.Frame(self.main_frame) 

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

127 # Create the text colour and text background colour selection elements 

128 self.colour = common.colour_selection(self.frame2, label="Text colour") 

129 self.colour.frame.pack(padx=2, pady=2, fill='x', side=Tk.LEFT, expand=1) 

130 self.background = common.colour_selection(self.frame2, label="Background colour") 

131 self.background.frame.pack(padx=2, pady=2, fill='x', side=Tk.LEFT, expand=1) 

132 # Create a Frame for the Text Justification 

133 self.frame3 = Tk.Frame(self.main_frame) 

134 self.frame3.pack(padx=2, pady=2, fill='x') 

135 # Use radio buttons for the text justification selection 

136 self.justify = common.selection_buttons(self.frame3, label="Text Justification", 

137 tool_tip= "select text justification", b1="Left", b2="Centre", b3="Right", 

138 callback=self.justification_updated) 

139 self.justify.frame.pack(padx=2, pady=2, fill='x') 

140 # Create a Frame for the Text Style Entry widgey 

141 self.textstyle = text_style_entry (self.main_frame, callback = self.text_style_updated) 

142 self.textstyle.frame.pack(padx=2, pady=2, fill='x') 

143 # load the initial UI state 

144 self.load_state() 

145 

146 def justification_updated(self): 

147 self.text.set_justification(self.justify.get_value()) 

148 

149 def text_style_updated(self): 

150 font, font_size, font_style, border = self.textstyle.get_values() 

151 self.text.set_font (font, font_size, font_style) 

152 

153 def load_state(self): 

154 # Check the object we are editing still exists (hasn't been deleted from the schematic) 

155 # If it no longer exists then we just destroy the window and exit without saving 

156 if self.object_id not in objects.schematic_objects.keys(): 156 ↛ 157line 156 didn't jump to line 157, because the condition on line 156 was never true

157 self.close_window() 

158 else: 

159 # Label the edit window 

160 self.window.title("Textbox") 

161 # Set the Initial UI state from the current object settings 

162 self.text.set_value(objects.schematic_objects[self.object_id]["text"]) 

163 self.colour.set_value(objects.schematic_objects[self.object_id]["colour"]) 

164 self.background.set_value(objects.schematic_objects[self.object_id]["background"]) 

165 justify = objects.schematic_objects[self.object_id]["justify"] 

166 font = objects.schematic_objects[self.object_id]["font"] 

167 font_size = objects.schematic_objects[self.object_id]["fontsize"] 

168 font_style = objects.schematic_objects[self.object_id]["fontstyle"] 

169 border = objects.schematic_objects[self.object_id]["border"] 

170 self.textstyle.set_values (font, font_size, font_style, border) 

171 self.justify.set_value(justify) 

172 # Justify the text and resize the font to match the initial selection 

173 self.text.set_justification(justify) 

174 self.text.set_font(font, font_size, font_style) 

175 # Hide the validation error message 

176 self.validation_error.pack_forget() 

177 return() 

178 

179 def save_state(self, close_window:bool): 

180 # Check the object we are editing still exists (hasn't been deleted from the schematic) 

181 # If it no longer exists then we just destroy the window and exit without saving 

182 if self.object_id not in objects.schematic_objects.keys(): 182 ↛ 183line 182 didn't jump to line 183, because the condition on line 182 was never true

183 self.close_window() 

184 elif self.textstyle.validate(): 184 ↛ 204line 184 didn't jump to line 204, because the condition on line 184 was never false

185 # Copy the original object Configuration (elements get overwritten as required) 

186 new_object_configuration = copy.deepcopy(objects.schematic_objects[self.object_id]) 

187 # Update the object coniguration elements from the current user selections 

188 new_object_configuration["text"] = self.text.get_value() 

189 new_object_configuration["colour"] = self.colour.get_value() 

190 new_object_configuration["background"] = self.background.get_value() 

191 new_object_configuration["justify"] = self.justify.get_value() 

192 font, font_size, font_style, border = self.textstyle.get_values() 

193 new_object_configuration["font"] = font 

194 new_object_configuration["fontsize"] = font_size 

195 new_object_configuration["fontstyle"] = font_style 

196 new_object_configuration["border"] = border 

197 # Save the updated configuration (and re-draw the object) 

198 objects.update_object(self.object_id, new_object_configuration) 

199 # Close window on "OK" or re-load UI for "apply" 

200 if close_window: self.close_window() 

201 else: self.load_state() 

202 else: 

203 # Display the validation error message 

204 self.validation_error.pack(side=Tk.BOTTOM, before=self.controls.frame) 

205 return() 

206 

207 def close_window(self): 

208 # Prevent the dialog being closed if the colour chooser is still open as 

209 # for some reason this doesn't get destroyed when the parent is destroyed 

210 if not self.colour.is_open() and not self.background.is_open(): 210 ↛ exitline 210 didn't return from function 'close_window', because the condition on line 210 was never false

211 self.window.destroy() 

212 del open_windows[self.object_id] 

213 

214#############################################################################################