Skip to content

Commit 543dc35

Browse files
authored
Merge pull request #19784 from ccordoba12/make-close-and-dock-different
PR: Remember undocked state of plugins when closed and allow to close Outline when the Editor is maximized or in an Editor window
2 parents 1475484 + 1149dcf commit 543dc35

File tree

24 files changed

+950
-381
lines changed

24 files changed

+950
-381
lines changed

spyder/api/plugins/new_api.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1072,7 +1072,7 @@ def after_long_process(self, message=""):
10721072
super().after_long_process(message)
10731073
self.get_widget().stop_spinner()
10741074

1075-
def get_widget(self):
1075+
def get_widget(self) -> PluginMainWidget:
10761076
"""
10771077
Return the plugin main widget.
10781078
"""
@@ -1128,7 +1128,7 @@ def create_window(self):
11281128
self.get_widget().create_window()
11291129

11301130
def close_window(self, save_undocked=False):
1131-
self.get_widget().close_window(save_undocked=save_undocked)
1131+
self.get_widget()._close_window(save_undocked=save_undocked)
11321132

11331133
def change_visibility(self, state, force_focus=False):
11341134
self.get_widget().change_visibility(state, force_focus)

spyder/api/widgets/main_widget.py

+105-24
Original file line numberDiff line numberDiff line change
@@ -354,11 +354,11 @@ def _setup(self):
354354
text=_("Dock"),
355355
tip=_("Dock the pane"),
356356
icon=self.create_icon('dock'),
357-
triggered=self.close_window,
357+
triggered=self.dock_window,
358358
)
359359
self.lock_unlock_action = self.create_action(
360360
name=PluginMainWidgetActions.LockUnlockPosition,
361-
text=_("Unlock position"),
361+
text=_("Move"),
362362
tip=_("Unlock to move pane to another position"),
363363
icon=self.create_icon('drag_dock_widget'),
364364
triggered=self.lock_unlock_position,
@@ -387,7 +387,7 @@ def _setup(self):
387387
)
388388

