Skip to content
Browse files

Fix a bug with setting the active tabwidget when an editor contains t…

…abs.

If an editor contains tabs, after certain operations (e.g., navigating
the editor tabs using keyboard shortcuts) a change in focus
sets the editor area pane `active_tabwidget` to one of those internal tabs,
rather than to the tab of the editor in the editor area.
  • Loading branch information...
1 parent 1719195 commit da801013fec24b7ac6c316dbeea053c8f4a30ede @pberkes pberkes committed Jun 20, 2013
View
20 pyface/ui/qt4/tasks/split_editor_area_pane.py
@@ -317,30 +317,40 @@ def _update_tooltip(self, editor, name, new):
#### Signal handlers ######################################################
+ def _find_ancestor_draggable_tab_widget(self, control):
+ """ Find the draggable tab widget to which a widget belongs. """
+
+ while not isinstance(control, DraggableTabWidget):
+ control = control.parent()
+
+ return control
+
def _focus_changed(self, old, new):
- """ Handle an application-level focus change to set the active_tabwidget
+ """Set the active tabwidget after an application-level change in focus.
"""
+
if new:
if isinstance(new, DraggableTabWidget):
if new.editor_area == self:
self.active_tabwidget = new
elif isinstance(new, QtGui.QTabBar):
if self.control.isAncestorOf(new):
- self.active_tabwidget = new.parent()
+ self.active_tabwidget = \
+ self._find_ancestor_draggable_tab_widget(new)
else:
# Check if any of the editor widgets have focus.
# If so, make it active.
for editor in self.editors:
control = editor.control
if control is not None and control.isAncestorOf(new):
- self.active_tabwidget = editor.control.parent().parent()
+ self.active_tabwidget = \
+ self._find_ancestor_draggable_tab_widget(new)
self.active_tabwidget.setCurrentWidget(editor.control)
# Set active_editor at the end so that the notification
# occurs when everything is ready.
self.active_editor = editor
break
-
###############################################################################
# Auxiliary classes.
###############################################################################
@@ -457,7 +467,7 @@ def tabwidget(self):
def tabwidgets(self):
""" Return a list of tabwidgets associated with current splitter or
- any of its descendents.
+ any of its descendants.
"""
tabwidgets = []
if self.is_leaf():
View
85 pyface/ui/qt4/tasks/tests/test_split_editor_area_pane.py
@@ -4,10 +4,59 @@
import tempfile
import unittest
+from traits.api import HasTraits, Instance
+from traitsui.api import Group, UItem, View
+from traitsui.api import Tabbed as TabbedGroup
+
from pyface.qt import QtGui, QtCore
from pyface.tasks.split_editor_area_pane import EditorAreaWidget, \
SplitEditorAreaPane
-from pyface.tasks.api import Editor, PaneItem, Splitter, Tabbed
+from pyface.tasks.api import Editor, PaneItem, Splitter, Tabbed, Task, \
+ TaskWindow
+from pyface.util.guisupport import get_app_qt4
+from pyface.util.testing import event_loop
+
+
+class ViewWithTabs(HasTraits):
+ """ A view with tabs used to confuse the SplitEditorAreaPane. """
+ traits_view = View(
+ TabbedGroup(
+ Group(UItem(label='tab 1')),
+ Group(UItem(label='tab 2')),
+ )
+ )
+
+
+class ViewWithTabsEditor(Editor):
+ """ Test editor, displaying a TraitsUI view with tabs. """
+
+ name = 'Test Editor'
+
+ def create(self, parent):
+ """ Create and set the toolkit-specific contents of the editor.
+ """
+ view = ViewWithTabs()
+ self.ui = view.edit_traits(kind='subpanel', parent=parent)
+ self.control = self.ui.control
+
+ def destroy(self):
+ """ Destroy the toolkit-specific control that represents the editor.
+ """
+ self.control = None
+ self.ui.dispose()
+ self.ui = None
+
+
+class SplitEditorAreaPaneTestTask(Task):
+ """ A test task containing a SplitEditorAreaPane. """
+
+ id = 'test_task'
+ name = 'Test Task'
+
+ editor_area = Instance(SplitEditorAreaPane, ())
+
+ def create_central_pane(self):
+ return self.editor_area
class TestEditorAreaWidget(unittest.TestCase):
@@ -262,5 +311,39 @@ def test_persistence(self):
self.assertEquals(right_bottom.items[0].id, 1)
self.assertEquals(right_bottom.items[1].id, 2)
+ def test_active_tabwidget_after_editor_containing_tabs_gets_focus(self):
+ # Regression test: if an editor contains tabs, a change in focus
+ # sets the editor area pane `active_tabwidget` to one of those tabs,
+ # rather than the editor's tab, after certain operations (e.g.,
+ # navigating the editor tabs using keyboard shortcuts).
+
+ window = TaskWindow()
+
+ task = SplitEditorAreaPaneTestTask()
+ editor_area = task.editor_area
+ window.add_task(task)
+
+ # Show the window.
+ with event_loop():
+ window.open()
+
+ with event_loop():
+ app = get_app_qt4()
+ app.setActiveWindow(window.control)
+
+ # Add and activate an editor which contains tabs.
+ editor = ViewWithTabsEditor()
+ with event_loop():
+ editor_area.add_editor(editor)
+ with event_loop():
+ editor_area.activate_editor(editor)
+
+ # Check that the active tabwidget is the right one.
+ self.assertIs(editor_area.active_tabwidget,
+ editor_area.control.tabwidget())
+
+ with event_loop():
+ window.close()
+
if __name__=="__main__":
unittest.main()
View
47 pyface/util/testing.py
@@ -0,0 +1,47 @@
+# Copyright (c) 2013 by Enthought, Inc., Austin, TX
+# All rights reserved.
+# This file is confidential and NOT open source. Do not distribute.
+""" Tools for testing. """
+
+
+from contextlib import contextmanager
+
+from pyface.qt.QtCore import QTimer
+from pyface.util.guisupport import get_app_qt4
+
+
+@contextmanager
+def event_loop():
+ """ Post and process the Qt events at the exit of the code block. """
+
+ app = get_app_qt4()
+
+ yield
+
+ app.sendPostedEvents()
+ app.processEvents()
+
+
+@contextmanager
+def delete_widget(widget, timeout=1.0):
+ """ Context manager that executes the event loop so that we can safely
+ delete a Qt widget.
+ """
+
+ app = get_app_qt4()
+
+ timer = QTimer()
+ timer.setSingleShot(True)
+ timer.setInterval(timeout*1000)
+ timer.timeout.connect(app.quit)
+ widget.destroyed.connect(app.quit)
+
+ yield
+
+ timer.start()
+ app.exec_()
+
+ if not timer.isActive():
+ # We exited the event loop on timeout.
+ msg = 'Could not destroy widget before timeout: {!r}'
+ raise AssertionError(msg.format(widget))

0 comments on commit da80101

Please sign in to comment.
Something went wrong with that request. Please try again.