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: Create a separate window when undocking plugins #3824

Merged
merged 26 commits into from Oct 14, 2017
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
6f45e71
New behavior when undoking Editor window.
dalthviz Dec 10, 2016
ece67e1
Fixed misspell.
dalthviz Dec 11, 2016
238ffd6
Added undock function. Added restriction to create more than one edit…
dalthviz Dec 14, 2016
71ec0aa
Fixed code style problem.
dalthviz Dec 14, 2016
6769d6d
Fixed code style problem.
dalthviz Dec 14, 2016
ecac519
Fixed problem with attribute create_new_window of the EditorStack.
dalthviz Dec 14, 2016
5dad62d
Moves undock method to the Editor.
dalthviz Jun 21, 2017
e679c2d
Initial implementation to undock as a window.
dalthviz Jul 15, 2017
aa36f3c
Improvements and some plugins with all the functionality.
dalthviz Jul 15, 2017
aaa59e8
Adds undock action to the Variable Explorer and the Internal Console.
dalthviz Jul 16, 2017
21cee40
Adds a validation for the parent.
dalthviz Jul 17, 2017
b668163
Initial base. Some plugins working.
dalthviz Aug 18, 2017
d2589cd
Merge branch 'master' into fixes_issue_3790_2
dalthviz Aug 19, 2017
d7db783
Add more plugins.
dalthviz Sep 17, 2017
a9da3f1
Fix variable explorer actions.
dalthviz Sep 17, 2017
160adca
Fix explorer.
dalthviz Sep 17, 2017
9f134f8
Fix code style issues.
dalthviz Sep 17, 2017
b899227
Use tool button from the plugin in the IPython console.
dalthviz Sep 21, 2017
c37f541
Merge branch 'master' into fixes_issue_3790_2
dalthviz Sep 22, 2017
072c4a3
Fix unabled actions variableexplorer.
dalthviz Sep 22, 2017
9fae0b0
Merge branch 'fixes_issue_3790_2' of https://github.com/dalthviz/spyd…
dalthviz Sep 22, 2017
43a55ad
Fix code style issues.
dalthviz Sep 22, 2017
5ebbc84
Change None for MENU_SEPARATOR, revert some unnecessary changes.
dalthviz Oct 10, 2017
f9168c4
Merge branch 'master' into fixes_issue_3790_2
dalthviz Oct 12, 2017
c186de6
Change 'Undock the plugin' to 'Undock'. Change 'menu' for 'options_me…
dalthviz Oct 12, 2017
3437f75
Fix tests.
dalthviz Oct 12, 2017
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
28 changes: 23 additions & 5 deletions spyder/api/plugins.py
Expand Up @@ -18,16 +18,18 @@
# Third party imports
from qtpy.QtCore import Qt, Signal
from qtpy.QtGui import QCursor
from qtpy.QtWidgets import QApplication, QMainWindow, QMessageBox
from qtpy.QtWidgets import QApplication, QMenu, QMessageBox, QToolButton

# Local imports
from spyder.config.gui import get_color_scheme
from spyder.config.main import CONF
from spyder.config.user import NoDefault
from spyder.plugins.base import BasePluginWidget
from spyder.plugins.base import _, BasePluginWidget
from spyder.py3compat import configparser, is_text_string
from spyder.utils import icon_manager as ima
from spyder.utils.qthelpers import toggle_actions
from spyder.utils.qthelpers import (add_actions, create_toolbutton,
MENU_SEPARATOR, toggle_actions)
from spyder.plugins.base import PluginMainWindow


class PluginWidget(BasePluginWidget):
Expand Down Expand Up @@ -66,6 +68,10 @@ def __init__(self, main=None):
self.mainwindow = None
self.ismaximized = False
self.isvisible = False
self.options_button = create_toolbutton(self, text=_('Options'),
icon=ima.icon('tooloptions'))
self.options_button.setPopupMode(QToolButton.InstantPopup)
self.menu = QMenu(self)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

menu is too generic. Please change it to options_menu.


