File: blink.py

package info (click to toggle)
frescobaldi 3.0.0~git20161001.0.eec60717%2Bds1-2
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 19,792 kB
  • ctags: 5,843
  • sloc: python: 37,853; sh: 180; makefile: 69
file content (127 lines) | stat: -rw-r--r-- 4,466 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
# This file is part of the Frescobaldi project, http://www.frescobaldi.org/
#
# Copyright (c) 2008 - 2014 by Wilbert Berendsen
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
# See http://www.gnu.org/licenses/ for more information.

"""
Shortly blinks a region on a widget.
"""

from PyQt5.QtCore import QTimer, pyqtSignal
from PyQt5.QtGui import QColor, QFontMetrics, QPainter, QPalette, QPen
from PyQt5.QtWidgets import QWidget


class Blinker(QWidget):
    """Can draw a blinking region above its parent widget."""
    
    finished = pyqtSignal()
    
    lineWidth = 3
    radius = 3
    
    @classmethod
    def blink(cls, widget, rect=None, color=None):
        """Shortly blinks a rectangular region on a widget.
        
        If rect is not given, the full rect() of the widget is used.
        If color is not given, the highlight color of the widget is used.
        This method instantiates a Blinker widget and discards it after use.
        
        """
        window = widget.window()
        if rect is None:
            rect = widget.rect()
        rect.moveTo(widget.mapTo(window, rect.topLeft()))
        b = cls(window)
        p = widget.palette()
        if color:
            p.setColor(QPalette.Highlight, color)
        b.setPalette(p)
        b.start(rect)
        b.finished.connect(b.deleteLater)

    @classmethod
    def blink_cursor(cls, textedit, color=None):
        """Highlights the cursor in a Q(Plain)TextEdit."""
        metrics = QFontMetrics(textedit.textCursor().charFormat().font())
        width = metrics.boundingRect("m").width()
        rect = textedit.cursorRect().normalized().adjusted(0, 0, width, 0)
        cls.blink(textedit, rect.translated(textedit.viewport().pos()), color)
    
    def __init__(self, widget):
        """Initializes ourselves to draw on the widget."""
        super(Blinker, self).__init__(widget)
        self._color = None
        self._animation = ()
        self._timer = QTimer(singleShot=True, timeout=self._updateAnimation)
        
    def start(self, rect):
        """Starts blinking the specified rectangle."""
        self._blink_rect = rect
        adj = self.lineWidth
        self.setGeometry(rect.adjusted(-adj, -adj, adj, adj))
        self.show()
        self._animation = self.animateColor()
        self._updateAnimation()
        
    def done(self):
        """(Internal) Called when the animation ends."""
        self.hide()
        self._animation = ()
        self.finished.emit()
    
    def _updateAnimation(self):
        for delta, self._color in self._animation:
            self.update()
            self._timer.start(delta)
            return
        self.done()
        
    def animateColor(self):
        """A generator yielding tuples (msec_delta, color) to animate colors.
        
        When the generator exits, the animation ends.
        The color is taken from the Highlight palette value.
        
        """
        color = self.palette().color(QPalette.Highlight)
        for delta, alpha in self.animateAlpha():
            color.setAlpha(alpha)
            yield delta, color
    
    def animateAlpha(self):
        """A generator yielding (msec_delta, alpha) tuples."""
        for alpha in (255, 0, 255, 0, 255):
            yield 120, alpha
        for alpha in range(255, 0, -15):
            yield 40, alpha
    
    def paintEvent(self, ev):
        color = self._color
        if not color or color.alpha() == 0:
            return
        painter = QPainter(self)
        adj = self.lineWidth // 2
        rect = self.rect().adjusted(adj, adj, -adj, -adj)
        pen = QPen(color)
        pen.setWidth(self.lineWidth)
        painter.setPen(pen)
        painter.setRenderHint(QPainter.Antialiasing, True)
        painter.drawRoundedRect(rect, self.radius, self.radius)