Skip to content

Commit

Permalink
first pass implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
benjamin-kirkbride committed Jul 5, 2023
1 parent 8065b37 commit 8775d89
Show file tree
Hide file tree
Showing 3 changed files with 159 additions and 2 deletions.
12 changes: 10 additions & 2 deletions porcupine/plugins/python_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
Available in Tools/Python/Black and Tools/Python/Isort.
"""

from __future__ import annotations

import logging
Expand All @@ -14,7 +13,7 @@
from tkinter import messagebox

from porcupine import menubar, tabs, textutils, utils
from porcupine.plugins import python_venv
from porcupine.plugins import python_venv, toolbar

log = logging.getLogger(__name__)

Expand Down Expand Up @@ -66,3 +65,12 @@ def format_code_in_textwidget(tool: str, tab: tabs.FileTab) -> None:
def setup() -> None:
menubar.add_filetab_command("Tools/Python/Black", partial(format_code_in_textwidget, "black"))
menubar.add_filetab_command("Tools/Python/Isort", partial(format_code_in_textwidget, "isort"))

buttons = []
buttons.append(
toolbar.Button(text="Black", command=partial(format_code_in_textwidget, "black"))
)
buttons.append(
toolbar.Button(text="isort", command=partial(format_code_in_textwidget, "isort"))
)
toolbar.add_button_group(filetype_name="Python", name="Python Tools", buttons=buttons)
110 changes: 110 additions & 0 deletions porcupine/plugins/toolbar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
"""Display a toolbar in each file tab."""
from __future__ import annotations

import dataclasses
import logging
import tkinter
from functools import partial
from tkinter import ttk
from typing import Any, Callable, Iterable

from porcupine import get_tab_manager, tabs

log = logging.getLogger(__name__)

setup_after = ["filetypes"]


# TODO: add icon (make text optional?)
@dataclasses.dataclass(kw_only=True)
class Button:
text: str
description: str | None = None
command: Callable


@dataclasses.dataclass(kw_only=True)
class ButtonGroup:
name: str
priority: int # 0 is highest
buttons: list[Button]
separator: bool = True


class SortedButtonGroupList(list[ButtonGroup]):
"""A of button groups that sorts itself automatically"""

# no this wasn't necessary, but I am in too deep to stop now
# but seriously why isn't there a sorted list type in the stdlib?
@classmethod
def _key_func(cls, group: ButtonGroup) -> int:
return group.priority

def __init__(self, *args: Iterable[ButtonGroup], **kwargs: ButtonGroup) -> None:
super().__init__(*args, **kwargs)
self.sort(key=self._key_func)

def append(self, __item: ButtonGroup) -> None:
super().append(__item)
self.sort(key=self._key_func)

def extend(self, __iterable: Iterable[ButtonGroup]) -> None:
super().extend(__iterable)
self.sort(key=self._key_func)


filetype_button_groups_mapping: dict[str, SortedButtonGroupList] = {}


def add_button_group(
*, filetype_name: str, name: str, buttons: list[Button], priority: int = 0, separator=True
) -> None:
button_group = ButtonGroup(name=name, priority=priority, buttons=buttons, separator=separator)
if filetype_button_groups_mapping.get(filetype_name):
filetype_button_groups_mapping[filetype_name].append(button_group)
else:
filetype_button_groups_mapping[filetype_name] = SortedButtonGroupList([button_group])


class ToolBar(ttk.Frame):
def __init__(self, tab: tabs.FileTab):
super().__init__(tab.top_frame, name="toolbar", border=1, relief="raised")
self._tab = tab

def update_buttons(self, tab: tabs.FileTab, junk: object = None) -> None:
"""Different filetypes have different buttons associated with them."""
filetype_name = tab.settings.get("filetype_name", object)
button_groups = filetype_button_groups_mapping.get(filetype_name)
if not button_groups:
return

for button_group in button_groups:
for button in button_group.buttons:
ttk.Button(
self,
command=partial(button.command, tab),
style="Statusbar.TButton",
text=button.text,
).pack(side="left", padx=10, pady=5)


def on_new_filetab(tab: tabs.FileTab) -> None:
toolbar = ToolBar(tab)
toolbar.pack(side="bottom", fill="x")

tab.bind("<<TabSettingChanged:filetype_name>>", partial(toolbar.update_buttons, tab), add=True)
toolbar.update_buttons(tab)


def update_button_style(junk_event: object = None) -> None:
# https://tkdocs.com/tutorial/styles.html
# tkinter's style stuff sucks
get_tab_manager().tk.eval(
"ttk::style configure Statusbar.TButton -padding {10 0} -anchor center"
)


def setup() -> None:
get_tab_manager().add_filetab_callback(on_new_filetab)
get_tab_manager().bind("<<ThemeChanged>>", update_button_style, add=True)
update_button_style()
39 changes: 39 additions & 0 deletions tests/test_toolbar_plugin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from __future__ import annotations

from porcupine.plugins import toolbar


def _gen_button_group(priority: int, name: str | None = None) -> toolbar.ButtonGroup:
return toolbar.ButtonGroup(
name=f"priority = {priority}" if not name else name, priority=priority, buttons=[]
)


def test_manual_sorted_button_group_list_with_append_and_extend():
# reverse order
sorted_button_group_list = toolbar.SortedButtonGroupList(
[_gen_button_group(i) for i in [100, 0, 50, 25, 2, 1, 99]]
)

sorted_button_group_list.append(_gen_button_group(33))
sorted_button_group_list.append(_gen_button_group(5))

sorted_button_group_list.extend(
[_gen_button_group(9), _gen_button_group(8), _gen_button_group(7)]
)

for i, button_group in zip(
[0, 1, 2, 5, 7, 8, 9, 25, 33, 50, 99, 100], sorted_button_group_list
):
assert i == button_group.priority


def test_big_reversed_sorted_button_group_list():
qty = 100
# reverse order
sorted_button_group_list = toolbar.SortedButtonGroupList(
[_gen_button_group(i) for i in reversed(range(qty))]
)

for i, button_group in zip(range(qty), sorted_button_group_list):
assert i == button_group.priority

0 comments on commit 8775d89

Please sign in to comment.