# NOTE: Don't use the default option of CONF.get to assign a
# None shortcut to plugins that don't have one. That will mess
Expand All @@ -88,7 +94,12 @@ def initialize_plugin(self):
It must be run at the end of __init__
"""
self.create_toggle_view_action()
self.plugin_actions = self.get_plugin_actions()
self.create_undock_action()
self.plugin_actions = self.get_plugin_actions() + [MENU_SEPARATOR,
self.undock_action]
add_actions(self.menu, self.plugin_actions)
self.options_button.setMenu(self.menu)
self.menu.aboutToShow.connect(self.refresh_actions)
self.sig_show_message.connect(self.show_message)
self.sig_update_plugin_title.connect(self.update_plugin_title)
self.sig_option_changed.connect(self.set_option)
Expand All @@ -100,7 +111,7 @@ def create_mainwindow(self):

Note: this method is currently not used in Spyder core plugins
"""
self.mainwindow = mainwindow = QMainWindow()
self.mainwindow = mainwindow = PluginMainWindow(self)
mainwindow.setAttribute(Qt.WA_DeleteOnClose)
icon = self.get_plugin_icon()
if is_text_string(icon):
Expand Down Expand Up @@ -188,6 +199,13 @@ def show_compatibility_message(self, message):
messageBox.setStandardButtons(QMessageBox.Ok)
messageBox.show()

def refresh_actions(self):
"""Clear the menu of the plugin and add the actions."""
self.menu.clear()
self.plugin_actions = self.get_plugin_actions() + [MENU_SEPARATOR,
self.undock_action]
add_actions(self.menu, self.plugin_actions)


class SpyderPluginWidget(PluginWidget):
"""
Expand Down
57 changes: 55 additions & 2 deletions spyder/plugins/base.py
Expand Up @@ -9,14 +9,15 @@
"""

# Third party imports
from qtpy.QtCore import Qt
from qtpy.QtCore import Qt, Slot
from qtpy.QtGui import QKeySequence
from qtpy.QtWidgets import QDockWidget, QShortcut, QWidget
from qtpy.QtWidgets import QDockWidget, QMainWindow, QShortcut, QWidget

# Local imports
from spyder.config.base import _
from spyder.config.gui import get_font
from spyder.config.main import CONF
from spyder.utils import icon_manager as ima
from spyder.utils.qthelpers import create_action
from spyder.widgets.dock import SpyderDockWidget

Expand Down Expand Up @@ -85,8 +86,10 @@ def create_dockwidget(self):
dock.setWidget(self)
self.update_margins()
dock.visibilityChanged.connect(self.visibility_changed)
dock.topLevelChanged.connect(self.create_window)
dock.plugin_closed.connect(self.plugin_closed)
self.dockwidget = dock
self.undocked = False
if self.shortcut is not None:
sc = QShortcut(QKeySequence(self.shortcut), self.main,
self.switch_to_plugin)
Expand Down Expand Up @@ -169,3 +172,53 @@ def toggle_view(self, checked):
self.dockwidget.raise_()
else:
self.dockwidget.hide()

@Slot()
def create_window(self):
"""Open a window of the plugin instead of undocking it."""
if self.dockwidget.isFloating() and not self.undocked:
self.dockwidget.setFloating(False)
self.dockwidget.setVisible(False)
self.undock_action.setDisabled(True)
window = self.create_mainwindow()
window.show()
elif self.undocked:
self.undock_action.setDisabled(True)
else:
self.undock_action.setDisabled(False)
self.undocked = False

def create_mainwindow(self):
"""
Create a QMainWindow instance containing this plugin.
"""
raise NotImplementedError

def create_undock_action(self):
"""Create the undock action for the plugin."""
self.undock_action = create_action(self,
_("Undock the plugin"),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's change this to simply Undock, instead of Undock the plugin.

icon=ima.icon('newwindow'),
tip=_("Undock the plugin"),
triggered=self.undock_plugin)

def undock_plugin(self):
"""Undocks the plugin from the MainWindow."""
self.undocked = True
self.dockwidget.setFloating(True)
self.undock_action.setDisabled(True)


class PluginMainWindow(QMainWindow):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please move this class to be before BasePluginWidget

"""Spyder Plugin MainWindow class."""
def __init__(self, plugin):
QMainWindow.__init__(self)
self.plugin = plugin

