eric7/EricWidgets/EricMapWidget.py

branch
eric7
changeset 8358
144a6b854f70
parent 8356
68ec9c3d4de5
child 8647
cdbce48aded8
equal deleted inserted replaced
8357:a081458cc57b 8358:144a6b854f70
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2014 - 2021 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a base class for showing a document map.
8 """
9
10 from PyQt6.QtCore import Qt, QSize, QRect, QCoreApplication
11 from PyQt6.QtGui import QColor, QBrush, QPainter
12 from PyQt6.QtWidgets import QWidget, QAbstractScrollArea
13
14
15 class EricMapWidget(QWidget):
16 """
17 Class implementing a base class for showing a document map.
18 """
19 def __init__(self, parent=None):
20 """
21 Constructor
22
23 @param parent reference to the parent widget (QWidget)
24 """
25 super().__init__(parent)
26 self.setAttribute(Qt.WidgetAttribute.WA_OpaquePaintEvent)
27
28 self.__width = 14
29 self.__lineBorder = 1
30 self.__lineHeight = 2
31 self.__backgroundColor = QColor("#e7e7e7")
32 self.__setSliderColor()
33
34 self._master = None
35 self.__enabled = False
36 self.__rightSide = True
37
38 if parent is not None and isinstance(parent, QAbstractScrollArea):
39 self.setMaster(parent)
40
41 def __setSliderColor(self):
42 """
43 Private method to set the slider color depending upon the background
44 color.
45 """
46 if self.__backgroundColor.toHsv().value() < 128:
47 # dark background, use white slider
48 self.__sliderColor = Qt.GlobalColor.white
49 else:
50 # light background, use black slider
51 self.__sliderColor = Qt.GlobalColor.black
52
53 def __updateMasterViewportWidth(self):
54 """
55 Private method to update the master's viewport width.
56 """
57 if self._master:
58 if self.__enabled:
59 width = self.__width
60 else:
61 width = 0
62 if self.__rightSide:
63 self._master.setViewportMargins(0, 0, width, 0)
64 else:
65 self._master.setViewportMargins(width, 0, 0, 0)
66
67 def setMaster(self, master):
68 """
69 Public method to set the map master widget.
70
71 @param master map master widget (QAbstractScrollArea)
72 """
73 self._master = master
74 self._master.setVerticalScrollBarPolicy(
75 Qt.ScrollBarPolicy.ScrollBarAlwaysOn)
76 self._master.verticalScrollBar().valueChanged.connect(self.update)
77 self._master.verticalScrollBar().rangeChanged.connect(self.update)
78 self.__updateMasterViewportWidth()
79
80 def setWidth(self, width):
81 """
82 Public method to set the widget width.
83
84 @param width widget width (integer)
85 """
86 if width != self.__width:
87 self.__width = max(6, width) # minimum width 6 pixels
88 self.__updateMasterViewportWidth()
89 self.update()
90
91 def width(self):
92 """
93 Public method to get the widget's width.
94
95 @return widget width (integer)
96 """
97 return self.__width
98
99 def setMapPosition(self, onRight):
100 """
101 Public method to set, whether the map should be shown to the right or
102 left of the master widget.
103
104 @param onRight flag indicating to show the map on the right side of
105 the master widget
106 @type bool
107 """
108 if onRight != self.__rightSide:
109 self.__rightSide = onRight
110 self.__updateMasterViewportWidth()
111 self.update()
112
113 def isOnRightSide(self):
114 """
115 Public method to test, if the map is shown on the right side of the
116 master widget.
117
118 @return flag indicating that the map is to the right of the master
119 widget
120 @rtype bool
121 """
122 return self.__rightSide
123
124 def setLineDimensions(self, border, height):
125 """
126 Public method to set the line (indicator) dimensions.
127
128 @param border border width on each side in x-direction (integer)
129 @param height height of the line in pixels (integer)
130 """
131 if border != self.__lineBorder or height != self.__lineHeight:
132 self.__lineBorder = max(1, border) # min border 1 pixel
133 self.__lineHeight = max(1, height) # min height 1 pixel
134 self.update()
135
136 def lineDimensions(self):
137 """
138 Public method to get the line (indicator) dimensions.
139
140 @return tuple with border width (integer) and line height (integer)
141 """
142 return self.__lineBorder, self.__lineHeight
143
144 def setEnabled(self, enable):
145 """
146 Public method to set the enabled state.
147
148 @param enable flag indicating the enabled state (boolean)
149 """
150 if enable != self.__enabled:
151 self.__enabled = enable
152 self.setVisible(enable)
153 self.__updateMasterViewportWidth()
154
155 def isEnabled(self):
156 """
157 Public method to check the enabled state.
158
159 @return flag indicating the enabled state (boolean)
160 """
161 return self.__enabled
162
163 def setBackgroundColor(self, color):
164 """
165 Public method to set the widget background color.
166
167 @param color color for the background (QColor)
168 """
169 if color != self.__backgroundColor:
170 self.__backgroundColor = color
171 self.__setSliderColor()
172 self.update()
173
174 def backgroundColor(self):
175 """
176 Public method to get the background color.
177
178 @return background color (QColor)
179 """
180 return QColor(self.__backgroundColor)
181
182 def sizeHint(self):
183 """
184 Public method to give an indication about the preferred size.
185
186 @return preferred size (QSize)
187 """
188 return QSize(self.__width, 0)
189
190 def paintEvent(self, event):
191 """
192 Protected method to handle a paint event.
193
194 @param event paint event (QPaintEvent)
195 """
196 # step 1: fill the whole painting area
197 painter = QPainter(self)
198 painter.fillRect(event.rect(), self.__backgroundColor)
199
200 # step 2: paint the indicators
201 self._paintIt(painter)
202
203 # step 3: paint the slider
204 if self._master:
205 penColor = self.__sliderColor
206 painter.setPen(penColor)
207 brushColor = Qt.GlobalColor.transparent
208 painter.setBrush(QBrush(brushColor))
209 painter.drawRect(self.__generateSliderRange(
210 self._master.verticalScrollBar()))
211
212 def _paintIt(self, painter):
213 """
214 Protected method for painting the widget's indicators.
215
216 Note: This method should be implemented by subclasses.
217
218 @param painter reference to the painter object (QPainter)
219 """
220 pass
221
222 def mousePressEvent(self, event):
223 """
224 Protected method to handle a mouse button press.
225
226 @param event reference to the mouse event (QMouseEvent)
227 """
228 if event.button() == Qt.MouseButton.LeftButton and self._master:
229 vsb = self._master.verticalScrollBar()
230 value = self.position2Value(event.position().toPoint().y() - 1)
231 vsb.setValue(value - 0.5 * vsb.pageStep()) # center on page
232 self.__mousePressPos = None
233
234 def mouseMoveEvent(self, event):
235 """
236 Protected method to handle a mouse moves.
237
238 @param event reference to the mouse event (QMouseEvent)
239 """
240 if event.buttons() & Qt.MouseButton.LeftButton and self._master:
241 vsb = self._master.verticalScrollBar()
242 value = self.position2Value(event.position().toPoint().y() - 1)
243 vsb.setValue(value - 0.5 * vsb.pageStep()) # center on page
244
245 def wheelEvent(self, event):
246 """
247 Protected slot handling mouse wheel events.
248
249 @param event reference to the wheel event (QWheelEvent)
250 """
251 isVertical = event.angleDelta().x() == 0
252 if (
253 self._master and
254 event.modifiers() == Qt.KeyboardModifier.NoModifier and
255 isVertical
256 ):
257 QCoreApplication.sendEvent(self._master.verticalScrollBar(), event)
258
259 def calculateGeometry(self):
260 """
261 Public method to recalculate the map widget's geometry.
262 """
263 if self._master:
264 cr = self._master.contentsRect()
265 vsb = self._master.verticalScrollBar()
266 if vsb.isVisible():
267 vsbw = vsb.contentsRect().width()
268 else:
269 vsbw = 0
270 margins = self._master.contentsMargins()
271 if margins.right() > vsbw:
272 vsbw = 0
273 if self.__rightSide:
274 self.setGeometry(
275 QRect(cr.right() - self.__width - vsbw, cr.top(),
276 self.__width, cr.height()))
277 else:
278 self.setGeometry(
279 QRect(0, cr.top(), self.__width, cr.height()))
280 self.update()
281
282 def scaleFactor(self, slider=False):
283 """
284 Public method to determine the scrollbar's scale factor.
285
286 @param slider flag indicating to calculate the result for the slider
287 (boolean)
288 @return scale factor (float)
289 """
290 if self._master:
291 delta = 0 if slider else 2
292 vsb = self._master.verticalScrollBar()
293 posHeight = vsb.height() - delta - 1
294 valHeight = vsb.maximum() - vsb.minimum() + vsb.pageStep()
295 return float(posHeight) / valHeight
296 else:
297 return 1.0
298
299 def value2Position(self, value, slider=False):
300 """
301 Public method to convert a scrollbar value into a position.
302
303 @param value value to convert (integer)
304 @param slider flag indicating to calculate the result for the slider
305 (boolean)
306 @return position (integer)
307 """
308 if self._master:
309 offset = 0 if slider else 1
310 vsb = self._master.verticalScrollBar()
311 return (value - vsb.minimum()) * self.scaleFactor(slider) + offset
312 else:
313 return value
314
315 def position2Value(self, position, slider=False):
316 """
317 Public method to convert a position into a scrollbar value.
318
319 @param position scrollbar position to convert (integer)
320 @param slider flag indicating to calculate the result for the slider
321 (boolean)
322 @return scrollbar value (integer)
323 """
324 if self._master:
325 offset = 0 if slider else 1
326 vsb = self._master.verticalScrollBar()
327 return vsb.minimum() + max(
328 0, (position - offset) / self.scaleFactor(slider))
329 else:
330 return position
331
332 def generateIndicatorRect(self, position):
333 """
334 Public method to generate an indicator rectangle.
335
336 @param position indicator position (integer)
337 @return indicator rectangle (QRect)
338 """
339 return QRect(self.__lineBorder, position - self.__lineHeight // 2,
340 self.__width - self.__lineBorder, self.__lineHeight)
341
342 def __generateSliderRange(self, scrollbar):
343 """
344 Private method to generate the slider rectangle.
345
346 @param scrollbar reference to the vertical scrollbar (QScrollBar)
347 @return slider rectangle (QRect)
348 """
349 pos1 = self.value2Position(scrollbar.value(), slider=True)
350 pos2 = self.value2Position(scrollbar.value() + scrollbar.pageStep(),
351 slider=True)
352 return QRect(0, pos1, self.__width - 1, pos2 - pos1)

eric ide

mercurial