def add_tooltip( targ_widget: tk.Misc, text: str='', image: img.Handle=None, delay: int=500, show_when_disabled: bool=False, ) -> None: """Add a tooltip to the specified widget. delay is the amount of milliseconds of hovering needed to show the tooltip. text is the initial text for the tooltip. If set, image is also shown on the tooltip. If show_when_disabled is false, no context menu will be shown if the target widget is disabled. """ targ_widget._bee2_tooltip_text = text targ_widget._bee2_tooltip_img = image event_id = None # The id of the enter event, so we can cancel it. # Only check for disabled widgets if the widget actually has a state, # and the user hasn't disabled the functionality check_disabled = hasattr(targ_widget, 'instate') and not show_when_disabled def after_complete(x, y): """Remove the id and show the tooltip after the delay.""" nonlocal event_id event_id = None # Invalidate event id # noinspection PyUnresolvedReferences, PyProtectedMember if targ_widget._bee2_tooltip_text or targ_widget._bee2_tooltip_img is not None: _show(targ_widget, x, y) def enter_handler(event): """Schedule showing the tooltip.""" nonlocal event_id # noinspection PyUnresolvedReferences, PyProtectedMember if targ_widget._bee2_tooltip_text or targ_widget._bee2_tooltip_img is not None: # We know it has this method from above! # noinspection PyUnresolvedReferences if check_disabled and not targ_widget.instate(('!disabled',)): return event_id = TK_ROOT.after( delay, after_complete, event.x_root, event.y_root, ) def exit_handler(e): """When the user leaves, cancel the event.""" # We only want to cancel if the event hasn't expired already nonlocal event_id window.withdraw() if event_id is not None: TK_ROOT.after_cancel( event_id ) targ_widget.bind('<Enter>', enter_handler) targ_widget.bind('<Leave>', exit_handler)
def add_tooltip( targ_widget: tk.Misc, text: str='', image: tk.Image=None, delay: int=500, show_when_disabled: bool=False, ) -> None: """Add a tooltip to the specified widget. delay is the amount of milliseconds of hovering needed to show the tooltip. text is the initial text for the tooltip. If set, image is also shown on the tooltip. If show_when_disabled is false, no context menu will be shown if the target widget is disabled. """ targ_widget._bee2_tooltip_text = text targ_widget._bee2_tooltip_img = image event_id = None # The id of the enter event, so we can cancel it. # Only check for disabled widgets if the widget actually has a state, # and the user hasn't disabled the functionality check_disabled = hasattr(targ_widget, 'instate') and not show_when_disabled def after_complete(x, y): """Remove the id and show the tooltip after the delay.""" nonlocal event_id event_id = None # Invalidate event id # noinspection PyUnresolvedReferences, PyProtectedMember if targ_widget._bee2_tooltip_text or targ_widget._bee2_tooltip_img is not None: _show(targ_widget, x, y) def enter_handler(event): """Schedule showing the tooltip.""" nonlocal event_id # noinspection PyUnresolvedReferences, PyProtectedMember if targ_widget._bee2_tooltip_text or targ_widget._bee2_tooltip_img is not None: # We know it has this method from above! # noinspection PyUnresolvedReferences if check_disabled and not targ_widget.instate(('!disabled',)): return event_id = TK_ROOT.after( delay, after_complete, event.x_root, event.y_root, ) def exit_handler(e): """When the user leaves, cancel the event.""" # We only want to cancel if the event hasn't expired already nonlocal event_id window.withdraw() if event_id is not None: TK_ROOT.after_cancel( event_id ) targ_widget.bind('<Enter>', enter_handler) targ_widget.bind('<Leave>', exit_handler)
def __init__( self, parent: tk.Misc, *, tool_frame: tk.Frame, tool_img: str, menu_bar: tk.Menu, tool_col: int=0, title: str='', resize_x: bool=False, resize_y: bool=False, name: str='', ) -> None: self.visible = tk.BooleanVar(parent, True) self.win_name = name self.allow_snap = False self.can_save = False self.parent = parent self.relX = 0 self.relY = 0 self.can_resize_x = resize_x self.can_resize_y = resize_y super().__init__(parent, name='pane_' + name) self.withdraw() # Hide by default self.tool_button = make_tool_button( frame=tool_frame, img=tool_img, command=self._toggle_win, ) self.tool_button.state(('pressed',)) self.tool_button.grid( row=0, column=tool_col, # Contract the spacing to allow the icons to fit. padx=(2 if utils.MAC else (5, 2)), ) tooltip.add_tooltip( self.tool_button, text=gettext('Hide/Show the "{}" window.').format(title)) menu_bar.add_checkbutton( label=title, variable=self.visible, command=self._set_state_from_menu, ) self.transient(master=parent) self.resizable(resize_x, resize_y) self.title(title) tk_tools.set_window_icon(self) self.protocol("WM_DELETE_WINDOW", self.hide_win) parent.bind('<Configure>', self.follow_main, add=True) self.bind('<Configure>', self.snap_win) self.bind('<FocusIn>', self.enable_snap)
def __init__( self, widget: tkinter.Misc, sequence: str, func: Callable[[EventWithData], BreakOrNone] ) -> None: self._widget = widget self._sequence = sequence not_bound_commands = widget.bind(sequence) self._tcl_command = bind_with_data(widget, sequence, func, add=True) bound_commands = widget.bind(sequence) assert bound_commands.startswith(not_bound_commands) self._new_things = bound_commands[len(not_bound_commands) :]
def use_pygments_theme( widget: tkinter.Misc, callback: Optional[Callable[[str, str], None]] = None) -> None: """ Configure *widget* to use the colors of the Pygments theme whenever the currently selected theme changes (see :mod:`porcupine.settings`). Porcupine does that automatically for the ``textwidget`` of each :class:`~porcupine.tabs.FileTab`. If you don't specify a *callback*, then ``widget`` must be a :class:`tkinter.Text` widget. If you specify a callback, then it will be called like ``callback(foreground_color, background_color)``, and the type of the widget doesn't matter. .. seealso:: This function is used in :source:`porcupine/plugins/linenumbers.py`. Syntax highlighting is implemented in :source:`porcupine/plugins/highlight.py`. """ def on_style_changed(junk: object = None) -> None: style = styles.get_style_by_name(settings.get("pygments_style", str)) bg = style.background_color # yes, style.default_style can be '#rrggbb', '' or nonexistent # this is undocumented # # >>> from pygments.styles import * # >>> [getattr(get_style_by_name(name), 'default_style', '???') # ... for name in get_all_styles()] # ['', '', '', '', '', '', '???', '???', '', '', '', '', # '???', '???', '', '#cccccc', '', '', '???', '', '', '', '', # '#222222', '', '', '', '???', ''] fg = getattr(style, "default_style", "") or utils.invert_color(bg) if callback is None: assert isinstance(widget, tkinter.Text) widget.config( foreground=fg, background=bg, insertbackground=fg, # cursor color selectforeground=bg, selectbackground=fg, ) else: callback(fg, bg) widget.bind("<<SettingChanged:pygments_style>>", on_style_changed, add=True) on_style_changed()
def bind_with_data( widget: tkinter.Misc, sequence: str, callback: Callable[[EventWithData], Optional[str]], add: bool = False, ) -> str: """ Like ``widget.bind(sequence, callback)``, but supports the ``data`` argument of ``event_generate()``. Note that the callback takes an argument of type :class:`EventWithData` rather than a usual ``tkinter.Event[tkinter.Misc]``. Here's an example:: from porcupine import utils def handle_event(event: utils.EventWithData): print(event.data_string) utils.bind_with_data(some_widget, '<<Thingy>>', handle_event, add=True) # this prints 'wut wut' some_widget.event_generate('<<Thingy>>', data='wut wut') Note that everything is a string in Tcl, so tkinter ``str()``'s the data. """ # tkinter creates event objects normally and appends them to the # deque, then run_callback() adds data_blablabla attributes to the # event objects and runs callback(event) # # TODO: is it possible to do this without a deque? event_objects: Deque[Union[tkinter.Event[tkinter.Misc], EventWithData]] = collections.deque() widget.bind(sequence, event_objects.append, add=add) def run_the_callback(data_string: str) -> Optional[str]: event = event_objects.popleft() event.__class__ = EventWithData # evil haxor muhaha assert isinstance(event, EventWithData) event.data_string = data_string return callback(event) # may return 'break' # tkinter's bind() ignores the add argument when the callback is a # string :( funcname = widget.register(run_the_callback) widget.tk.eval( 'bind %s %s {+ if {"[%s %%d]" == "break"} break }' % (widget, sequence, funcname) ) return funcname
def _bind_tree(self, widget: tk.Misc, event: str, callback: callable, add: str = "") -> None: """Binds an event to a widget and all its descendants recursively. Parameters ---------- widget : tk.Misc The widget to bind the callback to. event : str The event to bind to the widget. callback : callable The callback to call on event. add : str, default "" Specifies whether callback will be called additionally ("+") to the other bound function or whether it will replace the previous function (""). """ widget.bind(event, callback, add) for child in widget.winfo_children(): self._bind_tree(child, event, callback, add)