def make_widgets(self, master: 'CheckDetails'): if self.master is not None: # If we let items move between lists, the old widgets will become # orphaned! raise ValueError("Can't move Item objects between lists!") self.master = master self.check = ttk.Checkbutton( master.wid_frame, variable=self.state_var, onvalue=1, offvalue=0, takefocus=False, width=0, style='CheckDetails.TCheckbutton', command=self.master.update_allcheck, ) if self.locked: self.check.state(['disabled']) self.val_widgets = [] for value in self.values: wid = tk.Label( master.wid_frame, text=value, justify=tk.LEFT, anchor=tk.W, background='white', ) add_tooltip(wid) if self.hover_text: set_tooltip(wid, self.hover_text) wid.hover_override = True else: set_tooltip(wid) wid.hover_override = False if not self.locked: # Allow clicking on the row to toggle the checkbox wid.bind('<Enter>', self.hover_start, add=True) wid.bind('<Leave>', self.hover_stop, add=True) tk_tools.bind_leftclick(wid, self.row_click, add=True) wid.bind(tk_tools.EVENTS['LEFT_RELEASE'], self.row_unclick, add=True) self.val_widgets.append(wid) tk_tools.add_mousewheel(self.master.wid_canvas, self.check, *self.val_widgets)
def make_pane(parent: ttk.Frame): """Create all the widgets we use.""" CONFIG_ORDER.sort(key=lambda grp: grp.name) parent.columnconfigure(0, weight=1) # Need to use a canvas to allow scrolling canvas = tk.Canvas(parent, highlightthickness=0) canvas.grid(row=0, column=0, sticky='NSEW') parent.rowconfigure(0, weight=1) scrollbar = ttk.Scrollbar( parent, orient='vertical', command=canvas.yview, ) scrollbar.grid(column=1, row=0, sticky="ns") canvas['yscrollcommand'] = scrollbar.set tk_tools.add_mousewheel(canvas, canvas, parent) canvas_frame = ttk.Frame(canvas) canvas.create_window(0, 0, window=canvas_frame, anchor="nw") canvas_frame.rowconfigure(0, weight=1) sign_button = signage_ui.init_widgets(canvas_frame) if sign_button is not None: sign_button.grid(row=0, column=0, sticky='ew') for conf_row, config in enumerate(CONFIG_ORDER, start=1): frame = ttk.LabelFrame(canvas_frame, text=config.name) frame.columnconfigure(0, weight=1) frame.grid(row=conf_row, column=0, sticky='nsew') row = 0 widget_count = len(config.widgets) + len(config.multi_widgets) # Now make the widgets. if config.widgets: for row, wid in enumerate(config.widgets): wid_frame = ttk.Frame(frame) wid_frame.grid(row=row, column=0, sticky='ew') wid_frame.columnconfigure(1, weight=1) try: widget = wid.create_func(wid_frame, wid.values, wid.config) except Exception: LOGGER.exception('Could not construct widget {}.{}', config.id, wid.id) continue label = ttk.Label(wid_frame, text=wid.name + ': ') label.grid(row=0, column=0) widget.grid(row=0, column=1, sticky='e') if wid.tooltip: add_tooltip(widget, wid.tooltip) add_tooltip(label, wid.tooltip) add_tooltip(wid_frame, wid.tooltip) if config.widgets and config.multi_widgets: ttk.Separator(orient='horizontal').grid( row=1, column=0, sticky='ew', ) # Skip if no timer widgets if not config.multi_widgets: continue # Continue from wherever we were. for row, wid in enumerate(config.multi_widgets, start=row + 1): # If we only have 1 widget, don't add a redundant title. if widget_count == 1: wid_frame = ttk.Frame(frame) else: wid_frame = ttk.LabelFrame(frame, text=wid.name) wid_frame.grid(row=row, column=0, sticky='ew') wid.multi_func( wid_frame, wid.values, wid.config, ) if wid.tooltip: add_tooltip(wid_frame, wid.tooltip) canvas.update_idletasks() canvas.config( scrollregion=canvas.bbox('ALL'), width=canvas_frame.winfo_reqwidth(), ) def canvas_reflow(e): canvas['scrollregion'] = canvas.bbox('all') canvas.bind('<Configure>', canvas_reflow)
def init_widgets(master: ttk.Frame) -> Optional[tk.Widget]: """Construct the widgets, returning the configuration button. If no signages are defined, this returns None. """ if not any(Signage.all()): return ttk.Label(master) window.resizable(True, True) window.title(gettext('Configure Signage')) frame_selected = ttk.Labelframe( window, text=gettext('Selected'), relief='raised', labelanchor='n', ) canv_all = tk.Canvas(window) scroll = tk_tools.HidingScroll(window, orient='vertical', command=canv_all.yview) canv_all['yscrollcommand'] = scroll.set name_label = ttk.Label(window, text='', justify='center') frame_preview = ttk.Frame(window, relief='raised', borderwidth=4) frame_selected.grid(row=0, column=0, sticky='nsew') ttk.Separator(orient='horizontal').grid(row=1, column=0, sticky='ew') name_label.grid(row=2, column=0) frame_preview.grid(row=3, column=0, pady=4) canv_all.grid(row=0, column=1, rowspan=4, sticky='nsew') scroll.grid(row=0, column=2, rowspan=4, sticky='ns') window.columnconfigure(1, weight=1) window.rowconfigure(3, weight=1) tk_tools.add_mousewheel(canv_all, canv_all, window) preview_left = ttk.Label(frame_preview, anchor='e') preview_right = ttk.Label(frame_preview, anchor='w') img.apply(preview_left, IMG_BLANK) img.apply(preview_right, IMG_BLANK) preview_left.grid(row=0, column=0) preview_right.grid(row=0, column=1) try: sign_arrow = Signage.by_id('SIGN_ARROW') except KeyError: LOGGER.warning('No arrow signage defined!') sign_arrow = None hover_arrow = False hover_toggle_id = None hover_sign: Optional[Signage] = None def hover_toggle() -> None: """Toggle between arrows and dual icons.""" nonlocal hover_arrow, hover_toggle_id hover_arrow = not hover_arrow if hover_sign is None: return if hover_arrow and sign_arrow: left = hover_sign.dnd_icon right = sign_arrow.dnd_icon else: try: left = Signage.by_id(hover_sign.prim_id or '').dnd_icon except KeyError: left = hover_sign.dnd_icon try: right = Signage.by_id(hover_sign.sec_id or '').dnd_icon except KeyError: right = IMG_BLANK img.apply(preview_left, left) img.apply(preview_right, right) hover_toggle_id = TK_ROOT.after(1000, hover_toggle) def on_hover(slot: dragdrop.Slot[Signage]) -> None: """Show the signage when hovered.""" nonlocal hover_arrow, hover_sign if slot.contents is not None: name_label['text'] = gettext('Signage: {}').format(slot.contents.name) hover_sign = slot.contents hover_arrow = True hover_toggle() else: on_leave(slot) def on_leave(slot: dragdrop.Slot[Signage]) -> None: """Reset the visible sign when left.""" nonlocal hover_toggle_id, hover_sign name_label['text'] = '' hover_sign = None if hover_toggle_id is not None: TK_ROOT.after_cancel(hover_toggle_id) hover_toggle_id = None img.apply(preview_left, IMG_BLANK) img.apply(preview_right, IMG_BLANK) drag_man.reg_callback(dragdrop.Event.HOVER_ENTER, on_hover) drag_man.reg_callback(dragdrop.Event.HOVER_EXIT, on_leave) for i in range(3, 31): SLOTS_SELECTED[i] = slot = drag_man.slot( frame_selected, source=False, label=f'00:{i:02g}' ) row, col = divmod(i-3, 4) slot.grid(row=row, column=col, padx=1, pady=1) prev_id = DEFAULT_IDS.get(i, '') if prev_id: try: slot.contents = Signage.by_id(prev_id) except KeyError: LOGGER.warning('Missing sign id: {}', prev_id) for sign in sorted(Signage.all(), key=lambda s: s.name): if not sign.hidden: slot = drag_man.slot(canv_all, source=True) slot.contents = sign drag_man.flow_slots(canv_all, drag_man.sources()) canv_all.bind( '<Configure>', lambda e: drag_man.flow_slots(canv_all, drag_man.sources()), ) def hide_window() -> None: """Hide the window.""" window.withdraw() drag_man.unload_icons() img.apply(preview_left, IMG_BLANK) img.apply(preview_right, IMG_BLANK) def show_window() -> None: """Show the window.""" drag_man.load_icons() window.deiconify() utils.center_win(window, TK_ROOT) window.protocol("WM_DELETE_WINDOW", hide_window) return ttk.Button( master, text=gettext('Configure Signage'), command=show_window, )
def __init__(self, parent, items=(), headers=(), add_sizegrip=False): """Initialise a CheckDetails pane. parent is the parent widget. items is a list of Items objects. headers is a list of the header strings. If add_sizegrip is True, add a sizegrip object between the scrollbars. """ super(CheckDetails, self).__init__(parent) self.parent = parent self.headers = list(headers) self.items = [] # type: List[Item] self.sort_ind = None self.rev_sort = False # Should we sort in reverse? self.head_check_var = tk.IntVar(value=False) self.wid_head_check = ttk.Checkbutton( self, variable=self.head_check_var, command=self.toggle_allcheck, takefocus=False, width=0, ) self.wid_head_check.grid(row=0, column=0) add_tooltip(self.wid_head_check, gettext("Toggle all checkboxes.")) def checkbox_enter(e): """When hovering over the 'all' checkbox, highlight the others.""" for item in self.items: item.check.state(['active']) self.wid_head_check.bind('<Enter>', checkbox_enter) def checkbox_leave(e): for item in self.items: item.check.state(['!active']) self.wid_head_check.bind('<Leave>', checkbox_leave) self.wid_header = tk.PanedWindow( self, orient=tk.HORIZONTAL, sashrelief=tk.RAISED, sashpad=2, showhandle=False, ) self.wid_header.grid(row=0, column=1, sticky='EW') self.wid_head_frames = [0] * len(self.headers) # type: List[ttk.Frame] self.wid_head_label = [0] * len(self.headers) # type: List[ttk.Label] self.wid_head_sort = [0] * len(self.headers) # type: List[ttk.Label] self.make_headers() self.wid_canvas = tk.Canvas(self, ) self.wid_canvas.grid(row=1, column=0, columnspan=2, sticky='NSEW') self.columnconfigure(1, weight=1) self.rowconfigure(1, weight=1) self.horiz_scroll = ttk.Scrollbar( self, orient=tk.HORIZONTAL, command=self.wid_canvas.xview, ) self.vert_scroll = ttk.Scrollbar( self, orient=tk.VERTICAL, command=self.wid_canvas.yview, ) self.wid_canvas['xscrollcommand'] = self.horiz_scroll.set self.wid_canvas['yscrollcommand'] = self.vert_scroll.set self.horiz_scroll.grid(row=2, column=0, columnspan=2, sticky='EWS') self.vert_scroll.grid(row=1, column=2, sticky='NSE') if add_sizegrip and tk_tools.USE_SIZEGRIP: self.sizegrip = ttk.Sizegrip(self) self.sizegrip.grid(row=2, column=2) else: self.sizegrip = None self.wid_frame = tk.Frame(self.wid_canvas, background='white', border=0) self.wid_canvas.create_window(0, 0, window=self.wid_frame, anchor='nw') self.bind('<Configure>', self.refresh) self.bind('<Map>', self.refresh) # When added to a window, refresh self.wid_header.bind('<ButtonRelease-1>', self.refresh) self.wid_header.bind('<B1-Motion>', self.refresh) self.wid_header.bind('<Configure>', self.refresh) self.add_items(*items) tk_tools.add_mousewheel( self.wid_canvas, self.wid_canvas, self.wid_frame, self.wid_header, )
self.refresh() def checked(self) -> Iterator[Item]: """Yields enabled check items.""" return (item for item in self.items if item.state_var.get()) def unchecked(self) -> Iterator[Item]: """Yields disabled check items.""" return (item for item in self.items if not item.state_var.get()) if __name__ == '__main__': from app import TK_ROOT test_inst = CheckDetails(parent=TK_ROOT, headers=['Name', 'Author', 'Description'], items=[ Item('Item1', 'Auth1', 'Blah blah blah'), Item('Item5', 'Auth3', 'Lorem Ipsum'), Item('Item3', 'Auth2', '.........'), Item('Item4', 'Auth2', '.........'), Item('Item6', 'Sir VeryLongName', '.....'), Item('Item2', 'Auth1', '...'), ]) test_inst.grid(sticky='NSEW') tk_tools.add_mousewheel(test_inst.wid_canvas, TK_ROOT) TK_ROOT.columnconfigure(0, weight=1) TK_ROOT.rowconfigure(0, weight=1) TK_ROOT.deiconify() TK_ROOT.mainloop()
def init() -> None: """Initialise all widgets in the given window.""" for cat, btn_text in [ ('back_', gettext('Restore:')), ('game_', gettext('Backup:')), ]: UI[cat + 'frame'] = frame = ttk.Frame( window, ) UI[cat + 'title_frame'] = title_frame = ttk.Frame( frame, ) title_frame.grid(row=0, column=0, sticky='EW') UI[cat + 'title'] = ttk.Label( title_frame, font='TkHeadingFont', ) UI[cat + 'title'].grid(row=0, column=0) title_frame.rowconfigure(0, weight=1) title_frame.columnconfigure(0, weight=1) UI[cat + 'details'] = CheckDetails( frame, headers=HEADERS, ) UI[cat + 'details'].grid(row=1, column=0, sticky='NSEW') frame.rowconfigure(1, weight=1) frame.columnconfigure(0, weight=1) button_frame = ttk.Frame( frame, ) button_frame.grid(column=0, row=2) ttk.Label(button_frame, text=btn_text).grid(row=0, column=0) UI[cat + 'btn_all'] = ttk.Button( button_frame, text='All', width=3, ) UI[cat + 'btn_sel'] = ttk.Button( button_frame, text=gettext('Checked'), width=8, ) UI[cat + 'btn_all'].grid(row=0, column=1) UI[cat + 'btn_sel'].grid(row=0, column=2) UI[cat + 'btn_del'] = ttk.Button( button_frame, text=gettext('Delete Checked'), width=14, ) UI[cat + 'btn_del'].grid(row=1, column=0, columnspan=3) tk_tools.add_mousewheel( UI[cat + 'details'].wid_canvas, UI[cat + 'frame'], ) game_refresh = ttk.Button( UI['game_title_frame'], command=ui_refresh_game, ) game_refresh.grid(row=0, column=1, sticky='E') add_tooltip( game_refresh, "Reload the map list.", ) img.apply(game_refresh, img.Handle.builtin('icons/tool_sub', 16, 16)) UI['game_title']['textvariable'] = game_name UI['back_title']['textvariable'] = backup_name UI['game_btn_all']['command'] = ui_backup_all UI['game_btn_sel']['command'] = ui_backup_sel UI['game_btn_del']['command'] = ui_delete_game UI['back_btn_all']['command'] = ui_restore_all UI['back_btn_sel']['command'] = ui_restore_sel UI['back_btn_del']['command'] = ui_delete_backup UI['back_frame'].grid(row=1, column=0, sticky='NSEW') ttk.Separator(orient=tk.VERTICAL).grid( row=1, column=1, sticky='NS', padx=5, ) UI['game_frame'].grid(row=1, column=2, sticky='NSEW') window.rowconfigure(1, weight=1) window.columnconfigure(0, weight=1) window.columnconfigure(2, weight=1)
def make_pane(tool_frame: Frame, menu_bar: Menu, update_item_vis: Callable[[], None]) -> None: """Create the styleVar pane. update_item_vis is the callback fired whenever change defaults changes. """ global window, _load_cback _load_cback = update_item_vis window = SubPane( TK_ROOT, title=gettext('Style/Item Properties'), name='style', menu_bar=menu_bar, resize_y=True, tool_frame=tool_frame, tool_img='icons/win_stylevar', tool_col=3, ) UI['nbook'] = nbook = ttk.Notebook(window) nbook.grid(row=0, column=0, sticky=NSEW) window.rowconfigure(0, weight=1) window.columnconfigure(0, weight=1) nbook.enable_traversal() stylevar_frame = ttk.Frame(nbook) stylevar_frame.rowconfigure(0, weight=1) stylevar_frame.columnconfigure(0, weight=1) nbook.add(stylevar_frame, text=gettext('Styles')) canvas = Canvas(stylevar_frame, highlightthickness=0) # need to use a canvas to allow scrolling canvas.grid(sticky='NSEW') window.rowconfigure(0, weight=1) UI['style_scroll'] = ttk.Scrollbar( stylevar_frame, orient=VERTICAL, command=canvas.yview, ) UI['style_scroll'].grid(column=1, row=0, rowspan=2, sticky="NS") canvas['yscrollcommand'] = UI['style_scroll'].set tk_tools.add_mousewheel(canvas, stylevar_frame) canvas_frame = ttk.Frame(canvas) frame_all = ttk.Labelframe(canvas_frame, text=gettext("All:")) frame_all.grid(row=0, sticky='EW') frm_chosen = ttk.Labelframe(canvas_frame, text=gettext("Selected Style:")) frm_chosen.grid(row=1, sticky='EW') ttk.Separator( canvas_frame, orient=HORIZONTAL, ).grid(row=2, sticky='EW', pady=(10, 5)) frm_other = ttk.Labelframe(canvas_frame, text=gettext("Other Styles:")) frm_other.grid(row=3, sticky='EW') UI['stylevar_chosen_none'] = ttk.Label( frm_chosen, text=gettext('No Options!'), font='TkMenuFont', justify='center', ) UI['stylevar_other_none'] = ttk.Label( frm_other, text=gettext('None!'), font='TkMenuFont', justify='center', ) VAR_LIST[:] = sorted(StyleVar.all(), key=operator.attrgetter('id')) all_pos = 0 for all_pos, var in enumerate(styleOptions): # Add the special stylevars which apply to all styles tk_vars[var.id] = int_var = IntVar(value=var.default) checkbox_all[var.id] = ttk.Checkbutton( frame_all, variable=int_var, text=var.name, ) checkbox_all[var.id].grid(row=all_pos, column=0, sticky="W", padx=3) # Special case - this needs to refresh the filter when swapping, # so the items disappear or reappear. if var.id == 'UnlockDefault': checkbox_all[var.id]['command'] = lambda: update_item_vis() tooltip.add_tooltip(checkbox_all[var.id], make_desc(var)) for var in VAR_LIST: tk_vars[var.id] = IntVar(value=var.enabled) args = { 'variable': tk_vars[var.id], 'text': var.name, } desc = make_desc(var) if var.applies_to_all(): # Available in all styles - put with the hardcoded variables. all_pos += 1 checkbox_all[var.id] = check = ttk.Checkbutton(frame_all, **args) check.grid(row=all_pos, column=0, sticky="W", padx=3) tooltip.add_tooltip(check, desc) else: # Swap between checkboxes depending on style. checkbox_chosen[var.id] = ttk.Checkbutton(frm_chosen, **args) checkbox_other[var.id] = ttk.Checkbutton(frm_other, **args) tooltip.add_tooltip(checkbox_chosen[var.id], desc) tooltip.add_tooltip(checkbox_other[var.id], desc) canvas.create_window(0, 0, window=canvas_frame, anchor="nw") canvas.update_idletasks() canvas.config( scrollregion=canvas.bbox(ALL), width=canvas_frame.winfo_reqwidth(), ) if tk_tools.USE_SIZEGRIP: ttk.Sizegrip( window, cursor=tk_tools.Cursors.STRETCH_VERT, ).grid(row=1, column=0) canvas.bind('<Configure>', lambda e: canvas.configure(scrollregion=canvas.bbox(ALL))) item_config_frame = ttk.Frame(nbook) nbook.add(item_config_frame, text=gettext('Items')) itemconfig.make_pane(item_config_frame)
def __init__(self, translations: dict, pipe: multiprocessing.connection.Connection) -> None: """Initialise the window.""" self.win = window = tk.Toplevel(TK_ROOT) self.pipe = pipe window.columnconfigure(0, weight=1) window.rowconfigure(0, weight=1) window.title(translations['log_title']) window.protocol('WM_DELETE_WINDOW', self.evt_close) window.withdraw() self.has_text = False self.text = tk.Text( window, name='text_box', width=50, height=15, ) self.text.grid(row=0, column=0, sticky='NSEW') scroll = tk_tools.HidingScroll( window, name='scroll', orient=tk.VERTICAL, command=self.text.yview, ) scroll.grid(row=0, column=1, sticky='NS') self.text['yscrollcommand'] = scroll.set # Assign colours for each logging level for level, colour in LVL_COLOURS.items(): self.text.tag_config( logging.getLevelName(level), foreground=colour, # For multi-line messages, indent this much. lmargin2=30, ) self.text.tag_config( logging.getLevelName(logging.CRITICAL), background='red', ) # If multi-line messages contain carriage returns, lmargin2 doesn't # work. Add an additional tag for that. self.text.tag_config( 'INDENT', lmargin1=30, lmargin2=30, ) button_frame = ttk.Frame(window, name='button_frame') button_frame.grid(row=1, column=0, columnspan=2, sticky='EW') ttk.Button( button_frame, name='clear_btn', text=translations['clear'], command=self.evt_clear, ).grid(row=0, column=0) ttk.Button( button_frame, name='copy_btn', text=translations['copy'], command=self.evt_copy, ).grid(row=0, column=1) sel_frame = ttk.Frame(button_frame) sel_frame.grid(row=0, column=2, sticky='EW') button_frame.columnconfigure(2, weight=1) ttk.Label( sel_frame, text=translations['log_show'], anchor='e', justify='right', ).grid(row=0, column=0, sticky='E') self.level_selector = ttk.Combobox( sel_frame, name='level_selector', values=translations['level_text'], exportselection=False, # On Mac this defaults to being way too wide! width=15 if utils.MAC else None, ) self.level_selector.state(['readonly' ]) # Prevent directly typing in values self.level_selector.bind('<<ComboboxSelected>>', self.evt_set_level) self.level_selector.current(1) self.level_selector.grid(row=0, column=1, sticky='E') sel_frame.columnconfigure(1, weight=1) tk_tools.add_mousewheel(self.text, window, sel_frame, button_frame) if tk_tools.USE_SIZEGRIP: ttk.Sizegrip(button_frame).grid(row=0, column=3)