def closeEvent(self, event):
"""Reimplement Qt method."""
self.plugin.dockwidget.setWidget(self.plugin)
self.plugin.dockwidget.setVisible(True)
self.plugin.undock_action.setDisabled(False)
self.plugin.switch_to_plugin()
QMainWindow.closeEvent(self, event)
19 changes: 11 additions & 8 deletions spyder/plugins/console.py
Expand Up @@ -19,7 +19,7 @@
# Third party imports
from qtpy.compat import getopenfilename
from qtpy.QtCore import Signal, Slot, Qt
from qtpy.QtWidgets import (QInputDialog, QLineEdit, QMenu, QVBoxLayout,
from qtpy.QtWidgets import (QInputDialog, QLineEdit, QMenu, QHBoxLayout,
QMessageBox)

# Local imports
Expand All @@ -29,7 +29,8 @@
from spyder.utils.environ import EnvDialog
from spyder.utils.misc import get_error_match, remove_backslashes
from spyder.utils.qthelpers import (add_actions, create_action,
DialogManager, mimedata2url)
create_plugin_layout, DialogManager,
mimedata2url, MENU_SEPARATOR)
from spyder.widgets.internalshell import InternalShell
from spyder.widgets.findreplace import FindReplace
from spyder.widgets.variableexplorer.collectionseditor import CollectionsEditor
Expand Down Expand Up @@ -78,7 +79,11 @@ def __init__(self, parent=None, namespace=None, commands=[], message=None,
self.register_widget_shortcuts(self.find_widget)

# Main layout
layout = QVBoxLayout()
btn_layout = QHBoxLayout()
btn_layout.setAlignment(Qt.AlignLeft)
btn_layout.addStretch()
btn_layout.addWidget(self.options_button, Qt.AlignRight)
layout = create_plugin_layout(btn_layout)
layout.addWidget(self.shell)
layout.addWidget(self.find_widget)
self.setLayout(layout)
Expand Down Expand Up @@ -184,11 +189,9 @@ def get_plugin_actions(self):
codecompenter_action, exteditor_action))

plugin_actions = [None, run_action, environ_action, syspath_action,
option_menu, None, quit_action]

# Add actions to context menu
add_actions(self.shell.menu, plugin_actions)

option_menu, MENU_SEPARATOR, quit_action,
self.undock_action]

return plugin_actions

