def _get_fake_triangle(cache=[]): if not cache: cache.append(tkinter.PhotoImage( width=images.get('triangle').width(), height=images.get('triangle').height())) atexit.register(cache.clear) # see images/__init__.py return cache[0]
def __init__(self): # make it about 200 pixels wide by taking every magic'th pixel magic = images.get('logo').width() // 200 self._image = images.get('logo').subsample(magic) self._frame = ttk.Frame(get_tab_manager()) # pad only on left side so the image goes as far right as possible top = ttk.Frame(self._frame) top.pack(fill='x', padx=(BORDER_SIZE, 0)) ttk.Label(top, image=self._image).pack(side='right') # TODO: better way to center the label in its space? centerer = ttk.Frame(top) centerer.pack(fill='both', expand=True) self.title_label = ttk.Label(centerer, text="Welcome to Porcupine!", font=('', 25, 'bold')) self.title_label.place(relx=0.5, rely=0.5, anchor='center') self.message_label = ttk.Label(self._frame, text=MESSAGE, font=('', 15, '')) self.message_label.pack(pady=BORDER_SIZE) self._on_tab_closed()
def _get_blank_triangle_sized_image(*, _cache: List[tkinter.PhotoImage] = [] ) -> tkinter.PhotoImage: # see images/__init__.py if not _cache: _cache.append( tkinter.PhotoImage(width=images.get('triangle').width(), height=images.get('triangle').height())) atexit.register(_cache.clear) return _cache[0]
def __init__(self, *args: Any, **kwargs: Any): super().__init__(*args, **kwargs) big_label = ttk.Label(self, font=('', 16, ()), text="About Porcupine") big_label.pack(pady=5) self._textwidget = utils.create_passive_text_widget( self, width=60, height=18) self._textwidget.pack(fill='both', expand=True, padx=5, pady=5) # http://effbot.org/zone/tkinter-text-hyperlink.htm # that tutorial is almost as old as i am, but it's still usable self._textwidget.tag_config('link', foreground='blue', underline=True) self._textwidget.tag_bind('link', '<Enter>', self._enter_link) self._textwidget.tag_bind('link', '<Leave>', self._leave_link) self._link_tag_names = map('link-{}'.format, itertools.count()) self._textwidget.config(state='normal') for text_chunk in _BORING_TEXT.strip().split('\n\n'): self._add_minimal_markdown(text_chunk) self._textwidget.config(state='disabled') label = ttk.Label(self, image=images.get('logo-200x200'), cursor='hand2') label.pack(anchor='e') utils.set_tooltip(label, "Click to view in full size") label.bind('<Button-1>', show_huge_logo, add=True)
def run_command( workingdir: pathlib.Path, command: List[str], succeeded_callback: Callable[[], None] = (lambda: None), ) -> None: tab = get_tab_manager().select() assert tab is not None try: runner = _no_terminal_runners[str(tab)] except KeyError: runner = NoTerminalRunner(tab.bottom_frame) _no_terminal_runners[str(tab)] = runner # TODO: can this also be ran when tab is closed? def on_close(event: tkinter.Event[tkinter.Misc]) -> None: runner.destroy() del _no_terminal_runners[str(tab)] closebutton = tkinter.Label(runner.textwidget, image=images.get("closebutton"), cursor="hand2") closebutton.bind("<Button-1>", on_close, add=True) closebutton.place(relx=1, rely=0, anchor="ne") runner.textwidget.pack(side="top", fill="x") runner.run_command(workingdir, command, succeeded_callback)
def __init__(self, *args: Any, **kwargs: Any): super().__init__(*args, **kwargs) # TODO: calculate height automagically, instead of hard-coding self._textwidget = textwidget.create_passive_text_widget(self, width=60, height=25) self._textwidget.pack(fill="both", expand=True, padx=5, pady=5) # http://effbot.org/zone/tkinter-text-hyperlink.htm # that tutorial is almost as old as i am, but it's still usable self._textwidget.tag_config("link", foreground="blue", underline=True) self._textwidget.tag_bind("link", "<Enter>", self._enter_link) self._textwidget.tag_bind("link", "<Leave>", self._leave_link) self._link_tag_names = map("link-{}".format, itertools.count()) self._textwidget.config(state="normal") for text_chunk in _BORING_TEXT.strip().split("\n\n"): self._add_minimal_markdown(text_chunk) self._textwidget.insert("end", "\n\n") self._add_directory_link( "Porcupine is installed to", pathlib.Path(__file__).absolute().parent.parent.parent) self._add_directory_link("You can install plugins to", pathlib.Path(plugins.__path__[0])) self._textwidget.config(state="disabled") label = ttk.Label(self, image=images.get("logo-200x200"), cursor="hand2") label.pack(anchor="e") utils.set_tooltip(label, "Click to view in full size") label.bind("<Button-1>", show_huge_logo, add=True)
def add_tab(self, tab: 'Tab', select: bool = True) -> 'Tab': """Append a :class:`.Tab` to this tab manager. If ``tab.equivalent(existing_tab)`` returns True for any ``existing_tab`` that is already in the tab manager, then that existing tab is returned and the tab passed in as an argument is destroyed. Otherwise *tab* is added to the tab manager and returned. If *select* is True, then the returned tab is selected with :meth:`~select`. .. seealso:: The :meth:`.Tab.equivalent` and :meth:`~close_tab` methods. """ assert tab not in self.tabs(), "cannot add the same tab twice" for existing_tab in self.tabs(): if tab.equivalent(existing_tab): if select: self.select(existing_tab) tab.destroy() return existing_tab self.add(tab, text=tab.title, image=images.get('closebutton'), compound='right') if select: self.select(tab) # The update() is needed in some cases because virtual events don't run # if the widget isn't visible yet. self.update() self.event_generate('<<NewTab>>', data=tab) return tab
def update_wraplen(self, event): # images.get('logo-200x200').width() is always 200, but # hard-coding is bad self.title_label['wraplength'] = (event.width - images.get('logo-200x200').width() - BORDER_SIZE) self.message_label[ 'wraplength'] = event.width - 2 * BORDER_SIZE # noqa
def add_channel_like(self, channel_like): assert channel_like.name not in self._channel_likes self._channel_likes[channel_like.name] = channel_like self._channel_selector.append(channel_like.name) self._channel_selector.widget.selection_set(channel_like.name) if channel_like.name == _SERVER_VIEW_ID: assert len(self._channel_likes) == 1 self._channel_selector.widget.item(channel_like.name, text=self.core.host) elif channel_like.is_channel(): self._channel_selector.widget.item( channel_like.name, image=images.get('hashtagbubble-20x20')) else: self._channel_selector.widget.item(channel_like.name, image=images.get('face-20x20'))
def setup() -> None: tabmanager = get_tab_manager() tabmanager.add_tab_callback(lambda tab: get_tab_manager().tab( tab, image=images.get("closebutton"), compound="right")) tabmanager.bind("<<TabClosing:XButtonClickClose>>", on_x_clicked, add=True) tabmanager.bind("<<TabClosing:ShowMenu>>", show_menu, add=True) tabmanager.bind("<<TabClosing:HeaderClickClose>>", on_header_clicked, add=True)
def on_x_clicked(event: tkinter.Event[tabs.TabManager]) -> None: if event.widget.identify( event.x, event.y) == "label": # type: ignore[no-untyped-call] # find the right edge of the top label (including close button) right = event.x while event.widget.identify( right, event.y) == "label": # type: ignore[no-untyped-call] right += 1 # hopefully the image is on the right edge of the label and there's no padding :O if event.x >= right - images.get("closebutton").width(): close_clicked_tab(event)
def _on_click(self, event: 'tkinter.Event[tkinter.Misc]') -> None: if self.identify(event.x, event.y) != 'label': # something else than the top label was clicked return # find the right edge of the label right = event.x while self.identify(right, event.y) == 'label': right += 1 # hopefully the image is on the right edge of the label and # there's no padding :O if event.x + images.get('closebutton').width() >= right: # the close button was clicked tab = self.tabs()[self.index('@%d,%d' % (event.x, event.y))] if tab.can_be_closed(): self.close_tab(tab)
def var_changed(*junk: object) -> None: value_string = var.get() value: Optional[_StrOrInt] try: value = tybe(value_string) except ValueError: # e.g. int('foo') value = None else: if not callback(value): value = None if value is None: triangle.config(image=images.get('triangle')) else: triangle.config(image=_get_blank_triangle_sized_image()) set(option_name, value, from_config=True)
def run_command(workingdir, command, succeeded_callback=do_nothing): tab = get_tab_manager().select() try: runner = _no_terminal_runners[str(tab)] except KeyError: runner = NoTerminalRunner(tab.bottom_frame) _no_terminal_runners[str(tab)] = runner def on_close(event): runner.destroy() del _no_terminal_runners[str(tab)] closebutton = tkinter.Label(runner.textwidget, image=images.get('closebutton'), cursor='hand2') closebutton.bind('<Button-1>', on_close) closebutton.place(relx=1, rely=0, anchor='ne') runner.textwidget.pack(side='top', fill='x') runner.run_command(workingdir, command, succeeded_callback)
def __init__(self, parent, textwidget, **kwargs): super().__init__(parent, **kwargs) self._last_pattern = None self._matches = None self.grid_columnconfigure(1, weight=1) self._textwidget = textwidget entrygrid = ttk.Frame(self) entrygrid.grid(row=0, column=0) self._find_entry = self._add_entry(entrygrid, 0, "Find:", self.find) self._replace_entry = self._add_entry(entrygrid, 1, "Replace with:") buttonframe = ttk.Frame(self) buttonframe.grid(row=1, column=0, sticky='we') buttons = [ ("Find", self.find), ("Replace", self.replace), ("Replace and find", self.replace_and_find), ("Replace all", self.replace_all), ] for text, command in buttons: button = ttk.Button(buttonframe, text=text, command=command) button.pack(side='left', fill='x', expand=True) self._full_words_var = tkinter.BooleanVar() checkbox = ttk.Checkbutton(self, text="Full words only", variable=self._full_words_var) checkbox.grid(row=0, column=1, sticky='nw') self._statuslabel = ttk.Label(self) self._statuslabel.grid(row=1, column=1, columnspan=2, sticky='nswe') closebutton = ttk.Label(self, image=images.get('closebutton'), cursor='hand2') closebutton.grid(row=0, column=2, sticky='ne') closebutton.bind('<Button-1>', lambda event: self.pack_forget())
def __init__(self) -> None: self._frame = ttk.Frame(get_tab_manager()) # pad only on left side so the image goes as far right as possible top = ttk.Frame(self._frame) top.pack(fill="x", padx=(BORDER_SIZE, 0)) ttk.Label(top, image=images.get("logo-200x200")).pack(side="right") # TODO: better way to center the label in its space? centerer = ttk.Frame(top) centerer.pack(fill="both", expand=True) self.title_label = ttk.Label(centerer, text="Welcome to Porcupine!", font=("", 25, "bold")) self.title_label.place(relx=0.5, rely=0.5, anchor="center") self.message_label = ttk.Label(self._frame, text=get_message(), font=("", 15, "")) self.message_label.pack(pady=BORDER_SIZE) self._on_tab_closed()
def __init__(self) -> None: self._frame = ttk.Frame(get_tab_manager()) # pad only on left side so the image goes as far right as possible top = ttk.Frame(self._frame) top.pack(fill='x', padx=(BORDER_SIZE, 0)) ttk.Label(top, image=images.get('logo-200x200')).pack(side='right') # TODO: better way to center the label in its space? centerer = ttk.Frame(top) centerer.pack(fill='both', expand=True) self.title_label = ttk.Label(centerer, text="Welcome to Porcupine!", font=('', 25, 'bold')) self.title_label.place(relx=0.5, rely=0.5, anchor='center') self.message_label = ttk.Label(self._frame, text=MESSAGE, font=('', 15, '')) self.message_label.pack(pady=BORDER_SIZE) self._on_tab_closed()
def setup() -> None: window = get_main_window() window.title(f"Porcupine {porcupine_version}" ) # not related to the icon, but it's ok imo window.wm_iconphoto(False, images.get("logo-200x200"))
def on_errorvar_changed(*junk): if errorvar.get(): triangle_label['image'] = images.get('triangle') else: triangle_label['image'] = self._get_fake_triangle()
def __init__(self, parent, textwidget, **kwargs): super().__init__(parent, **kwargs) self._textwidget = textwidget # grid layout: # column 0 column 1 column 2 column 3 # ,---------------------------------------------------------------. # row0| Find: | text entry | | [x] Full words only | # |---------------|---------------|-------|-----------------------| # row1| Replace with: | text entry | | [x] Ignore case | # |---------------------------------------------------------------| # row2| button frame, this thing contains a bunch of buttons | # |---------------------------------------------------------------| # row3| status label with useful-ish text | # |---------------------------------------------------------------| # row4| separator | # `---------------------------------------------------------------' # # note that column 2 is used just for spacing, the separator helps # distinguish this from e.g. status bar below this self.grid_columnconfigure(2, minsize=30) self.grid_columnconfigure(3, weight=1) # TODO: use the pygments theme somehow? textwidget.tag_config('find_highlight', foreground='black', background='yellow') self.find_entry = self._add_entry(0, "Find:") find_var = self.find_entry['textvariable'] = tk.StringVar() find_var.trace('w', self.highlight_all_matches) self.find_entry.lol = find_var # because cpython gc self.replace_entry = self._add_entry(1, "Replace with:") self.find_entry.bind('<Shift-Return>', self._go_to_previous_match) self.find_entry.bind('<Return>', self._go_to_next_match) # commented out because pressing tab in self.find_entry unselects the # text in textwidget for some reason #self.replace_entry.bind('<Return>', self._replace_this) buttonframe = ttk.Frame(self) buttonframe.grid(row=2, column=0, columnspan=4, sticky='we') self.previous_button = ttk.Button(buttonframe, text="Previous match", command=self._go_to_previous_match) self.next_button = ttk.Button(buttonframe, text="Next match", command=self._go_to_next_match) self.replace_this_button = ttk.Button(buttonframe, text="Replace this match", command=self._replace_this) self.replace_all_button = ttk.Button(buttonframe, text="Replace all", command=self._replace_all) self.previous_button.pack(side='left') self.next_button.pack(side='left') self.replace_this_button.pack(side='left') self.replace_all_button.pack(side='left') self._update_buttons() self.full_words_var = tk.BooleanVar() self.full_words_var.trace('w', self.highlight_all_matches) self.ignore_case_var = tk.BooleanVar() self.ignore_case_var.trace('w', self.highlight_all_matches) ttk.Checkbutton(self, text="Full words only", variable=self.full_words_var).grid(row=0, column=3, sticky='w') ttk.Checkbutton(self, text="Ignore case", variable=self.ignore_case_var).grid(row=1, column=3, sticky='w') self.statuslabel = ttk.Label(self) self.statuslabel.grid(row=3, column=0, columnspan=4, sticky='we') ttk.Separator(self, orient='horizontal').grid(row=4, column=0, columnspan=4, sticky='we') closebutton = ttk.Label(self, cursor='hand2') closebutton.place(relx=1, rely=0, anchor='ne') closebutton.bind('<Button-1>', self.hide) # TODO: figure out why images don't work in tests if 'pytest' not in sys.modules: # pragma: no cover closebutton['image'] = images.get('closebutton') # explained in test_find_plugin.py textwidget.bind('<<Selection>>', self._update_buttons, add=True)
def setup(): window = get_main_window() window.tk.call('wm', 'iconphoto', window, images.get('logo'))
def add_tab(self, tab): self.add(tab, text=tab.title, image=images.get('closebutton'), compound='right') tab.tkraise(self) # make sure it's visible
def __init__(self, parent: tkinter.Tk, textwidget: tkinter.Text, **kwargs: Any) -> None: super().__init__(parent, **kwargs) # type: ignore self._textwidget = textwidget # grid layout: # column 0 column 1 column 2 column 3 # ,---------------------------------------------------------------. # row0| Find: | text entry | | [x] Full words only | # |---------------|---------------|-------|-----------------------| # row1| Replace with: | text entry | | [x] Ignore case | # |---------------------------------------------------------------| # row2| button frame, this thing contains a bunch of buttons | # |---------------------------------------------------------------| # row3| status label with useful-ish text | # |---------------------------------------------------------------| # row4| separator | # `---------------------------------------------------------------' # # note that column 2 is used just for spacing, the separator helps # distinguish this from e.g. status bar below this self.grid_columnconfigure(2, minsize=30) self.grid_columnconfigure(3, weight=1) # TODO: use the pygments theme somehow? textwidget.tag_config('find_highlight', foreground='black', background='yellow') self._textwidget.tag_lower('find_highlight', 'sel') self.find_entry = self._add_entry(0, "Find:") find_var = tkinter.StringVar() self.find_entry.config(textvariable=find_var) find_var.trace_add('write', self.highlight_all_matches) # because cpython gc cast(Any, self.find_entry).lol = find_var self.replace_entry = self._add_entry(1, "Replace with:") self.find_entry.bind('<Shift-Return>', self._go_to_previous_match, add=True) self.find_entry.bind('<Return>', self._go_to_next_match, add=True) # commented out because pressing tab in self.find_entry unselects the # text in textwidget for some reason #self.replace_entry.bind('<Return>', self._replace_this) buttonframe = ttk.Frame(self) buttonframe.grid(row=2, column=0, columnspan=4, sticky='we') self.previous_button = ttk.Button(buttonframe, text="Previous match", command=self._go_to_previous_match) self.next_button = ttk.Button(buttonframe, text="Next match", command=self._go_to_next_match) self.replace_this_button = ttk.Button(buttonframe, text="Replace this match", command=self._replace_this) self.replace_all_button = ttk.Button(buttonframe, text="Replace all", command=self._replace_all) self.previous_button.pack(side='left') self.next_button.pack(side='left') self.replace_this_button.pack(side='left') self.replace_all_button.pack(side='left') self._update_buttons() self.full_words_var = tkinter.BooleanVar() self.full_words_var.trace_add('write', self.highlight_all_matches) self.ignore_case_var = tkinter.BooleanVar() self.ignore_case_var.trace_add('write', self.highlight_all_matches) # TODO: add keyboard shortcut for "Full words only". I use it all the # time and reaching mouse is annoying. Tabbing through everything # is also annoying. ttk.Checkbutton(self, text="Full words only", variable=self.full_words_var).grid(row=0, column=3, sticky='w') ttk.Checkbutton(self, text="Ignore case", variable=self.ignore_case_var).grid(row=1, column=3, sticky='w') self.statuslabel = ttk.Label(self) self.statuslabel.grid(row=3, column=0, columnspan=4, sticky='we') ttk.Separator(self, orient='horizontal').grid(row=4, column=0, columnspan=4, sticky='we') closebutton = ttk.Label(self, cursor='hand2') closebutton.place(relx=1, rely=0, anchor='ne') closebutton.bind('<Button-1>', self.hide, add=True) closebutton.config(image=images.get('closebutton')) # explained in test_find_plugin.py textwidget.bind('<<Selection>>', self._update_buttons, add=True)
def setup(): window = get_main_window() window.title("Porcupine") # not related to the icon, but it's ok imo window.tk.call('wm', 'iconphoto', window, images.get('logo-200x200'))
def __init__(self, parent: tkinter.Misc, textwidget: tkinter.Text, **kwargs: Any) -> None: super().__init__(parent, **kwargs) self._textwidget = textwidget # grid layout: # column 0 column 1 column 2 column 3 # ,---------------------------------------------------------------. # row0| Find: | text entry | | [x] Full words only | # |---------------|---------------|-------|-----------------------| # row1| Replace with: | text entry | | [x] Ignore case | # |---------------------------------------------------------------| # row2| button frame, this thing contains a bunch of buttons | # |---------------------------------------------------------------| # row3| status label with useful-ish text | # |---------------------------------------------------------------| # row4| separator | # `---------------------------------------------------------------' # # note that column 2 is used just for spacing, the separator helps # distinguish this from e.g. status bar below this self.grid_columnconfigure(2, minsize=30) # type: ignore[no-untyped-call] self.grid_columnconfigure(3, weight=1) # type: ignore[no-untyped-call] self.full_words_var = tkinter.BooleanVar() self.ignore_case_var = tkinter.BooleanVar() find_var = tkinter.StringVar() self.find_entry = self._add_entry(0, "Find:") self.find_entry.config(textvariable=find_var) find_var.trace_add("write", self.highlight_all_matches) # because cpython gc cast(Any, self.find_entry).lol = find_var self.replace_entry = self._add_entry(1, "Replace with:") self.find_entry.bind("<Shift-Return>", self._go_to_previous_match, add=True) self.find_entry.bind("<Return>", self._go_to_next_match, add=True) buttonframe = ttk.Frame(self) buttonframe.grid(row=2, column=0, columnspan=4, sticky="we") self.previous_button = ttk.Button(buttonframe, text="Previous match", command=self._go_to_previous_match) self.next_button = ttk.Button(buttonframe, text="Next match", command=self._go_to_next_match) self.replace_this_button = ttk.Button( buttonframe, text="Replace this match", underline=len("Replace "), command=self._replace_this, ) self.replace_all_button = ttk.Button(buttonframe, text="Replace all", underline=len("Replace "), command=self._replace_all) self.previous_button.pack(side="left") self.next_button.pack(side="left") self.replace_this_button.pack(side="left") self.replace_all_button.pack(side="left") self._update_buttons() self.full_words_var.trace_add("write", self.highlight_all_matches) self.ignore_case_var.trace_add("write", self.highlight_all_matches) ttk.Checkbutton(self, text="Full words only", underline=0, variable=self.full_words_var).grid(row=0, column=3, sticky="w") ttk.Checkbutton(self, text="Ignore case", underline=0, variable=self.ignore_case_var).grid(row=1, column=3, sticky="w") self.statuslabel = ttk.Label(self) self.statuslabel.grid(row=3, column=0, columnspan=4, sticky="we") ttk.Separator(self, orient="horizontal").grid(row=4, column=0, columnspan=4, sticky="we") closebutton = ttk.Label(self, cursor="hand2") closebutton.place(relx=1, rely=0, anchor="ne") closebutton.bind("<Button-1>", self.hide, add=True) closebutton.config(image=images.get("closebutton")) # explained in test_find_plugin.py textwidget.bind("<<Selection>>", self._update_buttons, add=True) textwidget.bind("<<SettingChanged:pygments_style>>", self._config_tags, add=True) self._config_tags()
def test_get(): with pytest.raises(FileNotFoundError): images.get('watwat')
def update_wraplen(self, event: tkinter.Event[tkinter.Misc]) -> None: # images.get('logo-200x200').width() is always 200, but hard-coding is bad self.title_label.config( wraplength=(event.width - images.get("logo-200x200").width() - BORDER_SIZE)) self.message_label.config(wraplength=(event.width - 2 * BORDER_SIZE))