Skip to content

Commit

Permalink
Merge pull request #77 from nucleic/feature-dock-item-alert
Browse files Browse the repository at this point in the history
Feature dock item alert
  • Loading branch information
sccolbert committed Nov 1, 2013
2 parents a7eef2e + 98087a4 commit ba766d7
Show file tree
Hide file tree
Showing 9 changed files with 605 additions and 13 deletions.
19 changes: 19 additions & 0 deletions enaml/qt/docking/q_dock_bar.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
QDockItemEvent, DockItemExtended, DockItemRetracted,
DockAreaContentsChanged
)
from .utils import repolish


class QDockBar(QFrame):
Expand Down Expand Up @@ -202,6 +203,13 @@ def position(self):
return parent.position()
return QDockBar.North

def onAlerted(self, level):
""" A slot which can be connected to an 'alerted' signal.
"""
self.setProperty(u'alert', level or None)
repolish(self)

#--------------------------------------------------------------------------
# Reimplementations
#--------------------------------------------------------------------------
Expand Down Expand Up @@ -693,6 +701,14 @@ def _onSlideInFinished(self):
event = QDockItemEvent(DockItemRetracted, container.objectName())
QApplication.postEvent(area, event)

def _onButtonPressed(self):
""" Handle the 'pressed' signal from a dock bar button.
"""
button = self.sender()
container = self._widgets[button].widget()
container.dockItem().clearAlert() # likey a no-op, but just in case

#--------------------------------------------------------------------------
# Public API
#--------------------------------------------------------------------------
Expand Down Expand Up @@ -724,6 +740,8 @@ def addContainer(self, container, position, index=-1):
button.setText(container.title())
button.setIcon(container.icon())
button.toggled.connect(self._onButtonToggled)
button.pressed.connect(self._onButtonPressed)
container.alerted.connect(button.onAlerted)

dock_bar = self._getDockBar(position)
dock_bar.insertButton(index, button)
Expand Down Expand Up @@ -753,6 +771,7 @@ def removeContainer(self, container):
container.setParent(None)
container.setPinned(False, quiet=True)
container.frame_state.in_dock_bar = False
container.alerted.disconnect(button.onAlerted)

item = self._widgets.pop(button)
item.setParent(None)
Expand Down
15 changes: 15 additions & 0 deletions enaml/qt/docking/q_dock_container.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ class QDockContainer(QDockFrame):
#: A signal emitted when the container changes its toplevel state.
topLevelChanged = Signal(bool)

#: A signal emitted when the container is alerted.
alerted = Signal(unicode)

class FrameState(QDockFrame.FrameState):
""" A private class for managing container drag state.
Expand Down Expand Up @@ -141,6 +144,7 @@ def __init__(self, manager, parent=None):
layout.setSizeConstraint(QLayout.SetMinAndMaxSize)
self.setLayout(layout)
self.setProperty('floating', False)
self.alerted.connect(self.onAlerted)
self._dock_item = None

def titleBarGeometry(self):
Expand Down Expand Up @@ -256,13 +260,15 @@ def setDockItem(self, dock_item):
old.linkButtonToggled.disconnect(self.linkButtonToggled)
old.pinButtonToggled.disconnect(self.onPinButtonToggled)
old.titleBarLeftDoubleClicked.disconnect(self.toggleMaximized)
old.alerted.disconnect(self.alerted)
if dock_item is not None:
dock_item.maximizeButtonClicked.connect(self.showMaximized)
dock_item.restoreButtonClicked.connect(self.showNormal)
dock_item.closeButtonClicked.connect(self.close)
dock_item.linkButtonToggled.connect(self.linkButtonToggled)
dock_item.pinButtonToggled.connect(self.onPinButtonToggled)
dock_item.titleBarLeftDoubleClicked.connect(self.toggleMaximized)
dock_item.alerted.connect(self.alerted)
layout.setWidget(dock_item)
self._dock_item = dock_item

Expand Down Expand Up @@ -592,6 +598,13 @@ def onPinButtonToggled(self, pinned):
)[position]
plug_frame(area, None, self, guide)

def onAlerted(self, level):
""" A signal handler for the 'alerted' signal.
"""
self.setProperty('alert', level or None)
repolish(self)

#--------------------------------------------------------------------------
# Event Handlers
#--------------------------------------------------------------------------
Expand All @@ -606,6 +619,8 @@ def eventFilter(self, obj, event):
if obj is not self._dock_item:
return False
if event.type() == QEvent.MouseButtonPress:
if event.button() == Qt.LeftButton:
self._dock_item.clearAlert() # likely a no-op, but just in case
return self.filteredMousePressEvent(event)
elif event.type() == QEvent.MouseMove:
return self.filteredMouseMoveEvent(event)
Expand Down
142 changes: 141 additions & 1 deletion enaml/qt/docking/q_dock_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,30 @@
#
# The full license is in the file COPYING.txt, distributed with this software.
#------------------------------------------------------------------------------
from enaml.qt.QtCore import QRect, QSize, QPoint, QTimer, Signal
from enaml.qt.QtCore import Qt, QRect, QSize, QPoint, QTimer, Signal
from enaml.qt.QtGui import QApplication, QFrame, QLayout

from .event_types import (
QDockItemEvent, DockItemShown, DockItemHidden, DockItemClosed
)
from .q_dock_tab_widget import QDockTabWidget
from .q_dock_title_bar import QDockTitleBar
from .utils import repolish


class _AlertData(object):
""" A private class which stores the data needed for item alerts.
"""
def __init__(self, timer, level, on, off, repeat, persist):
self.timer = timer
self.level = level
self.on = on
self.off = off
self.repeat = repeat
self.persist = persist
self.remaining = repeat
self.active = False


class QDockItemLayout(QLayout):
Expand Down Expand Up @@ -257,6 +273,10 @@ class QDockItem(QFrame):
#: signal is proxied from the current dock item title bar.
titleBarRightClicked = Signal(QPoint)

#: A signal emitted when the item is alerted. The payload is the
#: new alert level. An empty string indicates no alert.
alerted = Signal(unicode)

def __init__(self, parent=None):
""" Initialize a QDockItem.
Expand All @@ -272,7 +292,9 @@ def __init__(self, parent=None):
layout.setSizeConstraint(QLayout.SetMinAndMaxSize)
self.setLayout(layout)
self.setTitleBarWidget(QDockTitleBar())
self.alerted.connect(self._onAlerted)
self._manager = None # Set and cleared by the DockManager
self._alert_data = None
self._vis_changed = None
self._closable = True
self._closing = False
Expand Down Expand Up @@ -324,6 +346,16 @@ def hideEvent(self, event):
if not self._closing:
self._postVisibilityChange(False)

