def setup(): displayer = WelcomeMessageDisplayer() get_tab_manager().bind('<Configure>', displayer.update_wraplen, add=True) utils.bind_with_data(get_tab_manager(), '<<NewTab>>', displayer.on_new_tab, add=True)
def setup(): # the action's binding feature cannot be used because then typing # a '#' outside the main text widget inserts a # to the main widget actions.add_command("Edit/Comment Block", comment_or_uncomment, filetype_names=filetype_names) utils.bind_with_data(get_tab_manager(), '<<NewTab>>', on_new_tab, add=True)
def on_new_tab(tab: tabs.Tab) -> None: if isinstance(tab, tabs.FileTab): underliner = _Underliner(tab.textwidget) utils.bind_with_data(tab, "<<SetUnderlines>>", underliner.set_underlines, add=True)
def setup(): actions.add_command("Edit/Find and Replace", find, '<Control-f>', tabtypes=[tabs.FileTab]) utils.bind_with_data(get_tab_manager(), '<<NewTab>>', on_new_tab, add=True) get_tab_manager().bind('<<NotebookTabChanged>>', on_tab_changed, add=True)
def setup() -> None: # trigger <<UnderlinerHidePopup>> when text widget goes invisible (e.g. switching tabs) get_main_window().event_add('<<UnderlinerHidePopup>>', '<Unmap>') # and when the entire porcupine window loses input focus (binding here to avoid unbinding) get_main_window().bind('<FocusOut>', hide_all_message_labels, add=True) utils.bind_with_data(get_tab_manager(), '<<NewTab>>', on_new_tab, add=True)
def setup() -> None: # the action's binding feature cannot be used because then typing # a '#' outside the main text widget inserts a # to the main widget menubar.get_menu("Edit").add_command( label="Comment Block", command=comment_or_uncomment_in_current_tab) menubar.set_enabled_based_on_tab( "Edit/Comment Block", (lambda tab: isinstance(tab, tabs.FileTab))) utils.bind_with_data(get_tab_manager(), '<<NewTab>>', on_new_tab, add=True)
def on_new_tab(event: utils.EventWithData) -> None: tab = event.data_widget() if isinstance(tab, tabs.FileTab): underliner = _Underliner(tab.textwidget) utils.bind_with_data(tab, '<<SetUnderlines>>', underliner.set_underlines, add=True)
def setup() -> None: # load_filetypes() got already called in setup_argument_parser() utils.bind_with_data(get_tab_manager(), '<<NewTab>>', on_new_tab, add=True) get_main_window().bind('<<PluginsLoaded>>', open_files_specified_on_command_line, add=True) add_default_filetype_setting() configure_filetypes_kwargs() create_filetypes_menu()
def test_bind_with_data_string(): # i don't know why the main window works better with this than a # temporary tkinter.Frame() events = [] utils.bind_with_data(get_main_window(), "<<Asd>>", events.append, add=True) get_main_window().event_generate("<<Asd>>", data="hello") [event] = events assert event.widget is get_main_window() assert event.data_string == "hello"
def test_bind_with_data_string(porcusession, mocker): # i don't know why the main window works better with this than a # temporary tkinter.Frame() events = [] utils.bind_with_data(get_main_window(), '<<Asd>>', events.append, add=True) get_main_window().event_generate('<<Asd>>', data='hello') [event] = events assert event.widget is get_main_window() assert event.data_string == 'hello'
def __init__(self, tab: tabs.FileTab) -> None: self._tab = tab self._orig_cursorpos: Optional[str] = None self._id_counter = itertools.count() self._waiting_for_response_id: Optional[int] = None self.popup = _Popup() utils.bind_with_data( tab, '<<AutoCompletionResponse>>', lambda event: self.receive_completions(event.data_class(Response)), add=True)
def test_bind_with_data_class(porcusession): events = [] utils.bind_with_data( get_main_window(), '<<DataclassAsd>>', events.append, add=True) get_main_window().event_generate( '<<DataclassAsd>>', data=Bar(foos=[Foo(message='abc', num=123)])) [event] = events bar = event.data_class(Bar) [foo] = bar.foos assert foo.message == 'abc' assert foo.num == 123
def test_bind_with_data_class(): events = [] utils.bind_with_data(get_main_window(), "<<DataclassAsd>>", events.append, add=True) get_main_window().event_generate( "<<DataclassAsd>>", data=Bar(foos=[Foo(message="abc", num=123)]) ) [event] = events bar = event.data_class(Bar) [foo] = bar.foos assert foo.message == "abc" assert foo.num == 123
def text_and_events(porcusession): text = tkinter.Text(get_main_window()) text.config(undo=True) # must be before track_changes() track_changes(text) # peers can mess things up create_peer_widget(text, tkinter.Text(get_main_window())) events = [] utils.bind_with_data(text, '<<ContentChanged>>', events.append, add=True) yield (text, events) assert not events
def on_new_tab(tab: tabs.Tab) -> None: if isinstance(tab, tabs.FileTab): # needed because pygments_lexer might change def on_lexer_changed(junk: object = None) -> None: assert isinstance(tab, tabs.FileTab) # f u mypy highlighter.set_lexer(tab.settings.get("pygments_lexer", LexerMeta)()) highlighter = Highlighter(tab.textwidget) tab.bind("<<TabSettingChanged:pygments_lexer>>", on_lexer_changed, add=True) on_lexer_changed() utils.bind_with_data(tab.textwidget, "<<ContentChanged>>", highlighter.on_change, add=True) utils.add_scroll_command( tab.textwidget, "yscrollcommand", debounce(tab, highlighter.highlight_visible, 100) ) highlighter.highlight_visible()
def test_bind_with_data(porcusession): ran = 0 # i don't know why the main window works better with this than a # temporary tkinter.Frame() def cb(event): assert event.widget is get_main_window() assert event.data_tuple(int, str, int) == (1, 'asd asd', 3) nonlocal ran ran += 1 utils.bind_with_data(get_main_window(), '<<Asd>>', cb, add=True) get_main_window().event_generate('<<Asd>>', data=utils.create_tcl_list( [1, 'asd asd', 3])) assert ran == 1
def setup() -> None: utils.bind_with_data(get_tab_manager(), '<<NewTab>>', on_new_tab, add=True) menubar.get_menu("Run").add_command(label="Compile", command=partial( do_something, 'compile')) menubar.get_menu("Run").add_command(label="Run", command=partial(do_something, 'run')) menubar.get_menu("Run").add_command(label="Compile and Run", command=partial( do_something, 'compilerun')) menubar.get_menu("Run").add_command(label="Lint", command=partial( do_something, 'compilerun')) # TODO: disable the menu items when they don't correspond to actual commands for label in {"Compile", "Run", "Compile and Run", "Lint"}: menubar.set_enabled_based_on_tab( f"Run/{label}", (lambda tab: isinstance(tab, tabs.FileTab)))
def on_new_tab(event: utils.EventWithData) -> None: tab = event.data_widget() if not isinstance(tab, tabs.FileTab): return tab.settings.add_option('autocomplete_chars', [], type=List[str]) completer = AutoCompleter(tab) # no idea why backspace has to be bound separately utils.bind_with_data(tab.textwidget, '<Key>', completer.on_any_key, add=True) tab.textwidget.bind('<BackSpace>', completer.on_any_key, add=True) utils.bind_tab_key(tab.textwidget, completer.on_tab, add=True) tab.textwidget.bind('<Return>', completer.on_enter, add=True) tab.textwidget.bind('<Escape>', completer.on_escape, add=True) tab.textwidget.bind('<Prior>', completer.popup.on_page_up_down, add=True) tab.textwidget.bind('<Next>', completer.popup.on_page_up_down, add=True) tab.textwidget.bind('<Up>', completer.popup.on_arrow_key_up_down, add=True) tab.textwidget.bind('<Down>', completer.popup.on_arrow_key_up_down, add=True) completer.popup.treeview.bind('<Button-1>', (lambda event: completer._accept()), add=True) # avoid weird corner cases tab.winfo_toplevel().bind('<FocusOut>', (lambda event: completer._reject()), add=True) tab.textwidget.bind( # any mouse button '<Button>', (lambda event: completer._reject()), add=True) tab.bind('<Destroy>', (lambda event: completer.popup.toplevel.destroy()), add=True)
def on_new_tab(tab: tabs.Tab) -> None: if not isinstance(tab, tabs.FileTab): return tab.settings.add_option("autocomplete_chars", [], List[str]) completer = AutoCompleter(tab) # no idea why backspace has to be bound separately utils.bind_with_data(tab.textwidget, "<Key>", completer.on_any_key, add=True) tab.textwidget.bind("<BackSpace>", completer.on_any_key, add=True) utils.bind_tab_key(tab.textwidget, completer.on_tab, add=True) tab.textwidget.bind("<Return>", completer.on_enter, add=True) tab.textwidget.bind("<Escape>", completer.on_escape, add=True) tab.textwidget.bind("<Prior>", completer.popup.on_page_up_down, add=True) tab.textwidget.bind("<Next>", completer.popup.on_page_up_down, add=True) tab.textwidget.bind("<Up>", completer.popup.on_arrow_key_up_down, add=True) tab.textwidget.bind("<Down>", completer.popup.on_arrow_key_up_down, add=True) completer.popup.treeview.bind("<Button-1>", (lambda event: completer._accept()), add=True) # avoid weird corner cases tab.winfo_toplevel().bind("<FocusOut>", (lambda event: completer._reject()), add=True) tab.textwidget.bind( # any mouse button "<Button>", lambda event: completer._reject(), add=True, ) tab.bind("<Destroy>", (lambda event: completer.popup.toplevel.destroy()), add=True)
def setup(): var = tkinter.StringVar() # this initial value isn't shown anywhere, it just needs to be set to # something to avoid an error in actions.add_choice var.set(filetypes.get_all_filetypes()[0].name) var.trace('w', functools.partial(var_value_to_tab, var)) get_tab_manager().bind('<<NotebookTabChanged>>', functools.partial(tab_filetype_to_var, var), add=True) utils.bind_with_data(get_tab_manager(), '<<NewTab>>', functools.partial(on_new_tab, var), add=True) actions.add_choice( 'Filetypes', [filetype.name for filetype in filetypes.get_all_filetypes()], var=var, tabtypes=[tabs.FileTab])
def setup(): window = get_main_window() menubar = MenuManager() window['menu'] = menubar.main_menu utils.bind_with_data(window, '<<NewAction>>', menubar.on_new_action, add=True) utils.bind_with_data(window, '<<ActionEnabled>>', (lambda event: menubar.on_enable_disable(event.data)), add=True) utils.bind_with_data(window, '<<ActionDisabled>>', (lambda event: menubar.on_enable_disable(event.data)), add=True) for action in actions.get_all_actions(): menubar.setup_action(action)
def setup(): window = get_main_window() menubar = MenubarHandler() window['menu'] = menubar.main_menu utils.bind_with_data(window, '<<NewAction>>', menubar.on_new_action, add=True) utils.bind_with_data(window, '<<ActionEnabled>>', functools.partial(menubar.on_enable_disable, 'normal'), add=True) utils.bind_with_data(window, '<<ActionDisabled>>', functools.partial(menubar.on_enable_disable, 'disabled'), add=True) for action in actions.get_all_actions(): menubar.setup_action(action)
def setup() -> None: utils.bind_with_data(get_tab_manager(), '<<NewTab>>', on_new_tab, add=True)
def setup() -> None: utils.bind_with_data(get_tab_manager(), '<<NewTab>>', on_new_tab, add=True) settings.add_option('autocomplete_popup_width', 500) settings.add_option('autocomplete_popup_height', 200) settings.add_option('autocomplete_divider_pos', 200)
def _add_any_action(path, kind, callback_or_choices, binding, var, *, filetype_names=None, tabtypes=None): if path.startswith('/') or path.endswith('/'): raise ValueError("action paths must not start or end with /") if filetype_names is not None and tabtypes is not None: # python raises TypeError when it comes to invalid arguments raise TypeError("only one of filetype_names and tabtypes can be used") if path in _actions: raise RuntimeError("there's already an action with path %r" % path) # event_generate must be before setting action.enabled, this way # plugins get a chance to do something to the new action before it's # disabled action = _Action(path, kind, callback_or_choices, binding, var) _actions[path] = action porcupine.get_main_window().event_generate('<<NewAction>>', data=path) if tabtypes is not None or filetype_names is not None: if tabtypes is not None: tabtypes = tuple( # None is the only type(None) object type(None) if cls is None else cls for cls in tabtypes) def enable_or_disable(junk_event=None): tab = porcupine.get_tab_manager().select() action.enabled = isinstance(tab, tabtypes) if filetype_names is not None: # the noqa comment is needed because flake8 thinks this is # a "redefinition of unused 'enable_or_disable'" def enable_or_disable(junk_event=None): # noqa tab = porcupine.get_tab_manager().select() if isinstance(tab, tabs.FileTab): action.enabled = tab.filetype.name in filetype_names else: action.enabled = False def on_new_tab(event): tab = event.data_widget if isinstance(tab, tabs.FileTab): tab.bind('<<FiletypeChanged>>', enable_or_disable, add=True) utils.bind_with_data(porcupine.get_tab_manager(), '<<NewTab>>', on_new_tab, add=True) enable_or_disable() porcupine.get_tab_manager().bind('<<NotebookTabChanged>>', enable_or_disable, add=True) # TODO: custom keyboard bindings with a config file or something if binding is not None: assert kind in {'command', 'yesno'}, repr(kind) def bind_callback(event): if action.enabled: if kind == 'command': action.callback() if kind == 'yesno': action.var.set(not action.var.get()) # try to allow binding keys that are used for other # things by default return 'break' # TODO: display a warning if it's already bound porcupine.get_main_window().bind(binding, bind_callback, add=True) return action
def _add_any_action(path, kind, callback_or_choices, binding, var, *, filetype_names=None, tabtypes=None): if path.startswith('/') or path.endswith('/'): raise ValueError("action paths must not start or end with /") if filetype_names is not None and tabtypes is not None: # python raises TypeError when it comes to invalid arguments raise TypeError("only one of filetype_names and tabtypes can be used") if path in _actions: raise RuntimeError("there's already an action with path %r" % path) # event_generate must be before setting action.enabled, this way # plugins get a chance to do something to the new action before it's # disabled action = _Action(path, kind, callback_or_choices, binding, var) _actions[path] = action porcupine.get_main_window().event_generate('<<NewAction>>', data=path) if tabtypes is not None or filetype_names is not None: if tabtypes is not None: tabtypes = tuple( # None is the only type(None) object type(None) if cls is None else cls for cls in tabtypes) def enable_or_disable(junk_event=None): tab = porcupine.get_tab_manager().select() action.enabled = isinstance(tab, tabtypes) if filetype_names is not None: # the noqa comment is needed because flake8 thinks this is # a "redefinition of unused 'enable_or_disable'" def enable_or_disable(junk_event=None): # noqa tab = porcupine.get_tab_manager().select() if isinstance(tab, tabs.FileTab): action.enabled = tab.filetype.name in filetype_names else: action.enabled = False def on_new_tab(event): tab = event.data_widget if isinstance(tab, tabs.FileTab): tab.bind('<<FiletypeChanged>>', enable_or_disable, add=True) utils.bind_with_data(porcupine.get_tab_manager(), '<<NewTab>>', on_new_tab, add=True) enable_or_disable() porcupine.get_tab_manager().bind('<<NotebookTabChanged>>', enable_or_disable, add=True) # TODO: custom keyboard bindings with a config file or something if binding is not None: assert kind in {'command', 'yesno'}, repr(kind) def bind_callback(event): if action.enabled: if kind == 'command': action.callback() if kind == 'yesno': action.var.set(not action.var.get()) # try to allow binding keys that are used for other # things by default return 'break' # TODO: display a warning if it's already bound? widget = porcupine.get_main_window() # any widget would do widget.bind_all(binding, bind_callback, add=True) # text widgets are tricky, by default they insert a newline on ctrl+o, # and i discovered how it works in a Tcl session: # # wish8.6 [~]bind Text <Control-o> # # if {!$tk_strictMotif} { # %W insert insert \n # %W mark set insert insert-1c # } # # preventing that is simple as binding it to nothing, and then they'll # do the bind_all thing as usual: # # wish8.6 [~]bind Text <Control-o> # wish8.6 [~]bind Text <Control-o> ;# empty string is returned # wish8.6 [~] # # this is done to all action bindings instead of just <Control-o> to # avoid any issues with other bindings widget.tk.call('bind', 'Text', binding, '') return action