Skip to content

feat: more complete main window implementation #604

New issue

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

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

Already on GitHub? # to your account

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

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

Successfully merging this pull request may close these issues.

2 participants