Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: more complete main window implementation #604

Open
wants to merge 43 commits into
base: main
Choose a base branch
from

Conversation

tlambert03
Copy link
Member

@tlambert03 tlambert03 commented Oct 20, 2023

This establishes the protocols and the qt backend for the main window elaboration discussed in #601.

This includes #597

it's turning into a large PR, so I might break it out into smaller ones, but this PR will serves as the cumulative progress.

cc @larsoner

for ease of review, here are the APIs this adds:

class MainWindowWidget(ContainerWidget):
    """Top level Application widget that can contain other widgets."""

    def add_dock_widget(
        self, widget: Widget, *, area: protocols.Area = "right"
    ) -> None:
        """Add a dock widget to the main window."""

    def add_tool_bar(self, widget: Widget, *, area: protocols.Area = "top") -> None:
        """Add a toolbar to the main window."""

    @property
    def menu_bar(self) -> MenuBarWidget:
        """Return the menu bar widget."""

    @menu_bar.setter
    def menu_bar(self, widget: MenuBarWidget | None) -> None:
        """Set the menu bar widget."""

    @property
    def status_bar(self) -> StatusBarWidget:
        """Return the status bar widget."""

    @status_bar.setter
    def status_bar(self, widget: StatusBarWidget | None) -> None:
        """Set the status bar widget."""

    @typing.deprecated
    def create_menu_item(
        self,
        menu_name: str,
        item_name: str,
        callback: Callable | None = None,
        shortcut: str | None = None,
    ) -> None:
        ...


class StatusBarWidget(Widget):
    def add_widget(self, widget: Widget) -> None:
        """Add a widget to the statusbar."""

    def insert_widget(self, position: int, widget: Widget) -> None:
        """Insert a widget at the given position."""

    def remove_widget(self, widget: Widget) -> None:
        """Remove a widget from the statusbar."""

    @property
    def message(self) -> str:
        """Return currently shown message, or empty string if None."""

    @message.setter
    def message(self, message: str) -> None:
        """Return the message timeout in milliseconds."""

    def set_message(self, message: str, timeout: int = 0) -> None:
        """Show a message in the status bar for a given timeout."""


class MenuBarWidget(Widget):
    """Menu bar containing menus. Can be added to a MainWindowWidget."""

    def __getitem__(self, key: str) -> MenuWidget:
        return self._menus[key]

    def add_menu(self, title: str, icon: str | None = None) -> MenuWidget:
        """Add a menu to the menu bar."""

    def clear(self) -> None:
        """Clear the menu bar."""


class MenuWidget(Widget):
    """Menu widget. Can be added to a MenuBarWidget or another MenuWidget."""

    def add_action(
        self,
        text: str,
        shortcut: str | None = None,
        icon: str | None = None,
        tooltip: str | None = None,
        callback: Callable | None = None,
    ) -> None:
        """Add an action to the menu."""

    def add_separator(self) -> None:
        """Add a separator line to the menu."""

    def add_menu(self, title: str, icon: str | None = None) -> MenuWidget:
        """Add a menu to the menu."""

    def clear(self) -> None:
        """Clear the menu bar."""



class ToolBarWidget(Widget):

    def add_button(
        self, text: str = "", icon: str = "", callback: Callable | None = None
    ) -> None:
        """Add an action to the toolbar."""

    def add_separator(self) -> None:
        """Add a separator line to the toolbar."""

    def add_spacer(self) -> None:
        """Add a spacer to the toolbar."""

    def add_widget(self, widget: Widget) -> None:
        """Add a widget to the toolbar."""

    @property
    def icon_size(self) -> tuple[int, int] | None:
        """Return the icon size of the toolbar."""

    @icon_size.setter
    def icon_size(self, size: int | tuple[int, int] | None) -> None:
        """Set the icon size of the toolbar."""

    def clear(self) -> None:
        """Clear the toolbar."""

@codecov
Copy link

codecov bot commented Oct 20, 2023

Codecov Report

Attention: 167 lines in your changes are missing coverage. Please review.

Comparison is base (aa38630) 87.75% compared to head (778c0ea) 85.42%.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #604      +/-   ##
==========================================
- Coverage   87.75%   85.42%   -2.34%     
==========================================
  Files          40       43       +3     
  Lines        4705     5091     +386     
==========================================
+ Hits         4129     4349     +220     
- Misses        576      742     +166     
Files Coverage Δ
src/magicgui/backends/_ipynb/__init__.py 100.00% <ø> (ø)
src/magicgui/backends/_qtpy/__init__.py 100.00% <ø> (ø)
src/magicgui/widgets/__init__.py 100.00% <ø> (ø)
src/magicgui/widgets/_concrete.py 89.64% <100.00%> (+0.13%) ⬆️
src/magicgui/widgets/bases/__init__.py 100.00% <100.00%> (ø)
src/magicgui/widgets/bases/_container_widget.py 90.95% <ø> (-0.18%) ⬇️
src/magicgui/widgets/bases/_toolbar.py 95.65% <100.00%> (-0.35%) ⬇️
src/magicgui/widgets/protocols.py 100.00% <100.00%> (ø)
src/magicgui/application.py 83.87% <90.00%> (+0.15%) ⬆️
src/magicgui/widgets/bases/_statusbar.py 71.42% <71.42%> (ø)
... and 4 more

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@larsoner
Copy link

it's turning into a large PR, so I might break it out into smaller ones, but this PR will serves as the cumulative progress.

+963 −89 is large but not insurmountable. Let me know when it would help for me to look / try / whatever!

@tlambert03
Copy link
Member Author

Thanks for checking in @larsoner! I was actually just gonna ping you today. I think the protocol is in good shape, and the qt backend is working pretty well. So it actually would be a great time for you to play with it and see if you can improve the ipywidgest backend.

I have a simple example.py in the root of this PR that I've been playing with. the ipywidgets backend loaded at one point, it just (mostly) does nothing for now. I implemented a barebones version of the GridSpec pattern you proposed.

I think you know ipywidgets better than I do, so if you had time and wanted to tinker a bit, i think just running example.py in jupyter and working on styles and implementing methods in the ipynb backend would be super useful! 🙏

@tlambert03
Copy link
Member Author

+963 −89 is large but not insurmountable

it's true, and most of this PR is really just boilerplate adding the new protocols

@larsoner
Copy link

I think you know ipywidgets better than I do

I am actually a bit of an ipywidgets newcomer but happy to give it a shot! Should be able to look next week

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants