Skip to content

Commit

Permalink
Make the closable feature of a DockItem configurable.
Browse files Browse the repository at this point in the history
  • Loading branch information
sccolbert committed May 21, 2013
1 parent f38b19b commit d839fb0
Show file tree
Hide file tree
Showing 5 changed files with 187 additions and 13 deletions.
33 changes: 25 additions & 8 deletions enaml/qt/docking/q_dock_container.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,16 +132,19 @@ def showMaximized(self):
""" Handle the show maximized request for the dock container.
"""
def update_buttons(bar):
buttons = bar.buttons()
buttons |= bar.RestoreButton
buttons &= ~bar.MaximizeButton
bar.setButtons(buttons)
if self.isWindow():
super(QDockContainer, self).showMaximized()
bar = self.dockItem().titleBarWidget()
bar.setButtons(bar.RestoreButton | bar.CloseButton)
update_buttons(self.dockItem().titleBarWidget())
else:
area = self.parentDockArea()
if area is not None:
item = self.dockItem()
bar = item.titleBarWidget()
bar.setButtons(bar.RestoreButton | bar.CloseButton)
update_buttons(item.titleBarWidget())
area.setMaximizedWidget(item)
self.frame_state.item_is_maximized = True
item.installEventFilter(self)
Expand All @@ -150,14 +153,17 @@ def showNormal(self):
""" Handle the show normal request for the dock container.
"""
def update_buttons(bar):
buttons = bar.buttons()
buttons |= bar.MaximizeButton
buttons &= ~bar.RestoreButton
bar.setButtons(buttons)
if self.isWindow():
super(QDockContainer, self).showNormal()
bar = self.dockItem().titleBarWidget()
bar.setButtons(bar.MaximizeButton | bar.CloseButton)
update_buttons(self.dockItem().titleBarWidget())
elif self.frame_state.item_is_maximized:
item = self.dockItem()
bar = item.titleBarWidget()
bar.setButtons(bar.MaximizeButton | bar.CloseButton)
update_buttons(item.titleBarWidget())
self.layout().setWidget(item)
self.item_is_maximized = False
item.removeEventFilter(self)
Expand Down Expand Up @@ -220,6 +226,17 @@ def icon(self):
return item.icon()
return QIcon()

def closable(self):
""" Get whether or not the container is closable.
This proxies the call to the underlying dock item.
"""
item = self.dockItem()
if item is not None:
return item.closable()
return True

def showTitleBar(self):
""" Show the title bar for the container.
Expand Down
53 changes: 53 additions & 0 deletions enaml/qt/docking/q_dock_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,19 @@ def __init__(self, parent=None):
layout.setSizeConstraint(QLayout.SetMinAndMaxSize)
self.setLayout(layout)
self.setTitleBarWidget(QDockTitleBar())
self._closable = True

#--------------------------------------------------------------------------
# Reimplementations
#--------------------------------------------------------------------------
def closeEvent(self, event):
""" Handle the close event for the dock item.
This handler will reject the event if the item is not closable.
"""
if not self._closable:
event.ignore()

#--------------------------------------------------------------------------
# Public API
Expand Down Expand Up @@ -340,6 +353,46 @@ def setIconSize(self, size):
"""
self.titleBarWidget().setIconSize(size)

def closable(self):
""" Get whether or not the dock item is closable.
Returns
-------
result : bool
True if the dock item is closable, False otherwise.
"""
return self._closable

def setClosable(self, closable):
""" Set whether or not the dock item is closable.
Parameters
----------
closable : bool
True if the dock item is closable, False otherwise.
"""
if closable != self._closable:
self._closable = closable
bar = self.titleBarWidget()
buttons = bar.buttons()
if closable:
buttons |= bar.CloseButton
else:
buttons &= ~bar.CloseButton
bar.setButtons(buttons)
# A concession to practicality: walk the ancestry and update
# the tab close button if this item lives in a dock tab.
container = self.parent()
if container is not None:
stacked = container.parent()
if stacked is not None:
tabs = stacked.parent()
if isinstance(tabs, QDockTabWidget):
index = tabs.indexOf(container)
tabs.setCloseButtonVisible(index, closable)

def titleBarWidget(self):
""" Get the title bar widget for the dock item.
Expand Down
97 changes: 94 additions & 3 deletions enaml/qt/docking/q_dock_tab_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
# The full license is in the file COPYING.txt, distributed with this software.
#------------------------------------------------------------------------------
from PyQt4.QtCore import Qt, QPoint, QMetaObject, QEvent
from PyQt4.QtGui import QApplication, QTabBar, QTabWidget, QMouseEvent
from PyQt4.QtGui import (
QApplication, QTabBar, QTabWidget, QMouseEvent, QResizeEvent
)


class QDockTabBar(QTabBar):
Expand All @@ -28,18 +30,81 @@ def __init__(self, parent=None):
"""
super(QDockTabBar, self).__init__(parent)
self.setSelectionBehaviorOnRemove(QTabBar.SelectPreviousTab)
self._has_mouse = False

