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: wid.tooltip_text = self.hover_text wid.hover_override = True else: wid.tooltip_text = '' wid.hover_override = False if not self.locked: # Allow clicking on the row to toggle the checkbox wid.bind('<Enter>', self.hover_start, add='+') wid.bind('<Leave>', self.hover_stop, add='+') utils.bind_leftclick(wid, self.row_click, add='+') wid.bind(utils.EVENTS['LEFT_RELEASE'], self.row_unclick, add='+') self.val_widgets.append(wid) utils.add_mousewheel( self.master.wid_canvas, self.check, *self.val_widgets )
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='+') wid.bind('<Leave>', self.hover_stop, add='+') utils.bind_leftclick(wid, self.row_click, add='+') wid.bind(utils.EVENTS['LEFT_RELEASE'], self.row_unclick, add='+') self.val_widgets.append(wid) utils.add_mousewheel(self.master.wid_canvas, self.check, *self.val_widgets)
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, _("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 utils.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) utils.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__': root = tk_tools.TK_ROOT test_inst = CheckDetails(parent=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') utils.add_mousewheel(test_inst.wid_canvas, root) root.columnconfigure(0, weight=1) root.rowconfigure(0, weight=1) root.deiconify() root.mainloop()
def init(): """Initialise all widgets in the given window.""" for cat, btn_text in [ ('back_', 'Restore:'), ('game_', '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='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='Delete Checked', width=14, ) UI[cat + 'btn_del'].grid(row=1, column=0, columnspan=3) utils.add_mousewheel( UI[cat + 'details'].wid_canvas, UI[cat + 'frame'], ) UI['game_refresh'] = ttk.Button( UI['game_title_frame'], image=img.png('icons/tool_sub'), command=ui_refresh_game, ) UI['game_refresh'].grid(row=0, column=1, sticky='E') add_tooltip( UI['game_refresh'], "Reload the map list.", ) 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['back_btn_all']['command'] = ui_restore_all UI['back_btn_sel']['command'] = ui_restore_sel 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): """Create the styleVar pane. """ global window window = SubPane( TK_ROOT, title=_('Style/Item Properties'), name='style', menu_bar=menu_bar, resize_y=True, tool_frame=tool_frame, tool_img=img.png('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=_('Styles')) UI['style_can'] = Canvas(stylevar_frame, highlightthickness=0) # need to use a canvas to allow scrolling UI['style_can'].grid(sticky='NSEW') window.rowconfigure(0, weight=1) UI['style_scroll'] = ttk.Scrollbar( stylevar_frame, orient=VERTICAL, command=UI['style_can'].yview, ) UI['style_scroll'].grid(column=1, row=0, rowspan=2, sticky="NS") UI['style_can']['yscrollcommand'] = UI['style_scroll'].set utils.add_mousewheel(UI['style_can'], stylevar_frame) canvas_frame = ttk.Frame(UI['style_can']) frame_all = ttk.Labelframe(canvas_frame, text=_("All:")) frame_all.grid(row=0, sticky='EW') frm_chosen = ttk.Labelframe(canvas_frame, text=_("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=_("Other Styles:")) frm_other.grid(row=3, sticky='EW') UI['stylevar_chosen_none'] = ttk.Label( frm_chosen, text=_('No Options!'), font='TkMenuFont', justify='center', ) UI['stylevar_other_none'] = ttk.Label( frm_other, text=_('None!'), font='TkMenuFont', justify='center', ) 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': def cmd(): update_filter() checkbox_all[var.id]['command'] = cmd tooltip.add_tooltip( checkbox_all[var.id], make_desc(var, is_hardcoded=True), ) 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, ) UI['style_can'].create_window(0, 0, window=canvas_frame, anchor="nw") UI['style_can'].update_idletasks() UI['style_can'].config( scrollregion=UI['style_can'].bbox(ALL), width=canvas_frame.winfo_reqwidth(), ) if utils.USE_SIZEGRIP: ttk.Sizegrip( window, cursor=utils.CURSORS['stretch_vert'], ).grid(row=1, column=0) UI['style_can'].bind('<Configure>', flow_stylevar) item_config_frame = ttk.Frame(nbook) nbook.add(item_config_frame, text=_('Items')) itemconfig.make_pane(item_config_frame)
def make_pane(parent: ttk.Frame): """Create all the widgets we use.""" if not CONFIG_ORDER: # No configs at all... ttk.Label(parent, text=_('No Item Configuration!')).pack(fill='both') return 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 utils.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) for conf_row, config in enumerate(CONFIG_ORDER): 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) label = ttk.Label(wid_frame, text=wid.name + ': ') label.grid(row=0, column=0) widget = wid.create_func(wid_frame, wid.values, wid.config) widget.grid(row=0, column=1, sticky='e') if wid.tooltip: add_tooltip(widget, wid.tooltip) add_tooltip(label, 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__( self, tk, lst, *, # Make all keyword-only for readability has_none=True, has_def=True, sound_sys: FileSystemChain = None, modal=False, # i18n: 'None' item description none_desc=_('Do not add anything.'), none_attrs=EmptyMapping, none_icon='BEE2/none_96.png', # i18n: 'None' item name. none_name=_("<None>"), title='BEE2', desc='', readonly_desc='', callback=None, callback_params=(), attributes=()): """Create a window object. Read from .selected_id to get the currently-chosen Item name, or None if the <none> Item is selected. Args: - tk: Must be a Toplevel window, either the tk() root or another window if needed. - lst: A list of Item objects, defining the visible items. - If has_none is True, a <none> item will be added to the beginning of the list. - If has_def is True, the 'Reset to Default' button will appear, which resets to the suggested item. - If snd_sample_sys is set, a '>' button will appear next to names to play the associated audio sample for the item. The value should be a FileSystem to look for samples in. - none_desc holds an optional description for the <none> Item, which can be used to describe what it results in. - none_icon allows changing the icon for the <none> Item. - none_name allows setting the name shown for the <none> Item. - title is the title of the selector window. - callback is a function to be called whenever the selected item changes. - callback_params is a list of additional values which will be passed to the callback function. The first argument to the callback is always the selected item ID. - full_context controls if the short or long names are used for the context menu. - attributes is a list of AttrDef tuples. Each tuple should contain an ID, display text, and default value. If the values are True or False a check/cross will be displayed, otherwise they're a string. - desc is descriptive text to display on the window, and in the widget tooltip. - readonly_desc will be displayed on the widget tooltip when readonly. - modal: If True, the window will block others while open. """ self.noneItem = Item( name='<NONE>', short_name='', icon=none_icon, desc=none_desc, attributes=dict(none_attrs), ) # The textbox on the parent window. self.display = None # type: tk_tools.ReadOnlyEntry # Variable associated with self.display. self.disp_label = StringVar() # The '...' button to open our window. self.disp_btn = None # type: ttk.Button # ID of the currently chosen item self.chosen_id = None # Callback function, and positional arugments to pass if callback is not None: self.callback = callback self.callback_params = list(callback_params) else: self.callback = None self.callback_params = () # Item object for the currently suggested item. self.suggested = None # Should we have the 'reset to default' button? self.has_def = has_def self.description = desc self.readonly_description = readonly_desc if has_none: self.item_list = [self.noneItem] + lst else: self.item_list = lst try: self.selected = self.item_list[0] # type: Item except IndexError: LOGGER.error('No items for window "{}"!', title) # We crash without items, forcefully add the None item in so at # least this works. self.item_list = [self.noneItem] self.selected = self.noneItem self.orig_selected = self.selected self.parent = tk self._readonly = False self.modal = modal self.win = Toplevel(tk) self.win.withdraw() self.win.title("BEE2 - " + title) self.win.transient(master=tk) # Allow resizing in X and Y. self.win.resizable(True, True) tk_tools.set_window_icon(self.win) # Run our quit command when the exit button is pressed, or Escape # on the keyboard. self.win.protocol("WM_DELETE_WINDOW", self.exit) self.win.bind("<Escape>", self.exit) # Allow navigating with arrow keys. self.win.bind("<KeyPress>", self.key_navigate) # A map from group name -> header widget self.group_widgets = {} # A map from folded name -> display name self.group_names = {} self.grouped_items = defaultdict(list) # A list of folded group names in the display order. self.group_order = [] # The maximum number of items that fits per row (set in flow_items) self.item_width = 1 if desc: self.desc_label = ttk.Label( self.win, text=desc, justify=LEFT, anchor=W, width=5, # Keep a small width, so this doesn't affect the # initial window size. ) self.desc_label.grid(row=0, column=0, sticky='EW') # PanedWindow allows resizing the two areas independently. self.pane_win = PanedWindow( self.win, orient=HORIZONTAL, sashpad=2, # Padding above/below panes sashwidth=3, # Width of border sashrelief=RAISED, # Raise the border between panes ) self.pane_win.grid(row=1, column=0, sticky="NSEW") self.win.columnconfigure(0, weight=1) self.win.rowconfigure(1, weight=1) shim = ttk.Frame(self.pane_win, relief="sunken") shim.rowconfigure(0, weight=1) shim.columnconfigure(0, weight=1) # We need to use a canvas to allow scrolling. self.wid_canvas = Canvas(shim, highlightthickness=0) self.wid_canvas.grid(row=0, column=0, sticky="NSEW") # Add another frame inside to place labels on. self.pal_frame = ttk.Frame(self.wid_canvas) self.wid_canvas.create_window(1, 1, window=self.pal_frame, anchor="nw") self.wid_scroll = tk_tools.HidingScroll( shim, orient=VERTICAL, command=self.wid_canvas.yview, ) self.wid_scroll.grid(row=0, column=1, sticky="NS") self.wid_canvas['yscrollcommand'] = self.wid_scroll.set utils.add_mousewheel(self.wid_canvas, self.win) if utils.MAC: # Labelframe doesn't look good here on OSX self.sugg_lbl = ttk.Label( self.pal_frame, # Draw lines with box drawing characters text="\u250E\u2500" + _("Suggested") + "\u2500\u2512", ) else: self.sugg_lbl = ttk.LabelFrame( self.pal_frame, text=_("Suggested"), labelanchor=N, height=50, ) # Holds all the widgets which provide info for the current item. self.prop_frm = ttk.Frame(self.pane_win, borderwidth=4, relief='raised') self.prop_frm.columnconfigure(1, weight=1) # Border around the selected item icon. width, height = img.tuple_size(ICON_SIZE_LRG) self.prop_icon_frm = ttk.Frame( self.prop_frm, borderwidth=4, relief='raised', width=width, height=height, ) self.prop_icon_frm.grid(row=0, column=0, columnspan=4) self.prop_icon = ttk.Label( self.prop_icon_frm, image=img.color_square(img.PETI_ITEM_BG, ICON_SIZE_LRG), ) self.prop_icon.grid(row=0, column=0) name_frame = ttk.Frame(self.prop_frm) self.prop_name = ttk.Label( name_frame, text="Item", justify=CENTER, font=("Helvetica", 12, "bold"), ) name_frame.grid(row=1, column=0, columnspan=4) name_frame.columnconfigure(0, weight=1) self.prop_name.grid(row=0, column=0) # For music items, add a '>' button to play sound samples if sound_sys is not None and sound.initiallised: self.samp_button = samp_button = ttk.Button( name_frame, text=BTN_PLAY, width=1, ) samp_button.grid(row=0, column=1) add_tooltip( samp_button, _("Play a sample of this item."), ) def set_samp_play(): samp_button['text'] = BTN_PLAY def set_samp_stop(): samp_button['text'] = BTN_STOP self.sampler = sound.SamplePlayer( stop_callback=set_samp_play, start_callback=set_samp_stop, system=sound_sys, ) samp_button['command'] = self.sampler.play_sample utils.bind_leftclick(self.prop_icon, self.sampler.play_sample) samp_button.state(('disabled', )) else: self.sampler = None # If we have a sound sampler, hold the system open while the window # is so it doesn't snap open/closed while finding files. self.sampler_held_open = False self.prop_author = ttk.Label(self.prop_frm, text="Author") self.prop_author.grid(row=2, column=0, columnspan=4) self.prop_desc_frm = ttk.Frame(self.prop_frm, relief="sunken") self.prop_desc_frm.grid(row=4, column=0, columnspan=4, sticky="NSEW") self.prop_desc_frm.rowconfigure(0, weight=1) self.prop_desc_frm.columnconfigure(0, weight=1) self.prop_frm.rowconfigure(4, weight=1) self.prop_desc = tkRichText( self.prop_desc_frm, width=40, height=4, font="TkSmallCaptionFont", ) self.prop_desc.grid( row=0, column=0, padx=(2, 0), pady=2, sticky='NSEW', ) self.prop_scroll = tk_tools.HidingScroll( self.prop_desc_frm, orient=VERTICAL, command=self.prop_desc.yview, ) self.prop_scroll.grid( row=0, column=1, sticky="NS", padx=(0, 2), pady=2, ) self.prop_desc['yscrollcommand'] = self.prop_scroll.set ttk.Button( self.prop_frm, text=_("OK"), command=self.save, ).grid( row=6, column=0, padx=(8, 8), ) if self.has_def: self.prop_reset = ttk.Button( self.prop_frm, text=_("Reset to Default"), command=self.sel_suggested, ) self.prop_reset.grid( row=6, column=1, sticky='EW', ) ttk.Button( self.prop_frm, text=_("Cancel"), command=self.exit, ).grid( row=6, column=2, padx=(8, 8), ) self.win.option_add('*tearOff', False) self.context_menu = Menu(self.win) self.norm_font = tk_font.nametofont('TkMenuFont') # Make a font for showing suggested items in the context menu self.sugg_font = self.norm_font.copy() self.sugg_font['weight'] = tk_font.BOLD # Make a font for previewing the suggested item self.mouseover_font = self.norm_font.copy() self.mouseover_font['slant'] = tk_font.ITALIC self.context_var = IntVar() # The headers for the context menu self.context_menus = {} # Sort alphabetically, preferring a sort key if present. self.item_list.sort(key=lambda it: it.sort_key or it.longName) for ind, item in enumerate(self.item_list): item._selector = self if item == self.noneItem: item.button = ttk.Button( self.pal_frame, image=item.icon, ) item.context_lbl = none_name else: item.button = ttk.Button( self.pal_frame, text=item.shortName, image=item.icon, compound='top', ) group_key = item.group.casefold() self.grouped_items[group_key].append(item) if group_key not in self.group_names: # If the item is groupless, use 'Other' for the header. self.group_names[group_key] = item.group or _('Other') if not item.group: # Ungrouped items appear directly in the menu. menu = self.context_menus[''] = self.context_menu else: try: menu = self.context_menus[group_key] except KeyError: self.context_menus[group_key] = menu = Menu( self.context_menu, ) menu.add_radiobutton( label=item.context_lbl, command=functools.partial(self.sel_item_id, item.name), var=self.context_var, value=ind, ) item._context_ind = len(self.grouped_items[group_key]) - 1 @utils.bind_leftclick(item.button) def click_item(event=None, *, _item=item): """Handle clicking on the item. If it's already selected, save and close the window. """ # We need to capture the item in a default, since it's # the same variable in different iterations if _item is self.selected: self.save() else: self.sel_item(_item) # Convert to a normal dictionary, after adding all items. self.grouped_items = dict(self.grouped_items) # Figure out the order for the groups - alphabetical. # Note - empty string should sort to the beginning! self.group_order[:] = sorted(self.grouped_items.keys()) for index, (key, menu) in enumerate( sorted(self.context_menus.items(), key=itemgetter(0)), # We start with the ungrouped items, so increase the index # appropriately. start=len(self.grouped_items.get('', ()))): if key == '': # Don't add the ungrouped menu to itself! continue self.context_menu.add_cascade( menu=menu, label=self.group_names[key], ) # Set a custom attribute to keep track of the menu's index. menu._context_index = index for group_key, text in self.group_names.items(): self.group_widgets[group_key] = GroupHeader( self, text, ) self.pane_win.add(shim) self.pane_win.add(self.prop_frm) # Force a minimum size for the two parts self.pane_win.paneconfigure(shim, minsize=100, stretch='always') self.prop_frm.update_idletasks() # Update reqwidth() self.pane_win.paneconfigure( self.prop_frm, minsize=200, stretch='never', ) if attributes: attr_frame = ttk.Frame(self.prop_frm) attr_frame.grid( row=5, column=0, columnspan=3, sticky=EW, ) self.attr = {} # Add in all the attribute labels for index, attr in enumerate(attributes): desc_label = ttk.Label( attr_frame, text=attr.desc, ) self.attr[attr.id] = val_label = ttk.Label(attr_frame, ) val_label.default = attr.default val_label.type = attr.type if attr.type is AttrTypes.BOOL: # It's a tick/cross label val_label['image'] = (ICON_CHECK if attr.default else ICON_CROSS, ) elif attr.type is AttrTypes.COLOR: # A small colour swatch. val_label.configure(relief=RAISED, ) # Show the color value when hovered. add_tooltip(val_label) # Position in a 2-wide grid desc_label.grid( row=index // 2, column=(index % 2) * 2, sticky=E, ) val_label.grid( row=index // 2, column=(index % 2) * 2 + 1, sticky=W, ) else: self.attr = self.desc_label = None self.flow_items() self.wid_canvas.bind("<Configure>", self.flow_items)
def init(start_open: bool, log_level: str = 'info') -> None: """Initialise the window.""" global log_handler, text_box, level_selector window.columnconfigure(0, weight=1) window.rowconfigure(0, weight=1) window.title(_('Logs - {}').format(utils.BEE_VERSION)) window.protocol('WM_DELETE_WINDOW', lambda: set_visible(False)) text_box = tk.Text( window, name='text_box', width=50, height=15, ) text_box.grid(row=0, column=0, sticky='NSEW') log_level = logging.getLevelName(log_level.upper()) log_handler = TextHandler(text_box) try: log_handler.setLevel(log_level) except ValueError: log_level = logging.INFO log_handler.setFormatter( logging.Formatter( # One letter for level name '[{levelname[0]}] {module}.{funcName}(): {message}', style='{', )) logging.getLogger().addHandler(log_handler) scroll = tk_tools.HidingScroll( window, name='scroll', orient=tk.VERTICAL, command=text_box.yview, ) scroll.grid(row=0, column=1, sticky='NS') text_box['yscrollcommand'] = scroll.set 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='Clear', command=btn_clear, ).grid(row=0, column=0) ttk.Button( button_frame, name='copy_btn', text=_('Copy'), command=btn_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=_('Show:'), anchor='e', justify='right', ).grid(row=0, column=0, sticky='E') level_selector = ttk.Combobox( sel_frame, name='level_selector', values=[LVL_TEXT[level] for level in BOX_LEVELS], exportselection=0, # On Mac this defaults to being way too wide! width=15 if utils.MAC else None, ) level_selector.state(['readonly']) # Prevent directly typing in values level_selector.bind('<<ComboboxSelected>>', set_level) level_selector.current(BOX_LEVELS.index(log_level)) level_selector.grid(row=0, column=1, sticky='E') sel_frame.columnconfigure(1, weight=1) utils.add_mousewheel(text_box, window, sel_frame, button_frame) if utils.USE_SIZEGRIP: ttk.Sizegrip(button_frame).grid(row=0, column=3) if start_open: window.deiconify() window.lift() # Force an update, we're busy with package extraction... window.update() else: window.withdraw()
def make_pane(tool_frame): """Create the styleVar pane. """ global window window = SubPane( TK_ROOT, options=GEN_OPTS, title='Style Properties', name='style', resize_y=True, tool_frame=tool_frame, tool_img=png.png('icons/win_stylevar'), tool_col=3, ) UI['style_can'] = Canvas(window, highlightthickness=0) # need to use a canvas to allow scrolling UI['style_can'].grid(sticky='NSEW') window.rowconfigure(0, weight=1) UI['style_scroll'] = ttk.Scrollbar( window, orient=VERTICAL, command=UI['style_can'].yview, ) UI['style_scroll'].grid(column=1, row=0, rowspan=2, sticky="NS") UI['style_can']['yscrollcommand'] = UI['style_scroll'].set utils.add_mousewheel(UI['style_can'], window) canvas_frame = ttk.Frame(UI['style_can']) frame_all = ttk.Labelframe(canvas_frame, text="All:") frame_all.grid(row=0, sticky='EW') frm_chosen = ttk.Labelframe(canvas_frame, text="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="Other Styles:") frm_other.grid(row=3, sticky='EW') UI['stylevar_chosen_none'] = ttk.Label( frm_chosen, text='No Options!', font='TkMenuFont', justify='center', ) UI['stylevar_other_none'] = ttk.Label( frm_other, text='None!', font='TkMenuFont', justify='center', ) for pos, var in enumerate(styleOptions): # Add the special stylevars which apply to all styles tk_vars[var.id] = IntVar( value=GEN_OPTS.get_bool('StyleVar', var.id, var.default) ) checkbox_special[var.id] = ttk.Checkbutton( frame_all, variable=tk_vars[var.id], text=var.name, command=functools.partial(set_stylevar, var.id) ) checkbox_special[var.id].grid(row=pos, column=0, sticky="W", padx=3) tooltip.add_tooltip( checkbox_special[var.id], make_desc(var, is_hardcoded=True), ) for var in VAR_LIST: tk_vars[var.id] = IntVar(value=var.enabled) args = { 'variable': tk_vars[var.id], 'text': var.name, 'command': functools.partial(set_stylevar, var.id) } checkbox_chosen[var.id] = ttk.Checkbutton(frm_chosen, **args) checkbox_other[var.id] = ttk.Checkbutton(frm_other, **args) desc = make_desc(var) tooltip.add_tooltip( checkbox_chosen[var.id], desc, ) tooltip.add_tooltip( checkbox_other[var.id], desc, ) UI['style_can'].create_window(0, 0, window=canvas_frame, anchor="nw") UI['style_can'].update_idletasks() UI['style_can'].config( scrollregion=UI['style_can'].bbox(ALL), width=canvas_frame.winfo_reqwidth(), ) if utils.USE_SIZEGRIP: ttk.Sizegrip( window, cursor=utils.CURSORS['stretch_vert'], ).grid(row=1, column=0) UI['style_can'].bind('<Configure>', flow_stylevar)
def make_pane(tool_frame): """Create the styleVar pane. """ global window window = SubPane( TK_ROOT, options=BEE2_config.GEN_OPTS, title=_('Style/Item Properties'), name='style', resize_y=True, tool_frame=tool_frame, tool_img=img.png('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=_('Styles')) UI['style_can'] = Canvas(stylevar_frame, highlightthickness=0) # need to use a canvas to allow scrolling UI['style_can'].grid(sticky='NSEW') window.rowconfigure(0, weight=1) UI['style_scroll'] = ttk.Scrollbar( stylevar_frame, orient=VERTICAL, command=UI['style_can'].yview, ) UI['style_scroll'].grid(column=1, row=0, rowspan=2, sticky="NS") UI['style_can']['yscrollcommand'] = UI['style_scroll'].set utils.add_mousewheel(UI['style_can'], stylevar_frame) canvas_frame = ttk.Frame(UI['style_can']) frame_all = ttk.Labelframe(canvas_frame, text=_("All:")) frame_all.grid(row=0, sticky='EW') frm_chosen = ttk.Labelframe(canvas_frame, text=_("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=_("Other Styles:")) frm_other.grid(row=3, sticky='EW') UI['stylevar_chosen_none'] = ttk.Label( frm_chosen, text=_('No Options!'), font='TkMenuFont', justify='center', ) UI['stylevar_other_none'] = ttk.Label( frm_other, text=_('None!'), font='TkMenuFont', justify='center', ) 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': def cmd(): update_filter() checkbox_all[var.id]['command'] = cmd tooltip.add_tooltip( checkbox_all[var.id], make_desc(var, is_hardcoded=True), ) 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, ) UI['style_can'].create_window(0, 0, window=canvas_frame, anchor="nw") UI['style_can'].update_idletasks() UI['style_can'].config( scrollregion=UI['style_can'].bbox(ALL), width=canvas_frame.winfo_reqwidth(), ) if utils.USE_SIZEGRIP: ttk.Sizegrip( window, cursor=utils.CURSORS['stretch_vert'], ).grid(row=1, column=0) UI['style_can'].bind('<Configure>', flow_stylevar) item_config_frame = ttk.Frame(nbook) nbook.add(item_config_frame, text=_('Items')) itemconfig.make_pane(item_config_frame)
def make_pane(parent: ttk.Frame): """Create all the widgets we use.""" if not CONFIG_ORDER: # No configs at all... ttk.Label(parent, text=_('No Item Configuration!')).pack(fill='both') return 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 utils.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) for conf_row, config in enumerate(CONFIG_ORDER): 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) label = ttk.Label(wid_frame, text=wid.name + ': ') label.grid(row=0, column=0) widget = wid.create_func(wid_frame, wid.values, wid.config) 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(start_open, log_level='info'): """Initialise the window.""" global window, log_handler, text_box, level_selector window = tk.Toplevel(TK_ROOT, name='console_win') window.withdraw() window.columnconfigure(0, weight=1) window.rowconfigure(0, weight=1) window.title('Logs ' + utils.BEE_VERSION) window.protocol('WM_DELETE_WINDOW', lambda: set_visible(False)) text_box = tk.Text( window, name='text_box', width=50, height=15, ) text_box.grid(row=0, column=0, sticky='NSEW') log_level = logging.getLevelName(log_level.upper()) log_handler = TextHandler(text_box) try: log_handler.setLevel(log_level) except ValueError: log_level = logging.INFO log_handler.setFormatter(utils.short_log_format) LOGGER.addHandler(log_handler) scroll = tk_tools.HidingScroll( window, name='scroll', orient=tk.VERTICAL, command=text_box.yview, ) scroll.grid(row=0, column=1, sticky='NS') text_box['yscrollcommand'] = scroll.set 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='Clear', command=btn_clear, ).grid(row=0, column=0) ttk.Button( button_frame, name='copy_btn', text='Copy', command=btn_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='Show:', anchor='e', justify='right', ).grid(row=0, column=0, sticky='E') level_selector = ttk.Combobox( sel_frame, name='level_selector', values=[ LVL_TEXT[level] for level in BOX_LEVELS ], exportselection=0, # On Mac this defaults to being way too wide! width=15 if utils.MAC else None, ) level_selector.state(['readonly']) # Prevent directly typing in values level_selector.bind('<<ComboboxSelected>>', set_level) level_selector.current(BOX_LEVELS.index(log_level)) level_selector.grid(row=0, column=1, sticky='E') sel_frame.columnconfigure(1, weight=1) utils.add_mousewheel(text_box, window, sel_frame, button_frame) if utils.USE_SIZEGRIP: ttk.Sizegrip(button_frame).grid(row=0, column=3) if start_open: window.deiconify() window.lift() else: window.withdraw()
def __init__( self, tk, lst, has_none=True, has_def=True, none_desc=(('line', 'Do not add anything.'),), none_attrs: dict=utils.EmptyMapping, title='BEE2', callback=_NO_OP, callback_params=(), attributes=(), ): """Create a window object. Read from .selected_id to get the currently-chosen Item name, or None if the <none> Item is selected. Args: - tk: Must be a Toplevel window, either the tk() root or another window if needed. - lst: A list of Item objects, defining the visible items. - If has_none is True, a <none> item will be added to the beginning of the list. - If has_def is True, the 'Reset to Default' button will appear, which resets to the suggested item. - none_desc holds an optional description for the <none> Item, which can be used to describe what it results in. - title is the title of the selector window. - callback is a function to be called whenever the selected item changes. - callback_params is a list of additional values which will be passed to the callback function. The first arguement to the callback is always the selected item ID. - full_context controls if the short or long names are used for the context menu. - attributes is a list of AttrDef tuples. Each tuple should contain an ID, display text, and default value. If the values are True or False a check/cross will be displayed, otherwise they're a string. """ self.noneItem = Item( 'NONE', '', desc=none_desc, attributes=dict(none_attrs), ) self.noneItem.icon = img.png('BEE2/none_96') self.disp_label = StringVar() self.display = None self.disp_btn = None self.chosen_id = None self.callback = callback self.callback_params = callback_params self.suggested = None self.has_def = has_def if has_none: self.item_list = [self.noneItem] + lst else: self.item_list = lst self.selected = self.item_list[0] self.orig_selected = self.selected self.parent = tk self._readonly = False self.win = Toplevel(tk) self.win.withdraw() self.win.title("BEE2 - " + title) self.win.transient(master=tk) self.win.resizable(True, True) self.win.iconbitmap('../BEE2.ico') self.win.protocol("WM_DELETE_WINDOW", self.exit) self.win.bind("<Escape>", self.exit) self.win.columnconfigure(0, weight=1) self.win.rowconfigure(0, weight=1) # PanedWindow allows resizing the two areas independently. self.pane_win = PanedWindow( self.win, orient=HORIZONTAL, sashpad=2, # Padding above/below panes sashwidth=3, # Width of border sashrelief=RAISED, # Raise the border between panes ) self.pane_win.grid(row=0, column=0, sticky="NSEW") self.win.columnconfigure(0, weight=1) self.win.rowconfigure(0, weight=1) self.wid = {} shim = ttk.Frame(self.pane_win, relief="sunken") shim.rowconfigure(0, weight=1) shim.columnconfigure(0, weight=1) # We need to use a canvas to allow scrolling. self.wid_canvas = Canvas(shim, highlightthickness=0) self.wid_canvas.grid(row=0, column=0, sticky="NSEW") # Add another frame inside to place labels on. self.pal_frame = ttk.Frame(self.wid_canvas) self.wid_canvas.create_window(1, 1, window=self.pal_frame, anchor="nw") self.wid_scroll = tk_tools.HidingScroll( shim, orient=VERTICAL, command=self.wid_canvas.yview, ) self.wid_scroll.grid(row=0, column=1, sticky="NS") self.wid_canvas['yscrollcommand'] = self.wid_scroll.set utils.add_mousewheel(self.wid_canvas, self.win) if utils.MAC: # Labelframe doesn't look good here on OSX self.sugg_lbl = ttk.Label( self.pal_frame, # Draw lines with box drawing characters text="\u250E\u2500Suggested\u2500\u2512" ) else: self.sugg_lbl = ttk.LabelFrame( self.pal_frame, text="Suggested", labelanchor=N, height=50, ) # Holds all the widgets which provide info for the current item. self.prop_frm = ttk.Frame(self.pane_win, borderwidth=4, relief='raised') self.prop_frm.columnconfigure(1, weight=1) # Border around the selected item icon. self.prop_icon_frm = ttk.Frame( self.prop_frm, borderwidth=4, relief='raised', width=ICON_SIZE, height=ICON_SIZE, ) self.prop_icon_frm.grid(row=0, column=0, columnspan=4) self.prop_icon = ttk.Label(self.prop_icon_frm) self.prop_icon.img = img.png('BEE2/blank_96') self.prop_icon['image'] = self.prop_icon.img self.prop_icon.grid(row=0, column=0) self.prop_name = ttk.Label( self.prop_frm, text="Item", justify=CENTER, font=("Helvetica", 12, "bold"), ) self.prop_name.grid(row=1, column=0, columnspan=4) self.prop_author = ttk.Label(self.prop_frm, text="Author") self.prop_author.grid(row=2, column=0, columnspan=4) self.prop_desc_frm = ttk.Frame(self.prop_frm, relief="sunken") self.prop_desc_frm.grid(row=4, column=0, columnspan=4, sticky="NSEW") self.prop_desc_frm.rowconfigure(0, weight=1) self.prop_desc_frm.columnconfigure(0, weight=1) self.prop_frm.rowconfigure(4, weight=1) self.prop_desc = tkRichText( self.prop_desc_frm, width=40, height=4, font="TkSmallCaptionFont", ) self.prop_desc.grid( row=0, column=0, padx=(2, 0), pady=2, sticky='NSEW', ) self.prop_scroll = tk_tools.HidingScroll( self.prop_desc_frm, orient=VERTICAL, command=self.prop_desc.yview, ) self.prop_scroll.grid( row=0, column=1, sticky="NS", padx=(0, 2), pady=2, ) self.prop_desc['yscrollcommand'] = self.prop_scroll.set ttk.Button( self.prop_frm, text="OK", command=self.save, ).grid( row=6, column=0, padx=(8, 8), ) if self.has_def: self.prop_reset = ttk.Button( self.prop_frm, text="Reset to Default", command=self.sel_suggested, ) self.prop_reset.grid( row=6, column=1, sticky='EW', ) ttk.Button( self.prop_frm, text="Cancel", command=self.exit, ).grid( row=6, column=2, padx=(8, 8), ) self.win.option_add('*tearOff', False) self.context_menu = Menu(self.win) self.norm_font = font.nametofont('TkMenuFont') # Make a font for showing suggested items in the context menu self.sugg_font = self.norm_font.copy() self.sugg_font['weight'] = font.BOLD # Make a font for previewing the suggested item self.mouseover_font = self.norm_font.copy() self.mouseover_font['slant'] = font.ITALIC self.context_var = IntVar() # Re-order items appropriately. # self.item_list.sort( # Alphabetical order key=lambda it: it.longName ) self.item_list.sort( # Sort by group name key=lambda it: it.group.casefold() if it.group else '' ) self.item_list.sort( # Make non-grouped items go first key=lambda it: 2 if it.group else 1 ) for ind, item in enumerate(self.item_list): if item == self.noneItem: item.button = ttk.Button( self.pal_frame, image=item.icon, ) item.context_lbl = '<None>' else: item.button = ttk.Button( self.pal_frame, text=item.shortName, image=item.icon, compound='top', ) self.context_menu.add_radiobutton( label=item.context_lbl, command=functools.partial(self.sel_item_id, item.name), var=self.context_var, value=ind, ) item.win = self.win utils.bind_leftclick( item.button, functools.partial(self.sel_item, item), ) utils.bind_leftclick_double( item.button, self.save, ) self.flow_items(None) self.wid_canvas.bind("<Configure>", self.flow_items) self.pane_win.add(shim) self.pane_win.add(self.prop_frm) # Force a minimum size for the two parts self.pane_win.paneconfigure(shim, minsize=100, stretch='always') self.prop_frm.update_idletasks() # Update reqwidth() self.pane_win.paneconfigure( self.prop_frm, minsize=200, stretch='never', ) if attributes: attr_frame = ttk.Frame(self.prop_frm) attr_frame.grid( row=5, column=0, columnspan=3, sticky=EW, ) self.attr = {} # Add in all the attribute labels for index, (at_id, desc, value) in enumerate(attributes): desc_label = ttk.Label( attr_frame, text=desc, ) self.attr[at_id] = val_label = ttk.Label( attr_frame, ) val_label.default = value if isinstance(value, bool): # It's a tick/cross label val_label['image'] = ( ICON_CHECK if value else ICON_CROSS, ) # Position in a 2-wide grid desc_label.grid( row=index // 2, column=(index % 2)*2, sticky=E, ) val_label.grid( row=index // 2, column=(index % 2)*2 + 1, sticky=W, ) else: self.attr = None
def init_widgets(master: ttk.Frame) -> Optional[tk.Misc]: """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.protocol("WM_DELETE_WINDOW", window.withdraw) window.resizable(True, True) window.title(_('Configure Signage')) frame_selected = ttk.Labelframe( window, text=_('Selected'), relief='raised', labelanchor='n', ) canv_all = tk.Canvas(window) scroll = 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='horiz').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) utils.add_mousewheel(canv_all, canv_all, window) blank_sign = img.color_square(img.PETI_ITEM_BG, 64) preview_left = ttk.Label(frame_preview, image=blank_sign, anchor='e') preview_right = ttk.Label(frame_preview, image=blank_sign, anchor='w') 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: preview_left['image'] = hover_sign.dnd_icon preview_right['image'] = sign_arrow.dnd_icon else: try: preview_left['image'] = Signage.by_id(hover_sign.prim_id or '').dnd_icon except KeyError: preview_left['image'] = hover_sign.dnd_icon try: preview_right['image'] = Signage.by_id(hover_sign.sec_id or '').dnd_icon except KeyError: preview_right['image'] = blank_sign 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'] = _('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 preview_left['image'] = blank_sign preview_right['image'] = blank_sign 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 show_window() -> None: """Show the window.""" window.deiconify() utils.center_win(window, TK_ROOT) return ttk.Button( master, text=_('Configure Signage'), command=show_window, )
def make_pane(tool_frame): """Create the styleVar pane. """ global window window = SubPane( TK_ROOT, options=GEN_OPTS, title=_('Style Properties'), name='style', resize_y=True, tool_frame=tool_frame, tool_img=png.png('icons/win_stylevar'), tool_col=3, ) UI['style_can'] = Canvas(window, highlightthickness=0) # need to use a canvas to allow scrolling UI['style_can'].grid(sticky='NSEW') window.rowconfigure(0, weight=1) UI['style_scroll'] = ttk.Scrollbar( window, orient=VERTICAL, command=UI['style_can'].yview, ) UI['style_scroll'].grid(column=1, row=0, rowspan=2, sticky="NS") UI['style_can']['yscrollcommand'] = UI['style_scroll'].set utils.add_mousewheel(UI['style_can'], window) canvas_frame = ttk.Frame(UI['style_can']) frame_all = ttk.Labelframe(canvas_frame, text=_("All:")) frame_all.grid(row=0, sticky='EW') frm_chosen = ttk.Labelframe(canvas_frame, text=_("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=_("Other Styles:")) frm_other.grid(row=3, sticky='EW') UI['stylevar_chosen_none'] = ttk.Label( frm_chosen, text=_('No Options!'), font='TkMenuFont', justify='center', ) UI['stylevar_other_none'] = ttk.Label( frm_other, text=_('None!'), font='TkMenuFont', justify='center', ) all_pos = 0 for all_pos, var in enumerate(styleOptions): # Add the special stylevars which apply to all styles tk_vars[var.id] = IntVar( value=GEN_OPTS.get_bool('StyleVar', var.id, var.default)) checkbox_all[var.id] = ttk.Checkbutton(frame_all, variable=tk_vars[var.id], text=var.name, command=functools.partial( set_stylevar, var.id)) checkbox_all[var.id].grid(row=all_pos, column=0, sticky="W", padx=3) tooltip.add_tooltip( checkbox_all[var.id], make_desc(var, is_hardcoded=True), ) for var in VAR_LIST: tk_vars[var.id] = IntVar(value=var.enabled) args = { 'variable': tk_vars[var.id], 'text': var.name, 'command': functools.partial(set_stylevar, var.id) } 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, ) UI['style_can'].create_window(0, 0, window=canvas_frame, anchor="nw") UI['style_can'].update_idletasks() UI['style_can'].config( scrollregion=UI['style_can'].bbox(ALL), width=canvas_frame.winfo_reqwidth(), ) if utils.USE_SIZEGRIP: ttk.Sizegrip( window, cursor=utils.CURSORS['stretch_vert'], ).grid(row=1, column=0) UI['style_can'].bind('<Configure>', flow_stylevar)
) self.sort_ind = index self.items.sort( key=lambda item: item.values[index], reverse=self.rev_sort, ) self.refresh() if __name__ == '__main__': root = tk_tools.TK_ROOT test_inst = CheckDetails( parent=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') utils.add_mousewheel(test_inst.wid_canvas, root) root.columnconfigure(0, weight=1) root.rowconfigure(0, weight=1) root.deiconify() root.mainloop()
def make_pane(parent: ttk.Frame): """Create all the widgets we use.""" if not CONFIG_ORDER: # No configs at all... ttk.Label(parent, text=_('No Item Configuration!')).pack(fill='both') return CONFIG_ORDER.sort(key=lambda grp: grp.name) parent.columnconfigure(0, weight=1) parent.rowconfigure(1, weight=1) item_frames = [] # type: List[ttk.Frame] def swap_to_item(e: tk.Event = None): """Swap what's shown in the pane.""" for frame in item_frames: frame.grid_forget() cur_dropdown = dropdown.current() if cur_dropdown == -1: # Not valid return # Block sound for the first few millisec to stop excess sounds from # playing during gridding. sound.block_fx() item_frames[cur_dropdown].grid(row=1, column=0, sticky='nsew') dropdown = ttk.Combobox( parent, state='readonly', exportselection=False, values=[group.name for group in CONFIG_ORDER], ) dropdown.grid(row=0, column=0, columnspan=2, sticky='ew') dropdown.bind('<<ComboboxSelected>>', swap_to_item) # need to use a canvas to allow scrolling canvas = tk.Canvas(parent, highlightthickness=0) canvas.grid(row=1, column=0, sticky='NSEW') parent.rowconfigure(1, weight=1) scrollbar = ttk.Scrollbar( parent, orient='vertical', command=canvas.yview, ) scrollbar.grid(column=1, row=1, sticky="ns") canvas['yscrollcommand'] = scrollbar.set utils.add_mousewheel(canvas, parent) canvas_frame = ttk.Frame(canvas) canvas.create_window(0, 0, window=canvas_frame, anchor="nw") canvas_frame.rowconfigure(0, weight=1) for config in CONFIG_ORDER: frame = ttk.Frame(canvas_frame) frame.columnconfigure(0, weight=1) item_frames.append(frame) # Now make the widgets. if config.widgets: non_timer_frame = ttk.LabelFrame(frame, text=_('General')) non_timer_frame.grid(row=0, column=0, sticky='ew') non_timer_frame.columnconfigure(1, weight=1) for row, wid in enumerate(config.widgets): label = ttk.Label(non_timer_frame, text=wid.name + ': ') label.grid(row=row, column=0) widget = wid.create_func(non_timer_frame, wid.values, wid.config) widget.grid(row=row, column=1, sticky='ew') if wid.tooltip: add_tooltip(widget, wid.tooltip) add_tooltip(label, 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 for row, wid in enumerate(config.multi_widgets): wid_frame = ttk.LabelFrame(frame, text=wid.name) wid_frame.grid(row=2 + row, column=0, sticky='ew') wid_frame.columnconfigure(1, weight=1) wid.multi_func( wid_frame, wid.values, wid.config, ) if wid.tooltip: add_tooltip(wid_frame, wid.tooltip) # Select the first item, so we show something. dropdown.current(0) swap_to_item() 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__(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 = [] 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, "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) self.wid_head_label = [0] * len(self.headers) self.wid_head_sort = [0] * len(self.headers) 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 utils.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) utils.add_mousewheel( self.wid_canvas, self.wid_canvas, self.wid_frame, self.wid_header, )
def __init__( self, tk, lst, has_none=True, has_def=True, has_snd_sample=False, none_desc=(('line', 'Do not add anything.'),), none_attrs: dict=utils.EmptyMapping, title='BEE2', desc='', readonly_desc='', callback: Callable[..., None]=None, callback_params=(), attributes=(), ): """Create a window object. Read from .selected_id to get the currently-chosen Item name, or None if the <none> Item is selected. Args: - tk: Must be a Toplevel window, either the tk() root or another window if needed. - lst: A list of Item objects, defining the visible items. - If has_none is True, a <none> item will be added to the beginning of the list. - If has_def is True, the 'Reset to Default' button will appear, which resets to the suggested item. - If has_snd_sample is True, a '>' button will appear next to names to play the associated audio sample for the item. - none_desc holds an optional description for the <none> Item, which can be used to describe what it results in. - title is the title of the selector window. - callback is a function to be called whenever the selected item changes. - callback_params is a list of additional values which will be passed to the callback function. The first arguement to the callback is always the selected item ID. - full_context controls if the short or long names are used for the context menu. - attributes is a list of AttrDef tuples. Each tuple should contain an ID, display text, and default value. If the values are True or False a check/cross will be displayed, otherwise they're a string. - desc is descriptive text to display on the window, and in the widget tooltip. - readonly_desc will be displayed on the widget tooltip when readonly. """ self.noneItem = Item( 'NONE', '', desc=none_desc, attributes=dict(none_attrs), ) self.noneItem.icon = img.png('BEE2/none_96') # The textbox on the parent window. self.display = None # type: tk_tools.ReadOnlyEntry # Variable associated with self.display. self.disp_label = StringVar() # The '...' button to open our window. self.disp_btn = None # type: ttk.Button # ID of the currently chosen item self.chosen_id = None # Callback function, and positional arugments to pass if callback is not None: self.callback = callback self.callback_params = list(callback_params) else: self.callback = None self.callback_params = () # Item object for the currently suggested item. self.suggested = None # Should we have the 'reset to default' button? self.has_def = has_def self.description = desc self.readonly_description = readonly_desc if has_none: self.item_list = [self.noneItem] + lst else: self.item_list = lst self.selected = self.item_list[0] # type: Item self.orig_selected = self.selected self.parent = tk self._readonly = False self.win = Toplevel(tk) self.win.withdraw() self.win.title("BEE2 - " + title) self.win.transient(master=tk) # Allow resizing in X and Y. self.win.resizable(True, True) self.win.iconbitmap('../BEE2.ico') # Run our quit command when the exit button is pressed, or Escape # on the keyboard. self.win.protocol("WM_DELETE_WINDOW", self.exit) self.win.bind("<Escape>", self.exit) # Allow navigating with arrow keys. self.win.bind("<KeyPress>", self.key_navigate) # A map from group name -> header widget self.group_widgets = {} # A map from folded name -> display name self.group_names = {} self.grouped_items = defaultdict(list) # A list of folded group names in the display order. self.group_order = [] # The maximum number of items that fits per row (set in flow_items) self.item_width = 1 if desc: self.desc_label = ttk.Label( self.win, text=desc, justify=LEFT, anchor=W, width=5, # Keep a small width, so this doesn't affect the # initial window size. ) self.desc_label.grid(row=0, column=0, sticky='EW') # PanedWindow allows resizing the two areas independently. self.pane_win = PanedWindow( self.win, orient=HORIZONTAL, sashpad=2, # Padding above/below panes sashwidth=3, # Width of border sashrelief=RAISED, # Raise the border between panes ) self.pane_win.grid(row=1, column=0, sticky="NSEW") self.win.columnconfigure(0, weight=1) self.win.rowconfigure(1, weight=1) self.wid = {} shim = ttk.Frame(self.pane_win, relief="sunken") shim.rowconfigure(0, weight=1) shim.columnconfigure(0, weight=1) # We need to use a canvas to allow scrolling. self.wid_canvas = Canvas(shim, highlightthickness=0) self.wid_canvas.grid(row=0, column=0, sticky="NSEW") # Add another frame inside to place labels on. self.pal_frame = ttk.Frame(self.wid_canvas) self.wid_canvas.create_window(1, 1, window=self.pal_frame, anchor="nw") self.wid_scroll = tk_tools.HidingScroll( shim, orient=VERTICAL, command=self.wid_canvas.yview, ) self.wid_scroll.grid(row=0, column=1, sticky="NS") self.wid_canvas['yscrollcommand'] = self.wid_scroll.set utils.add_mousewheel(self.wid_canvas, self.win) if utils.MAC: # Labelframe doesn't look good here on OSX self.sugg_lbl = ttk.Label( self.pal_frame, # Draw lines with box drawing characters text="\u250E\u2500Suggested\u2500\u2512" ) else: self.sugg_lbl = ttk.LabelFrame( self.pal_frame, text="Suggested", labelanchor=N, height=50, ) # Holds all the widgets which provide info for the current item. self.prop_frm = ttk.Frame(self.pane_win, borderwidth=4, relief='raised') self.prop_frm.columnconfigure(1, weight=1) # Border around the selected item icon. self.prop_icon_frm = ttk.Frame( self.prop_frm, borderwidth=4, relief='raised', width=ICON_SIZE, height=ICON_SIZE, ) self.prop_icon_frm.grid(row=0, column=0, columnspan=4) self.prop_icon = ttk.Label(self.prop_icon_frm) self.prop_icon.img = img.png('BEE2/blank_96') self.prop_icon['image'] = self.prop_icon.img self.prop_icon.grid(row=0, column=0) name_frame = ttk.Frame(self.prop_frm) self.prop_name = ttk.Label( name_frame, text="Item", justify=CENTER, font=("Helvetica", 12, "bold"), ) name_frame.grid(row=1, column=0, columnspan=4) name_frame.columnconfigure(0, weight=1) self.prop_name.grid(row=0, column=0) # For music items, add a '>' button to play sound samples if has_snd_sample and sound.initiallised: self.samp_button = samp_button = ttk.Button( name_frame, text=BTN_PLAY, width=1, ) samp_button.grid(row=0, column=1) add_tooltip( samp_button, "Play a sample of this item.", ) def set_samp_play(): samp_button['text'] = BTN_PLAY def set_samp_stop(): samp_button['text'] = BTN_STOP self.sampler = sound.SamplePlayer( stop_callback=set_samp_play, start_callback=set_samp_stop, ) samp_button['command'] = self.sampler.play_sample utils.bind_leftclick(self.prop_icon, self.sampler.play_sample) samp_button.state(('disabled',)) else: self.sampler = None self.prop_author = ttk.Label(self.prop_frm, text="Author") self.prop_author.grid(row=2, column=0, columnspan=4) self.prop_desc_frm = ttk.Frame(self.prop_frm, relief="sunken") self.prop_desc_frm.grid(row=4, column=0, columnspan=4, sticky="NSEW") self.prop_desc_frm.rowconfigure(0, weight=1) self.prop_desc_frm.columnconfigure(0, weight=1) self.prop_frm.rowconfigure(4, weight=1) self.prop_desc = tkRichText( self.prop_desc_frm, width=40, height=4, font="TkSmallCaptionFont", ) self.prop_desc.grid( row=0, column=0, padx=(2, 0), pady=2, sticky='NSEW', ) self.prop_scroll = tk_tools.HidingScroll( self.prop_desc_frm, orient=VERTICAL, command=self.prop_desc.yview, ) self.prop_scroll.grid( row=0, column=1, sticky="NS", padx=(0, 2), pady=2, ) self.prop_desc['yscrollcommand'] = self.prop_scroll.set ttk.Button( self.prop_frm, text="OK", command=self.save, ).grid( row=6, column=0, padx=(8, 8), ) if self.has_def: self.prop_reset = ttk.Button( self.prop_frm, text="Reset to Default", command=self.sel_suggested, ) self.prop_reset.grid( row=6, column=1, sticky='EW', ) ttk.Button( self.prop_frm, text="Cancel", command=self.exit, ).grid( row=6, column=2, padx=(8, 8), ) self.win.option_add('*tearOff', False) self.context_menu = Menu(self.win) self.norm_font = tk_font.nametofont('TkMenuFont') # Make a font for showing suggested items in the context menu self.sugg_font = self.norm_font.copy() self.sugg_font['weight'] = tk_font.BOLD # Make a font for previewing the suggested item self.mouseover_font = self.norm_font.copy() self.mouseover_font['slant'] = tk_font.ITALIC self.context_var = IntVar() # The headers for the context menu self.context_menus = {} # Sort alphabetically, prefering a sort key if present. self.item_list.sort(key=lambda it: it.sort_key or it.longName) for ind, item in enumerate(self.item_list): # type: int, Item if item == self.noneItem: item.button = ttk.Button( self.pal_frame, image=item.icon, ) item.context_lbl = '<None>' else: item.button = ttk.Button( self.pal_frame, text=item.shortName, image=item.icon, compound='top', ) group_key = item.group.casefold() self.grouped_items[group_key].append(item) if group_key not in self.group_names: # If the item is groupless, use 'Other' for the header. self.group_names[group_key] = item.group or 'Other' if not item.group: # Ungrouped items appear directly in the menu. menu = self.context_menu else: try: menu = self.context_menus[group_key] except KeyError: self.context_menus[group_key] = menu = Menu( self.context_menu, ) menu.add_radiobutton( label=item.context_lbl, command=functools.partial(self.sel_item_id, item.name), var=self.context_var, value=ind, ) item.win = self.win utils.bind_leftclick( item.button, functools.partial(self.sel_item, item), ) utils.bind_leftclick_double( item.button, self.save, ) # Convert to a normal dictionary, after adding all items. self.grouped_items = dict(self.grouped_items) # Figure out the order for the groups - alphabetical. # Note - empty string should sort to the beginning! self.group_order[:] = sorted(self.grouped_items.keys()) for index, (key, menu) in enumerate( sorted(self.context_menus.items(), key=itemgetter(0)), # We start with the ungrouped items, so increase the index # appropriately. start=len(self.grouped_items.get('', ()))): self.context_menu.add_cascade( menu=menu, label=self.group_names[key], ) # Set a custom attribute to keep track of the menu's index. menu.index = index for group_key, text in self.group_names.items(): self.group_widgets[group_key] = GroupHeader( self, text, ) self.group_widgets[group_key].should_show = True self.flow_items(None) self.wid_canvas.bind("<Configure>", self.flow_items) self.pane_win.add(shim) self.pane_win.add(self.prop_frm) # Force a minimum size for the two parts self.pane_win.paneconfigure(shim, minsize=100, stretch='always') self.prop_frm.update_idletasks() # Update reqwidth() self.pane_win.paneconfigure( self.prop_frm, minsize=200, stretch='never', ) if attributes: attr_frame = ttk.Frame(self.prop_frm) attr_frame.grid( row=5, column=0, columnspan=3, sticky=EW, ) self.attr = {} # Add in all the attribute labels for index, attr in enumerate(attributes): desc_label = ttk.Label( attr_frame, text=attr.desc, ) self.attr[attr.id] = val_label = ttk.Label( attr_frame, ) val_label.default = attr.default val_label.type = attr.type if attr.type is AttrTypes.BOOL: # It's a tick/cross label val_label['image'] = ( ICON_CHECK if attr.default else ICON_CROSS, ) elif attr.type is AttrTypes.COLOR: # A small colour swatch. val_label.configure( relief=RAISED, ) # Show the color value when hovered. add_tooltip(val_label) # Position in a 2-wide grid desc_label.grid( row=index // 2, column=(index % 2) * 2, sticky=E, ) val_label.grid( row=index // 2, column=(index % 2) * 2 + 1, sticky=W, ) else: self.attr = None
def init_widgets(master: ttk.Frame) -> Optional[tk.Misc]: """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.protocol("WM_DELETE_WINDOW", window.withdraw) window.resizable(True, True) window.title(_('Configure Signage')) frame_selected = ttk.Labelframe( window, text=_('Selected'), relief='raised', labelanchor='n', ) canv_all = tk.Canvas(window) scroll = HidingScroll(window, orient='vertical', command=canv_all.yview) canv_all['yscrollcommand'] = scroll.set name_label = ttk.Label(window, text='', justify='center') frame_selected.grid(row=0, column=0, sticky='nsew') name_label.grid(row=1, column=0, sticky='ew') canv_all.grid(row=0, column=1, rowspan=2, sticky='nsew') scroll.grid(row=0, column=2, rowspan=2, sticky='ns') window.columnconfigure(1, weight=1) window.rowconfigure(0, weight=1) utils.add_mousewheel(canv_all, canv_all, window) def on_hover(slot: dragdrop.Slot[Signage]) -> None: name_label['text'] = (_('Signage: {}').format(slot.contents.name) if slot.contents is not None else '') def on_leave(slot: dragdrop.Slot[Signage]) -> None: name_label['text'] = '' 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 show_window() -> None: """Show the window.""" window.deiconify() utils.center_win(window, TK_ROOT) return ttk.Button( master, text=_('Configure Signage'), command=show_window, )
def init() -> None: """Initialise all widgets in the given window.""" for cat, btn_text in [ ('back_', _('Restore:')), ('game_', _('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=_('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=_('Delete Checked'), width=14, ) UI[cat + 'btn_del'].grid(row=1, column=0, columnspan=3) utils.add_mousewheel( UI[cat + 'details'].wid_canvas, UI[cat + 'frame'], ) UI['game_refresh'] = ttk.Button( UI['game_title_frame'], image=img.png('icons/tool_sub'), command=ui_refresh_game, ) UI['game_refresh'].grid(row=0, column=1, sticky='E') add_tooltip( UI['game_refresh'], "Reload the map list.", ) 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)
) 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') utils.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()