File: marker.py

package info (click to toggle)
turing 0.11-4
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 11,560 kB
  • sloc: python: 106,582; xml: 101; makefile: 53; sh: 29
file content (249 lines) | stat: -rw-r--r-- 8,021 bytes parent folder | download | duplicates (6)
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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
# -*- coding: utf-8 -*-
"""
This module contains the marker panel
"""
import logging

from pyqode.core.api import TextDecoration
from pyqode.core.api.panel import Panel
from pyqode.core.api.utils import DelayJobRunner, TextHelper
from pyqode.qt import QtCore, QtWidgets, QtGui


def _logger():
    """ Gets module's logger """
    return logging.getLogger(__name__)


class Marker(QtCore.QObject):
    """
    A marker is an icon draw on a marker panel at a specific line position and
    with a possible tooltip.
    """

    @property
    def position(self):
        """
        Gets the marker position (line number)
        :type: int
        """
        try:
            return self.block.blockNumber()
        except AttributeError:
            return self._position  # not added yet

    @property
    def icon(self):
        """
        Gets the icon file name. Read-only.
        """
        if isinstance(self._icon, str):
            if QtGui.QIcon.hasThemeIcon(self._icon):
                return QtGui.QIcon.fromTheme(self._icon)
            else:
                return QtGui.QIcon(self._icon)
        elif isinstance(self._icon, tuple):
            return QtGui.QIcon.fromTheme(self._icon[0],
                                         QtGui.QIcon(self._icon[1]))
        elif isinstance(self._icon, QtGui.QIcon):
            return self._icon
        return QtGui.QIcon()

    @property
    def description(self):
        """ Gets the marker description. """
        return self._description

    def __init__(self, position, icon="", description="", parent=None):
        """
        :param position: The marker position/line number.
        :type position: int

        :param icon: The icon to display
        :type icon: QtGui.QIcon

        :param parent: The optional parent object.
        :type parent: QtCore.QObject or None
        """
        QtCore.QObject.__init__(self, parent)
        #: The position of the marker (line number)
        self._position = position
        self._icon = icon
        self._description = description


class MarkerPanel(Panel):
    """
    General purpose marker panel.
    This panels takes care of drawing icons at a specific line number.

    Use addMarker, removeMarker and clearMarkers to manage the collection of
    displayed makers.

    You can create a user editable panel (e.g. a breakpoints panel) by using
    the following signals:

      - :attr:`pyqode.core.panels.MarkerPanel.add_marker_requested`
      - :attr:`pyqode.core.panels.MarkerPanel.remove_marker_requested`

    """
    #: Signal emitted when the user clicked in a place where there is no
    #: marker.
    add_marker_requested = QtCore.Signal(int)
    #: Signal emitted when the user right clicked on an existing marker.
    edit_marker_requested = QtCore.Signal(int)
    #: Signal emitted when the user left clicked on an existing marker.
    remove_marker_requested = QtCore.Signal(int)

    @property
    def background(self):
        """
        Marker background color in editor. Use None if no text decoration
        should be used.
        """
        return self._background

    @background.setter
    def background(self, value):
        self._background = value

    def __init__(self):
        Panel.__init__(self)
        self._background = QtGui.QColor('#FFC8C8')
        self._markers = []
        self._icons = {}
        self._previous_line = -1
        self.scrollable = True
        self._job_runner = DelayJobRunner(delay=100)
        self.setMouseTracking(True)
        self._to_remove = []

    @property
    def markers(self):
        """
        Gets all markers.
        """
        return self._markers

    def add_marker(self, marker):
        """
        Adds the marker to the panel.

        :param marker: Marker to add
        :type marker: pyqode.core.modes.Marker
        """
        self._markers.append(marker)
        doc = self.editor.document()
        assert isinstance(doc, QtGui.QTextDocument)
        block = doc.findBlockByLineNumber(marker._position)
        marker.block = block
        d = TextDecoration(block)
        d.set_full_width()
        if self._background:
            d.set_background(QtGui.QBrush(self._background))
            marker.decoration = d
        self.editor.decorations.append(d)
        self.repaint()

    def remove_marker(self, marker):
        """
        Removes a marker from the panel

        :param marker: Marker to remove
        :type marker: pyqode.core.Marker
        """
        self._markers.remove(marker)
        self._to_remove.append(marker)
        if hasattr(marker, 'decoration'):
            self.editor.decorations.remove(marker.decoration)
        self.repaint()

    def clear_markers(self):
        """ Clears the markers list """
        while len(self._markers):
            self.remove_marker(self._markers[0])

    def marker_for_line(self, line):
        """
        Returns the marker that is displayed at the specified line number if
        any.

        :param line: The marker line.

        :return: Marker of None
        :rtype: pyqode.core.Marker
        """
        markers = []
        for marker in self._markers:
            if line == marker.position:
                markers.append(marker)
        return markers

    def sizeHint(self):
        """
        Returns the panel size hint. (fixed with of 16px)
        """
        metrics = QtGui.QFontMetricsF(self.editor.font())
        size_hint = QtCore.QSize(metrics.height(), metrics.height())
        if size_hint.width() > 16:
            size_hint.setWidth(16)
        return size_hint

    def paintEvent(self, event):
        Panel.paintEvent(self, event)
        painter = QtGui.QPainter(self)
        for top, block_nbr, block in self.editor.visible_blocks:
            for marker in self._markers:
                if marker.block == block and marker.icon:
                    rect = QtCore.QRect()
                    rect.setX(0)
                    rect.setY(top)
                    rect.setWidth(self.sizeHint().width())
                    rect.setHeight(self.sizeHint().height())
                    marker.icon.paint(painter, rect)

    def mousePressEvent(self, event):
        # Handle mouse press:
        # - emit add marker signal if there were no marker under the mouse
        #   cursor
        # - emit remove marker signal if there were one or more markers under
        #   the mouse cursor.
        line = TextHelper(self.editor).line_nbr_from_position(event.pos().y())
        if self.marker_for_line(line):
            if event.button() == QtCore.Qt.LeftButton:
                self.remove_marker_requested.emit(line)
            else:
                self.edit_marker_requested.emit(line)
        else:
            self.add_marker_requested.emit(line)

    def mouseMoveEvent(self, event):
        # Requests a tooltip if the cursor is currently over a marker.
        line = TextHelper(self.editor).line_nbr_from_position(event.pos().y())
        markers = self.marker_for_line(line)
        text = '\n'.join([marker.description for marker in markers if
                          marker.description])
        if len(markers):
            if self._previous_line != line:
                top = TextHelper(self.editor).line_pos_from_number(
                    markers[0].position)
                if top:
                    self._job_runner.request_job(self._display_tooltip,
                                                 text, top)
        else:
            self._job_runner.cancel_requests()
        self._previous_line = line

    def leaveEvent(self, *args, **kwargs):
        """
        Hide tooltip when leaving the panel region.
        """
        QtWidgets.QToolTip.hideText()
        self._previous_line = -1

    def _display_tooltip(self, tooltip, top):
        """
        Display tooltip at the specified top position.
        """
        QtWidgets.QToolTip.showText(self.mapToGlobal(QtCore.QPoint(
            self.sizeHint().width(), top)), tooltip, self)