class PuzzleViewer(Frame): @classmethod def _format(cls, obj, indent=0): prefix = ' ' * indent if isinstance(obj, list): return textwrap.indent( '\n[' + ','.join(cls._format(e, indent + 1) for e in obj) + '\n]', prefix) elif isinstance(obj, dict): return textwrap.indent( '\n{' + (','.join( '{}:{}'.format(cls._format(k, indent + 1), cls._format(v, indent + 1)) for k, v in obj.items())) + '\n}', prefix) return textwrap.indent('\n' + str(obj), prefix) def __init__(self, master): super().__init__(master) self._text = Text(self, font=("Mono", 12)) self.button = Button(self, text='repr', command=self.toggle) self._raw_text = '' self._text.pack(fill='both', expand=1) self.button.pack() def update_text(self, obj): self._raw_text = obj self._text.delete('0.0', 'end') if self.button.cget('text') == 'repr': self._text.insert('end', pprint.pformat(obj)) else: self._text.insert('end', self._format(obj)) def toggle(self): if self.button.cget('text') == 'repr': self.button.config(text='str') self.update_text(self._raw_text) else: self.button.config(text='repr') self.update_text(self._raw_text)
def createTempMenu(self): level = self.menu_tree.getSelectionLevel(self.temp_menu_tree.tree[0]) self.menu_tree.traverseDownToSelectionLevel( self.temp_menu_tree.tree[0]) rowNumber = 0 for child in level: settingValue = Label(self.menuFrame, text=child.value[0]) settingValue.grid(row=rowNumber, column=1) settingKey = Button( self.menuFrame, text=child.name, command=lambda: self.changeMenuValue(child, settingValue)) self.buttonColor = settingKey.cget('bg') settingKey.grid(row=rowNumber, column=0) self.nodeToButtonDict[child] = (settingKey, settingValue) if rowNumber == 0: self.currentSelectionButton = settingKey self.currentSelectionNode = child rowNumber += 1 self.makeSelectedButtonColored(self.currentSelectionButton)
class GetKeysDialog(Toplevel): # Dialog title for invalid key sequence keyerror_title = 'Key Sequence Error' def __init__(self, parent, title, action, current_key_sequences, *, _htest=False, _utest=False): """ parent - parent of this dialog title - string which is the title of the popup dialog action - string, the name of the virtual event these keys will be mapped to current_key_sequences - list, a list of all key sequence lists currently mapped to virtual events, for overlap checking _htest - bool, change box location when running htest _utest - bool, do not wait when running unittest """ Toplevel.__init__(self, parent) self.withdraw() # Hide while setting geometry. self.configure(borderwidth=5) self.resizable(height=False, width=False) self.title(title) self.transient(parent) self.grab_set() self.protocol("WM_DELETE_WINDOW", self.cancel) self.parent = parent self.action = action self.current_key_sequences = current_key_sequences self.result = '' self.key_string = StringVar(self) self.key_string.set('') # Set self.modifiers, self.modifier_label. self.set_modifiers_for_platform() self.modifier_vars = [] for modifier in self.modifiers: variable = StringVar(self) variable.set('') self.modifier_vars.append(variable) self.advanced = False self.create_widgets() self.update_idletasks() self.geometry("+%d+%d" % (parent.winfo_rootx() + (parent.winfo_width() / 2 - self.winfo_reqwidth() / 2), parent.winfo_rooty() + ((parent.winfo_height() / 2 - self.winfo_reqheight() / 2) if not _htest else 150)) ) # Center dialog over parent (or below htest box). if not _utest: self.deiconify() # Geometry set, unhide. self.wait_window() def showerror(self, *args, **kwargs): # Make testing easier. Replace in #30751. messagebox.showerror(*args, **kwargs) def create_widgets(self): self.frame = frame = Frame(self, borderwidth=2, relief='sunken') frame.pack(side='top', expand=True, fill='both') frame_buttons = Frame(self) frame_buttons.pack(side='bottom', fill='x') self.button_ok = Button(frame_buttons, text='OK', width=8, command=self.ok) self.button_ok.grid(row=0, column=0, padx=5, pady=5) self.button_cancel = Button(frame_buttons, text='Cancel', width=8, command=self.cancel) self.button_cancel.grid(row=0, column=1, padx=5, pady=5) # Basic entry key sequence. self.frame_keyseq_basic = Frame(frame, name='keyseq_basic') self.frame_keyseq_basic.grid(row=0, column=0, sticky='nsew', padx=5, pady=5) basic_title = Label(self.frame_keyseq_basic, text=f"New keys for '{self.action}' :") basic_title.pack(anchor='w') basic_keys = Label(self.frame_keyseq_basic, justify='left', textvariable=self.key_string, relief='groove', borderwidth=2) basic_keys.pack(ipadx=5, ipady=5, fill='x') # Basic entry controls. self.frame_controls_basic = Frame(frame) self.frame_controls_basic.grid(row=1, column=0, sticky='nsew', padx=5) # Basic entry modifiers. self.modifier_checkbuttons = {} column = 0 for modifier, variable in zip(self.modifiers, self.modifier_vars): label = self.modifier_label.get(modifier, modifier) check = Checkbutton(self.frame_controls_basic, command=self.build_key_string, text=label, variable=variable, onvalue=modifier, offvalue='') check.grid(row=0, column=column, padx=2, sticky='w') self.modifier_checkbuttons[modifier] = check column += 1 # Basic entry help text. help_basic = Label(self.frame_controls_basic, justify='left', text="Select the desired modifier keys\n" + "above, and the final key from the\n" + "list on the right.\n\n" + "Use upper case Symbols when using\n" + "the Shift modifier. (Letters will be\n" + "converted automatically.)") help_basic.grid(row=1, column=0, columnspan=4, padx=2, sticky='w') # Basic entry key list. self.list_keys_final = Listbox(self.frame_controls_basic, width=15, height=10, selectmode='single') self.list_keys_final.insert('end', *AVAILABLE_KEYS) self.list_keys_final.bind('<ButtonRelease-1>', self.final_key_selected) self.list_keys_final.grid(row=0, column=4, rowspan=4, sticky='ns') scroll_keys_final = Scrollbar(self.frame_controls_basic, orient='vertical', command=self.list_keys_final.yview) self.list_keys_final.config(yscrollcommand=scroll_keys_final.set) scroll_keys_final.grid(row=0, column=5, rowspan=4, sticky='ns') self.button_clear = Button(self.frame_controls_basic, text='Clear Keys', command=self.clear_key_seq) self.button_clear.grid(row=2, column=0, columnspan=4) # Advanced entry key sequence. self.frame_keyseq_advanced = Frame(frame, name='keyseq_advanced') self.frame_keyseq_advanced.grid(row=0, column=0, sticky='nsew', padx=5, pady=5) advanced_title = Label( self.frame_keyseq_advanced, justify='left', text=f"Enter new binding(s) for '{self.action}' :\n" + "(These bindings will not be checked for validity!)") advanced_title.pack(anchor='w') self.advanced_keys = Entry(self.frame_keyseq_advanced, textvariable=self.key_string) self.advanced_keys.pack(fill='x') # Advanced entry help text. self.frame_help_advanced = Frame(frame) self.frame_help_advanced.grid(row=1, column=0, sticky='nsew', padx=5) help_advanced = Label( self.frame_help_advanced, justify='left', text="Key bindings are specified using Tkinter keysyms as\n" + "in these samples: <Control-f>, <Shift-F2>, <F12>,\n" "<Control-space>, <Meta-less>, <Control-Alt-Shift-X>.\n" "Upper case is used when the Shift modifier is present!\n\n" + "'Emacs style' multi-keystroke bindings are specified as\n" + "follows: <Control-x><Control-y>, where the first key\n" + "is the 'do-nothing' keybinding.\n\n" + "Multiple separate bindings for one action should be\n" + "separated by a space, eg., <Alt-v> <Meta-v>.") help_advanced.grid(row=0, column=0, sticky='nsew') # Switch between basic and advanced. self.button_level = Button(frame, command=self.toggle_level, text='<< Basic Key Binding Entry') self.button_level.grid(row=2, column=0, stick='ew', padx=5, pady=5) self.toggle_level() def set_modifiers_for_platform(self): """Determine list of names of key modifiers for this platform. The names are used to build Tk bindings -- it doesn't matter if the keyboard has these keys; it matters if Tk understands them. The order is also important: key binding equality depends on it, so config-keys.def must use the same ordering. """ if sys.platform == "darwin": self.modifiers = ['Shift', 'Control', 'Option', 'Command'] else: self.modifiers = ['Control', 'Alt', 'Shift'] self.modifier_label = {'Control': 'Ctrl'} # Short name. def toggle_level(self): "Toggle between basic and advanced keys." if self.button_level.cget('text').startswith('Advanced'): self.clear_key_seq() self.button_level.config(text='<< Basic Key Binding Entry') self.frame_keyseq_advanced.lift() self.frame_help_advanced.lift() self.advanced_keys.focus_set() self.advanced = True else: self.clear_key_seq() self.button_level.config(text='Advanced Key Binding Entry >>') self.frame_keyseq_basic.lift() self.frame_controls_basic.lift() self.advanced = False def final_key_selected(self, event=None): "Handler for clicking on key in basic settings list." self.build_key_string() def build_key_string(self): "Create formatted string of modifiers plus the key." keylist = modifiers = self.get_modifiers() final_key = self.list_keys_final.get('anchor') if final_key: final_key = translate_key(final_key, modifiers) keylist.append(final_key) self.key_string.set(f"<{'-'.join(keylist)}>") def get_modifiers(self): "Return ordered list of modifiers that have been selected." mod_list = [variable.get() for variable in self.modifier_vars] return [mod for mod in mod_list if mod] def clear_key_seq(self): "Clear modifiers and keys selection." self.list_keys_final.select_clear(0, 'end') self.list_keys_final.yview('moveto', '0.0') for variable in self.modifier_vars: variable.set('') self.key_string.set('') def ok(self, event=None): keys = self.key_string.get().strip() if not keys: self.showerror(title=self.keyerror_title, parent=self, message="No key specified.") return if (self.advanced or self.keys_ok(keys)) and self.bind_ok(keys): self.result = keys self.grab_release() self.destroy() def cancel(self, event=None): self.result = '' self.grab_release() self.destroy() def keys_ok(self, keys): """Validity check on user's 'basic' keybinding selection. Doesn't check the string produced by the advanced dialog because 'modifiers' isn't set. """ final_key = self.list_keys_final.get('anchor') modifiers = self.get_modifiers() title = self.keyerror_title key_sequences = [ key for keylist in self.current_key_sequences for key in keylist ] if not keys.endswith('>'): self.showerror(title, parent=self, message='Missing the final Key') elif (not modifiers and final_key not in FUNCTION_KEYS + MOVE_KEYS): self.showerror(title=title, parent=self, message='No modifier key(s) specified.') elif (modifiers == ['Shift']) \ and (final_key not in FUNCTION_KEYS + MOVE_KEYS + ('Tab', 'Space')): msg = 'The shift modifier by itself may not be used with'\ ' this key symbol.' self.showerror(title=title, parent=self, message=msg) elif keys in key_sequences: msg = 'This key combination is already in use.' self.showerror(title=title, parent=self, message=msg) else: return True return False def bind_ok(self, keys): "Return True if Tcl accepts the new keys else show message." try: binding = self.bind(keys, lambda: None) except TclError as err: self.showerror( title=self.keyerror_title, parent=self, message=(f'The entered key sequence is not accepted.\n\n' f'Error: {err}')) return False else: self.unbind(keys, binding) return True
class Application(Frame): """The main Tk application, a simple dialog.""" def __init__(self, master=None): super().__init__(master) self.badge = None self.grid() self.columnconfigure(0, minsize=200) self.columnconfigure(1, minsize=200) self.rowconfigure(0, minsize=300) self.rowconfigure(3, minsize=30) self.create_widgets() # self.output.insert("1.0", "This is a test\n") # self.sentto.insert("end", b"this is an error\n", "error") self.connect() self.after(SERIAL_POLL_INTERVAL, self.poll_serial) def create_widgets(self): """Sets up dialog elements.""" da_row = 0 "counter so I don't have to update every grid() call when I add/remove row" self.select = tix.FileSelectBox(self, browsecmd=self.on_file_selected, pattern="*.fs", directory="forth") # self.select["textVariable"] = self.forth_file self.select.grid(row=da_row, columnspan=2, sticky='nwes', pady=10) da_row += 1 self.output_label = Label(self, text="Badge Output") self.output_label.grid(row=da_row, column=1, sticky='w', padx=10, pady=3) self.sentto_label = Label(self, text="Sent To Badge") self.sentto_label.grid(row=da_row, column=0, sticky='w', padx=10, pady=3) da_row += 1 # height is in lines, width in characters self.output = Text(self, height=16, width=40) self.output.grid(row=da_row, column=1, padx=10) self.sentto = Text(self, height=16, width=40) self.sentto.tag_configure("error", background="red") self.sentto.grid(row=da_row, column=0, padx=10) da_row += 1 self.connect_btn = Button(self, text="Connect", command=self.toggle_connect) self.connect_btn.grid(row=da_row, column=0, columnspan=2) da_row += 1 self.exec_btn = Button(self, text="Execute", command=self.send_file) self.exec_btn.state(["disabled"]) self.exec_btn.grid(row=da_row, column=0, sticky='w' + 'e', padx=10, pady=3) self.quit = Button(self, text="QUIT", command=self.master.destroy) self.quit.grid(row=da_row, column=1, sticky='w' + 'e', padx=10, pady=3) da_row += 1 self.status_panel = Frame(self, relief="sunken", borderwidth=3) self.status_panel.grid(row=da_row, columnspan=2, sticky='nwse') self.connect_status = Label(self.status_panel, text="Not Connected") self.connect_status.grid(row=0, padx=10, pady=5, sticky="w") if self.badge is not None: self.connect_btn.state(["disabled"]) self.connect_status.config(text="Connected: " + self.badge.os_device) def send_file(self, _retry=False): """Send the selected file to the badge.""" if self.badge: try: # oddly, very first set LED seems to not set correct color self.badge.led(0, 0, 128) self.badge.led(0, 0, 128) with open(self.select.cget("value"), 'r') as forthin: self.badge.forth_run(forthin.read()) time.sleep(1) # because forth_run() may be too fast self.badge.led(0, 128, 0) except IOError: if not _retry: self.connect() self.send_file(True) else: raise def poll_serial(self): "Checks serial port for incoming bytes, reads and displays them." if self.badge is not None: bytes_in = self.badge.read_from() if bytes_in: self.output.insert("end", bytes_in + b'\n') self.after(SERIAL_POLL_INTERVAL, self.poll_serial) def toggle_connect(self): "If connected, disconnect, otherwise connect." if self.connect_btn.cget("text") == "Connect": self.connect() else: self.disconnect() def disconnect(self): "Disconnect from current badge." isinstance(self.badge, Badge) self.badge.close() self.badge = None self.connect_btn.config(text="Connect") self.connect_status.config(text="Not connected.") self.exec_btn.state(["disabled"]) def connect(self): """Attempt to connect to a badge; toggle Connect button if successful.""" try: self.badge = Badge(on_serial_write=self.on_bytes_sent, min_write_dt=0.01) self.connect_status.config(text="Connected: " + self.badge.os_device) self.connect_btn.config(text="Disconnect") # enable "Execute" if file is selected self.on_file_selected(self.select.cget("value")) except BadgeSerialException: self.connect_status.config(text="Not connected") def on_file_selected(self, selected_file): """Respond to user selection of file by enabling the Execute button.""" if Path(selected_file).is_file: self.exec_btn.state(["!disabled"]) else: self.exec_btn.state(["disabled"]) def on_bytes_sent(self, data, count): self.sentto.insert("end", data[:count]) if count < len(data): self.sentto.insert("end", data[count:], "error") self.sentto.insert("end", "\n")
class Application(Frame): """The main Tk application, a simple dialog.""" def __init__(self, master=None): super().__init__(master) self.badge = None self.grid() self.columnconfigure(0, minsize=200) self.columnconfigure(1, minsize=200) self.rowconfigure(0, minsize=300) self.rowconfigure(3, minsize=30) self.create_widgets() self.connect() def create_widgets(self): """Sets up dialog elements.""" self.select = tix.FileSelectBox(self, browsecmd=self.on_file_selected, pattern="*.fs", directory="forth") # self.select["textVariable"] = self.forth_file self.select.grid(row=0, columnspan=2, sticky='n'+'w'+'e') self.connect_btn = Button(self, text="Connect", command=self.toggle_connect) self.connect_btn.grid(row=1, column=0, columnspan=2) self.exec_btn = Button(self, text="Execute", command=self.send_file) self.exec_btn.state(["disabled"]) self.exec_btn.grid(row=2, column=0, sticky='w' + 'e', padx=5, pady=3) self.quit = Button(self, text="QUIT", command=self.master.destroy) self.quit.grid(row=2, column=1, sticky='w' + 'e', padx=5, pady=3) self.status_panel = Frame(self, relief="groove", borderwidth=3) self.status_panel.grid(row=3, columnspan=2, sticky='nwse') self.connect_status = Label(self.status_panel, text="Not Connected") self.connect_status.grid(row=0, padx=5, pady=5, sticky="w") if self.badge is not None: self.connect_btn.state(["disabled"]) self.connect_status.config(text="Connected: " + self.badge.os_device) def send_file(self, _retry=False): """Send the selected file to the badge.""" if self.badge: try: # oddly, very first set LED seems to not set correct color self.badge.led(0, 0, 128) self.badge.led(0, 0, 128) with open(self.select.cget("value"), 'r') as forthin: self.badge.forth_run(forthin.read()) time.sleep(1) # because forth_run() may be too fast self.badge.led(0, 128, 0) except IOError: if not _retry: self.connect() self.send_file(True) else: raise def toggle_connect(self): "If connected, disconnect, otherwise connect." if self.connect_btn.cget("text") == "Connect": self.connect() else: self.disconnect() def disconnect(self): "Disconnect from current badge." isinstance(self.badge, Badge) self.badge.close() self.badge = None self.connect_btn.config(text="Connect") self.connect_status.config(text="Not connected.") self.exec_btn.state(["disabled"]) def connect(self): """Attempt to connect to a badge; toggle Connect button if successful.""" try: self.badge = Badge() self.connect_status.config(text="Connected: " + self.badge.os_device) self.connect_btn.config(text="Disconnect") # enable "Execute" if file is selected self.on_file_selected(self.select.cget("value")) except BadgeSerialException: self.connect_status.config(text="Not connected") def on_file_selected(self, selected_file): """Respond to user selection of file by enabling the Execute button.""" if Path(selected_file).is_file: self.exec_btn.state(["!disabled"]) else: self.exec_btn.state(["disabled"])
class GetKeysDialog(Toplevel): # Dialog title for invalid key sequence keyerror_title = 'Key Sequence Error' def __init__(self, parent, title, action, current_key_sequences, *, _htest=False, _utest=False): """ parent - parent of this dialog title - string which is the title of the popup dialog action - string, the name of the virtual event these keys will be mapped to current_key_sequences - list, a list of all key sequence lists currently mapped to virtual events, for overlap checking _htest - bool, change box location when running htest _utest - bool, do not wait when running unittest """ Toplevel.__init__(self, parent) self.withdraw() # Hide while setting geometry. self.configure(borderwidth=5) self.resizable(height=False, width=False) self.title(title) self.transient(parent) self.grab_set() self.protocol("WM_DELETE_WINDOW", self.cancel) self.parent = parent self.action = action self.current_key_sequences = current_key_sequences self.result = '' self.key_string = StringVar(self) self.key_string.set('') # Set self.modifiers, self.modifier_label. self.set_modifiers_for_platform() self.modifier_vars = [] for modifier in self.modifiers: variable = StringVar(self) variable.set('') self.modifier_vars.append(variable) self.advanced = False self.create_widgets() self.update_idletasks() self.geometry( "+%d+%d" % ( parent.winfo_rootx() + (parent.winfo_width()/2 - self.winfo_reqwidth()/2), parent.winfo_rooty() + ((parent.winfo_height()/2 - self.winfo_reqheight()/2) if not _htest else 150) ) ) # Center dialog over parent (or below htest box). if not _utest: self.deiconify() # Geometry set, unhide. self.wait_window() def showerror(self, *args, **kwargs): # Make testing easier. Replace in #30751. messagebox.showerror(*args, **kwargs) def create_widgets(self): self.frame = frame = Frame(self, borderwidth=2, relief='sunken') frame.pack(side='top', expand=True, fill='both') frame_buttons = Frame(self) frame_buttons.pack(side='bottom', fill='x') self.button_ok = Button(frame_buttons, text='OK', width=8, command=self.ok) self.button_ok.grid(row=0, column=0, padx=5, pady=5) self.button_cancel = Button(frame_buttons, text='Cancel', width=8, command=self.cancel) self.button_cancel.grid(row=0, column=1, padx=5, pady=5) # Basic entry key sequence. self.frame_keyseq_basic = Frame(frame, name='keyseq_basic') self.frame_keyseq_basic.grid(row=0, column=0, sticky='nsew', padx=5, pady=5) basic_title = Label(self.frame_keyseq_basic, text=f"New keys for '{self.action}' :") basic_title.pack(anchor='w') basic_keys = Label(self.frame_keyseq_basic, justify='left', textvariable=self.key_string, relief='groove', borderwidth=2) basic_keys.pack(ipadx=5, ipady=5, fill='x') # Basic entry controls. self.frame_controls_basic = Frame(frame) self.frame_controls_basic.grid(row=1, column=0, sticky='nsew', padx=5) # Basic entry modifiers. self.modifier_checkbuttons = {} column = 0 for modifier, variable in zip(self.modifiers, self.modifier_vars): label = self.modifier_label.get(modifier, modifier) check = Checkbutton(self.frame_controls_basic, command=self.build_key_string, text=label, variable=variable, onvalue=modifier, offvalue='') check.grid(row=0, column=column, padx=2, sticky='w') self.modifier_checkbuttons[modifier] = check column += 1 # Basic entry help text. help_basic = Label(self.frame_controls_basic, justify='left', text="Select the desired modifier keys\n"+ "above, and the final key from the\n"+ "list on the right.\n\n" + "Use upper case Symbols when using\n" + "the Shift modifier. (Letters will be\n" + "converted automatically.)") help_basic.grid(row=1, column=0, columnspan=4, padx=2, sticky='w') # Basic entry key list. self.list_keys_final = Listbox(self.frame_controls_basic, width=15, height=10, selectmode='single') self.list_keys_final.insert('end', *AVAILABLE_KEYS) self.list_keys_final.bind('<ButtonRelease-1>', self.final_key_selected) self.list_keys_final.grid(row=0, column=4, rowspan=4, sticky='ns') scroll_keys_final = Scrollbar(self.frame_controls_basic, orient='vertical', command=self.list_keys_final.yview) self.list_keys_final.config(yscrollcommand=scroll_keys_final.set) scroll_keys_final.grid(row=0, column=5, rowspan=4, sticky='ns') self.button_clear = Button(self.frame_controls_basic, text='Clear Keys', command=self.clear_key_seq) self.button_clear.grid(row=2, column=0, columnspan=4) # Advanced entry key sequence. self.frame_keyseq_advanced = Frame(frame, name='keyseq_advanced') self.frame_keyseq_advanced.grid(row=0, column=0, sticky='nsew', padx=5, pady=5) advanced_title = Label(self.frame_keyseq_advanced, justify='left', text=f"Enter new binding(s) for '{self.action}' :\n" + "(These bindings will not be checked for validity!)") advanced_title.pack(anchor='w') self.advanced_keys = Entry(self.frame_keyseq_advanced, textvariable=self.key_string) self.advanced_keys.pack(fill='x') # Advanced entry help text. self.frame_help_advanced = Frame(frame) self.frame_help_advanced.grid(row=1, column=0, sticky='nsew', padx=5) help_advanced = Label(self.frame_help_advanced, justify='left', text="Key bindings are specified using Tkinter keysyms as\n"+ "in these samples: <Control-f>, <Shift-F2>, <F12>,\n" "<Control-space>, <Meta-less>, <Control-Alt-Shift-X>.\n" "Upper case is used when the Shift modifier is present!\n\n" + "'Emacs style' multi-keystroke bindings are specified as\n" + "follows: <Control-x><Control-y>, where the first key\n" + "is the 'do-nothing' keybinding.\n\n" + "Multiple separate bindings for one action should be\n"+ "separated by a space, eg., <Alt-v> <Meta-v>." ) help_advanced.grid(row=0, column=0, sticky='nsew') # Switch between basic and advanced. self.button_level = Button(frame, command=self.toggle_level, text='<< Basic Key Binding Entry') self.button_level.grid(row=2, column=0, stick='ew', padx=5, pady=5) self.toggle_level() def set_modifiers_for_platform(self): """Determine list of names of key modifiers for this platform. The names are used to build Tk bindings -- it doesn't matter if the keyboard has these keys; it matters if Tk understands them. The order is also important: key binding equality depends on it, so config-keys.def must use the same ordering. """ if sys.platform == "darwin": self.modifiers = ['Shift', 'Control', 'Option', 'Command'] else: self.modifiers = ['Control', 'Alt', 'Shift'] self.modifier_label = {'Control': 'Ctrl'} # Short name. def toggle_level(self): "Toggle between basic and advanced keys." if self.button_level.cget('text').startswith('Advanced'): self.clear_key_seq() self.button_level.config(text='<< Basic Key Binding Entry') self.frame_keyseq_advanced.lift() self.frame_help_advanced.lift() self.advanced_keys.focus_set() self.advanced = True else: self.clear_key_seq() self.button_level.config(text='Advanced Key Binding Entry >>') self.frame_keyseq_basic.lift() self.frame_controls_basic.lift() self.advanced = False def final_key_selected(self, event=None): "Handler for clicking on key in basic settings list." self.build_key_string() def build_key_string(self): "Create formatted string of modifiers plus the key." keylist = modifiers = self.get_modifiers() final_key = self.list_keys_final.get('anchor') if final_key: final_key = translate_key(final_key, modifiers) keylist.append(final_key) self.key_string.set(f"<{'-'.join(keylist)}>") def get_modifiers(self): "Return ordered list of modifiers that have been selected." mod_list = [variable.get() for variable in self.modifier_vars] return [mod for mod in mod_list if mod] def clear_key_seq(self): "Clear modifiers and keys selection." self.list_keys_final.select_clear(0, 'end') self.list_keys_final.yview('moveto', '0.0') for variable in self.modifier_vars: variable.set('') self.key_string.set('') def ok(self, event=None): keys = self.key_string.get().strip() if not keys: self.showerror(title=self.keyerror_title, parent=self, message="No key specified.") return if (self.advanced or self.keys_ok(keys)) and self.bind_ok(keys): self.result = keys self.grab_release() self.destroy() def cancel(self, event=None): self.result = '' self.grab_release() self.destroy() def keys_ok(self, keys): """Validity check on user's 'basic' keybinding selection. Doesn't check the string produced by the advanced dialog because 'modifiers' isn't set. """ final_key = self.list_keys_final.get('anchor') modifiers = self.get_modifiers() title = self.keyerror_title key_sequences = [key for keylist in self.current_key_sequences for key in keylist] if not keys.endswith('>'): self.showerror(title, parent=self, message='Missing the final Key') elif (not modifiers and final_key not in FUNCTION_KEYS + MOVE_KEYS): self.showerror(title=title, parent=self, message='No modifier key(s) specified.') elif (modifiers == ['Shift']) \ and (final_key not in FUNCTION_KEYS + MOVE_KEYS + ('Tab', 'Space')): msg = 'The shift modifier by itself may not be used with'\ ' this key symbol.' self.showerror(title=title, parent=self, message=msg) elif keys in key_sequences: msg = 'This key combination is already in use.' self.showerror(title=title, parent=self, message=msg) else: return True return False def bind_ok(self, keys): "Return True if Tcl accepts the new keys else show message." try: binding = self.bind(keys, lambda: None) except TclError as err: self.showerror( title=self.keyerror_title, parent=self, message=(f'The entered key sequence is not accepted.\n\n' f'Error: {err}')) return False else: self.unbind(keys, binding) return True
class Application(Frame): """The main Tk application, a simple dialog.""" def __init__(self, master=None): super().__init__(master) self.badge = None self.grid() self.columnconfigure(0, minsize=200) self.columnconfigure(1, minsize=200) self.rowconfigure(0, minsize=300) self.rowconfigure(3, minsize=30) self.create_widgets() self.connect() def create_widgets(self): """Sets up dialog elements.""" self.select = tix.FileSelectBox(self, browsecmd=self.on_file_selected, pattern="*.fs", directory="forth") # self.select["textVariable"] = self.forth_file self.select.grid(row=0, columnspan=2, sticky='n' + 'w' + 'e') self.connect_btn = Button(self, text="Connect", command=self.toggle_connect) self.connect_btn.grid(row=1, column=0, columnspan=2) self.exec_btn = Button(self, text="Execute", command=self.send_file) self.exec_btn.state(["disabled"]) self.exec_btn.grid(row=2, column=0, sticky='w' + 'e', padx=5, pady=3) self.quit = Button(self, text="QUIT", command=self.master.destroy) self.quit.grid(row=2, column=1, sticky='w' + 'e', padx=5, pady=3) self.status_panel = Frame(self, relief="groove", borderwidth=3) self.status_panel.grid(row=3, columnspan=2, sticky='nwse') self.connect_status = Label(self.status_panel, text="Not Connected") self.connect_status.grid(row=0, padx=5, pady=5, sticky="w") if self.badge is not None: self.connect_btn.state(["disabled"]) self.connect_status.config(text="Connected: " + self.badge.os_device) def send_file(self, _retry=False): """Send the selected file to the badge.""" if self.badge: try: # oddly, very first set LED seems to not set correct color self.badge.led(0, 0, 128) self.badge.led(0, 0, 128) with open(self.select.cget("value"), 'r') as forthin: self.badge.forth_run(forthin.read()) time.sleep(1) # because forth_run() may be too fast self.badge.led(0, 128, 0) except IOError: if not _retry: self.connect() self.send_file(True) else: raise def toggle_connect(self): "If connected, disconnect, otherwise connect." if self.connect_btn.cget("text") == "Connect": self.connect() else: self.disconnect() def disconnect(self): "Disconnect from current badge." isinstance(self.badge, Badge) self.badge.close() self.badge = None self.connect_btn.config(text="Connect") self.connect_status.config(text="Not connected.") self.exec_btn.state(["disabled"]) def connect(self): """Attempt to connect to a badge; toggle Connect button if successful.""" try: self.badge = Badge() self.connect_status.config(text="Connected: " + self.badge.os_device) self.connect_btn.config(text="Disconnect") # enable "Execute" if file is selected self.on_file_selected(self.select.cget("value")) except BadgeSerialException: self.connect_status.config(text="Not connected") def on_file_selected(self, selected_file): """Respond to user selection of file by enabling the Execute button.""" if Path(selected_file).is_file: self.exec_btn.state(["!disabled"]) else: self.exec_btn.state(["disabled"])
class SetupWindow(Tk): """ Little GUI for setting user's name, the partner's ip and that the PC is a server or not. """ def __init__(self, *args, **kwargs): Tk.__init__(self, *args, **kwargs) try: # Set icon self.iconbitmap('icon.ico') except Exception: pass self.wm_title('TinyChat') self.resizable(False, False) try: self.eval('tk::PlaceWindow %s center' % self.winfo_pathname(self.winfo_id())) except TclError: pass self.name = 'anonymous' self.other_ip = socket.gethostbyname(socket.gethostname()) self.is_server = True # Read last config from file if os.path.isfile('config.json'): with open('config.json', 'r') as file: config = json.load(file) self.name = config['name'] self.other_ip = config['other_ip'] self.is_server = config['is_server'] self.name_label = Label(self, text='Name') self.other_ip_label = Label(self, text='IP') self.server_label = Label(self, text='Server') self.name_entry = Entry(self, textvariable=StringVar(), bd=1, width=30, bg='white', fg='black') self.other_ip_entry = Entry(self, textvariable=StringVar(), bd=1, width=30, bg='white', fg='black') self.server_button = Button(self, text='Yes' if self.is_server else 'No', width=22, command=lambda: self.set_is_server()) self.apply_button = Button(self, text='Start', width=7, command=self.apply) self.name_entry.config(textvariable=StringVar(self, self.name)) self.other_ip_entry.config(textvariable=StringVar(self, self.other_ip)) self.other_ip_entry.config( state='disabled' if self.is_server else 'normal') self.name_label.grid(padx=1, pady=2, row=0, column=0) self.name_entry.grid(padx=1, pady=2, row=0, column=1) self.other_ip_label.grid(padx=1, pady=2, row=1, column=0) self.other_ip_entry.grid(padx=1, pady=2, row=1, column=1) self.server_label.grid(padx=1, pady=2, row=2, column=0) self.server_button.grid(padx=1, pady=2, row=2, column=1) self.apply_button.grid(padx=1, pady=5, row=3, column=1) self.mainloop() def set_is_server(self): self.is_server = not self.is_server self.server_button.config(text='Yes' if self.is_server else 'No') self.other_ip_entry.config( state='disabled' if self.is_server else 'normal') def apply(self): global MY_NAME, OTHER_IP, IS_SERVER if self.name_entry.get() and self.other_ip_entry.get(): MY_NAME = self.name_entry.get() OTHER_IP = self.other_ip_entry.get() IS_SERVER = self.server_button.cget('text') == 'Yes' config = { 'name': MY_NAME, 'other_ip': OTHER_IP, 'is_server': IS_SERVER } with open('config.json', 'w+') as file: json.dump(config, file) self.destroy()