diff --git a/enaml/qt/q_bubble_view.py b/enaml/qt/q_bubble_view.py index d2c7d401b..6d6d2133e 100644 --- a/enaml/qt/q_bubble_view.py +++ b/enaml/qt/q_bubble_view.py @@ -5,7 +5,9 @@ # # The full license is in the file COPYING.txt, distributed with this software. #------------------------------------------------------------------------------ -from PyQt4.QtCore import Qt, QSize, QPoint, QMargins, QEvent, pyqtSignal +from PyQt4.QtCore import ( + Qt, QSize, QPoint, QMargins, QEvent, pyqtSignal, QPropertyAnimation, +) from PyQt4.QtGui import QWidget, QLayout, QPainter, QPainterPath from .q_single_widget_layout import QSingleWidgetLayout @@ -28,6 +30,9 @@ class QBubbleView(QWidget): AnchorLeft = 2 AnchorRight = 3 + #: close on defocus + _close_on_defocus = True + def __init__(self, parent=None): super(QBubbleView, self).__init__(parent) self._central_widget = None @@ -47,6 +52,14 @@ def __init__(self, parent=None): self.setArrowSize(20) self.setRadius(10) + # Closing animation + self._fade_time = 150 + self._close_anim = anim = QPropertyAnimation(self, "windowOpacity", self) + anim.setDuration(self._fade_time) + anim.setStartValue(1) + anim.setEndValue(0) + anim.finished.connect(super(QBubbleView, self).close) + # track parent window movement parent.window().installEventFilter(self) parent.destroyed.connect(self.deleteLater) @@ -191,6 +204,29 @@ def relativePos(self): """ return self._relative_pos + def setCloseOnDefocus(self, do_close): + """ Set whether the bubble view closes when it loses focus + + Parameters + ---------- + do_close : bool + Whether to automatically close the bubble view when it + loses focus + + """ + self._close_on_defocus = do_close + + def closeOnDefocus(self): + """ Return whether to close on losing focus + + Returns + ------- + result : bool + Whether to close on losing focus + + """ + return self._close_on_defocus + def paintEvent(self, event): """ Reimplement the paint event @@ -230,6 +266,20 @@ def eventFilter(self, obj, event): self.move(self.pos() + event.pos() - event.oldPos()) return False + def close(self): + """ Fade the popup out + + """ + self._close_anim.start() + + def event(self, event): + """ Handle focus lost + + """ + if self._close_on_defocus and event.type() == QEvent.Leave: + self.close() + return super(QBubbleView, self).event(event) + def _rebuild(self): """ Rebuild the path used to draw the outline of the popup. diff --git a/enaml/qt/qt_bubble_view.py b/enaml/qt/qt_bubble_view.py index 17782d1f0..148d6ce29 100644 --- a/enaml/qt/qt_bubble_view.py +++ b/enaml/qt/qt_bubble_view.py @@ -47,6 +47,7 @@ def init_widget(self): self.set_arrow(d.arrow) self.set_radius(d.radius) self.set_relative_pos(d.relative_pos) + self.set_close_on_defocus(d.close_on_defocus) self.widget.closed.connect(self.on_closed) def init_layout(self): @@ -114,3 +115,9 @@ def set_relative_pos(self, relative_pos): """ self.widget.setRelativePos(relative_pos) + + def set_close_on_defocus(self, do_close): + """ Set whether to close bubble view on losing focus + + """ + self.widget.setCloseOnDefocus(do_close) diff --git a/enaml/widgets/bubble_view.py b/enaml/widgets/bubble_view.py index c4cc3cede..c908d7646 100644 --- a/enaml/widgets/bubble_view.py +++ b/enaml/widgets/bubble_view.py @@ -6,7 +6,7 @@ # The full license is in the file COPYING.txt, distributed with this software. #------------------------------------------------------------------------------ from atom.api import ( - Enum, Event, Int, Tuple, Typed, ForwardTyped, observe, set_default, + Enum, Event, Int, Tuple, Typed, ForwardTyped, Bool, observe, set_default, ) from enaml.application import deferred_call @@ -38,6 +38,9 @@ def set_arrow(self, arrow): def set_relative_pos(self, relative_pos): raise NotImplementedError + def set_close_on_defocus(self, do_close): + raise NotImplementedError + def close(self): raise NotImplementedError @@ -65,6 +68,9 @@ class BubbleView(Widget): #: bounds relative_pos = d_(Tuple(float, (0.5, 0.5))) + #: Whether to close on losing focus + close_on_defocus = d_(Bool(True)) + #: An event fired when the BubbleView is closed. closed = d_(Event(), writable=False) @@ -110,7 +116,7 @@ def show(self): #-------------------------------------------------------------------------- # Observers #-------------------------------------------------------------------------- - @observe(('anchor', 'radius', 'arrow', 'relative_pos')) + @observe(('anchor', 'radius', 'arrow', 'relative_pos', 'close_on_defocus')) def _update_proxy(self, change): """ Update the ProxyBubbleView when the BubbleView data changes.