File: hover_tool.py

package info (click to toggle)
python-enable 4.3.0-2
  • links: PTS, VCS
  • area: main
  • in suites: jessie, jessie-kfreebsd
  • size: 7,280 kB
  • ctags: 13,899
  • sloc: cpp: 48,447; python: 28,502; ansic: 9,004; makefile: 315; sh: 44
file content (151 lines) | stat: -rw-r--r-- 5,216 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
"""
Tool to detect when the user hovers over a specific part of an underlying
components.
"""

# Enthought library imports
from enable.base_tool import BaseTool
from traits.etsconfig.api import ETSConfig
from pyface.toolkit import toolkit_object
from traits.api import Any, Callable, Enum, Float, Int

# Define a toolkit-specific function for determining the global mouse position
if ETSConfig.toolkit == 'wx':
    import wx
    def GetGlobalMousePosition():
        pos = wx.GetMousePosition()
        if isinstance(pos, tuple):
            return pos
        elif hasattr(pos, "x") and hasattr(pos, "y"):
            return (pos.x, pos.y)
        else:
            raise RuntimeError("Unable to determine mouse position")

elif ETSConfig.toolkit == 'qt4':
    from pyface.qt import QtGui
    def GetGlobalMousePosition():
        pos = QtGui.QCursor.pos()
        return (pos.x(), pos.y())

else:
    def GetGlobalMousePosition():
        raise NotImplementedError, "GetGlobalMousePosition is not defined for" \
            "toolkit '%s'." % ETSConfig.toolkit


class HoverTool(BaseTool):
    """
    Tool to detect when the user hovers over a certain area on a component.
    The type of area to detect can be configured by the 'area_type' and 'bounds'
    traits.

    Users of the class should either set the 'callback' attribute, or
    subclass and override on_hover().
    """

    # Defines the part of the component that the hover tool will listen
    area_type = Enum("top", "bottom", "left", "right", "borders",   # borders
                     "UL", "UR", "LL", "LR", "corners")             # corners

    # The width/height of the border or corner area.  (Corners are assumed to
    # be square.)
    area = Float(35.0)

    # The number of milliseconds that the user has to keep the mouse within
    # the hover threshold before a hover is triggered.
    hover_delay = Int(500)

    # Controls the mouse sensitivity; if the mouse moves less than the
    # threshold amount in X or Y, then the hover timer is not reset.
    hover_threshold = Int(5)

    # The action to perform when the hover activates.  This can be used
    # instead of subclassing and overriding on_hover().
    # If cb_param is not None, then the callback gets passed it as
    # its single parameter.
    callback = Callable

    # An optional parameter that gets passed to the callback.
    cb_param = Any

    #-------------------------------------------------------------------------
    # Private traits
    #-------------------------------------------------------------------------

    # A tuple (x,y) of the mouse position (in global coordinate) when we set the timer
    _start_xy = Any

    # The timer
    _timer = Any


    #-------------------------------------------------------------------------
    # Public methods
    #-------------------------------------------------------------------------

    def on_hover(self):
        """ This gets called when all the conditions of the hover action have
        been met, and the tool determines that the mouse is, in fact, hovering
        over a target region on the component.

        By default, this method call self.callback (if one is configured).
        """
        if self.callback is not None:
            if self.cb_param is not None:
                self.callback(self.cb_param)
            else:
                self.callback()

    def normal_mouse_move(self, event):
        if self._is_in(event.x, event.y):
            # update xy and restart the timer
            self._start_xy = GetGlobalMousePosition()
            self.restart_hover_timer(event)
        else:
            if self._timer:
                self._timer.Stop()

    def restart_hover_timer(self, event):
        if self._timer is None:
            self._create_timer(event)
        else:
            self._timer.Start()

    def on_timer(self):
        position = GetGlobalMousePosition()
        diffx = abs(position[0] - self._start_xy[0])
        diffy = abs(position[1] - self._start_xy[1])

        if (diffx < self.hover_threshold) and (diffy < self.hover_threshold):
            self.on_hover()

        self._timer.Stop()


    #-------------------------------------------------------------------------
    # Private methods
    #-------------------------------------------------------------------------

    def _is_in(self, x, y):
        """ Returns True if local coordinates (x,y) is inside our hover region """
        area_type = self.area_type.lower()
        c = self.component

        t = (c.y2 - y <= self.area)
        b = (y - c.y <= self.area)
        r = (c.x2 - x <= self.area)
        l = (x - c.x <= self.area)

        if area_type in ("top", "bottom", "left", "right"):
            return locals()[area_type[0]]
        elif area_type.lower() in ("ul", "ur", "ll", "lr"):
            u = t
            return locals()[area_type[0]] and locals()[area_type[1]]
        elif area_type == "corners":
            return (t | b) & (l | r)
        elif area_type == "borders":
            return any((t, b, r, l))

    def _create_timer(self, event):
        klass = toolkit_object("timer.timer:Timer")
        self._timer = klass(self.hover_delay, self.on_timer)