Skip to content

Commit

Permalink
Merge pull request #19784 from ccordoba12/make-close-and-dock-different
Browse files Browse the repository at this point in the history
PR: Remember undocked state of plugins when closed and allow to close Outline when the Editor is maximized or in an Editor window
  • Loading branch information
ccordoba12 authored May 25, 2024
2 parents 1475484 + 1149dcf commit 543dc35
Show file tree
Hide file tree
Showing 24 changed files with 950 additions and 381 deletions.
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
Loading

0 comments on commit 543dc35

Please sign in to comment.