def mousePressEvent(self, event):
""" Handle the mouse press event for the dock item.
This handler will clear any alert level on a left click.
"""
if event.button() == Qt.LeftButton:
self.clearAlert()
super(QDockItem, self).mousePressEvent(event)

#--------------------------------------------------------------------------
# Public API
#--------------------------------------------------------------------------
Expand Down Expand Up @@ -645,9 +677,117 @@ def setDockWidget(self, widget):
"""
self.layout().setDockWidget(widget)

def alert(self, level, on=250, off=250, repeat=4, persist=False):
""" Set the alert level on the dock item.
This will override any currently applied alert level.
Parameters
----------
level : unicode
The alert level token to apply to the dock item.
on : int
The duration of the 'on' cycle, in ms. A value of -1 means
always on.
off : int
The duration of the 'off' cycle, in ms. If 'on' is -1, this
value is ignored.
repeat : int
The number of times to repeat the on-off cycle. If 'on' is
-1, this value is ignored.
persist : bool
Whether to leave the alert in the 'on' state when the cycles
finish. If 'on' is -1, this value is ignored.
"""
if self._alert_data is not None:
self.clearAlert()
app = QApplication.instance()
app.focusChanged.connect(self._onAppFocusChanged)
timer = QTimer()
timer.setSingleShot(True)
timer.timeout.connect(self._onAlertTimer)
on, off, repeat = max(-1, on), max(0, off), max(1, repeat)
self._alert_data = _AlertData(timer, level, on, off, repeat, persist)
if on < 0:
self.alerted.emit(level)
else:
self._onAlertTimer()

def clearAlert(self):
""" Clear the current alert level, if any.
"""
if self._alert_data is not None:
self._alert_data.timer.stop()
self._alert_data = None
app = QApplication.instance()
app.focusChanged.disconnect(self._onAppFocusChanged)
self.alerted.emit(u'')

#--------------------------------------------------------------------------
# Private API
#--------------------------------------------------------------------------
def _onAlertTimer(self):
""" Handle the alert data timer timeout.
This handler will refresh the alert level for the current tick,
or clear|persist the alert level if the ticks have expired.
"""
data = self._alert_data
if data is not None:
if not data.active:
data.active = True
data.timer.start(data.on)
self.alerted.emit(data.level)
else:
data.active = False
data.remaining -= 1
if data.remaining > 0:
data.timer.start(data.off)
self.alerted.emit(u'')
elif data.persist:
data.timer.stop()
self.alerted.emit(data.level)
else:
self.clearAlert()

def _onAlerted(self, level):
""" A signal handler for the 'alerted' signal.
This handler will set the 'alert' dynamic property on the
dock item, the title bar, and the title bar label, and then
repolish all three items.
"""
level = level or None
title_bar = self.titleBarWidget()
label = title_bar.label()
self.setProperty(u'alert', level)
title_bar.setProperty(u'alert', level)
label.setProperty(u'alert', level)
repolish(label)
repolish(title_bar)
repolish(self)

def _onAppFocusChanged(self, old, new):
""" A signal handler for the 'focusChanged' app signal
This handler will clear the alert if one of the descendant
widgets or the item itself gains focus.
"""
while new is not None:
if new is self:
self.clearAlert()
break
new = new.parent()

def _onVisibilityTimer(self):
""" Handle the visibility timer timeout.
Expand Down
Loading

0 comments on commit ba766d7

Please sign in to comment.