From fe6f19e09871215bcf203a495b19f345adddf72e Mon Sep 17 00:00:00 2001 From: dpeterson Date: Mon, 14 May 2007 20:08:27 +0000 Subject: [PATCH 01/60] * Adding the framework directories for Envisage and PyFace in an egg-development world. From 6f626ad2d58a59f115394b65e7187af65a8ce1df Mon Sep 17 00:00:00 2001 From: phil Date: Wed, 1 Aug 2007 17:29:16 +0000 Subject: [PATCH 02/60] Added the stubs for the PyQt backend for TraitsUI. From e421b6562b3a68dee3a5a8d26cdf5e817c458552 Mon Sep 17 00:00:00 2001 From: Ilan Schnell Date: Wed, 20 Apr 2011 16:43:47 -0500 Subject: [PATCH 03/60] move pyface out of enthought namespace --- pyface/ui/qt4/workbench/__init__.py | 17 + pyface/ui/qt4/workbench/editor.py | 65 ++ pyface/ui/qt4/workbench/split_tab_widget.py | 987 ++++++++++++++++++ pyface/ui/qt4/workbench/view.py | 61 ++ .../qt4/workbench/workbench_window_layout.py | 599 +++++++++++ pyface/ui/wx/workbench/__init__.py | 6 + pyface/ui/wx/workbench/editor.py | 64 ++ .../workbench/editor_set_structure_handler.py | 100 ++ pyface/ui/wx/workbench/view.py | 70 ++ .../workbench/view_set_structure_handler.py | 73 ++ .../ui/wx/workbench/workbench_dock_window.py | 155 +++ .../wx/workbench/workbench_window_layout.py | 825 +++++++++++++++ pyface/workbench/__init__.py | 0 pyface/workbench/action/__init__.py | 0 pyface/workbench/action/action_controller.py | 37 + pyface/workbench/action/api.py | 3 + .../action/delete_user_perspective_action.py | 80 ++ pyface/workbench/action/menu_bar_manager.py | 36 + .../action/new_user_perspective_action.py | 55 + .../action/perspective_menu_manager.py | 135 +++ .../action/rename_user_perspective_action.py | 46 + .../action/reset_active_perspective_action.py | 40 + .../action/reset_all_perspectives_action.py | 40 + .../action/save_as_user_perspective_action.py | 54 + .../action/set_active_perspective_action.py | 67 ++ pyface/workbench/action/setattr_action.py | 36 + pyface/workbench/action/show_view_action.py | 44 + .../action/toggle_view_visibility_action.py | 107 ++ pyface/workbench/action/tool_bar_manager.py | 37 + .../action/user_perspective_action.py | 61 ++ .../workbench/action/user_perspective_name.py | 90 ++ pyface/workbench/action/view_chooser.py | 199 ++++ pyface/workbench/action/view_menu_manager.py | 145 +++ pyface/workbench/action/workbench_action.py | 20 + pyface/workbench/api.py | 20 + pyface/workbench/debug/__init__.py | 0 pyface/workbench/debug/api.py | 1 + pyface/workbench/debug/debug_view.py | 94 ++ pyface/workbench/editor.py | 21 + pyface/workbench/editor_manager.py | 97 ++ pyface/workbench/i_editor.py | 146 +++ pyface/workbench/i_editor_manager.py | 45 + pyface/workbench/i_perspective.py | 42 + pyface/workbench/i_perspective_item.py | 52 + pyface/workbench/i_view.py | 142 +++ pyface/workbench/i_workbench.py | 105 ++ pyface/workbench/i_workbench_part.py | 125 +++ pyface/workbench/i_workbench_window_layout.py | 375 +++++++ pyface/workbench/perspective.py | 195 ++++ pyface/workbench/perspective_item.py | 57 + pyface/workbench/traits_ui_editor.py | 92 ++ pyface/workbench/traits_ui_view.py | 108 ++ pyface/workbench/user_perspective_manager.py | 220 ++++ pyface/workbench/view.py | 21 + pyface/workbench/window_event.py | 26 + pyface/workbench/workbench.py | 424 ++++++++ pyface/workbench/workbench_window.py | 913 ++++++++++++++++ pyface/workbench/workbench_window_layout.py | 24 + pyface/workbench/workbench_window_memento.py | 30 + 59 files changed, 7729 insertions(+) create mode 100644 pyface/ui/qt4/workbench/__init__.py create mode 100644 pyface/ui/qt4/workbench/editor.py create mode 100644 pyface/ui/qt4/workbench/split_tab_widget.py create mode 100644 pyface/ui/qt4/workbench/view.py create mode 100755 pyface/ui/qt4/workbench/workbench_window_layout.py create mode 100644 pyface/ui/wx/workbench/__init__.py create mode 100644 pyface/ui/wx/workbench/editor.py create mode 100755 pyface/ui/wx/workbench/editor_set_structure_handler.py create mode 100644 pyface/ui/wx/workbench/view.py create mode 100755 pyface/ui/wx/workbench/view_set_structure_handler.py create mode 100755 pyface/ui/wx/workbench/workbench_dock_window.py create mode 100644 pyface/ui/wx/workbench/workbench_window_layout.py create mode 100755 pyface/workbench/__init__.py create mode 100755 pyface/workbench/action/__init__.py create mode 100644 pyface/workbench/action/action_controller.py create mode 100644 pyface/workbench/action/api.py create mode 100644 pyface/workbench/action/delete_user_perspective_action.py create mode 100644 pyface/workbench/action/menu_bar_manager.py create mode 100644 pyface/workbench/action/new_user_perspective_action.py create mode 100644 pyface/workbench/action/perspective_menu_manager.py create mode 100644 pyface/workbench/action/rename_user_perspective_action.py create mode 100644 pyface/workbench/action/reset_active_perspective_action.py create mode 100644 pyface/workbench/action/reset_all_perspectives_action.py create mode 100644 pyface/workbench/action/save_as_user_perspective_action.py create mode 100644 pyface/workbench/action/set_active_perspective_action.py create mode 100644 pyface/workbench/action/setattr_action.py create mode 100644 pyface/workbench/action/show_view_action.py create mode 100644 pyface/workbench/action/toggle_view_visibility_action.py create mode 100644 pyface/workbench/action/tool_bar_manager.py create mode 100644 pyface/workbench/action/user_perspective_action.py create mode 100644 pyface/workbench/action/user_perspective_name.py create mode 100644 pyface/workbench/action/view_chooser.py create mode 100644 pyface/workbench/action/view_menu_manager.py create mode 100644 pyface/workbench/action/workbench_action.py create mode 100755 pyface/workbench/api.py create mode 100644 pyface/workbench/debug/__init__.py create mode 100644 pyface/workbench/debug/api.py create mode 100644 pyface/workbench/debug/debug_view.py create mode 100755 pyface/workbench/editor.py create mode 100755 pyface/workbench/editor_manager.py create mode 100755 pyface/workbench/i_editor.py create mode 100755 pyface/workbench/i_editor_manager.py create mode 100755 pyface/workbench/i_perspective.py create mode 100644 pyface/workbench/i_perspective_item.py create mode 100755 pyface/workbench/i_view.py create mode 100644 pyface/workbench/i_workbench.py create mode 100644 pyface/workbench/i_workbench_part.py create mode 100755 pyface/workbench/i_workbench_window_layout.py create mode 100755 pyface/workbench/perspective.py create mode 100755 pyface/workbench/perspective_item.py create mode 100644 pyface/workbench/traits_ui_editor.py create mode 100644 pyface/workbench/traits_ui_view.py create mode 100644 pyface/workbench/user_perspective_manager.py create mode 100755 pyface/workbench/view.py create mode 100644 pyface/workbench/window_event.py create mode 100755 pyface/workbench/workbench.py create mode 100755 pyface/workbench/workbench_window.py create mode 100755 pyface/workbench/workbench_window_layout.py create mode 100644 pyface/workbench/workbench_window_memento.py diff --git a/pyface/ui/qt4/workbench/__init__.py b/pyface/ui/qt4/workbench/__init__.py new file mode 100644 index 000000000..fc094dfe7 --- /dev/null +++ b/pyface/ui/qt4/workbench/__init__.py @@ -0,0 +1,17 @@ +#------------------------------------------------------------------------------ +# Copyright (c) 2007 by Enthought, Inc. +# All rights reserved. +#------------------------------------------------------------------------------ + +try: + __import__('pkg_resources').declare_namespace(__name__) +except: + pass + +# For py2app / py2exe support +try: + import modulefinder + for p in __path__: + modulefinder.AddPackagePath(__name__, p) +except: + pass diff --git a/pyface/ui/qt4/workbench/editor.py b/pyface/ui/qt4/workbench/editor.py new file mode 100644 index 000000000..f5beebce2 --- /dev/null +++ b/pyface/ui/qt4/workbench/editor.py @@ -0,0 +1,65 @@ +#------------------------------------------------------------------------------ +# Copyright (c) 2007, Riverbank Computing Limited +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD license. +# However, when used with the GPL version of PyQt the additional terms described in the PyQt GPL exception also apply + +# +# Author: Riverbank Computing Limited +# Description: +#------------------------------------------------------------------------------ + + +# Local imports. +from enthought.pyface.workbench.i_editor import MEditor + + +class Editor(MEditor): + """ The toolkit specific implementation of an Editor. + + See the IEditor interface for the API documentation. + + """ + + ########################################################################### + # 'IWorkbenchPart' interface. + ########################################################################### + + def create_control(self, parent): + """ Create the toolkit-specific control that represents the part. """ + + from enthought.qt import QtGui + + # By default we create a yellow panel! + control = QtGui.QWidget(parent) + + pal = control.palette() + pal.setColour(QtGui.QPalette.Window, QtCore.Qt.yellow) + control.setPalette(pal) + + control.setAutoFillBackground(True) + control.resize(100, 200) + + return control + + def destroy_control(self): + """ Destroy the toolkit-specific control that represents the part. """ + + if self.control is not None: + self.control.hide() + self.control.close() + self.control.deleteLater() + self.control = None + + return + + def set_focus(self): + """ Set the focus to the appropriate control in the part. """ + + if self.control is not None: + self.control.setFocus() + + return + +#### EOF ###################################################################### diff --git a/pyface/ui/qt4/workbench/split_tab_widget.py b/pyface/ui/qt4/workbench/split_tab_widget.py new file mode 100644 index 000000000..212e7044f --- /dev/null +++ b/pyface/ui/qt4/workbench/split_tab_widget.py @@ -0,0 +1,987 @@ +#------------------------------------------------------------------------------ +# Copyright (c) 2008, Riverbank Computing Limited +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD license. +# However, when used with the GPL version of PyQt the additional terms described in the PyQt GPL exception also apply + +#------------------------------------------------------------------------------ + +# Standard library imports. +import sys + +# Major library imports. +import sip +from enthought.qt import QtCore, QtGui + + +class SplitTabWidget(QtGui.QSplitter): + """ The SplitTabWidget class is a hierarchy of QSplitters the leaves of + which are QTabWidgets. Any tab may be moved around with the hierarchy + automatically extended and reduced as required. + """ + + # Signals for WorkbenchWindowLayout to handle + new_window_request = QtCore.Signal(QtCore.QPoint, QtGui.QWidget) + tab_close_request = QtCore.Signal(QtGui.QWidget) + tab_window_changed = QtCore.Signal(QtGui.QWidget) + + # The different hotspots of a QTabWidget. An non-negative value is a tab + # index and the hotspot is to the left of it. + _HS_NONE = -1 + _HS_AFTER_LAST_TAB = -2 + _HS_NORTH = -3 + _HS_SOUTH = -4 + _HS_EAST = -5 + _HS_WEST = -6 + _HS_OUTSIDE = -7 + + def __init__(self, *args): + """ Initialise the instance. """ + + QtGui.QSplitter.__init__(self, *args) + + self.clear() + + QtCore.QObject.connect(QtGui.qApp, + QtCore.SIGNAL('focusChanged(QWidget *,QWidget *)'), + self._focus_changed) + + def clear(self): + """ Restore the widget to its pristine state. """ + + w = None + for i in range(self.count()): + w = self.widget(i) + w.hide() + w.deleteLater() + del w + + self._repeat_focus_changes = True + self._rband = None + self._selected_tab_widget = None + self._selected_hotspot = self._HS_NONE + + self._current_tab_w = None + self._current_tab_idx = -1 + + def saveState(self): + """ Returns a Python object containing the saved state of the widget. + Widgets are saved only by their object name. + """ + + return self._save_qsplitter(self) + + def _save_qsplitter(self, qsplitter): + # A splitter state is a tuple of the QSplitter state (as a string) and + # the list of child states. + sp_ch_states = [] + + # Save the children. + for i in range(qsplitter.count()): + ch = qsplitter.widget(i) + + if isinstance(ch, _TabWidget): + # A tab widget state is a tuple of the current tab index and + # the list of individual tab states. + tab_states = [] + + for t in range(ch.count()): + # A tab state is a tuple of the widget's object name and + # the title. + name = unicode(ch.widget(t).objectName()) + title = unicode(ch.tabText(t)) + + tab_states.append((name, title)) + + ch_state = (ch.currentIndex(), tab_states) + else: + # Recurse down the tree of splitters. + ch_state = self._save_qsplitter(ch) + + sp_ch_states.append(ch_state) + + return (str(QtGui.QSplitter.saveState(qsplitter)), sp_ch_states) + + def restoreState(self, state, factory): + """ Restore the contents from the given state (returned by a previous + call to saveState()). factory is a callable that is passed the object + name of the widget that is in the state and needs to be restored. The + callable returns the restored widget. + """ + + # Ensure we are not restoring to a non-empty widget. + assert self.count() == 0 + + self._restore_qsplitter(state, factory, self) + + def _restore_qsplitter(self, state, factory, qsplitter): + sp_qstate, sp_ch_states = state + + # Go through each child state which will consist of a tuple of two + # objects. We use the type of the first to determine if the child is a + # tab widget or another splitter. + for ch_state in sp_ch_states: + if isinstance(ch_state[0], int): + current_idx, tabs = ch_state + + new_tab = _TabWidget(self) + + # Go through each tab and use the factory to restore the page. + for name, title in tabs: + page = factory(name) + + if page is not None: + new_tab.addTab(page, title) + + # Only add the new tab widget if it is used. + if new_tab.count() > 0: + qsplitter.addWidget(new_tab) + + # Set the correct tab as the current one. + new_tab.setCurrentIndex(current_idx) + else: + del new_tab + else: + new_qsp = QtGui.QSplitter() + + # Recurse down the tree of splitters. + self._restore_qsplitter(ch_state, factory, new_qsp) + + # Only add the new splitter if it is used. + if new_qsp.count() > 0: + qsplitter.addWidget(new_qsp) + else: + del new_qsp + + # Restore the QSplitter state (being careful to get the right + # implementation). + QtGui.QSplitter.restoreState(qsplitter, sp_qstate) + + def addTab(self, w, text): + """ Add a new tab to the main tab widget. """ + + # Find the first tab widget going down the left of the hierarchy. This + # will be the one in the top left corner. + if self.count() > 0: + ch = self.widget(0) + + while not isinstance(ch, _TabWidget): + assert isinstance(ch, QtGui.QSplitter) + ch = ch.widget(0) + else: + # There is no tab widget so create one. + ch = _TabWidget(self) + self.addWidget(ch) + + idx = ch.addTab(w, text) + + # If the tab has been added to the current tab widget then make it the + # current tab. + if ch is not self._current_tab_w: + self._set_current_tab(ch, idx) + ch.tabBar().setFocus() + + def _close_tab_request(self, w): + """ A close button was clicked in one of out _TabWidgets """ + + self.tab_close_request.emit(w) + + def setCurrentWidget(self, w): + """ Make the given widget current. """ + + tw, tidx = self._tab_widget(w) + + if tw is not None: + self._set_current_tab(tw, tidx) + + def setActiveIcon(self, w, icon=None): + """ Set the active icon on a widget. """ + + tw, tidx = self._tab_widget(w) + + if tw is not None: + if icon is None: + icon = tw.active_icon() + + tw.setTabIcon(tidx, icon) + + def setTabTextColor(self, w, color=None): + """ Set the tab text color on a particular widget w + """ + tw, tidx = self._tab_widget(w) + + if tw is not None: + if color is None: + # null color reverts to foreground role color + color = QtGui.QColor() + tw.tabBar().setTabTextColor(tidx, color) + + def setWidgetTitle(self, w, title): + """ Set the title for the given widget. """ + + tw, idx = self._tab_widget(w) + + if tw is not None: + tw.setTabText(idx, title) + + def _tab_widget(self, w): + """ Return the tab widget and index containing the given widget. """ + + for tw in self.findChildren(_TabWidget, None): + idx = tw.indexOf(w) + + if idx >= 0: + return (tw, idx) + + return (None, None) + + def _set_current_tab(self, tw, tidx): + """ Set the new current tab. """ + + # Handle the trivial case. + if self._current_tab_w is tw and self._current_tab_idx == tidx: + return + + if tw is not None: + tw.setCurrentIndex(tidx) + + # Save the new current widget. + self._current_tab_w = tw + self._current_tab_idx = tidx + + def _set_focus(self): + """ Set the focus to an appropriate widget in the current tab. """ + + # Only try and change the focus if the current focus isn't already a + # child of the widget. + w = self._current_tab_w.widget(self._current_tab_idx) + fw = self.window().focusWidget() + + if fw is not None and not w.isAncestorOf(fw): + # Find a widget to focus using the same method as + # QStackedLayout::setCurrentIndex(). First try the last widget + # with the focus. + nfw = w.focusWidget() + + if nfw is None: + # Next, try the first child widget in the focus chain. + nfw = fw.nextInFocusChain() + + while nfw is not fw: + if nfw.focusPolicy() & QtCore.Qt.TabFocus and \ + nfw.focusProxy() is None and \ + nfw.isVisibleTo(w) and \ + nfw.isEnabled() and \ + w.isAncestorOf(nfw): + break + + nfw = nfw.nextInFocusChain() + else: + # Fallback to the tab page widget. + nfw = w + + nfw.setFocus() + + def _focus_changed(self, old, new): + """ Handle a change in focus that affects the current tab. """ + + # It is possible for the C++ layer of this object to be deleted between + # the time when the focus change signal is emitted and time when the + # slots are dispatched by the Qt event loop. This may be a bug in PyQt4. + if sip.isdeleted(self): + return + + if self._repeat_focus_changes: + self.emit(QtCore.SIGNAL('focusChanged(QWidget *,QWidget *)'), + old, new) + + if isinstance(new, _DragableTabBar): + ntw = new.parent() + ntidx = ntw.currentIndex() + else: + ntw, ntidx = self._tab_widget_of(new) + + if ntw is not None: + self._set_current_tab(ntw, ntidx) + + # See if the widget that has lost the focus is ours. + otw, _ = self._tab_widget_of(old) + + if otw is not None or ntw is not None: + if ntw is None: + nw = None + else: + nw = ntw.widget(ntidx) + + self.emit(QtCore.SIGNAL('hasFocus'), nw) + + def _tab_widget_of(self, target): + """ Return the tab widget and index of the widget that contains the + given widget. + """ + + for tw in self.findChildren(_TabWidget, None): + for tidx in range(tw.count()): + w = tw.widget(tidx) + + if w is not None and w.isAncestorOf(target): + return (tw, tidx) + + return (None, None) + + def _move_left(self, tw, tidx): + """ Move the current tab to the one logically to the left. """ + + tidx -= 1 + + if tidx < 0: + # Find the tab widget logically to the left. + twlist = self.findChildren(_TabWidget, None) + i = twlist.index(tw) + i -= 1 + + if i < 0: + i = len(twlist) - 1 + + tw = twlist[i] + + # Move the to right most tab. + tidx = tw.count() - 1 + + self._set_current_tab(tw, tidx) + tw.setFocus() + + def _move_right(self, tw, tidx): + """ Move the current tab to the one logically to the right. """ + + tidx += 1 + + if tidx >= tw.count(): + # Find the tab widget logically to the right. + twlist = self.findChildren(_TabWidget, None) + i = twlist.index(tw) + i += 1 + + if i >= len(twlist): + i = 0 + + tw = twlist[i] + + # Move the to left most tab. + tidx = 0 + + self._set_current_tab(tw, tidx) + tw.setFocus() + + def _select(self, pos): + tw, hs, hs_geom = self._hotspot(pos) + + # See if the hotspot has changed. + if self._selected_tab_widget is not tw or self._selected_hotspot != hs: + if self._selected_tab_widget is not None: + self._rband.hide() + + if tw is not None and hs != self._HS_NONE: + if self._rband: + self._rband.deleteLater() + position = QtCore.QPoint(*hs_geom[0:2]) + window = tw.window() + self._rband = QtGui.QRubberBand(QtGui.QRubberBand.Rectangle, + window) + self._rband.move(window.mapFromGlobal(position)) + self._rband.resize(*hs_geom[2:4]) + self._rband.show() + + self._selected_tab_widget = tw + self._selected_hotspot = hs + + def _drop(self, pos, stab_w, stab): + self._rband.hide() + + # Get the destination locations. + dtab_w = self._selected_tab_widget + dhs = self._selected_hotspot + if dhs == self._HS_NONE: + return + elif dhs != self._HS_OUTSIDE: + dsplit_w = dtab_w.parent() + while not isinstance(dsplit_w, SplitTabWidget): + dsplit_w = dsplit_w.parent() + + self._selected_tab_widget = None + self._selected_hotspot = self._HS_NONE + + # See if the tab is being moved to a new window. + if dhs == self._HS_OUTSIDE: + # Disable tab tear-out for now. It works, but this is something that + # should be turned on manually. We need an interface for this. + #ticon, ttext, ttextcolor, twidg = self._remove_tab(stab_w, stab) + #self.new_window_request.emit(pos, twidg) + return + + # See if the tab is being moved to an existing tab widget. + if dhs >= 0 or dhs == self._HS_AFTER_LAST_TAB: + # Make sure it really is being moved. + if stab_w is dtab_w: + if stab == dhs: + return + + if dhs == self._HS_AFTER_LAST_TAB and stab == stab_w.count()-1: + return + + QtGui.qApp.blockSignals(True) + + ticon, ttext, ttextcolor, twidg = self._remove_tab(stab_w, stab) + + if dhs == self._HS_AFTER_LAST_TAB: + idx = dtab_w.addTab(twidg, ticon, ttext) + dtab_w.tabBar().setTabTextColor(idx, ttextcolor) + elif dtab_w is stab_w: + # Adjust the index if necessary in case the removal of the tab + # from its old position has skewed things. + dst = dhs + + if dhs > stab: + dst -= 1 + + idx = dtab_w.insertTab(dst, twidg, ticon, ttext) + dtab_w.tabBar().setTabTextColor(idx, ttextcolor) + else: + idx = dtab_w.insertTab(dhs, twidg, ticon, ttext) + dtab_w.tabBar().setTabTextColor(idx, ttextcolor) + + dsplit_w._set_current_tab(dtab_w, idx) + + else: + # Ignore drops to the same tab widget when it only has one tab. + if stab_w is dtab_w and stab_w.count() == 1: + return + + QtGui.qApp.blockSignals(True) + + # Remove the tab from its current tab widget and create a new one + # for it. + ticon, ttext, ttextcolor, twidg = self._remove_tab(stab_w, stab) + new_tw = _TabWidget(dsplit_w) + new_tw.addTab(twidg, ticon, ttext) + new_tw.tabBar().setTabTextColor(0, ttextcolor) + + # Get the splitter containing the destination tab widget. + dspl = dtab_w.parent() + dspl_idx = dspl.indexOf(dtab_w) + + if dhs in (self._HS_NORTH, self._HS_SOUTH): + dspl, dspl_idx = dsplit_w._horizontal_split(dspl, dspl_idx, dhs) + else: + dspl, dspl_idx = dsplit_w._vertical_split(dspl, dspl_idx, dhs) + + # Add the new tab widget in the right place. + dspl.insertWidget(dspl_idx, new_tw) + + dsplit_w._set_current_tab(new_tw, 0) + + dsplit_w._set_focus() + + # Signal that the tab's SplitTabWidget has changed, if necessary. + if dsplit_w != self: + self.tab_window_changed.emit(twidg) + + QtGui.qApp.blockSignals(False) + + def _horizontal_split(self, spl, idx, hs): + """ Returns a tuple of the splitter and index where the new tab widget + should be put. + """ + + if spl.orientation() == QtCore.Qt.Vertical: + if hs == self._HS_SOUTH: + idx += 1 + elif spl is self and spl.count() == 1: + # The splitter is the root and only has one child so we can just + # change its orientation. + spl.setOrientation(QtCore.Qt.Vertical) + + if hs == self._HS_SOUTH: + idx = -1 + else: + new_spl = QtGui.QSplitter(QtCore.Qt.Vertical) + new_spl.addWidget(spl.widget(idx)) + spl.insertWidget(idx, new_spl) + + if hs == self._HS_SOUTH: + idx = -1 + else: + idx = 0 + + spl = new_spl + + return (spl, idx) + + def _vertical_split(self, spl, idx, hs): + """ Returns a tuple of the splitter and index where the new tab widget + should be put. + """ + + if spl.orientation() == QtCore.Qt.Horizontal: + if hs == self._HS_EAST: + idx += 1 + elif spl is self and spl.count() == 1: + # The splitter is the root and only has one child so we can just + # change its orientation. + spl.setOrientation(QtCore.Qt.Horizontal) + + if hs == self._HS_EAST: + idx = -1 + else: + new_spl = QtGui.QSplitter(QtCore.Qt.Horizontal) + new_spl.addWidget(spl.widget(idx)) + spl.insertWidget(idx, new_spl) + + if hs == self._HS_EAST: + idx = -1 + else: + idx = 0 + + spl = new_spl + + return (spl, idx) + + def _remove_tab(self, tab_w, tab): + """ Remove a tab from a tab widget and return a tuple of the icon, + label text and the widget so that it can be recreated. + """ + + icon = tab_w.tabIcon(tab) + text = tab_w.tabText(tab) + text_color = tab_w.tabBar().tabTextColor(tab) + w = tab_w.widget(tab) + tab_w.removeTab(tab) + + return (icon, text, text_color, w) + + def _hotspot(self, pos): + """ Return a tuple of the tab widget, hotspot and hostspot geometry (as + a tuple) at the given position. + """ + global_pos = self.mapToGlobal(pos) + miss = (None, self._HS_NONE, None) + + # Get the bounding rect of the cloned QTbarBar. + top_widget = QtGui.qApp.topLevelAt(global_pos) + if isinstance(top_widget, QtGui.QTabBar): + cloned_rect = top_widget.frameGeometry() + else: + cloned_rect = None + + # Determine which visible SplitTabWidget, if any, is under the cursor + # (compensating for the cloned QTabBar that may be rendered over it). + split_widget = None + for top_widget in QtGui.qApp.topLevelWidgets(): + for split_widget in top_widget.findChildren(SplitTabWidget, None): + visible_region = split_widget.visibleRegion() + widget_pos = split_widget.mapFromGlobal(global_pos) + if cloned_rect and split_widget.geometry().contains(widget_pos): + visible_rect = visible_region.boundingRect() + widget_rect = QtCore.QRect( + split_widget.mapFromGlobal(cloned_rect.topLeft()), + split_widget.mapFromGlobal(cloned_rect.bottomRight())) + if not visible_rect.intersected(widget_rect).isEmpty(): + break + elif visible_region.contains(widget_pos): + break + else: + split_widget = None + if split_widget: + break + + # Handle a drag outside of any split tab widget. + if not split_widget: + if self.window().frameGeometry().contains(global_pos): + return miss + else: + return (None, self._HS_OUTSIDE, None) + + # Go through each tab widget. + pos = split_widget.mapFromGlobal(global_pos) + for tw in split_widget.findChildren(_TabWidget, None): + if tw.geometry().contains(tw.parent().mapFrom(split_widget, pos)): + break + else: + return miss + + # See if the hotspot is in the widget area. + widg = tw.currentWidget() + if widg is not None: + + # Get the widget's position relative to its parent. + wpos = widg.parent().mapFrom(split_widget, pos) + + if widg.geometry().contains(wpos): + # Get the position of the widget relative to itself (ie. the + # top left corner is (0, 0)). + p = widg.mapFromParent(wpos) + x = p.x() + y = p.y() + h = widg.height() + w = widg.width() + + # Get the global position of the widget. + gpos = widg.mapToGlobal(widg.pos()) + gx = gpos.x() + gy = gpos.y() + + # The corners of the widget belong to the north and south + # sides. + if y < h / 4: + return (tw, self._HS_NORTH, (gx, gy, w, h / 4)) + + if y >= (3 * h) / 4: + return (tw, self._HS_SOUTH, (gx, gy + (3*h) / 4, w, h / 4)) + + if x < w / 4: + return (tw, self._HS_WEST, (gx, gy, w / 4, h)) + + if x >= (3 * w) / 4: + return (tw, self._HS_EAST, (gx + (3*w) / 4, gy, w / 4, h)) + + return miss + + # See if the hotspot is in the tab area. + tpos = tw.mapFrom(split_widget, pos) + tab_bar = tw.tabBar() + top_bottom = tw.tabPosition() in (QtGui.QTabWidget.North, + QtGui.QTabWidget.South) + for i in range(tw.count()): + rect = tab_bar.tabRect(i) + + if rect.contains(tpos): + w = rect.width() + h = rect.height() + + # Get the global position. + gpos = tab_bar.mapToGlobal(rect.topLeft()) + gx = gpos.x() + gy = gpos.y() + + if top_bottom: + off = pos.x() - rect.x() + ext = w + gx -= w / 2 + else: + off = pos.y() - rect.y() + ext = h + gy -= h / 2 + + # See if it is in the left (or top) half or the right (or + # bottom) half. + if off < ext / 2: + return (tw, i, (gx, gy, w, h)) + + if top_bottom: + gx += w + else: + gy += h + + if i + 1 == tw.count(): + return (tw, self._HS_AFTER_LAST_TAB, (gx, gy, w, h)) + + return (tw, i + 1, (gx, gy, w, h)) + else: + rect = tab_bar.rect() + if rect.contains(tpos): + gpos = tab_bar.mapToGlobal(rect.topLeft()) + gx = gpos.x() + gy = gpos.y() + w = rect.width() + h = rect.height() + if top_bottom: + tab_widths = sum(tab_bar.tabRect(i).width() + for i in range(tab_bar.count())) + w -= tab_widths + gx += tab_widths + else: + tab_heights = sum(tab_bar.tabRect(i).height() + for i in range(tab_bar.count())) + h -= tab_heights + gy -= tab_heights + return (tw, self._HS_AFTER_LAST_TAB, (gx, gy, w, h)) + + return miss + + +active_style = """QTabWidget::pane { /* The tab widget frame */ + border: 2px solid #00FF00; + } +""" +inactive_style = """QTabWidget::pane { /* The tab widget frame */ + border: 2px solid #C2C7CB; + margin: 0px; + } +""" + +class _TabWidget(QtGui.QTabWidget): + """ The _TabWidget class is a QTabWidget with a dragable tab bar. """ + + # The active icon. It is created when it is first needed. + _active_icon = None + + def __init__(self, root, *args): + """ Initialise the instance. """ + + QtGui.QTabWidget.__init__(self, *args) + + # XXX this requires Qt > 4.5 + if sys.platform == 'darwin': + self.setDocumentMode(True) + #self.setStyleSheet(inactive_style) + + self._root = root + + # We explicitly pass the parent to the tab bar ctor to work round a bug + # in PyQt v4.2 and earlier. + self.setTabBar(_DragableTabBar(self._root, self)) + + self.setTabsClosable(True) + self.tabCloseRequested.connect(self._close_tab) + + def active_icon(self): + """ Return the QIcon to be used to indicate an active tab page. """ + + if _TabWidget._active_icon is None: + # The gradient start and stop colours. + start = QtGui.QColor(0, 255, 0) + stop = QtGui.QColor(0, 63, 0) + + size = self.iconSize() + width = size.width() + height = size.height() + + pm = QtGui.QPixmap(size) + + p = QtGui.QPainter() + p.begin(pm) + + # Fill the image background from the tab background. + p.initFrom(self.tabBar()) + p.fillRect(0, 0, width, height, p.background()) + + # Create the colour gradient. + rg = QtGui.QRadialGradient(width / 2, height / 2, width) + rg.setColorAt(0.0, start) + rg.setColorAt(1.0, stop) + + # Draw the circle. + p.setBrush(rg) + p.setPen(QtCore.Qt.NoPen) + p.setRenderHint(QtGui.QPainter.Antialiasing) + p.drawEllipse(0, 0, width, height) + + p.end() + + _TabWidget._active_icon = QtGui.QIcon(pm) + + return _TabWidget._active_icon + + def _still_needed(self): + """ Delete the tab widget (and any relevant parent splitters) if it is + no longer needed. + """ + + if self.count() == 0: + prune = self + parent = prune.parent() + + # Go up the QSplitter hierarchy until we find one with at least one + # sibling. + while parent is not self._root and parent.count() == 1: + prune = parent + parent = prune.parent() + + prune.hide() + prune.deleteLater() + + def tabRemoved(self, idx): + """ Reimplemented to update the record of the current tab if it is + removed. + """ + + self._still_needed() + + if self._root._current_tab_w is self and self._root._current_tab_idx == idx: + self._root._current_tab_w = None + + def _close_tab(self, index): + """ Close the current tab. """ + + self._root._close_tab_request(self.widget(index)) + + +class _DragableTabBar(QtGui.QTabBar): + """ The _DragableTabBar class is a QTabBar that can be dragged around. """ + + def __init__(self, root, parent): + """ Initialise the instance. """ + + QtGui.QTabBar.__init__(self, parent) + + # XXX this requires Qt > 4.5 + if sys.platform == 'darwin': + self.setDocumentMode(True) + + self._root = root + self._drag_state = None + + def keyPressEvent(self, e): + """ Reimplemented to handle traversal across different tab widgets. """ + + if e.key() == QtCore.Qt.Key_Left: + self._root._move_left(self.parent(), self.currentIndex()) + elif e.key() == QtCore.Qt.Key_Right: + self._root._move_right(self.parent(), self.currentIndex()) + else: + e.ignore() + + def mousePressEvent(self, e): + """ Reimplemented to handle mouse press events. """ + + # There is something odd in the focus handling where focus temporarily + # moves elsewhere (actually to a View) when switching to a different + # tab page. We suppress the notification so that the workbench doesn't + # temporarily make the View active. + self._root._repeat_focus_changes = False + QtGui.QTabBar.mousePressEvent(self, e) + self._root._repeat_focus_changes = True + + # Update the current tab. + self._root._set_current_tab(self.parent(), self.currentIndex()) + self._root._set_focus() + + if e.button() != QtCore.Qt.LeftButton: + return + + if self._drag_state is not None: + return + + # Potentially start dragging if the tab under the mouse is the current + # one (which will eliminate disabled tabs). + tab = self._tab_at(e.pos()) + + if tab < 0 or tab != self.currentIndex(): + return + + self._drag_state = _DragState(self._root, self, tab, e.pos()) + + def mouseMoveEvent(self, e): + """ Reimplemented to handle mouse move events. """ + + QtGui.QTabBar.mouseMoveEvent(self, e) + + if self._drag_state is None: + return + + if self._drag_state.dragging: + self._drag_state.drag(e.pos()) + else: + self._drag_state.start_dragging(e.pos()) + + # If the mouse has moved far enough that dragging has started then + # tell the user. + if self._drag_state.dragging: + QtGui.QApplication.setOverrideCursor(QtCore.Qt.OpenHandCursor) + + def mouseReleaseEvent(self, e): + """ Reimplemented to handle mouse release events. """ + + QtGui.QTabBar.mouseReleaseEvent(self, e) + + if e.button() != QtCore.Qt.LeftButton: + return + + if self._drag_state is not None and self._drag_state.dragging: + QtGui.QApplication.restoreOverrideCursor() + self._drag_state.drop(e.pos()) + + self._drag_state = None + + def _tab_at(self, pos): + """ Return the index of the tab at the given point. """ + + for i in range(self.count()): + if self.tabRect(i).contains(pos): + return i + + return -1 + + +class _DragState(object): + """ The _DragState class handles most of the work when dragging a tab. """ + + def __init__(self, root, tab_bar, tab, start_pos): + """ Initialise the instance. """ + + self.dragging = False + + self._root = root + self._tab_bar = tab_bar + self._tab = tab + self._start_pos = QtCore.QPoint(start_pos) + self._clone = None + + def start_dragging(self, pos): + """ Start dragging a tab. """ + + if (pos - self._start_pos).manhattanLength() <= QtGui.QApplication.startDragDistance(): + return + + self.dragging = True + + # Create a clone of the tab being moved (except for its icon). + otb = self._tab_bar + tab = self._tab + + ctb = self._clone = QtGui.QTabBar() + if sys.platform == 'darwin' and QtCore.QT_VERSION >= 0x40500: + ctb.setDocumentMode(True) + + ctb.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents) + ctb.setWindowFlags(QtCore.Qt.FramelessWindowHint | + QtCore.Qt.Tool | + QtCore.Qt.X11BypassWindowManagerHint) + ctb.setWindowOpacity(0.5) + ctb.setElideMode(otb.elideMode()) + ctb.setShape(otb.shape()) + + ctb.addTab(otb.tabText(tab)) + ctb.setTabTextColor(0, otb.tabTextColor(tab)) + + # The clone offset is the position of the clone relative to the mouse. + trect = otb.tabRect(tab) + self._clone_offset = trect.topLeft() - pos + + # The centre offset is the position of the center of the clone relative + # to the mouse. The center of the clone determines the hotspot, not + # the position of the mouse. + self._centre_offset = trect.center() - pos + + self.drag(pos) + + ctb.show() + + def drag(self, pos): + """ Handle the movement of the cloned tab during dragging. """ + + self._clone.move(self._tab_bar.mapToGlobal(pos) + self._clone_offset) + self._root._select(self._tab_bar.mapTo(self._root, + pos + self._centre_offset)) + + def drop(self, pos): + """ Handle the drop of the cloned tab. """ + + self.drag(pos) + self._clone = None + + global_pos = self._tab_bar.mapToGlobal(pos) + self._root._drop(global_pos, self._tab_bar.parent(), self._tab) + + self.dragging = False diff --git a/pyface/ui/qt4/workbench/view.py b/pyface/ui/qt4/workbench/view.py new file mode 100644 index 000000000..f1dd1c70a --- /dev/null +++ b/pyface/ui/qt4/workbench/view.py @@ -0,0 +1,61 @@ +#------------------------------------------------------------------------------ +# Copyright (c) 2007, Riverbank Computing Limited +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD license. +# However, when used with the GPL version of PyQt the additional terms described in the PyQt GPL exception also apply + +# +# Author: Riverbank Computing Limited +# Description: +#------------------------------------------------------------------------------ + + +# Enthought library imports. +from enthought.pyface.workbench.i_view import MView + + +class View(MView): + """ The toolkit specific implementation of a View. + + See the IView interface for the API documentation. + + """ + + ########################################################################### + # 'IWorkbenchPart' interface. + ########################################################################### + + def create_control(self, parent): + """ Create the toolkit-specific control that represents the part. """ + + from enthought.qt import QtGui + + control = QtGui.QWidget(parent) + + palette = control.palette() + palette.setColor(QtGui.QPalette.Window, QtGui.QColor('red')) + control.setPalette(palette) + control.setAutoFillBackground(True) + + return control + + def destroy_control(self): + """ Destroy the toolkit-specific control that represents the part. """ + + if self.control is not None: + self.control.hide() + self.control.deleteLater() + self.control = None + + return + + def set_focus(self): + """ Set the focus to the appropriate control in the part. """ + + if self.control is not None: + self.control.setFocus() + + return + +#### EOF ###################################################################### diff --git a/pyface/ui/qt4/workbench/workbench_window_layout.py b/pyface/ui/qt4/workbench/workbench_window_layout.py new file mode 100755 index 000000000..2707029f2 --- /dev/null +++ b/pyface/ui/qt4/workbench/workbench_window_layout.py @@ -0,0 +1,599 @@ +#------------------------------------------------------------------------------ +# Copyright (c) 2008, Riverbank Computing Limited +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD license. +# However, when used with the GPL version of PyQt the additional terms described in the PyQt GPL exception also apply + +# +# Author: Riverbank Computing Limited +# Description: +#------------------------------------------------------------------------------ + + +# Standard library imports. +import logging + +# Major package imports. +from enthought.qt import QtCore, QtGui + +# Enthought library imports. +from enthought.traits.api import Instance, on_trait_change + +# Local imports. +from split_tab_widget import SplitTabWidget +from enthought.pyface.message_dialog import error +from enthought.pyface.workbench.i_workbench_window_layout import \ + MWorkbenchWindowLayout + + +# Logging. +logger = logging.getLogger(__name__) + + +# For mapping positions relative to the editor area. +_EDIT_AREA_MAP = { + 'left': QtCore.Qt.LeftDockWidgetArea, + 'right': QtCore.Qt.RightDockWidgetArea, + 'top': QtCore.Qt.TopDockWidgetArea, + 'bottom': QtCore.Qt.BottomDockWidgetArea +} + +# For mapping positions relative to another view. +_VIEW_AREA_MAP = { + 'left': (QtCore.Qt.Horizontal, True), + 'right': (QtCore.Qt.Horizontal, False), + 'top': (QtCore.Qt.Vertical, True), + 'bottom': (QtCore.Qt.Vertical, False) +} + + +class WorkbenchWindowLayout(MWorkbenchWindowLayout): + """ The Qt4 implementation of the workbench window layout interface. + + See the 'IWorkbenchWindowLayout' interface for the API documentation. + + """ + + #### Private interface #################################################### + + # The widget that implements the editor area. We keep (and use) this + # separate reference because we can't always assume that it has been set to + # be the main window's central widget. + _qt4_editor_area = Instance(SplitTabWidget) + + ########################################################################### + # 'IWorkbenchWindowLayout' interface. + ########################################################################### + + def activate_editor(self, editor): + if editor.control is not None: + editor.control.show() + self._qt4_editor_area.setCurrentWidget(editor.control) + editor.set_focus() + + return editor + + def activate_view(self, view): + # FIXME v3: This probably doesn't work as expected. + view.control.raise_() + view.set_focus() + + return view + + def add_editor(self, editor, title): + if editor is None: + return None + + try: + self._qt4_editor_area.addTab(self._qt4_get_editor_control(editor), title) + except Exception: + logger.exception('error creating editor control [%s]', editor.id) + + return editor + + def add_view(self, view, position=None, relative_to=None, size=(-1, -1)): + if view is None: + return None + + try: + self._qt4_add_view(view, position, relative_to, size) + view.visible = True + except Exception: + logger.exception('error creating view control [%s]', view.id) + + # Even though we caught the exception, it sometimes happens that + # the view's control has been created as a child of the application + # window (or maybe even the dock control). We should destroy the + # control to avoid bad UI effects. + view.destroy_control() + + # Additionally, display an error message to the user. + error(self.window.control, 'Unable to add view [%s]' % view.id, + 'Workbench Plugin Error') + + return view + + def close_editor(self, editor): + if editor.control is not None: + editor.control.close() + + return editor + + def close_view(self, view): + self.hide_view(view) + + return view + + def close(self): + # Don't fire signals for editors that have destroyed their controls. + QtCore.QObject.disconnect(self._qt4_editor_area, + QtCore.SIGNAL('hasFocus'), self._qt4_editor_focus) + + self._qt4_editor_area.clear() + + # Delete all dock widgets. + for v in self.window.views: + if self.contains_view(v): + self._qt4_delete_view_dock_widget(v) + + def create_initial_layout(self, parent): + self._qt4_editor_area = editor_area = SplitTabWidget(parent) + + QtCore.QObject.connect(editor_area, QtCore.SIGNAL('hasFocus'), + self._qt4_editor_focus) + + # We are interested in focus changes but we get them from the editor + # area rather than qApp to allow the editor area to restrict them when + # needed. + QtCore.QObject.connect( + editor_area, QtCore.SIGNAL('focusChanged(QWidget *,QWidget *)'), + self._qt4_view_focus_changed) + + editor_area.new_window_request.connect(self._qt4_new_window_request) + editor_area.tab_close_request.connect(self._qt4_tab_close_request) + editor_area.tab_window_changed.connect(self._qt4_tab_window_changed) + + return editor_area + + def contains_view(self, view): + return hasattr(view, '_qt4_dock') + + def hide_editor_area(self): + self._qt4_editor_area.hide() + + def hide_view(self, view): + view._qt4_dock.hide() + view.visible = False + + return view + + def refresh(self): + # Nothing to do. + pass + + def reset_editors(self): + self._qt4_editor_area.setCurrentIndex(0) + + def reset_views(self): + # Qt doesn't provide information about the order of dock widgets in a + # dock area. + pass + + def show_editor_area(self): + self._qt4_editor_area.show() + + def show_view(self, view): + view._qt4_dock.show() + view.visible = True + + #### Methods for saving and restoring the layout ########################## + + def get_view_memento(self): + # Get the IDs of the views in the main window. This information is + # also in the QMainWindow state, but that is opaque. + view_ids = [v.id for v in self.window.views if self.contains_view(v)] + + # Everything else is provided by QMainWindow. + state = str(self.window.control.saveState()) + + return (0, (view_ids, state)) + + def set_view_memento(self, memento): + version, mdata = memento + + # There has only ever been version 0 so far so check with an assert. + assert version == 0 + + # Now we know the structure of the memento we can "parse" it. + view_ids, state = mdata + + # Get a list of all views that have dock widgets and mark them. + dock_views = [v for v in self.window.views if self.contains_view(v)] + + for v in dock_views: + v._qt4_gone = True + + # Create a dock window for all views that had one last time. + for v in self.window.views: + # Make sure this is in a known state. + v.visible = False + + for vid in view_ids: + if vid == v.id: + # Create the dock widget if needed and make sure that it is + # invisible so that it matches the state of the visible + # trait. Things will all come right when the main window + # state is restored below. + self._qt4_create_view_dock_widget(v).setVisible(False) + + if v in dock_views: + delattr(v, '_qt4_gone') + + break + + # Remove any remain unused dock widgets. + for v in dock_views: + try: + delattr(v, '_qt4_gone') + except AttributeError: + pass + else: + self._qt4_delete_view_dock_widget(v) + + # Restore the state. This will update the view's visible trait through + # the dock window's toggle action. + self.window.control.restoreState(state) + + def get_editor_memento(self): + # Get the layout of the editors. + editor_layout = self._qt4_editor_area.saveState() + + # Get a memento for each editor that describes its contents. + editor_references = self._get_editor_references() + + return (0, (editor_layout, editor_references)) + + def set_editor_memento(self, memento): + version, mdata = memento + + # There has only ever been version 0 so far so check with an assert. + assert version == 0 + + # Now we know the structure of the memento we can "parse" it. + editor_layout, editor_references = mdata + + def resolve_id(id): + # Get the memento for the editor contents (if any). + editor_memento = editor_references.get(id) + + if editor_memento is None: + return None + + # Create the restored editor. + editor = self.window.editor_manager.set_editor_memento( + editor_memento) + if editor is None: + return None + + # Save the editor. + self.window.editors.append(editor) + + # Create the control if needed and return it. + return self._qt4_get_editor_control(editor) + + self._qt4_editor_area.restoreState(editor_layout, resolve_id) + + ########################################################################### + # Private interface. + ########################################################################### + + def _qt4_editor_focus(self, new): + """ Handle an editor getting the focus. """ + + for editor in self.window.editors: + control = editor.control + editor.has_focus = control is new or \ + (control is not None and new in control.children()) + + def _qt4_view_focus_changed(self, old, new): + """ Handle the change of focus for a view. """ + + focus_part = None + + if new is not None: + # Handle focus changes to views. + for view in self.window.views: + if view.control is not None and view.control.isAncestorOf(new): + view.has_focus = True + focus_part = view + break + + if old is not None: + # Handle focus changes from views. + for view in self.window.views: + if view is not focus_part and view.control is not None and view.control.isAncestorOf(old): + view.has_focus = False + break + + def _qt4_new_window_request(self, pos, control): + """ Handle a tab tear-out request from the splitter widget. """ + + editor = self._qt4_remove_editor_with_control(control) + kind = self.window.editor_manager.get_editor_kind(editor) + + window = self.window.workbench.create_window() + window.open() + window.add_editor(editor) + window.editor_manager.add_editor(editor, kind) + window.position = (pos.x(), pos.y()) + window.size = self.window.size + window.activate_editor(editor) + editor.window = window + + def _qt4_tab_close_request(self, control): + """ Handle a tabCloseRequest from the splitter widget. """ + + for editor in self.window.editors: + if editor.control == control: + editor.close() + break + + def _qt4_tab_window_changed(self, control): + """ Handle a tab drag to a different WorkbenchWindow. """ + + editor = self._qt4_remove_editor_with_control(control) + kind = self.window.editor_manager.get_editor_kind(editor) + + while not control.isWindow(): + control = control.parent() + for window in self.window.workbench.windows: + if window.control == control: + window.editors.append(editor) + window.editor_manager.add_editor(editor, kind) + window.layout._qt4_get_editor_control(editor) + window.activate_editor(editor) + editor.window = window + break + + def _qt4_remove_editor_with_control(self, control): + """ Finds the editor associated with 'control' and removes it. Returns + the editor, or None if no editor was found. + """ + for editor in self.window.editors: + if editor.control == control: + self.editor_closing = editor + control.removeEventFilter(self._qt4_mon) + self.editor_closed = editor + + # Make sure that focus events get fired if this editor is + # subsequently added to another window. + editor.has_focus = False + + return editor + + def _qt4_get_editor_control(self, editor): + """ Create the editor control if it hasn't already been done. """ + + if editor.control is None: + self.editor_opening = editor + + # We must provide a parent (because TraitsUI checks for it when + # deciding what sort of panel to create) but it can't be the editor + # area (because it will be automatically added to the base + # QSplitter). + editor.control = editor.create_control(self.window.control) + editor.control.setObjectName(editor.id) + + self.editor_opened = editor + + def on_name_changed(editor, trait_name, old, new): + self._qt4_editor_area.setWidgetTitle(editor.control, editor.name) + + editor.on_trait_change(on_name_changed, 'name') + + self._qt4_monitor(editor.control) + + return editor.control + + def _qt4_add_view(self, view, position, relative_to, size): + """ Add a view. """ + + # If no specific position is specified then use the view's default + # position. + if position is None: + position = view.position + + dw = self._qt4_create_view_dock_widget(view, size) + mw = self.window.control + + try: + rel_dw = relative_to._qt4_dock + except AttributeError: + rel_dw = None + + if rel_dw is None: + # If we are trying to add a view with a non-existent item, then + # just default to the left of the editor area. + if position == 'with': + position = 'left' + + # Position the view relative to the editor area. + try: + dwa = _EDIT_AREA_MAP[position] + except KeyError: + raise ValueError, "unknown view position: %s" % position + + mw.addDockWidget(dwa, dw) + elif position == 'with': + # FIXME v3: The Qt documentation says that the second should be + # placed above the first, but it always seems to be underneath (ie. + # hidden) which is not what the user is expecting. + mw.tabifyDockWidget(rel_dw, dw) + else: + try: + orient, swap = _VIEW_AREA_MAP[position] + except KeyError: + raise ValueError, "unknown view position: %s" % position + + mw.splitDockWidget(rel_dw, dw, orient) + + # The Qt documentation implies that the layout direction can be + # used to position the new dock widget relative to the existing one + # but I could only get the button positions to change. Instead we + # move things around afterwards if required. + if swap: + mw.removeDockWidget(rel_dw) + mw.splitDockWidget(dw, rel_dw, orient) + rel_dw.show() + + def _qt4_create_view_dock_widget(self, view, size=(-1, -1)): + """ Create a dock widget that wraps a view. """ + + # See if it has already been created. + try: + dw = view._qt4_dock + except AttributeError: + dw = QtGui.QDockWidget(view.name, self.window.control) + dw.setWidget(_ViewContainer(size, self.window.control)) + dw.setObjectName(view.id) + dw.connect(dw.toggleViewAction(), QtCore.SIGNAL('toggled(bool)'), + self._qt4_handle_dock_visibility) + + # Save the dock window. + view._qt4_dock = dw + + def on_name_changed(): + view._qt4_dock.setWindowTitle(view.name) + + view.on_trait_change(on_name_changed, 'name') + + # Make sure the view control exists. + if view.control is None: + # Make sure that the view knows which window it is in. + view.window = self.window + + try: + view.control = view.create_control(self.window.control) + except: + # Tidy up if the view couldn't be created. + delattr(view, '_qt4_dock') + self.window.control.removeDockWidget(dw) + dw.deleteLater() + del dw + raise + + dw.widget().setCentralWidget(view.control) + + return dw + + def _qt4_delete_view_dock_widget(self, view): + """ Delete a view's dock widget. """ + + dw = view._qt4_dock + + # Disassociate the view from the dock. + if view.control is not None: + view.control.setParent(None) + + delattr(view, '_qt4_dock') + + # Delete the dock (and the view container). + self.window.control.removeDockWidget(dw) + dw.deleteLater() + + def _qt4_handle_dock_visibility(self, checked): + """ Handle the visibility of a dock window changing. """ + + # Find the dock window by its toggle action. + for v in self.window.views: + try: + dw = v._qt4_dock + except AttributeError: + continue + + if dw.toggleViewAction() is dw.sender(): + v.visible = checked + + def _qt4_monitor(self, control): + """ Install an event filter for a view or editor control to keep an eye + on certain events. + """ + + # Create the monitoring object if needed. + try: + mon = self._qt4_mon + except AttributeError: + mon = self._qt4_mon = _Monitor(self) + + control.installEventFilter(mon) + + +class _Monitor(QtCore.QObject): + """ This class monitors a view or editor control. """ + + def __init__(self, layout): + QtCore.QObject.__init__(self, layout.window.control) + + self._layout = layout + + def eventFilter(self, obj, e): + if isinstance(e, QtGui.QCloseEvent): + for editor in self._layout.window.editors: + if editor.control is obj: + self._layout.editor_closing = editor + editor.destroy_control() + self._layout.editor_closed = editor + + break + + return False + + +class _ViewContainer(QtGui.QMainWindow): + """ This class is a container for a view that allows an initial size + (specified as a tuple) to be set. + """ + + def __init__(self, size, main_window): + """ Initialise the object. """ + + QtGui.QMainWindow.__init__(self) + + # Save the size and main window. + self._width, self._height = size + self._main_window = main_window + + def sizeHint(self): + """ Reimplemented to return the initial size or the view's current + size. + """ + + sh = self.centralWidget().sizeHint() + + if self._width > 0: + if self._width > 1: + w = self._width + else: + w = self._main_window.width() * self._width + + sh.setWidth(int(w)) + + if self._height > 0: + if self._height > 1: + h = self._height + else: + h = self._main_window.height() * self._height + + sh.setHeight(int(h)) + + return sh + + def showEvent(self, e): + """ Reimplemented to use the view's current size once shown. """ + + self._width = self._height = -1 + + QtGui.QMainWindow.showEvent(self, e) + +#### EOF ###################################################################### diff --git a/pyface/ui/wx/workbench/__init__.py b/pyface/ui/wx/workbench/__init__.py new file mode 100644 index 000000000..f06a5c56b --- /dev/null +++ b/pyface/ui/wx/workbench/__init__.py @@ -0,0 +1,6 @@ +#------------------------------------------------------------------------------ +# +# Copyright (c) 2007 by Enthought, Inc. +# All rights reserved. +# +#------------------------------------------------------------------------------ diff --git a/pyface/ui/wx/workbench/editor.py b/pyface/ui/wx/workbench/editor.py new file mode 100644 index 000000000..dcd36dcae --- /dev/null +++ b/pyface/ui/wx/workbench/editor.py @@ -0,0 +1,64 @@ +#------------------------------------------------------------------------------ +# +# Copyright (c) 2005, Enthought, Inc. +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in enthought/LICENSE.txt and may be redistributed only +# under the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# +# Thanks for using Enthought open source! +# +# Author: Enthought, Inc. +# +#------------------------------------------------------------------------------ + +""" Enthought pyface package component +""" + +# Local imports. +from enthought.pyface.workbench.i_editor import MEditor + + +class Editor(MEditor): + """ The toolkit specific implementation of an Editor. + + See the IEditor interface for the API documentation. + + """ + + ########################################################################### + # 'IWorkbenchPart' interface. + ########################################################################### + + def create_control(self, parent): + """ Create the toolkit-specific control that represents the part. """ + + import wx + + # By default we create a yellow panel! + control = wx.Panel(parent, -1) + control.SetBackgroundColour("yellow") + control.SetSize((100, 200)) + + return control + + def destroy_control(self): + """ Destroy the toolkit-specific control that represents the part. """ + + if self.control is not None: + self.control.Destroy() + self.control = None + + return + + def set_focus(self): + """ Set the focus to the appropriate control in the part. """ + + if self.control is not None: + self.control.SetFocus() + + return + +#### EOF ###################################################################### diff --git a/pyface/ui/wx/workbench/editor_set_structure_handler.py b/pyface/ui/wx/workbench/editor_set_structure_handler.py new file mode 100755 index 000000000..1a31b4a86 --- /dev/null +++ b/pyface/ui/wx/workbench/editor_set_structure_handler.py @@ -0,0 +1,100 @@ +#------------------------------------------------------------------------------ +# +# Copyright (c) 2005, Enthought, Inc. +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in enthought/LICENSE.txt and may be redistributed only +# under the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# +# Thanks for using Enthought open source! +# +# Author: Enthought, Inc. +# +#------------------------------------------------------------------------------ + +""" The handler used to restore editors. +""" + +# Standard library imports. +import logging + +# Enthought library imports. +from enthought.pyface.dock.api import SetStructureHandler + + +logger = logging.getLogger(__name__) + + +class EditorSetStructureHandler(SetStructureHandler): + """ The handler used to restore editors. + + This is part of the 'dock window' API. It is used to resolve dock control + Ids when setting the structure of a dock window. + + """ + + ########################################################################### + # 'object' interface. + ########################################################################### + + def __init__(self, window_layout, editor_mementos): + """ Creates a new handler. """ + + self.window_layout = window_layout + self.editor_mementos = editor_mementos + + return + + ########################################################################### + # 'SetStructureHandler' interface. + ########################################################################### + + def resolve_id(self, id): + """ Resolves an unresolved dock control id. """ + + window_layout = self.window_layout + window = window_layout.window + + try: + # Get the memento for the editor with this Id. + memento = self._get_editor_memento(id) + + # Ask the editor manager to create an editor from the memento. + editor = window.editor_manager.set_editor_memento(memento) + + # Get the editor's toolkit-specific control. + # + # fixme: This is using a 'private' method on the window layout. + # This may be ok since this structure handler is really part of the + # layout! + control = window_layout._wx_get_editor_control(editor) + + # fixme: This is ugly manipulating the editors list from in here! + window.editors.append(editor) + + except: + logger.warn('could not restore editor [%s]', id) + control = None + + return control + + ########################################################################### + # Private interface. + ########################################################################### + + def _get_editor_memento(self, id): + """ Return the editor memento for the editor with the specified Id. + + Raises a 'ValueError' if no such memento exists. + + """ + + editor_memento = self.editor_mementos.get(id) + if editor_memento is None: + raise ValueError('no editor memento with Id %s' % id) + + return editor_memento + +#### EOF ###################################################################### diff --git a/pyface/ui/wx/workbench/view.py b/pyface/ui/wx/workbench/view.py new file mode 100644 index 000000000..cde3c7e31 --- /dev/null +++ b/pyface/ui/wx/workbench/view.py @@ -0,0 +1,70 @@ +#------------------------------------------------------------------------------ +# +# Copyright (c) 2005, Enthought, Inc. +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in enthought/LICENSE.txt and may be redistributed only +# under the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# +# Thanks for using Enthought open source! +# +# Author: Enthought, Inc. +# +#------------------------------------------------------------------------------ + +""" Enthought pyface package component +""" + +# Enthought library imports. +from enthought.traits.api import Bool +from enthought.pyface.workbench.i_view import MView + + +class View(MView): + """ The toolkit specific implementation of a View. + + See the IView interface for the API documentation. + + """ + + # Trait to indicate if the dock window containing the view should be + # closeable. See FIXME comment in the _wx_create_view_dock_control method + # in workbench_window_layout.py. + closeable = Bool(False) + + ########################################################################### + # 'IWorkbenchPart' interface. + ########################################################################### + + def create_control(self, parent): + """ Create the toolkit-specific control that represents the part. """ + + import wx + + # By default we create a red panel! + control = wx.Panel(parent, -1) + control.SetBackgroundColour("red") + control.SetSize((100, 200)) + + return control + + def destroy_control(self): + """ Destroy the toolkit-specific control that represents the part. """ + + if self.control is not None: + self.control.Destroy() + self.control = None + + return + + def set_focus(self): + """ Set the focus to the appropriate control in the part. """ + + if self.control is not None: + self.control.SetFocus() + + return + +#### EOF ###################################################################### diff --git a/pyface/ui/wx/workbench/view_set_structure_handler.py b/pyface/ui/wx/workbench/view_set_structure_handler.py new file mode 100755 index 000000000..a41a27d71 --- /dev/null +++ b/pyface/ui/wx/workbench/view_set_structure_handler.py @@ -0,0 +1,73 @@ +#------------------------------------------------------------------------------ +# +# Copyright (c) 2005, Enthought, Inc. +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in enthought/LICENSE.txt and may be redistributed only +# under the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# +# Thanks for using Enthought open source! +# +# Author: Enthought, Inc. +# +#------------------------------------------------------------------------------ + +""" The handler used to restore views. +""" + +# Standard library imports. +import logging + +# Enthought library imports. +from enthought.pyface.dock.api import SetStructureHandler + + +logger = logging.getLogger(__name__) + + +class ViewSetStructureHandler(SetStructureHandler): + """ The handler used to restore views. + + This is part of the 'dock window' API. It is used to resolve dock control + IDs when setting the structure of a dock window. + + """ + + ########################################################################### + # 'object' interface. + ########################################################################### + + def __init__(self, window_layout): + """ Creates a new handler. """ + + self.window_layout = window_layout + + return + + ########################################################################### + # 'SetStructureHandler' interface. + ########################################################################### + + def resolve_id(self, id): + """ Resolves an unresolved dock control *id*. """ + + window_layout = self.window_layout + window = window_layout.window + + view = window.get_view_by_id(id) + if view is not None: + # Get the view's toolkit-specific control. + # + # fixme: This is using a 'private' method on the window layout. + # This may be ok since this is really part of the layout! + control = window_layout._wx_get_view_control(view) + + else: + logger.warn('could not restore view [%s]', id) + control = None + + return control + +#### EOF ###################################################################### diff --git a/pyface/ui/wx/workbench/workbench_dock_window.py b/pyface/ui/wx/workbench/workbench_dock_window.py new file mode 100755 index 000000000..a7ae6ac89 --- /dev/null +++ b/pyface/ui/wx/workbench/workbench_dock_window.py @@ -0,0 +1,155 @@ +#------------------------------------------------------------------------------ +# +# Copyright (c) 2005, Enthought, Inc. +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in enthought/LICENSE.txt and may be redistributed only +# under the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# +# Thanks for using Enthought open source! +# +# Author: Enthought, Inc. +# +#------------------------------------------------------------------------------ + +""" Base class for workbench dock windows. +""" + +# Standard library imports. +import logging + +# Enthought library imports. +from enthought.pyface.dock.api import DockGroup, DockRegion, DockWindow + + +logger = logging.getLogger(__name__) + +class WorkbenchDockWindow(DockWindow): + """ Base class for workbench dock windows. + + This class just adds a few useful methods to the standard 'DockWindow' + interface. Hopefully at some stage these can be part of that API too! + + """ + + ########################################################################### + # Protected 'DockWindow' interface. + ########################################################################### + + def _right_up(self, event): + """ Handles the right mouse button being released. + + We override this to stop the default dock window context menus from + appearing. + + """ + + pass + + ########################################################################### + # 'WorkbenchDockWindow' interface. + ########################################################################### + + def activate_control(self, id): + """ Activates the dock control with the specified Id. + + Does nothing if no such dock control exists (well, it *does* write + a debug message to the logger). + + """ + + control = self.get_control(id) + if control is not None: + logger.debug('activating control <%s>', id) + control.activate() + + else: + logger.debug('no control <%s> to activate', id) + + return + + def close_control(self, id): + """ Closes the dock control with the specified Id. + + Does nothing if no such dock control exists (well, it *does* write + a debug message to the logger). + + """ + + control = self.get_control(id) + if control is not None: + logger.debug('closing control <%s>', id) + control.close() + + else: + logger.debug('no control <%s> to close', id) + + return + + def get_control(self, id, visible_only=True): + """ Returns the dock control with the specified Id. + + Returns None if no such dock control exists. + + """ + + for control in self.get_controls(visible_only): + if control.id == id: + break + + else: + control = None + + return control + + def get_controls(self, visible_only=True): + """ Returns all of the dock controls in the window. """ + + sizer = self.control.GetSizer() + section = sizer.GetContents() + + return section.get_controls(visible_only=visible_only) + + def get_regions(self, group): + """ Returns all dock regions in a dock group (recursively). """ + + regions = [] + for item in group.contents: + if isinstance(item, DockRegion): + regions.append(item) + + if isinstance(item, DockGroup): + regions.extend(self.get_regions(item)) + + return regions + + def get_structure(self): + """ Returns the window structure (minus the content). """ + + sizer = self.control.GetSizer() + + return sizer.GetStructure() + + def reset_regions(self): + """ Activates the first dock control in every region. """ + + sizer = self.control.GetSizer() + section = sizer.GetContents() + + for region in self.get_regions(section): + if len(region.contents) > 0: + region.contents[0].activate(layout=False) + + return + + def set_structure(self, structure, handler=None): + """ Sets the window structure. """ + + sizer = self.control.GetSizer() + sizer.SetStructure(self.control.GetParent(), structure, handler) + + return + +#### EOF ###################################################################### diff --git a/pyface/ui/wx/workbench/workbench_window_layout.py b/pyface/ui/wx/workbench/workbench_window_layout.py new file mode 100644 index 000000000..ad44216b2 --- /dev/null +++ b/pyface/ui/wx/workbench/workbench_window_layout.py @@ -0,0 +1,825 @@ +#------------------------------------------------------------------------------ +# +# Copyright (c) 2005, Enthought, Inc. +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in enthought/LICENSE.txt and may be redistributed only +# under the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# +# Thanks for using Enthought open source! +# +# Author: Enthought, Inc. +# +#------------------------------------------------------------------------------ + +""" The wx implementation of the workbench window layout interface. +""" + +# Standard library imports. +import cPickle +import logging + +# Major package imports. +import wx + +# Enthought library imports. +from enthought.pyface.dock.api import DOCK_BOTTOM, DOCK_LEFT, DOCK_RIGHT +from enthought.pyface.dock.api import DOCK_TOP +from enthought.pyface.dock.api import DockControl, DockRegion, DockSection +from enthought.pyface.dock.api import DockSizer +from enthought.traits.api import Delegate +from enthought.traits.ui.dockable_view_element import DockableViewElement + +# Mixin class imports. +from enthought.pyface.workbench.i_workbench_window_layout import \ + MWorkbenchWindowLayout + +# Local imports. +from editor_set_structure_handler import EditorSetStructureHandler +from view_set_structure_handler import ViewSetStructureHandler +from workbench_dock_window import WorkbenchDockWindow + + +# Logging. +logger = logging.getLogger(__name__) + +# Mapping from view position to the appropriate dock window constant. +_POSITION_MAP = { + 'top' : DOCK_TOP, + 'bottom' : DOCK_BOTTOM, + 'left' : DOCK_LEFT, + 'right' : DOCK_RIGHT +} + + +class WorkbenchWindowLayout(MWorkbenchWindowLayout): + """ The wx implementation of the workbench window layout interface. + + See the 'IWorkbenchWindowLayout' interface for the API documentation. + + """ + + #### 'IWorkbenchWindowLayout' interface ################################### + + editor_area_id = Delegate('window') + + ########################################################################### + # 'IWorkbenchWindowLayout' interface. + ########################################################################### + + def activate_editor(self, editor): + """ Activate an editor. """ + + # This brings the dock control tab to the front. + self._wx_editor_dock_window.activate_control(editor.id) + + editor.set_focus() + + return editor + + def activate_view(self, view): + """ Activate a view. """ + + # This brings the dock control tab to the front. + self._wx_view_dock_window.activate_control(view.id) + + view.set_focus() + + return view + + def add_editor(self, editor, title): + """ Add an editor. """ + + try: + self._wx_add_editor(editor, title) + + except Exception: + logger.exception('error creating editor control <%s>', editor.id) + + return editor + + def add_view(self, view, position=None, relative_to=None, size=(-1, -1)): + """ Add a view. """ + + try: + self._wx_add_view(view, position, relative_to, size) + view.visible = True + + except Exception: + logger.exception('error creating view control <%s>', view.id) + + # Even though we caught the exception, it sometimes happens that + # the view's control has been created as a child of the application + # window (or maybe even the dock control). We should destroy the + # control to avoid bad UI effects. + view.destroy_control() + + # Additionally, display an error message to the user. + self.window.error('Unable to add view %s' % view.id) + + return view + + def close_editor(self, editor): + """ Close and editor. """ + + self._wx_editor_dock_window.close_control(editor.id) + + return editor + + def close_view(self, view): + """ Close a view. """ + + self.hide_view(view) + + return view + + def close(self): + """ Close the entire window layout. """ + + self._wx_editor_dock_window.close() + self._wx_view_dock_window.close() + + return + + def create_initial_layout(self, parent): + """ Create the initial window layout. """ + + # The view dock window is where all of the views live. It also contains + # a nested dock window where all of the editors live. + self._wx_view_dock_window = WorkbenchDockWindow(parent) + + # The editor dock window (which is nested inside the view dock window) + # is where all of the editors live. + self._wx_editor_dock_window = WorkbenchDockWindow( + self._wx_view_dock_window.control + ) + editor_dock_window_sizer = DockSizer(contents=DockSection()) + self._wx_editor_dock_window.control.SetSizer(editor_dock_window_sizer) + + # Nest the editor dock window in the view dock window. + editor_dock_window_control = DockControl( + id = self.editor_area_id, + name = 'Editors', + control = self._wx_editor_dock_window.control, + style = 'fixed', + width = self.window.editor_area_size[0], + height = self.window.editor_area_size[1], + ) + + view_dock_window_sizer = DockSizer( + contents=[editor_dock_window_control] + ) + + self._wx_view_dock_window.control.SetSizer(view_dock_window_sizer) + + return self._wx_view_dock_window.control + + def contains_view(self, view): + """ Return True if the view exists in the window layout. """ + + view_control = self._wx_view_dock_window.get_control(view.id, False) + + return view_control is not None + + def hide_editor_area(self): + """ Hide the editor area. """ + + dock_control = self._wx_view_dock_window.get_control( + self.editor_area_id, visible_only=False + ) + dock_control.show(False, layout=True) + + return + + def hide_view(self, view): + """ Hide a view. """ + + dock_control = self._wx_view_dock_window.get_control( + view.id, visible_only=False + ) + + dock_control.show(False, layout=True) + view.visible = False + + return view + + def refresh(self): + """ Refresh the window layout to reflect any changes. """ + + self._wx_view_dock_window.update_layout() + + return + + def reset_editors(self): + """ Activate the first editor in every group. """ + + self._wx_editor_dock_window.reset_regions() + + return + + def reset_views(self): + """ Activate the first view in every group. """ + + self._wx_view_dock_window.reset_regions() + + return + + def show_editor_area(self): + """ Show the editor area. """ + + dock_control = self._wx_view_dock_window.get_control( + self.editor_area_id, visible_only=False + ) + dock_control.show(True, layout=True) + + return + + def show_view(self, view): + """ Show a view. """ + + dock_control = self._wx_view_dock_window.get_control( + view.id, visible_only=False + ) + + dock_control.show(True, layout=True) + view.visible = True + + return + + #### Methods for saving and restoring the layout ########################## + + def get_view_memento(self): + structure = self._wx_view_dock_window.get_structure() + + # We always return a clone. + return cPickle.loads(cPickle.dumps(structure)) + + def set_view_memento(self, memento): + # We always use a clone. + memento = cPickle.loads(cPickle.dumps(memento)) + + # The handler knows how to resolve view Ids when setting the dock + # window structure. + handler = ViewSetStructureHandler(self) + + # Set the layout of the views. + self._wx_view_dock_window.set_structure(memento, handler) + + # fixme: We should be able to do this in the handler but we don't get a + # reference to the actual dock control in 'resolve_id'. + for view in self.window.views: + control = self._wx_view_dock_window.get_control(view.id) + if control is not None: + self._wx_initialize_view_dock_control(view, control) + view.visible = control.visible + else: + view.visible = False + + return + + def get_editor_memento(self): + # Get the layout of the editors. + structure = self._wx_editor_dock_window.get_structure() + + # Get a memento to every editor. + editor_references = self._get_editor_references() + + return (structure, editor_references) + + def set_editor_memento(self, memento): + # fixme: Mementos might want to be a bit more formal than tuples! + structure, editor_references = memento + + if len(structure.contents) > 0: + # The handler knows how to resolve editor Ids when setting the dock + # window structure. + handler = EditorSetStructureHandler(self, editor_references) + + # Set the layout of the editors. + self._wx_editor_dock_window.set_structure(structure, handler) + + # fixme: We should be able to do this in the handler but we don't + # get a reference to the actual dock control in 'resolve_id'. + for editor in self.window.editors: + control = self._wx_editor_dock_window.get_control(editor.id) + if control is not None: + self._wx_initialize_editor_dock_control(editor, control) + + return + + ########################################################################### + # Private interface. + ########################################################################### + + def _wx_add_editor(self, editor, title): + """ Adds an editor. """ + + # Create a dock control that contains the editor. + editor_dock_control = self._wx_create_editor_dock_control(editor) + + # If there are no other editors open (i.e., this is the first one!), + # then create a new region to put the editor in. + controls = self._wx_editor_dock_window.get_controls() + if len(controls) == 0: + # Get a reference to the empty editor section. + sizer = self._wx_editor_dock_window.control.GetSizer() + section = sizer.GetContents() + + # Add a region containing the editor dock control. + region = DockRegion(contents=[editor_dock_control]) + section.contents = [region] + + # Otherwise, add the editor to the same region as the first editor + # control. + # + # fixme: We might want a more flexible placement strategy at some + # point! + else: + region = controls[0].parent + region.add(editor_dock_control) + + # fixme: Without this the window does not draw properly (manually + # resizing the window makes it better!). + self._wx_editor_dock_window.update_layout() + + return + + def _wx_add_view(self, view, position, relative_to, size): + """ Adds a view. """ + + # If no specific position is specified then use the view's default + # position. + if position is None: + position = view.position + + # Create a dock control that contains the view. + dock_control = self._wx_create_view_dock_control(view) + + if position == 'with': + # Does the item we are supposed to be positioned 'with' actual + # exist? + with_item = self._wx_view_dock_window.get_control(relative_to.id) + + # If so then we put the items in the same tab group. + if with_item is not None: + self._wx_add_view_with(dock_control, relative_to) + + # Otherwise, just fall back to the 'left' of the editor area. + else: + self._wx_add_view_relative(dock_control, None, 'left', size) + + else: + self._wx_add_view_relative(dock_control,relative_to,position,size) + + return + + # fixme: Make the view dock window a sub class of dock window, and add + # 'add_with' and 'add_relative_to' as methods on that. + # + # fixme: This is a good idea in theory, but the sizing is a bit iffy, as + # it requires the window to be passed in to calculate the relative size + # of the control. We could just calculate that here and pass in absolute + # pixel sizes to the dock window subclass? + def _wx_add_view_relative(self, dock_control, relative_to, position, size): + """ Adds a view relative to another item. """ + + # If no 'relative to' Id is specified then we assume that the position + # is relative to the editor area. + if relative_to is None: + relative_to_item = self._wx_view_dock_window.get_control( + self.editor_area_id, visible_only=False + ) + + # Find the item that we are adding the view relative to. + else: + relative_to_item = self._wx_view_dock_window.get_control( + relative_to.id + ) + + # Set the size of the dock control. + self._wx_set_item_size(dock_control, size) + + # The parent of a dock control is a dock region. + region = relative_to_item.parent + section = region.parent + section.add(dock_control, region, _POSITION_MAP[position]) + + return + + def _wx_add_view_with(self, dock_control, with_obj): + """ Adds a view in the same region as another item. """ + + # Find the item that we are adding the view 'with'. + with_item = self._wx_view_dock_window.get_control(with_obj.id) + if with_item is None: + raise ValueError('Cannot find item %s' % with_obj) + + # The parent of a dock control is a dock region. + with_item.parent.add(dock_control) + + return + + def _wx_set_item_size(self, dock_control, size): + """ Sets the size of a dock control. """ + + window_width, window_height = self.window.control.GetSize() + width, height = size + + if width != -1: + dock_control.width = int(window_width * width) + + if height != -1: + dock_control.height = int(window_height * height) + + return + + def _wx_create_editor_dock_control(self, editor): + """ Creates a dock control that contains the specified editor. """ + + self._wx_get_editor_control(editor) + + # Wrap a dock control around it. + editor_dock_control = DockControl( + id = editor.id, + name = editor.name, + closeable = True, + control = editor.control, + style = 'tab', + # fixme: Create a subclass of dock control and give it a proper + # editor trait! + _editor = editor + ) + + # Hook up the 'on_close' and trait change handlers etc. + self._wx_initialize_editor_dock_control(editor, editor_dock_control) + + return editor_dock_control + + def _wx_create_view_dock_control(self, view): + """ Creates a dock control that contains the specified view. """ + + # Get the view's toolkit-specific control. + control = self._wx_get_view_control(view) + + # Check if the dock control should be 'closeable'. + # FIXME: The 'fixme' comment below suggests some issue with closing a + # view by clicking 'X' rather than just hiding the view. The two actions + # appear to do the same thing however, so I'm not sure if the comment + # below is an out-of-date comment. This needs more investigation. + # For the time being, I am making a view closeable if it has a + # 'closeable' trait set to True. + closeable = view.closeable + + # Wrap a dock control around it. + view_dock_control = DockControl( + id = view.id, + name = view.name, + # fixme: We would like to make views closeable, but closing via the + # tab is different than calling show(False, layout=True) on the + # control! If we use a close handler can we change that?!? + closeable = closeable, + control = control, + style = view.style_hint, + # fixme: Create a subclass of dock control and give it a proper + # view trait! + _view = view + ) + + # Hook up the 'on_close' and trait change handlers etc. + self._wx_initialize_view_dock_control(view, view_dock_control) + + return view_dock_control + + def _wx_get_editor_control(self, editor): + """ Returns the editor's toolkit-specific control. + + If the editor has not yet created its control, we will ask it to create + it here. + + """ + + if editor.control is None: + parent = self._wx_editor_dock_window.control + + # This is the toolkit-specific control that represents the 'guts' + # of the editor. + self.editor_opening = editor + editor.control = editor.create_control(parent) + self.editor_opened = editor + + # Hook up toolkit-specific events that are managed by the framework + # etc. + self._wx_initialize_editor_control(editor) + + return editor.control + + def _wx_initialize_editor_control(self, editor): + """ Initializes the toolkit-specific control for an editor. + + This is used to hook events managed by the framework etc. + + """ + + def on_set_focus(event): + """ Called when the control gets the focus. """ + + editor.has_focus = True + + # Let the default wx event handling do its thang. + event.Skip() + + return + + def on_kill_focus(event): + """ Called when the control gets the focus. """ + + editor.has_focus = False + + # Let the default wx event handling do its thang. + event.Skip() + + return + + self._wx_add_focus_listeners(editor.control,on_set_focus,on_kill_focus) + + return + + def _wx_get_view_control(self, view): + """ Returns a view's toolkit-specific control. + + If the view has not yet created its control, we will ask it to create + it here. + + """ + + if view.control is None: + parent = self._wx_view_dock_window.control + + # Make sure that the view knows which window it is in. + view.window = self.window + + # This is the toolkit-specific control that represents the 'guts' + # of the view. + self.view_opening = view + view.control = view.create_control(parent) + self.view_opened = view + + # Hook up toolkit-specific events that are managed by the + # framework etc. + self._wx_initialize_view_control(view) + + return view.control + + def _wx_initialize_view_control(self, view): + """ Initializes the toolkit-specific control for a view. + + This is used to hook events managed by the framework. + + """ + + def on_set_focus(event): + """ Called when the control gets the focus. """ + + view.has_focus = True + + # Let the default wx event handling do its thang. + event.Skip() + + return + + def on_kill_focus(event): + """ Called when the control gets the focus. """ + + view.has_focus = False + + # Let the default wx event handling do its thang. + event.Skip() + + return + + self._wx_add_focus_listeners(view.control, on_set_focus, on_kill_focus) + + return + + def _wx_add_focus_listeners(self, control, on_set_focus, on_kill_focus): + """ Recursively adds focus listeners to a control. """ + + # NOTE: If we are passed a wx control that isn't correctly initialized + # (like when the TraitsUIView isn't properly creating it) but it is + # actually a wx control, then we get weird exceptions from trying to + # register event handlers. The exception messages complain that + # the passed control is a str object instead of a wx object. + if on_set_focus is not None: + #control.Bind(wx.EVT_SET_FOCUS, on_set_focus) + wx.EVT_SET_FOCUS(control, on_set_focus) + + if on_kill_focus is not None: + #control.Bind(wx.EVT_KILL_FOCUS, on_kill_focus) + wx.EVT_KILL_FOCUS(control, on_kill_focus) + + for child in control.GetChildren(): + self._wx_add_focus_listeners(child, on_set_focus, on_kill_focus) + + return + + def _wx_initialize_editor_dock_control(self, editor, editor_dock_control): + """ Initializes an editor dock control. + + fixme: We only need this method because of a problem with the dock + window API in the 'SetStructureHandler' class. Currently we do not get + a reference to the dock control in 'resolve_id' and hence we cannot set + up the 'on_close' and trait change handlers etc. + + """ + + # Some editors append information to their name to indicate status (in + # our case this is often a 'dirty' indicator that shows when the + # contents of an editor have been modified but not saved). When the + # dock window structure is persisted it contains the name of each dock + # control, which obviously includes any appended state information. + # Here we make sure that when the dock control is recreated its name is + # set to the editor name and nothing more! + editor_dock_control.set_name(editor.name) + + # fixme: Should we roll the traits UI stuff into the default editor. + if hasattr(editor, 'ui') and editor.ui is not None: + # This makes the control draggable outside of the main window. + #editor_dock_control.export = 'enthought.pyface.workbench.editor' + editor_dock_control.dockable = DockableViewElement( + should_close=True, ui=editor.ui + ) + + editor_dock_control.on_close = self._wx_on_editor_closed + + def on_id_changed(editor, trait_name, old, new): + editor_dock_control.id = editor.id + return + + editor.on_trait_change(on_id_changed, 'id') + + def on_name_changed(editor, trait_name, old, new): + editor_dock_control.set_name(editor.name) + return + + editor.on_trait_change(on_name_changed, 'name') + + def on_activated_changed(editor_dock_control, trait_name, old, new): + if editor_dock_control._editor is not None: + editor_dock_control._editor.set_focus() + return + + editor_dock_control.on_trait_change(on_activated_changed, 'activated') + + return + + def _wx_initialize_view_dock_control(self, view, view_dock_control): + """ Initializes a view dock control. + + fixme: We only need this method because of a problem with the dock + window API in the 'SetStructureHandler' class. Currently we do not get + a reference to the dock control in 'resolve_id' and hence we cannot set + up the 'on_close' and trait change handlers etc. + + """ + + # Some views append information to their name to indicate status (in + # our case this is often a 'dirty' indicator that shows when the + # contents of a view have been modified but not saved). When the + # dock window structure is persisted it contains the name of each dock + # control, which obviously includes any appended state information. + # Here we make sure that when the dock control is recreated its name is + # set to the view name and nothing more! + view_dock_control.set_name(view.name) + + # fixme: Should we roll the traits UI stuff into the default editor. + if hasattr(view, 'ui') and view.ui is not None: + # This makes the control draggable outside of the main window. + #view_dock_control.export = 'enthought.pyface.workbench.view' + + # If the ui's 'view' trait has an 'export' field set, pass that on + # to the dock control. This makes the control detachable from the + # main window (if 'export' is not an empty string). + if view.ui.view is not None: + view_dock_control.export = view.ui.view.export + view_dock_control.dockable = DockableViewElement( + should_close=True, ui=view.ui + ) + + view_dock_control.on_close = self._wx_on_view_closed + + def on_id_changed(view, trait_name, old, new): + view_dock_control.id = view.id + return + + view.on_trait_change(on_id_changed, 'id') + + def on_name_changed(view, trait_name, old, new): + view_dock_control.set_name(view.name) + return + + view.on_trait_change(on_name_changed, 'name') + + def on_activated_changed(view_dock_control, trait_name, old, new): + if view_dock_control._view is not None: + view_dock_control._view.set_focus() + return + + view_dock_control.on_trait_change(on_activated_changed, 'activated') + + return + + #### Trait change handlers ################################################ + + #### Static #### + + def _window_changed(self, old, new): + """ Static trait change handler. """ + + if old is not None: + old.on_trait_change( + self._wx_on_editor_area_size_changed, 'editor_area_size', + remove=True + ) + + + if new is not None: + new.on_trait_change( + self._wx_on_editor_area_size_changed, 'editor_area_size', + ) + + #### Dynamic #### + + def _wx_on_editor_area_size_changed(self, new): + """ Dynamic trait change handler. """ + + window_width, window_height = self.window.control.GetSize() + + # Get the dock control that contains the editor dock window. + control = self._wx_view_dock_window.get_control(self.editor_area_id) + + # We actually resize the region that the editor area is in. + region = control.parent + region.width = int(new[0] * window_width) + region.height = int(new[1] * window_height) + return + + #### Dock window handlers ################################################# + + # fixme: Should these just fire events that the window listens to? + def _wx_on_view_closed(self, dock_control, force): + """ Called when a view is closed via the dock window control. """ + + view = self.window.get_view_by_id(dock_control.id) + if view is not None: + logger.debug('workbench destroying view control <%s>', view) + try: + view.visible = False + + self.view_closing = view + view.destroy_control() + self.view_closed = view + + except: + logger.exception('error destroying view control <%s>', view) + + return True + + def _wx_on_editor_closed(self, dock_control, force): + """ Called when an editor is closed via the dock window control. """ + + dock_control._editor = None + editor = self.window.get_editor_by_id(dock_control.id) + +## import weakref +## editor_ref = weakref.ref(editor) + + if editor is not None: + logger.debug('workbench destroying editor control <%s>', editor) + try: + # fixme: We would like this event to be vetoable, but it isn't + # just yet (we will need to modify the dock window package). + self.editor_closing = editor + editor.destroy_control() + self.editor_closed = editor + + except: + logger.exception('error destroying editor control <%s>',editor) + +## import gc +## gc.collect() + +## print 'Editor references', len(gc.get_referrers(editor)) +## for r in gc.get_referrers(editor): +## print '********************************************' +## print type(r), id(r), r + +## del editor +## gc.collect() + +## print 'Is editor gone?', editor_ref() is None, 'ref', editor_ref() + + return True + +#### EOF ###################################################################### diff --git a/pyface/workbench/__init__.py b/pyface/workbench/__init__.py new file mode 100755 index 000000000..e69de29bb diff --git a/pyface/workbench/action/__init__.py b/pyface/workbench/action/__init__.py new file mode 100755 index 000000000..e69de29bb diff --git a/pyface/workbench/action/action_controller.py b/pyface/workbench/action/action_controller.py new file mode 100644 index 000000000..6b30c2c8b --- /dev/null +++ b/pyface/workbench/action/action_controller.py @@ -0,0 +1,37 @@ +""" The action controller for workbench menu and tool bars. """ + + +# Enthought library imports. +from enthought.pyface.action.api import ActionController +from enthought.pyface.workbench.api import WorkbenchWindow +from enthought.traits.api import HasTraits, Instance + + +class ActionController(ActionController): + """ The action controller for workbench menu and tool bars. + + The controller is used to 'hook' the invocation of every action on the menu + and tool bars. This is done so that additional (and workbench specific) + information can be added to action events. Currently, we attach a reference + to the workbench window. + + """ + + #### 'ActionController' interface ######################################### + + # The workbench window that this is the controller for. + window = Instance(WorkbenchWindow) + + ########################################################################### + # 'ActionController' interface. + ########################################################################### + + def perform(self, action, event): + """ Control an action invocation. """ + + # Add a reference to the window and the application to the event. + event.window = self.window + + return action.perform(event) + +#### EOF ###################################################################### diff --git a/pyface/workbench/action/api.py b/pyface/workbench/action/api.py new file mode 100644 index 000000000..c97585fa6 --- /dev/null +++ b/pyface/workbench/action/api.py @@ -0,0 +1,3 @@ +from menu_bar_manager import MenuBarManager +from tool_bar_manager import ToolBarManager +from view_menu_manager import ViewMenuManager diff --git a/pyface/workbench/action/delete_user_perspective_action.py b/pyface/workbench/action/delete_user_perspective_action.py new file mode 100644 index 000000000..756afef2f --- /dev/null +++ b/pyface/workbench/action/delete_user_perspective_action.py @@ -0,0 +1,80 @@ +#----------------------------------------------------------------------------- +# +# Copyright (c) 2005-2006 by Enthought, Inc. +# All rights reserved. +# +# Author: David C. Morrill +# +#----------------------------------------------------------------------------- +""" An action that deletes a user perspective. """ + + +# Enthought library imports. +from enthought.pyface.api import YES + +# Local imports. +from user_perspective_action import UserPerspectiveAction + + +class DeleteUserPerspectiveAction(UserPerspectiveAction): + """ An action that deletes a user perspective. """ + + #### 'Action' interface ################################################### + + # The action's unique identifier (may be None). + id = 'enthought.pyface.workbench.action.delete_user_perspective_action' + + # The action's name (displayed on menus/tool bar tools etc). + name = 'Delete Perspective' + + ########################################################################### + # 'Action' interface. + ########################################################################### + + def perform(self, event): + """ Perform the action. """ + + window = event.window + manager = window.workbench.user_perspective_manager + + # The perspective to delete. + perspective = window.active_perspective + + # Make sure that the user isn't having second thoughts! + message = 'Are you sure you want to delete the "%s" perspective?' % \ + perspective.name + + answer = window.confirm(message, title='Confirm Delete') + if answer == YES: + # Set the active perspective to be the first remaining perspective. + # + # There is always a default NON-user perspective (even if no + # perspectives are explicitly defined) so we should never(!) not + # be able to find one! + window.active_perspective = self._get_next_perspective(window) + + # Remove the perspective from the window. + window.perspectives.remove(perspective) + + # Remove it from the user perspective manager. + manager.remove(perspective.id) + + return + + ########################################################################### + # Private interface. + ########################################################################### + + def _get_next_perspective(self, window): + """ Return the first perspective that is not the active one! """ + + if window.active_perspective is window.perspectives[0]: + index = 1 + + else: + index = 0 + + return window.perspectives[index] + +#### EOF ##################################################################### + diff --git a/pyface/workbench/action/menu_bar_manager.py b/pyface/workbench/action/menu_bar_manager.py new file mode 100644 index 000000000..1398fa984 --- /dev/null +++ b/pyface/workbench/action/menu_bar_manager.py @@ -0,0 +1,36 @@ +""" The menu bar manager for Envisage workbench windows. """ + + +# Enthought library imports. +from enthought.pyface.action.api import MenuBarManager as BaseMenuBarManager +from enthought.traits.api import Instance + +# Local imports. +from action_controller import ActionController + + +class MenuBarManager(BaseMenuBarManager): + """ The menu bar manager for Envisage workbench windows. """ + + #### 'MenuBarManager' interface ########################################### + + # The workbench window that we are the menu bar manager for. + window = Instance('enthought.pyface.workbench.api.WorkbenchWindow') + + ########################################################################### + # 'MenuBarManager' interface. + ########################################################################### + + def create_menu_bar(self, parent): + """ Creates a menu bar representation of the manager. """ + + # The controller handles the invocation of every action. + controller = ActionController(window=self.window) + + menu_bar = super(MenuBarManager, self).create_menu_bar( + parent, controller=controller + ) + + return menu_bar + +#### EOF ###################################################################### diff --git a/pyface/workbench/action/new_user_perspective_action.py b/pyface/workbench/action/new_user_perspective_action.py new file mode 100644 index 000000000..224ef8300 --- /dev/null +++ b/pyface/workbench/action/new_user_perspective_action.py @@ -0,0 +1,55 @@ +#----------------------------------------------------------------------------- +# +# Copyright (c) 2005-2006 by Enthought, Inc. +# All rights reserved. +# +# Author: David C. Morrill +# +#----------------------------------------------------------------------------- +""" An action that creates a new (and empty) user perspective. """ + + +# Local imports. +from user_perspective_name import UserPerspectiveName +from workbench_action import WorkbenchAction + + +class NewUserPerspectiveAction(WorkbenchAction): + """ An action that creates a new (and empty) user perspective. """ + + #### 'Action' interface ################################################### + + # The action's unique identifier. + id = 'enthought.pyface.workbench.action.new_user_perspective_action' + + # The action's name. + name = 'New Perspective...' + + ########################################################################### + # 'Action' interface. + ########################################################################### + + def perform(self, event): + """ Peform the action. """ + + window = event.window + manager = window.workbench.user_perspective_manager + + # Get the details of the new perspective. + upn = UserPerspectiveName(name='User Perspective %d' % manager.next_id) + if upn.edit_traits(view='new_view').result: + # Create a new (and empty) user perspective. + perspective = manager.create_perspective( + upn.name.strip(), upn.show_editor_area + ) + + # Add it to the window... + window.perspectives.append(perspective) + + # ... and make it the active perspective. + window.active_perspective = perspective + + return + +#### EOF ##################################################################### + diff --git a/pyface/workbench/action/perspective_menu_manager.py b/pyface/workbench/action/perspective_menu_manager.py new file mode 100644 index 000000000..17e3d758d --- /dev/null +++ b/pyface/workbench/action/perspective_menu_manager.py @@ -0,0 +1,135 @@ +""" The default perspective menu for a workbench window. """ + + +# Enthought library imports. +from enthought.pyface.action.api import Group, MenuManager +from enthought.traits.api import Instance, List, on_trait_change + +# Local imports. +from delete_user_perspective_action import DeleteUserPerspectiveAction +from new_user_perspective_action import NewUserPerspectiveAction +from rename_user_perspective_action import RenameUserPerspectiveAction +from reset_all_perspectives_action import ResetAllPerspectivesAction +from reset_active_perspective_action import ResetActivePerspectiveAction +from save_as_user_perspective_action import SaveAsUserPerspectiveAction +from set_active_perspective_action import SetActivePerspectiveAction + + +class PerspectiveMenuManager(MenuManager): + """ The default perspective menu for a workbench window. """ + + #### 'ActionManager' interface ############################################ + + # All of the groups in the manager. + groups = List(Group) + + # The manager's unique identifier. + id = 'PerspectivesMenu' + + #### 'MenuManager' interface ############################################## + + # The menu manager's name. + name = 'Perspectives' + + #### 'PerspectiveMenuManager' interface ################################### + + # The workbench window that the manager is part of. + window = Instance('enthought.pyface.workbench.api.WorkbenchWindow') + + ########################################################################### + # 'ActionManager' interface. + ########################################################################### + + def _groups_default(self): + """ Trait initializer. """ + + groups = [ + # Create a group containing the actions that switch to specific + # perspectives. + self._create_perspective_group(self.window), + + # Create a group containing the user perspective create/save/rename + # /delete actions. + self._create_user_perspective_group(self.window), + + # Create a group containing the reset actions. + self._create_reset_perspective_group(self.window) + + ] + + return groups + + ########################################################################### + # 'PerspectiveMenuManager' interface. + ########################################################################### + + @on_trait_change('window.perspectives') + @on_trait_change('window.perspectives_items') + def rebuild(self): + """ Rebuild the menu. + + This is called when user perspectives have been added or removed. + + """ + + # Clear out the old menu. This gives any actions that have trait + # listeners (i.e. the rename and delete actions!) a chance to unhook + # them. + self.destroy() + + # Resetting the trait allows the initializer to run again (which will + # happen just as soon as we fire the 'changed' event). + self.reset_traits(['groups']) + + # Let the associated menu know that we have changed. + self.changed = True + + return + + ########################################################################### + # Private interface. + ########################################################################### + + def _create_perspective_group(self, window): + """ Create the actions that switch to specific perspectives. """ + + # fixme: Not sure if alphabetic sorting is appropriate in all cases, + # but it will do for now! + perspectives = window.perspectives[:] + perspectives.sort(lambda x, y: cmp(x.name, y.name)) + + # For each perspective, create an action that sets the active + # perspective to it. + group = Group() + for perspective in perspectives: + group.append( + SetActivePerspectiveAction( + perspective=perspective, window=window + ) + ) + + return group + + def _create_user_perspective_group(self, window): + """ Create the user perspective create/save/rename/delete actions. """ + + group = Group( + NewUserPerspectiveAction(window=window), + SaveAsUserPerspectiveAction(window=window), + RenameUserPerspectiveAction(window=window), + DeleteUserPerspectiveAction(window=window) + ) + + return group + + def _create_reset_perspective_group(self, window): + """ Create the reset perspective actions. """ + + group = Group( + ResetActivePerspectiveAction(window=window), + ResetAllPerspectivesAction(window=window) + ) + + return group + +#### EOF ###################################################################### diff --git a/pyface/workbench/action/rename_user_perspective_action.py b/pyface/workbench/action/rename_user_perspective_action.py new file mode 100644 index 000000000..1344dcb29 --- /dev/null +++ b/pyface/workbench/action/rename_user_perspective_action.py @@ -0,0 +1,46 @@ +#----------------------------------------------------------------------------- +# +# Copyright (c) 2005-2006 by Enthought, Inc. +# All rights reserved. +# +# Author: David C. Morrill +# +#----------------------------------------------------------------------------- +""" An action that renames a user perspective. """ + + +# Local imports. +from user_perspective_action import UserPerspectiveAction +from user_perspective_name import UserPerspectiveName + + +class RenameUserPerspectiveAction(UserPerspectiveAction): + """ An action that renames a user perspective. """ + + #### 'Action' interface ################################################### + + # The action's unique identifier (may be None). + id = 'enthought.pyface.workbench.action.rename_user_perspective_action' + + # The action's name (displayed on menus/tool bar tools etc). + name = 'Rename Perspective...' + + ########################################################################### + # 'Action' interface. + ########################################################################### + + def perform( self, event): + """ Perform the action. """ + + window = event.window + manager = window.workbench.user_perspective_manager + + # Get the new name. + upn = UserPerspectiveName(name=window.active_perspective.name) + if upn.edit_traits(view='rename_view').result: + manager.rename(window.active_perspective, upn.name.strip()) + + return + +#### EOF ##################################################################### + diff --git a/pyface/workbench/action/reset_active_perspective_action.py b/pyface/workbench/action/reset_active_perspective_action.py new file mode 100644 index 000000000..f34f64cc9 --- /dev/null +++ b/pyface/workbench/action/reset_active_perspective_action.py @@ -0,0 +1,40 @@ +""" An action that resets the active perspective. """ + + +# Enthought library imports. +from enthought.pyface.api import YES + +# Local imports. +from workbench_action import WorkbenchAction + + +# The message used when confirming the action. +MESSAGE = 'Do you want to reset the current "%s" perspective to its defaults?' + + +class ResetActivePerspectiveAction(WorkbenchAction): + """ An action that resets the active perspective. """ + + #### 'Action' interface ################################################### + + # The action's unique identifier (may be None). + id = 'enthought.pyface.workbench.action.reset_active_perspective' + + # The action's name (displayed on menus/tool bar tools etc). + name = 'Reset Perspective' + + ########################################################################### + # 'Action' interface. + ########################################################################### + + def perform(self, event): + """ Perform the action. """ + + window = self.window + + if window.confirm(MESSAGE % window.active_perspective.name) == YES: + window.reset_active_perspective() + + return + +#### EOF ###################################################################### diff --git a/pyface/workbench/action/reset_all_perspectives_action.py b/pyface/workbench/action/reset_all_perspectives_action.py new file mode 100644 index 000000000..c0d40696d --- /dev/null +++ b/pyface/workbench/action/reset_all_perspectives_action.py @@ -0,0 +1,40 @@ +""" An action that resets *all* perspectives. """ + + +# Enthought library imports. +from enthought.pyface.api import YES + +# Local imports. +from workbench_action import WorkbenchAction + + +# The message used when confirming the action. +MESSAGE = 'Do you want to reset ALL perspectives to their defaults?' + + +class ResetAllPerspectivesAction(WorkbenchAction): + """ An action that resets *all* perspectives. """ + + #### 'Action' interface ################################################### + + # The action's unique identifier (may be None). + id = 'enthought.pyface.workbench.action.reset_all_perspectives' + + # The action's name (displayed on menus/tool bar tools etc). + name = 'Reset All Perspectives' + + ########################################################################### + # 'Action' interface. + ########################################################################### + + def perform(self, event): + """ Perform the action. """ + + window = self.window + + if window.confirm(MESSAGE) == YES: + window.reset_all_perspectives() + + return + +#### EOF ###################################################################### diff --git a/pyface/workbench/action/save_as_user_perspective_action.py b/pyface/workbench/action/save_as_user_perspective_action.py new file mode 100644 index 000000000..8b818e28f --- /dev/null +++ b/pyface/workbench/action/save_as_user_perspective_action.py @@ -0,0 +1,54 @@ +#----------------------------------------------------------------------------- +# +# Copyright (c) 2005-2006 by Enthought, Inc. +# All rights reserved. +# +# Author: David C. Morrill +# +#----------------------------------------------------------------------------- +""" An action that saves the active perspective as a user perspective. """ + + +# Local imports. +from user_perspective_name import UserPerspectiveName +from workbench_action import WorkbenchAction + + +class SaveAsUserPerspectiveAction(WorkbenchAction): + """ An action that saves the active perspective as a user perspective. """ + + #### 'Action' interface ################################################### + + # The action's unique identifier. + id = 'enthought.pyface.workbench.action.save_as_user_perspective_action' + + # The action's name (displayed on menus/tool bar tools etc). + name = 'Save Perspective As...' + + ########################################################################### + # 'Action' interface. + ########################################################################### + + def perform(self, event): + """ Perform the action. """ + + window = event.window + manager = window.workbench.user_perspective_manager + + # Get the name of the new perspective. + upn = UserPerspectiveName(name=window.active_perspective.name) + if upn.edit_traits(view='save_as_view').result: + # Make a clone of the active perspective, but give it the new name. + perspective = manager.clone_perspective( + window, window.active_perspective, name=upn.name.strip() + ) + + # Add it to the window... + window.perspectives.append(perspective) + + # ... and make it the active perspective. + window.active_perspective = perspective + + return + +#### EOF ##################################################################### diff --git a/pyface/workbench/action/set_active_perspective_action.py b/pyface/workbench/action/set_active_perspective_action.py new file mode 100644 index 000000000..e3fa9819f --- /dev/null +++ b/pyface/workbench/action/set_active_perspective_action.py @@ -0,0 +1,67 @@ +""" An action that sets the active perspective. """ + + +# Enthought library imports. +from enthought.pyface.workbench.api import IPerspective +from enthought.traits.api import Delegate, Instance, on_trait_change + +# Local imports. +from workbench_action import WorkbenchAction + + +class SetActivePerspectiveAction(WorkbenchAction): + """ An action that sets the active perspective. """ + + #### 'Action' interface ################################################### + + # Is the action enabled? + enabled = Delegate('perspective') + + # The action's unique identifier (may be None). + id = Delegate('perspective') + + # The action's name (displayed on menus/tool bar tools etc). + name = Delegate('perspective') + + # The action's style. + style = 'radio' + + #### 'SetActivePerspectiveAction' interface ############################### + + # The perspective that we set the active perspective to. + perspective = Instance(IPerspective) + + ########################################################################### + # 'Action' interface. + ########################################################################### + + def destroy(self): + """ Destroy the action. """ + + self.window = None + + return + + def perform(self, event): + """ Perform the action. """ + + self.window.active_perspective = self.perspective + + return + + ########################################################################### + # Private interface. + ########################################################################### + + @on_trait_change('perspective,window.active_perspective') + def _refresh_checked(self): + """ Refresh the checked state of the action. """ + + self.checked = self.perspective is not None \ + and self.window is not None \ + and self.window.active_perspective is not None \ + and self.perspective.id is self.window.active_perspective.id + + return + +#### EOF ###################################################################### diff --git a/pyface/workbench/action/setattr_action.py b/pyface/workbench/action/setattr_action.py new file mode 100644 index 000000000..54662984c --- /dev/null +++ b/pyface/workbench/action/setattr_action.py @@ -0,0 +1,36 @@ +""" An action that sets an attribute. """ + + +# Enthought library imports. +from enthought.traits.api import Any, Str + +# Local imports. +from workbench_action import WorkbenchAction + + +class SetattrAction(WorkbenchAction): + """ An action that sets an attribute. """ + + #### 'SetattrAction' interface ############################################ + + # The object that we set the attribute on. + obj = Any + + # The name of the attribute that we set. + attribute_name = Str + + # The value that we set the attribute to. + value = Any + + ########################################################################### + # 'Action' interface. + ########################################################################### + + def perform(self, event): + """ Performs the action. """ + + setattr(self.obj, self.attribute_name, self.value) + + return + +#### EOF ###################################################################### diff --git a/pyface/workbench/action/show_view_action.py b/pyface/workbench/action/show_view_action.py new file mode 100644 index 000000000..2b19ef567 --- /dev/null +++ b/pyface/workbench/action/show_view_action.py @@ -0,0 +1,44 @@ +""" An action that shows a dialog to allow the user to choose a view. """ + + +# Local imports. +from view_chooser import ViewChooser +from workbench_action import WorkbenchAction + + +class ShowViewAction(WorkbenchAction): + """ An action that shows a dialog to allow the user to choose a view. """ + + #### 'Action' interface ################################################### + + # The action's unique identifier (may be None). + id = 'enthought.pyface.workbench.action.show_view' + + # The action's name (displayed on menus/tool bar tools etc). + name = 'Show View' + + ########################################################################### + # 'Action' interface. + ########################################################################### + + def perform(self, event): + """ Perform the action. """ + + chooser = ViewChooser(window=self.window) + + ui = chooser.edit_traits(parent=self.window.control, kind='livemodal') + + # If the user closes the dialog by using the window manager's close button + # (e.g. the little [x] in the top corner), ui.result is True, but chooser.view + # might be None, so we need an explicit check for that. + if ui.result and chooser.view is not None: + # This shows the view... + chooser.view.show() + + # ... and this makes it active (brings it to the front, gives it + # focus etc). + chooser.view.activate() + + return + +#### EOF ###################################################################### diff --git a/pyface/workbench/action/toggle_view_visibility_action.py b/pyface/workbench/action/toggle_view_visibility_action.py new file mode 100644 index 000000000..0294fbf3f --- /dev/null +++ b/pyface/workbench/action/toggle_view_visibility_action.py @@ -0,0 +1,107 @@ +""" An action that toggles a view's visibility (ie. hides/shows it). """ + + +# Enthought library imports. +from enthought.pyface.workbench.api import IView +from enthought.traits.api import Delegate, Instance + +# Local imports. +from workbench_action import WorkbenchAction + + +class ToggleViewVisibilityAction(WorkbenchAction): + """ An action that toggles a view's visibility (ie. hides/shows it). """ + + #### 'Action' interface ################################################### + + # The action's unique identifier (may be None). + id = Delegate('view', modify=True) + + # The action's name (displayed on menus/tool bar tools etc). + name = Delegate('view', modify=True) + + # The action's style. + style = 'toggle' + + #### 'ViewAction' interface ############################################### + + # The view that we toggle the visibility for. + view = Instance(IView) + + ########################################################################### + # 'Action' interface. + ########################################################################### + + def destroy(self): + """ Called when the action is no longer required. """ + + if self.view is not None: + self._remove_view_listeners(self.view) + + return + + def perform(self, event): + """ Perform the action. """ + + self._toggle_view_visibility(self.view) + + return + + ########################################################################### + # Private interface. + ########################################################################### + + #### Trait change handlers ################################################ + + def _view_changed(self, old, new): + """ Static trait change handler. """ + + if old is not None: + self._remove_view_listeners(old) + + if new is not None: + self._add_view_listeners(new) + + self._refresh_checked() + + return + + #### Methods ############################################################## + + def _add_view_listeners(self, view): + """ Add listeners for trait events on a view. """ + + view.on_trait_change(self._refresh_checked, 'visible') + view.on_trait_change(self._refresh_checked, 'window') + + return + + def _remove_view_listeners(self, view): + """ Add listeners for trait events on a view. """ + + view.on_trait_change(self._refresh_checked, 'visible', remove=True) + view.on_trait_change(self._refresh_checked, 'window', remove=True) + + return + + def _refresh_checked(self): + """ Refresh the checked state of the action. """ + + self.checked = self.view is not None \ + and self.view.window is not None \ + and self.view.visible + + return + + def _toggle_view_visibility(self, view): + """ Toggle the visibility of a view. """ + + if view.visible: + view.hide() + + else: + view.show() + + return + +#### EOF ###################################################################### diff --git a/pyface/workbench/action/tool_bar_manager.py b/pyface/workbench/action/tool_bar_manager.py new file mode 100644 index 000000000..cc309eb53 --- /dev/null +++ b/pyface/workbench/action/tool_bar_manager.py @@ -0,0 +1,37 @@ +""" The tool bar manager for the Envisage workbench window. """ + + +# Enthought library imports. +import enthought.pyface.action.api as pyface +from enthought.traits.api import Instance + +# Local imports. +from action_controller import ActionController + + +class ToolBarManager(pyface.ToolBarManager): + """ The tool bar manager for the Envisage workbench window. """ + + #### 'ToolBarManager' interface ########################################### + + # The workbench window that we are the tool bar manager for. + window = Instance('enthought.pyface.workbench.api.WorkbenchWindow') + + ########################################################################### + # 'ToolBarManager' interface. + ########################################################################### + + def create_tool_bar(self, parent, controller=None): + """ Creates a tool bar representation of the manager. """ + + # The controller handles the invocation of every action. + if controller is None: + controller = ActionController(window=self.window) + + tool_bar = super(ToolBarManager, self).create_tool_bar( + parent, controller=controller + ) + + return tool_bar + +#### EOF ###################################################################### diff --git a/pyface/workbench/action/user_perspective_action.py b/pyface/workbench/action/user_perspective_action.py new file mode 100644 index 000000000..6bc6c6d72 --- /dev/null +++ b/pyface/workbench/action/user_perspective_action.py @@ -0,0 +1,61 @@ +#----------------------------------------------------------------------------- +# +# Copyright (c) 2005-2006 by Enthought, Inc. +# All rights reserved. +# +# Author: David C. Morrill +# +#----------------------------------------------------------------------------- +""" The base class for user perspective actions. """ + + +# Enthought library imports. +from enthought.traits.api import on_trait_change + +# Local imports. +from workbench_action import WorkbenchAction + + +class UserPerspectiveAction(WorkbenchAction): + """ The base class for user perspective actions. + + Instances of this class (or its subclasses ;^) are enabled only when the + active perspective is a user perspective. + + """ + + ########################################################################### + # 'Action' interface. + ########################################################################### + + def destroy(self): + """ Destroy the action. """ + + # This removes the active perspective listener. + self.window = None + + return + + ########################################################################### + # Private interface. + ########################################################################### + + def _is_user_perspective(self, perspective): + """ Is the specified perspective a user perspective? """ + + # fixme: This seems a bit of a smelly way to make the determinaction! + id = perspective.id + + return ((id[:19] == '__user_perspective_') and (id[-2:] == '__')) + + @on_trait_change('window.active_perspective') + def _refresh_enabled(self): + """ Refresh the enabled state of the action. """ + + self.enabled = self.window is not None \ + and self.window.active_perspective is not None \ + and self._is_user_perspective(self.window.active_perspective) + + return + +#### EOF ##################################################################### diff --git a/pyface/workbench/action/user_perspective_name.py b/pyface/workbench/action/user_perspective_name.py new file mode 100644 index 000000000..011895faa --- /dev/null +++ b/pyface/workbench/action/user_perspective_name.py @@ -0,0 +1,90 @@ +#----------------------------------------------------------------------------- +# +# Copyright (c) 2005-2006 by Enthought, Inc. +# All rights reserved. +# +# Author: David C. Morrill +# +#----------------------------------------------------------------------------- +""" Object with views for naming or renaming a user perspective. """ + + +# Enthought library imports. +from enthought.traits.api import Bool, HasTraits, Trait, TraitError, Constant +from enthought.traits.ui.api import View, Item, VGroup + + +#### Trait definitions ######################################################## + +def not_empty_string(object, name, value): + """a not-empty string""" + + if isinstance(value, basestring) and (value.strip() != ''): + return value + + raise TraitError + +# Define a trait which can not be the empty string: +NotEmptyString = Trait('', not_empty_string) + + +class UserPerspectiveName(HasTraits): + """ Object with views for naming or renaming a user perspective. """ + + ########################################################################### + # 'UserPerspectiveName' interface. + ########################################################################### + + # The name of the new user perspective. + name = NotEmptyString + + # Should the editor area be shown in this perpsective? + show_editor_area = Bool(True) + + # Help notes when creating a new view. + new_help = Constant("""Note: + - The new perspective will initially be empty. + - Add new views to the perspective by selecting + them from the 'View' menu. + - Drag the notebook tabs and splitter bars to + arrange the views within the perspective.""") + + #### Traits views ######################################################### + + new_view = View( + VGroup( + VGroup( 'name', 'show_editor_area' ), + VGroup( '_', + Item( 'new_help', + style = 'readonly' ), + show_labels = False + ) + ), + title = 'New User Perspective', + id = 'enthought.envisage.workbench.action.' + 'new_user_perspective_action.UserPerspectiveName', + buttons = [ 'OK', 'Cancel' ], + kind = 'livemodal', + width = 300 + ) + + save_as_view = View( 'name', + title = 'Save User Perspective As', + id = 'enthought.envisage.workbench.action.' + 'save_as_user_perspective_action.UserPerspectiveName', + buttons = [ 'OK', 'Cancel' ], + kind = 'livemodal', + width = 300 + ) + + rename_view = View( 'name', + title = 'Rename User Perspective', + id = 'enthought.envisage.workbench.action.' + 'rename_user_perspective_action.UserPerspectiveName', + buttons = [ 'OK', 'Cancel' ], + kind = 'livemodal', + width = 300 + ) + +#### EOF ##################################################################### + diff --git a/pyface/workbench/action/view_chooser.py b/pyface/workbench/action/view_chooser.py new file mode 100644 index 000000000..ac4159af6 --- /dev/null +++ b/pyface/workbench/action/view_chooser.py @@ -0,0 +1,199 @@ +""" A UI that allows the user to choose a view. """ + + +# Enthought library imports. +from enthought.pyface.workbench.api import IView, WorkbenchWindow +from enthought.traits.api import Any, HasTraits, Instance, List, Str +from enthought.traits.api import TraitError, Undefined +from enthought.traits.ui.api import Item, TreeEditor, TreeNode, View +from enthought.traits.ui.menu import Action # fixme: Non-api import! + + +class Category(HasTraits): + """ A view category. """ + + # The name of the category. + name = Str + + # The views in the category. + views = List + + +class WorkbenchWindowTreeNode(TreeNode): + """ A tree node for workbench windows that displays the window's views. + + The views are grouped by their category. + + """ + + #### 'TreeNode' interface ################################################# + + # List of object classes that the node applies to. + node_for = [WorkbenchWindow] + + ########################################################################### + # 'TreeNode' interface. + ########################################################################### + + def get_children(self, object): + """ Get the object's children. """ + + # Collate the window's views into categories. + categories_by_name = self._get_categories_by_name(object) + + categories = categories_by_name.values() + categories.sort(key=lambda category: category.name) + + return categories + + ########################################################################### + # Private interface. + ########################################################################### + + def _get_categories_by_name(self, window): + """ Return a dictionary containing all categories keyed by name. """ + + categories_by_name = {} + for view in window.views: + category = categories_by_name.get(view.category) + if category is None: + category = Category(name=view.category) + categories_by_name[view.category] = category + + category.views.append(view) + + return categories_by_name + + +class IViewTreeNode(TreeNode): + """ A tree node for objects that implement the 'IView' interface. + + This node does *not* recognise objects that can be *adapted* to the 'IView' + interface, only those that actually implement it. If we wanted to allow + for adaptation we would have to work out a way for the rest of the + 'TreeNode' code to access the adapter, not the original object. We could, + of course override every method, but that seems a little, errr, tedious. + We could probably do with something like in the PyFace tree where there + is a method that returns the actual object that we want to manipulate. + + """ + + def is_node_for(self, obj): + """ Returns whether this is the node that handles a specified object. + + """ + + # By checking for 'is obj' here, we are *not* allowing adaptation (if + # we were allowing adaptation it would be 'is not None'). See the class + # doc string for details. + return IView(obj, Undefined) is obj + + def get_icon(self, obj, is_expanded): + """ Returns the icon for a specified object. """ + + if obj.image is not None: + icon = obj.image + + else: + # fixme: A bit of magic here! Is there a better way to say 'use + # the default leaf icon'? + icon = '' + + return icon + + +class ViewChooser(HasTraits): + """ Allow the user to choose a view. + + This implementation shows views in a tree grouped by category. + + """ + + # The window that contains the views to choose from. + window = Instance('enthought.pyface.workbench.api.WorkbenchWindow') + + # The currently selected tree item (at any point in time this might be + # either None, a view category, or a view). + selected = Any + + # The selected view (None if the selected item is not a view). + view = Instance(IView) + + #### Traits UI views ###################################################### + + traits_ui_view = View( + Item( + name = 'window', + editor = TreeEditor( + nodes = [ + WorkbenchWindowTreeNode( + auto_open = True, + label = '=Views', + rename = False, + copy = False, + delete = False, + insert = False, + menu = None, + ), + + TreeNode( + node_for = [Category], + auto_open = True, + children = 'views', + label = 'name', + rename = False, + copy = False, + delete = False, + insert = False, + menu = None, + ), + + IViewTreeNode( + auto_open = False, + label = 'name', + rename = False, + copy = False, + delete = False, + insert = False, + menu = None, + ) + ], + + editable = False, + hide_root = True, + selected = 'selected', + show_icons = True + ), + show_label = False + ), + + buttons = [ + Action(name='OK', enabled_when='view is not None'), 'Cancel' + ], + + resizable = True, + style = 'custom', + title = 'Show View', + + width = .2, + height = .4 + ) + + ########################################################################### + # 'ViewChooser' interface. + ########################################################################### + + def _selected_changed(self, old, new): + """ Static trait change handler. """ + + # If the assignment fails then the selected object does *not* implement + # the 'IView' interface. + try: + self.view = new + + except TraitError: + self.view = None + + return + +#### EOF ###################################################################### diff --git a/pyface/workbench/action/view_menu_manager.py b/pyface/workbench/action/view_menu_manager.py new file mode 100644 index 000000000..b9e8b327b --- /dev/null +++ b/pyface/workbench/action/view_menu_manager.py @@ -0,0 +1,145 @@ +""" The 'View' menu """ + + +# Standard library imports. +import logging + +# Enthought library imports. +from enthought.pyface.action.api import Group, MenuManager +from enthought.traits.api import Any, Bool, Instance, List, Str, Unicode +from enthought.traits.api import on_trait_change + +# Local imports. +from perspective_menu_manager import PerspectiveMenuManager +from show_view_action import ShowViewAction +from toggle_view_visibility_action import ToggleViewVisibilityAction + + +# Logging. +logger = logging.getLogger(__name__) + + +class ViewMenuManager(MenuManager): + """ The 'View' menu. + + By default, this menu is displayed on the main menu bar. + + """ + + #### 'ActionManager' interface ############################################ + + # All of the groups in the manager. + groups = List(Group) + + # The manager's unique identifier (if it has one). + id = Str('View') + + #### 'MenuManager' interface ############################################## + + # The menu manager's name (if the manager is a sub-menu, this is what its + # label will be). + name = Unicode('&View') + + #### 'ViewMenuManager' interface ########################################## + + # Should the perspective menu be shown? + show_perspective_menu = Bool(True) + + # The workbench window that the menu is part of. + window = Instance('enthought.pyface.workbench.api.WorkbenchWindow') + + #### 'Private' interface ################################################## + + # The group containing the view hide/show actions. + _view_group = Any + + ########################################################################### + # 'ActionManager' interface. + ########################################################################### + + def _groups_default(self): + """ Trait initializer. """ + + groups = [] + + # Add a group containing the perspective menu (if requested). + if self.show_perspective_menu and len(self.window.perspectives) > 0: + groups.append(Group(PerspectiveMenuManager(window=self.window))) + + # Add a group containing a 'toggler' for all visible views. + self._view_group = self._create_view_group(self.window) + groups.append(self._view_group) + + # Add a group containing an 'Other...' item that will launch a dialog + # to allow the user to choose a view to show. + groups.append(self._create_other_group(self.window)) + + return groups + + ########################################################################### + # 'ViewMenuManager' interface. + ########################################################################### + + @on_trait_change( + 'window.active_perspective,window.active_part,' + 'window.views,window.views_items' + ) + def refresh(self): + """ Refreshes the checked state of the actions in the menu. """ + + logger.debug('refreshing view menu') + + if self._view_group is not None: + self._clear_group(self._view_group) + self._initialize_view_group(self.window, self._view_group) + self.changed = True + + return + + ########################################################################### + # Private interface. + ########################################################################### + + def _clear_group(self, group): + """ Remove all items in a group. """ + + # fixme: Fix this API in PyFace so there is only one call! + group.destroy() + group.clear() + + return + + def _create_other_group(self, window): + """ Creates a group containing the 'Other...' action. """ + + group = Group() + group.append(ShowViewAction(name='Other...', window=window)) + + return group + + def _create_view_group(self, window): + """ Creates a group containing the view 'togglers'. """ + + group = Group() + self._initialize_view_group(window, group) + + return group + + def _initialize_view_group(self, window, group): + """ Initializes a group containing the view 'togglers'. """ + + views = window.views[:] + views.sort(None, lambda view: view.name) + + for view in views: + # fixme: It seems a little smelly to be reaching in to the window + # layout here. Should the 'contains_view' method be part of the + # window interface? + if window.layout.contains_view(view): + group.append( + ToggleViewVisibilityAction(view=view, window=window) + ) + + return + +#### EOF ###################################################################### diff --git a/pyface/workbench/action/workbench_action.py b/pyface/workbench/action/workbench_action.py new file mode 100644 index 000000000..1ab55e4fd --- /dev/null +++ b/pyface/workbench/action/workbench_action.py @@ -0,0 +1,20 @@ +""" Abstract base class for all workbench actions. """ + + +# Enthought library imports. +from enthought.pyface.workbench.api import WorkbenchWindow +from enthought.pyface.action.api import Action +from enthought.traits.api import Instance + + +class WorkbenchAction(Action): + """ Abstract base class for all workbench actions. """ + + #### 'WorkbenchAction' interface ########################################## + + # The workbench window that the action is in. + # + # This is set by the framework. + window = Instance(WorkbenchWindow) + +#### EOF ###################################################################### diff --git a/pyface/workbench/api.py b/pyface/workbench/api.py new file mode 100755 index 000000000..496cfb467 --- /dev/null +++ b/pyface/workbench/api.py @@ -0,0 +1,20 @@ +from i_editor import IEditor +from editor import Editor + +from i_editor_manager import IEditorManager +from editor_manager import EditorManager + +from i_perspective import IPerspective +from perspective import Perspective +from perspective_item import PerspectiveItem + +from i_view import IView +from view import View + +from i_workbench import IWorkbench +from workbench import Workbench + +from workbench_window import WorkbenchWindow + +from traits_ui_editor import TraitsUIEditor +from traits_ui_view import TraitsUIView diff --git a/pyface/workbench/debug/__init__.py b/pyface/workbench/debug/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/pyface/workbench/debug/api.py b/pyface/workbench/debug/api.py new file mode 100644 index 000000000..20d5eb938 --- /dev/null +++ b/pyface/workbench/debug/api.py @@ -0,0 +1 @@ +from debug_view import DebugView diff --git a/pyface/workbench/debug/debug_view.py b/pyface/workbench/debug/debug_view.py new file mode 100644 index 000000000..9d5f30409 --- /dev/null +++ b/pyface/workbench/debug/debug_view.py @@ -0,0 +1,94 @@ +""" A view containing a main walter canvas. """ + + +# Enthought library imports. +from enthought.pyface.workbench.api import View, WorkbenchWindow +from enthought.traits.api import HasTraits, Instance, Str, on_trait_change +from enthought.traits.ui.api import View as TraitsView + + +class DebugViewModel(HasTraits): + """ The model for the debug view! """ + + #### 'Model' interface #################################################### + + active_editor = Str + active_part = Str + active_view = Str + + window = Instance(WorkbenchWindow) + + ########################################################################### + # 'Model' interface. + ########################################################################### + + @on_trait_change( + 'window.active_editor', 'window.active_part', 'window.active_view' + ) + def refresh(self): + """ Refresh the model. """ + + self.active_editor = self._get_id(self.window.active_editor) + self.active_part = self._get_id(self.window.active_part) + self.active_view = self._get_id(self.window.active_view) + + return + + def _window_changed(self): + """ Window changed! """ + + self.refresh() + + return + + ########################################################################### + # Private interface. + ########################################################################### + + def _get_id(self, obj): + """ Return the Id of an object. """ + + if obj is None: + id = 'None' + + else: + id = obj.id + + return id + + +class DebugView(View): + """ A view containing a main walter canvas. """ + + #### 'IWorkbenchPart' interface ########################################### + + # The part's name (displayed to the user). + name = 'Debug' + + #### 'DebugView' interface ################################################ + + # The model for the debug view! + model = Instance(DebugViewModel) + + ########################################################################### + # 'IWorkbenchPart' interface. + ########################################################################### + + def create_control(self, parent): + """ Creates the toolkit-specific control that represents the view. + + 'parent' is the toolkit-specific control that is the view's parent. + + """ + + self.model = DebugViewModel(window=self.window) + + ui = self.model.edit_traits( + parent = parent, + kind = 'subpanel', + view = TraitsView('active_part', 'active_editor', 'active_view') + ) + + return ui.control + +#### EOF ###################################################################### diff --git a/pyface/workbench/editor.py b/pyface/workbench/editor.py new file mode 100755 index 000000000..9f7f3207b --- /dev/null +++ b/pyface/workbench/editor.py @@ -0,0 +1,21 @@ +#------------------------------------------------------------------------------ +# Copyright (c) 2005, Enthought, Inc. +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in enthought/LICENSE.txt and may be redistributed only +# under the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# Thanks for using Enthought open source! +# +# Author: Enthought, Inc. +# Description: +#------------------------------------------------------------------------------ +""" The implementation of a workbench editor. """ + + +# Import the toolkit specific version. +from enthought.pyface.toolkit import toolkit_object +Editor = toolkit_object('workbench.editor:Editor') + +### EOF ####################################################################### diff --git a/pyface/workbench/editor_manager.py b/pyface/workbench/editor_manager.py new file mode 100755 index 000000000..04234f971 --- /dev/null +++ b/pyface/workbench/editor_manager.py @@ -0,0 +1,97 @@ +""" The default editor manager. """ + +# Standard library imports. +import weakref + +# Enthought library imports. +from enthought.traits.api import HasTraits, Instance, implements + +# Local imports. +from i_editor_manager import IEditorManager +from traits_ui_editor import TraitsUIEditor + + +class EditorManager(HasTraits): + """ The default editor manager. """ + + implements(IEditorManager) + + #### 'IEditorManager' interface ########################################### + + # The workbench window that the editor manager manages editors for ;^) + window = Instance('enthought.pyface.workbench.api.WorkbenchWindow') + + ########################################################################### + # 'object' interface. + ########################################################################### + + def __init__(self, **traits): + """ Constructor. """ + + super(EditorManager, self).__init__(**traits) + + # A mapping from editor to editor kind (the factory that created them). + self._editor_to_kind_map = weakref.WeakKeyDictionary() + + return + + ########################################################################### + # 'IEditorManager' interface. + ########################################################################### + + def add_editor(self, editor, kind): + """ Registers an existing editor. """ + + self._editor_to_kind_map[editor] = kind + + def create_editor(self, window, obj, kind): + """ Create an editor for an object. """ + + editor = TraitsUIEditor(window=window, obj=obj) + + self.add_editor(editor, kind) + + return editor + + def get_editor(self, window, obj, kind): + """ Get the editor that is currently editing an object. """ + + for editor in window.editors: + if self._is_editing(editor, obj, kind): + break + else: + editor = None + + return editor + + def get_editor_kind(self, editor): + """ Return the 'kind' associated with 'editor'. """ + + return self._editor_to_kind_map[editor] + + def get_editor_memento(self, editor): + """ Return the state of an editor suitable for pickling etc. + + By default we don't save the state of editors. + """ + + return None + + def set_editor_memento(self, memento): + """ Restore the state of an editor from a memento. + + By default we don't try to restore the state of editors. + """ + + return None + + ########################################################################### + # 'Protected' 'EditorManager' interface. + ########################################################################### + + def _is_editing(self, editor, obj, kind): + """ Return True if the editor is editing the object. """ + + return editor.obj == obj + +#### EOF ###################################################################### diff --git a/pyface/workbench/i_editor.py b/pyface/workbench/i_editor.py new file mode 100755 index 000000000..445f84ff5 --- /dev/null +++ b/pyface/workbench/i_editor.py @@ -0,0 +1,146 @@ +#------------------------------------------------------------------------------ +# Copyright (c) 2005, Enthought, Inc. +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in enthought/LICENSE.txt and may be redistributed only +# under the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# Thanks for using Enthought open source! +# +# Author: Enthought, Inc. +# Description: +#------------------------------------------------------------------------------ +""" The interface of a workbench editor. """ +# standard library imports +import uuid + +# Enthought library imports. +from enthought.traits.api import Any, Bool, Event, VetoableEvent, Vetoable, \ + HasTraits, Instance, Interface +from enthought.traits.api import implements + +# Local imports. +from i_workbench_part import IWorkbenchPart, MWorkbenchPart + + +class IEditor(IWorkbenchPart): + """ The interface of a workbench editor. """ + + # The optional command stack. + command_stack = Instance('enthought.undo.api.ICommandStack') + + # Is the object that the editor is editing 'dirty' i.e., has it been + # modified but not saved? + dirty = Bool(False) + + # The object that the editor is editing. + # + # The framework sets this when the editor is created. + obj = Any + + #### Editor Lifecycle Events ############################################## + + # Fired when the editor is closing. + closing = VetoableEvent + + # Fired when the editor is closed. + closed = Event + + #### Methods ############################################################## + + def close(self): + """ Close the editor. + + This method is not currently called by the framework itself as the user + is normally in control of the editor lifecycle. Call this if you want + to control the editor lifecycle programmatically. + + """ + + +class MEditor(MWorkbenchPart): + """ Mixin containing common code for toolkit-specific implementations. """ + + implements(IEditor) + + #### 'IEditor' interface ################################################## + + # The optional command stack. + command_stack = Instance('enthought.undo.api.ICommandStack') + + # Is the object that the editor is editing 'dirty' i.e., has it been + # modified but not saved? + dirty = Bool(False) + + # The object that the editor is editing. + # + # The framework sets this when the editor is created. + obj = Any + + #### Editor Lifecycle Events ############################################## + + # Fired when the editor is opening. + opening = VetoableEvent + + # Fired when the editor has been opened. + open = Event + + # Fired when the editor is closing. + closing = Event(VetoableEvent) + + # Fired when the editor is closed. + closed = Event + + ########################################################################### + # 'object' interface. + ########################################################################### + + def __str__(self): + """ Return an informal string representation of the object. """ + + return 'Editor(%s)' % self.id + + ########################################################################### + # 'IWorkbenchPart' interface. + ########################################################################### + + def _id_default(self): + """ Trait initializer. """ + + # If no Id is specified then use a random uuid + # this gaurantees (barring *really* unusual cases) that there are no + # collisions between the ids of editors. + return uuid.uuid4().hex + + ########################################################################### + # 'IEditor' interface. + ########################################################################### + + def close(self): + """ Close the editor. """ + + if self.control is not None: + self.closing = event = Vetoable() + if not event.veto: + self.window.close_editor(self) + + self.closed = True + + return + + #### Initializers ######################################################### + + def _command_stack_default(self): + """ Trait initializer. """ + + # We make sure the undo package is entirely optional. + try: + from enthought.undo.api import CommandStack + except ImportError: + return None + + return CommandStack(undo_manager=self.window.workbench.undo_manager) + + +#### EOF ###################################################################### diff --git a/pyface/workbench/i_editor_manager.py b/pyface/workbench/i_editor_manager.py new file mode 100755 index 000000000..1b8048007 --- /dev/null +++ b/pyface/workbench/i_editor_manager.py @@ -0,0 +1,45 @@ +""" The editor manager interface. """ + + +# Enthought library imports. +from enthought.traits.api import Instance, Interface + + +class IEditorManager(Interface): + """ The editor manager interface. """ + + # The workbench window that the editor manager manages editors for ;^) + window = Instance('enthought.pyface.workbench.api.WorkbenchWindow') + + def add_editor(self, editor, kind): + """ Registers an existing editor. """ + + def create_editor(self, window, obj, kind): + """ Create an editor for an object. + + 'kind' optionally contains any data required by the specific editor + manager implementation to decide what type of editor to create. + + Returns None if no editor can be created for the resource. + """ + + def get_editor(self, window, obj, kind): + """ Get the editor that is currently editing an object. + + 'kind' optionally contains any data required by the specific editor + manager implementation to decide what type of editor to create. + + Returns None if no such editor exists. + """ + + def get_editor_kind(self, editor): + """ Return the 'kind' associated with 'editor'. """ + + def get_editor_memento(self, editor): + """ Return the state of an editor suitable for pickling etc. + """ + + def set_editor_memento(self, memento): + """ Restore an editor from a memento and return it. """ + +#### EOF ###################################################################### diff --git a/pyface/workbench/i_perspective.py b/pyface/workbench/i_perspective.py new file mode 100755 index 000000000..a6be93358 --- /dev/null +++ b/pyface/workbench/i_perspective.py @@ -0,0 +1,42 @@ +""" The perspective interface. """ + + +# Enthought library imports. +from enthought.traits.api import Bool, Interface, List, Str, Tuple + +# Local imports. +from perspective_item import PerspectiveItem + + +class IPerspective(Interface): + """ The perspective interface. """ + + # The perspective's unique identifier (unique within a workbench window). + id = Str + + # The perspective's name. + name = Str + + # The contents of the perspective. + contents = List(PerspectiveItem) + + # The size of the editor area in this perspective. A value of (-1, -1) + # indicates that the workbench window should choose an appropriate size + # based on the sizes of the views in the perspective. + editor_area_size = Tuple + + # Is the perspective enabled? + enabled = Bool + + # Should the editor area be shown in this perspective? + show_editor_area = Bool + + #### Methods ############################################################## + + def create(self, window): + """ Create the perspective in a workbench window. """ + + def show(self, window): + """ Called when the perspective is shown in a workbench window. """ + +#### EOF ###################################################################### diff --git a/pyface/workbench/i_perspective_item.py b/pyface/workbench/i_perspective_item.py new file mode 100644 index 000000000..42b85fff4 --- /dev/null +++ b/pyface/workbench/i_perspective_item.py @@ -0,0 +1,52 @@ +""" The interface for perspective items. """ + + +# Enthought library imports. +from enthought.traits.api import Enum, Float, Interface, Str + + +class IPerspectiveItem(Interface): + """ The interface for perspective items. """ + + # The Id of the view to display in the perspective. + id = Str + + # The position of the view relative to the item specified in the + # 'relative_to' trait. + # + # 'top' puts the view above the 'relative_to' item. + # 'bottom' puts the view below the 'relative_to' item. + # 'left' puts the view to the left of the 'relative_to' item. + # 'right' puts the view to the right of the 'relative_to' item. + # 'with' puts the view in the same region as the 'relative_to' item. + # + # If the position is specified as 'with' you must specify a 'relative_to' + # item other than the editor area (i.e., you cannot position a view 'with' + # the editor area). + position = Enum('left', 'top', 'bottom', 'right', 'with') + + # The Id of the view to position relative to. If this is not specified + # (or if no view exists with this Id) then the view will be placed relative + # to the editor area. + relative_to = Str + + # The width of the item (as a fraction of the window width). + # + # e.g. 0.5 == half the window width. + # + # Note that this is treated as a suggestion, and it may not be possible + # for the workbench to allocate the space requested. + width = Float(-1) + + # The height of the item (as a fraction of the window height). + # + # e.g. 0.5 == half the window height. + # + # Note that this is treated as a suggestion, and it may not be possible + # for the workbench to allocate the space requested. + height = Float(-1) + + # The style of the dock window. + style_hint = Enum('tab', 'horizontal', 'vertical', 'fixed') + +#### EOF ###################################################################### diff --git a/pyface/workbench/i_view.py b/pyface/workbench/i_view.py new file mode 100755 index 000000000..521f01b31 --- /dev/null +++ b/pyface/workbench/i_view.py @@ -0,0 +1,142 @@ +#------------------------------------------------------------------------------ +# Copyright (c) 2005, Enthought, Inc. +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in enthought/LICENSE.txt and may be redistributed only +# under the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# Thanks for using Enthought open source! +# +# Author: Enthought, Inc. +# Description: +#------------------------------------------------------------------------------ +""" The interface for workbench views. """ + +# Standard library imports. +import logging + +# Enthought library imports. +from enthought.pyface.api import ImageResource +from enthought.traits.api import Bool, Enum, Float, Instance, List, Str, \ + implements +from enthought.util.camel_case import camel_case_to_words + +# Local imports. +from i_perspective_item import IPerspectiveItem +from i_workbench_part import IWorkbenchPart, MWorkbenchPart +from perspective_item import PerspectiveItem + +# Logging. +logger = logging.getLogger(__name__) + + +class IView(IWorkbenchPart, IPerspectiveItem): + """ The interface for workbench views. """ + + # Is the view busy? (i.e., should the busy cursor (often an hourglass) be + # displayed?). + busy = Bool(False) + + # The category that the view belongs to (this can used to group views when + # they are displayed to the user). + category = Str('General') + + # An image used to represent the view to the user (shown in the view tab + # and in the view chooser etc). + image = Instance(ImageResource) + + # Whether the view is visible or not. + visible = Bool(False) + + ########################################################################### + # 'IView' interface. + ########################################################################### + + def activate(self): + """ Activate the view. + + """ + + def hide(self): + """ Hide the view. + + """ + + def show(self): + """ Show the view. + + """ + + +class MView(MWorkbenchPart, PerspectiveItem): + """ Mixin containing common code for toolkit-specific implementations. """ + + implements(IView) + + #### 'IView' interface #################################################### + + # Is the view busy? (i.e., should the busy cursor (often an hourglass) be + # displayed?). + busy = Bool(False) + + # The category that the view belongs to (this can be used to group views + # when they are displayed to the user). + category = Str('General') + + # An image used to represent the view to the user (shown in the view tab + # and in the view chooser etc). + image = Instance(ImageResource) + + # Whether the view is visible or not. + visible = Bool(False) + + ########################################################################### + # 'IWorkbenchPart' interface. + ########################################################################### + + def _id_default(self): + """ Trait initializer. """ + + id = '%s.%s' % (type(self).__module__, type(self).__name__) + logger.warn('view %s has no Id - using <%s>' % (self, id)) + + # If no Id is specified then use the name. + return id + + def _name_default(self): + """ Trait initializer. """ + + name = camel_case_to_words(type(self).__name__) + logger.warn('view %s has no name - using <%s>' % (self, name)) + + return name + + ########################################################################### + # 'IView' interface. + ########################################################################### + + def activate(self): + """ Activate the view. + + """ + + self.window.activate_view(self) + + return + + def hide(self): + """ Hide the view. """ + + self.window.hide_view(self) + + return + + def show(self): + """ Show the view. """ + + self.window.show_view(self) + + return + +#### EOF ###################################################################### diff --git a/pyface/workbench/i_workbench.py b/pyface/workbench/i_workbench.py new file mode 100644 index 000000000..af2f62924 --- /dev/null +++ b/pyface/workbench/i_workbench.py @@ -0,0 +1,105 @@ +""" The workbench interface. """ + + +# Enthought library imports. +from enthought.traits.api import Event, Instance, Interface, List, Str +from enthought.traits.api import VetoableEvent, implements + +# Local imports. +from user_perspective_manager import UserPerspectiveManager +from window_event import WindowEvent, VetoableWindowEvent +from workbench_window import WorkbenchWindow + + +class IWorkbench(Interface): + """ The workbench interface. """ + + #### 'IWorkbench' interface ############################################### + + # The active workbench window (the last one to get focus). + active_window = Instance(WorkbenchWindow) + + # The optional application scripting manager. + script_manager = Instance('enthought.appscripting.api.IScriptManager') + + # A directory on the local file system that we can read and write to at + # will. This is used to persist window layout information, etc. + state_location = Str + + # The optional undo manager. + undo_manager = Instance('enthought.undo.api.IUndoManager') + + # The user defined perspectives manager. + user_perspective_manager = Instance(UserPerspectiveManager) + + # All of the workbench windows created by the workbench. + windows = List(WorkbenchWindow) + + #### Workbench lifecycle events #### + + # Fired when the workbench is about to exit. + # + # This can be caused by either:- + # + # a) The 'exit' method being called. + # b) The last open window being closed. + exiting = VetoableEvent + + # Fired when the workbench has exited. + # + # This is fired after the last open window has been closed. + exited = Event + + #### Window lifecycle events #### + + # Fired when a workbench window has been created. + window_created = Event(WindowEvent) + + # Fired when a workbench window is opening. + window_opening = Event(VetoableWindowEvent) + + # Fired when a workbench window has been opened. + window_opened = Event(WindowEvent) + + # Fired when a workbench window is closing. + window_closing = Event(VetoableWindowEvent) + + # Fired when a workbench window has been closed. + window_closed = Event(WindowEvent) + + ########################################################################### + # 'IWorkbench' interface. + ########################################################################### + + def create_window(self, **kw): + """ Factory method that creates a new workbench window. """ + + def edit(self, obj, kind=None, use_existing=True): + """ Edit an object in the active workbench window. """ + + def exit(self): + """ Exit the workbench. + + This closes all open workbench windows. + + This method is not called when the user clicks the close icon. Nor when + they do an Alt+F4 in Windows. It is only called when the application + menu File->Exit item is selected. + + """ + + def get_editor(self, obj, kind=None): + """ Return the editor that is editing an object. + + Returns None if no such editor exists. + + """ + + def get_editor_by_id(self, id): + """ Return the editor with the specified Id. + + Returns None if no such editor exists. + + """ + +#### EOF ###################################################################### diff --git a/pyface/workbench/i_workbench_part.py b/pyface/workbench/i_workbench_part.py new file mode 100644 index 000000000..4bd881d89 --- /dev/null +++ b/pyface/workbench/i_workbench_part.py @@ -0,0 +1,125 @@ +#------------------------------------------------------------------------------ +# Copyright (c) 2005, Enthought, Inc. +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in enthought/LICENSE.txt and may be redistributed only +# under the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# Thanks for using Enthought open source! +# +# Author: Enthought, Inc. +# Description: +#------------------------------------------------------------------------------ +""" The interface for workbench parts. """ + + +# Enthought library imports. +from enthought.traits.api import Any, Bool, HasTraits, Instance, Interface +from enthought.traits.api import List, Str, Unicode, implements + + +class IWorkbenchPart(Interface): + """ The interface for workbench parts. + + A workbench part is a visual section within the workbench. There are two + sub-types, 'View' and 'Editor'. + + """ + + # The toolkit-specific control that represents the part. + # + # The framework sets this to the value returned by 'create_control'. + control = Any + + # Does the part currently have the focus? + has_focus = Bool(False) + + # The part's globally unique identifier. + id = Str + + # The part's name (displayed to the user). + name = Unicode + + # The current selection within the part. + selection = List + + # The workbench window that the part is in. + # + # The framework sets this when the part is created. + window = Instance('enthought.pyface.workbench.api.WorkbenchWindow') + + #### Methods ############################################################## + + def create_control(self, parent): + """ Create the toolkit-specific control that represents the part. + + The parameter *parent* is the toolkit-specific control that is the + parts's parent. + + Return the toolkit-specific control. + + """ + + def destroy_control(self): + """ Destroy the toolkit-specific control that represents the part. + + Return None. + + """ + + def set_focus(self): + """ Set the focus to the appropriate control in the part. + + Return None. + + """ + + +class MWorkbenchPart(HasTraits): + """ Mixin containing common code for toolkit-specific implementations. """ + + implements(IWorkbenchPart) + + #### 'IWorkbenchPart' interface ########################################### + + # The toolkit-specific control that represents the part. + # + # The framework sets this to the value returned by 'create_control'. + control = Any + + # Does the part currently have the focus? + has_focus = Bool(False) + + # The part's globally unique identifier. + id = Str + + # The part's name (displayed to the user). + name = Unicode + + # The current selection within the part. + selection = List + + # The workbench window that the part is in. + # + # The framework sets this when the part is created. + window = Instance('enthought.pyface.workbench.api.WorkbenchWindow') + + #### Methods ############################################################## + + def create_control(self, parent): + """ Create the toolkit-specific control that represents the part. """ + + raise NotImplementedError + + def destroy_control(self): + """ Destroy the toolkit-specific control that represents the part. """ + + raise NotImplementedError + + def set_focus(self): + """ Set the focus to the appropriate control in the part. """ + + raise NotImplementedError + +#### EOF ###################################################################### diff --git a/pyface/workbench/i_workbench_window_layout.py b/pyface/workbench/i_workbench_window_layout.py new file mode 100755 index 000000000..365362027 --- /dev/null +++ b/pyface/workbench/i_workbench_window_layout.py @@ -0,0 +1,375 @@ +#------------------------------------------------------------------------------ +# Copyright (c) 2005, Enthought, Inc. +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in enthought/LICENSE.txt and may be redistributed only +# under the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# Thanks for using Enthought open source! +# +# Author: Enthought, Inc. +# Description: +#------------------------------------------------------------------------------ +""" The workbench window layout interface. """ + + +# Enthought library imports. +from enthought.traits.api import Event, HasTraits, Instance, Interface, Str +from enthought.traits.api import implements + +# Local imports. +from i_editor import IEditor +from i_view import IView + + +class IWorkbenchWindowLayout(Interface): + """ The workbench window layout interface. + + Window layouts are responsible for creating and managing the internal + structure of a workbench window (it knows how to add and remove views and + editors etc). + + """ + + # The Id of the editor area. + # FIXME v3: This is toolkit specific. + editor_area_id = Str + + # The workbench window that this is the layout for. + window = Instance('enthought.pyface.workbench.api.WorkbenchWindow') + + #### Events #### + + # Fired when an editor is about to be opened (or restored). + editor_opening = Event(IEditor) + + # Fired when an editor has been opened (or restored). + editor_opened = Event(IEditor) + + # Fired when an editor is about to be closed. + editor_closing = Event(IEditor) + + # Fired when an editor has been closed. + editor_closed = Event(IEditor) + + # Fired when a view is about to be opened (or restored). + view_opening = Event(IView) + + # Fired when a view has been opened (or restored). + view_opened = Event(IView) + + # Fired when a view is about to be closed (*not* hidden!). + view_closing = Event(IView) + + # Fired when a view has been closed (*not* hidden!). + view_closed = Event(IView) + + # FIXME v3: The "just for convenience" returns are a really bad idea. + # + # Why? They allow the call to be used on the LHS of an expression... + # Because they have nothing to do with what the call is supposed to be + # doing, they are unlikely to be used (because they are so unexpected and + # inconsistently implemented), and only serve to replace two shorter lines + # of code with one long one, arguably making code more difficult to read. + def activate_editor(self, editor): + """ Activate an editor. + + Returns the editor (just for convenience). + + """ + + def activate_view(self, view): + """ Activate a view. + + Returns the view (just for convenience). + + """ + + def add_editor(self, editor, title): + """ Add an editor. + + Returns the editor (just for convenience). + + """ + + def add_view(self, view, position=None, relative_to=None, size=(-1, -1)): + """ Add a view. + + Returns the view (just for convenience). + + """ + + def close_editor(self, editor): + """ Close an editor. + + Returns the editor (just for convenience). + + """ + + def close_view(self, view): + """ Close a view. + + FIXME v3: Currently views are never 'closed' in the same sense as an + editor is closed. When we close an editor, we destroy its control. + When we close a view, we merely hide its control. I'm not sure if this + is a good idea or not. It came about after discussion with Dave P. and + he mentioned that some views might find it hard to persist enough state + that they can be re-created exactly as they were when they are shown + again. + + Returns the view (just for convenience). + + """ + + def close(self): + """ Close the entire window layout. + + FIXME v3: Should this be called 'destroy'? + + """ + + def create_initial_layout(self, parent): + """ Create the initial window layout. + + Returns the layout. + + """ + + def contains_view(self, view): + """ Return True if the view exists in the window layout. + + Note that this returns True even if the view is hidden. + + """ + + def hide_editor_area(self): + """ Hide the editor area. + + """ + + def hide_view(self, view): + """ Hide a view. + + Returns the view (just for convenience). + + """ + + def refresh(self): + """ Refresh the window layout to reflect any changes. + + """ + + def reset_editors(self): + """ Activate the first editor in every group. + + """ + + def reset_views(self): + """ Activate the first view in every region. + + """ + + def show_editor_area(self): + """ Show the editor area. + + """ + + def show_view(self, view): + """ Show a view. + + """ + + #### Methods for saving and restoring the layout ########################## + + def get_view_memento(self): + """ Returns the state of the views. + + """ + + def set_view_memento(self, memento): + """ Restores the state of the views. + + """ + + def get_editor_memento(self): + """ Returns the state of the editors. + + """ + + def set_editor_memento(self, memento): + """ Restores the state of the editors. + + """ + + +class MWorkbenchWindowLayout(HasTraits): + """ Mixin containing common code for toolkit-specific implementations. """ + + implements(IWorkbenchWindowLayout) + + #### 'IWorkbenchWindowLayout' interface ################################### + + # The Id of the editor area. + # FIXME v3: This is toolkit specific. + editor_area_id = Str + + # The workbench window that this is the layout for. + window = Instance('enthought.pyface.workbench.api.WorkbenchWindow') + + #### Events #### + + # Fired when an editor is about to be opened (or restored). + editor_opening = Event(IEditor) + + # Fired when an editor has been opened (or restored). + editor_opened = Event(IEditor) + + # Fired when an editor is about to be closed. + editor_closing = Event(IEditor) + + # Fired when an editor has been closed. + editor_closed = Event(IEditor) + + # Fired when a view is about to be opened (or restored). + view_opening = Event(IView) + + # Fired when a view has been opened (or restored). + view_opened = Event(IView) + + # Fired when a view is about to be closed (*not* hidden!). + view_closing = Event(IView) + + # Fired when a view has been closed (*not* hidden!). + view_closed = Event(IView) + + ########################################################################### + # 'IWorkbenchWindowLayout' interface. + ########################################################################### + + def activate_editor(self, editor): + """ Activate an editor. """ + + raise NotImplementedError + + def activate_view(self, view): + """ Activate a view. """ + + raise NotImplementedError + + def add_editor(self, editor, title): + """ Add an editor. """ + + raise NotImplementedError + + def add_view(self, view, position=None, relative_to=None, size=(-1, -1)): + """ Add a view. """ + + raise NotImplementedError + + def close_editor(self, editor): + """ Close an editor. """ + + raise NotImplementedError + + def close_view(self, view): + """ Close a view. """ + + raise NotImplementedError + + def close(self): + """ Close the entire window layout. """ + + raise NotImplementedError + + def create_initial_layout(self, parent): + """ Create the initial window layout. """ + + raise NotImplementedError + + def contains_view(self, view): + """ Return True if the view exists in the window layout. """ + + raise NotImplementedError + + def hide_editor_area(self): + """ Hide the editor area. """ + + raise NotImplementedError + + def hide_view(self, view): + """ Hide a view. """ + + raise NotImplementedError + + def refresh(self): + """ Refresh the window layout to reflect any changes. """ + + raise NotImplementedError + + def reset_editors(self): + """ Activate the first editor in every group. """ + + raise NotImplementedError + + def reset_views(self): + """ Activate the first view in every region. """ + + raise NotImplementedError + + def show_editor_area(self): + """ Show the editor area. """ + + raise NotImplementedError + + def show_view(self, view): + """ Show a view. """ + + raise NotImplementedError + + #### Methods for saving and restoring the layout ########################## + + def get_view_memento(self): + """ Returns the state of the views. """ + + raise NotImplementedError + + def set_view_memento(self, memento): + """ Restores the state of the views. """ + + raise NotImplementedError + + def get_editor_memento(self): + """ Returns the state of the editors. """ + + raise NotImplementedError + + def set_editor_memento(self, memento): + """ Restores the state of the editors. """ + + raise NotImplementedError + + ########################################################################### + # Protected 'MWorkbenchWindowLayout' interface. + ########################################################################### + + def _get_editor_references(self): + """ Returns a reference to every editor. """ + + editor_manager = self.window.editor_manager + + editor_references = {} + for editor in self.window.editors: + # Create the editor reference. + # + # If the editor manager returns 'None' instead of a resource + # reference then this editor will not appear the next time the + # workbench starts up. This is useful for things like text files + # that have an editor but have NEVER been saved. + editor_reference = editor_manager.get_editor_memento(editor) + if editor_reference is not None: + editor_references[editor.id] = editor_reference + + return editor_references + +#### EOF ###################################################################### diff --git a/pyface/workbench/perspective.py b/pyface/workbench/perspective.py new file mode 100755 index 000000000..992bf9736 --- /dev/null +++ b/pyface/workbench/perspective.py @@ -0,0 +1,195 @@ +""" The default perspective. """ + + +# Standard library imports. +import logging + +# Enthought library imports. +from enthought.traits.api import Bool, HasTraits, List, Str, Tuple, implements + +# Local imports. +from i_perspective import IPerspective +from perspective_item import PerspectiveItem + + +# Logging. +logger = logging.getLogger(__name__) + + +class Perspective(HasTraits): + """ The default perspective. """ + + implements(IPerspective) + + # The ID of the default perspective. + DEFAULT_ID = 'enthought.pyface.workbench.default' + + # The name of the default perspective. + DEFAULT_NAME = 'Default' + + #### 'IPerspective' interface ############################################# + + # The perspective's unique identifier (unique within a workbench window). + id = Str(DEFAULT_ID) + + # The perspective's name. + name = Str(DEFAULT_NAME) + + # The contents of the perspective. + contents = List(PerspectiveItem) + + # The size of the editor area in this perspective. A value of (-1, -1) + # indicates that the workbench window should choose an appropriate size + # based on the sizes of the views in the perspective. + editor_area_size = Tuple((-1, -1)) + + # Is the perspective enabled? + enabled = Bool(True) + + # Should the editor area be shown in this perspective? + show_editor_area = Bool(True) + + ########################################################################### + # 'object' interface. + ########################################################################### + + def __str__(self): + """ Return an informal string representation of the object. """ + + return 'Perspective(%s)' % self.id + + ########################################################################### + # 'Perspective' interface. + ########################################################################### + + #### Initializers ######################################################### + + def _id_default(self): + """ Trait initializer. """ + + # If no Id is specified then use the name. + return self.name + + #### Methods ############################################################## + + def create(self, window): + """ Create the perspective in a workbench window. + + For most cases you should just be able to set the 'contents' trait to + lay out views as required. However, you can override this method if + you want to have complete control over how the perspective is created. + + """ + + # Set the size of the editor area. + if self.editor_area_size != (-1, -1): + window.editor_area_size = self.editor_area_size + + # If the perspective has specific contents then add just those. + if len(self.contents) > 0: + self._add_contents(window, self.contents) + + # Otherwise, add all of the views defined in the window at their + # default positions realtive to the editor area. + else: + self._add_all(window) + + # Activate the first view in every region. + window.reset_views() + + return + + def show(self, window): + """ Called when the perspective is shown in a workbench window. + + The default implementation does nothing, but you can override this + method if you want to do something whenever the perspective is + activated. + + """ + + return + + ########################################################################### + # Private interface. + ########################################################################### + + def _add_contents(self, window, contents): + """ Adds the specified contents. """ + + # If we are adding specific contents then we ignore any default view + # visibility. + # + # fixme: This is a bit ugly! Why don't we pass the visibility in to + # 'window.add_view'? + for view in window.views: + view.visible = False + + for item in contents: + self._add_perspective_item(window, item) + + return + + def _add_perspective_item(self, window, item): + """ Adds a perspective item to a window. """ + + # If no 'relative_to' is specified then the view is positioned + # relative to the editor area. + if len(item.relative_to) > 0: + relative_to = window.get_view_by_id(item.relative_to) + + else: + relative_to = None + + # fixme: This seems a bit ugly, having to reach back up to the + # window to get the view. Maybe its not that bad? + view = window.get_view_by_id(item.id) + if view is not None: + # fixme: This is probably not the ideal way to sync view traits + # and perspective_item traits. + view.style_hint = item.style_hint + # Add the view to the window. + window.add_view( + view, item.position, relative_to, (item.width, item.height) + ) + + else: + # The reason that we don't just barf here is that a perspective + # might use views from multiple plugins, and we probably want to + # continue even if one or two of them aren't present. + # + # fixme: This is worth keeping an eye on though. If we end up with + # a strict mode that throws exceptions early and often for + # developers, then this might be a good place to throw one ;^) + logger.error('missing view for perspective item <%s>' % item.id) + + return + + def _add_all(self, window): + """ Adds *all* of the window's views defined in the window. """ + + for view in window.views: + if view.visible: + self._add_view(window, view) + + return + + def _add_view(self, window, view): + """ Adds a view to a window. """ + + # If no 'relative_to' is specified then the view is positioned + # relative to the editor area. + if len(view.relative_to) > 0: + relative_to = window.get_view_by_id(view.relative_to) + + else: + relative_to = None + + # Add the view to the window. + window.add_view( + view, view.position, relative_to, (view.width, view.height) + ) + + return + +#### EOF ###################################################################### diff --git a/pyface/workbench/perspective_item.py b/pyface/workbench/perspective_item.py new file mode 100755 index 000000000..c03e62956 --- /dev/null +++ b/pyface/workbench/perspective_item.py @@ -0,0 +1,57 @@ +""" An item in a Perspective contents list. """ + + +# Enthought library imports. +from enthought.traits.api import Enum, Float, HasTraits, Str, implements + +# Local imports. +from i_perspective_item import IPerspectiveItem + + +class PerspectiveItem(HasTraits): + """ An item in a Perspective contents list. """ + + implements(IPerspectiveItem) + + # The Id of the view to display in the perspective. + id = Str + + # The position of the view relative to the item specified in the + # 'relative_to' trait. + # + # 'top' puts the view above the 'relative_to' item. + # 'bottom' puts the view below the 'relative_to' item. + # 'left' puts the view to the left of the 'relative_to' item. + # 'right' puts the view to the right of the 'relative_to' item. + # 'with' puts the view in the same region as the 'relative_to' item. + # + # If the position is specified as 'with' you must specify a 'relative_to' + # item other than the editor area (i.e., you cannot position a view 'with' + # the editor area). + position = Enum('left', 'top', 'bottom', 'right', 'with') + + # The Id of the view to position relative to. If this is not specified + # (or if no view exists with this Id) then the view will be placed relative + # to the editor area. + relative_to = Str + + # The width of the item (as a fraction of the window width). + # + # e.g. 0.5 == half the window width. + # + # Note that this is treated as a suggestion, and it may not be possible + # for the workbench to allocate the space requested. + width = Float(-1) + + # The height of the item (as a fraction of the window height). + # + # e.g. 0.5 == half the window height. + # + # Note that this is treated as a suggestion, and it may not be possible + # for the workbench to allocate the space requested. + height = Float(-1) + + # The style of the dock control created. + style_hint = Enum('tab', 'vertical', 'horizontal', 'fixed') + +#### EOF ###################################################################### diff --git a/pyface/workbench/traits_ui_editor.py b/pyface/workbench/traits_ui_editor.py new file mode 100644 index 000000000..78c187682 --- /dev/null +++ b/pyface/workbench/traits_ui_editor.py @@ -0,0 +1,92 @@ +""" An editor whose content is provided by a traits UI. """ + + +# Standard library imports. +import logging + +# Enthought library imports. +from enthought.traits.api import Instance, Str +from enthought.traits.ui.api import UI + +# Local imports. +from editor import Editor + + +# Logging. +logger = logging.getLogger(__name__) + + +class TraitsUIEditor(Editor): + """ An editor whose content is provided by a traits UI. """ + + #### 'TraitsUIEditor' interface ########################################### + + # The traits UI that represents the editor. + # + # The framework sets this to the value returned by 'create_ui'. + ui = Instance(UI) + + # The name of the traits UI view used to create the UI (if not specified, + # the default traits UI view is used). + view = Str + + ########################################################################### + # 'IWorkbenchPart' interface. + ########################################################################### + + #### Trait initializers ################################################### + + def _name_default(self): + """ Trait initializer. """ + + return str(self.obj) + + #### Methods ############################################################## + + def create_control(self, parent): + """ Creates the toolkit-specific control that represents the editor. + + 'parent' is the toolkit-specific control that is the editor's parent. + + Overridden to call 'create_ui' to get the traits UI. + + """ + + self.ui = self.create_ui(parent) + + return self.ui.control + + def destroy_control(self): + """ Destroys the toolkit-specific control that represents the editor. + + Overridden to call 'dispose' on the traits UI. + + """ + + # Give the traits UI a chance to clean itself up. + if self.ui is not None: + logger.debug('disposing traits UI for editor [%s]', self) + self.ui.dispose() + self.ui = None + + return + + ########################################################################### + # 'TraitsUIEditor' interface. + ########################################################################### + + def create_ui(self, parent): + """ Creates the traits UI that represents the editor. + + By default it calls 'edit_traits' on the editor's 'obj'. If you + want more control over the creation of the traits UI then override! + + """ + + ui = self.obj.edit_traits( + parent=parent, view=self.view, kind='subpanel' + ) + + return ui + +#### EOF ###################################################################### diff --git a/pyface/workbench/traits_ui_view.py b/pyface/workbench/traits_ui_view.py new file mode 100644 index 000000000..4d8de2c04 --- /dev/null +++ b/pyface/workbench/traits_ui_view.py @@ -0,0 +1,108 @@ +""" A view whose content is provided by a traits UI. """ + + +# Standard library imports. +import logging + +# Enthought library imports. +from enthought.traits.api import Any, Instance, Str +from enthought.traits.ui.api import UI + +# Local imports. +from view import View + + +# Logging. +logger = logging.getLogger(__name__) + + +class TraitsUIView(View): + """ A view whose content is provided by a traits UI. """ + + #### 'TraitsUIView' interface ############################################# + + # The object that we povide a traits UI of (this defaults to the view + # iteself ie. 'self'). + obj = Any + + # The traits UI that represents the view. + # + # The framework sets this to the value returned by 'create_ui'. + ui = Instance(UI) + + # The name of the traits UI view used to create the UI (if not specified, + # the default traits UI view is used). + view = Str + + ########################################################################### + # 'IWorkbenchPart' interface. + ########################################################################### + + #### Trait initializers ################################################### + + def _name_default(self): + """ Trait initializer. """ + + return str(self.obj) + + #### Methods ############################################################## + + def create_control(self, parent): + """ Creates the toolkit-specific control that represents the editor. + + 'parent' is the toolkit-specific control that is the editor's parent. + + Overridden to call 'create_ui' to get the traits UI. + + """ + + self.ui = self.create_ui(parent) + + return self.ui.control + + def destroy_control(self): + """ Destroys the toolkit-specific control that represents the editor. + + Overridden to call 'dispose' on the traits UI. + + """ + + # Give the traits UI a chance to clean itself up. + if self.ui is not None: + logger.debug('disposing traits UI for view [%s]', self) + self.ui.dispose() + self.ui = None + # Break reference to the control, so the view is created afresh + # next time. + self.control = None + + return + + ########################################################################### + # 'TraitsUIView' interface. + ########################################################################### + + #### Trait initializers ################################################### + + def _obj_default(self): + """ Trait initializer. """ + + return self + + #### Methods ############################################################## + + def create_ui(self, parent): + """ Creates the traits UI that represents the editor. + + By default it calls 'edit_traits' on the view's 'model'. If you + want more control over the creation of the traits UI then override! + + """ + + ui = self.obj.edit_traits( + parent=parent, view=self.view, kind='subpanel' + ) + + return ui + +#### EOF ###################################################################### diff --git a/pyface/workbench/user_perspective_manager.py b/pyface/workbench/user_perspective_manager.py new file mode 100644 index 000000000..6be4e904a --- /dev/null +++ b/pyface/workbench/user_perspective_manager.py @@ -0,0 +1,220 @@ +""" Manages a set of user perspectives. """ + + +# Standard library imports. +import logging +import os + +# Enthought library imports. +from enthought.pyface.workbench.api import Perspective +from enthought.traits.api import Any, Dict, HasTraits, Int, List, Property +from enthought.traits.api import Unicode + + +# Logging. +logger = logging.getLogger(__name__) + + +class UserPerspectiveManager(HasTraits): + """ Manages a set of user perspectives. """ + + #### 'UserPerspective' interface ########################################## + + # A directory on the local file system that we can read and write to at + # will. This is used to persist window layout information, etc. + state_location = Unicode + + # Next available user perspective id. + next_id = Property(Int) + + # Dictionary mapping perspective id to user defined perspective definition. + id_to_perspective = Property(Dict) + + # The list of user defined perspective definitions. + perspectives = Property(List) + + # The name of the user defined perspectives definition file. + file_name = Property(Unicode) + + #### Private interface #################################################### + + # Shadow trait for the 'id_to_perspective' property. + _id_to_perspective = Any + + ########################################################################### + # 'UserPerspective' interface. + ########################################################################### + + #### Properties ########################################################### + + def _get_next_id ( self ): + """ Property getter. """ + + # Get all of the current perspective ids: + ids = self.id_to_perspective.keys() + + # If there are none: + if len( ids ) == 0: + # Return the starting id: + return 1 + + # Else return the current highest id + 1 as the next id: + ids.sort() + + return int( ids[-1][19:-2] ) + 1 + + def _get_id_to_perspective ( self ): + """ Property getter. """ + + if self._id_to_perspective is None: + self._id_to_perspective = dic = {} + try: + fh = file( self.file_name, 'r' ) + for line in fh: + data = line.split( ':', 1 ) + if len( data ) == 2: + id, name = data[0].strip(), data[1].strip() + dic[ id ] = Perspective( + id = id, + name = name, + show_editor_area = False + ) + fh.close() + except: + pass + + return self._id_to_perspective + + def _get_perspectives ( self ): + """ Property getter. """ + + return self.id_to_perspective.values() + + def _get_file_name ( self ): + """ Property getter. """ + + return os.path.join(self.state_location, '__user_perspective__') + + #### Methods ############################################################## + + def create_perspective(self, name, show_editor_area=True): + """ Create a new (and empty) user-defined perspective. """ + + perspective = Perspective( + id = '__user_perspective_%09d__' % self.next_id, + name = name, + show_editor_area = show_editor_area + ) + + # Add the perspective to the map. + self.id_to_perspective[perspective.id] = perspective + + # Update the persistent file information. + self._update_persistent_data() + + return perspective + + def clone_perspective(self, window, perspective, **traits): + """ Clone a perspective as a user perspective. """ + + clone = perspective.clone_traits() + + # Give the clone a special user perspective Id! + clone.id = '__user_perspective_%09d__' % self.next_id + + # Set any traits specified as keyword arguments. + clone.set(**traits) + + # Add the perspective to the map. + self.id_to_perspective[clone.id] = clone + + # fixme: This needs to be pushed into the window API!!!!!!! + window._memento.perspective_mementos[clone.id] = ( + window.layout.get_view_memento(), + window.active_view and window.active_view.id or None + ) + + # Update the persistent file information. + self._update_persistent_data() + + return clone + + def save(self): + """ Persist the current state of the user perspectives. """ + + self._update_persistent_data() + + return + + def add(self, perspective, name=None): + """ Add a perspective with an optional name. """ + + # Define the id for the new perspective: + perspective.id = id = '__user_perspective_%09d__' % self.next_id + + # Save the new name (if specified): + if name is not None: + perspective.name = name + + # Create the perspective: + self.id_to_perspective[id] = perspective + + # Update the persistent file information: + self._update_persistent_data() + + # Return the new perspective created: + return perspective + + def rename(self, perspective, name): + """ Rename the user perspective with the specified id. """ + + perspective.name = name + + self.id_to_perspective[perspective.id].name = name + + # Update the persistent file information: + self._update_persistent_data() + + return + + def remove(self, id): + """ Remove the user perspective with the specified id. + + This method also updates the persistent data. + + """ + + if id in self.id_to_perspective: + del self.id_to_perspective[id] + + # Update the persistent file information: + self._update_persistent_data() + + # Try to delete the associated perspective layout file: + try: + os.remove(os.path.join(self.state_location, id)) + except: + pass + + return + + ########################################################################### + # Private interface. + ########################################################################### + + def _update_persistent_data(self): + """ Update the persistent file information. """ + + try: + fh = file( self.file_name, 'w' ) + fh.write( '\n'.join( [ '%s: %s' % ( p.id, p.name ) + for p in self.perspectives ] ) ) + fh.close() + + except: + logger.error( "Could not write the user defined perspective " + "definition file: " + self.file_name ) + + return + +#### EOF ###################################################################### diff --git a/pyface/workbench/view.py b/pyface/workbench/view.py new file mode 100755 index 000000000..4aacb94a8 --- /dev/null +++ b/pyface/workbench/view.py @@ -0,0 +1,21 @@ +#------------------------------------------------------------------------------ +# Copyright (c) 2005, Enthought, Inc. +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in enthought/LICENSE.txt and may be redistributed only +# under the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# Thanks for using Enthought open source! +# +# Author: Enthought, Inc. +# Description: +#------------------------------------------------------------------------------ +""" The implementation of a workbench view. """ + + +# Import the toolkit specific version. +from enthought.pyface.toolkit import toolkit_object +View = toolkit_object('workbench.view:View') + +### EOF ####################################################################### diff --git a/pyface/workbench/window_event.py b/pyface/workbench/window_event.py new file mode 100644 index 000000000..5d3f5c538 --- /dev/null +++ b/pyface/workbench/window_event.py @@ -0,0 +1,26 @@ +""" Window events. """ + + +# Enthought library imports. +from enthought.traits.api import HasTraits, Instance, Vetoable + +# Local imports. +from workbench_window import WorkbenchWindow + + +class WindowEvent(HasTraits): + """ A window lifecycle event. """ + + #### 'WindowEvent' interface ############################################## + + # The window that the event occurred on. + window = Instance(WorkbenchWindow) + + +class VetoableWindowEvent(WindowEvent, Vetoable): + """ A vetoable window lifecycle event. """ + + pass + +#### EOF ###################################################################### + diff --git a/pyface/workbench/workbench.py b/pyface/workbench/workbench.py new file mode 100755 index 000000000..01ec50dc4 --- /dev/null +++ b/pyface/workbench/workbench.py @@ -0,0 +1,424 @@ +""" A workbench. """ + + +# Standard library imports. +import cPickle +import logging +import os + +# Enthought library imports. +from enthought.etsconfig.api import ETSConfig +from enthought.pyface.api import NO +from enthought.traits.api import Bool, Callable, Event, HasTraits, implements +from enthought.traits.api import Instance, List, Unicode, Vetoable +from enthought.traits.api import VetoableEvent + +# Local imports. +from i_editor_manager import IEditorManager +from i_workbench import IWorkbench +from user_perspective_manager import UserPerspectiveManager +from workbench_window import WorkbenchWindow +from window_event import WindowEvent, VetoableWindowEvent + + +# Logging. +logger = logging.getLogger(__name__) + + +class Workbench(HasTraits): + """ A workbench. + + There is exactly *one* workbench per application. The workbench can create + any number of workbench windows. + + """ + + implements(IWorkbench) + + #### 'IWorkbench' interface ############################################### + + # The active workbench window (the last one to get focus). + active_window = Instance(WorkbenchWindow) + + # The editor manager is used to create/restore editors. + editor_manager = Instance(IEditorManager) + + # The optional application scripting manager. + script_manager = Instance('enthought.appscripting.api.IScriptManager') + + # A directory on the local file system that we can read and write to at + # will. This is used to persist window layout information, etc. + state_location = Unicode + + # The optional undo manager. + undo_manager = Instance('enthought.undo.api.IUndoManager') + + # The user-defined perspectives manager. + user_perspective_manager = Instance(UserPerspectiveManager) + + # All of the workbench windows created by the workbench. + windows = List(WorkbenchWindow) + + #### Workbench lifecycle events ########################################### + + # Fired when the workbench is about to exit. + # + # This can be caused by either:- + # + # a) The 'exit' method being called. + # b) The last open window being closed. + # + exiting = VetoableEvent + + # Fired when the workbench has exited. + exited = Event + + #### Window lifecycle events ############################################## + + # Fired when a workbench window has been created. + window_created = Event(WindowEvent) + + # Fired when a workbench window is opening. + window_opening = Event(VetoableWindowEvent) + + # Fired when a workbench window has been opened. + window_opened = Event(WindowEvent) + + # Fired when a workbench window is closing. + window_closing = Event(VetoableWindowEvent) + + # Fired when a workbench window has been closed. + window_closed = Event(WindowEvent) + + #### 'Workbench' interface ################################################ + + # The factory that is used to create workbench windows. This is used in + # the default implementation of 'create_window'. If you override that + # method then you obviously don't need to set this trait! + window_factory = Callable + + #### Private interface #################################################### + + # An 'explicit' exit is when the the 'exit' method is called. + # An 'implicit' exit is when the user closes the last open window. + _explicit_exit = Bool(False) + + ########################################################################### + # 'IWorkbench' interface. + ########################################################################### + + def create_window(self, **kw): + """ Factory method that creates a new workbench window. """ + + window = self.window_factory(workbench=self, **kw) + + # Add on any user-defined perspectives. + window.perspectives.extend(self.user_perspective_manager.perspectives) + + # Restore the saved window memento (if there is one). + self._restore_window_layout(window) + + # Listen for the window being activated/opened/closed etc. Activated in + # this context means 'gets the focus'. + # + # NOTE: 'activated' is not fired on a window when the window first + # opens and gets focus. It is only fired when the window comes from + # lower in the stack to be the active window. + window.on_trait_change(self._on_window_activated, 'activated') + window.on_trait_change(self._on_window_opening, 'opening') + window.on_trait_change(self._on_window_opened, 'opened') + window.on_trait_change(self._on_window_closing, 'closing') + window.on_trait_change(self._on_window_closed, 'closed') + + # Event notification. + self.window_created = WindowEvent(window=window) + + return window + + def exit(self): + """ Exits the workbench. + + This closes all open workbench windows. + + This method is not called when the user clicks the close icon. Nor when + they do an Alt+F4 in Windows. It is only called when the application + menu File->Exit item is selected. + + Returns True if the exit succeeded, False if it was vetoed. + + """ + + logger.debug('**** exiting the workbench ****') + + # Event notification. + self.exiting = event = Vetoable() + if not event.veto: + # This flag is checked in '_on_window_closing' to see what kind of + # exit is being performed. + self._explicit_exit = True + + if len(self.windows) > 0: + exited = self._close_all_windows() + + # The degenerate case where no workbench windows have ever been + # created! + else: + # Trait notification. + self.exited = self + + exited = True + + # Whether the exit succeeded or not, we are no longer in the + # process of exiting! + self._explicit_exit = False + + else: + exited = False + + if not exited: + logger.debug('**** exit of the workbench vetoed ****') + + return exited + + #### Convenience methods on the active window ############################# + + def edit(self, obj, kind=None, use_existing=True): + """ Edit an object in the active workbench window. """ + + return self.active_window.edit(obj, kind, use_existing) + + def get_editor(self, obj, kind=None): + """ Return the editor that is editing an object. + + Returns None if no such editor exists. + + """ + + if self.active_window is None: + return None + + return self.active_window.get_editor(obj, kind) + + def get_editor_by_id(self, id): + """ Return the editor with the specified Id. + + Returns None if no such editor exists. + + """ + + return self.active_window.get_editor_by_id(id) + + #### Message dialogs #### + + def confirm(self, message, title=None, cancel=False, default=NO): + """ Convenience method to show a confirmation dialog. """ + + return self.active_window.confirm(message, title, cancel, default) + + def information(self, message, title='Information'): + """ Convenience method to show an information message dialog. """ + + return self.active_window.information(message, title) + + def warning(self, message, title='Warning'): + """ Convenience method to show a warning message dialog. """ + + return self.active_window.warning(message, title) + + def error(self, message, title='Error'): + """ Convenience method to show an error message dialog. """ + + return self.active_window.error(message, title) + + ########################################################################### + # 'Workbench' interface. + ########################################################################### + + #### Initializers ######################################################### + + def _state_location_default(self): + """ Trait initializer. """ + + # It would be preferable to base this on GUI.state_location. + state_location = os.path.join( + ETSConfig.application_home, + 'pyface', + 'workbench', + ETSConfig.toolkit + ) + + if not os.path.exists(state_location): + os.makedirs(state_location) + + logger.debug('workbench state location is %s', state_location) + + return state_location + + def _undo_manager_default(self): + """ Trait initializer. """ + + # We make sure the undo package is entirely optional. + try: + from enthought.undo.api import UndoManager + except ImportError: + return None + + return UndoManager() + + def _user_perspective_manager_default(self): + """ Trait initializer. """ + + return UserPerspectiveManager(state_location=self.state_location) + + ########################################################################### + # Protected 'Workbench' interface. + ########################################################################### + + def _create_window(self, **kw): + """ Factory method that creates a new workbench window. """ + + raise NotImplementedError + + ########################################################################### + # Private interface. + ########################################################################### + + def _close_all_windows(self): + """ Closes all open windows. + + Returns True if all windows were closed, False if the user changed + their mind ;^) + + """ + + # We take a copy of the windows list because as windows are closed + # they are removed from it! + windows = self.windows[:] + windows.reverse() + + for window in windows: + # We give the user chance to cancel the exit as each window is + # closed. + if not window.close(): + all_closed = False + break + + else: + all_closed = True + + return all_closed + + def _restore_window_layout(self, window): + """ Restore the window layout. """ + + filename = os.path.join(self.state_location, 'window_memento') + if os.path.exists(filename): + try: + # If the memento class itself has been modified then there + # is a chance that the unpickle will fail. If so then we just + # carry on as if there was no memento! + f = file(filename, 'r') + memento = cPickle.load(f) + f.close() + + # The memento doesn't actually get used until the window is + # opened, so there is nothing to go wrong in this step! + window.set_memento(memento) + + # If *anything* goes wrong then simply log the error and carry on + # with no memento! + except: + logger.exception('restoring window layout from %s', filename) + + return + + def _save_window_layout(self, window): + """ Save the window layout. """ + + # Save the window layout. + f = file(os.path.join(self.state_location, 'window_memento'), 'w') + cPickle.dump(window.get_memento(), f) + f.close() + + return + + #### Trait change handlers ################################################ + + def _on_window_activated(self, window, trait_name, event): + """ Dynamic trait change handler. """ + + logger.debug('window %s activated', window) + + self.active_window = window + + return + + def _on_window_opening(self, window, trait_name, event): + """ Dynamic trait change handler. """ + + # Event notification. + self.window_opening = window_event = VetoableWindowEvent(window=window) + if window_event.veto: + event.veto = True + + return + + def _on_window_opened(self, window, trait_name, event): + """ Dynamic trait change handler. """ + + # We maintain a list of all open windows so that (amongst other things) + # we can detect when the user is attempting to close the last one. + self.windows.append(window) + + # This is necessary because the activated event is not fired when a + # window is first opened and gets focus. It is only fired when the + # window comes from lower in the stack to be the active window. + self.active_window = window + + # Event notification. + self.window_opened = WindowEvent(window=window) + + return + + def _on_window_closing(self, window, trait_name, event): + """ Dynamic trait change handler. """ + + # Event notification. + self.window_closing = window_event = VetoableWindowEvent(window=window) + + if window_event.veto: + event.veto = True + + else: + # Is this the last open window? + if len(self.windows) == 1: + # If this is an 'implicit exit' then make sure that we fire the + # appropriate workbench lifecycle events. + if not self._explicit_exit: + # Event notification. + self.exiting = window_event = Vetoable() + if window_event.veto: + event.veto = True + + if not event.veto: + # Save the window size, position and layout. + self._save_window_layout(window) + + return + + def _on_window_closed(self, window, trait_name, event): + """ Dynamic trait change handler. """ + + self.windows.remove(window) + + # Event notification. + self.window_closed = WindowEvent(window=window) + + # Was this the last window? + if len(self.windows) == 0: + # Event notification. + self.exited = self + + return + +#### EOF ###################################################################### diff --git a/pyface/workbench/workbench_window.py b/pyface/workbench/workbench_window.py new file mode 100755 index 000000000..45d02de58 --- /dev/null +++ b/pyface/workbench/workbench_window.py @@ -0,0 +1,913 @@ +""" A workbench window. """ + + +# Standard library imports. +import logging + +# Enthought library imports. +from enthought.pyface.api import ApplicationWindow, GUI +from enthought.traits.api import Callable, Constant, Delegate, Event, Instance +from enthought.traits.api import List, Str, Tuple, Unicode, Vetoable +from enthought.traits.api import implements, on_trait_change + +# Local imports. +from i_editor import IEditor +from i_editor_manager import IEditorManager +from i_perspective import IPerspective +from i_view import IView +from i_workbench_part import IWorkbenchPart +from perspective import Perspective +from workbench_window_layout import WorkbenchWindowLayout +from workbench_window_memento import WorkbenchWindowMemento + + +# Logging. +logger = logging.getLogger(__name__) + + +class WorkbenchWindow(ApplicationWindow): + """ A workbench window. """ + + #### 'IWorkbenchWindow' interface ######################################### + + # The view or editor that currently has the focus. + active_part = Instance(IWorkbenchPart) + + # The editor manager is used to create/restore editors. + editor_manager = Instance(IEditorManager) + + # The current selection within the window. + selection = List + + # The workbench that the window belongs to. + workbench = Instance('enthought.pyface.workbench.api.IWorkbench') + + #### Editors ####################### + + # The active editor. + active_editor = Instance(IEditor) + + # The visible (open) editors. + editors = List(IEditor) + + # The Id of the editor area. + editor_area_id = Constant('enthought.pyface.workbench.editors') + + # The (initial) size of the editor area (the user is free to resize it of + # course). + editor_area_size = Tuple((100, 100)) + + # Fired when an editor is about to be opened (or restored). + editor_opening = Delegate('layout') # Event(IEditor) + + # Fired when an editor has been opened (or restored). + editor_opened = Delegate('layout') # Event(IEditor) + + # Fired when an editor is about to be closed. + editor_closing = Delegate('layout') # Event(IEditor) + + # Fired when an editor has been closed. + editor_closed = Delegate('layout') # Event(IEditor) + + #### Views ######################### + + # The active view. + active_view = Instance(IView) + + # The available views (note that this is *all* of the views, not just those + # currently visible). + # + # Views *cannot* be shared between windows as each view has a reference to + # its toolkit-specific control etc. + views = List(IView) + + #### Perspectives ################## + + # The active perspective. + active_perspective = Instance(IPerspective) + + # The available perspectives. If no perspectives are specified then the + # a single instance of the 'Perspective' class is created. + perspectives = List(IPerspective) + + # The Id of the default perspective. + # + # There are two situations in which this is used: + # + # 1. When the window is being created from scratch (i.e., not restored). + # + # If this is the empty string, then the first perspective in the list of + # perspectives is shown (if there are no perspectives then an instance + # of the default 'Perspective' class is used). If this is *not* the + # empty string then the perspective with this Id is shown. + # + # 2. When the window is being restored. + # + # If this is the empty string, then the last perspective that was + # visible when the window last closed is shown. If this is not the empty + # string then the perspective with this Id is shown. + # + default_perspective_id = Str + + #### 'WorkbenchWindow' interface ########################################## + + # The window layout is responsible for creating and managing the internal + # structure of the window (i.e., it knows how to add and remove views and + # editors etc). + layout = Instance(WorkbenchWindowLayout) + + #### 'Private' interface ################################################## + + # The state of the window suitable for pickling etc. + _memento = Instance(WorkbenchWindowMemento) + + ########################################################################### + # 'Window' interface. + ########################################################################### + + def open(self): + """ Open the window. + + Overridden to make the 'opening' event vetoable. + + Return True if the window opened successfully; False if the open event + was vetoed. + + """ + + logger.debug('window %s opening', self) + + # Trait notification. + self.opening = event = Vetoable() + if not event.veto: + if self.control is None: + self._create() + + self.show(True) + + # Trait notification. + self.opened = self + + logger.debug('window %s opened', self) + + else: + logger.debug('window %s open was vetoed', self) + + # fixme: This is not actually part of the Pyface 'Window' API (but + # maybe it should be). We return this to indicate whether the window + # actually opened. + return self.control is not None + + def close(self): + """ Closes the window. + + Overridden to make the 'closing' event vetoable. + + Return True if the window closed successfully (or was not even open!), + False if the close event was vetoed. + + """ + + logger.debug('window %s closing', self) + + if self.control is not None: + # Trait notification. + self.closing = event = Vetoable() + + # fixme: Hack to mimic vetoable events! + if not event.veto: + # Give views and editors a chance to cleanup after themselves. + self.destroy_views(self.views) + self.destroy_editors(self.editors) + + # Cleanup the window layout (event handlers, etc.) + self.layout.close() + + # Cleanup the toolkit-specific control. + self.destroy() + + # Cleanup our reference to the control so that we can (at least + # in theory!) be opened again. + self.control = None + + # Trait notification. + self.closed = self + + logger.debug('window %s closed', self) + + else: + logger.debug('window %s close was vetoed', self) + + else: + logger.debug('window %s is not open', self) + + # FIXME v3: This is not actually part of the Pyface 'Window' API (but + # maybe it should be). We return this to indicate whether the window + # actually closed. + return self.control is None + + ########################################################################### + # Protected 'Window' interface. + ########################################################################### + + def _create_contents(self, parent): + """ Create and return the window contents. """ + + # Create the initial window layout. + contents = self.layout.create_initial_layout(parent) + + # Save the initial window layout so that we can reset it when changing + # to a perspective that has not been seen yet. + self._initial_layout = self.layout.get_view_memento() + + # Are we creating the window from scratch or restoring it from a + # memento? + if self._memento is None: + self._memento = WorkbenchWindowMemento() + + else: + self._restore_contents() + + # Set the initial perspective. + self.active_perspective = self._get_initial_perspective() + + return contents + + ########################################################################### + # 'WorkbenchWindow' interface. + ########################################################################### + + #### Initializers ######################################################### + + def _editor_manager_default(self): + """ Trait initializer. """ + + from editor_manager import EditorManager + + return EditorManager(window=self) + + def _layout_default(self): + """ Trait initializer. """ + + return WorkbenchWindowLayout(window=self) + + #### Methods ############################################################## + + def activate_editor(self, editor): + """ Activates an editor. """ + + self.layout.activate_editor(editor) + + return + + def activate_view(self, view): + """ Activates a view. """ + + self.layout.activate_view(view) + + return + + def add_editor(self, editor, title=None): + """ Adds an editor. + + If no title is specified, the editor's name is used. + + """ + + if title is None: + title = editor.name + + self.layout.add_editor(editor, title) + self.editors.append(editor) + + return + + def add_view(self, view, position=None, relative_to=None, size=(-1, -1)): + """ Adds a view. """ + + self.layout.add_view(view, position, relative_to, size) + + # This case allows for views that are created and added dynamically + # (i.e. they were not even known about when the window was created). + if not view in self.views: + self.views.append(view) + + return + + def close_editor(self, editor): + """ Closes an editor. """ + + self.layout.close_editor(editor) + + return + + def close_view(self, view): + """ Closes a view. + + fixme: Currently views are never 'closed' in the same sense as an + editor is closed. Views are merely hidden. + + """ + + self.hide_view(view) + + return + + def create_editor(self, obj, kind=None): + """ Create an editor for an object. + + Return None if no editor can be created for the object. + + """ + + return self.editor_manager.create_editor(self, obj, kind) + + def destroy_editors(self, editors): + """ Destroy a list of editors. """ + + for editor in editors: + if editor.control is not None: + editor.destroy_control() + + return + + def destroy_views(self, views): + """ Destroy a list of views. """ + + for view in views: + if view.control is not None: + view.destroy_control() + + return + + def edit(self, obj, kind=None, use_existing=True): + """ Edit an object. + + 'kind' is simply passed through to the window's editor manager to + allow it to create a particular kind of editor depending on context + etc. + + If 'use_existing' is True and the object is already being edited in + the window then the existing editor will be activated (i.e., given + focus, brought to the front, etc.). + + If 'use_existing' is False, then a new editor will be created even if + one already exists. + + """ + + if use_existing: + # Is the object already being edited in the window? + editor = self.get_editor(obj, kind) + + if editor is not None: + # If so, activate the existing editor (i.e., bring it to the + # front, give it the focus etc). + self.activate_editor(editor) + return editor + + # Otherwise, create an editor for it. + editor = self.create_editor(obj, kind) + + if editor is None: + logger.warn('no editor for object %s', obj) + + self.add_editor(editor) + self.activate_editor(editor) + + return editor + + def get_editor(self, obj, kind=None): + """ Return the editor that is editing an object. + + Return None if no such editor exists. + + """ + + return self.editor_manager.get_editor(self, obj, kind) + + def get_editor_by_id(self, id): + """ Return the editor with the specified Id. + + Return None if no such editor exists. + + """ + + for editor in self.editors: + if editor.id == id: + break + + else: + editor = None + + return editor + + def get_part_by_id(self, id): + """ Return the workbench part with the specified Id. + + Return None if no such part exists. + + """ + + return self.get_view_by_id(id) or self.get_editor_by_id(id) + + def get_perspective_by_id(self, id): + """ Return the perspective with the specified Id. + + Return None if no such perspective exists. + + """ + + for perspective in self.perspectives: + if perspective.id == id: + break + + else: + if id == Perspective.DEFAULT_ID: + perspective = Perspective() + + else: + perspective = None + + return perspective + + def get_perspective_by_name(self, name): + """ Return the perspective with the specified name. + + Return None if no such perspective exists. + + """ + + for perspective in self.perspectives: + if perspective.name == name: + break + + else: + perspective = None + + return perspective + + def get_view_by_id(self, id): + """ Return the view with the specified Id. + + Return None if no such view exists. + + """ + + for view in self.views: + if view.id == id: + break + + else: + view = None + + return view + + def hide_editor_area(self): + """ Hide the editor area. """ + + self.layout.hide_editor_area() + + return + + def hide_view(self, view): + """ Hide a view. """ + + self.layout.hide_view(view) + + return + + def refresh(self): + """ Refresh the window to reflect any changes. """ + + self.layout.refresh() + + return + + def reset_active_perspective(self): + """ Reset the active perspective back to its original contents. """ + + perspective = self.active_perspective + + # If the perspective has been seen before then delete its memento. + if perspective.id in self._memento.perspective_mementos: + # Remove the perspective's memento. + del self._memento.perspective_mementos[perspective.id] + + # Re-display the perspective (because a memento no longer exists for + # the perspective, its 'create_contents' method will be called again). + self._show_perspective(perspective, perspective) + + return + + def reset_all_perspectives(self): + """ Reset all perspectives back to their original contents. """ + + # Remove all perspective mementos (except user perspectives). + for id in self._memento.perspective_mementos.keys(): + if not id.startswith('__user_perspective'): + del self._memento.perspective_mementos[id] + + # Re-display the active perspective. + self._show_perspective(self.active_perspective,self.active_perspective) + + return + + def reset_editors(self): + """ Activate the first editor in every tab. """ + + self.layout.reset_editors() + + return + + def reset_views(self): + """ Activate the first view in every tab. """ + + self.layout.reset_views() + + return + + def show_editor_area(self): + """ Show the editor area. """ + + self.layout.show_editor_area() + + return + + def show_view(self, view): + """ Show a view. """ + + # If the view is already in the window layout, but hidden, then just + # show it. + # + # fixme: This is a little gorpy, reaching into the window layout here, + # but currently this is the only thing that knows whether or not the + # view exists but is hidden. + if self.layout.contains_view(view): + self.layout.show_view(view) + + # Otherwise, we have to add the view to the layout. + else: + self._add_view_in_default_position(view) + self.refresh() + + return + + #### Methods for saving and restoring the layout ########################## + + def get_memento(self): + """ Return the state of the window suitable for pickling etc. """ + + # The size and position of the window. + self._memento.size = self.size + self._memento.position = self.position + + # The Id of the active perspective. + self._memento.active_perspective_id = self.active_perspective.id + + # The layout of the active perspective. + self._memento.perspective_mementos[self.active_perspective.id] = ( + self.layout.get_view_memento(), + self.active_view and self.active_view.id or None + ) + + # The layout of the editor area. + self._memento.editor_area_memento = self.layout.get_editor_memento() + + return self._memento + + def set_memento(self, memento): + """ Restore the state of the window from a memento. """ + + # All we do here is save a reference to the memento - we don't actually + # do anything with it until the window is opened. + # + # This obviously means that you can't set the memento of a window + # that is already open, but I can't see a use case for that anyway! + self._memento = memento + + return + + ########################################################################### + # Private interface. + ########################################################################### + + def _add_view_in_default_position(self, view): + """ Adds a view in its 'default' position. """ + + # Is the view in the current perspectives contents list? If it is then + # we use the positioning information in the perspective item. Otherwise + # we will use the default positioning specified in the view itself. + item = self._get_perspective_item(self.active_perspective, view) + if item is None: + item = view + + # fixme: This only works because 'PerspectiveItem' and 'View' have the + # identical 'position', 'relative_to', 'width' and 'height' traits! We + # need to unify these somehow! + relative_to = self.get_view_by_id(item.relative_to) + size = (item.width, item.height) + + self.add_view(view, item.position, relative_to, size) + + return + + def _get_initial_perspective(self, *methods): + """ Return the initial perspective. """ + + methods = [ + # If a default perspective was specified then we prefer that over + # any other perspective. + self._get_default_perspective, + + # If there was no default perspective then try the perspective that + # was active the last time the application was run. + self._get_previous_perspective, + + # If there was no previous perspective, then try the first one that + # we know about. + self._get_first_perspective + ] + + for method in methods: + perspective = method() + if perspective is not None: + break + + # If we have no known perspectives, make a new blank one up. + else: + logger.warn('no known perspectives - creating a new one') + perspective = Perspective() + + return perspective + + def _get_default_perspective(self): + """ Return the default perspective. + + Return None if no default perspective was specified or it no longer + exists. + + """ + + id = self.default_perspective_id + + if len(id) > 0: + perspective = self.get_perspective_by_id(id) + if perspective is None: + logger.warn('default perspective %s no longer available', id) + + else: + perspective = None + + return perspective + + def _get_previous_perspective(self): + """ Return the previous perspective. + + Return None if there has been no previous perspective or it no longer + exists. + + """ + + id = self._memento.active_perspective_id + + if len(id) > 0: + perspective = self.get_perspective_by_id(id) + if perspective is None: + logger.warn('previous perspective %s no longer available', id) + + else: + perspective = None + + return perspective + + def _get_first_perspective(self): + """ Return the first perspective in our list of perspectives. + + Return None if no perspectives have been defined. + + """ + + if len(self.perspectives) > 0: + perspective = self.perspectives[0] + + else: + perspective = None + + return perspective + + def _get_perspective_item(self, perspective, view): + """ Return the perspective item for a view. + + Return None if the view is not mentioned in the perspectives contents. + + """ + + # fixme: Errrr, shouldn't this be a method on the window?!? + for item in perspective.contents: + if item.id == view.id: + break + + else: + item = None + + return item + + def _hide_perspective(self, perspective): + """ Hide a perspective. """ + + # fixme: This is a bit ugly but... when we restore the layout we ignore + # the default view visibility. + for view in self.views: + view.visible = False + + # Save the current layout of the perspective. + self._memento.perspective_mementos[perspective.id] = ( + self.layout.get_view_memento(), + self.active_view and self.active_view.id or None + ) + + return + + def _show_perspective(self, old, new): + """ Show a perspective. """ + + # If the perspective has been seen before then restore it. + memento = self._memento.perspective_mementos.get(new.id) + if memento is not None: + view_memento, active_view_id = memento + self.layout.set_view_memento(view_memento) + + # Make sure the active part, view and editor reflect the new + # perspective. + view = self.get_view_by_id(active_view_id) + if view is not None: + self.active_view = view + + # Otherwise, this is the first time the perspective has been seen + # so create it. + else: + if old is not None: + # Reset the window layout to its initial state. + self.layout.set_view_memento(self._initial_layout) + + # Create the perspective in the window. + new.create(self) + + # Make sure the active part, view and editor reflect the new + # perspective. + self.active_view = None + + # Show the editor area? + if new.show_editor_area: + self.show_editor_area() + else: + self.hide_editor_area() + self.active_editor = None + + # Inform the perspective that it has been shown. + new.show(self) + + # This forces the dock window to update its layout. + if old is not None: + self.refresh() + + return + + def _restore_contents(self): + """ Restore the contents of the window. """ + + self.layout.set_editor_memento(self._memento.editor_area_memento) + + self.size = self._memento.size + self.position = self._memento.position + + return + + #### Trait change handlers ################################################ + + #### Static #### + + def _active_perspective_changed(self, old, new): + """ Static trait change handler. """ + + logger.debug('active perspective changed from <%s> to <%s>', old, new) + + # Hide the old perspective... + if old is not None: + self._hide_perspective(old) + + # ... and show the new one. + if new is not None: + self._show_perspective(old, new) + + return + + def _active_editor_changed(self, old, new): + """ Static trait change handler. """ + + logger.debug('active editor changed from <%s> to <%s>', old, new) + self.active_part = new + + return + + def _active_part_changed(self, old, new): + """ Static trait change handler. """ + + if new is None: + self.selection = [] + + else: + self.selection = new.selection + + logger.debug('active part changed from <%s> to <%s>', old, new) + + return + + def _active_view_changed(self, old, new): + """ Static trait change handler. """ + + logger.debug('active view changed from <%s> to <%s>', old, new) + self.active_part = new + + return + + def _views_changed(self, old, new): + """ Static trait change handler. """ + + # Cleanup any old views. + for view in old: + view.window = None + + # Initialize any new views. + for view in new: + view.window = self + + return + + def _views_items_changed(self, event): + """ Static trait change handler. """ + + # Cleanup any old views. + for view in event.removed: + view.window = None + + # Initialize any new views. + for view in event.added: + view.window = self + + return + + #### Dynamic #### + + @on_trait_change('layout.editor_closed') + def _on_editor_closed(self, editor): + """ Dynamic trait change handler. """ + + index = self.editors.index(editor) + del self.editors[index] + if editor is self.active_editor: + if len(self.editors) > 0: + index = min(index, len(self.editors) - 1) + # If the user closed the editor manually then this method is + # being called from a toolkit-specific event handler. Because + # of that we have to make sure that we don't change the focus + # from within this method directly hence we activate the editor + # later in the GUI thread. + GUI.invoke_later(self.activate_editor, self.editors[index]) + + else: + self.active_editor = None + + return + + @on_trait_change('editors.has_focus') + def _on_editor_has_focus_changed(self, obj, trait_name, old, new): + """ Dynamic trait change handler. """ + + if trait_name == 'has_focus' and new: + self.active_editor = obj + + return + + @on_trait_change('views.has_focus') + def _has_focus_changed_for_view(self, obj, trait_name, old, new): + """ Dynamic trait change handler. """ + + if trait_name == 'has_focus' and new: + self.active_view = obj + + return + + @on_trait_change('views.visible') + def _visible_changed_for_view(self, obj, trait_name, old, new): + """ Dynamic trait change handler. """ + + if trait_name == 'visible': + if not new: + if obj is self.active_view: + self.active_view = None + + return + +#### EOF ###################################################################### diff --git a/pyface/workbench/workbench_window_layout.py b/pyface/workbench/workbench_window_layout.py new file mode 100755 index 000000000..641db14d8 --- /dev/null +++ b/pyface/workbench/workbench_window_layout.py @@ -0,0 +1,24 @@ +#------------------------------------------------------------------------------ +# Copyright (c) 2005, Enthought, Inc. +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in enthought/LICENSE.txt and may be redistributed only +# under the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# Thanks for using Enthought open source! +# +# Author: Enthought, Inc. +# Description: +#------------------------------------------------------------------------------ +""" The implementation of a workbench window layout. """ + + +# Import the toolkit specific version. +from enthought.pyface.toolkit import toolkit_object + +WorkbenchWindowLayout = toolkit_object( + 'workbench.workbench_window_layout:WorkbenchWindowLayout' +) + +### EOF ####################################################################### diff --git a/pyface/workbench/workbench_window_memento.py b/pyface/workbench/workbench_window_memento.py new file mode 100644 index 000000000..cbe741e48 --- /dev/null +++ b/pyface/workbench/workbench_window_memento.py @@ -0,0 +1,30 @@ +""" A memento for a workbench window. """ + + +# Enthought library imports. +from enthought.traits.api import Any, Dict, HasTraits, Str, Tuple + + +class WorkbenchWindowMemento(HasTraits): + """ A memento for a workbench window. """ + + # The Id of the active perspective. + active_perspective_id = Str + + # The memento for the editor area. + editor_area_memento = Any + + # Mementos for each perspective that has been seen. + # + # The keys are the perspective Ids, the values are the toolkit-specific + # mementos. + perspective_mementos = Dict(Str, Any) + + # The position of the window. + position = Tuple + + # The size of the window. + size = Tuple + + +#### EOF ###################################################################### From dd7d2260be17ed8bffc673908dbbfdeb74c2f448 Mon Sep 17 00:00:00 2001 From: Ilan Schnell Date: Wed, 20 Apr 2011 16:43:47 -0500 Subject: [PATCH 04/60] fixed imports --- pyface/ui/qt4/workbench/editor.py | 4 ++-- pyface/ui/qt4/workbench/split_tab_widget.py | 2 +- pyface/ui/qt4/workbench/view.py | 4 ++-- .../qt4/workbench/workbench_window_layout.py | 8 ++++---- pyface/ui/wx/workbench/editor.py | 2 +- .../workbench/editor_set_structure_handler.py | 2 +- pyface/ui/wx/workbench/view.py | 4 ++-- .../wx/workbench/view_set_structure_handler.py | 2 +- .../ui/wx/workbench/workbench_dock_window.py | 2 +- .../ui/wx/workbench/workbench_window_layout.py | 18 +++++++++--------- pyface/workbench/action/action_controller.py | 6 +++--- .../action/delete_user_perspective_action.py | 4 ++-- pyface/workbench/action/menu_bar_manager.py | 6 +++--- .../action/new_user_perspective_action.py | 2 +- .../action/perspective_menu_manager.py | 6 +++--- .../action/rename_user_perspective_action.py | 2 +- .../action/reset_active_perspective_action.py | 4 ++-- .../action/reset_all_perspectives_action.py | 4 ++-- .../action/save_as_user_perspective_action.py | 2 +- .../action/set_active_perspective_action.py | 4 ++-- pyface/workbench/action/setattr_action.py | 2 +- pyface/workbench/action/show_view_action.py | 2 +- .../action/toggle_view_visibility_action.py | 4 ++-- pyface/workbench/action/tool_bar_manager.py | 6 +++--- .../action/user_perspective_action.py | 2 +- .../workbench/action/user_perspective_name.py | 4 ++-- pyface/workbench/action/view_chooser.py | 12 ++++++------ pyface/workbench/action/view_menu_manager.py | 8 ++++---- pyface/workbench/action/workbench_action.py | 6 +++--- pyface/workbench/debug/debug_view.py | 6 +++--- pyface/workbench/editor.py | 2 +- pyface/workbench/editor_manager.py | 4 ++-- pyface/workbench/i_editor.py | 4 ++-- pyface/workbench/i_editor_manager.py | 4 ++-- pyface/workbench/i_perspective.py | 2 +- pyface/workbench/i_perspective_item.py | 2 +- pyface/workbench/i_view.py | 6 +++--- pyface/workbench/i_workbench.py | 4 ++-- pyface/workbench/i_workbench_part.py | 8 ++++---- pyface/workbench/i_workbench_window_layout.py | 8 ++++---- pyface/workbench/perspective.py | 4 ++-- pyface/workbench/perspective_item.py | 2 +- pyface/workbench/traits_ui_editor.py | 4 ++-- pyface/workbench/traits_ui_view.py | 4 ++-- pyface/workbench/user_perspective_manager.py | 6 +++--- pyface/workbench/view.py | 2 +- pyface/workbench/window_event.py | 2 +- pyface/workbench/workbench.py | 10 +++++----- pyface/workbench/workbench_window.py | 12 ++++++------ pyface/workbench/workbench_window_layout.py | 2 +- pyface/workbench/workbench_window_memento.py | 2 +- 51 files changed, 117 insertions(+), 117 deletions(-) diff --git a/pyface/ui/qt4/workbench/editor.py b/pyface/ui/qt4/workbench/editor.py index f5beebce2..d4de22213 100644 --- a/pyface/ui/qt4/workbench/editor.py +++ b/pyface/ui/qt4/workbench/editor.py @@ -12,7 +12,7 @@ # Local imports. -from enthought.pyface.workbench.i_editor import MEditor +from pyface.workbench.i_editor import MEditor class Editor(MEditor): @@ -29,7 +29,7 @@ class Editor(MEditor): def create_control(self, parent): """ Create the toolkit-specific control that represents the part. """ - from enthought.qt import QtGui + from traits.qt import QtGui # By default we create a yellow panel! control = QtGui.QWidget(parent) diff --git a/pyface/ui/qt4/workbench/split_tab_widget.py b/pyface/ui/qt4/workbench/split_tab_widget.py index 212e7044f..d8738ab0c 100644 --- a/pyface/ui/qt4/workbench/split_tab_widget.py +++ b/pyface/ui/qt4/workbench/split_tab_widget.py @@ -12,7 +12,7 @@ # Major library imports. import sip -from enthought.qt import QtCore, QtGui +from traits.qt import QtCore, QtGui class SplitTabWidget(QtGui.QSplitter): diff --git a/pyface/ui/qt4/workbench/view.py b/pyface/ui/qt4/workbench/view.py index f1dd1c70a..cfae48d2d 100644 --- a/pyface/ui/qt4/workbench/view.py +++ b/pyface/ui/qt4/workbench/view.py @@ -12,7 +12,7 @@ # Enthought library imports. -from enthought.pyface.workbench.i_view import MView +from pyface.workbench.i_view import MView class View(MView): @@ -29,7 +29,7 @@ class View(MView): def create_control(self, parent): """ Create the toolkit-specific control that represents the part. """ - from enthought.qt import QtGui + from traits.qt import QtGui control = QtGui.QWidget(parent) diff --git a/pyface/ui/qt4/workbench/workbench_window_layout.py b/pyface/ui/qt4/workbench/workbench_window_layout.py index 2707029f2..288cff4f3 100755 --- a/pyface/ui/qt4/workbench/workbench_window_layout.py +++ b/pyface/ui/qt4/workbench/workbench_window_layout.py @@ -15,15 +15,15 @@ import logging # Major package imports. -from enthought.qt import QtCore, QtGui +from traits.qt import QtCore, QtGui # Enthought library imports. -from enthought.traits.api import Instance, on_trait_change +from traits.api import Instance, on_trait_change # Local imports. from split_tab_widget import SplitTabWidget -from enthought.pyface.message_dialog import error -from enthought.pyface.workbench.i_workbench_window_layout import \ +from pyface.message_dialog import error +from pyface.workbench.i_workbench_window_layout import \ MWorkbenchWindowLayout diff --git a/pyface/ui/wx/workbench/editor.py b/pyface/ui/wx/workbench/editor.py index dcd36dcae..e0d562b13 100644 --- a/pyface/ui/wx/workbench/editor.py +++ b/pyface/ui/wx/workbench/editor.py @@ -18,7 +18,7 @@ """ # Local imports. -from enthought.pyface.workbench.i_editor import MEditor +from pyface.workbench.i_editor import MEditor class Editor(MEditor): diff --git a/pyface/ui/wx/workbench/editor_set_structure_handler.py b/pyface/ui/wx/workbench/editor_set_structure_handler.py index 1a31b4a86..a442d7192 100755 --- a/pyface/ui/wx/workbench/editor_set_structure_handler.py +++ b/pyface/ui/wx/workbench/editor_set_structure_handler.py @@ -21,7 +21,7 @@ import logging # Enthought library imports. -from enthought.pyface.dock.api import SetStructureHandler +from pyface.dock.api import SetStructureHandler logger = logging.getLogger(__name__) diff --git a/pyface/ui/wx/workbench/view.py b/pyface/ui/wx/workbench/view.py index cde3c7e31..b6e1abe69 100644 --- a/pyface/ui/wx/workbench/view.py +++ b/pyface/ui/wx/workbench/view.py @@ -18,8 +18,8 @@ """ # Enthought library imports. -from enthought.traits.api import Bool -from enthought.pyface.workbench.i_view import MView +from traits.api import Bool +from pyface.workbench.i_view import MView class View(MView): diff --git a/pyface/ui/wx/workbench/view_set_structure_handler.py b/pyface/ui/wx/workbench/view_set_structure_handler.py index a41a27d71..ccb6ef07f 100755 --- a/pyface/ui/wx/workbench/view_set_structure_handler.py +++ b/pyface/ui/wx/workbench/view_set_structure_handler.py @@ -21,7 +21,7 @@ import logging # Enthought library imports. -from enthought.pyface.dock.api import SetStructureHandler +from pyface.dock.api import SetStructureHandler logger = logging.getLogger(__name__) diff --git a/pyface/ui/wx/workbench/workbench_dock_window.py b/pyface/ui/wx/workbench/workbench_dock_window.py index a7ae6ac89..fe4b508bd 100755 --- a/pyface/ui/wx/workbench/workbench_dock_window.py +++ b/pyface/ui/wx/workbench/workbench_dock_window.py @@ -21,7 +21,7 @@ import logging # Enthought library imports. -from enthought.pyface.dock.api import DockGroup, DockRegion, DockWindow +from pyface.dock.api import DockGroup, DockRegion, DockWindow logger = logging.getLogger(__name__) diff --git a/pyface/ui/wx/workbench/workbench_window_layout.py b/pyface/ui/wx/workbench/workbench_window_layout.py index ad44216b2..aff5a5f14 100644 --- a/pyface/ui/wx/workbench/workbench_window_layout.py +++ b/pyface/ui/wx/workbench/workbench_window_layout.py @@ -25,15 +25,15 @@ import wx # Enthought library imports. -from enthought.pyface.dock.api import DOCK_BOTTOM, DOCK_LEFT, DOCK_RIGHT -from enthought.pyface.dock.api import DOCK_TOP -from enthought.pyface.dock.api import DockControl, DockRegion, DockSection -from enthought.pyface.dock.api import DockSizer -from enthought.traits.api import Delegate -from enthought.traits.ui.dockable_view_element import DockableViewElement +from pyface.dock.api import DOCK_BOTTOM, DOCK_LEFT, DOCK_RIGHT +from pyface.dock.api import DOCK_TOP +from pyface.dock.api import DockControl, DockRegion, DockSection +from pyface.dock.api import DockSizer +from traits.api import Delegate +from traitsui.dockable_view_element import DockableViewElement # Mixin class imports. -from enthought.pyface.workbench.i_workbench_window_layout import \ +from pyface.workbench.i_workbench_window_layout import \ MWorkbenchWindowLayout # Local imports. @@ -646,7 +646,7 @@ def _wx_initialize_editor_dock_control(self, editor, editor_dock_control): # fixme: Should we roll the traits UI stuff into the default editor. if hasattr(editor, 'ui') and editor.ui is not None: # This makes the control draggable outside of the main window. - #editor_dock_control.export = 'enthought.pyface.workbench.editor' + #editor_dock_control.export = 'pyface.workbench.editor' editor_dock_control.dockable = DockableViewElement( should_close=True, ui=editor.ui ) @@ -696,7 +696,7 @@ def _wx_initialize_view_dock_control(self, view, view_dock_control): # fixme: Should we roll the traits UI stuff into the default editor. if hasattr(view, 'ui') and view.ui is not None: # This makes the control draggable outside of the main window. - #view_dock_control.export = 'enthought.pyface.workbench.view' + #view_dock_control.export = 'pyface.workbench.view' # If the ui's 'view' trait has an 'export' field set, pass that on # to the dock control. This makes the control detachable from the diff --git a/pyface/workbench/action/action_controller.py b/pyface/workbench/action/action_controller.py index 6b30c2c8b..438c0bc86 100644 --- a/pyface/workbench/action/action_controller.py +++ b/pyface/workbench/action/action_controller.py @@ -2,9 +2,9 @@ # Enthought library imports. -from enthought.pyface.action.api import ActionController -from enthought.pyface.workbench.api import WorkbenchWindow -from enthought.traits.api import HasTraits, Instance +from pyface.action.api import ActionController +from pyface.workbench.api import WorkbenchWindow +from traits.api import HasTraits, Instance class ActionController(ActionController): diff --git a/pyface/workbench/action/delete_user_perspective_action.py b/pyface/workbench/action/delete_user_perspective_action.py index 756afef2f..714430e12 100644 --- a/pyface/workbench/action/delete_user_perspective_action.py +++ b/pyface/workbench/action/delete_user_perspective_action.py @@ -10,7 +10,7 @@ # Enthought library imports. -from enthought.pyface.api import YES +from pyface.api import YES # Local imports. from user_perspective_action import UserPerspectiveAction @@ -22,7 +22,7 @@ class DeleteUserPerspectiveAction(UserPerspectiveAction): #### 'Action' interface ################################################### # The action's unique identifier (may be None). - id = 'enthought.pyface.workbench.action.delete_user_perspective_action' + id = 'pyface.workbench.action.delete_user_perspective_action' # The action's name (displayed on menus/tool bar tools etc). name = 'Delete Perspective' diff --git a/pyface/workbench/action/menu_bar_manager.py b/pyface/workbench/action/menu_bar_manager.py index 1398fa984..d737f40a9 100644 --- a/pyface/workbench/action/menu_bar_manager.py +++ b/pyface/workbench/action/menu_bar_manager.py @@ -2,8 +2,8 @@ # Enthought library imports. -from enthought.pyface.action.api import MenuBarManager as BaseMenuBarManager -from enthought.traits.api import Instance +from pyface.action.api import MenuBarManager as BaseMenuBarManager +from traits.api import Instance # Local imports. from action_controller import ActionController @@ -15,7 +15,7 @@ class MenuBarManager(BaseMenuBarManager): #### 'MenuBarManager' interface ########################################### # The workbench window that we are the menu bar manager for. - window = Instance('enthought.pyface.workbench.api.WorkbenchWindow') + window = Instance('pyface.workbench.api.WorkbenchWindow') ########################################################################### # 'MenuBarManager' interface. diff --git a/pyface/workbench/action/new_user_perspective_action.py b/pyface/workbench/action/new_user_perspective_action.py index 224ef8300..ac45b8d18 100644 --- a/pyface/workbench/action/new_user_perspective_action.py +++ b/pyface/workbench/action/new_user_perspective_action.py @@ -20,7 +20,7 @@ class NewUserPerspectiveAction(WorkbenchAction): #### 'Action' interface ################################################### # The action's unique identifier. - id = 'enthought.pyface.workbench.action.new_user_perspective_action' + id = 'pyface.workbench.action.new_user_perspective_action' # The action's name. name = 'New Perspective...' diff --git a/pyface/workbench/action/perspective_menu_manager.py b/pyface/workbench/action/perspective_menu_manager.py index 17e3d758d..665033261 100644 --- a/pyface/workbench/action/perspective_menu_manager.py +++ b/pyface/workbench/action/perspective_menu_manager.py @@ -2,8 +2,8 @@ # Enthought library imports. -from enthought.pyface.action.api import Group, MenuManager -from enthought.traits.api import Instance, List, on_trait_change +from pyface.action.api import Group, MenuManager +from traits.api import Instance, List, on_trait_change # Local imports. from delete_user_perspective_action import DeleteUserPerspectiveAction @@ -34,7 +34,7 @@ class PerspectiveMenuManager(MenuManager): #### 'PerspectiveMenuManager' interface ################################### # The workbench window that the manager is part of. - window = Instance('enthought.pyface.workbench.api.WorkbenchWindow') + window = Instance('pyface.workbench.api.WorkbenchWindow') ########################################################################### # 'ActionManager' interface. diff --git a/pyface/workbench/action/rename_user_perspective_action.py b/pyface/workbench/action/rename_user_perspective_action.py index 1344dcb29..bae2fc17d 100644 --- a/pyface/workbench/action/rename_user_perspective_action.py +++ b/pyface/workbench/action/rename_user_perspective_action.py @@ -20,7 +20,7 @@ class RenameUserPerspectiveAction(UserPerspectiveAction): #### 'Action' interface ################################################### # The action's unique identifier (may be None). - id = 'enthought.pyface.workbench.action.rename_user_perspective_action' + id = 'pyface.workbench.action.rename_user_perspective_action' # The action's name (displayed on menus/tool bar tools etc). name = 'Rename Perspective...' diff --git a/pyface/workbench/action/reset_active_perspective_action.py b/pyface/workbench/action/reset_active_perspective_action.py index f34f64cc9..3c7aad68b 100644 --- a/pyface/workbench/action/reset_active_perspective_action.py +++ b/pyface/workbench/action/reset_active_perspective_action.py @@ -2,7 +2,7 @@ # Enthought library imports. -from enthought.pyface.api import YES +from pyface.api import YES # Local imports. from workbench_action import WorkbenchAction @@ -18,7 +18,7 @@ class ResetActivePerspectiveAction(WorkbenchAction): #### 'Action' interface ################################################### # The action's unique identifier (may be None). - id = 'enthought.pyface.workbench.action.reset_active_perspective' + id = 'pyface.workbench.action.reset_active_perspective' # The action's name (displayed on menus/tool bar tools etc). name = 'Reset Perspective' diff --git a/pyface/workbench/action/reset_all_perspectives_action.py b/pyface/workbench/action/reset_all_perspectives_action.py index c0d40696d..02e9b7ebd 100644 --- a/pyface/workbench/action/reset_all_perspectives_action.py +++ b/pyface/workbench/action/reset_all_perspectives_action.py @@ -2,7 +2,7 @@ # Enthought library imports. -from enthought.pyface.api import YES +from pyface.api import YES # Local imports. from workbench_action import WorkbenchAction @@ -18,7 +18,7 @@ class ResetAllPerspectivesAction(WorkbenchAction): #### 'Action' interface ################################################### # The action's unique identifier (may be None). - id = 'enthought.pyface.workbench.action.reset_all_perspectives' + id = 'pyface.workbench.action.reset_all_perspectives' # The action's name (displayed on menus/tool bar tools etc). name = 'Reset All Perspectives' diff --git a/pyface/workbench/action/save_as_user_perspective_action.py b/pyface/workbench/action/save_as_user_perspective_action.py index 8b818e28f..8cf5c7a55 100644 --- a/pyface/workbench/action/save_as_user_perspective_action.py +++ b/pyface/workbench/action/save_as_user_perspective_action.py @@ -20,7 +20,7 @@ class SaveAsUserPerspectiveAction(WorkbenchAction): #### 'Action' interface ################################################### # The action's unique identifier. - id = 'enthought.pyface.workbench.action.save_as_user_perspective_action' + id = 'pyface.workbench.action.save_as_user_perspective_action' # The action's name (displayed on menus/tool bar tools etc). name = 'Save Perspective As...' diff --git a/pyface/workbench/action/set_active_perspective_action.py b/pyface/workbench/action/set_active_perspective_action.py index e3fa9819f..db3b1df11 100644 --- a/pyface/workbench/action/set_active_perspective_action.py +++ b/pyface/workbench/action/set_active_perspective_action.py @@ -2,8 +2,8 @@ # Enthought library imports. -from enthought.pyface.workbench.api import IPerspective -from enthought.traits.api import Delegate, Instance, on_trait_change +from pyface.workbench.api import IPerspective +from traits.api import Delegate, Instance, on_trait_change # Local imports. from workbench_action import WorkbenchAction diff --git a/pyface/workbench/action/setattr_action.py b/pyface/workbench/action/setattr_action.py index 54662984c..8d5ba814b 100644 --- a/pyface/workbench/action/setattr_action.py +++ b/pyface/workbench/action/setattr_action.py @@ -2,7 +2,7 @@ # Enthought library imports. -from enthought.traits.api import Any, Str +from traits.api import Any, Str # Local imports. from workbench_action import WorkbenchAction diff --git a/pyface/workbench/action/show_view_action.py b/pyface/workbench/action/show_view_action.py index 2b19ef567..65691f1d4 100644 --- a/pyface/workbench/action/show_view_action.py +++ b/pyface/workbench/action/show_view_action.py @@ -12,7 +12,7 @@ class ShowViewAction(WorkbenchAction): #### 'Action' interface ################################################### # The action's unique identifier (may be None). - id = 'enthought.pyface.workbench.action.show_view' + id = 'pyface.workbench.action.show_view' # The action's name (displayed on menus/tool bar tools etc). name = 'Show View' diff --git a/pyface/workbench/action/toggle_view_visibility_action.py b/pyface/workbench/action/toggle_view_visibility_action.py index 0294fbf3f..5baa45f79 100644 --- a/pyface/workbench/action/toggle_view_visibility_action.py +++ b/pyface/workbench/action/toggle_view_visibility_action.py @@ -2,8 +2,8 @@ # Enthought library imports. -from enthought.pyface.workbench.api import IView -from enthought.traits.api import Delegate, Instance +from pyface.workbench.api import IView +from traits.api import Delegate, Instance # Local imports. from workbench_action import WorkbenchAction diff --git a/pyface/workbench/action/tool_bar_manager.py b/pyface/workbench/action/tool_bar_manager.py index cc309eb53..fe3536cbc 100644 --- a/pyface/workbench/action/tool_bar_manager.py +++ b/pyface/workbench/action/tool_bar_manager.py @@ -2,8 +2,8 @@ # Enthought library imports. -import enthought.pyface.action.api as pyface -from enthought.traits.api import Instance +import pyface.action.api as pyface +from traits.api import Instance # Local imports. from action_controller import ActionController @@ -15,7 +15,7 @@ class ToolBarManager(pyface.ToolBarManager): #### 'ToolBarManager' interface ########################################### # The workbench window that we are the tool bar manager for. - window = Instance('enthought.pyface.workbench.api.WorkbenchWindow') + window = Instance('pyface.workbench.api.WorkbenchWindow') ########################################################################### # 'ToolBarManager' interface. diff --git a/pyface/workbench/action/user_perspective_action.py b/pyface/workbench/action/user_perspective_action.py index 6bc6c6d72..f554677dd 100644 --- a/pyface/workbench/action/user_perspective_action.py +++ b/pyface/workbench/action/user_perspective_action.py @@ -10,7 +10,7 @@ # Enthought library imports. -from enthought.traits.api import on_trait_change +from traits.api import on_trait_change # Local imports. from workbench_action import WorkbenchAction diff --git a/pyface/workbench/action/user_perspective_name.py b/pyface/workbench/action/user_perspective_name.py index 011895faa..b261d020f 100644 --- a/pyface/workbench/action/user_perspective_name.py +++ b/pyface/workbench/action/user_perspective_name.py @@ -10,8 +10,8 @@ # Enthought library imports. -from enthought.traits.api import Bool, HasTraits, Trait, TraitError, Constant -from enthought.traits.ui.api import View, Item, VGroup +from traits.api import Bool, HasTraits, Trait, TraitError, Constant +from traitsui.api import View, Item, VGroup #### Trait definitions ######################################################## diff --git a/pyface/workbench/action/view_chooser.py b/pyface/workbench/action/view_chooser.py index ac4159af6..2f331b6a3 100644 --- a/pyface/workbench/action/view_chooser.py +++ b/pyface/workbench/action/view_chooser.py @@ -2,11 +2,11 @@ # Enthought library imports. -from enthought.pyface.workbench.api import IView, WorkbenchWindow -from enthought.traits.api import Any, HasTraits, Instance, List, Str -from enthought.traits.api import TraitError, Undefined -from enthought.traits.ui.api import Item, TreeEditor, TreeNode, View -from enthought.traits.ui.menu import Action # fixme: Non-api import! +from pyface.workbench.api import IView, WorkbenchWindow +from traits.api import Any, HasTraits, Instance, List, Str +from traits.api import TraitError, Undefined +from traitsui.api import Item, TreeEditor, TreeNode, View +from traitsui.menu import Action # fixme: Non-api import! class Category(HasTraits): @@ -110,7 +110,7 @@ class ViewChooser(HasTraits): """ # The window that contains the views to choose from. - window = Instance('enthought.pyface.workbench.api.WorkbenchWindow') + window = Instance('pyface.workbench.api.WorkbenchWindow') # The currently selected tree item (at any point in time this might be # either None, a view category, or a view). diff --git a/pyface/workbench/action/view_menu_manager.py b/pyface/workbench/action/view_menu_manager.py index b9e8b327b..01420f305 100644 --- a/pyface/workbench/action/view_menu_manager.py +++ b/pyface/workbench/action/view_menu_manager.py @@ -5,9 +5,9 @@ import logging # Enthought library imports. -from enthought.pyface.action.api import Group, MenuManager -from enthought.traits.api import Any, Bool, Instance, List, Str, Unicode -from enthought.traits.api import on_trait_change +from pyface.action.api import Group, MenuManager +from traits.api import Any, Bool, Instance, List, Str, Unicode +from traits.api import on_trait_change # Local imports. from perspective_menu_manager import PerspectiveMenuManager @@ -46,7 +46,7 @@ class ViewMenuManager(MenuManager): show_perspective_menu = Bool(True) # The workbench window that the menu is part of. - window = Instance('enthought.pyface.workbench.api.WorkbenchWindow') + window = Instance('pyface.workbench.api.WorkbenchWindow') #### 'Private' interface ################################################## diff --git a/pyface/workbench/action/workbench_action.py b/pyface/workbench/action/workbench_action.py index 1ab55e4fd..2e18975d6 100644 --- a/pyface/workbench/action/workbench_action.py +++ b/pyface/workbench/action/workbench_action.py @@ -2,9 +2,9 @@ # Enthought library imports. -from enthought.pyface.workbench.api import WorkbenchWindow -from enthought.pyface.action.api import Action -from enthought.traits.api import Instance +from pyface.workbench.api import WorkbenchWindow +from pyface.action.api import Action +from traits.api import Instance class WorkbenchAction(Action): diff --git a/pyface/workbench/debug/debug_view.py b/pyface/workbench/debug/debug_view.py index 9d5f30409..67e876b6e 100644 --- a/pyface/workbench/debug/debug_view.py +++ b/pyface/workbench/debug/debug_view.py @@ -2,9 +2,9 @@ # Enthought library imports. -from enthought.pyface.workbench.api import View, WorkbenchWindow -from enthought.traits.api import HasTraits, Instance, Str, on_trait_change -from enthought.traits.ui.api import View as TraitsView +from pyface.workbench.api import View, WorkbenchWindow +from traits.api import HasTraits, Instance, Str, on_trait_change +from traitsui.api import View as TraitsView class DebugViewModel(HasTraits): diff --git a/pyface/workbench/editor.py b/pyface/workbench/editor.py index 9f7f3207b..8cd0f7b8a 100755 --- a/pyface/workbench/editor.py +++ b/pyface/workbench/editor.py @@ -15,7 +15,7 @@ # Import the toolkit specific version. -from enthought.pyface.toolkit import toolkit_object +from pyface.toolkit import toolkit_object Editor = toolkit_object('workbench.editor:Editor') ### EOF ####################################################################### diff --git a/pyface/workbench/editor_manager.py b/pyface/workbench/editor_manager.py index 04234f971..332760833 100755 --- a/pyface/workbench/editor_manager.py +++ b/pyface/workbench/editor_manager.py @@ -4,7 +4,7 @@ import weakref # Enthought library imports. -from enthought.traits.api import HasTraits, Instance, implements +from traits.api import HasTraits, Instance, implements # Local imports. from i_editor_manager import IEditorManager @@ -19,7 +19,7 @@ class EditorManager(HasTraits): #### 'IEditorManager' interface ########################################### # The workbench window that the editor manager manages editors for ;^) - window = Instance('enthought.pyface.workbench.api.WorkbenchWindow') + window = Instance('pyface.workbench.api.WorkbenchWindow') ########################################################################### # 'object' interface. diff --git a/pyface/workbench/i_editor.py b/pyface/workbench/i_editor.py index 445f84ff5..b7844e744 100755 --- a/pyface/workbench/i_editor.py +++ b/pyface/workbench/i_editor.py @@ -16,9 +16,9 @@ import uuid # Enthought library imports. -from enthought.traits.api import Any, Bool, Event, VetoableEvent, Vetoable, \ +from traits.api import Any, Bool, Event, VetoableEvent, Vetoable, \ HasTraits, Instance, Interface -from enthought.traits.api import implements +from traits.api import implements # Local imports. from i_workbench_part import IWorkbenchPart, MWorkbenchPart diff --git a/pyface/workbench/i_editor_manager.py b/pyface/workbench/i_editor_manager.py index 1b8048007..def0f3533 100755 --- a/pyface/workbench/i_editor_manager.py +++ b/pyface/workbench/i_editor_manager.py @@ -2,14 +2,14 @@ # Enthought library imports. -from enthought.traits.api import Instance, Interface +from traits.api import Instance, Interface class IEditorManager(Interface): """ The editor manager interface. """ # The workbench window that the editor manager manages editors for ;^) - window = Instance('enthought.pyface.workbench.api.WorkbenchWindow') + window = Instance('pyface.workbench.api.WorkbenchWindow') def add_editor(self, editor, kind): """ Registers an existing editor. """ diff --git a/pyface/workbench/i_perspective.py b/pyface/workbench/i_perspective.py index a6be93358..c6c7a9f7a 100755 --- a/pyface/workbench/i_perspective.py +++ b/pyface/workbench/i_perspective.py @@ -2,7 +2,7 @@ # Enthought library imports. -from enthought.traits.api import Bool, Interface, List, Str, Tuple +from traits.api import Bool, Interface, List, Str, Tuple # Local imports. from perspective_item import PerspectiveItem diff --git a/pyface/workbench/i_perspective_item.py b/pyface/workbench/i_perspective_item.py index 42b85fff4..4962471cf 100644 --- a/pyface/workbench/i_perspective_item.py +++ b/pyface/workbench/i_perspective_item.py @@ -2,7 +2,7 @@ # Enthought library imports. -from enthought.traits.api import Enum, Float, Interface, Str +from traits.api import Enum, Float, Interface, Str class IPerspectiveItem(Interface): diff --git a/pyface/workbench/i_view.py b/pyface/workbench/i_view.py index 521f01b31..875413526 100755 --- a/pyface/workbench/i_view.py +++ b/pyface/workbench/i_view.py @@ -17,10 +17,10 @@ import logging # Enthought library imports. -from enthought.pyface.api import ImageResource -from enthought.traits.api import Bool, Enum, Float, Instance, List, Str, \ +from pyface.api import ImageResource +from traits.api import Bool, Enum, Float, Instance, List, Str, \ implements -from enthought.util.camel_case import camel_case_to_words +from traits.util.camel_case import camel_case_to_words # Local imports. from i_perspective_item import IPerspectiveItem diff --git a/pyface/workbench/i_workbench.py b/pyface/workbench/i_workbench.py index af2f62924..48a030d72 100644 --- a/pyface/workbench/i_workbench.py +++ b/pyface/workbench/i_workbench.py @@ -2,8 +2,8 @@ # Enthought library imports. -from enthought.traits.api import Event, Instance, Interface, List, Str -from enthought.traits.api import VetoableEvent, implements +from traits.api import Event, Instance, Interface, List, Str +from traits.api import VetoableEvent, implements # Local imports. from user_perspective_manager import UserPerspectiveManager diff --git a/pyface/workbench/i_workbench_part.py b/pyface/workbench/i_workbench_part.py index 4bd881d89..7ceca23c5 100644 --- a/pyface/workbench/i_workbench_part.py +++ b/pyface/workbench/i_workbench_part.py @@ -15,8 +15,8 @@ # Enthought library imports. -from enthought.traits.api import Any, Bool, HasTraits, Instance, Interface -from enthought.traits.api import List, Str, Unicode, implements +from traits.api import Any, Bool, HasTraits, Instance, Interface +from traits.api import List, Str, Unicode, implements class IWorkbenchPart(Interface): @@ -47,7 +47,7 @@ class IWorkbenchPart(Interface): # The workbench window that the part is in. # # The framework sets this when the part is created. - window = Instance('enthought.pyface.workbench.api.WorkbenchWindow') + window = Instance('pyface.workbench.api.WorkbenchWindow') #### Methods ############################################################## @@ -103,7 +103,7 @@ class MWorkbenchPart(HasTraits): # The workbench window that the part is in. # # The framework sets this when the part is created. - window = Instance('enthought.pyface.workbench.api.WorkbenchWindow') + window = Instance('pyface.workbench.api.WorkbenchWindow') #### Methods ############################################################## diff --git a/pyface/workbench/i_workbench_window_layout.py b/pyface/workbench/i_workbench_window_layout.py index 365362027..c919b3f87 100755 --- a/pyface/workbench/i_workbench_window_layout.py +++ b/pyface/workbench/i_workbench_window_layout.py @@ -15,8 +15,8 @@ # Enthought library imports. -from enthought.traits.api import Event, HasTraits, Instance, Interface, Str -from enthought.traits.api import implements +from traits.api import Event, HasTraits, Instance, Interface, Str +from traits.api import implements # Local imports. from i_editor import IEditor @@ -37,7 +37,7 @@ class IWorkbenchWindowLayout(Interface): editor_area_id = Str # The workbench window that this is the layout for. - window = Instance('enthought.pyface.workbench.api.WorkbenchWindow') + window = Instance('pyface.workbench.api.WorkbenchWindow') #### Events #### @@ -215,7 +215,7 @@ class MWorkbenchWindowLayout(HasTraits): editor_area_id = Str # The workbench window that this is the layout for. - window = Instance('enthought.pyface.workbench.api.WorkbenchWindow') + window = Instance('pyface.workbench.api.WorkbenchWindow') #### Events #### diff --git a/pyface/workbench/perspective.py b/pyface/workbench/perspective.py index 992bf9736..f8884dc9c 100755 --- a/pyface/workbench/perspective.py +++ b/pyface/workbench/perspective.py @@ -5,7 +5,7 @@ import logging # Enthought library imports. -from enthought.traits.api import Bool, HasTraits, List, Str, Tuple, implements +from traits.api import Bool, HasTraits, List, Str, Tuple, implements # Local imports. from i_perspective import IPerspective @@ -22,7 +22,7 @@ class Perspective(HasTraits): implements(IPerspective) # The ID of the default perspective. - DEFAULT_ID = 'enthought.pyface.workbench.default' + DEFAULT_ID = 'pyface.workbench.default' # The name of the default perspective. DEFAULT_NAME = 'Default' diff --git a/pyface/workbench/perspective_item.py b/pyface/workbench/perspective_item.py index c03e62956..c47ff3d40 100755 --- a/pyface/workbench/perspective_item.py +++ b/pyface/workbench/perspective_item.py @@ -2,7 +2,7 @@ # Enthought library imports. -from enthought.traits.api import Enum, Float, HasTraits, Str, implements +from traits.api import Enum, Float, HasTraits, Str, implements # Local imports. from i_perspective_item import IPerspectiveItem diff --git a/pyface/workbench/traits_ui_editor.py b/pyface/workbench/traits_ui_editor.py index 78c187682..53117a68d 100644 --- a/pyface/workbench/traits_ui_editor.py +++ b/pyface/workbench/traits_ui_editor.py @@ -5,8 +5,8 @@ import logging # Enthought library imports. -from enthought.traits.api import Instance, Str -from enthought.traits.ui.api import UI +from traits.api import Instance, Str +from traitsui.api import UI # Local imports. from editor import Editor diff --git a/pyface/workbench/traits_ui_view.py b/pyface/workbench/traits_ui_view.py index 4d8de2c04..81754d57f 100644 --- a/pyface/workbench/traits_ui_view.py +++ b/pyface/workbench/traits_ui_view.py @@ -5,8 +5,8 @@ import logging # Enthought library imports. -from enthought.traits.api import Any, Instance, Str -from enthought.traits.ui.api import UI +from traits.api import Any, Instance, Str +from traitsui.api import UI # Local imports. from view import View diff --git a/pyface/workbench/user_perspective_manager.py b/pyface/workbench/user_perspective_manager.py index 6be4e904a..7ea921abe 100644 --- a/pyface/workbench/user_perspective_manager.py +++ b/pyface/workbench/user_perspective_manager.py @@ -6,9 +6,9 @@ import os # Enthought library imports. -from enthought.pyface.workbench.api import Perspective -from enthought.traits.api import Any, Dict, HasTraits, Int, List, Property -from enthought.traits.api import Unicode +from pyface.workbench.api import Perspective +from traits.api import Any, Dict, HasTraits, Int, List, Property +from traits.api import Unicode # Logging. diff --git a/pyface/workbench/view.py b/pyface/workbench/view.py index 4aacb94a8..536453382 100755 --- a/pyface/workbench/view.py +++ b/pyface/workbench/view.py @@ -15,7 +15,7 @@ # Import the toolkit specific version. -from enthought.pyface.toolkit import toolkit_object +from pyface.toolkit import toolkit_object View = toolkit_object('workbench.view:View') ### EOF ####################################################################### diff --git a/pyface/workbench/window_event.py b/pyface/workbench/window_event.py index 5d3f5c538..8ecbab864 100644 --- a/pyface/workbench/window_event.py +++ b/pyface/workbench/window_event.py @@ -2,7 +2,7 @@ # Enthought library imports. -from enthought.traits.api import HasTraits, Instance, Vetoable +from traits.api import HasTraits, Instance, Vetoable # Local imports. from workbench_window import WorkbenchWindow diff --git a/pyface/workbench/workbench.py b/pyface/workbench/workbench.py index 01ec50dc4..79004898e 100755 --- a/pyface/workbench/workbench.py +++ b/pyface/workbench/workbench.py @@ -7,11 +7,11 @@ import os # Enthought library imports. -from enthought.etsconfig.api import ETSConfig -from enthought.pyface.api import NO -from enthought.traits.api import Bool, Callable, Event, HasTraits, implements -from enthought.traits.api import Instance, List, Unicode, Vetoable -from enthought.traits.api import VetoableEvent +from traits.etsconfig.api import ETSConfig +from pyface.api import NO +from traits.api import Bool, Callable, Event, HasTraits, implements +from traits.api import Instance, List, Unicode, Vetoable +from traits.api import VetoableEvent # Local imports. from i_editor_manager import IEditorManager diff --git a/pyface/workbench/workbench_window.py b/pyface/workbench/workbench_window.py index 45d02de58..23051b39b 100755 --- a/pyface/workbench/workbench_window.py +++ b/pyface/workbench/workbench_window.py @@ -5,10 +5,10 @@ import logging # Enthought library imports. -from enthought.pyface.api import ApplicationWindow, GUI -from enthought.traits.api import Callable, Constant, Delegate, Event, Instance -from enthought.traits.api import List, Str, Tuple, Unicode, Vetoable -from enthought.traits.api import implements, on_trait_change +from pyface.api import ApplicationWindow, GUI +from traits.api import Callable, Constant, Delegate, Event, Instance +from traits.api import List, Str, Tuple, Unicode, Vetoable +from traits.api import implements, on_trait_change # Local imports. from i_editor import IEditor @@ -40,7 +40,7 @@ class WorkbenchWindow(ApplicationWindow): selection = List # The workbench that the window belongs to. - workbench = Instance('enthought.pyface.workbench.api.IWorkbench') + workbench = Instance('pyface.workbench.api.IWorkbench') #### Editors ####################### @@ -51,7 +51,7 @@ class WorkbenchWindow(ApplicationWindow): editors = List(IEditor) # The Id of the editor area. - editor_area_id = Constant('enthought.pyface.workbench.editors') + editor_area_id = Constant('pyface.workbench.editors') # The (initial) size of the editor area (the user is free to resize it of # course). diff --git a/pyface/workbench/workbench_window_layout.py b/pyface/workbench/workbench_window_layout.py index 641db14d8..c3d9b1eaf 100755 --- a/pyface/workbench/workbench_window_layout.py +++ b/pyface/workbench/workbench_window_layout.py @@ -15,7 +15,7 @@ # Import the toolkit specific version. -from enthought.pyface.toolkit import toolkit_object +from pyface.toolkit import toolkit_object WorkbenchWindowLayout = toolkit_object( 'workbench.workbench_window_layout:WorkbenchWindowLayout' diff --git a/pyface/workbench/workbench_window_memento.py b/pyface/workbench/workbench_window_memento.py index cbe741e48..1faf9562a 100644 --- a/pyface/workbench/workbench_window_memento.py +++ b/pyface/workbench/workbench_window_memento.py @@ -2,7 +2,7 @@ # Enthought library imports. -from enthought.traits.api import Any, Dict, HasTraits, Str, Tuple +from traits.api import Any, Dict, HasTraits, Str, Tuple class WorkbenchWindowMemento(HasTraits): From 2977d2cec45f48c738712b8f79f80ba6d3d63460 Mon Sep 17 00:00:00 2001 From: Ilan Schnell Date: Tue, 26 Apr 2011 23:12:56 -0500 Subject: [PATCH 05/60] updating __init__.py files --- pyface/ui/qt4/workbench/__init__.py | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/pyface/ui/qt4/workbench/__init__.py b/pyface/ui/qt4/workbench/__init__.py index fc094dfe7..87c9bf0e3 100644 --- a/pyface/ui/qt4/workbench/__init__.py +++ b/pyface/ui/qt4/workbench/__init__.py @@ -1,17 +1,2 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2007 by Enthought, Inc. +# Copyright (c) 2007-2011 by Enthought, Inc. # All rights reserved. -#------------------------------------------------------------------------------ - -try: - __import__('pkg_resources').declare_namespace(__name__) -except: - pass - -# For py2app / py2exe support -try: - import modulefinder - for p in __path__: - modulefinder.AddPackagePath(__name__, p) -except: - pass From 31a5360839e0b6010f8f6ca67e12c20b8580bf02 Mon Sep 17 00:00:00 2001 From: Ilan Schnell Date: Sat, 4 Jun 2011 21:45:23 -0500 Subject: [PATCH 06/60] update the few imports to not use etsproxy --- pyface/workbench/action/user_perspective_name.py | 6 +++--- pyface/workbench/i_editor.py | 6 +++--- pyface/workbench/i_workbench.py | 4 ++-- pyface/workbench/workbench.py | 6 +++--- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pyface/workbench/action/user_perspective_name.py b/pyface/workbench/action/user_perspective_name.py index b261d020f..e38b36ef7 100644 --- a/pyface/workbench/action/user_perspective_name.py +++ b/pyface/workbench/action/user_perspective_name.py @@ -61,7 +61,7 @@ class UserPerspectiveName(HasTraits): ) ), title = 'New User Perspective', - id = 'enthought.envisage.workbench.action.' + id = 'envisage.workbench.action.' 'new_user_perspective_action.UserPerspectiveName', buttons = [ 'OK', 'Cancel' ], kind = 'livemodal', @@ -70,7 +70,7 @@ class UserPerspectiveName(HasTraits): save_as_view = View( 'name', title = 'Save User Perspective As', - id = 'enthought.envisage.workbench.action.' + id = 'envisage.workbench.action.' 'save_as_user_perspective_action.UserPerspectiveName', buttons = [ 'OK', 'Cancel' ], kind = 'livemodal', @@ -79,7 +79,7 @@ class UserPerspectiveName(HasTraits): rename_view = View( 'name', title = 'Rename User Perspective', - id = 'enthought.envisage.workbench.action.' + id = 'envisage.workbench.action.' 'rename_user_perspective_action.UserPerspectiveName', buttons = [ 'OK', 'Cancel' ], kind = 'livemodal', diff --git a/pyface/workbench/i_editor.py b/pyface/workbench/i_editor.py index b7844e744..78bb4161d 100755 --- a/pyface/workbench/i_editor.py +++ b/pyface/workbench/i_editor.py @@ -28,7 +28,7 @@ class IEditor(IWorkbenchPart): """ The interface of a workbench editor. """ # The optional command stack. - command_stack = Instance('enthought.undo.api.ICommandStack') + command_stack = Instance('apptools.undo.api.ICommandStack') # Is the object that the editor is editing 'dirty' i.e., has it been # modified but not saved? @@ -67,7 +67,7 @@ class MEditor(MWorkbenchPart): #### 'IEditor' interface ################################################## # The optional command stack. - command_stack = Instance('enthought.undo.api.ICommandStack') + command_stack = Instance('apptools.undo.api.ICommandStack') # Is the object that the editor is editing 'dirty' i.e., has it been # modified but not saved? @@ -136,7 +136,7 @@ def _command_stack_default(self): # We make sure the undo package is entirely optional. try: - from enthought.undo.api import CommandStack + from apptools.undo.api import CommandStack except ImportError: return None diff --git a/pyface/workbench/i_workbench.py b/pyface/workbench/i_workbench.py index 48a030d72..0449438eb 100644 --- a/pyface/workbench/i_workbench.py +++ b/pyface/workbench/i_workbench.py @@ -20,14 +20,14 @@ class IWorkbench(Interface): active_window = Instance(WorkbenchWindow) # The optional application scripting manager. - script_manager = Instance('enthought.appscripting.api.IScriptManager') + script_manager = Instance('apptools.appscripting.api.IScriptManager') # A directory on the local file system that we can read and write to at # will. This is used to persist window layout information, etc. state_location = Str # The optional undo manager. - undo_manager = Instance('enthought.undo.api.IUndoManager') + undo_manager = Instance('apptools.undo.api.IUndoManager') # The user defined perspectives manager. user_perspective_manager = Instance(UserPerspectiveManager) diff --git a/pyface/workbench/workbench.py b/pyface/workbench/workbench.py index 79004898e..df32d2710 100755 --- a/pyface/workbench/workbench.py +++ b/pyface/workbench/workbench.py @@ -44,14 +44,14 @@ class Workbench(HasTraits): editor_manager = Instance(IEditorManager) # The optional application scripting manager. - script_manager = Instance('enthought.appscripting.api.IScriptManager') + script_manager = Instance('apptools.appscripting.api.IScriptManager') # A directory on the local file system that we can read and write to at # will. This is used to persist window layout information, etc. state_location = Unicode # The optional undo manager. - undo_manager = Instance('enthought.undo.api.IUndoManager') + undo_manager = Instance('apptools.undo.api.IUndoManager') # The user-defined perspectives manager. user_perspective_manager = Instance(UserPerspectiveManager) @@ -259,7 +259,7 @@ def _undo_manager_default(self): # We make sure the undo package is entirely optional. try: - from enthought.undo.api import UndoManager + from apptools.undo.api import UndoManager except ImportError: return None From 64ae29c963282a9336aec022e9dab7ee4ab9b14c Mon Sep 17 00:00:00 2001 From: Ilan Schnell Date: Mon, 13 Jun 2011 17:03:04 -0500 Subject: [PATCH 07/60] update traits.qt imports --- pyface/ui/qt4/workbench/editor.py | 2 +- pyface/ui/qt4/workbench/split_tab_widget.py | 2 +- pyface/ui/qt4/workbench/view.py | 2 +- pyface/ui/qt4/workbench/workbench_window_layout.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pyface/ui/qt4/workbench/editor.py b/pyface/ui/qt4/workbench/editor.py index d4de22213..3fafb1ab8 100644 --- a/pyface/ui/qt4/workbench/editor.py +++ b/pyface/ui/qt4/workbench/editor.py @@ -29,7 +29,7 @@ class Editor(MEditor): def create_control(self, parent): """ Create the toolkit-specific control that represents the part. """ - from traits.qt import QtGui + from pyface.qt import QtGui # By default we create a yellow panel! control = QtGui.QWidget(parent) diff --git a/pyface/ui/qt4/workbench/split_tab_widget.py b/pyface/ui/qt4/workbench/split_tab_widget.py index d8738ab0c..c88eeaed5 100644 --- a/pyface/ui/qt4/workbench/split_tab_widget.py +++ b/pyface/ui/qt4/workbench/split_tab_widget.py @@ -12,7 +12,7 @@ # Major library imports. import sip -from traits.qt import QtCore, QtGui +from pyface.qt import QtCore, QtGui class SplitTabWidget(QtGui.QSplitter): diff --git a/pyface/ui/qt4/workbench/view.py b/pyface/ui/qt4/workbench/view.py index cfae48d2d..357e43e19 100644 --- a/pyface/ui/qt4/workbench/view.py +++ b/pyface/ui/qt4/workbench/view.py @@ -29,7 +29,7 @@ class View(MView): def create_control(self, parent): """ Create the toolkit-specific control that represents the part. """ - from traits.qt import QtGui + from pyface.qt import QtGui control = QtGui.QWidget(parent) diff --git a/pyface/ui/qt4/workbench/workbench_window_layout.py b/pyface/ui/qt4/workbench/workbench_window_layout.py index 288cff4f3..7e2e8f4ce 100755 --- a/pyface/ui/qt4/workbench/workbench_window_layout.py +++ b/pyface/ui/qt4/workbench/workbench_window_layout.py @@ -15,7 +15,7 @@ import logging # Major package imports. -from traits.qt import QtCore, QtGui +from pyface.qt import QtCore, QtGui # Enthought library imports. from traits.api import Instance, on_trait_change From 226479210d374288262cf841f4622528c17fa6c2 Mon Sep 17 00:00:00 2001 From: Robert Kern Date: Thu, 10 Nov 2011 11:01:49 +0000 Subject: [PATCH 08/60] BUG: Make sure that the editor area is restored correctly when the perspective is changed. --- pyface/workbench/workbench_window.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pyface/workbench/workbench_window.py b/pyface/workbench/workbench_window.py index 23051b39b..eb8e85765 100755 --- a/pyface/workbench/workbench_window.py +++ b/pyface/workbench/workbench_window.py @@ -735,6 +735,13 @@ def _show_perspective(self, old, new): # If the perspective has been seen before then restore it. memento = self._memento.perspective_mementos.get(new.id) if memento is not None: + # Show the editor area? + if new.show_editor_area: + self.show_editor_area() + else: + self.hide_editor_area() + self.active_editor = None + view_memento, active_view_id = memento self.layout.set_view_memento(view_memento) From be3cb3ed9ab712d870e3503e513beef263932f5a Mon Sep 17 00:00:00 2001 From: Robert Kern Date: Thu, 10 Nov 2011 11:30:15 +0000 Subject: [PATCH 09/60] BUG: Fix the persistence of WorkbenchWindow geometry for the Qt backend. --- .../qt4/workbench/workbench_window_layout.py | 14 ++++++++++++++ pyface/workbench/i_workbench_window_layout.py | 18 ++++++++++++++++++ pyface/workbench/workbench_window.py | 10 ++++++++++ pyface/workbench/workbench_window_memento.py | 3 +++ 4 files changed, 45 insertions(+) diff --git a/pyface/ui/qt4/workbench/workbench_window_layout.py b/pyface/ui/qt4/workbench/workbench_window_layout.py index 7e2e8f4ce..5548dfad8 100755 --- a/pyface/ui/qt4/workbench/workbench_window_layout.py +++ b/pyface/ui/qt4/workbench/workbench_window_layout.py @@ -284,6 +284,20 @@ def resolve_id(id): self._qt4_editor_area.restoreState(editor_layout, resolve_id) + def get_toolkit_memento(self): + return (0, dict(geometry=str(self.window.control.saveGeometry()))) + + def set_toolkit_memento(self, memento): + if hasattr(memento, 'toolkit_data'): + data = memento.toolkit_data + if isinstance(data, tuple) and len(data) == 2: + version, datadict = data + if version == 0: + geometry = datadict.pop('geometry', None) + if geometry is not None: + self.window.control.restoreGeometry(geometry) + + ########################################################################### # Private interface. ########################################################################### diff --git a/pyface/workbench/i_workbench_window_layout.py b/pyface/workbench/i_workbench_window_layout.py index c919b3f87..c922a878a 100755 --- a/pyface/workbench/i_workbench_window_layout.py +++ b/pyface/workbench/i_workbench_window_layout.py @@ -202,6 +202,14 @@ def set_editor_memento(self, memento): """ + def get_toolkit_memento(self): + """ Return any toolkit-specific data that should be part of the memento. + """ + + def set_toolkit_memento(self, memento): + """ Restores any toolkit-specific data. + """ + class MWorkbenchWindowLayout(HasTraits): """ Mixin containing common code for toolkit-specific implementations. """ @@ -349,6 +357,16 @@ def set_editor_memento(self, memento): raise NotImplementedError + def get_toolkit_memento(self): + """ Return any toolkit-specific data that should be part of the memento. + """ + return None + + def set_toolkit_memento(self, memento): + """ Restores any toolkit-specific data. + """ + return + ########################################################################### # Protected 'MWorkbenchWindowLayout' interface. ########################################################################### diff --git a/pyface/workbench/workbench_window.py b/pyface/workbench/workbench_window.py index eb8e85765..7f7e50c53 100755 --- a/pyface/workbench/workbench_window.py +++ b/pyface/workbench/workbench_window.py @@ -574,6 +574,9 @@ def get_memento(self): # The layout of the editor area. self._memento.editor_area_memento = self.layout.get_editor_memento() + # Any extra toolkit-specific data. + self._memento.toolkit_data = self.layout.get_toolkit_memento() + return self._memento def set_memento(self, memento): @@ -789,6 +792,13 @@ def _restore_contents(self): self.size = self._memento.size self.position = self._memento.position + # Set the toolkit-specific data last because it may override the generic + # implementation. + # FIXME: The primary use case is to let Qt restore the window's geometry + # wholesale, including maximization state. If we ever go Qt-only, this + # is a good area to refactor. + self.layout.set_toolkit_memento(self._memento) + return #### Trait change handlers ################################################ diff --git a/pyface/workbench/workbench_window_memento.py b/pyface/workbench/workbench_window_memento.py index 1faf9562a..3b3ec3506 100644 --- a/pyface/workbench/workbench_window_memento.py +++ b/pyface/workbench/workbench_window_memento.py @@ -26,5 +26,8 @@ class WorkbenchWindowMemento(HasTraits): # The size of the window. size = Tuple + # Any extra data the toolkit implementation may want to keep. + toolkit_data = Any() + #### EOF ###################################################################### From 16845ff77673cb667f816e98b1096d365fa7588f Mon Sep 17 00:00:00 2001 From: Robert Kern Date: Thu, 10 Nov 2011 12:11:39 +0000 Subject: [PATCH 10/60] BUG: work around a crash when changing the focus in certain tabs. --- pyface/ui/qt4/workbench/split_tab_widget.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyface/ui/qt4/workbench/split_tab_widget.py b/pyface/ui/qt4/workbench/split_tab_widget.py index c88eeaed5..9d18a7295 100644 --- a/pyface/ui/qt4/workbench/split_tab_widget.py +++ b/pyface/ui/qt4/workbench/split_tab_widget.py @@ -296,7 +296,9 @@ def _focus_changed(self, old, new): self.emit(QtCore.SIGNAL('focusChanged(QWidget *,QWidget *)'), old, new) - if isinstance(new, _DragableTabBar): + if new is None: + return + elif isinstance(new, _DragableTabBar): ntw = new.parent() ntidx = ntw.currentIndex() else: From 7e8ec29c3990a32aa808262b7b1ae9640c56f350 Mon Sep 17 00:00:00 2001 From: Naveen Michaud-Agrawal Date: Thu, 10 Nov 2011 09:40:26 -0500 Subject: [PATCH 11/60] BF: Handle view being closed through close button --- pyface/ui/qt4/workbench/workbench_window_layout.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pyface/ui/qt4/workbench/workbench_window_layout.py b/pyface/ui/qt4/workbench/workbench_window_layout.py index 7e2e8f4ce..559dadd1f 100755 --- a/pyface/ui/qt4/workbench/workbench_window_layout.py +++ b/pyface/ui/qt4/workbench/workbench_window_layout.py @@ -459,6 +459,8 @@ def _qt4_create_view_dock_widget(self, view, size=(-1, -1)): dw.setObjectName(view.id) dw.connect(dw.toggleViewAction(), QtCore.SIGNAL('toggled(bool)'), self._qt4_handle_dock_visibility) + dw.connect(dw, QtCore.SIGNAL('visibilityChanged(bool)'), + self._qt4_handle_dock_visibility) # Save the dock window. view._qt4_dock = dw @@ -512,7 +514,11 @@ def _qt4_handle_dock_visibility(self, checked): except AttributeError: continue - if dw.toggleViewAction() is dw.sender(): + sender = dw.sender() + if (sender is dw.toggleViewAction() or + sender in dw.children()): + # Toggling the action or pressing the close button on + # the view v.visible = checked def _qt4_monitor(self, control): From 4543fa7063fed49013b7ae805160c5728a4b6046 Mon Sep 17 00:00:00 2001 From: Robert Kern Date: Thu, 10 Nov 2011 15:15:13 +0000 Subject: [PATCH 12/60] ENH: Added spinner on tab, midddle-click to close tab, and add new tabs immediately after current tab instead of end of tab bar. --- pyface/ui/qt4/workbench/images/spinner.gif | Bin 0 -> 1737 bytes pyface/ui/qt4/workbench/split_tab_widget.py | 27 +++++++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 pyface/ui/qt4/workbench/images/spinner.gif diff --git a/pyface/ui/qt4/workbench/images/spinner.gif b/pyface/ui/qt4/workbench/images/spinner.gif new file mode 100644 index 0000000000000000000000000000000000000000..1560b646cff2cc4fd292d7fdb6f7adc7eb484b4a GIT binary patch literal 1737 zcmaLXeN0p99l-I&!tEQiv~W&~lv_&SmI|fRw)eE0zR=T_(^9a!crF*Q_O?JP*50BW za+x#6J*VfyIVLB@`G?mSW{fe8%LJS?j`_k2*Er)fUT2(h#u;OrF~)0*amLK)uKSPd z_ve%6mwfVklPAzh+MD7sAOkM}c>DG(0N}=r8~gU{i^XCdkH_!#XEK??hY#PmbLYsB zBQ-TOdcEFcGGQ3zcDqxl)R{A9Dk>_n+3b@iPo6$~y0EaYd-v|n&dz8wdiLzuYuB#f zIId7A#>dBxA3q+6M7D3=e*gY`mSv?<>Cn*7!Gi~r$>gb1r$~|%i^V5SoY=pAe_2@> zpU;2v=+TQ8FUH2ka=F~gmoFiN$BrF){rdHrH*e0LKY#i1-Mo2odV2cVvuD?@ zUw`o6!QH!euU@^nxVSh!KY#Duy<4|#J%9fE_U+r-wrx9k^5pF7Y2!hz{_K}ehqtPf73O8@wtkGyFib|)`J9qAU_vSxN)!U+Q3$?~abhem{rh;Yf zPOJW1?#m|PBZ)!HR~tu$H1Sk?EIk|_G;Yi!he6f93Ps{~w+!$1-$w+3U+n~t&M}Ot zdSwI42rFvpj1a?ZjJW9IPwE??&E&#p9=}A*VhFMy6cm+?16Q*P7E0tZ7Ed$$XgM+i z94(M9MQ3b;8w!dQGl;PjLVE4Y?<_P{QXeB^>`CbQFBvD_OT*#R6y9XGLopA~j#=m{7->pd6t>(Yi=3 zP}uo3V`jwsY7W}yU|ZK5z%f|Z?m9z~uy*yuIRu5uFfoVs{~5H(gmaYV0%%rtm$QvL z{~uwK+1e_?@y3>z>U)~d^%(jpL%ul0QBAF>DcN4YPSb$5Y$@O`J^)4H(vAG6fyV-$ zpp9QFT`ozb8GCfYP#22&x;IILw01D;WQQ6$Rd7I-5KT1ikgn+a(|F~;N5`@Dq1(|Cs^MXKy8P08>2 ze(zaIH}OH&tXIpzRp|f=nv{at6&u#2!XbvJ4yIM4%e%g~!%0&*vz<+T(e8jvJ$eoV zB?T~K5as2YK8NWfo<~YPgTv`>IqKsqR5<(dp)cEDK=00n>#89cvunA{%L%$M)}%E- z+Y7LTL0WhsE#TMm`-=Xj>PZ!`)~ z(K&8wwg6)N&3|FkYgh7U3x!zd`r4lqi5ZME;n0{F5#}>?i)rEJH#GeJiA|m+eZ8DO zSU?f>fl#Gzi`dPes#t)+aBX={4?$xdcO&byCid?1(awZqf1}Z}+v{|OO zt|T#wanNsY&NCKu;zG`1RYSh)xbINpDJ|DFE; D?d&fs literal 0 HcmV?d00001 diff --git a/pyface/ui/qt4/workbench/split_tab_widget.py b/pyface/ui/qt4/workbench/split_tab_widget.py index 9d18a7295..d05a10695 100644 --- a/pyface/ui/qt4/workbench/split_tab_widget.py +++ b/pyface/ui/qt4/workbench/split_tab_widget.py @@ -14,6 +14,8 @@ import sip from pyface.qt import QtCore, QtGui +from pyface.image_resource import ImageResource + class SplitTabWidget(QtGui.QSplitter): """ The SplitTabWidget class is a hierarchy of QSplitters the leaves of @@ -174,7 +176,7 @@ def addTab(self, w, text): ch = _TabWidget(self) self.addWidget(ch) - idx = ch.addTab(w, text) + idx = ch.insertTab(self._current_tab_idx+1, w, text) # If the tab has been added to the current tab widget then make it the # current tab. @@ -728,6 +730,8 @@ class _TabWidget(QtGui.QTabWidget): # The active icon. It is created when it is first needed. _active_icon = None + _spinner_data = None + def __init__(self, root, *args): """ Initialise the instance. """ @@ -747,6 +751,25 @@ def __init__(self, root, *args): self.setTabsClosable(True) self.tabCloseRequested.connect(self._close_tab) + if not (_TabWidget._spinner_data): + #reference = resource_manager.locate_image( 'spinner.gif', '.') + _TabWidget._spinner_data = ImageResource('spinner.gif') + + def show_spinner(self, index): + lbl = QtGui.QLabel(self) + movie = QtGui.QMovie(_TabWidget._spinner_data.absolute_path, parent=lbl) + movie.setCacheMode(QtGui.QMovie.CacheAll) + movie.setScaledSize(QtCore.QSize(16, 16)) + lbl.setMovie(movie) + movie.start() + self.tabBar().setTabButton(index, QtGui.QTabBar.LeftSide, lbl) + + def hide_spinner(self, index): + curr = self.tabBar().tabButton(index, QtGui.QTabBar.LeftSide) + if curr: + curr.close() + self.tabBar().setTabButton(index, QtGui.QTabBar.LeftSide, None) + def active_icon(self): """ Return the QIcon to be used to indicate an active tab page. """ @@ -898,6 +921,8 @@ def mouseReleaseEvent(self, e): QtGui.QTabBar.mouseReleaseEvent(self, e) if e.button() != QtCore.Qt.LeftButton: + if e.button() == QtCore.Qt.MidButton: + self.tabCloseRequested.emit(self.tabAt(e.pos())) return if self._drag_state is not None and self._drag_state.dragging: From af8c33bc338aea84fe2238d46c1082b4a9c325bd Mon Sep 17 00:00:00 2001 From: Robert Kern Date: Thu, 10 Nov 2011 15:17:47 +0000 Subject: [PATCH 13/60] BUG: Fixed bug with showing spinner when moving tabs between different tab bars. --- pyface/ui/qt4/workbench/split_tab_widget.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/pyface/ui/qt4/workbench/split_tab_widget.py b/pyface/ui/qt4/workbench/split_tab_widget.py index d05a10695..ee3af3856 100644 --- a/pyface/ui/qt4/workbench/split_tab_widget.py +++ b/pyface/ui/qt4/workbench/split_tab_widget.py @@ -420,7 +420,7 @@ def _drop(self, pos, stab_w, stab): if dhs == self._HS_OUTSIDE: # Disable tab tear-out for now. It works, but this is something that # should be turned on manually. We need an interface for this. - #ticon, ttext, ttextcolor, twidg = self._remove_tab(stab_w, stab) + #ticon, ttext, ttextcolor, tbuttn, twidg = self._remove_tab(stab_w, stab) #self.new_window_request.emit(pos, twidg) return @@ -436,7 +436,7 @@ def _drop(self, pos, stab_w, stab): QtGui.qApp.blockSignals(True) - ticon, ttext, ttextcolor, twidg = self._remove_tab(stab_w, stab) + ticon, ttext, ttextcolor, tbuttn, twidg = self._remove_tab(stab_w, stab) if dhs == self._HS_AFTER_LAST_TAB: idx = dtab_w.addTab(twidg, ticon, ttext) @@ -455,6 +455,8 @@ def _drop(self, pos, stab_w, stab): idx = dtab_w.insertTab(dhs, twidg, ticon, ttext) dtab_w.tabBar().setTabTextColor(idx, ttextcolor) + if (tbuttn): + dtab_w.show_button(idx) dsplit_w._set_current_tab(dtab_w, idx) else: @@ -466,10 +468,12 @@ def _drop(self, pos, stab_w, stab): # Remove the tab from its current tab widget and create a new one # for it. - ticon, ttext, ttextcolor, twidg = self._remove_tab(stab_w, stab) + ticon, ttext, ttextcolor, tbuttn, twidg = self._remove_tab(stab_w, stab) new_tw = _TabWidget(dsplit_w) - new_tw.addTab(twidg, ticon, ttext) + idx = new_tw.addTab(twidg, ticon, ttext) new_tw.tabBar().setTabTextColor(0, ttextcolor) + if tbuttn: + new_tw.show_button(idx) # Get the splitter containing the destination tab widget. dspl = dtab_w.parent() @@ -559,10 +563,11 @@ def _remove_tab(self, tab_w, tab): icon = tab_w.tabIcon(tab) text = tab_w.tabText(tab) text_color = tab_w.tabBar().tabTextColor(tab) + button = tab_w.tabBar().tabButton(tab, QtGui.QTabBar.LeftSide) w = tab_w.widget(tab) tab_w.removeTab(tab) - return (icon, text, text_color, w) + return (icon, text, text_color, button, w) def _hotspot(self, pos): """ Return a tuple of the tab widget, hotspot and hostspot geometry (as @@ -752,10 +757,9 @@ def __init__(self, root, *args): self.tabCloseRequested.connect(self._close_tab) if not (_TabWidget._spinner_data): - #reference = resource_manager.locate_image( 'spinner.gif', '.') _TabWidget._spinner_data = ImageResource('spinner.gif') - def show_spinner(self, index): + def show_button(self, index): lbl = QtGui.QLabel(self) movie = QtGui.QMovie(_TabWidget._spinner_data.absolute_path, parent=lbl) movie.setCacheMode(QtGui.QMovie.CacheAll) @@ -764,7 +768,7 @@ def show_spinner(self, index): movie.start() self.tabBar().setTabButton(index, QtGui.QTabBar.LeftSide, lbl) - def hide_spinner(self, index): + def hide_button(self, index): curr = self.tabBar().tabButton(index, QtGui.QTabBar.LeftSide) if curr: curr.close() From 998b3112062abfffba1f2bb255cf6be2f17ad219 Mon Sep 17 00:00:00 2001 From: Robert Kern Date: Thu, 10 Nov 2011 15:34:30 +0000 Subject: [PATCH 14/60] ENH: Update workbench to support showing spinner on tabs. --- pyface/ui/qt4/workbench/editor.py | 7 ++++++- .../ui/qt4/workbench/workbench_window_layout.py | 15 +++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/pyface/ui/qt4/workbench/editor.py b/pyface/ui/qt4/workbench/editor.py index 3fafb1ab8..62b666051 100644 --- a/pyface/ui/qt4/workbench/editor.py +++ b/pyface/ui/qt4/workbench/editor.py @@ -12,6 +12,7 @@ # Local imports. +from traits.api import Event, Bool from pyface.workbench.i_editor import MEditor @@ -22,6 +23,10 @@ class Editor(MEditor): """ + # Traits for showing spinner + _loading = Event(Bool) + _loading_on_open = Bool(False) + ########################################################################### # 'IWorkbenchPart' interface. ########################################################################### @@ -29,7 +34,7 @@ class Editor(MEditor): def create_control(self, parent): """ Create the toolkit-specific control that represents the part. """ - from pyface.qt import QtGui + from pyface.qt import QtCore, QtGui # By default we create a yellow panel! control = QtGui.QWidget(parent) diff --git a/pyface/ui/qt4/workbench/workbench_window_layout.py b/pyface/ui/qt4/workbench/workbench_window_layout.py index 5548dfad8..009c3de68 100755 --- a/pyface/ui/qt4/workbench/workbench_window_layout.py +++ b/pyface/ui/qt4/workbench/workbench_window_layout.py @@ -87,6 +87,9 @@ def add_editor(self, editor, title): try: self._qt4_editor_area.addTab(self._qt4_get_editor_control(editor), title) + + if editor._loading_on_open: + self._qt4_editor_tab_spinner(editor, '', True) except Exception: logger.exception('error creating editor control [%s]', editor.id) @@ -310,6 +313,16 @@ def _qt4_editor_focus(self, new): editor.has_focus = control is new or \ (control is not None and new in control.children()) + def _qt4_editor_tab_spinner(self, editor, name, new): + # Do we need to do this verification? + tw, tidx = self._qt4_editor_area._tab_widget(editor.control) + + if new: tw.show_button(tidx) + else: tw.hide_button(tidx) + + if not new and not editor == self.window.active_editor: + self._qt4_editor_area.setTabTextColor(editor.control, QtCore.Qt.red) + def _qt4_view_focus_changed(self, old, new): """ Handle the change of focus for a view. """ @@ -399,6 +412,8 @@ def _qt4_get_editor_control(self, editor): editor.control = editor.create_control(self.window.control) editor.control.setObjectName(editor.id) + editor.on_trait_change(self._qt4_editor_tab_spinner, '_loading') + self.editor_opened = editor def on_name_changed(editor, trait_name, old, new): From 0b38ecacdb9438e19a74d623b726f115387107c0 Mon Sep 17 00:00:00 2001 From: Robert Kern Date: Thu, 10 Nov 2011 15:38:58 +0000 Subject: [PATCH 15/60] ENH: Double-click on an editor tab to change the title. --- pyface/ui/qt4/workbench/split_tab_widget.py | 35 +++++++++++++++++++ .../qt4/workbench/workbench_window_layout.py | 9 +++++ 2 files changed, 44 insertions(+) diff --git a/pyface/ui/qt4/workbench/split_tab_widget.py b/pyface/ui/qt4/workbench/split_tab_widget.py index ee3af3856..a4aa5dd0f 100644 --- a/pyface/ui/qt4/workbench/split_tab_widget.py +++ b/pyface/ui/qt4/workbench/split_tab_widget.py @@ -30,6 +30,8 @@ class SplitTabWidget(QtGui.QSplitter): # The different hotspots of a QTabWidget. An non-negative value is a tab # index and the hotspot is to the left of it. + + tabTextChanged = QtCore.Signal(QtGui.QWidget, unicode) _HS_NONE = -1 _HS_AFTER_LAST_TAB = -2 _HS_NORTH = -3 @@ -845,6 +847,22 @@ def _close_tab(self, index): self._root._close_tab_request(self.widget(index)) +class _IndependentLineEdit(QtGui.QLineEdit): + def __init__(self, *args): + QtGui.QLineEdit.__init__(self, *args) + self.setWindowFlags(QtCore.Qt.FramelessWindowHint) + self.connect(self, QtCore.SIGNAL('editingFinished()'), self.hide) + + def setText(self, text): + QtGui.QLineEdit.setText(self, text) + self.setFocus() + self.selectAll() + self.show() + + def keyPressEvent(self, e): + QtGui.QLineEdit.keyPressEvent(self, e) + if (e.key() == QtCore.Qt.Key_Escape): + self.hide() class _DragableTabBar(QtGui.QTabBar): """ The _DragableTabBar class is a QTabBar that can be dragged around. """ @@ -860,6 +878,14 @@ def __init__(self, root, parent): self._root = root self._drag_state = None + self._title_edit = _IndependentLineEdit("") + self.connect(self._title_edit, QtCore.SIGNAL('returnPressed()'), self._setCurrentTabText) + + def _setCurrentTabText(self): + idx = self.currentIndex() + text = self._title_edit.text() + self.setTabText(idx, text) + self._root.emit(QtCore.SIGNAL('tabTextChanged(QWidget *, QString)'), self.parent().widget(idx), text) def keyPressEvent(self, e): """ Reimplemented to handle traversal across different tab widgets. """ @@ -871,6 +897,15 @@ def keyPressEvent(self, e): else: e.ignore() + def mouseDoubleClickEvent(self, e): + idx = self.currentIndex() + rect = self.tabRect(idx) + rect.adjust(10, 3, -self.tabButton(idx, QtGui.QTabBar.RightSide).width(), -3) + self._title_edit.setFixedSize(rect.size()) + self._title_edit.setGeometry(rect) + self._title_edit.move(self.mapToGlobal(rect.center())) + self._title_edit.setText(self.tabText(idx)[1:]) + def mousePressEvent(self, e): """ Reimplemented to handle mouse press events. """ diff --git a/pyface/ui/qt4/workbench/workbench_window_layout.py b/pyface/ui/qt4/workbench/workbench_window_layout.py index 009c3de68..560fb2cd0 100755 --- a/pyface/ui/qt4/workbench/workbench_window_layout.py +++ b/pyface/ui/qt4/workbench/workbench_window_layout.py @@ -153,6 +153,10 @@ def create_initial_layout(self, parent): editor_area, QtCore.SIGNAL('focusChanged(QWidget *,QWidget *)'), self._qt4_view_focus_changed) + QtCore.QObject.connect(self._qt4_editor_area, + QtCore.SIGNAL('tabTextChanged(QWidget *, QString)'), + self._qt4_editor_title_changed) + editor_area.new_window_request.connect(self._qt4_new_window_request) editor_area.tab_close_request.connect(self._qt4_tab_close_request) editor_area.tab_window_changed.connect(self._qt4_tab_window_changed) @@ -313,6 +317,11 @@ def _qt4_editor_focus(self, new): editor.has_focus = control is new or \ (control is not None and new in control.children()) + def _qt4_editor_title_changed(self, control, title): + """ Handle the title being changed """ + for editor in self.window.editors: + if editor.control == control: editor.name = unicode(title) + def _qt4_editor_tab_spinner(self, editor, name, new): # Do we need to do this verification? tw, tidx = self._qt4_editor_area._tab_widget(editor.control) From a82c8a347ed60607c3843b483fd24da9fdbe0620 Mon Sep 17 00:00:00 2001 From: Robert Kern Date: Thu, 10 Nov 2011 15:41:48 +0000 Subject: [PATCH 16/60] ENH: Tab bar title editing in place --- pyface/ui/qt4/workbench/split_tab_widget.py | 57 +++++++++++---------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/pyface/ui/qt4/workbench/split_tab_widget.py b/pyface/ui/qt4/workbench/split_tab_widget.py index a4aa5dd0f..9f435f9b3 100644 --- a/pyface/ui/qt4/workbench/split_tab_widget.py +++ b/pyface/ui/qt4/workbench/split_tab_widget.py @@ -848,17 +848,6 @@ def _close_tab(self, index): self._root._close_tab_request(self.widget(index)) class _IndependentLineEdit(QtGui.QLineEdit): - def __init__(self, *args): - QtGui.QLineEdit.__init__(self, *args) - self.setWindowFlags(QtCore.Qt.FramelessWindowHint) - self.connect(self, QtCore.SIGNAL('editingFinished()'), self.hide) - - def setText(self, text): - QtGui.QLineEdit.setText(self, text) - self.setFocus() - self.selectAll() - self.show() - def keyPressEvent(self, e): QtGui.QLineEdit.keyPressEvent(self, e) if (e.key() == QtCore.Qt.Key_Escape): @@ -878,14 +867,18 @@ def __init__(self, root, parent): self._root = root self._drag_state = None - self._title_edit = _IndependentLineEdit("") - self.connect(self._title_edit, QtCore.SIGNAL('returnPressed()'), self._setCurrentTabText) - - def _setCurrentTabText(self): - idx = self.currentIndex() - text = self._title_edit.text() - self.setTabText(idx, text) - self._root.emit(QtCore.SIGNAL('tabTextChanged(QWidget *, QString)'), self.parent().widget(idx), text) + # LineEdit to change tab bar title + te = _IndependentLineEdit("", self) + te.hide() + te.connect(te, QtCore.SIGNAL('editingFinished()'), te, QtCore.SLOT('hide()')) + self.connect(te, QtCore.SIGNAL('returnPressed()'), self._setCurrentTabText) + self._title_edit = te + + def resizeEvent(self, e): + # resize edit tab + if self._title_edit.isVisible(): + self._resize_title_edit_to_current_tab() + QtGui.QTabBar.resizeEvent(self, e) def keyPressEvent(self, e): """ Reimplemented to handle traversal across different tab widgets. """ @@ -898,13 +891,12 @@ def keyPressEvent(self, e): e.ignore() def mouseDoubleClickEvent(self, e): - idx = self.currentIndex() - rect = self.tabRect(idx) - rect.adjust(10, 3, -self.tabButton(idx, QtGui.QTabBar.RightSide).width(), -3) - self._title_edit.setFixedSize(rect.size()) - self._title_edit.setGeometry(rect) - self._title_edit.move(self.mapToGlobal(rect.center())) - self._title_edit.setText(self.tabText(idx)[1:]) + self._resize_title_edit_to_current_tab() + te = self._title_edit + te.setText(self.tabText(self.currentIndex())[1:]) + te.setFocus() + te.selectAll() + te.show() def mousePressEvent(self, e): """ Reimplemented to handle mouse press events. """ @@ -979,6 +971,19 @@ def _tab_at(self, pos): return -1 + def _setCurrentTabText(self): + idx = self.currentIndex() + text = self._title_edit.text() + self.setTabText(idx, u'\u25b6'+text) + self._root.emit(QtCore.SIGNAL('tabTextChanged(QWidget *, QString)'), self.parent().widget(idx), text) + + def _resize_title_edit_to_current_tab(self): + idx = self.currentIndex() + tab = QtGui.QStyleOptionTabV3() + self.initStyleOption(tab, idx) + rect = self.style().subElementRect(QtGui.QStyle.SE_TabBarTabText, tab) + self._title_edit.setGeometry(rect.adjusted(0,8,0,-8)) + class _DragState(object): """ The _DragState class handles most of the work when dragging a tab. """ From 779faf1be8a3cdf2c9ac433109f391a1e036d508 Mon Sep 17 00:00:00 2001 From: Robert Kern Date: Wed, 30 Nov 2011 17:45:59 +0000 Subject: [PATCH 17/60] BUG: Use QApplication.instance() instead of qApp because there are some instances under (a possibly buggy) PySide where qApp is None. Guard a PyQt4-specific workaround. --- pyface/ui/qt4/workbench/split_tab_widget.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/pyface/ui/qt4/workbench/split_tab_widget.py b/pyface/ui/qt4/workbench/split_tab_widget.py index c88eeaed5..281600c68 100644 --- a/pyface/ui/qt4/workbench/split_tab_widget.py +++ b/pyface/ui/qt4/workbench/split_tab_widget.py @@ -11,8 +11,7 @@ import sys # Major library imports. -import sip -from pyface.qt import QtCore, QtGui +from pyface.qt import QtCore, QtGui, qt_api class SplitTabWidget(QtGui.QSplitter): @@ -43,7 +42,7 @@ def __init__(self, *args): self.clear() - QtCore.QObject.connect(QtGui.qApp, + QtCore.QObject.connect(QtGui.QApplication.instance(), QtCore.SIGNAL('focusChanged(QWidget *,QWidget *)'), self._focus_changed) @@ -289,8 +288,10 @@ def _focus_changed(self, old, new): # It is possible for the C++ layer of this object to be deleted between # the time when the focus change signal is emitted and time when the # slots are dispatched by the Qt event loop. This may be a bug in PyQt4. - if sip.isdeleted(self): - return + if qt_api == 'pyqt': + import sip + if sip.isdeleted(self): + return if self._repeat_focus_changes: self.emit(QtCore.SIGNAL('focusChanged(QWidget *,QWidget *)'), @@ -430,7 +431,7 @@ def _drop(self, pos, stab_w, stab): if dhs == self._HS_AFTER_LAST_TAB and stab == stab_w.count()-1: return - QtGui.qApp.blockSignals(True) + QtGui.QApplication.instance().blockSignals(True) ticon, ttext, ttextcolor, twidg = self._remove_tab(stab_w, stab) @@ -458,7 +459,7 @@ def _drop(self, pos, stab_w, stab): if stab_w is dtab_w and stab_w.count() == 1: return - QtGui.qApp.blockSignals(True) + QtGui.QApplication.instance().blockSignals(True) # Remove the tab from its current tab widget and create a new one # for it. @@ -487,7 +488,7 @@ def _drop(self, pos, stab_w, stab): if dsplit_w != self: self.tab_window_changed.emit(twidg) - QtGui.qApp.blockSignals(False) + QtGui.QApplication.instance().blockSignals(False) def _horizontal_split(self, spl, idx, hs): """ Returns a tuple of the splitter and index where the new tab widget @@ -568,7 +569,7 @@ def _hotspot(self, pos): miss = (None, self._HS_NONE, None) # Get the bounding rect of the cloned QTbarBar. - top_widget = QtGui.qApp.topLevelAt(global_pos) + top_widget = QtGui.QApplication.instance().topLevelAt(global_pos) if isinstance(top_widget, QtGui.QTabBar): cloned_rect = top_widget.frameGeometry() else: @@ -577,7 +578,7 @@ def _hotspot(self, pos): # Determine which visible SplitTabWidget, if any, is under the cursor # (compensating for the cloned QTabBar that may be rendered over it). split_widget = None - for top_widget in QtGui.qApp.topLevelWidgets(): + for top_widget in QtGui.QApplication.instance().topLevelWidgets(): for split_widget in top_widget.findChildren(SplitTabWidget, None): visible_region = split_widget.visibleRegion() widget_pos = split_widget.mapFromGlobal(global_pos) From 015e9d2be834cfa85a9a26fddb8d79ba87d3151e Mon Sep 17 00:00:00 2001 From: Naveen Michaud-Agrawal Date: Fri, 20 Jan 2012 14:01:54 -0500 Subject: [PATCH 18/60] BF: Reset tab title to foreground color when it receives focus --- pyface/ui/qt4/workbench/workbench_window_layout.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pyface/ui/qt4/workbench/workbench_window_layout.py b/pyface/ui/qt4/workbench/workbench_window_layout.py index 560fb2cd0..1ee4061ba 100755 --- a/pyface/ui/qt4/workbench/workbench_window_layout.py +++ b/pyface/ui/qt4/workbench/workbench_window_layout.py @@ -332,6 +332,12 @@ def _qt4_editor_tab_spinner(self, editor, name, new): if not new and not editor == self.window.active_editor: self._qt4_editor_area.setTabTextColor(editor.control, QtCore.Qt.red) + @on_trait_change('window:active_editor') + def _qt4_active_editor_changed(self, old, new): + """ Handle change of active editor """ + # Reset tab title to foreground color + self._qt4_editor_area.setTabTextColor(new.control) + def _qt4_view_focus_changed(self, old, new): """ Handle the change of focus for a view. """ From 418d33b89a81be0ffc8a2511e81334167023f946 Mon Sep 17 00:00:00 2001 From: Pietro Berkes Date: Fri, 12 Oct 2012 17:25:26 +0100 Subject: [PATCH 19/60] FIX: Workbench editor destruction in PySide. --- pyface/ui/qt4/workbench/editor.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyface/ui/qt4/workbench/editor.py b/pyface/ui/qt4/workbench/editor.py index 62b666051..317003fe2 100644 --- a/pyface/ui/qt4/workbench/editor.py +++ b/pyface/ui/qt4/workbench/editor.py @@ -54,7 +54,10 @@ def destroy_control(self): if self.control is not None: self.control.hide() self.control.close() - self.control.deleteLater() + # Copy the reference: PySide schedules a delete on self.control + # but it is garbage collected before it can do so. + _control = self.control + _control.deleteLater() self.control = None return From 22bdf527af815e2399fc6033ff5a10ce0964d584 Mon Sep 17 00:00:00 2001 From: Pietro Berkes Date: Tue, 23 Oct 2012 21:17:44 +0100 Subject: [PATCH 20/60] Better comment for this fix. --- pyface/ui/qt4/workbench/editor.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/pyface/ui/qt4/workbench/editor.py b/pyface/ui/qt4/workbench/editor.py index 317003fe2..2a0629cc2 100644 --- a/pyface/ui/qt4/workbench/editor.py +++ b/pyface/ui/qt4/workbench/editor.py @@ -52,14 +52,20 @@ def destroy_control(self): """ Destroy the toolkit-specific control that represents the part. """ if self.control is not None: - self.control.hide() - self.control.close() - # Copy the reference: PySide schedules a delete on self.control - # but it is garbage collected before it can do so. + # The `close` method emits a closeEvent event which is listened + # by the workbench window layout, which responds by calling + # destroy_control again. + + # We copy the control locally and set it to None immediately + # to make sure this block of codeis executed exactly once. + _control = self.control - _control.deleteLater() self.control = None + _control.hide() + _control.close() + _control.deleteLater() + return def set_focus(self): From a84aa898c4a472136ca43cfe515f1a7d497124a8 Mon Sep 17 00:00:00 2001 From: Pietro Berkes Date: Wed, 24 Oct 2012 09:52:53 +0200 Subject: [PATCH 21/60] Update pyface/ui/qt4/workbench/editor.py Correct typo. --- pyface/ui/qt4/workbench/editor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyface/ui/qt4/workbench/editor.py b/pyface/ui/qt4/workbench/editor.py index 2a0629cc2..3e0be2556 100644 --- a/pyface/ui/qt4/workbench/editor.py +++ b/pyface/ui/qt4/workbench/editor.py @@ -57,7 +57,7 @@ def destroy_control(self): # destroy_control again. # We copy the control locally and set it to None immediately - # to make sure this block of codeis executed exactly once. + # to make sure this block of code is executed exactly once. _control = self.control self.control = None From 8aafd563a7fcdcfd9ea795dc832846b6c31c2be4 Mon Sep 17 00:00:00 2001 From: Yves Delley Date: Mon, 27 May 2013 00:06:57 +0200 Subject: [PATCH 22/60] replaced deprecated PyProtocols 'implements' by new traits.adaption 'provides' --- pyface/ui/qt4/workbench/workbench_window_layout.py | 2 +- pyface/workbench/editor_manager.py | 6 ++---- pyface/workbench/i_editor.py | 6 ++---- pyface/workbench/i_view.py | 6 ++---- pyface/workbench/i_workbench.py | 2 +- pyface/workbench/i_workbench_part.py | 6 ++---- pyface/workbench/i_workbench_window_layout.py | 6 ++---- pyface/workbench/perspective.py | 6 ++---- pyface/workbench/perspective_item.py | 6 ++---- pyface/workbench/workbench.py | 6 ++---- pyface/workbench/workbench_window.py | 2 +- 11 files changed, 19 insertions(+), 35 deletions(-) diff --git a/pyface/ui/qt4/workbench/workbench_window_layout.py b/pyface/ui/qt4/workbench/workbench_window_layout.py index 532b36b23..361feb90c 100755 --- a/pyface/ui/qt4/workbench/workbench_window_layout.py +++ b/pyface/ui/qt4/workbench/workbench_window_layout.py @@ -57,7 +57,7 @@ class WorkbenchWindowLayout(MWorkbenchWindowLayout): #### Private interface #################################################### - # The widget that implements the editor area. We keep (and use) this + # The widget that provides the editor area. We keep (and use) this # separate reference because we can't always assume that it has been set to # be the main window's central widget. _qt4_editor_area = Instance(SplitTabWidget) diff --git a/pyface/workbench/editor_manager.py b/pyface/workbench/editor_manager.py index 332760833..c5466250d 100755 --- a/pyface/workbench/editor_manager.py +++ b/pyface/workbench/editor_manager.py @@ -4,18 +4,16 @@ import weakref # Enthought library imports. -from traits.api import HasTraits, Instance, implements +from traits.api import HasTraits, Instance, provides # Local imports. from i_editor_manager import IEditorManager from traits_ui_editor import TraitsUIEditor +@provides(IEditorManager) class EditorManager(HasTraits): """ The default editor manager. """ - - implements(IEditorManager) - #### 'IEditorManager' interface ########################################### # The workbench window that the editor manager manages editors for ;^) diff --git a/pyface/workbench/i_editor.py b/pyface/workbench/i_editor.py index 78bb4161d..c66ec2328 100755 --- a/pyface/workbench/i_editor.py +++ b/pyface/workbench/i_editor.py @@ -18,7 +18,7 @@ # Enthought library imports. from traits.api import Any, Bool, Event, VetoableEvent, Vetoable, \ HasTraits, Instance, Interface -from traits.api import implements +from traits.api import provides # Local imports. from i_workbench_part import IWorkbenchPart, MWorkbenchPart @@ -59,11 +59,9 @@ def close(self): """ +@provides(IEditor) class MEditor(MWorkbenchPart): """ Mixin containing common code for toolkit-specific implementations. """ - - implements(IEditor) - #### 'IEditor' interface ################################################## # The optional command stack. diff --git a/pyface/workbench/i_view.py b/pyface/workbench/i_view.py index 875413526..098cde010 100755 --- a/pyface/workbench/i_view.py +++ b/pyface/workbench/i_view.py @@ -19,7 +19,7 @@ # Enthought library imports. from pyface.api import ImageResource from traits.api import Bool, Enum, Float, Instance, List, Str, \ - implements + provides from traits.util.camel_case import camel_case_to_words # Local imports. @@ -69,11 +69,9 @@ def show(self): """ +@provides(IView) class MView(MWorkbenchPart, PerspectiveItem): """ Mixin containing common code for toolkit-specific implementations. """ - - implements(IView) - #### 'IView' interface #################################################### # Is the view busy? (i.e., should the busy cursor (often an hourglass) be diff --git a/pyface/workbench/i_workbench.py b/pyface/workbench/i_workbench.py index 0449438eb..aba9cc029 100644 --- a/pyface/workbench/i_workbench.py +++ b/pyface/workbench/i_workbench.py @@ -3,7 +3,7 @@ # Enthought library imports. from traits.api import Event, Instance, Interface, List, Str -from traits.api import VetoableEvent, implements +from traits.api import VetoableEvent, provides # Local imports. from user_perspective_manager import UserPerspectiveManager diff --git a/pyface/workbench/i_workbench_part.py b/pyface/workbench/i_workbench_part.py index 7ceca23c5..1382071e8 100644 --- a/pyface/workbench/i_workbench_part.py +++ b/pyface/workbench/i_workbench_part.py @@ -16,7 +16,7 @@ # Enthought library imports. from traits.api import Any, Bool, HasTraits, Instance, Interface -from traits.api import List, Str, Unicode, implements +from traits.api import List, Str, Unicode, provides class IWorkbenchPart(Interface): @@ -76,11 +76,9 @@ def set_focus(self): """ +@provides(IWorkbenchPart) class MWorkbenchPart(HasTraits): """ Mixin containing common code for toolkit-specific implementations. """ - - implements(IWorkbenchPart) - #### 'IWorkbenchPart' interface ########################################### # The toolkit-specific control that represents the part. diff --git a/pyface/workbench/i_workbench_window_layout.py b/pyface/workbench/i_workbench_window_layout.py index c922a878a..9ae6ee367 100755 --- a/pyface/workbench/i_workbench_window_layout.py +++ b/pyface/workbench/i_workbench_window_layout.py @@ -16,7 +16,7 @@ # Enthought library imports. from traits.api import Event, HasTraits, Instance, Interface, Str -from traits.api import implements +from traits.api import provides # Local imports. from i_editor import IEditor @@ -211,11 +211,9 @@ def set_toolkit_memento(self, memento): """ +@provides(IWorkbenchWindowLayout) class MWorkbenchWindowLayout(HasTraits): """ Mixin containing common code for toolkit-specific implementations. """ - - implements(IWorkbenchWindowLayout) - #### 'IWorkbenchWindowLayout' interface ################################### # The Id of the editor area. diff --git a/pyface/workbench/perspective.py b/pyface/workbench/perspective.py index f8884dc9c..b12756d97 100755 --- a/pyface/workbench/perspective.py +++ b/pyface/workbench/perspective.py @@ -5,7 +5,7 @@ import logging # Enthought library imports. -from traits.api import Bool, HasTraits, List, Str, Tuple, implements +from traits.api import Bool, HasTraits, List, Str, Tuple, provides # Local imports. from i_perspective import IPerspective @@ -16,11 +16,9 @@ logger = logging.getLogger(__name__) +@provides(IPerspective) class Perspective(HasTraits): """ The default perspective. """ - - implements(IPerspective) - # The ID of the default perspective. DEFAULT_ID = 'pyface.workbench.default' diff --git a/pyface/workbench/perspective_item.py b/pyface/workbench/perspective_item.py index c47ff3d40..18eee5c30 100755 --- a/pyface/workbench/perspective_item.py +++ b/pyface/workbench/perspective_item.py @@ -2,17 +2,15 @@ # Enthought library imports. -from traits.api import Enum, Float, HasTraits, Str, implements +from traits.api import Enum, Float, HasTraits, Str, provides # Local imports. from i_perspective_item import IPerspectiveItem +@provides(IPerspectiveItem) class PerspectiveItem(HasTraits): """ An item in a Perspective contents list. """ - - implements(IPerspectiveItem) - # The Id of the view to display in the perspective. id = Str diff --git a/pyface/workbench/workbench.py b/pyface/workbench/workbench.py index df32d2710..e0a3dbe15 100755 --- a/pyface/workbench/workbench.py +++ b/pyface/workbench/workbench.py @@ -9,7 +9,7 @@ # Enthought library imports. from traits.etsconfig.api import ETSConfig from pyface.api import NO -from traits.api import Bool, Callable, Event, HasTraits, implements +from traits.api import Bool, Callable, Event, HasTraits, provides from traits.api import Instance, List, Unicode, Vetoable from traits.api import VetoableEvent @@ -25,6 +25,7 @@ logger = logging.getLogger(__name__) +@provides(IWorkbench) class Workbench(HasTraits): """ A workbench. @@ -32,9 +33,6 @@ class Workbench(HasTraits): any number of workbench windows. """ - - implements(IWorkbench) - #### 'IWorkbench' interface ############################################### # The active workbench window (the last one to get focus). diff --git a/pyface/workbench/workbench_window.py b/pyface/workbench/workbench_window.py index 7f7e50c53..556c7cf83 100755 --- a/pyface/workbench/workbench_window.py +++ b/pyface/workbench/workbench_window.py @@ -8,7 +8,7 @@ from pyface.api import ApplicationWindow, GUI from traits.api import Callable, Constant, Delegate, Event, Instance from traits.api import List, Str, Tuple, Unicode, Vetoable -from traits.api import implements, on_trait_change +from traits.api import provides, on_trait_change # Local imports. from i_editor import IEditor From d24d3e4d767b2884d19b75f4c25007654f77f801 Mon Sep 17 00:00:00 2001 From: Yves Delley Date: Tue, 28 May 2013 10:42:03 +0200 Subject: [PATCH 23/60] fixed ETS import convention --- pyface/workbench/i_view.py | 3 +-- pyface/workbench/i_workbench.py | 2 +- pyface/workbench/i_workbench_part.py | 2 +- pyface/workbench/perspective.py | 2 +- pyface/workbench/perspective_item.py | 2 +- pyface/workbench/workbench_window.py | 2 +- 6 files changed, 6 insertions(+), 7 deletions(-) diff --git a/pyface/workbench/i_view.py b/pyface/workbench/i_view.py index 098cde010..2e3504525 100755 --- a/pyface/workbench/i_view.py +++ b/pyface/workbench/i_view.py @@ -18,8 +18,7 @@ # Enthought library imports. from pyface.api import ImageResource -from traits.api import Bool, Enum, Float, Instance, List, Str, \ - provides +from traits.api import Bool, Enum, Float, Instance, List, provides, Str from traits.util.camel_case import camel_case_to_words # Local imports. diff --git a/pyface/workbench/i_workbench.py b/pyface/workbench/i_workbench.py index aba9cc029..2534d6830 100644 --- a/pyface/workbench/i_workbench.py +++ b/pyface/workbench/i_workbench.py @@ -3,7 +3,7 @@ # Enthought library imports. from traits.api import Event, Instance, Interface, List, Str -from traits.api import VetoableEvent, provides +from traits.api import provides, VetoableEvent # Local imports. from user_perspective_manager import UserPerspectiveManager diff --git a/pyface/workbench/i_workbench_part.py b/pyface/workbench/i_workbench_part.py index 1382071e8..c4c90bc2b 100644 --- a/pyface/workbench/i_workbench_part.py +++ b/pyface/workbench/i_workbench_part.py @@ -16,7 +16,7 @@ # Enthought library imports. from traits.api import Any, Bool, HasTraits, Instance, Interface -from traits.api import List, Str, Unicode, provides +from traits.api import List, provides, Str, Unicode class IWorkbenchPart(Interface): diff --git a/pyface/workbench/perspective.py b/pyface/workbench/perspective.py index b12756d97..5fc2496b2 100755 --- a/pyface/workbench/perspective.py +++ b/pyface/workbench/perspective.py @@ -5,7 +5,7 @@ import logging # Enthought library imports. -from traits.api import Bool, HasTraits, List, Str, Tuple, provides +from traits.api import Bool, HasTraits, List, provides, Str, Tuple # Local imports. from i_perspective import IPerspective diff --git a/pyface/workbench/perspective_item.py b/pyface/workbench/perspective_item.py index 18eee5c30..644bcd855 100755 --- a/pyface/workbench/perspective_item.py +++ b/pyface/workbench/perspective_item.py @@ -2,7 +2,7 @@ # Enthought library imports. -from traits.api import Enum, Float, HasTraits, Str, provides +from traits.api import Enum, Float, HasTraits, provides, Str # Local imports. from i_perspective_item import IPerspectiveItem diff --git a/pyface/workbench/workbench_window.py b/pyface/workbench/workbench_window.py index 556c7cf83..ef84cd366 100755 --- a/pyface/workbench/workbench_window.py +++ b/pyface/workbench/workbench_window.py @@ -8,7 +8,7 @@ from pyface.api import ApplicationWindow, GUI from traits.api import Callable, Constant, Delegate, Event, Instance from traits.api import List, Str, Tuple, Unicode, Vetoable -from traits.api import provides, on_trait_change +from traits.api import on_trait_change, provides # Local imports. from i_editor import IEditor From b7bc0da6ca841731bfcdb8274c96db764695f08e Mon Sep 17 00:00:00 2001 From: Corran Webster Date: Mon, 17 Aug 2015 19:22:32 +0100 Subject: [PATCH 24/60] Hunt down all the relative imports. --- .../qt4/workbench/workbench_window_layout.py | 2 +- .../wx/workbench/workbench_window_layout.py | 6 ++-- pyface/workbench/action/api.py | 8 +++-- .../action/delete_user_perspective_action.py | 3 +- pyface/workbench/action/menu_bar_manager.py | 2 +- .../action/new_user_perspective_action.py | 5 ++-- .../action/perspective_menu_manager.py | 14 ++++----- .../action/rename_user_perspective_action.py | 5 ++-- .../action/reset_active_perspective_action.py | 2 +- .../action/reset_all_perspectives_action.py | 2 +- .../action/save_as_user_perspective_action.py | 4 +-- .../action/set_active_perspective_action.py | 2 +- pyface/workbench/action/setattr_action.py | 2 +- pyface/workbench/action/show_view_action.py | 4 +-- .../action/toggle_view_visibility_action.py | 2 +- pyface/workbench/action/tool_bar_manager.py | 2 +- .../action/user_perspective_action.py | 2 +- pyface/workbench/action/view_menu_manager.py | 6 ++-- pyface/workbench/api.py | 30 ++++++++++--------- pyface/workbench/debug/api.py | 5 +++- pyface/workbench/editor_manager.py | 4 +-- pyface/workbench/i_editor.py | 2 +- pyface/workbench/i_perspective.py | 2 +- pyface/workbench/i_view.py | 6 ++-- pyface/workbench/i_workbench.py | 6 ++-- pyface/workbench/i_workbench_window_layout.py | 4 +-- pyface/workbench/perspective.py | 4 +-- pyface/workbench/perspective_item.py | 2 +- pyface/workbench/traits_ui_editor.py | 2 +- pyface/workbench/traits_ui_view.py | 2 +- pyface/workbench/window_event.py | 3 +- pyface/workbench/workbench.py | 10 +++---- pyface/workbench/workbench_window.py | 16 +++++----- 33 files changed, 87 insertions(+), 84 deletions(-) diff --git a/pyface/ui/qt4/workbench/workbench_window_layout.py b/pyface/ui/qt4/workbench/workbench_window_layout.py index 361feb90c..a8e2780b7 100755 --- a/pyface/ui/qt4/workbench/workbench_window_layout.py +++ b/pyface/ui/qt4/workbench/workbench_window_layout.py @@ -21,10 +21,10 @@ from traits.api import Instance, on_trait_change # Local imports. -from split_tab_widget import SplitTabWidget from pyface.message_dialog import error from pyface.workbench.i_workbench_window_layout import \ MWorkbenchWindowLayout +from .split_tab_widget import SplitTabWidget # Logging. diff --git a/pyface/ui/wx/workbench/workbench_window_layout.py b/pyface/ui/wx/workbench/workbench_window_layout.py index aff5a5f14..6f2bc6a42 100644 --- a/pyface/ui/wx/workbench/workbench_window_layout.py +++ b/pyface/ui/wx/workbench/workbench_window_layout.py @@ -37,9 +37,9 @@ MWorkbenchWindowLayout # Local imports. -from editor_set_structure_handler import EditorSetStructureHandler -from view_set_structure_handler import ViewSetStructureHandler -from workbench_dock_window import WorkbenchDockWindow +from .editor_set_structure_handler import EditorSetStructureHandler +from .view_set_structure_handler import ViewSetStructureHandler +from .workbench_dock_window import WorkbenchDockWindow # Logging. diff --git a/pyface/workbench/action/api.py b/pyface/workbench/action/api.py index c97585fa6..b7e81ca59 100644 --- a/pyface/workbench/action/api.py +++ b/pyface/workbench/action/api.py @@ -1,3 +1,5 @@ -from menu_bar_manager import MenuBarManager -from tool_bar_manager import ToolBarManager -from view_menu_manager import ViewMenuManager +from __future__ import absolute_import + +from .menu_bar_manager import MenuBarManager +from .tool_bar_manager import ToolBarManager +from .view_menu_manager import ViewMenuManager diff --git a/pyface/workbench/action/delete_user_perspective_action.py b/pyface/workbench/action/delete_user_perspective_action.py index 714430e12..d9638a389 100644 --- a/pyface/workbench/action/delete_user_perspective_action.py +++ b/pyface/workbench/action/delete_user_perspective_action.py @@ -13,7 +13,7 @@ from pyface.api import YES # Local imports. -from user_perspective_action import UserPerspectiveAction +from .user_perspective_action import UserPerspectiveAction class DeleteUserPerspectiveAction(UserPerspectiveAction): @@ -77,4 +77,3 @@ def _get_next_perspective(self, window): return window.perspectives[index] #### EOF ##################################################################### - diff --git a/pyface/workbench/action/menu_bar_manager.py b/pyface/workbench/action/menu_bar_manager.py index d737f40a9..312662c58 100644 --- a/pyface/workbench/action/menu_bar_manager.py +++ b/pyface/workbench/action/menu_bar_manager.py @@ -6,7 +6,7 @@ from traits.api import Instance # Local imports. -from action_controller import ActionController +from .action_controller import ActionController class MenuBarManager(BaseMenuBarManager): diff --git a/pyface/workbench/action/new_user_perspective_action.py b/pyface/workbench/action/new_user_perspective_action.py index ac45b8d18..aeb1af2c3 100644 --- a/pyface/workbench/action/new_user_perspective_action.py +++ b/pyface/workbench/action/new_user_perspective_action.py @@ -10,8 +10,8 @@ # Local imports. -from user_perspective_name import UserPerspectiveName -from workbench_action import WorkbenchAction +from .user_perspective_name import UserPerspectiveName +from .workbench_action import WorkbenchAction class NewUserPerspectiveAction(WorkbenchAction): @@ -52,4 +52,3 @@ def perform(self, event): return #### EOF ##################################################################### - diff --git a/pyface/workbench/action/perspective_menu_manager.py b/pyface/workbench/action/perspective_menu_manager.py index 665033261..a7ec36b64 100644 --- a/pyface/workbench/action/perspective_menu_manager.py +++ b/pyface/workbench/action/perspective_menu_manager.py @@ -6,13 +6,13 @@ from traits.api import Instance, List, on_trait_change # Local imports. -from delete_user_perspective_action import DeleteUserPerspectiveAction -from new_user_perspective_action import NewUserPerspectiveAction -from rename_user_perspective_action import RenameUserPerspectiveAction -from reset_all_perspectives_action import ResetAllPerspectivesAction -from reset_active_perspective_action import ResetActivePerspectiveAction -from save_as_user_perspective_action import SaveAsUserPerspectiveAction -from set_active_perspective_action import SetActivePerspectiveAction +from .delete_user_perspective_action import DeleteUserPerspectiveAction +from .new_user_perspective_action import NewUserPerspectiveAction +from .rename_user_perspective_action import RenameUserPerspectiveAction +from .reset_all_perspectives_action import ResetAllPerspectivesAction +from .reset_active_perspective_action import ResetActivePerspectiveAction +from .save_as_user_perspective_action import SaveAsUserPerspectiveAction +from .set_active_perspective_action import SetActivePerspectiveAction class PerspectiveMenuManager(MenuManager): diff --git a/pyface/workbench/action/rename_user_perspective_action.py b/pyface/workbench/action/rename_user_perspective_action.py index bae2fc17d..7d2a645b2 100644 --- a/pyface/workbench/action/rename_user_perspective_action.py +++ b/pyface/workbench/action/rename_user_perspective_action.py @@ -10,8 +10,8 @@ # Local imports. -from user_perspective_action import UserPerspectiveAction -from user_perspective_name import UserPerspectiveName +from .user_perspective_action import UserPerspectiveAction +from .user_perspective_name import UserPerspectiveName class RenameUserPerspectiveAction(UserPerspectiveAction): @@ -43,4 +43,3 @@ def perform( self, event): return #### EOF ##################################################################### - diff --git a/pyface/workbench/action/reset_active_perspective_action.py b/pyface/workbench/action/reset_active_perspective_action.py index 3c7aad68b..824daa6ac 100644 --- a/pyface/workbench/action/reset_active_perspective_action.py +++ b/pyface/workbench/action/reset_active_perspective_action.py @@ -5,7 +5,7 @@ from pyface.api import YES # Local imports. -from workbench_action import WorkbenchAction +from .workbench_action import WorkbenchAction # The message used when confirming the action. diff --git a/pyface/workbench/action/reset_all_perspectives_action.py b/pyface/workbench/action/reset_all_perspectives_action.py index 02e9b7ebd..1cfc99a17 100644 --- a/pyface/workbench/action/reset_all_perspectives_action.py +++ b/pyface/workbench/action/reset_all_perspectives_action.py @@ -5,7 +5,7 @@ from pyface.api import YES # Local imports. -from workbench_action import WorkbenchAction +from .workbench_action import WorkbenchAction # The message used when confirming the action. diff --git a/pyface/workbench/action/save_as_user_perspective_action.py b/pyface/workbench/action/save_as_user_perspective_action.py index 8cf5c7a55..bba4e7c53 100644 --- a/pyface/workbench/action/save_as_user_perspective_action.py +++ b/pyface/workbench/action/save_as_user_perspective_action.py @@ -10,8 +10,8 @@ # Local imports. -from user_perspective_name import UserPerspectiveName -from workbench_action import WorkbenchAction +from .user_perspective_name import UserPerspectiveName +from .workbench_action import WorkbenchAction class SaveAsUserPerspectiveAction(WorkbenchAction): diff --git a/pyface/workbench/action/set_active_perspective_action.py b/pyface/workbench/action/set_active_perspective_action.py index db3b1df11..8fa6309d8 100644 --- a/pyface/workbench/action/set_active_perspective_action.py +++ b/pyface/workbench/action/set_active_perspective_action.py @@ -6,7 +6,7 @@ from traits.api import Delegate, Instance, on_trait_change # Local imports. -from workbench_action import WorkbenchAction +from .workbench_action import WorkbenchAction class SetActivePerspectiveAction(WorkbenchAction): diff --git a/pyface/workbench/action/setattr_action.py b/pyface/workbench/action/setattr_action.py index 8d5ba814b..f88a395a9 100644 --- a/pyface/workbench/action/setattr_action.py +++ b/pyface/workbench/action/setattr_action.py @@ -5,7 +5,7 @@ from traits.api import Any, Str # Local imports. -from workbench_action import WorkbenchAction +from .workbench_action import WorkbenchAction class SetattrAction(WorkbenchAction): diff --git a/pyface/workbench/action/show_view_action.py b/pyface/workbench/action/show_view_action.py index 65691f1d4..ddd996150 100644 --- a/pyface/workbench/action/show_view_action.py +++ b/pyface/workbench/action/show_view_action.py @@ -2,8 +2,8 @@ # Local imports. -from view_chooser import ViewChooser -from workbench_action import WorkbenchAction +from .view_chooser import ViewChooser +from .workbench_action import WorkbenchAction class ShowViewAction(WorkbenchAction): diff --git a/pyface/workbench/action/toggle_view_visibility_action.py b/pyface/workbench/action/toggle_view_visibility_action.py index 5baa45f79..ef1f26f1b 100644 --- a/pyface/workbench/action/toggle_view_visibility_action.py +++ b/pyface/workbench/action/toggle_view_visibility_action.py @@ -6,7 +6,7 @@ from traits.api import Delegate, Instance # Local imports. -from workbench_action import WorkbenchAction +from .workbench_action import WorkbenchAction class ToggleViewVisibilityAction(WorkbenchAction): diff --git a/pyface/workbench/action/tool_bar_manager.py b/pyface/workbench/action/tool_bar_manager.py index fe3536cbc..21f1ea34a 100644 --- a/pyface/workbench/action/tool_bar_manager.py +++ b/pyface/workbench/action/tool_bar_manager.py @@ -6,7 +6,7 @@ from traits.api import Instance # Local imports. -from action_controller import ActionController +from .action_controller import ActionController class ToolBarManager(pyface.ToolBarManager): diff --git a/pyface/workbench/action/user_perspective_action.py b/pyface/workbench/action/user_perspective_action.py index f554677dd..84e4eed1b 100644 --- a/pyface/workbench/action/user_perspective_action.py +++ b/pyface/workbench/action/user_perspective_action.py @@ -13,7 +13,7 @@ from traits.api import on_trait_change # Local imports. -from workbench_action import WorkbenchAction +from .workbench_action import WorkbenchAction class UserPerspectiveAction(WorkbenchAction): diff --git a/pyface/workbench/action/view_menu_manager.py b/pyface/workbench/action/view_menu_manager.py index 01420f305..9ac8ad156 100644 --- a/pyface/workbench/action/view_menu_manager.py +++ b/pyface/workbench/action/view_menu_manager.py @@ -10,9 +10,9 @@ from traits.api import on_trait_change # Local imports. -from perspective_menu_manager import PerspectiveMenuManager -from show_view_action import ShowViewAction -from toggle_view_visibility_action import ToggleViewVisibilityAction +from .perspective_menu_manager import PerspectiveMenuManager +from .show_view_action import ShowViewAction +from .toggle_view_visibility_action import ToggleViewVisibilityAction # Logging. diff --git a/pyface/workbench/api.py b/pyface/workbench/api.py index 496cfb467..4763b2ca1 100755 --- a/pyface/workbench/api.py +++ b/pyface/workbench/api.py @@ -1,20 +1,22 @@ -from i_editor import IEditor -from editor import Editor +from __future__ import absolute_import -from i_editor_manager import IEditorManager -from editor_manager import EditorManager +from .i_editor import IEditor +from .editor import Editor -from i_perspective import IPerspective -from perspective import Perspective -from perspective_item import PerspectiveItem +from .i_editor_manager import IEditorManager +from .editor_manager import EditorManager -from i_view import IView -from view import View +from .i_perspective import IPerspective +from .perspective import Perspective +from .perspective_item import PerspectiveItem -from i_workbench import IWorkbench -from workbench import Workbench +from .i_view import IView +from .view import View -from workbench_window import WorkbenchWindow +from .i_workbench import IWorkbench +from .workbench import Workbench -from traits_ui_editor import TraitsUIEditor -from traits_ui_view import TraitsUIView +from .workbench_window import WorkbenchWindow + +from .traits_ui_editor import TraitsUIEditor +from .traits_ui_view import TraitsUIView diff --git a/pyface/workbench/debug/api.py b/pyface/workbench/debug/api.py index 20d5eb938..6f51642a8 100644 --- a/pyface/workbench/debug/api.py +++ b/pyface/workbench/debug/api.py @@ -1 +1,4 @@ -from debug_view import DebugView +from __future__ import absolute_import + + +from .debug_view import DebugView diff --git a/pyface/workbench/editor_manager.py b/pyface/workbench/editor_manager.py index c5466250d..0e757afcf 100755 --- a/pyface/workbench/editor_manager.py +++ b/pyface/workbench/editor_manager.py @@ -7,8 +7,8 @@ from traits.api import HasTraits, Instance, provides # Local imports. -from i_editor_manager import IEditorManager -from traits_ui_editor import TraitsUIEditor +from .i_editor_manager import IEditorManager +from .traits_ui_editor import TraitsUIEditor @provides(IEditorManager) diff --git a/pyface/workbench/i_editor.py b/pyface/workbench/i_editor.py index c66ec2328..f947059b7 100755 --- a/pyface/workbench/i_editor.py +++ b/pyface/workbench/i_editor.py @@ -21,7 +21,7 @@ from traits.api import provides # Local imports. -from i_workbench_part import IWorkbenchPart, MWorkbenchPart +from .i_workbench_part import IWorkbenchPart, MWorkbenchPart class IEditor(IWorkbenchPart): diff --git a/pyface/workbench/i_perspective.py b/pyface/workbench/i_perspective.py index c6c7a9f7a..7e0533181 100755 --- a/pyface/workbench/i_perspective.py +++ b/pyface/workbench/i_perspective.py @@ -5,7 +5,7 @@ from traits.api import Bool, Interface, List, Str, Tuple # Local imports. -from perspective_item import PerspectiveItem +from .perspective_item import PerspectiveItem class IPerspective(Interface): diff --git a/pyface/workbench/i_view.py b/pyface/workbench/i_view.py index 2e3504525..f1c1a906f 100755 --- a/pyface/workbench/i_view.py +++ b/pyface/workbench/i_view.py @@ -22,9 +22,9 @@ from traits.util.camel_case import camel_case_to_words # Local imports. -from i_perspective_item import IPerspectiveItem -from i_workbench_part import IWorkbenchPart, MWorkbenchPart -from perspective_item import PerspectiveItem +from .i_perspective_item import IPerspectiveItem +from .i_workbench_part import IWorkbenchPart, MWorkbenchPart +from .perspective_item import PerspectiveItem # Logging. logger = logging.getLogger(__name__) diff --git a/pyface/workbench/i_workbench.py b/pyface/workbench/i_workbench.py index 2534d6830..593d025e0 100644 --- a/pyface/workbench/i_workbench.py +++ b/pyface/workbench/i_workbench.py @@ -6,9 +6,9 @@ from traits.api import provides, VetoableEvent # Local imports. -from user_perspective_manager import UserPerspectiveManager -from window_event import WindowEvent, VetoableWindowEvent -from workbench_window import WorkbenchWindow +from .user_perspective_manager import UserPerspectiveManager +from .window_event import WindowEvent, VetoableWindowEvent +from .workbench_window import WorkbenchWindow class IWorkbench(Interface): diff --git a/pyface/workbench/i_workbench_window_layout.py b/pyface/workbench/i_workbench_window_layout.py index 9ae6ee367..17d36f9e3 100755 --- a/pyface/workbench/i_workbench_window_layout.py +++ b/pyface/workbench/i_workbench_window_layout.py @@ -19,8 +19,8 @@ from traits.api import provides # Local imports. -from i_editor import IEditor -from i_view import IView +from .i_editor import IEditor +from .i_view import IView class IWorkbenchWindowLayout(Interface): diff --git a/pyface/workbench/perspective.py b/pyface/workbench/perspective.py index 5fc2496b2..571495622 100755 --- a/pyface/workbench/perspective.py +++ b/pyface/workbench/perspective.py @@ -8,8 +8,8 @@ from traits.api import Bool, HasTraits, List, provides, Str, Tuple # Local imports. -from i_perspective import IPerspective -from perspective_item import PerspectiveItem +from .i_perspective import IPerspective +from .perspective_item import PerspectiveItem # Logging. diff --git a/pyface/workbench/perspective_item.py b/pyface/workbench/perspective_item.py index 644bcd855..8a3709eed 100755 --- a/pyface/workbench/perspective_item.py +++ b/pyface/workbench/perspective_item.py @@ -5,7 +5,7 @@ from traits.api import Enum, Float, HasTraits, provides, Str # Local imports. -from i_perspective_item import IPerspectiveItem +from .i_perspective_item import IPerspectiveItem @provides(IPerspectiveItem) diff --git a/pyface/workbench/traits_ui_editor.py b/pyface/workbench/traits_ui_editor.py index 53117a68d..9c56ac620 100644 --- a/pyface/workbench/traits_ui_editor.py +++ b/pyface/workbench/traits_ui_editor.py @@ -9,7 +9,7 @@ from traitsui.api import UI # Local imports. -from editor import Editor +from .editor import Editor # Logging. diff --git a/pyface/workbench/traits_ui_view.py b/pyface/workbench/traits_ui_view.py index 81754d57f..a5761e3d6 100644 --- a/pyface/workbench/traits_ui_view.py +++ b/pyface/workbench/traits_ui_view.py @@ -9,7 +9,7 @@ from traitsui.api import UI # Local imports. -from view import View +from .view import View # Logging. diff --git a/pyface/workbench/window_event.py b/pyface/workbench/window_event.py index 8ecbab864..dee7fec90 100644 --- a/pyface/workbench/window_event.py +++ b/pyface/workbench/window_event.py @@ -5,7 +5,7 @@ from traits.api import HasTraits, Instance, Vetoable # Local imports. -from workbench_window import WorkbenchWindow +from .workbench_window import WorkbenchWindow class WindowEvent(HasTraits): @@ -23,4 +23,3 @@ class VetoableWindowEvent(WindowEvent, Vetoable): pass #### EOF ###################################################################### - diff --git a/pyface/workbench/workbench.py b/pyface/workbench/workbench.py index e0a3dbe15..dbacc7ff6 100755 --- a/pyface/workbench/workbench.py +++ b/pyface/workbench/workbench.py @@ -14,11 +14,11 @@ from traits.api import VetoableEvent # Local imports. -from i_editor_manager import IEditorManager -from i_workbench import IWorkbench -from user_perspective_manager import UserPerspectiveManager -from workbench_window import WorkbenchWindow -from window_event import WindowEvent, VetoableWindowEvent +from .i_editor_manager import IEditorManager +from .i_workbench import IWorkbench +from .user_perspective_manager import UserPerspectiveManager +from .workbench_window import WorkbenchWindow +from .window_event import WindowEvent, VetoableWindowEvent # Logging. diff --git a/pyface/workbench/workbench_window.py b/pyface/workbench/workbench_window.py index ef84cd366..d7b9a6d64 100755 --- a/pyface/workbench/workbench_window.py +++ b/pyface/workbench/workbench_window.py @@ -11,14 +11,14 @@ from traits.api import on_trait_change, provides # Local imports. -from i_editor import IEditor -from i_editor_manager import IEditorManager -from i_perspective import IPerspective -from i_view import IView -from i_workbench_part import IWorkbenchPart -from perspective import Perspective -from workbench_window_layout import WorkbenchWindowLayout -from workbench_window_memento import WorkbenchWindowMemento +from .i_editor import IEditor +from .i_editor_manager import IEditorManager +from .i_perspective import IPerspective +from .i_view import IView +from .i_workbench_part import IWorkbenchPart +from .perspective import Perspective +from .workbench_window_layout import WorkbenchWindowLayout +from .workbench_window_memento import WorkbenchWindowMemento # Logging. From f16d2ea576a111fdc7c9808bb936ec1d75b1d445 Mon Sep 17 00:00:00 2001 From: Prabhu Ramachandran Date: Thu, 20 Aug 2015 16:51:21 +0530 Subject: [PATCH 25/60] A few more fixes. --- pyface/ui/qt4/workbench/split_tab_widget.py | 3 +++ pyface/workbench/action/perspective_menu_manager.py | 2 +- pyface/workbench/action/view_menu_manager.py | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/pyface/ui/qt4/workbench/split_tab_widget.py b/pyface/ui/qt4/workbench/split_tab_widget.py index b6c1d1eaf..aa305190d 100644 --- a/pyface/ui/qt4/workbench/split_tab_widget.py +++ b/pyface/ui/qt4/workbench/split_tab_widget.py @@ -10,6 +10,9 @@ # Standard library imports. import sys +if sys.version_info[0] > 2: + unicode = str + # Major library imports. from pyface.qt import QtCore, QtGui, qt_api diff --git a/pyface/workbench/action/perspective_menu_manager.py b/pyface/workbench/action/perspective_menu_manager.py index a7ec36b64..a84433018 100644 --- a/pyface/workbench/action/perspective_menu_manager.py +++ b/pyface/workbench/action/perspective_menu_manager.py @@ -96,7 +96,7 @@ def _create_perspective_group(self, window): # fixme: Not sure if alphabetic sorting is appropriate in all cases, # but it will do for now! perspectives = window.perspectives[:] - perspectives.sort(lambda x, y: cmp(x.name, y.name)) + perspectives.sort(key=lambda x:x.name) # For each perspective, create an action that sets the active # perspective to it. diff --git a/pyface/workbench/action/view_menu_manager.py b/pyface/workbench/action/view_menu_manager.py index 9ac8ad156..622ee0240 100644 --- a/pyface/workbench/action/view_menu_manager.py +++ b/pyface/workbench/action/view_menu_manager.py @@ -129,7 +129,7 @@ def _initialize_view_group(self, window, group): """ Initializes a group containing the view 'togglers'. """ views = window.views[:] - views.sort(None, lambda view: view.name) + views.sort(key=lambda view: view.name) for view in views: # fixme: It seems a little smelly to be reaching in to the window From f5bae5aa3923407705af91a412a8a241606e2d25 Mon Sep 17 00:00:00 2001 From: Prabhu Ramachandran Date: Thu, 20 Aug 2015 17:41:13 +0530 Subject: [PATCH 26/60] BUG: Pickle files should use bytes. --- pyface/workbench/workbench.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyface/workbench/workbench.py b/pyface/workbench/workbench.py index dbacc7ff6..f4aff4a24 100755 --- a/pyface/workbench/workbench.py +++ b/pyface/workbench/workbench.py @@ -315,7 +315,7 @@ def _restore_window_layout(self, window): # If the memento class itself has been modified then there # is a chance that the unpickle will fail. If so then we just # carry on as if there was no memento! - f = file(filename, 'r') + f = open(filename, 'rb') memento = cPickle.load(f) f.close() @@ -334,7 +334,7 @@ def _save_window_layout(self, window): """ Save the window layout. """ # Save the window layout. - f = file(os.path.join(self.state_location, 'window_memento'), 'w') + f = open(os.path.join(self.state_location, 'window_memento'), 'wb') cPickle.dump(window.get_memento(), f) f.close() From 4056d8bce1e9ab4bd9d40054f83300833d18bf90 Mon Sep 17 00:00:00 2001 From: Corran Webster Date: Thu, 20 Aug 2015 21:47:44 +0100 Subject: [PATCH 27/60] Remove use of 'file' in favour of 'open'. --- pyface/workbench/user_perspective_manager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyface/workbench/user_perspective_manager.py b/pyface/workbench/user_perspective_manager.py index 7ea921abe..e9c486bbf 100644 --- a/pyface/workbench/user_perspective_manager.py +++ b/pyface/workbench/user_perspective_manager.py @@ -69,7 +69,7 @@ def _get_id_to_perspective ( self ): if self._id_to_perspective is None: self._id_to_perspective = dic = {} try: - fh = file( self.file_name, 'r' ) + fh = open( self.file_name, 'r' ) for line in fh: data = line.split( ':', 1 ) if len( data ) == 2: @@ -206,7 +206,7 @@ def _update_persistent_data(self): """ Update the persistent file information. """ try: - fh = file( self.file_name, 'w' ) + fh = open( self.file_name, 'w' ) fh.write( '\n'.join( [ '%s: %s' % ( p.id, p.name ) for p in self.perspectives ] ) ) fh.close() From f0473c6093eb64a66988be9f683966aa549e61d4 Mon Sep 17 00:00:00 2001 From: Corran Webster Date: Thu, 20 Aug 2015 21:49:43 +0100 Subject: [PATCH 28/60] Fix Qt4 serialization in Python3. --- pyface/ui/qt4/workbench/split_tab_widget.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyface/ui/qt4/workbench/split_tab_widget.py b/pyface/ui/qt4/workbench/split_tab_widget.py index aa305190d..bf45d963c 100644 --- a/pyface/ui/qt4/workbench/split_tab_widget.py +++ b/pyface/ui/qt4/workbench/split_tab_widget.py @@ -107,7 +107,7 @@ def _save_qsplitter(self, qsplitter): sp_ch_states.append(ch_state) - return (str(QtGui.QSplitter.saveState(qsplitter)), sp_ch_states) + return (QtGui.QSplitter.saveState(qsplitter).data(), sp_ch_states) def restoreState(self, state, factory): """ Restore the contents from the given state (returned by a previous From c6d8a42420721c785d47457eee0dd5a332f68ddb Mon Sep 17 00:00:00 2001 From: Kit Yan Choi Date: Thu, 18 Feb 2016 16:28:19 +0000 Subject: [PATCH 29/60] the ui parent should be the ViewContainer in the dock widget --- pyface/ui/qt4/workbench/workbench_window_layout.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyface/ui/qt4/workbench/workbench_window_layout.py b/pyface/ui/qt4/workbench/workbench_window_layout.py index a8e2780b7..10b291151 100755 --- a/pyface/ui/qt4/workbench/workbench_window_layout.py +++ b/pyface/ui/qt4/workbench/workbench_window_layout.py @@ -520,7 +520,7 @@ def on_name_changed(): view.window = self.window try: - view.control = view.create_control(self.window.control) + view.control = view.create_control(dw.widget()) except: # Tidy up if the view couldn't be created. delattr(view, '_qt4_dock') From c5a5d8e5e63ce6a50db1e8b3a82daabbb9238430 Mon Sep 17 00:00:00 2001 From: Stefano Borini Date: Tue, 22 Mar 2016 13:21:05 +0000 Subject: [PATCH 30/60] Fix exception when closing all tabs Fixes behavior experienced in enthought/mayavi#321. Performs a check before trying to set text color. --- pyface/ui/qt4/workbench/tests/__init__.py | 0 .../tests/test_workbench_window_layout.py | 19 +++++++++++++++++++ .../qt4/workbench/workbench_window_layout.py | 3 ++- 3 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 pyface/ui/qt4/workbench/tests/__init__.py create mode 100644 pyface/ui/qt4/workbench/tests/test_workbench_window_layout.py diff --git a/pyface/ui/qt4/workbench/tests/__init__.py b/pyface/ui/qt4/workbench/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/pyface/ui/qt4/workbench/tests/test_workbench_window_layout.py b/pyface/ui/qt4/workbench/tests/test_workbench_window_layout.py new file mode 100644 index 000000000..426cad226 --- /dev/null +++ b/pyface/ui/qt4/workbench/tests/test_workbench_window_layout.py @@ -0,0 +1,19 @@ +from __future__ import absolute_import +import mock + +from traits.testing.unittest_tools import unittest + +from pyface.ui.qt4.workbench.split_tab_widget import SplitTabWidget +from pyface.ui.qt4.workbench.workbench_window_layout import \ + WorkbenchWindowLayout + + +class TestWorkbenchWindowLayout(unittest.TestCase): + def test_change_of_active_qt_editor(self): + # Test error condition for enthought/mayavi#321 + + layout = WorkbenchWindowLayout( + _qt4_editor_area=mock.Mock( + spec=SplitTabWidget)) + + layout._qt4_active_editor_changed(None, None) diff --git a/pyface/ui/qt4/workbench/workbench_window_layout.py b/pyface/ui/qt4/workbench/workbench_window_layout.py index 10b291151..93998a227 100755 --- a/pyface/ui/qt4/workbench/workbench_window_layout.py +++ b/pyface/ui/qt4/workbench/workbench_window_layout.py @@ -336,7 +336,8 @@ def _qt4_editor_tab_spinner(self, editor, name, new): def _qt4_active_editor_changed(self, old, new): """ Handle change of active editor """ # Reset tab title to foreground color - self._qt4_editor_area.setTabTextColor(new.control) + if new: + self._qt4_editor_area.setTabTextColor(new.control) def _qt4_view_focus_changed(self, old, new): """ Handle the change of focus for a view. """ From 3f20e736af08bbd73f30406807f606d5e1effe7a Mon Sep 17 00:00:00 2001 From: Stefano Borini Date: Tue, 22 Mar 2016 13:31:07 +0000 Subject: [PATCH 31/60] Minor adjustments to test --- .../workbench/tests/test_workbench_window_layout.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/pyface/ui/qt4/workbench/tests/test_workbench_window_layout.py b/pyface/ui/qt4/workbench/tests/test_workbench_window_layout.py index 426cad226..715a88428 100644 --- a/pyface/ui/qt4/workbench/tests/test_workbench_window_layout.py +++ b/pyface/ui/qt4/workbench/tests/test_workbench_window_layout.py @@ -12,8 +12,15 @@ class TestWorkbenchWindowLayout(unittest.TestCase): def test_change_of_active_qt_editor(self): # Test error condition for enthought/mayavi#321 - layout = WorkbenchWindowLayout( - _qt4_editor_area=mock.Mock( - spec=SplitTabWidget)) + mock_split_tab_widget = mock.Mock(spec=SplitTabWidget) + layout = WorkbenchWindowLayout(_qt4_editor_area=mock_split_tab_widget) + + # This should not throw layout._qt4_active_editor_changed(None, None) + self.assertEqual(mock_split_tab_widget.setTabTextColor.called, False) + + mock_active_editor = mock.Mock() + layout._qt4_active_editor_changed(None, mock_active_editor) + + self.assertEqual(mock_split_tab_widget.setTabTextColor.called, True) From be2b93608f0bd363b1ed6624df2bbc94d6c7dfbb Mon Sep 17 00:00:00 2001 From: Stefano Borini Date: Tue, 22 Mar 2016 17:37:07 +0000 Subject: [PATCH 32/60] Review --- pyface/ui/qt4/workbench/workbench_window_layout.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyface/ui/qt4/workbench/workbench_window_layout.py b/pyface/ui/qt4/workbench/workbench_window_layout.py index 93998a227..4441a9874 100755 --- a/pyface/ui/qt4/workbench/workbench_window_layout.py +++ b/pyface/ui/qt4/workbench/workbench_window_layout.py @@ -336,7 +336,7 @@ def _qt4_editor_tab_spinner(self, editor, name, new): def _qt4_active_editor_changed(self, old, new): """ Handle change of active editor """ # Reset tab title to foreground color - if new: + if new is not None: self._qt4_editor_area.setTabTextColor(new.control) def _qt4_view_focus_changed(self, old, new): From 514f74710dc570fec6126fdac16605bd1cda8575 Mon Sep 17 00:00:00 2001 From: Kit Yan Choi Date: Wed, 6 Apr 2016 17:58:57 +0100 Subject: [PATCH 33/60] str(qtwindow state) leads to decoding error in python3: --- pyface/ui/qt4/workbench/workbench_window_layout.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyface/ui/qt4/workbench/workbench_window_layout.py b/pyface/ui/qt4/workbench/workbench_window_layout.py index 10b291151..daecba8b9 100755 --- a/pyface/ui/qt4/workbench/workbench_window_layout.py +++ b/pyface/ui/qt4/workbench/workbench_window_layout.py @@ -202,7 +202,7 @@ def get_view_memento(self): view_ids = [v.id for v in self.window.views if self.contains_view(v)] # Everything else is provided by QMainWindow. - state = str(self.window.control.saveState()) + state = self.window.control.saveState() return (0, (view_ids, state)) @@ -292,7 +292,7 @@ def resolve_id(id): self._qt4_editor_area.restoreState(editor_layout, resolve_id) def get_toolkit_memento(self): - return (0, dict(geometry=str(self.window.control.saveGeometry()))) + return (0, dict(geometry=self.window.control.saveGeometry())) def set_toolkit_memento(self, memento): if hasattr(memento, 'toolkit_data'): From bc014538b51b846f18171d1b648faea6c78a2188 Mon Sep 17 00:00:00 2001 From: Kit Yan Choi Date: Thu, 7 Apr 2016 16:32:03 +0100 Subject: [PATCH 34/60] added is_editor_area_visible function to qt4 workbench_window_layout --- pyface/ui/qt4/workbench/workbench_window_layout.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyface/ui/qt4/workbench/workbench_window_layout.py b/pyface/ui/qt4/workbench/workbench_window_layout.py index 10b291151..1616f2d99 100755 --- a/pyface/ui/qt4/workbench/workbench_window_layout.py +++ b/pyface/ui/qt4/workbench/workbench_window_layout.py @@ -304,6 +304,8 @@ def set_toolkit_memento(self, memento): if geometry is not None: self.window.control.restoreGeometry(geometry) + def is_editor_area_visible(self): + return self._qt4_editor_area.isVisible() ########################################################################### # Private interface. From 90de4dbff9af6c98cb05323a02a94f331887f929 Mon Sep 17 00:00:00 2001 From: Kit Yan Choi Date: Thu, 7 Apr 2016 16:33:14 +0100 Subject: [PATCH 35/60] added is_editor_area_visible function to wx workbench_window_layout --- pyface/ui/wx/workbench/workbench_window_layout.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pyface/ui/wx/workbench/workbench_window_layout.py b/pyface/ui/wx/workbench/workbench_window_layout.py index 6f2bc6a42..126ac92fd 100644 --- a/pyface/ui/wx/workbench/workbench_window_layout.py +++ b/pyface/ui/wx/workbench/workbench_window_layout.py @@ -248,6 +248,12 @@ def show_view(self, view): return + def is_editor_area_visible(self): + dock_control = self._wx_view_dock_window.get_control( + self.editor_area_id, visible_only=False + ) + return dock_control.visible + #### Methods for saving and restoring the layout ########################## def get_view_memento(self): From 422759ad013ae6babcfeccd829d550cb5e9f15d8 Mon Sep 17 00:00:00 2001 From: Kit Yan Choi Date: Thu, 7 Apr 2016 16:35:13 +0100 Subject: [PATCH 36/60] added whether the editor area is visible to the perspective_memento --- pyface/workbench/user_perspective_manager.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyface/workbench/user_perspective_manager.py b/pyface/workbench/user_perspective_manager.py index e9c486bbf..6bf8692f0 100644 --- a/pyface/workbench/user_perspective_manager.py +++ b/pyface/workbench/user_perspective_manager.py @@ -131,7 +131,8 @@ def clone_perspective(self, window, perspective, **traits): # fixme: This needs to be pushed into the window API!!!!!!! window._memento.perspective_mementos[clone.id] = ( window.layout.get_view_memento(), - window.active_view and window.active_view.id or None + window.active_view and window.active_view.id or None, + window.layout.get_editor_memento() ) # Update the persistent file information. From 696a0e011f7d5c2c14a8ac79f22a39fb9fccfe15 Mon Sep 17 00:00:00 2001 From: Kit Yan Choi Date: Thu, 7 Apr 2016 16:42:59 +0100 Subject: [PATCH 37/60] handle saved editor_area_visible while restoring perspective --- pyface/workbench/workbench_window.py | 55 +++++++++++++++++++++------- 1 file changed, 41 insertions(+), 14 deletions(-) diff --git a/pyface/workbench/workbench_window.py b/pyface/workbench/workbench_window.py index d7b9a6d64..7a8aa307e 100755 --- a/pyface/workbench/workbench_window.py +++ b/pyface/workbench/workbench_window.py @@ -220,16 +220,20 @@ def _create_contents(self, parent): # to a perspective that has not been seen yet. self._initial_layout = self.layout.get_view_memento() + # Are we creating the window from scratch or restoring it from a # memento? if self._memento is None: self._memento = WorkbenchWindowMemento() + # Set the initial perspective. + self.active_perspective = self._get_initial_perspective() + else: - self._restore_contents() + # Set the initial perspective. + self.active_perspective = self._get_initial_perspective() - # Set the initial perspective. - self.active_perspective = self._get_initial_perspective() + self._restore_contents() return contents @@ -467,7 +471,6 @@ def hide_editor_area(self): """ Hide the editor area. """ self.layout.hide_editor_area() - return def hide_view(self, view): @@ -568,7 +571,8 @@ def get_memento(self): # The layout of the active perspective. self._memento.perspective_mementos[self.active_perspective.id] = ( self.layout.get_view_memento(), - self.active_view and self.active_view.id or None + self.active_view and self.active_view.id or None, + self.layout.is_editor_area_visible() ) # The layout of the editor area. @@ -727,7 +731,8 @@ def _hide_perspective(self, perspective): # Save the current layout of the perspective. self._memento.perspective_mementos[perspective.id] = ( self.layout.get_view_memento(), - self.active_view and self.active_view.id or None + self.active_view and self.active_view.id or None, + self.layout.is_editor_area_visible() ) return @@ -737,15 +742,26 @@ def _show_perspective(self, old, new): # If the perspective has been seen before then restore it. memento = self._memento.perspective_mementos.get(new.id) + if memento is not None: # Show the editor area? - if new.show_editor_area: + if len(memento) == 2: + logger.warning(("Your saved layout is from an older version. " + "Please reset.")) + editor_area_visible = True + else: + editor_area_visible = memento[2] + + # We need to show/hide the editor area before setting + # the views + if editor_area_visible: self.show_editor_area() else: self.hide_editor_area() self.active_editor = None - view_memento, active_view_id = memento + # Now set the views + view_memento, active_view_id = memento[:2] self.layout.set_view_memento(view_memento) # Make sure the active part, view and editor reflect the new @@ -768,12 +784,12 @@ def _show_perspective(self, old, new): # perspective. self.active_view = None - # Show the editor area? - if new.show_editor_area: - self.show_editor_area() - else: - self.hide_editor_area() - self.active_editor = None + # Show the editor area? + if new.show_editor_area: + self.show_editor_area() + else: + self.hide_editor_area() + self.active_editor = None # Inform the perspective that it has been shown. new.show(self) @@ -789,6 +805,17 @@ def _restore_contents(self): self.layout.set_editor_memento(self._memento.editor_area_memento) + # Is the editor area visible? + perspective = self.active_perspective + memento = self._memento.perspective_mementos[perspective.id] + editor_area_visible = memento[2] + + if editor_area_visible: + self.show_editor_area() + else: + self.hide_editor_area() + self.active_editor = None + self.size = self._memento.size self.position = self._memento.position From 142c042f080d046df18d745798acc59b57d3ddb7 Mon Sep 17 00:00:00 2001 From: Kit Yan Choi Date: Thu, 7 Apr 2016 16:57:49 +0100 Subject: [PATCH 38/60] attempt some refactoring --- pyface/workbench/workbench_window.py | 38 ++++++++++++---------------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/pyface/workbench/workbench_window.py b/pyface/workbench/workbench_window.py index 7a8aa307e..4f294c1fe 100755 --- a/pyface/workbench/workbench_window.py +++ b/pyface/workbench/workbench_window.py @@ -745,20 +745,7 @@ def _show_perspective(self, old, new): if memento is not None: # Show the editor area? - if len(memento) == 2: - logger.warning(("Your saved layout is from an older version. " - "Please reset.")) - editor_area_visible = True - else: - editor_area_visible = memento[2] - - # We need to show/hide the editor area before setting - # the views - if editor_area_visible: - self.show_editor_area() - else: - self.hide_editor_area() - self.active_editor = None + self._restore_editor_area_visibility(memento) # Now set the views view_memento, active_view_id = memento[:2] @@ -800,22 +787,29 @@ def _show_perspective(self, old, new): return - def _restore_contents(self): - """ Restore the contents of the window. """ - - self.layout.set_editor_memento(self._memento.editor_area_memento) - + def _restore_editor_area_visibility(self, memento): # Is the editor area visible? - perspective = self.active_perspective - memento = self._memento.perspective_mementos[perspective.id] - editor_area_visible = memento[2] + if len(memento) == 2: + logger.warning(("Restoring perspective from a file saved from " + "an older version.")) + editor_area_visible = True + else: + editor_area_visible = memento[2] + # Show the editor area if it is set to be visible if editor_area_visible: self.show_editor_area() else: self.hide_editor_area() self.active_editor = None + def _restore_contents(self): + """ Restore the contents of the window. """ + self.layout.set_editor_memento(self._memento.editor_area_memento) + + memento = self._memento.perspective_mementos[self.active_perspective.id] + self._restore_editor_area_visibility(memento) + self.size = self._memento.size self.position = self._memento.position From b3333cda75122470a62cb524865039f0146a2c88 Mon Sep 17 00:00:00 2001 From: Kit Yan Choi Date: Thu, 7 Apr 2016 17:00:05 +0100 Subject: [PATCH 39/60] user_perspective_manager perspective_mementos should be consistent --- pyface/workbench/user_perspective_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyface/workbench/user_perspective_manager.py b/pyface/workbench/user_perspective_manager.py index 6bf8692f0..50072a7f0 100644 --- a/pyface/workbench/user_perspective_manager.py +++ b/pyface/workbench/user_perspective_manager.py @@ -132,7 +132,7 @@ def clone_perspective(self, window, perspective, **traits): window._memento.perspective_mementos[clone.id] = ( window.layout.get_view_memento(), window.active_view and window.active_view.id or None, - window.layout.get_editor_memento() + window.layout.is_editor_area_visible() ) # Update the persistent file information. From 1d1b7b6bf060bab0b8199da0ec6a0cd96ce07ae2 Mon Sep 17 00:00:00 2001 From: Kit Yan Choi Date: Thu, 7 Apr 2016 17:08:31 +0100 Subject: [PATCH 40/60] the refactoring is not needed as the function is only used once --- pyface/workbench/workbench_window.py | 35 +++++++++++----------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/pyface/workbench/workbench_window.py b/pyface/workbench/workbench_window.py index 4f294c1fe..c5bcf08c2 100755 --- a/pyface/workbench/workbench_window.py +++ b/pyface/workbench/workbench_window.py @@ -532,7 +532,6 @@ def reset_views(self): def show_editor_area(self): """ Show the editor area. """ - self.layout.show_editor_area() return @@ -745,7 +744,20 @@ def _show_perspective(self, old, new): if memento is not None: # Show the editor area? - self._restore_editor_area_visibility(memento) + # We need to set the editor area before setting the views + if len(memento) == 2: + logger.warning(("Restoring perspective from a file saved from " + "an older version.")) + editor_area_visible = True + else: + editor_area_visible = memento[2] + + # Show the editor area if it is set to be visible + if editor_area_visible: + self.show_editor_area() + else: + self.hide_editor_area() + self.active_editor = None # Now set the views view_memento, active_view_id = memento[:2] @@ -787,29 +799,10 @@ def _show_perspective(self, old, new): return - def _restore_editor_area_visibility(self, memento): - # Is the editor area visible? - if len(memento) == 2: - logger.warning(("Restoring perspective from a file saved from " - "an older version.")) - editor_area_visible = True - else: - editor_area_visible = memento[2] - - # Show the editor area if it is set to be visible - if editor_area_visible: - self.show_editor_area() - else: - self.hide_editor_area() - self.active_editor = None - def _restore_contents(self): """ Restore the contents of the window. """ self.layout.set_editor_memento(self._memento.editor_area_memento) - memento = self._memento.perspective_mementos[self.active_perspective.id] - self._restore_editor_area_visibility(memento) - self.size = self._memento.size self.position = self._memento.position From 80ee494b6b89fb1963158026bca226f28b89cc9c Mon Sep 17 00:00:00 2001 From: Kit Yan Choi Date: Thu, 7 Apr 2016 17:10:56 +0100 Subject: [PATCH 41/60] removed extra blank lines --- pyface/workbench/workbench_window.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pyface/workbench/workbench_window.py b/pyface/workbench/workbench_window.py index c5bcf08c2..d903e3026 100755 --- a/pyface/workbench/workbench_window.py +++ b/pyface/workbench/workbench_window.py @@ -220,7 +220,6 @@ def _create_contents(self, parent): # to a perspective that has not been seen yet. self._initial_layout = self.layout.get_view_memento() - # Are we creating the window from scratch or restoring it from a # memento? if self._memento is None: @@ -533,7 +532,6 @@ def reset_views(self): def show_editor_area(self): """ Show the editor area. """ self.layout.show_editor_area() - return def show_view(self, view): From ff114b86daec3d9ef76c15f762b312287b49b4bd Mon Sep 17 00:00:00 2001 From: Kit Yan Choi Date: Thu, 7 Apr 2016 18:08:09 +0100 Subject: [PATCH 42/60] tested test for show_editor_area and hide_editor_area --- .../workbench/tests/test_workbench_window.py | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 pyface/workbench/tests/test_workbench_window.py diff --git a/pyface/workbench/tests/test_workbench_window.py b/pyface/workbench/tests/test_workbench_window.py new file mode 100644 index 000000000..c68c8ff71 --- /dev/null +++ b/pyface/workbench/tests/test_workbench_window.py @@ -0,0 +1,51 @@ +import mock + +from traits.testing.unittest_tools import unittest, UnittestTools + +from pyface.workbench.perspective import Perspective +from pyface.workbench.workbench_window import (WorkbenchWindow, + WorkbenchWindowLayout, + WorkbenchWindowMemento) + + +class TestWindow(unittest.TestCase, UnittestTools): + + def test_restore_perspective_editor_area(self): + # A perspective with show_editor_area switched on + with_editor = Perspective(show_editor_area=True, + id="test_id", name="test_name") + + # A perspective with show_editor_area switched off + without_editor = Perspective(show_editor_area=False, + id="test_id2", name="test_name2") + + # Set up the WorkbenchWindow + workbench_window = WorkbenchWindow( + perspectives=[with_editor, without_editor]) + + # mock a bunch of objects + workbench_window._memento = WorkbenchWindowMemento() + workbench_window._initial_layout = workbench_window._memento + workbench_window.layout = mock.Mock(spec=WorkbenchWindowLayout) + + # There are the methods we want to test if they are called + workbench_window.show_editor_area = mock.MagicMock() + workbench_window.hide_editor_area = mock.MagicMock() + + # Show a perspective with editor area + workbench_window._show_perspective(None, with_editor) + + # show_editor_area should be called + # hide_editor_area should not be called when there was + # no old perspective + self.assertTrue(workbench_window.show_editor_area.called) + self.assertFalse(workbench_window.hide_editor_area.called) + + # Show a perspective with editor area + workbench_window.show_editor_area.reset_mock() + workbench_window.hide_editor_area.reset_mock() + workbench_window._show_perspective(with_editor, without_editor) + + # show_editor_area should not be called and hide_editor_area is called + self.assertFalse(workbench_window.show_editor_area.called) + self.assertTrue(workbench_window.hide_editor_area.called) From 218cf817e900f09cd81906d6cf22e91a66c5b1dc Mon Sep 17 00:00:00 2001 From: Kit Yan Choi Date: Thu, 7 Apr 2016 18:11:14 +0100 Subject: [PATCH 43/60] added test for show_editor_area and hide_editor_area --- pyface/workbench/tests/test_workbench_window.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pyface/workbench/tests/test_workbench_window.py b/pyface/workbench/tests/test_workbench_window.py index c68c8ff71..8ebb0e00c 100644 --- a/pyface/workbench/tests/test_workbench_window.py +++ b/pyface/workbench/tests/test_workbench_window.py @@ -49,3 +49,14 @@ def test_restore_perspective_editor_area(self): # show_editor_area should not be called and hide_editor_area is called self.assertFalse(workbench_window.show_editor_area.called) self.assertTrue(workbench_window.hide_editor_area.called) + + # The with_editor has been seen so this will read from the memento + workbench_window.show_editor_area.reset_mock() + workbench_window.hide_editor_area.reset_mock() + workbench_window._show_perspective(None, with_editor) + + # show_editor_area should be called + # hide_editor_area should not be called as we fake + # old perspective to be None + self.assertTrue(workbench_window.show_editor_area.called) + self.assertFalse(workbench_window.hide_editor_area.called) From 1829fc19de5f674917af8d71b1a08b3c145cadb3 Mon Sep 17 00:00:00 2001 From: Kit Yan Choi Date: Thu, 7 Apr 2016 18:21:45 +0100 Subject: [PATCH 44/60] amended test for show/hide_editor_area --- pyface/workbench/tests/test_workbench_window.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pyface/workbench/tests/test_workbench_window.py b/pyface/workbench/tests/test_workbench_window.py index 8ebb0e00c..03c6974bc 100644 --- a/pyface/workbench/tests/test_workbench_window.py +++ b/pyface/workbench/tests/test_workbench_window.py @@ -33,18 +33,19 @@ def test_restore_perspective_editor_area(self): workbench_window.hide_editor_area = mock.MagicMock() # Show a perspective with editor area - workbench_window._show_perspective(None, with_editor) + workbench_window.active_perspective = with_editor + workbench_window.layout.is_editor_area_visible = mock.MagicMock(return_value=True) # show_editor_area should be called - # hide_editor_area should not be called when there was - # no old perspective + # hide_editor_area should not be called self.assertTrue(workbench_window.show_editor_area.called) self.assertFalse(workbench_window.hide_editor_area.called) # Show a perspective with editor area workbench_window.show_editor_area.reset_mock() workbench_window.hide_editor_area.reset_mock() - workbench_window._show_perspective(with_editor, without_editor) + workbench_window.active_perspective = without_editor + workbench_window.layout.is_editor_area_visible = mock.MagicMock(return_value=False) # show_editor_area should not be called and hide_editor_area is called self.assertFalse(workbench_window.show_editor_area.called) @@ -53,10 +54,9 @@ def test_restore_perspective_editor_area(self): # The with_editor has been seen so this will read from the memento workbench_window.show_editor_area.reset_mock() workbench_window.hide_editor_area.reset_mock() - workbench_window._show_perspective(None, with_editor) + workbench_window.active_perspective = with_editor + workbench_window.layout.is_editor_area_visible = mock.MagicMock(return_value=True) # show_editor_area should be called - # hide_editor_area should not be called as we fake - # old perspective to be None self.assertTrue(workbench_window.show_editor_area.called) self.assertFalse(workbench_window.hide_editor_area.called) From 9ad6215c4473c869361ee31c07113bb16f06144d Mon Sep 17 00:00:00 2001 From: Kit Yan Choi Date: Fri, 8 Apr 2016 09:35:13 +0100 Subject: [PATCH 45/60] reverted changes to workbench_window that are no longer needed --- pyface/workbench/workbench_window.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/pyface/workbench/workbench_window.py b/pyface/workbench/workbench_window.py index d903e3026..5676062a1 100755 --- a/pyface/workbench/workbench_window.py +++ b/pyface/workbench/workbench_window.py @@ -225,15 +225,12 @@ def _create_contents(self, parent): if self._memento is None: self._memento = WorkbenchWindowMemento() - # Set the initial perspective. - self.active_perspective = self._get_initial_perspective() - else: - # Set the initial perspective. - self.active_perspective = self._get_initial_perspective() - self._restore_contents() + # Set the initial perspective. + self.active_perspective = self._get_initial_perspective() + return contents ########################################################################### @@ -470,6 +467,7 @@ def hide_editor_area(self): """ Hide the editor area. """ self.layout.hide_editor_area() + return def hide_view(self, view): @@ -531,7 +529,9 @@ def reset_views(self): def show_editor_area(self): """ Show the editor area. """ + self.layout.show_editor_area() + return def show_view(self, view): @@ -799,6 +799,7 @@ def _show_perspective(self, old, new): def _restore_contents(self): """ Restore the contents of the window. """ + self.layout.set_editor_memento(self._memento.editor_area_memento) self.size = self._memento.size From 2132d0f4388c5b3f653c72550289fbb7f54ed212 Mon Sep 17 00:00:00 2001 From: Kit Yan Choi Date: Fri, 8 Apr 2016 09:35:40 +0100 Subject: [PATCH 46/60] amended mock tests --- .../workbench/tests/test_workbench_window.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/pyface/workbench/tests/test_workbench_window.py b/pyface/workbench/tests/test_workbench_window.py index 03c6974bc..660aa534c 100644 --- a/pyface/workbench/tests/test_workbench_window.py +++ b/pyface/workbench/tests/test_workbench_window.py @@ -27,7 +27,7 @@ def test_restore_perspective_editor_area(self): workbench_window._memento = WorkbenchWindowMemento() workbench_window._initial_layout = workbench_window._memento workbench_window.layout = mock.Mock(spec=WorkbenchWindowLayout) - + # There are the methods we want to test if they are called workbench_window.show_editor_area = mock.MagicMock() workbench_window.hide_editor_area = mock.MagicMock() @@ -38,18 +38,19 @@ def test_restore_perspective_editor_area(self): # show_editor_area should be called # hide_editor_area should not be called - self.assertTrue(workbench_window.show_editor_area.called) - self.assertFalse(workbench_window.hide_editor_area.called) + self.assertEqual(workbench_window.show_editor_area.call_count, 1) + self.assertEqual(workbench_window.hide_editor_area.call_count, 0) # Show a perspective with editor area workbench_window.show_editor_area.reset_mock() - workbench_window.hide_editor_area.reset_mock() + workbench_window.hide_editor_area.reset_mock() workbench_window.active_perspective = without_editor workbench_window.layout.is_editor_area_visible = mock.MagicMock(return_value=False) - # show_editor_area should not be called and hide_editor_area is called - self.assertFalse(workbench_window.show_editor_area.called) - self.assertTrue(workbench_window.hide_editor_area.called) + # show_editor_area should not be called + # hide_editor_area should be called + self.assertEqual(workbench_window.show_editor_area.call_count, 0) + self.assertEqual(workbench_window.hide_editor_area.call_count, 1) # The with_editor has been seen so this will read from the memento workbench_window.show_editor_area.reset_mock() @@ -58,5 +59,5 @@ def test_restore_perspective_editor_area(self): workbench_window.layout.is_editor_area_visible = mock.MagicMock(return_value=True) # show_editor_area should be called - self.assertTrue(workbench_window.show_editor_area.called) - self.assertFalse(workbench_window.hide_editor_area.called) + self.assertEqual(workbench_window.show_editor_area.call_count, 1) + self.assertEqual(workbench_window.hide_editor_area.call_count, 0) From 5f171172ef71e5d2750e6290280ecac5a190d8e2 Mon Sep 17 00:00:00 2001 From: Kit Yan Choi Date: Fri, 8 Apr 2016 09:38:08 +0100 Subject: [PATCH 47/60] make the test more thorough --- pyface/workbench/tests/test_workbench_window.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pyface/workbench/tests/test_workbench_window.py b/pyface/workbench/tests/test_workbench_window.py index 660aa534c..b066253c1 100644 --- a/pyface/workbench/tests/test_workbench_window.py +++ b/pyface/workbench/tests/test_workbench_window.py @@ -61,3 +61,14 @@ def test_restore_perspective_editor_area(self): # show_editor_area should be called self.assertEqual(workbench_window.show_editor_area.call_count, 1) self.assertEqual(workbench_window.hide_editor_area.call_count, 0) + + # The without_editor has been seen so this will read from the memento + workbench_window.show_editor_area.reset_mock() + workbench_window.hide_editor_area.reset_mock() + workbench_window.active_perspective = without_editor + workbench_window.layout.is_editor_area_visible = mock.MagicMock(return_value=False) + + # hide_editor_area should be called + self.assertEqual(workbench_window.show_editor_area.call_count, 0) + self.assertEqual(workbench_window.hide_editor_area.call_count, 1) + From 18d003f996764d94613b92ca44043a757e9daecb Mon Sep 17 00:00:00 2001 From: Kit Yan Choi Date: Fri, 8 Apr 2016 12:28:06 +0100 Subject: [PATCH 48/60] add tests for restoring perspective from file --- .../workbench/tests/test_workbench_window.py | 171 +++++++++++++----- 1 file changed, 129 insertions(+), 42 deletions(-) diff --git a/pyface/workbench/tests/test_workbench_window.py b/pyface/workbench/tests/test_workbench_window.py index b066253c1..c5ddf10a4 100644 --- a/pyface/workbench/tests/test_workbench_window.py +++ b/pyface/workbench/tests/test_workbench_window.py @@ -1,74 +1,161 @@ import mock +import tempfile +import shutil +import os from traits.testing.unittest_tools import unittest, UnittestTools from pyface.workbench.perspective import Perspective +from pyface.workbench.api import Workbench +from pyface.workbench.user_perspective_manager import UserPerspectiveManager from pyface.workbench.workbench_window import (WorkbenchWindow, WorkbenchWindowLayout, WorkbenchWindowMemento) -class TestWindow(unittest.TestCase, UnittestTools): +class TestWorkbenchWindowUserPerspective(unittest.TestCase, UnittestTools): - def test_restore_perspective_editor_area(self): + def setUp(self): # A perspective with show_editor_area switched on - with_editor = Perspective(show_editor_area=True, - id="test_id", name="test_name") + self.with_editor = Perspective(show_editor_area=True, + id="test_id", name="test_name") # A perspective with show_editor_area switched off - without_editor = Perspective(show_editor_area=False, - id="test_id2", name="test_name2") - - # Set up the WorkbenchWindow - workbench_window = WorkbenchWindow( - perspectives=[with_editor, without_editor]) - - # mock a bunch of objects - workbench_window._memento = WorkbenchWindowMemento() - workbench_window._initial_layout = workbench_window._memento - workbench_window.layout = mock.Mock(spec=WorkbenchWindowLayout) + self.without_editor = Perspective(show_editor_area=False, + id="test_id2", name="test_name2") + + # Where the state file should be saved + self.state_location = tempfile.mkdtemp(dir="./") + + # Make sure the temporary directory is removed + self.addCleanup(self.rm_tempdir) + + def rm_tempdir(self): + shutil.rmtree(self.state_location) + + def get_workbench_with_window(self): + workbench = Workbench() + workbench_window = WorkbenchWindow() + workbench.windows = [workbench_window] + + # Saved perspectives should go to the temporary directory + workbench.state_location = self.state_location + + # Mock the layout for the workbench window + workbench_window.layout = mock.MagicMock(spec=WorkbenchWindowLayout) + workbench_window.layout.window = workbench_window + + return workbench, workbench_window + + def show_perspective(self, workbench_window, perspective): + workbench_window.active_perspective = perspective + workbench_window.layout.is_editor_area_visible = mock.MagicMock( + return_value=perspective.show_editor_area) + + def test_editor_area_with_perspectives(self): + # The workbench and workbench window with layout mocked + workbench, workbench_window = self.get_workbench_with_window() + workbench.active_window = workbench_window + + # Add perspectives + # This will write to the state file + workbench.user_perspective_manager.add(self.with_editor) + workbench.user_perspective_manager.add(self.without_editor) # There are the methods we want to test if they are called workbench_window.show_editor_area = mock.MagicMock() workbench_window.hide_editor_area = mock.MagicMock() - # Show a perspective with editor area - workbench_window.active_perspective = with_editor - workbench_window.layout.is_editor_area_visible = mock.MagicMock(return_value=True) + # Mock more things for initialing the Workbench Window + workbench_window._memento = WorkbenchWindowMemento() + workbench_window._initial_layout = workbench_window._memento + # Show a perspective with an editor area + self.show_perspective(workbench_window, self.with_editor) + # show_editor_area should be called - # hide_editor_area should not be called - self.assertEqual(workbench_window.show_editor_area.call_count, 1) - self.assertEqual(workbench_window.hide_editor_area.call_count, 0) + self.assertTrue(workbench_window.show_editor_area.called) - # Show a perspective with editor area - workbench_window.show_editor_area.reset_mock() + # Show a perspective withOUT an editor area workbench_window.hide_editor_area.reset_mock() - workbench_window.active_perspective = without_editor - workbench_window.layout.is_editor_area_visible = mock.MagicMock(return_value=False) + self.show_perspective(workbench_window, self.without_editor) - # show_editor_area should not be called # hide_editor_area should be called - self.assertEqual(workbench_window.show_editor_area.call_count, 0) - self.assertEqual(workbench_window.hide_editor_area.call_count, 1) + self.assertTrue(workbench_window.hide_editor_area.called) - # The with_editor has been seen so this will read from the memento + # The with_editor has been seen so this will be restored from the memento workbench_window.show_editor_area.reset_mock() - workbench_window.hide_editor_area.reset_mock() - workbench_window.active_perspective = with_editor - workbench_window.layout.is_editor_area_visible = mock.MagicMock(return_value=True) + self.show_perspective(workbench_window, self.with_editor) # show_editor_area should be called - self.assertEqual(workbench_window.show_editor_area.call_count, 1) - self.assertEqual(workbench_window.hide_editor_area.call_count, 0) + self.assertTrue(workbench_window.show_editor_area.called) - # The without_editor has been seen so this will read from the memento - workbench_window.show_editor_area.reset_mock() - workbench_window.hide_editor_area.reset_mock() - workbench_window.active_perspective = without_editor - workbench_window.layout.is_editor_area_visible = mock.MagicMock(return_value=False) + def test_editor_area_restore_from_saved_state(self): + # The workbench and workbench window with layout mocked + workbench, workbench_window = self.get_workbench_with_window() + workbench.active_window = workbench_window - # hide_editor_area should be called - self.assertEqual(workbench_window.show_editor_area.call_count, 0) - self.assertEqual(workbench_window.hide_editor_area.call_count, 1) + # Add perspectives + workbench.user_perspective_manager.add(self.with_editor) + workbench.user_perspective_manager.add(self.without_editor) + + # Mock for initialising the workbench window + workbench_window._memento = WorkbenchWindowMemento() + workbench_window._initial_layout = workbench_window._memento + + # Mock layout functions for pickling + layout_functions = {"get_view_memento.return_value": (0, (None, None)), + "get_editor_memento.return_value": (0, (None, None)), + "get_toolkit_memento.return_value": (0, dict(geometry=""))} + + workbench_window.layout.configure_mock(**layout_functions) + # The following records perspective mementos to workbench_window._memento + self.show_perspective(workbench_window, self.without_editor) + self.show_perspective(workbench_window, self.with_editor) + + # Save the window layout to a state file + workbench._save_window_layout(workbench_window) + + # We only needed the state file for this test + del workbench_window + del workbench + + # We create another workbench which uses the state location + # and we test if we can retore them correctly + workbench, workbench_window = self.get_workbench_with_window() + + # Mock window factory since we already created a workbench window + workbench.window_factory = mock.MagicMock(return_value=workbench_window) + + # There are the methods we want to test if they are called + workbench_window.show_editor_area = mock.MagicMock() + workbench_window.hide_editor_area = mock.MagicMock() + + # This restores the perspectives and mementos + workbench.create_window() + + # Create contents + workbench_window._create_contents(mock.Mock()) + + # Perspective mementos should be restored + self.assertIn(self.with_editor.id, workbench_window._memento.perspective_mementos) + self.assertIn(self.without_editor.id, workbench_window._memento.perspective_mementos) + + # Since the with_editor perspective is used last, + # it should be used as initial perspective + self.assertTrue(workbench_window.show_editor_area.called) + + # Try restoring the perspective without editor + # The restored perspectives are not the same instance as before + # We need to get them using their id + perspective_without_editor = workbench_window.get_perspective_by_id( + self.without_editor.id) + + # Show the perspective with editor area + workbench_window.hide_editor_area.reset_mock() + self.show_perspective(workbench_window, perspective_without_editor) + + # make sure hide_editor_area is called + self.assertTrue(workbench_window.hide_editor_area.called) + From 5af559414de339fd80969fa317bda51877cc6378 Mon Sep 17 00:00:00 2001 From: Kit Yan Choi Date: Fri, 8 Apr 2016 12:28:35 +0100 Subject: [PATCH 49/60] add __init__ --- pyface/workbench/tests/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 pyface/workbench/tests/__init__.py diff --git a/pyface/workbench/tests/__init__.py b/pyface/workbench/tests/__init__.py new file mode 100644 index 000000000..e69de29bb From dffba8090ebb050a559a3e623eb8c651627e349d Mon Sep 17 00:00:00 2001 From: Kit Yan Choi Date: Fri, 8 Apr 2016 12:36:05 +0100 Subject: [PATCH 50/60] edited comments --- pyface/workbench/tests/test_workbench_window.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pyface/workbench/tests/test_workbench_window.py b/pyface/workbench/tests/test_workbench_window.py index c5ddf10a4..ee5d74dde 100644 --- a/pyface/workbench/tests/test_workbench_window.py +++ b/pyface/workbench/tests/test_workbench_window.py @@ -53,12 +53,13 @@ def show_perspective(self, workbench_window, perspective): return_value=perspective.show_editor_area) def test_editor_area_with_perspectives(self): + """ Test show_editor_area is respected while switching perspective""" + # The workbench and workbench window with layout mocked workbench, workbench_window = self.get_workbench_with_window() workbench.active_window = workbench_window # Add perspectives - # This will write to the state file workbench.user_perspective_manager.add(self.with_editor) workbench.user_perspective_manager.add(self.without_editor) @@ -91,6 +92,8 @@ def test_editor_area_with_perspectives(self): self.assertTrue(workbench_window.show_editor_area.called) def test_editor_area_restore_from_saved_state(self): + """ Test if show_editor_area is restored properly from saved state """ + # The workbench and workbench window with layout mocked workbench, workbench_window = self.get_workbench_with_window() workbench.active_window = workbench_window @@ -104,6 +107,7 @@ def test_editor_area_restore_from_saved_state(self): workbench_window._initial_layout = workbench_window._memento # Mock layout functions for pickling + # We only care about show_editor_area and not the layout in this test layout_functions = {"get_view_memento.return_value": (0, (None, None)), "get_editor_memento.return_value": (0, (None, None)), "get_toolkit_memento.return_value": (0, dict(geometry=""))} @@ -122,7 +126,7 @@ def test_editor_area_restore_from_saved_state(self): del workbench # We create another workbench which uses the state location - # and we test if we can retore them correctly + # and we test if we can retore the saved perspective correctly workbench, workbench_window = self.get_workbench_with_window() # Mock window factory since we already created a workbench window From 4d8f101fec8b3966d94ba7bbfe6b836f55050b79 Mon Sep 17 00:00:00 2001 From: Kit Yan Choi Date: Fri, 8 Apr 2016 12:38:39 +0100 Subject: [PATCH 51/60] edited warning message --- pyface/workbench/workbench_window.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyface/workbench/workbench_window.py b/pyface/workbench/workbench_window.py index 5676062a1..fe4ccdb51 100755 --- a/pyface/workbench/workbench_window.py +++ b/pyface/workbench/workbench_window.py @@ -744,8 +744,7 @@ def _show_perspective(self, old, new): # Show the editor area? # We need to set the editor area before setting the views if len(memento) == 2: - logger.warning(("Restoring perspective from a file saved from " - "an older version.")) + logger.warning("Restoring perspective from an older version.") editor_area_visible = True else: editor_area_visible = memento[2] From 513279f7b3b608e07dbb557a8871cfa9d6bfad29 Mon Sep 17 00:00:00 2001 From: Kit Yan Choi Date: Thu, 14 Apr 2016 09:39:00 +0100 Subject: [PATCH 52/60] use {} to create dict --- pyface/ui/qt4/workbench/workbench_window_layout.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyface/ui/qt4/workbench/workbench_window_layout.py b/pyface/ui/qt4/workbench/workbench_window_layout.py index daecba8b9..a9f491f94 100755 --- a/pyface/ui/qt4/workbench/workbench_window_layout.py +++ b/pyface/ui/qt4/workbench/workbench_window_layout.py @@ -292,7 +292,7 @@ def resolve_id(id): self._qt4_editor_area.restoreState(editor_layout, resolve_id) def get_toolkit_memento(self): - return (0, dict(geometry=self.window.control.saveGeometry())) + return (0, {'geometry' : self.window.control.saveGeometry()}) def set_toolkit_memento(self, memento): if hasattr(memento, 'toolkit_data'): From 586b34cb4468979454cc612e5541d0dddec43c98 Mon Sep 17 00:00:00 2001 From: Kit Yan Choi Date: Thu, 14 Apr 2016 10:59:41 +0100 Subject: [PATCH 53/60] sanity check for editor in _on_editor_closed --- pyface/workbench/workbench_window.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyface/workbench/workbench_window.py b/pyface/workbench/workbench_window.py index fe4ccdb51..3772ce57e 100755 --- a/pyface/workbench/workbench_window.py +++ b/pyface/workbench/workbench_window.py @@ -7,7 +7,7 @@ # Enthought library imports. from pyface.api import ApplicationWindow, GUI from traits.api import Callable, Constant, Delegate, Event, Instance -from traits.api import List, Str, Tuple, Unicode, Vetoable +from traits.api import List, Str, Tuple, Unicode, Vetoable, Undefined from traits.api import on_trait_change, provides # Local imports. @@ -893,6 +893,9 @@ def _views_items_changed(self, event): def _on_editor_closed(self, editor): """ Dynamic trait change handler. """ + if editor is None or editor is Undefined: + return + index = self.editors.index(editor) del self.editors[index] if editor is self.active_editor: From 0640dcc618984ac689f8e05d870a8c6df19cdccd Mon Sep 17 00:00:00 2001 From: Corran Webster Date: Mon, 9 Jan 2017 13:20:20 +0000 Subject: [PATCH 54/60] Guard traitsui imports to places where we are fairly certain we are dealing with traitsui. --- pyface/ui/wx/workbench/workbench_window_layout.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pyface/ui/wx/workbench/workbench_window_layout.py b/pyface/ui/wx/workbench/workbench_window_layout.py index 126ac92fd..3ae31d4f4 100644 --- a/pyface/ui/wx/workbench/workbench_window_layout.py +++ b/pyface/ui/wx/workbench/workbench_window_layout.py @@ -30,7 +30,6 @@ from pyface.dock.api import DockControl, DockRegion, DockSection from pyface.dock.api import DockSizer from traits.api import Delegate -from traitsui.dockable_view_element import DockableViewElement # Mixin class imports. from pyface.workbench.i_workbench_window_layout import \ @@ -253,7 +252,7 @@ def is_editor_area_visible(self): self.editor_area_id, visible_only=False ) return dock_control.visible - + #### Methods for saving and restoring the layout ########################## def get_view_memento(self): @@ -651,6 +650,7 @@ def _wx_initialize_editor_dock_control(self, editor, editor_dock_control): # fixme: Should we roll the traits UI stuff into the default editor. if hasattr(editor, 'ui') and editor.ui is not None: + from traitsui.dockable_view_element import DockableViewElement # This makes the control draggable outside of the main window. #editor_dock_control.export = 'pyface.workbench.editor' editor_dock_control.dockable = DockableViewElement( @@ -701,6 +701,7 @@ def _wx_initialize_view_dock_control(self, view, view_dock_control): # fixme: Should we roll the traits UI stuff into the default editor. if hasattr(view, 'ui') and view.ui is not None: + from traitsui.dockable_view_element import DockableViewElement # This makes the control draggable outside of the main window. #view_dock_control.export = 'pyface.workbench.view' From f4b43c6d2abbd789a661639af297344a529a0d57 Mon Sep 17 00:00:00 2001 From: Corran Webster Date: Mon, 9 Jan 2017 13:34:31 +0000 Subject: [PATCH 55/60] Defer some traitsui imports within Workbench code. --- pyface/workbench/traits_ui_editor.py | 3 +-- pyface/workbench/traits_ui_view.py | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/pyface/workbench/traits_ui_editor.py b/pyface/workbench/traits_ui_editor.py index 9c56ac620..add2448a0 100644 --- a/pyface/workbench/traits_ui_editor.py +++ b/pyface/workbench/traits_ui_editor.py @@ -6,7 +6,6 @@ # Enthought library imports. from traits.api import Instance, Str -from traitsui.api import UI # Local imports. from .editor import Editor @@ -24,7 +23,7 @@ class TraitsUIEditor(Editor): # The traits UI that represents the editor. # # The framework sets this to the value returned by 'create_ui'. - ui = Instance(UI) + ui = Instance("traitsui.ui.UI") # The name of the traits UI view used to create the UI (if not specified, # the default traits UI view is used). diff --git a/pyface/workbench/traits_ui_view.py b/pyface/workbench/traits_ui_view.py index a5761e3d6..650030f5c 100644 --- a/pyface/workbench/traits_ui_view.py +++ b/pyface/workbench/traits_ui_view.py @@ -6,7 +6,6 @@ # Enthought library imports. from traits.api import Any, Instance, Str -from traitsui.api import UI # Local imports. from .view import View @@ -28,7 +27,7 @@ class TraitsUIView(View): # The traits UI that represents the view. # # The framework sets this to the value returned by 'create_ui'. - ui = Instance(UI) + ui = Instance('traitsui.ui.UI') # The name of the traits UI view used to create the UI (if not specified, # the default traits UI view is used). From 86961489da5fb6e808f5066ed949450eca27bb0d Mon Sep 17 00:00:00 2001 From: Corran Webster Date: Wed, 11 Jan 2017 10:53:08 +0000 Subject: [PATCH 56/60] Remove old-style signals and slots. --- pyface/ui/qt4/workbench/split_tab_widget.py | 15 +++++-------- .../qt4/workbench/workbench_window_layout.py | 22 ++++++------------- 2 files changed, 13 insertions(+), 24 deletions(-) diff --git a/pyface/ui/qt4/workbench/split_tab_widget.py b/pyface/ui/qt4/workbench/split_tab_widget.py index bf45d963c..5570e2afe 100644 --- a/pyface/ui/qt4/workbench/split_tab_widget.py +++ b/pyface/ui/qt4/workbench/split_tab_widget.py @@ -49,9 +49,7 @@ def __init__(self, *args): self.clear() - QtCore.QObject.connect(QtGui.QApplication.instance(), - QtCore.SIGNAL('focusChanged(QWidget *,QWidget *)'), - self._focus_changed) + QtGui.QApplication.instance().focusChanged.connect(self._focus_changed) def clear(self): """ Restore the widget to its pristine state. """ @@ -301,8 +299,7 @@ def _focus_changed(self, old, new): return if self._repeat_focus_changes: - self.emit(QtCore.SIGNAL('focusChanged(QWidget *,QWidget *)'), - old, new) + self.focusChanged.emit(old, new) if new is None: return @@ -324,7 +321,7 @@ def _focus_changed(self, old, new): else: nw = ntw.widget(ntidx) - self.emit(QtCore.SIGNAL('hasFocus'), nw) + self.hasFocus.emit(nw) def _tab_widget_of(self, target): """ Return the tab widget and index of the widget that contains the @@ -874,8 +871,8 @@ def __init__(self, root, parent): # LineEdit to change tab bar title te = _IndependentLineEdit("", self) te.hide() - te.connect(te, QtCore.SIGNAL('editingFinished()'), te, QtCore.SLOT('hide()')) - self.connect(te, QtCore.SIGNAL('returnPressed()'), self._setCurrentTabText) + te.editingFinished.connect(te.hide) + te.returnPressed.connect(self._setCurrentTabText) self._title_edit = te def resizeEvent(self, e): @@ -979,7 +976,7 @@ def _setCurrentTabText(self): idx = self.currentIndex() text = self._title_edit.text() self.setTabText(idx, u'\u25b6'+text) - self._root.emit(QtCore.SIGNAL('tabTextChanged(QWidget *, QString)'), self.parent().widget(idx), text) + self._root.tabTextChanged.emit(self.parent().widget(idx), text) def _resize_title_edit_to_current_tab(self): idx = self.currentIndex() diff --git a/pyface/ui/qt4/workbench/workbench_window_layout.py b/pyface/ui/qt4/workbench/workbench_window_layout.py index 96d4ade29..1770245ad 100755 --- a/pyface/ui/qt4/workbench/workbench_window_layout.py +++ b/pyface/ui/qt4/workbench/workbench_window_layout.py @@ -130,8 +130,7 @@ def close_view(self, view): def close(self): # Don't fire signals for editors that have destroyed their controls. - QtCore.QObject.disconnect(self._qt4_editor_area, - QtCore.SIGNAL('hasFocus'), self._qt4_editor_focus) + self._qt4_editor_area.hasFocus.disconnect(self._qt4_editor_focus) self._qt4_editor_area.clear() @@ -143,20 +142,14 @@ def close(self): def create_initial_layout(self, parent): self._qt4_editor_area = editor_area = SplitTabWidget(parent) - QtCore.QObject.connect(editor_area, QtCore.SIGNAL('hasFocus'), - self._qt4_editor_focus) + editor_area.hasFocus.connect(self._qt4_editor_focus) # We are interested in focus changes but we get them from the editor # area rather than qApp to allow the editor area to restrict them when # needed. - QtCore.QObject.connect( - editor_area, QtCore.SIGNAL('focusChanged(QWidget *,QWidget *)'), - self._qt4_view_focus_changed) - - QtCore.QObject.connect(self._qt4_editor_area, - QtCore.SIGNAL('tabTextChanged(QWidget *, QString)'), - self._qt4_editor_title_changed) + editor_area.focusChanged.connect(self._qt4_view_focus_changed) + editor_area.tabTextChanged.connect(self._qt4_editor_title_changed) editor_area.new_window_request.connect(self._qt4_new_window_request) editor_area.tab_close_request.connect(self._qt4_tab_close_request) editor_area.tab_window_changed.connect(self._qt4_tab_window_changed) @@ -504,10 +497,9 @@ def _qt4_create_view_dock_widget(self, view, size=(-1, -1)): dw = QtGui.QDockWidget(view.name, self.window.control) dw.setWidget(_ViewContainer(size, self.window.control)) dw.setObjectName(view.id) - dw.connect(dw.toggleViewAction(), QtCore.SIGNAL('toggled(bool)'), - self._qt4_handle_dock_visibility) - dw.connect(dw, QtCore.SIGNAL('visibilityChanged(bool)'), - self._qt4_handle_dock_visibility) + dw.toggleViewAction().toggled.connect( + self._qt4_handle_dock_visibility) + dw.visibilityChanged.connect(self._qt4_handle_dock_visibility) # Save the dock window. view._qt4_dock = dw From c2645e52e0392054fcea4719b7a6459e066182b4 Mon Sep 17 00:00:00 2001 From: Robert Kern Date: Mon, 3 Apr 2017 22:49:24 -0700 Subject: [PATCH 57/60] BUG: Restore ad hoc signals in SplitTabWidget. --- pyface/ui/qt4/workbench/split_tab_widget.py | 6 ++++-- pyface/ui/qt4/workbench/workbench_window_layout.py | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/pyface/ui/qt4/workbench/split_tab_widget.py b/pyface/ui/qt4/workbench/split_tab_widget.py index 5570e2afe..4aab6e4af 100644 --- a/pyface/ui/qt4/workbench/split_tab_widget.py +++ b/pyface/ui/qt4/workbench/split_tab_widget.py @@ -29,6 +29,8 @@ class SplitTabWidget(QtGui.QSplitter): new_window_request = QtCore.Signal(QtCore.QPoint, QtGui.QWidget) tab_close_request = QtCore.Signal(QtGui.QWidget) tab_window_changed = QtCore.Signal(QtGui.QWidget) + editor_has_focus = QtCore.Signal(QtGui.QWidget) + focus_changed = QtCore.Signal(QtGui.QWidget, QtGui.QWidget) # The different hotspots of a QTabWidget. An non-negative value is a tab # index and the hotspot is to the left of it. @@ -299,7 +301,7 @@ def _focus_changed(self, old, new): return if self._repeat_focus_changes: - self.focusChanged.emit(old, new) + self.focus_changed.emit(old, new) if new is None: return @@ -321,7 +323,7 @@ def _focus_changed(self, old, new): else: nw = ntw.widget(ntidx) - self.hasFocus.emit(nw) + self.editor_has_focus.emit(nw) def _tab_widget_of(self, target): """ Return the tab widget and index of the widget that contains the diff --git a/pyface/ui/qt4/workbench/workbench_window_layout.py b/pyface/ui/qt4/workbench/workbench_window_layout.py index 1770245ad..52fe8c64e 100755 --- a/pyface/ui/qt4/workbench/workbench_window_layout.py +++ b/pyface/ui/qt4/workbench/workbench_window_layout.py @@ -130,7 +130,7 @@ def close_view(self, view): def close(self): # Don't fire signals for editors that have destroyed their controls. - self._qt4_editor_area.hasFocus.disconnect(self._qt4_editor_focus) + self._qt4_editor_area.editor_has_focus.disconnect(self._qt4_editor_focus) self._qt4_editor_area.clear() @@ -142,12 +142,12 @@ def close(self): def create_initial_layout(self, parent): self._qt4_editor_area = editor_area = SplitTabWidget(parent) - editor_area.hasFocus.connect(self._qt4_editor_focus) + editor_area.editor_has_focus.connect(self._qt4_editor_focus) # We are interested in focus changes but we get them from the editor # area rather than qApp to allow the editor area to restrict them when # needed. - editor_area.focusChanged.connect(self._qt4_view_focus_changed) + editor_area.focus_changed.connect(self._qt4_view_focus_changed) editor_area.tabTextChanged.connect(self._qt4_editor_title_changed) editor_area.new_window_request.connect(self._qt4_new_window_request) From 6ededcace5021574cc48585dc1259398e9db787e Mon Sep 17 00:00:00 2001 From: Corran Webster Date: Thu, 24 Aug 2017 11:10:16 +0100 Subject: [PATCH 58/60] Add pending deprecation warning for Workbench. --- pyface/workbench/__init__.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pyface/workbench/__init__.py b/pyface/workbench/__init__.py index e69de29bb..f9cadef6d 100755 --- a/pyface/workbench/__init__.py +++ b/pyface/workbench/__init__.py @@ -0,0 +1,8 @@ +import warnings + +warnings.warn( + PendingDeprecationWarning( + "Workbench will be moved from pyface.workbench to apptools.workbench " + "in Pyface 7.0.0" + ) +) From fb87810023ce46c4907e57de06ad3edfa552dc3a Mon Sep 17 00:00:00 2001 From: Corran Webster Date: Wed, 3 Jan 2018 10:17:16 +0000 Subject: [PATCH 59/60] Filter history from pyface and rename to apptools directories. --- {pyface => apptools}/workbench/__init__.py | 0 .../tests => apptools/workbench/action}/__init__.py | 0 .../workbench/action/action_controller.py | 0 {pyface => apptools}/workbench/action/api.py | 0 .../action/delete_user_perspective_action.py | 0 .../workbench/action/menu_bar_manager.py | 0 .../workbench/action/new_user_perspective_action.py | 0 .../workbench/action/perspective_menu_manager.py | 0 .../action/rename_user_perspective_action.py | 0 .../action/reset_active_perspective_action.py | 0 .../action/reset_all_perspectives_action.py | 0 .../action/save_as_user_perspective_action.py | 0 .../action/set_active_perspective_action.py | 0 .../workbench/action/setattr_action.py | 0 .../workbench/action/show_view_action.py | 0 .../action/toggle_view_visibility_action.py | 0 .../workbench/action/tool_bar_manager.py | 0 .../workbench/action/user_perspective_action.py | 0 .../workbench/action/user_perspective_name.py | 0 .../workbench/action/view_chooser.py | 0 .../workbench/action/view_menu_manager.py | 0 .../workbench/action/workbench_action.py | 0 {pyface => apptools}/workbench/api.py | 0 .../action => apptools/workbench/debug}/__init__.py | 0 {pyface => apptools}/workbench/debug/api.py | 0 {pyface => apptools}/workbench/debug/debug_view.py | 0 {pyface => apptools}/workbench/editor.py | 0 {pyface => apptools}/workbench/editor_manager.py | 0 {pyface => apptools}/workbench/i_editor.py | 0 {pyface => apptools}/workbench/i_editor_manager.py | 0 {pyface => apptools}/workbench/i_perspective.py | 0 .../workbench/i_perspective_item.py | 0 {pyface => apptools}/workbench/i_view.py | 0 {pyface => apptools}/workbench/i_workbench.py | 0 {pyface => apptools}/workbench/i_workbench_part.py | 0 .../workbench/i_workbench_window_layout.py | 0 {pyface => apptools}/workbench/perspective.py | 0 {pyface => apptools}/workbench/perspective_item.py | 0 .../workbench/qt4}/__init__.py | 0 .../workbench => apptools/workbench/qt4}/editor.py | 0 .../workbench/qt4}/images/spinner.gif | Bin .../workbench/qt4}/split_tab_widget.py | 0 .../workbench/qt4/tests}/__init__.py | 0 .../qt4}/tests/test_workbench_window_layout.py | 0 .../workbench => apptools/workbench/qt4}/view.py | 0 .../workbench/qt4}/workbench_window_layout.py | 0 {pyface => apptools}/workbench/tests/__init__.py | 0 .../workbench/tests/test_workbench_window.py | 0 {pyface => apptools}/workbench/traits_ui_editor.py | 0 {pyface => apptools}/workbench/traits_ui_view.py | 0 .../workbench/user_perspective_manager.py | 0 {pyface => apptools}/workbench/view.py | 0 {pyface => apptools}/workbench/window_event.py | 0 {pyface => apptools}/workbench/workbench.py | 0 {pyface => apptools}/workbench/workbench_window.py | 0 .../workbench/workbench_window_layout.py | 0 .../workbench/workbench_window_memento.py | 0 .../workbench => apptools/workbench/wx}/__init__.py | 0 .../workbench => apptools/workbench/wx}/editor.py | 0 .../workbench/wx}/editor_set_structure_handler.py | 0 .../wx/workbench => apptools/workbench/wx}/view.py | 0 .../workbench/wx}/view_set_structure_handler.py | 0 .../workbench/wx}/workbench_dock_window.py | 0 .../workbench/wx}/workbench_window_layout.py | 0 64 files changed, 0 insertions(+), 0 deletions(-) rename {pyface => apptools}/workbench/__init__.py (100%) rename {pyface/ui/qt4/workbench/tests => apptools/workbench/action}/__init__.py (100%) mode change 100644 => 100755 rename {pyface => apptools}/workbench/action/action_controller.py (100%) rename {pyface => apptools}/workbench/action/api.py (100%) rename {pyface => apptools}/workbench/action/delete_user_perspective_action.py (100%) rename {pyface => apptools}/workbench/action/menu_bar_manager.py (100%) rename {pyface => apptools}/workbench/action/new_user_perspective_action.py (100%) rename {pyface => apptools}/workbench/action/perspective_menu_manager.py (100%) rename {pyface => apptools}/workbench/action/rename_user_perspective_action.py (100%) rename {pyface => apptools}/workbench/action/reset_active_perspective_action.py (100%) rename {pyface => apptools}/workbench/action/reset_all_perspectives_action.py (100%) rename {pyface => apptools}/workbench/action/save_as_user_perspective_action.py (100%) rename {pyface => apptools}/workbench/action/set_active_perspective_action.py (100%) rename {pyface => apptools}/workbench/action/setattr_action.py (100%) rename {pyface => apptools}/workbench/action/show_view_action.py (100%) rename {pyface => apptools}/workbench/action/toggle_view_visibility_action.py (100%) rename {pyface => apptools}/workbench/action/tool_bar_manager.py (100%) rename {pyface => apptools}/workbench/action/user_perspective_action.py (100%) rename {pyface => apptools}/workbench/action/user_perspective_name.py (100%) rename {pyface => apptools}/workbench/action/view_chooser.py (100%) rename {pyface => apptools}/workbench/action/view_menu_manager.py (100%) rename {pyface => apptools}/workbench/action/workbench_action.py (100%) rename {pyface => apptools}/workbench/api.py (100%) rename {pyface/workbench/action => apptools/workbench/debug}/__init__.py (100%) mode change 100755 => 100644 rename {pyface => apptools}/workbench/debug/api.py (100%) rename {pyface => apptools}/workbench/debug/debug_view.py (100%) rename {pyface => apptools}/workbench/editor.py (100%) rename {pyface => apptools}/workbench/editor_manager.py (100%) rename {pyface => apptools}/workbench/i_editor.py (100%) rename {pyface => apptools}/workbench/i_editor_manager.py (100%) rename {pyface => apptools}/workbench/i_perspective.py (100%) rename {pyface => apptools}/workbench/i_perspective_item.py (100%) rename {pyface => apptools}/workbench/i_view.py (100%) rename {pyface => apptools}/workbench/i_workbench.py (100%) rename {pyface => apptools}/workbench/i_workbench_part.py (100%) rename {pyface => apptools}/workbench/i_workbench_window_layout.py (100%) rename {pyface => apptools}/workbench/perspective.py (100%) rename {pyface => apptools}/workbench/perspective_item.py (100%) rename {pyface/ui/qt4/workbench => apptools/workbench/qt4}/__init__.py (100%) rename {pyface/ui/qt4/workbench => apptools/workbench/qt4}/editor.py (100%) rename {pyface/ui/qt4/workbench => apptools/workbench/qt4}/images/spinner.gif (100%) rename {pyface/ui/qt4/workbench => apptools/workbench/qt4}/split_tab_widget.py (100%) rename {pyface/workbench/debug => apptools/workbench/qt4/tests}/__init__.py (100%) rename {pyface/ui/qt4/workbench => apptools/workbench/qt4}/tests/test_workbench_window_layout.py (100%) rename {pyface/ui/qt4/workbench => apptools/workbench/qt4}/view.py (100%) rename {pyface/ui/qt4/workbench => apptools/workbench/qt4}/workbench_window_layout.py (100%) rename {pyface => apptools}/workbench/tests/__init__.py (100%) rename {pyface => apptools}/workbench/tests/test_workbench_window.py (100%) rename {pyface => apptools}/workbench/traits_ui_editor.py (100%) rename {pyface => apptools}/workbench/traits_ui_view.py (100%) rename {pyface => apptools}/workbench/user_perspective_manager.py (100%) rename {pyface => apptools}/workbench/view.py (100%) rename {pyface => apptools}/workbench/window_event.py (100%) rename {pyface => apptools}/workbench/workbench.py (100%) rename {pyface => apptools}/workbench/workbench_window.py (100%) rename {pyface => apptools}/workbench/workbench_window_layout.py (100%) rename {pyface => apptools}/workbench/workbench_window_memento.py (100%) rename {pyface/ui/wx/workbench => apptools/workbench/wx}/__init__.py (100%) rename {pyface/ui/wx/workbench => apptools/workbench/wx}/editor.py (100%) rename {pyface/ui/wx/workbench => apptools/workbench/wx}/editor_set_structure_handler.py (100%) rename {pyface/ui/wx/workbench => apptools/workbench/wx}/view.py (100%) rename {pyface/ui/wx/workbench => apptools/workbench/wx}/view_set_structure_handler.py (100%) rename {pyface/ui/wx/workbench => apptools/workbench/wx}/workbench_dock_window.py (100%) rename {pyface/ui/wx/workbench => apptools/workbench/wx}/workbench_window_layout.py (100%) diff --git a/pyface/workbench/__init__.py b/apptools/workbench/__init__.py similarity index 100% rename from pyface/workbench/__init__.py rename to apptools/workbench/__init__.py diff --git a/pyface/ui/qt4/workbench/tests/__init__.py b/apptools/workbench/action/__init__.py old mode 100644 new mode 100755 similarity index 100% rename from pyface/ui/qt4/workbench/tests/__init__.py rename to apptools/workbench/action/__init__.py diff --git a/pyface/workbench/action/action_controller.py b/apptools/workbench/action/action_controller.py similarity index 100% rename from pyface/workbench/action/action_controller.py rename to apptools/workbench/action/action_controller.py diff --git a/pyface/workbench/action/api.py b/apptools/workbench/action/api.py similarity index 100% rename from pyface/workbench/action/api.py rename to apptools/workbench/action/api.py diff --git a/pyface/workbench/action/delete_user_perspective_action.py b/apptools/workbench/action/delete_user_perspective_action.py similarity index 100% rename from pyface/workbench/action/delete_user_perspective_action.py rename to apptools/workbench/action/delete_user_perspective_action.py diff --git a/pyface/workbench/action/menu_bar_manager.py b/apptools/workbench/action/menu_bar_manager.py similarity index 100% rename from pyface/workbench/action/menu_bar_manager.py rename to apptools/workbench/action/menu_bar_manager.py diff --git a/pyface/workbench/action/new_user_perspective_action.py b/apptools/workbench/action/new_user_perspective_action.py similarity index 100% rename from pyface/workbench/action/new_user_perspective_action.py rename to apptools/workbench/action/new_user_perspective_action.py diff --git a/pyface/workbench/action/perspective_menu_manager.py b/apptools/workbench/action/perspective_menu_manager.py similarity index 100% rename from pyface/workbench/action/perspective_menu_manager.py rename to apptools/workbench/action/perspective_menu_manager.py diff --git a/pyface/workbench/action/rename_user_perspective_action.py b/apptools/workbench/action/rename_user_perspective_action.py similarity index 100% rename from pyface/workbench/action/rename_user_perspective_action.py rename to apptools/workbench/action/rename_user_perspective_action.py diff --git a/pyface/workbench/action/reset_active_perspective_action.py b/apptools/workbench/action/reset_active_perspective_action.py similarity index 100% rename from pyface/workbench/action/reset_active_perspective_action.py rename to apptools/workbench/action/reset_active_perspective_action.py diff --git a/pyface/workbench/action/reset_all_perspectives_action.py b/apptools/workbench/action/reset_all_perspectives_action.py similarity index 100% rename from pyface/workbench/action/reset_all_perspectives_action.py rename to apptools/workbench/action/reset_all_perspectives_action.py diff --git a/pyface/workbench/action/save_as_user_perspective_action.py b/apptools/workbench/action/save_as_user_perspective_action.py similarity index 100% rename from pyface/workbench/action/save_as_user_perspective_action.py rename to apptools/workbench/action/save_as_user_perspective_action.py diff --git a/pyface/workbench/action/set_active_perspective_action.py b/apptools/workbench/action/set_active_perspective_action.py similarity index 100% rename from pyface/workbench/action/set_active_perspective_action.py rename to apptools/workbench/action/set_active_perspective_action.py diff --git a/pyface/workbench/action/setattr_action.py b/apptools/workbench/action/setattr_action.py similarity index 100% rename from pyface/workbench/action/setattr_action.py rename to apptools/workbench/action/setattr_action.py diff --git a/pyface/workbench/action/show_view_action.py b/apptools/workbench/action/show_view_action.py similarity index 100% rename from pyface/workbench/action/show_view_action.py rename to apptools/workbench/action/show_view_action.py diff --git a/pyface/workbench/action/toggle_view_visibility_action.py b/apptools/workbench/action/toggle_view_visibility_action.py similarity index 100% rename from pyface/workbench/action/toggle_view_visibility_action.py rename to apptools/workbench/action/toggle_view_visibility_action.py diff --git a/pyface/workbench/action/tool_bar_manager.py b/apptools/workbench/action/tool_bar_manager.py similarity index 100% rename from pyface/workbench/action/tool_bar_manager.py rename to apptools/workbench/action/tool_bar_manager.py diff --git a/pyface/workbench/action/user_perspective_action.py b/apptools/workbench/action/user_perspective_action.py similarity index 100% rename from pyface/workbench/action/user_perspective_action.py rename to apptools/workbench/action/user_perspective_action.py diff --git a/pyface/workbench/action/user_perspective_name.py b/apptools/workbench/action/user_perspective_name.py similarity index 100% rename from pyface/workbench/action/user_perspective_name.py rename to apptools/workbench/action/user_perspective_name.py diff --git a/pyface/workbench/action/view_chooser.py b/apptools/workbench/action/view_chooser.py similarity index 100% rename from pyface/workbench/action/view_chooser.py rename to apptools/workbench/action/view_chooser.py diff --git a/pyface/workbench/action/view_menu_manager.py b/apptools/workbench/action/view_menu_manager.py similarity index 100% rename from pyface/workbench/action/view_menu_manager.py rename to apptools/workbench/action/view_menu_manager.py diff --git a/pyface/workbench/action/workbench_action.py b/apptools/workbench/action/workbench_action.py similarity index 100% rename from pyface/workbench/action/workbench_action.py rename to apptools/workbench/action/workbench_action.py diff --git a/pyface/workbench/api.py b/apptools/workbench/api.py similarity index 100% rename from pyface/workbench/api.py rename to apptools/workbench/api.py diff --git a/pyface/workbench/action/__init__.py b/apptools/workbench/debug/__init__.py old mode 100755 new mode 100644 similarity index 100% rename from pyface/workbench/action/__init__.py rename to apptools/workbench/debug/__init__.py diff --git a/pyface/workbench/debug/api.py b/apptools/workbench/debug/api.py similarity index 100% rename from pyface/workbench/debug/api.py rename to apptools/workbench/debug/api.py diff --git a/pyface/workbench/debug/debug_view.py b/apptools/workbench/debug/debug_view.py similarity index 100% rename from pyface/workbench/debug/debug_view.py rename to apptools/workbench/debug/debug_view.py diff --git a/pyface/workbench/editor.py b/apptools/workbench/editor.py similarity index 100% rename from pyface/workbench/editor.py rename to apptools/workbench/editor.py diff --git a/pyface/workbench/editor_manager.py b/apptools/workbench/editor_manager.py similarity index 100% rename from pyface/workbench/editor_manager.py rename to apptools/workbench/editor_manager.py diff --git a/pyface/workbench/i_editor.py b/apptools/workbench/i_editor.py similarity index 100% rename from pyface/workbench/i_editor.py rename to apptools/workbench/i_editor.py diff --git a/pyface/workbench/i_editor_manager.py b/apptools/workbench/i_editor_manager.py similarity index 100% rename from pyface/workbench/i_editor_manager.py rename to apptools/workbench/i_editor_manager.py diff --git a/pyface/workbench/i_perspective.py b/apptools/workbench/i_perspective.py similarity index 100% rename from pyface/workbench/i_perspective.py rename to apptools/workbench/i_perspective.py diff --git a/pyface/workbench/i_perspective_item.py b/apptools/workbench/i_perspective_item.py similarity index 100% rename from pyface/workbench/i_perspective_item.py rename to apptools/workbench/i_perspective_item.py diff --git a/pyface/workbench/i_view.py b/apptools/workbench/i_view.py similarity index 100% rename from pyface/workbench/i_view.py rename to apptools/workbench/i_view.py diff --git a/pyface/workbench/i_workbench.py b/apptools/workbench/i_workbench.py similarity index 100% rename from pyface/workbench/i_workbench.py rename to apptools/workbench/i_workbench.py diff --git a/pyface/workbench/i_workbench_part.py b/apptools/workbench/i_workbench_part.py similarity index 100% rename from pyface/workbench/i_workbench_part.py rename to apptools/workbench/i_workbench_part.py diff --git a/pyface/workbench/i_workbench_window_layout.py b/apptools/workbench/i_workbench_window_layout.py similarity index 100% rename from pyface/workbench/i_workbench_window_layout.py rename to apptools/workbench/i_workbench_window_layout.py diff --git a/pyface/workbench/perspective.py b/apptools/workbench/perspective.py similarity index 100% rename from pyface/workbench/perspective.py rename to apptools/workbench/perspective.py diff --git a/pyface/workbench/perspective_item.py b/apptools/workbench/perspective_item.py similarity index 100% rename from pyface/workbench/perspective_item.py rename to apptools/workbench/perspective_item.py diff --git a/pyface/ui/qt4/workbench/__init__.py b/apptools/workbench/qt4/__init__.py similarity index 100% rename from pyface/ui/qt4/workbench/__init__.py rename to apptools/workbench/qt4/__init__.py diff --git a/pyface/ui/qt4/workbench/editor.py b/apptools/workbench/qt4/editor.py similarity index 100% rename from pyface/ui/qt4/workbench/editor.py rename to apptools/workbench/qt4/editor.py diff --git a/pyface/ui/qt4/workbench/images/spinner.gif b/apptools/workbench/qt4/images/spinner.gif similarity index 100% rename from pyface/ui/qt4/workbench/images/spinner.gif rename to apptools/workbench/qt4/images/spinner.gif diff --git a/pyface/ui/qt4/workbench/split_tab_widget.py b/apptools/workbench/qt4/split_tab_widget.py similarity index 100% rename from pyface/ui/qt4/workbench/split_tab_widget.py rename to apptools/workbench/qt4/split_tab_widget.py diff --git a/pyface/workbench/debug/__init__.py b/apptools/workbench/qt4/tests/__init__.py similarity index 100% rename from pyface/workbench/debug/__init__.py rename to apptools/workbench/qt4/tests/__init__.py diff --git a/pyface/ui/qt4/workbench/tests/test_workbench_window_layout.py b/apptools/workbench/qt4/tests/test_workbench_window_layout.py similarity index 100% rename from pyface/ui/qt4/workbench/tests/test_workbench_window_layout.py rename to apptools/workbench/qt4/tests/test_workbench_window_layout.py diff --git a/pyface/ui/qt4/workbench/view.py b/apptools/workbench/qt4/view.py similarity index 100% rename from pyface/ui/qt4/workbench/view.py rename to apptools/workbench/qt4/view.py diff --git a/pyface/ui/qt4/workbench/workbench_window_layout.py b/apptools/workbench/qt4/workbench_window_layout.py similarity index 100% rename from pyface/ui/qt4/workbench/workbench_window_layout.py rename to apptools/workbench/qt4/workbench_window_layout.py diff --git a/pyface/workbench/tests/__init__.py b/apptools/workbench/tests/__init__.py similarity index 100% rename from pyface/workbench/tests/__init__.py rename to apptools/workbench/tests/__init__.py diff --git a/pyface/workbench/tests/test_workbench_window.py b/apptools/workbench/tests/test_workbench_window.py similarity index 100% rename from pyface/workbench/tests/test_workbench_window.py rename to apptools/workbench/tests/test_workbench_window.py diff --git a/pyface/workbench/traits_ui_editor.py b/apptools/workbench/traits_ui_editor.py similarity index 100% rename from pyface/workbench/traits_ui_editor.py rename to apptools/workbench/traits_ui_editor.py diff --git a/pyface/workbench/traits_ui_view.py b/apptools/workbench/traits_ui_view.py similarity index 100% rename from pyface/workbench/traits_ui_view.py rename to apptools/workbench/traits_ui_view.py diff --git a/pyface/workbench/user_perspective_manager.py b/apptools/workbench/user_perspective_manager.py similarity index 100% rename from pyface/workbench/user_perspective_manager.py rename to apptools/workbench/user_perspective_manager.py diff --git a/pyface/workbench/view.py b/apptools/workbench/view.py similarity index 100% rename from pyface/workbench/view.py rename to apptools/workbench/view.py diff --git a/pyface/workbench/window_event.py b/apptools/workbench/window_event.py similarity index 100% rename from pyface/workbench/window_event.py rename to apptools/workbench/window_event.py diff --git a/pyface/workbench/workbench.py b/apptools/workbench/workbench.py similarity index 100% rename from pyface/workbench/workbench.py rename to apptools/workbench/workbench.py diff --git a/pyface/workbench/workbench_window.py b/apptools/workbench/workbench_window.py similarity index 100% rename from pyface/workbench/workbench_window.py rename to apptools/workbench/workbench_window.py diff --git a/pyface/workbench/workbench_window_layout.py b/apptools/workbench/workbench_window_layout.py similarity index 100% rename from pyface/workbench/workbench_window_layout.py rename to apptools/workbench/workbench_window_layout.py diff --git a/pyface/workbench/workbench_window_memento.py b/apptools/workbench/workbench_window_memento.py similarity index 100% rename from pyface/workbench/workbench_window_memento.py rename to apptools/workbench/workbench_window_memento.py diff --git a/pyface/ui/wx/workbench/__init__.py b/apptools/workbench/wx/__init__.py similarity index 100% rename from pyface/ui/wx/workbench/__init__.py rename to apptools/workbench/wx/__init__.py diff --git a/pyface/ui/wx/workbench/editor.py b/apptools/workbench/wx/editor.py similarity index 100% rename from pyface/ui/wx/workbench/editor.py rename to apptools/workbench/wx/editor.py diff --git a/pyface/ui/wx/workbench/editor_set_structure_handler.py b/apptools/workbench/wx/editor_set_structure_handler.py similarity index 100% rename from pyface/ui/wx/workbench/editor_set_structure_handler.py rename to apptools/workbench/wx/editor_set_structure_handler.py diff --git a/pyface/ui/wx/workbench/view.py b/apptools/workbench/wx/view.py similarity index 100% rename from pyface/ui/wx/workbench/view.py rename to apptools/workbench/wx/view.py diff --git a/pyface/ui/wx/workbench/view_set_structure_handler.py b/apptools/workbench/wx/view_set_structure_handler.py similarity index 100% rename from pyface/ui/wx/workbench/view_set_structure_handler.py rename to apptools/workbench/wx/view_set_structure_handler.py diff --git a/pyface/ui/wx/workbench/workbench_dock_window.py b/apptools/workbench/wx/workbench_dock_window.py similarity index 100% rename from pyface/ui/wx/workbench/workbench_dock_window.py rename to apptools/workbench/wx/workbench_dock_window.py diff --git a/pyface/ui/wx/workbench/workbench_window_layout.py b/apptools/workbench/wx/workbench_window_layout.py similarity index 100% rename from pyface/ui/wx/workbench/workbench_window_layout.py rename to apptools/workbench/wx/workbench_window_layout.py From d3947d3e663f264ae8304dbaf0ecb7d7dcf7fbeb Mon Sep 17 00:00:00 2001 From: Corran Webster Date: Wed, 3 Jan 2018 10:33:18 +0000 Subject: [PATCH 60/60] First cut at fixing imports. --- apptools/workbench/__init__.py | 8 --- .../workbench/action/action_controller.py | 7 +-- .../action/delete_user_perspective_action.py | 2 +- apptools/workbench/action/menu_bar_manager.py | 2 +- .../action/new_user_perspective_action.py | 2 +- .../action/perspective_menu_manager.py | 2 +- .../action/rename_user_perspective_action.py | 2 +- .../action/reset_active_perspective_action.py | 2 +- .../action/reset_all_perspectives_action.py | 2 +- .../action/save_as_user_perspective_action.py | 2 +- .../action/set_active_perspective_action.py | 4 +- apptools/workbench/action/show_view_action.py | 4 +- .../action/toggle_view_visibility_action.py | 2 +- apptools/workbench/action/tool_bar_manager.py | 2 +- apptools/workbench/action/view_chooser.py | 8 +-- .../workbench/action/view_menu_manager.py | 2 +- apptools/workbench/action/workbench_action.py | 5 +- apptools/workbench/debug/debug_view.py | 3 +- apptools/workbench/editor.py | 3 +- apptools/workbench/editor_manager.py | 5 +- apptools/workbench/i_editor_manager.py | 2 +- apptools/workbench/i_workbench_part.py | 2 +- .../workbench/i_workbench_window_layout.py | 2 +- apptools/workbench/null/__init__.py | 0 apptools/workbench/perspective.py | 2 +- apptools/workbench/qt4/editor.py | 3 +- .../qt4/tests/test_workbench_window_layout.py | 4 +- apptools/workbench/qt4/view.py | 3 +- .../workbench/qt4/workbench_window_layout.py | 3 +- .../workbench/tests/test_workbench_window.py | 35 ++++++------ apptools/workbench/toolkit.py | 57 +++++++++++++++++++ .../workbench/user_perspective_manager.py | 3 +- apptools/workbench/workbench_window.py | 2 +- apptools/workbench/wx/editor.py | 2 +- apptools/workbench/wx/view.py | 3 +- .../workbench/wx/workbench_window_layout.py | 6 +- 36 files changed, 121 insertions(+), 77 deletions(-) create mode 100644 apptools/workbench/null/__init__.py create mode 100644 apptools/workbench/toolkit.py diff --git a/apptools/workbench/__init__.py b/apptools/workbench/__init__.py index f9cadef6d..e69de29bb 100755 --- a/apptools/workbench/__init__.py +++ b/apptools/workbench/__init__.py @@ -1,8 +0,0 @@ -import warnings - -warnings.warn( - PendingDeprecationWarning( - "Workbench will be moved from pyface.workbench to apptools.workbench " - "in Pyface 7.0.0" - ) -) diff --git a/apptools/workbench/action/action_controller.py b/apptools/workbench/action/action_controller.py index 438c0bc86..f8bfe927e 100644 --- a/apptools/workbench/action/action_controller.py +++ b/apptools/workbench/action/action_controller.py @@ -3,8 +3,9 @@ # Enthought library imports. from pyface.action.api import ActionController -from pyface.workbench.api import WorkbenchWindow -from traits.api import HasTraits, Instance +from traits.api import Instance + +from apptools.workbench.api import WorkbenchWindow class ActionController(ActionController): @@ -33,5 +34,3 @@ def perform(self, action, event): event.window = self.window return action.perform(event) - -#### EOF ###################################################################### diff --git a/apptools/workbench/action/delete_user_perspective_action.py b/apptools/workbench/action/delete_user_perspective_action.py index d9638a389..7e1d9241c 100644 --- a/apptools/workbench/action/delete_user_perspective_action.py +++ b/apptools/workbench/action/delete_user_perspective_action.py @@ -22,7 +22,7 @@ class DeleteUserPerspectiveAction(UserPerspectiveAction): #### 'Action' interface ################################################### # The action's unique identifier (may be None). - id = 'pyface.workbench.action.delete_user_perspective_action' + id = 'apptools.workbench.action.delete_user_perspective_action' # The action's name (displayed on menus/tool bar tools etc). name = 'Delete Perspective' diff --git a/apptools/workbench/action/menu_bar_manager.py b/apptools/workbench/action/menu_bar_manager.py index 312662c58..237c57e49 100644 --- a/apptools/workbench/action/menu_bar_manager.py +++ b/apptools/workbench/action/menu_bar_manager.py @@ -15,7 +15,7 @@ class MenuBarManager(BaseMenuBarManager): #### 'MenuBarManager' interface ########################################### # The workbench window that we are the menu bar manager for. - window = Instance('pyface.workbench.api.WorkbenchWindow') + window = Instance('apptools.workbench.api.WorkbenchWindow') ########################################################################### # 'MenuBarManager' interface. diff --git a/apptools/workbench/action/new_user_perspective_action.py b/apptools/workbench/action/new_user_perspective_action.py index aeb1af2c3..711b99938 100644 --- a/apptools/workbench/action/new_user_perspective_action.py +++ b/apptools/workbench/action/new_user_perspective_action.py @@ -20,7 +20,7 @@ class NewUserPerspectiveAction(WorkbenchAction): #### 'Action' interface ################################################### # The action's unique identifier. - id = 'pyface.workbench.action.new_user_perspective_action' + id = 'apptools.workbench.action.new_user_perspective_action' # The action's name. name = 'New Perspective...' diff --git a/apptools/workbench/action/perspective_menu_manager.py b/apptools/workbench/action/perspective_menu_manager.py index a84433018..32adf0ea2 100644 --- a/apptools/workbench/action/perspective_menu_manager.py +++ b/apptools/workbench/action/perspective_menu_manager.py @@ -34,7 +34,7 @@ class PerspectiveMenuManager(MenuManager): #### 'PerspectiveMenuManager' interface ################################### # The workbench window that the manager is part of. - window = Instance('pyface.workbench.api.WorkbenchWindow') + window = Instance('apptools.workbench.api.WorkbenchWindow') ########################################################################### # 'ActionManager' interface. diff --git a/apptools/workbench/action/rename_user_perspective_action.py b/apptools/workbench/action/rename_user_perspective_action.py index 7d2a645b2..8106faf7f 100644 --- a/apptools/workbench/action/rename_user_perspective_action.py +++ b/apptools/workbench/action/rename_user_perspective_action.py @@ -20,7 +20,7 @@ class RenameUserPerspectiveAction(UserPerspectiveAction): #### 'Action' interface ################################################### # The action's unique identifier (may be None). - id = 'pyface.workbench.action.rename_user_perspective_action' + id = 'apptools.workbench.action.rename_user_perspective_action' # The action's name (displayed on menus/tool bar tools etc). name = 'Rename Perspective...' diff --git a/apptools/workbench/action/reset_active_perspective_action.py b/apptools/workbench/action/reset_active_perspective_action.py index 824daa6ac..a3074d009 100644 --- a/apptools/workbench/action/reset_active_perspective_action.py +++ b/apptools/workbench/action/reset_active_perspective_action.py @@ -18,7 +18,7 @@ class ResetActivePerspectiveAction(WorkbenchAction): #### 'Action' interface ################################################### # The action's unique identifier (may be None). - id = 'pyface.workbench.action.reset_active_perspective' + id = 'apptools.workbench.action.reset_active_perspective' # The action's name (displayed on menus/tool bar tools etc). name = 'Reset Perspective' diff --git a/apptools/workbench/action/reset_all_perspectives_action.py b/apptools/workbench/action/reset_all_perspectives_action.py index 1cfc99a17..a5c75154e 100644 --- a/apptools/workbench/action/reset_all_perspectives_action.py +++ b/apptools/workbench/action/reset_all_perspectives_action.py @@ -18,7 +18,7 @@ class ResetAllPerspectivesAction(WorkbenchAction): #### 'Action' interface ################################################### # The action's unique identifier (may be None). - id = 'pyface.workbench.action.reset_all_perspectives' + id = 'apptools.workbench.action.reset_all_perspectives' # The action's name (displayed on menus/tool bar tools etc). name = 'Reset All Perspectives' diff --git a/apptools/workbench/action/save_as_user_perspective_action.py b/apptools/workbench/action/save_as_user_perspective_action.py index bba4e7c53..5a0da8202 100644 --- a/apptools/workbench/action/save_as_user_perspective_action.py +++ b/apptools/workbench/action/save_as_user_perspective_action.py @@ -20,7 +20,7 @@ class SaveAsUserPerspectiveAction(WorkbenchAction): #### 'Action' interface ################################################### # The action's unique identifier. - id = 'pyface.workbench.action.save_as_user_perspective_action' + id = 'apptools.workbench.action.save_as_user_perspective_action' # The action's name (displayed on menus/tool bar tools etc). name = 'Save Perspective As...' diff --git a/apptools/workbench/action/set_active_perspective_action.py b/apptools/workbench/action/set_active_perspective_action.py index 8fa6309d8..3cd871198 100644 --- a/apptools/workbench/action/set_active_perspective_action.py +++ b/apptools/workbench/action/set_active_perspective_action.py @@ -1,11 +1,9 @@ """ An action that sets the active perspective. """ -# Enthought library imports. -from pyface.workbench.api import IPerspective from traits.api import Delegate, Instance, on_trait_change -# Local imports. +from apptools.workbench.api import IPerspective from .workbench_action import WorkbenchAction diff --git a/apptools/workbench/action/show_view_action.py b/apptools/workbench/action/show_view_action.py index ddd996150..a87741d13 100644 --- a/apptools/workbench/action/show_view_action.py +++ b/apptools/workbench/action/show_view_action.py @@ -12,7 +12,7 @@ class ShowViewAction(WorkbenchAction): #### 'Action' interface ################################################### # The action's unique identifier (may be None). - id = 'pyface.workbench.action.show_view' + id = 'apptools.workbench.action.show_view' # The action's name (displayed on menus/tool bar tools etc). name = 'Show View' @@ -40,5 +40,3 @@ def perform(self, event): chooser.view.activate() return - -#### EOF ###################################################################### diff --git a/apptools/workbench/action/toggle_view_visibility_action.py b/apptools/workbench/action/toggle_view_visibility_action.py index ef1f26f1b..6005fbdef 100644 --- a/apptools/workbench/action/toggle_view_visibility_action.py +++ b/apptools/workbench/action/toggle_view_visibility_action.py @@ -2,10 +2,10 @@ # Enthought library imports. -from pyface.workbench.api import IView from traits.api import Delegate, Instance # Local imports. +from apptools.workbench.api import IView from .workbench_action import WorkbenchAction diff --git a/apptools/workbench/action/tool_bar_manager.py b/apptools/workbench/action/tool_bar_manager.py index 21f1ea34a..cc6c9b0b4 100644 --- a/apptools/workbench/action/tool_bar_manager.py +++ b/apptools/workbench/action/tool_bar_manager.py @@ -15,7 +15,7 @@ class ToolBarManager(pyface.ToolBarManager): #### 'ToolBarManager' interface ########################################### # The workbench window that we are the tool bar manager for. - window = Instance('pyface.workbench.api.WorkbenchWindow') + window = Instance('apptools.workbench.api.WorkbenchWindow') ########################################################################### # 'ToolBarManager' interface. diff --git a/apptools/workbench/action/view_chooser.py b/apptools/workbench/action/view_chooser.py index 2f331b6a3..b78bf39a6 100644 --- a/apptools/workbench/action/view_chooser.py +++ b/apptools/workbench/action/view_chooser.py @@ -2,11 +2,11 @@ # Enthought library imports. -from pyface.workbench.api import IView, WorkbenchWindow from traits.api import Any, HasTraits, Instance, List, Str from traits.api import TraitError, Undefined -from traitsui.api import Item, TreeEditor, TreeNode, View -from traitsui.menu import Action # fixme: Non-api import! +from traitsui.api import Action, Item, TreeEditor, TreeNode, View + +from apptools.workbench.api import IView, WorkbenchWindow class Category(HasTraits): @@ -110,7 +110,7 @@ class ViewChooser(HasTraits): """ # The window that contains the views to choose from. - window = Instance('pyface.workbench.api.WorkbenchWindow') + window = Instance('apptools.workbench.api.WorkbenchWindow') # The currently selected tree item (at any point in time this might be # either None, a view category, or a view). diff --git a/apptools/workbench/action/view_menu_manager.py b/apptools/workbench/action/view_menu_manager.py index 622ee0240..962fc609e 100644 --- a/apptools/workbench/action/view_menu_manager.py +++ b/apptools/workbench/action/view_menu_manager.py @@ -46,7 +46,7 @@ class ViewMenuManager(MenuManager): show_perspective_menu = Bool(True) # The workbench window that the menu is part of. - window = Instance('pyface.workbench.api.WorkbenchWindow') + window = Instance('apptools.workbench.api.WorkbenchWindow') #### 'Private' interface ################################################## diff --git a/apptools/workbench/action/workbench_action.py b/apptools/workbench/action/workbench_action.py index 2e18975d6..f7b79189f 100644 --- a/apptools/workbench/action/workbench_action.py +++ b/apptools/workbench/action/workbench_action.py @@ -2,10 +2,11 @@ # Enthought library imports. -from pyface.workbench.api import WorkbenchWindow from pyface.action.api import Action from traits.api import Instance +from apptools.workbench.api import WorkbenchWindow + class WorkbenchAction(Action): """ Abstract base class for all workbench actions. """ @@ -16,5 +17,3 @@ class WorkbenchAction(Action): # # This is set by the framework. window = Instance(WorkbenchWindow) - -#### EOF ###################################################################### diff --git a/apptools/workbench/debug/debug_view.py b/apptools/workbench/debug/debug_view.py index 67e876b6e..3a5317cfb 100644 --- a/apptools/workbench/debug/debug_view.py +++ b/apptools/workbench/debug/debug_view.py @@ -2,10 +2,11 @@ # Enthought library imports. -from pyface.workbench.api import View, WorkbenchWindow from traits.api import HasTraits, Instance, Str, on_trait_change from traitsui.api import View as TraitsView +from apptools.workbench.api import View, WorkbenchWindow + class DebugViewModel(HasTraits): """ The model for the debug view! """ diff --git a/apptools/workbench/editor.py b/apptools/workbench/editor.py index 8cd0f7b8a..b61b280bd 100755 --- a/apptools/workbench/editor.py +++ b/apptools/workbench/editor.py @@ -16,6 +16,5 @@ # Import the toolkit specific version. from pyface.toolkit import toolkit_object -Editor = toolkit_object('workbench.editor:Editor') -### EOF ####################################################################### +Editor = toolkit_object('workbench.editor:Editor') diff --git a/apptools/workbench/editor_manager.py b/apptools/workbench/editor_manager.py index 0e757afcf..7fe71d196 100755 --- a/apptools/workbench/editor_manager.py +++ b/apptools/workbench/editor_manager.py @@ -14,10 +14,11 @@ @provides(IEditorManager) class EditorManager(HasTraits): """ The default editor manager. """ + #### 'IEditorManager' interface ########################################### # The workbench window that the editor manager manages editors for ;^) - window = Instance('pyface.workbench.api.WorkbenchWindow') + window = Instance('apptools.workbench.api.WorkbenchWindow') ########################################################################### # 'object' interface. @@ -91,5 +92,3 @@ def _is_editing(self, editor, obj, kind): """ Return True if the editor is editing the object. """ return editor.obj == obj - -#### EOF ###################################################################### diff --git a/apptools/workbench/i_editor_manager.py b/apptools/workbench/i_editor_manager.py index def0f3533..b957ebbc6 100755 --- a/apptools/workbench/i_editor_manager.py +++ b/apptools/workbench/i_editor_manager.py @@ -9,7 +9,7 @@ class IEditorManager(Interface): """ The editor manager interface. """ # The workbench window that the editor manager manages editors for ;^) - window = Instance('pyface.workbench.api.WorkbenchWindow') + window = Instance('apptools.workbench.api.WorkbenchWindow') def add_editor(self, editor, kind): """ Registers an existing editor. """ diff --git a/apptools/workbench/i_workbench_part.py b/apptools/workbench/i_workbench_part.py index c4c90bc2b..df186cedb 100644 --- a/apptools/workbench/i_workbench_part.py +++ b/apptools/workbench/i_workbench_part.py @@ -47,7 +47,7 @@ class IWorkbenchPart(Interface): # The workbench window that the part is in. # # The framework sets this when the part is created. - window = Instance('pyface.workbench.api.WorkbenchWindow') + window = Instance('apptools.workbench.api.WorkbenchWindow') #### Methods ############################################################## diff --git a/apptools/workbench/i_workbench_window_layout.py b/apptools/workbench/i_workbench_window_layout.py index 17d36f9e3..151af9a0d 100755 --- a/apptools/workbench/i_workbench_window_layout.py +++ b/apptools/workbench/i_workbench_window_layout.py @@ -37,7 +37,7 @@ class IWorkbenchWindowLayout(Interface): editor_area_id = Str # The workbench window that this is the layout for. - window = Instance('pyface.workbench.api.WorkbenchWindow') + window = Instance('apptools.workbench.api.WorkbenchWindow') #### Events #### diff --git a/apptools/workbench/null/__init__.py b/apptools/workbench/null/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apptools/workbench/perspective.py b/apptools/workbench/perspective.py index 571495622..2707cde48 100755 --- a/apptools/workbench/perspective.py +++ b/apptools/workbench/perspective.py @@ -20,7 +20,7 @@ class Perspective(HasTraits): """ The default perspective. """ # The ID of the default perspective. - DEFAULT_ID = 'pyface.workbench.default' + DEFAULT_ID = 'apptools.workbench.default' # The name of the default perspective. DEFAULT_NAME = 'Default' diff --git a/apptools/workbench/qt4/editor.py b/apptools/workbench/qt4/editor.py index 3e0be2556..6b76af0b9 100644 --- a/apptools/workbench/qt4/editor.py +++ b/apptools/workbench/qt4/editor.py @@ -13,7 +13,8 @@ # Local imports. from traits.api import Event, Bool -from pyface.workbench.i_editor import MEditor + +from apptools.workbench.i_editor import MEditor class Editor(MEditor): diff --git a/apptools/workbench/qt4/tests/test_workbench_window_layout.py b/apptools/workbench/qt4/tests/test_workbench_window_layout.py index 715a88428..806337075 100644 --- a/apptools/workbench/qt4/tests/test_workbench_window_layout.py +++ b/apptools/workbench/qt4/tests/test_workbench_window_layout.py @@ -3,8 +3,8 @@ from traits.testing.unittest_tools import unittest -from pyface.ui.qt4.workbench.split_tab_widget import SplitTabWidget -from pyface.ui.qt4.workbench.workbench_window_layout import \ +from apptools.workbench.qt4.split_tab_widget import SplitTabWidget +from apptools.workbench.qt4.workbench_window_layout import \ WorkbenchWindowLayout diff --git a/apptools/workbench/qt4/view.py b/apptools/workbench/qt4/view.py index 357e43e19..fee60c19a 100644 --- a/apptools/workbench/qt4/view.py +++ b/apptools/workbench/qt4/view.py @@ -11,8 +11,7 @@ #------------------------------------------------------------------------------ -# Enthought library imports. -from pyface.workbench.i_view import MView +from apptools.workbench.i_view import MView class View(MView): diff --git a/apptools/workbench/qt4/workbench_window_layout.py b/apptools/workbench/qt4/workbench_window_layout.py index 52fe8c64e..f477f4b31 100755 --- a/apptools/workbench/qt4/workbench_window_layout.py +++ b/apptools/workbench/qt4/workbench_window_layout.py @@ -22,7 +22,8 @@ # Local imports. from pyface.message_dialog import error -from pyface.workbench.i_workbench_window_layout import \ + +from apptools.workbench.i_workbench_window_layout import \ MWorkbenchWindowLayout from .split_tab_widget import SplitTabWidget diff --git a/apptools/workbench/tests/test_workbench_window.py b/apptools/workbench/tests/test_workbench_window.py index ee5d74dde..a6fac52e0 100644 --- a/apptools/workbench/tests/test_workbench_window.py +++ b/apptools/workbench/tests/test_workbench_window.py @@ -5,12 +5,12 @@ from traits.testing.unittest_tools import unittest, UnittestTools -from pyface.workbench.perspective import Perspective -from pyface.workbench.api import Workbench -from pyface.workbench.user_perspective_manager import UserPerspectiveManager -from pyface.workbench.workbench_window import (WorkbenchWindow, - WorkbenchWindowLayout, - WorkbenchWindowMemento) +from apptools.workbench.perspective import Perspective +from apptools.workbench.api import Workbench +from apptools.workbench.user_perspective_manager import UserPerspectiveManager +from apptools.workbench.workbench_window import ( + WorkbenchWindow, WorkbenchWindowLayout, WorkbenchWindowMemento +) class TestWorkbenchWindowUserPerspective(unittest.TestCase, UnittestTools): @@ -51,7 +51,7 @@ def show_perspective(self, workbench_window, perspective): workbench_window.active_perspective = perspective workbench_window.layout.is_editor_area_visible = mock.MagicMock( return_value=perspective.show_editor_area) - + def test_editor_area_with_perspectives(self): """ Test show_editor_area is respected while switching perspective""" @@ -62,7 +62,7 @@ def test_editor_area_with_perspectives(self): # Add perspectives workbench.user_perspective_manager.add(self.with_editor) workbench.user_perspective_manager.add(self.without_editor) - + # There are the methods we want to test if they are called workbench_window.show_editor_area = mock.MagicMock() workbench_window.hide_editor_area = mock.MagicMock() @@ -73,12 +73,12 @@ def test_editor_area_with_perspectives(self): # Show a perspective with an editor area self.show_perspective(workbench_window, self.with_editor) - + # show_editor_area should be called self.assertTrue(workbench_window.show_editor_area.called) # Show a perspective withOUT an editor area - workbench_window.hide_editor_area.reset_mock() + workbench_window.hide_editor_area.reset_mock() self.show_perspective(workbench_window, self.without_editor) # hide_editor_area should be called @@ -113,18 +113,18 @@ def test_editor_area_restore_from_saved_state(self): "get_toolkit_memento.return_value": (0, dict(geometry=""))} workbench_window.layout.configure_mock(**layout_functions) - + # The following records perspective mementos to workbench_window._memento self.show_perspective(workbench_window, self.without_editor) self.show_perspective(workbench_window, self.with_editor) # Save the window layout to a state file workbench._save_window_layout(workbench_window) - + # We only needed the state file for this test del workbench_window del workbench - + # We create another workbench which uses the state location # and we test if we can retore the saved perspective correctly workbench, workbench_window = self.get_workbench_with_window() @@ -135,7 +135,7 @@ def test_editor_area_restore_from_saved_state(self): # There are the methods we want to test if they are called workbench_window.show_editor_area = mock.MagicMock() workbench_window.hide_editor_area = mock.MagicMock() - + # This restores the perspectives and mementos workbench.create_window() @@ -145,7 +145,7 @@ def test_editor_area_restore_from_saved_state(self): # Perspective mementos should be restored self.assertIn(self.with_editor.id, workbench_window._memento.perspective_mementos) self.assertIn(self.without_editor.id, workbench_window._memento.perspective_mementos) - + # Since the with_editor perspective is used last, # it should be used as initial perspective self.assertTrue(workbench_window.show_editor_area.called) @@ -155,11 +155,10 @@ def test_editor_area_restore_from_saved_state(self): # We need to get them using their id perspective_without_editor = workbench_window.get_perspective_by_id( self.without_editor.id) - + # Show the perspective with editor area - workbench_window.hide_editor_area.reset_mock() + workbench_window.hide_editor_area.reset_mock() self.show_perspective(workbench_window, perspective_without_editor) # make sure hide_editor_area is called self.assertTrue(workbench_window.hide_editor_area.called) - diff --git a/apptools/workbench/toolkit.py b/apptools/workbench/toolkit.py new file mode 100644 index 000000000..34d3510d8 --- /dev/null +++ b/apptools/workbench/toolkit.py @@ -0,0 +1,57 @@ +""" Toolkit management. """ + +# This will determine the Pyface backend, which we will use. +from pyface.toolkit import toolkit_object as pyface_toolkit + + +def toolkit_object(name): + """ Return the toolkit specific object with the given name. + + Parameters + ---------- + name : str + The name consists of the relative module path and the object name + separated by a colon. + """ + import os + import sys + import importlib + + package = 'apptools.workbench.' + pyface_toolkit.toolkit + + mname, oname = name.split(':') + try: + module = importlib.import_module('.' + mname, package) + except ImportError as exc: + # is the error while trying to import package mname or not? + if all(part not in exc.args[0] for part in mname.split('.')): + # something else went wrong - let the exception be raised + raise + + # Ignore *ANY* errors unless a debug ENV variable is set. + if 'ETS_DEBUG' in os.environ: + # Attempt to only skip errors in importing the backend modules. + # The idea here is that this only happens when the last entry in + # the traceback's stack frame mentions the toolkit in question. + import traceback + frames = traceback.extract_tb(sys.exc_traceback) + filename, lineno, function, text = frames[-1] + if not package in filename: + raise + else: + obj = getattr(module, oname, None) + if obj is not None: + return obj + + class Unimplemented(object): + """ An unimplemented toolkit object + + This is returned if an object isn't implemented by the selected + toolkit. It raises an exception if it is ever instantiated. + """ + + def __init__(self, *args, **kwargs): + msg = "the %s workbench backend doesn't implement %s" + raise NotImplementedError(msg % (pyface_toolkit.toolkit, name)) + + return Unimplemented diff --git a/apptools/workbench/user_perspective_manager.py b/apptools/workbench/user_perspective_manager.py index 50072a7f0..8a23de927 100644 --- a/apptools/workbench/user_perspective_manager.py +++ b/apptools/workbench/user_perspective_manager.py @@ -6,10 +6,11 @@ import os # Enthought library imports. -from pyface.workbench.api import Perspective from traits.api import Any, Dict, HasTraits, Int, List, Property from traits.api import Unicode +from apptools.workbench.api import Perspective + # Logging. logger = logging.getLogger(__name__) diff --git a/apptools/workbench/workbench_window.py b/apptools/workbench/workbench_window.py index 3772ce57e..d30f46070 100755 --- a/apptools/workbench/workbench_window.py +++ b/apptools/workbench/workbench_window.py @@ -40,7 +40,7 @@ class WorkbenchWindow(ApplicationWindow): selection = List # The workbench that the window belongs to. - workbench = Instance('pyface.workbench.api.IWorkbench') + workbench = Instance('apptools.workbench.api.IWorkbench') #### Editors ####################### diff --git a/apptools/workbench/wx/editor.py b/apptools/workbench/wx/editor.py index e0d562b13..1bf254efa 100644 --- a/apptools/workbench/wx/editor.py +++ b/apptools/workbench/wx/editor.py @@ -18,7 +18,7 @@ """ # Local imports. -from pyface.workbench.i_editor import MEditor +from apptools.workbench.i_editor import MEditor class Editor(MEditor): diff --git a/apptools/workbench/wx/view.py b/apptools/workbench/wx/view.py index b6e1abe69..23d135c96 100644 --- a/apptools/workbench/wx/view.py +++ b/apptools/workbench/wx/view.py @@ -19,7 +19,8 @@ # Enthought library imports. from traits.api import Bool -from pyface.workbench.i_view import MView + +from apptools.workbench.i_view import MView class View(MView): diff --git a/apptools/workbench/wx/workbench_window_layout.py b/apptools/workbench/wx/workbench_window_layout.py index 3ae31d4f4..fb9e8e93c 100644 --- a/apptools/workbench/wx/workbench_window_layout.py +++ b/apptools/workbench/wx/workbench_window_layout.py @@ -32,7 +32,7 @@ from traits.api import Delegate # Mixin class imports. -from pyface.workbench.i_workbench_window_layout import \ +from apptools.workbench.i_workbench_window_layout import \ MWorkbenchWindowLayout # Local imports. @@ -652,7 +652,7 @@ def _wx_initialize_editor_dock_control(self, editor, editor_dock_control): if hasattr(editor, 'ui') and editor.ui is not None: from traitsui.dockable_view_element import DockableViewElement # This makes the control draggable outside of the main window. - #editor_dock_control.export = 'pyface.workbench.editor' + editor_dock_control.export = 'apptools.workbench.editor' editor_dock_control.dockable = DockableViewElement( should_close=True, ui=editor.ui ) @@ -703,7 +703,7 @@ def _wx_initialize_view_dock_control(self, view, view_dock_control): if hasattr(view, 'ui') and view.ui is not None: from traitsui.dockable_view_element import DockableViewElement # This makes the control draggable outside of the main window. - #view_dock_control.export = 'pyface.workbench.view' + view_dock_control.export = 'apptools.workbench.view' # If the ui's 'view' trait has an 'export' field set, pass that on # to the dock control. This makes the control detachable from the