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

PR: Remember undocked state of plugins when closed and allow to close Outline when the Editor is maximized or in an Editor window #19784

Merged
merged 30 commits into from
May 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
ab6b051
API: Restore undocked state when plugin is made visible
ccordoba12 Sep 18, 2022
bc4b011
API: Add the possibility to close undocked windows
ccordoba12 Sep 18, 2022
1f643ff
API: Rename text of unlock action to easily distinguish it from "Undock"
ccordoba12 Sep 18, 2022
dd13a51
Outline: Allow to close it when shown next to the maximized editor
ccordoba12 Dec 23, 2023
48172dd
Outline: Use option to preserve visible state when editor is maximized
ccordoba12 Dec 23, 2023
e5256be
API: Save if plugin window was undocked before hiding it in our confi…
ccordoba12 Dec 23, 2023
77b394c
Testing: Move function to get a random dockable plugin out of a test
ccordoba12 Dec 24, 2023
2f455ac
Testing: Check the new close plugins when undocked functionality
ccordoba12 Dec 24, 2023
f745ffb
Stylesheet: Improve style of QSplitter handlers
ccordoba12 Dec 27, 2023
c52dc88
Editor: Improve style of splitter used in EditorWidget
ccordoba12 Dec 27, 2023
5d3baeb
Editor: Allow to close Outline in EditorWidget from its Options menu
ccordoba12 Dec 27, 2023
1d81836
Main menu: Use SpyderMenuMixin API to create the app menus
ccordoba12 Dec 27, 2023
577c4f4
API: Make render method of SpyderToolbar public
ccordoba12 Dec 27, 2023
6227e08
Toolbar: Register toolbar toogle view actions in ACTION_REGISTRY
ccordoba12 Dec 27, 2023
ddac76a
API: Add toolbar_id to the constructor of ApplicationToolbar
ccordoba12 Dec 27, 2023
3fd86b1
Editor: Build EditorMainWindow menus and toolbars using our new API
ccordoba12 Dec 27, 2023
3410fea
Editor: Add action to EditorMainWindow View menu to show/hide Outline
ccordoba12 Dec 27, 2023
080ad88
Editor: Hide splitter handle in EditorMainWindow when collpasing Outline
ccordoba12 Dec 27, 2023
61f5fe2
Main menu: Add singleton with the menubar stylesheet to its API
ccordoba12 Dec 28, 2023
fe613bc
Testing: Check the behavior of Outline and toolbars in editor windows
ccordoba12 Dec 28, 2023
80ced8c
Editor: Catch KeyError when getting toolbars/menus in EditorMainWindow
ccordoba12 Dec 28, 2023
73e1022
Layout: Fix error when plugins required for tabification are unavailable
ccordoba12 Dec 28, 2023
f9de0fe
Merge branch 'master' into make-close-and-dock-different
ccordoba12 May 16, 2024
da5976d
Editor: Fix creating editor windows after merge with master
ccordoba12 May 16, 2024
695c4c3
Testing: Fix test_editor_window_outline_and_toolbars
ccordoba12 May 16, 2024
11f671a
Apply suggestions from code review
ccordoba12 May 16, 2024
f7653c8
Variable Explorer: Fix rendering toolbars for its editors
ccordoba12 May 16, 2024
c36103a
API: Simplify text of lock_unlock_action
ccordoba12 May 16, 2024
369544d
Outline Explorer: Hide unnecessary actions when the plugin is undocked
ccordoba12 May 16, 2024
1149dcf
Layout: Fix visual glitches when switching layouts
ccordoba12 May 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions spyder/api/plugins/new_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1072,7 +1072,7 @@ def after_long_process(self, message=""):
super().after_long_process(message)
self.get_widget().stop_spinner()

def get_widget(self):
def get_widget(self) -> PluginMainWidget:
"""
Return the plugin main widget.
"""
Expand Down Expand Up @@ -1128,7 +1128,7 @@ def create_window(self):
self.get_widget().create_window()

def close_window(self, save_undocked=False):
self.get_widget().close_window(save_undocked=save_undocked)
self.get_widget()._close_window(save_undocked=save_undocked)