#--------------------------------------------------------------------------
# Public API
#--------------------------------------------------------------------------
def setCloseButtonVisible(self, index, visible):
""" Set the close button visibility for the given tab index.
Parameters
----------
index : int
The index of the tab to set the close button visibility.
visible : bool
Whether or not the close button should be visible.
"""
if index < 0 or index >= self.count():
return
button = self.tabButton(index, QTabBar.RightSide)
if button is not None:
if button.isVisibleTo(self) != visible:
# The public QTabBar api does not provide a way to
# trigger the 'layoutTabs' method of QTabBarPrivate
# and there are certain operations (such as modifying
# a tab close button) which need to have that happen.
# A workaround is to send a dummy resize event.
button.setVisible(visible)
if not visible:
button.resize(0, 0)
else:
button.resize(button.sizeHint())
size = self.size()
event = QResizeEvent(size, size)
QApplication.sendEvent(self, event)
self.update()

#--------------------------------------------------------------------------
# Reimplementations
#--------------------------------------------------------------------------
def tabInserted(self, index):
""" Handle a tab insertion in the tab bar.
This handler will update the visibilty of close button for
the inserted tab. This method assumes that this tab bar is
properly parented by a QDockTabWidget.
"""
visible = self.parent().widget(index).closable()
self.setCloseButtonVisible(index, visible)

def mousePressEvent(self, event):
""" Handle the mouse press event for the tab bar.
This handler will set the internal '_has_mouse' flag if the
left mouse button is pressed on a tab.
"""
super(QDockTabBar, self).mousePressEvent(event)
self._has_mouse = False
if event.button() == Qt.LeftButton:
if self.tabAt(event.pos()) != -1:
self._has_mouse = True

def mouseMoveEvent(self, event):
""" Handle the mouse move event for the tab bar.
If the dock drag is initiated and distances is greater than the
start drag distances, the item will be undocked.
This handler will undock the tab if the mouse is held and the
drag leaves the boundary of the container by the application
drag distance amount.
"""
super(QDockTabBar, self).mouseMoveEvent(event)
if not self._has_mouse:
return
pos = event.pos()
if self.rect().contains(pos):
return
Expand All @@ -56,6 +121,18 @@ def mouseMoveEvent(self, event):
QApplication.sendEvent(self, evt)
container = self.parent().widget(self.currentIndex())
container.untab(event.globalPos())
self._has_mouse = False

def mouseReleaseEvent(self, event):
""" Handle the mouse release event for the tab bar.
This handler will reset the internal '_has_mouse' flag when the
left mouse button is released.
"""
super(QDockTabBar, self).mouseReleaseEvent(event)
if event.button() == Qt.LeftButton:
self._has_mouse = False


class QDockTabWidget(QTabWidget):
Expand Down Expand Up @@ -95,3 +172,17 @@ def _onTabCloseRequested(self, index):
# Invoke the close slot later to allow the signal to return.
container = self.widget(index)
QMetaObject.invokeMethod(container, 'close', Qt.QueuedConnection)

def setCloseButtonVisible(self, index, visible):
""" Set the close button visibility for the given tab index.
Parameters
----------
index : int
The index of the tab to set the close button visibility.
visible : bool
Whether or not the close button should be visible.
"""
self.tabBar().setCloseButtonVisible(index, visible)
7 changes: 7 additions & 0 deletions enaml/qt/qt_dock_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ def init_widget(self):
if -1 not in d.icon_size:
self.set_icon_size(d.icon_size)
self.set_stretch(d.stretch)
self.set_closable(d.closable)

def init_layout(self):
""" Initialize the layout for the underyling widget.
Expand Down Expand Up @@ -153,3 +154,9 @@ def set_stretch(self, stretch):
sp.setHorizontalStretch(stretch)
sp.setVerticalStretch(stretch)
self.widget.setSizePolicy(sp)

def set_closable(self, closable):
""" Set the closable flag for the underlying widget.
"""
self.widget.setClosable(closable)
10 changes: 8 additions & 2 deletions enaml/widgets/dock_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# The full license is in the file COPYING.txt, distributed with this software.
#------------------------------------------------------------------------------
from atom.api import (
Coerced, Event, Unicode, Range, Typed, ForwardTyped, observe
Coerced, Event, Unicode, Bool, Range, Typed, ForwardTyped, observe
)

from enaml.application import deferred_call
Expand Down Expand Up @@ -37,6 +37,9 @@ def set_icon_size(self, size):
def set_stretch(self, stretch):
raise NotImplementedError

def set_closable(self, closable):
raise NotImplementedError


class DockItem(Widget):
""" A widget which can be docked in a DockArea.
Expand All @@ -57,6 +60,9 @@ class DockItem(Widget):
#: The stretch factor for the item when docked in a splitter.
stretch = d_(Range(low=0, value=1))

#: Whether or not the dock item is closable via a close button.
closable = d_(Bool(True))

#: An event emitted when the dock item is closed. The item will be
#: destroyed after this event has completed.
closed = d_(Event(), writable=False)
Expand All @@ -77,7 +83,7 @@ def dock_widget(self):
#--------------------------------------------------------------------------
# Observers
#--------------------------------------------------------------------------
@observe(('title', 'icon', 'icon_size', 'stretch'))
@observe(('title', 'icon', 'icon_size', 'stretch', 'closable'))
def _update_proxy(self, change):
""" Update the proxy when the item state changes.
Expand Down

0 comments on commit d839fb0

Please sign in to comment.