def register_plugin(self):
Expand Down
32 changes: 30 additions & 2 deletions spyder/plugins/editor.py
Expand Up @@ -1391,7 +1391,7 @@ def register_editorstack(self, editorstack):
editorstack.file_saved.connect(self.file_saved_in_editorstack)
editorstack.file_renamed_in_data.connect(
self.file_renamed_in_data_in_editorstack)
editorstack.create_new_window.connect(self.create_new_window)
editorstack.sig_undock_window.connect(self.undock_plugin)
editorstack.opened_files_list_changed.connect(
self.opened_files_list_changed)
editorstack.analysis_results_changed.connect(
Expand Down Expand Up @@ -1508,7 +1508,32 @@ def setup_other_windows(self):
for layout_settings in self.editorwindows_to_be_created:
win = self.create_new_window()
win.set_layout_settings(layout_settings)


@Slot()
def create_window(self):
"""Open a new window instance of the Editor instead of undocking it."""
if self.dockwidget.isFloating() and not self.undocked:
self.dockwidget.setVisible(False)
self.create_new_window()
self.toggle_view_action.setChecked(False)
self.dockwidget.setFloating(False)
self.undocked = False
if self.get_current_editorstack():
self.get_current_editorstack().new_window = False

def undock_plugin(self):
"""Undocks the Editor window."""
super(Editor, self).undock_plugin()
self.get_current_editorstack().new_window = True

def switch_to_plugin(self):
"""
Reimplemented method to desactivate shortcut when
opening a new window.
"""
if not self.editorwindows or self.dockwidget.isVisible():
super(Editor, self).switch_to_plugin()

def create_new_window(self):
oe_options = self.outlineexplorer.explorer.get_options()
fullpath_sorting=self.get_option('fullpath_sorting', True),
Expand All @@ -1522,6 +1547,7 @@ def create_new_window(self):
window.load_toolbars()
window.resize(self.size())
window.show()
window.editorwidget.editorsplitter.editorstack.new_window = True
self.register_editorwindow(window)
window.destroyed.connect(lambda: self.unregister_editorwindow(window))
return window
Expand All @@ -1530,6 +1556,8 @@ def register_editorwindow(self, window):
self.editorwindows.append(window)

def unregister_editorwindow(self, window):
if len(self.editorwindows) == 1:
self.toggle_view_action.setChecked(True)
self.editorwindows.pop(self.editorwindows.index(window))


Expand Down
14 changes: 8 additions & 6 deletions spyder/plugins/explorer.py
Expand Up @@ -20,10 +20,9 @@
# Local imports
from spyder.config.base import _
from spyder.api.plugins import SpyderPluginWidget
from spyder.py3compat import to_text_string
from spyder.utils.qthelpers import add_actions, MENU_SEPARATOR
from spyder.widgets.explorer import ExplorerWidget


class Explorer(SpyderPluginWidget):
"""File and Directories Explorer DockWidget."""

Expand All @@ -33,19 +32,20 @@ def __init__(self, parent=None):
"""Initialization."""
SpyderPluginWidget.__init__(self, parent)

# Initialize plugin
self.initialize_plugin()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(A comment out of the scope of this PR)

We could consider moving initialize_plugin plugin to a meta class (or __init_subclass__ in python3) so plugins shouldn't have to add it. I'll open a new issue about this for further discussing (there's also other things that we could improve using a metaclass)


self.fileexplorer = ExplorerWidget(
self,
name_filters=self.get_option('name_filters'),
show_all=self.get_option('show_all'),
show_icontext=self.get_option('show_icontext'))
show_icontext=self.get_option('show_icontext'),
options_button=self.options_button)

layout = QVBoxLayout()
layout.addWidget(self.fileexplorer)
self.setLayout(layout)

# Initialize plugin
self.initialize_plugin()

#------ SpyderPluginWidget API ---------------------------------------------
def get_plugin_title(self):
"""Return widget title"""
Expand All @@ -66,9 +66,11 @@ def register_plugin(self):
"""Register plugin in Spyder's main window"""
ipyconsole = self.main.ipyconsole
treewidget = self.fileexplorer.treewidget
undock = [MENU_SEPARATOR, self.undock_action]

self.main.add_dockwidget(self)
self.fileexplorer.sig_open_file.connect(self.main.open_file)
add_actions(self.fileexplorer.menu, undock)

treewidget.sig_edit.connect(self.main.editor.load)
treewidget.sig_removed.connect(self.main.editor.removed)
Expand Down
3 changes: 2 additions & 1 deletion spyder/plugins/findinfiles.py
Expand Up @@ -61,7 +61,8 @@ def __init__(self, parent=None):
exclude, exclude_idx, exclude_regexp,
supported_encodings,
in_python_path, more_options,
case_sensitive, path_history)
case_sensitive, path_history,
options_button=self.options_button)

layout = QVBoxLayout()
layout.addWidget(self.findinfiles)
Expand Down
19 changes: 9 additions & 10 deletions spyder/plugins/help.py
Expand Up @@ -31,7 +31,8 @@
from spyder.utils.help.sphinxify import (CSS_PATH, generate_context,
sphinxify, usage, warning)
from spyder.utils.qthelpers import (add_actions, create_action,
create_toolbutton, create_plugin_layout)
create_toolbutton, create_plugin_layout,
MENU_SEPARATOR)
from spyder.widgets.browser import FrameWebView
from spyder.widgets.comboboxes import EditableComboBox
from spyder.widgets.findreplace import FindReplace
Expand Down Expand Up @@ -425,15 +426,13 @@ def __init__(self, parent):
self._update_lock_icon()

# Option menu
options_button = create_toolbutton(self, text=_('Options'),
icon=ima.icon('tooloptions'))
options_button.setPopupMode(QToolButton.InstantPopup)
menu = QMenu(self)
add_actions(menu, [self.rich_text_action, self.plain_text_action,
self.show_source_action, None,
self.auto_import_action])
options_button.setMenu(menu)
layout_edit.addWidget(options_button)
self.menu = QMenu(self)
add_actions(self.menu, [self.rich_text_action, self.plain_text_action,
self.show_source_action, MENU_SEPARATOR,
self.auto_import_action, MENU_SEPARATOR,
self.undock_action])
self.options_button.setMenu(self.menu)
layout_edit.addWidget(self.options_button)

if self.rich_help:
self.switch_to_rich_text()
Expand Down