389389
for item in [self.lock_unlock_action, self.undock_action,
390-
self.close_action, self.dock_action]:
390+
self.dock_action, self.close_action]:
391391
self.add_item_to_menu(
392392
item,
393393
self._options_menu,
@@ -416,7 +416,6 @@ def _update_actions(self):
416416
"""
417417
show_dock_actions = self.windowwidget is None
418418
self.undock_action.setVisible(show_dock_actions)
419-
self.close_action.setVisible(show_dock_actions)
420419
self.lock_unlock_action.setVisible(show_dock_actions)
421420
self.dock_action.setVisible(not show_dock_actions)
422421

@@ -442,13 +441,13 @@ def _on_title_bar_shown(self, visible):
442441
Actions to perform when the title bar is shown/hidden.
443442
"""
444443
if visible:
445-
self.lock_unlock_action.setText(_('Lock position'))
444+
self.lock_unlock_action.setText(_('Lock'))
446445
self.lock_unlock_action.setIcon(self.create_icon('lock_open'))
447446
for method_name in ['setToolTip', 'setStatusTip']:
448447
method = getattr(self.lock_unlock_action, method_name)
449448
method(_("Lock pane to the current position"))
450449
else:
451-
self.lock_unlock_action.setText(_('Unlock position'))
450+
self.lock_unlock_action.setText(_('Move'))
452451
self.lock_unlock_action.setIcon(
453452
self.create_icon('drag_dock_widget'))
454453
for method_name in ['setToolTip', 'setStatusTip']:
@@ -703,21 +702,21 @@ def render_toolbars(self):
703702
This action can only be performed once.
704703
"""
705704
# if not self._toolbars_already_rendered:
706-
self._main_toolbar._render()
707-
self._corner_toolbar._render()
705+
self._main_toolbar.render()
706+
self._corner_toolbar.render()
708707
for __, toolbar in self._auxiliary_toolbars.items():
709-
toolbar._render()
708+
toolbar.render()
710709

711710
# self._toolbars_already_rendered = True
712711

713-
# ---- SpyderDockwidget handling
712+
# ---- SpyderWindowWidget handling
714713
# -------------------------------------------------------------------------
715714
@Slot()
716715
def create_window(self):
717716
"""
718-
Create a QMainWindow instance containing this widget.
717+
Create an undocked window containing this widget.
719718
"""
720-
logger.debug("Undocking plugin")
719+
logger.debug(f"Undocking plugin {self._name}")
721720

722721
# Widgets
723722
self.windowwidget = window = SpyderWindowWidget(self)
@@ -756,21 +755,63 @@ def create_window(self):
756755
window.show()
757756

758757
@Slot()
759-
def close_window(self, save_undocked=False):
758+
def dock_window(self):
759+
"""Dock undocked window back to the main window."""
760+
logger.debug(f"Docking window of plugin {self._name}")
761+
762+
# Reset undocked state
763+
self.set_conf('window_was_undocked_before_hiding', False)
764+
765+
# This avoids trying to close the window twice: once when calling
766+
# _close_window below and the other when Qt calls the closeEvent of
767+
# windowwidget
768+
self.windowwidget.blockSignals(True)
769+
770+
# Close window
771+
self._close_window(switch_to_plugin=True)
772+
773+
# Make plugin visible on main window
774+
self.dockwidget.setVisible(True)
775+
self.dockwidget.raise_()
776+
777+
@Slot()
778+
def close_window(self):
760779
"""
761-
Close QMainWindow instance that contains this widget.
780+
Close undocked window when clicking on the close window button.
781+
782+
Notes
783+
-----
784+
* This can either dock or hide the window, depending on whether the
785+
user hid the window before.
786+
* The default behavior is to dock the window, so that new users can
787+
experiment with the dock/undock functionality without surprises.
788+
* If the user closes the window by clicking on the `Close` action in
789+
the plugin's Options menu or by going to the `View > Panes` menu,
790+
then we will hide it when they click on the close button again.
791+
That gives users the ability to show/hide plugins without
792+
docking/undocking them first.
793+
"""
794+
if self.get_conf('window_was_undocked_before_hiding', default=False):
795+
self.close_dock()
796+
else:
797+
self.dock_window()
798+
799+
def _close_window(self, save_undocked=False, switch_to_plugin=True):
800+
"""
801+
Helper function to close the undocked window with different parameters.
762802
763803
Parameters
764804
----------
765805
save_undocked : bool, optional
766806
True if the undocked state needs to be saved. The default is False.
807+
switch_to_plugin : bool, optional
808+
Whether to switch to the plugin after closing the window. The
809+
default is True.
767810
768811
Returns
769812
-------
770813
None.
771814
"""
772-
logger.debug("Docking plugin back to the main window")
773-
774815
if self.windowwidget is not None:
775816
# Save window geometry to restore it when undocking the plugin
776817
# again.
@@ -793,15 +834,20 @@ def close_window(self, save_undocked=False):
793834

794835
if self.dockwidget is not None:
795836
self.sig_update_ancestor_requested.emit()
796-
self.get_plugin().switch_to_plugin()
837+
if switch_to_plugin:
838+
# This is necessary to restore the main window layout when
839+
# there's a maximized plugin on it when the user requests
840+
# to dock back this plugin.
841+
self.get_plugin().switch_to_plugin()
842+
797843
self.dockwidget.setWidget(self)
798-
self.dockwidget.setVisible(True)
799-
self.dockwidget.raise_()
800844
self._update_actions()
801845
else:
802846
# Reset undocked state
803847
self.set_conf('undocked_on_window_close', False)
804848

849+
# ---- SpyderDockwidget handling
850+
# -------------------------------------------------------------------------
805851
def change_visibility(self, enable, force_focus=None):
806852
"""Dock widget visibility has changed."""
807853
if self.dockwidget is None:
@@ -853,15 +899,40 @@ def toggle_view(self, checked):
853899
if not self.dockwidget:
854900
return
855901

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

860905
if checked:
861906
self.dockwidget.show()
862907
self.dockwidget.raise_()
863908
self.is_visible = True
909+
910+
# We need to undock the plugin if that was its state before
911+
# toggling its visibility.
912+
if (
913+
# Don't run this while the window is being created to not
914+
# affect setting up the layout at startup.
915+
not self._plugin.main.is_setting_up
916+
and self.get_conf(
917+
'window_was_undocked_before_hiding', default=False
918+
)
919+
):
920+
undock = True
864921
else:
922+
if self.windowwidget is not None:
923+
logger.debug(f"Closing window of plugin {self._name}")
924+
925+
# This avoids trying to close the window twice: once when
926+
# calling _close_window below and the other when Qt calls the
927+
# closeEvent of windowwidget
928+
self.windowwidget.blockSignals(True)
929+
930+
# Dock plugin if it's undocked before hiding it.
931+
self._close_window(switch_to_plugin=False)
932+
933+
# Save undocked state to restore it afterwards.
934+
self.set_conf('window_was_undocked_before_hiding', True)
935+
865936
self.dockwidget.hide()
866937
self.is_visible = False
867938

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

874945
self.sig_toggle_view_changed.emit(checked)
875946

947+
logger.debug(
948+
f"Plugin {self._name} is now {'visible' if checked else 'hidden'}"
949+
)
950+
951+
if undock:
952+
# We undock the plugin at this point so that the View menu is
953+
# updated correctly.
954+
self.create_window()
955+
876956
def create_dockwidget(self, mainwindow):
877957
"""
878958
Add to parent QMainWindow as a dock widget.
@@ -897,7 +977,8 @@ def close_dock(self):
897977
"""
898978
Close the dockwidget.
899979
"""
900-
self.toggle_view(False)
980+
logger.debug(f"Hiding plugin {self._name}")
981+
self.toggle_view_action.setChecked(False)
901982

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

929-
# --- API: methods to define or override
1010+
# ---- API: methods to define or override
9301011
# ------------------------------------------------------------------------
9311012
def get_title(self):
9321013
"""

spyder/api/widgets/mixins.py

+6-3
Original file line numberDiff line numberDiff line change
@@ -268,20 +268,22 @@ def add_item_to_menu(self, action_or_menu, menu, section=None,
268268
def _create_menu(
269269
self,
270270
menu_id: str,
271+
parent: Optional[QWidget] = None,
271272
title: Optional[str] = None,
272273
icon: Optional[QIcon] = None,
273274
reposition: Optional[bool] = True,
274275
register: bool = True,
276+
min_width: Optional[int] = None,
275277
MenuClass=SpyderMenu
276278
) -> SpyderMenu:
277279
"""
278280
Create a SpyderMenu or a subclass of it.
279281
280282
Notes
281283
-----
282-
* This method should only be used directly to generate a menu that is a
284+
* This method must only be used directly to generate a menu that is a
283285
subclass of SpyderMenu.
284-
* Refer to the documentation for `create_menu` to learn about its args.
286+
* Refer to the documentation for `SpyderMenu` to learn about its args.
285287
"""
286288
if register:
287289
menus = getattr(self, '_menus', None)
@@ -294,9 +296,10 @@ def _create_menu(
294296
)
295297

296298
menu = MenuClass(
297-
parent=self,
299+
parent=self if parent is None else parent,
298300
menu_id=menu_id,
299301
title=title,
302+
min_width=min_width,
300303
reposition=reposition
301304
)
302305

0 commit comments

Comments
 (0)