def change_visibility(self, state, force_focus=False):
self.get_widget().change_visibility(state, force_focus)
Expand Down
129 changes: 105 additions & 24 deletions spyder/api/widgets/main_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -354,11 +354,11 @@ def _setup(self):
text=_("Dock"),
tip=_("Dock the pane"),
icon=self.create_icon('dock'),
triggered=self.close_window,
triggered=self.dock_window,
)
self.lock_unlock_action = self.create_action(
name=PluginMainWidgetActions.LockUnlockPosition,
text=_("Unlock position"),
text=_("Move"),
tip=_("Unlock to move pane to another position"),
icon=self.create_icon('drag_dock_widget'),
triggered=self.lock_unlock_position,
Expand Down Expand Up @@ -387,7 +387,7 @@ def _setup(self):
)

for item in [self.lock_unlock_action, self.undock_action,
self.close_action, self.dock_action]:
self.dock_action, self.close_action]:
self.add_item_to_menu(
item,
self._options_menu,
Expand Down Expand Up @@ -416,7 +416,6 @@ def _update_actions(self):
"""
show_dock_actions = self.windowwidget is None
self.undock_action.setVisible(show_dock_actions)
self.close_action.setVisible(show_dock_actions)
self.lock_unlock_action.setVisible(show_dock_actions)
self.dock_action.setVisible(not show_dock_actions)

Expand All @@ -442,13 +441,13 @@ def _on_title_bar_shown(self, visible):
Actions to perform when the title bar is shown/hidden.
"""
if visible:
self.lock_unlock_action.setText(_('Lock position'))
self.lock_unlock_action.setText(_('Lock'))
self.lock_unlock_action.setIcon(self.create_icon('lock_open'))
for method_name in ['setToolTip', 'setStatusTip']:
method = getattr(self.lock_unlock_action, method_name)
method(_("Lock pane to the current position"))
else:
self.lock_unlock_action.setText(_('Unlock position'))
self.lock_unlock_action.setText(_('Move'))
self.lock_unlock_action.setIcon(
self.create_icon('drag_dock_widget'))
for method_name in ['setToolTip', 'setStatusTip']:
Expand Down Expand Up @@ -703,21 +702,21 @@ def render_toolbars(self):
This action can only be performed once.
"""
# if not self._toolbars_already_rendered:
self._main_toolbar._render()
self._corner_toolbar._render()
self._main_toolbar.render()
self._corner_toolbar.render()
for __, toolbar in self._auxiliary_toolbars.items():
toolbar._render()
toolbar.render()

# self._toolbars_already_rendered = True

# ---- SpyderDockwidget handling
# ---- SpyderWindowWidget handling
# -------------------------------------------------------------------------
@Slot()
def create_window(self):
"""
Create a QMainWindow instance containing this widget.
Create an undocked window containing this widget.
"""
logger.debug("Undocking plugin")
logger.debug(f"Undocking plugin {self._name}")

# Widgets
self.windowwidget = window = SpyderWindowWidget(self)
Expand Down Expand Up @@ -756,21 +755,63 @@ def create_window(self):
window.show()

@Slot()
def close_window(self, save_undocked=False):
def dock_window(self):
"""Dock undocked window back to the main window."""
logger.debug(f"Docking window of plugin {self._name}")

# Reset undocked state
self.set_conf('window_was_undocked_before_hiding', False)

# This avoids trying to close the window twice: once when calling
# _close_window below and the other when Qt calls the closeEvent of
# windowwidget
self.windowwidget.blockSignals(True)

# Close window
self._close_window(switch_to_plugin=True)

# Make plugin visible on main window
self.dockwidget.setVisible(True)
self.dockwidget.raise_()

@Slot()
def close_window(self):
"""
Close QMainWindow instance that contains this widget.
Close undocked window when clicking on the close window button.

Notes
-----
* This can either dock or hide the window, depending on whether the
user hid the window before.
* The default behavior is to dock the window, so that new users can
experiment with the dock/undock functionality without surprises.
* If the user closes the window by clicking on the `Close` action in
the plugin's Options menu or by going to the `View > Panes` menu,
then we will hide it when they click on the close button again.
That gives users the ability to show/hide plugins without
docking/undocking them first.
"""
if self.get_conf('window_was_undocked_before_hiding', default=False):
self.close_dock()
else:
self.dock_window()

def _close_window(self, save_undocked=False, switch_to_plugin=True):
"""
Helper function to close the undocked window with different parameters.

Parameters
----------
save_undocked : bool, optional
True if the undocked state needs to be saved. The default is False.
switch_to_plugin : bool, optional
Whether to switch to the plugin after closing the window. The
default is True.

Returns
-------
None.
"""
logger.debug("Docking plugin back to the main window")

if self.windowwidget is not None:
# Save window geometry to restore it when undocking the plugin
# again.
Expand All @@ -793,15 +834,20 @@ def close_window(self, save_undocked=False):

if self.dockwidget is not None:
self.sig_update_ancestor_requested.emit()
self.get_plugin().switch_to_plugin()
if switch_to_plugin:
# This is necessary to restore the main window layout when
# there's a maximized plugin on it when the user requests
# to dock back this plugin.
self.get_plugin().switch_to_plugin()

self.dockwidget.setWidget(self)
self.dockwidget.setVisible(True)
self.dockwidget.raise_()
self._update_actions()
else:
# Reset undocked state
self.set_conf('undocked_on_window_close', False)

# ---- SpyderDockwidget handling
# -------------------------------------------------------------------------
def change_visibility(self, enable, force_focus=None):
"""Dock widget visibility has changed."""
if self.dockwidget is None:
Expand Down Expand Up @@ -853,15 +899,40 @@ def toggle_view(self, checked):
if not self.dockwidget:
return

# Dock plugin if it's undocked before hiding it.
if self.windowwidget is not None:
self.close_window(save_undocked=True)
# To check if the plugin needs to be undocked at the end
undock = False

if checked:
self.dockwidget.show()
self.dockwidget.raise_()
self.is_visible = True

# We need to undock the plugin if that was its state before
# toggling its visibility.
if (
# Don't run this while the window is being created to not
# affect setting up the layout at startup.
not self._plugin.main.is_setting_up
and self.get_conf(
'window_was_undocked_before_hiding', default=False
)
):
undock = True
else:
if self.windowwidget is not None:
logger.debug(f"Closing window of plugin {self._name}")

# This avoids trying to close the window twice: once when
# calling _close_window below and the other when Qt calls the
# closeEvent of windowwidget
self.windowwidget.blockSignals(True)

# Dock plugin if it's undocked before hiding it.
self._close_window(switch_to_plugin=False)

# Save undocked state to restore it afterwards.
self.set_conf('window_was_undocked_before_hiding', True)

self.dockwidget.hide()
self.is_visible = False

Expand All @@ -873,6 +944,15 @@ def toggle_view(self, checked):

self.sig_toggle_view_changed.emit(checked)

logger.debug(
f"Plugin {self._name} is now {'visible' if checked else 'hidden'}"
)

if undock:
# We undock the plugin at this point so that the View menu is
# updated correctly.
self.create_window()

def create_dockwidget(self, mainwindow):
"""
Add to parent QMainWindow as a dock widget.
Expand All @@ -897,7 +977,8 @@ def close_dock(self):
"""
Close the dockwidget.
"""
self.toggle_view(False)
logger.debug(f"Hiding plugin {self._name}")
self.toggle_view_action.setChecked(False)

def lock_unlock_position(self):
"""
Expand Down Expand Up @@ -926,7 +1007,7 @@ def set_maximized_state(self, state):
# This is necessary for old API plugins interacting with new ones.
self._plugin._ismaximized = state

# --- API: methods to define or override
# ---- API: methods to define or override
# ------------------------------------------------------------------------
def get_title(self):
"""
Expand Down
9 changes: 6 additions & 3 deletions spyder/api/widgets/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -268,20 +268,22 @@ def add_item_to_menu(self, action_or_menu, menu, section=None,
def _create_menu(
self,
menu_id: str,
parent: Optional[QWidget] = None,
title: Optional[str] = None,
icon: Optional[QIcon] = None,
reposition: Optional[bool] = True,
register: bool = True,
min_width: Optional[int] = None,
MenuClass=SpyderMenu
) -> SpyderMenu:
"""
Create a SpyderMenu or a subclass of it.

Notes
-----
* This method should only be used directly to generate a menu that is a
* This method must only be used directly to generate a menu that is a
subclass of SpyderMenu.
* Refer to the documentation for `create_menu` to learn about its args.
* Refer to the documentation for `SpyderMenu` to learn about its args.
"""
if register:
menus = getattr(self, '_menus', None)
Expand All @@ -294,9 +296,10 @@ def _create_menu(
)

menu = MenuClass(
parent=self,
parent=self if parent is None else parent,
menu_id=menu_id,
title=title,
min_width=min_width,
reposition=reposition
)

Expand Down