|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2014 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing a base class for showing a document map. |
|
8 """ |
|
9 |
|
10 from PyQt4.QtCore import Qt, QSize, QRect |
|
11 from PyQt4.QtGui import QWidget, QAbstractScrollArea, QColor, QBrush, QPainter |
|
12 |
|
13 |
|
14 class E5MapWidget(QWidget): |
|
15 """ |
|
16 Class implementing a base class for showing a document map. |
|
17 """ |
|
18 def __init__(self, parent=None): |
|
19 """ |
|
20 Constructor |
|
21 |
|
22 @param parent reference to the parent widget (QWidget) |
|
23 """ |
|
24 super().__init__(parent) |
|
25 self.setAttribute(Qt.WA_OpaquePaintEvent) |
|
26 |
|
27 self.__width = 12 |
|
28 self.__lineBorder = 2 |
|
29 self.__lineHeight = 2 |
|
30 self.__backgroundColor = QColor(Qt.lightGray).lighter(120) |
|
31 self.__sliderBorderColor = QColor(Qt.black) |
|
32 self.__sliderBackgroundColor = QColor(Qt.white) |
|
33 |
|
34 self.__master = None |
|
35 self.__enabled = False |
|
36 |
|
37 if parent is not None and isinstance(parent, QAbstractScrollArea): |
|
38 self.setMaster(parent) |
|
39 |
|
40 def __updateMasterViewportWidth(self): |
|
41 """ |
|
42 Private method to update the master's viewport width. |
|
43 """ |
|
44 if self.__master: |
|
45 if self.__enabled: |
|
46 width = self.__width |
|
47 else: |
|
48 width = 0 |
|
49 self.__master.setViewportMargins(0, 0, width, 0) |
|
50 |
|
51 def setMaster(self, master): |
|
52 """ |
|
53 Public method to set the map master widget. |
|
54 |
|
55 @param master map master widget (QAbstractScrollArea) |
|
56 """ |
|
57 self.__master = master |
|
58 self.__master.verticalScrollBar().valueChanged.connect(self.repaint) |
|
59 self.__updateMasterViewportWidth() |
|
60 |
|
61 def setWidth(self, width): |
|
62 """ |
|
63 Public method to set the widget width. |
|
64 |
|
65 @param width widget width (integer) |
|
66 """ |
|
67 self.__width = width |
|
68 |
|
69 def width(self): |
|
70 """ |
|
71 Public method to get the widget's width. |
|
72 |
|
73 @return widget width (integer) |
|
74 """ |
|
75 return self.__width |
|
76 |
|
77 def setLineDimensions(self, border, height): |
|
78 """ |
|
79 Public method to set the line (indicator) dimensions. |
|
80 |
|
81 @param border border width on each side in x-direction (integer) |
|
82 @param height height of the line in pixels (integer) |
|
83 """ |
|
84 self.__lineBorder = border |
|
85 self.__lineHeight = max(2, height) # min height is 2 pixels |
|
86 |
|
87 def lineDimensions(self): |
|
88 """ |
|
89 Public method to get the line (indicator) dimensions. |
|
90 |
|
91 @return tuple with border width (integer) and line height (integer) |
|
92 """ |
|
93 return self.__lineBorder, self.__lineHeight |
|
94 |
|
95 def setEnabled(self, enable): |
|
96 """ |
|
97 Public method to set the enabled state. |
|
98 |
|
99 @param enable flag indicating the enabled state (boolean) |
|
100 """ |
|
101 self.__enabled = enable |
|
102 self.setVisible(enable) |
|
103 self.__updateMasterViewportWidth() |
|
104 |
|
105 def isEnabled(self): |
|
106 """ |
|
107 Public method to check the enabled state. |
|
108 |
|
109 @return flag indicating the enabled state (boolean) |
|
110 """ |
|
111 return self.__enabled |
|
112 |
|
113 def setBackgroundColor(self, color): |
|
114 """ |
|
115 Public method to set the widget background color. |
|
116 |
|
117 @param color color for the background (QColor) |
|
118 """ |
|
119 self.__backgroundColor = color |
|
120 |
|
121 def backgroundColor(self): |
|
122 """ |
|
123 Public method to get the background color. |
|
124 |
|
125 @return background color (QColor) |
|
126 """ |
|
127 return QColor(self.__backgroundColor) |
|
128 |
|
129 def setSliderColors(self, border, background): |
|
130 """ |
|
131 Public method to set the slider colors. |
|
132 |
|
133 @param border border color (QColor) |
|
134 @param background background color (QColor) |
|
135 """ |
|
136 self.__sliderBorderColor = border |
|
137 self.__sliderBackgroundColor = background |
|
138 |
|
139 def sliderColors(self): |
|
140 """ |
|
141 Public method to get the slider colors. |
|
142 |
|
143 @return tuple with the slider's border color (QColor) and |
|
144 background color (QColor) |
|
145 """ |
|
146 return (QColor(self.__sliderBorderColor), |
|
147 QColor(self.__sliderBackgroundColor)) |
|
148 |
|
149 def sizeHint(self): |
|
150 """ |
|
151 Public method to give an indication about the preferred size. |
|
152 |
|
153 @return preferred size (QSize) |
|
154 """ |
|
155 return QSize(self.__width, 0) |
|
156 |
|
157 def paintEvent(self, event): |
|
158 """ |
|
159 Protected method to handle a paint event. |
|
160 |
|
161 @param event paint event (QPaintEvent) |
|
162 """ |
|
163 # step 1: fill the whole painting area |
|
164 painter = QPainter(self) |
|
165 painter.fillRect(event.rect(), self.__backgroundColor) |
|
166 |
|
167 # step 2: paint the indicators |
|
168 self._paintIt(painter) |
|
169 |
|
170 # step 3: paint the slider |
|
171 if self.__master: |
|
172 penColor = self.__sliderBorderColor |
|
173 penColor.setAlphaF(0.8) |
|
174 painter.setPen(penColor) |
|
175 brushColor = self.__sliderBackgroundColor |
|
176 brushColor.setAlphaF(0.5) |
|
177 painter.setBrush(QBrush(brushColor)) |
|
178 painter.drawRect(self.__generateSliderRange( |
|
179 self.__master.verticalScrollBar())) |
|
180 |
|
181 def _paintIt(self, painter): |
|
182 """ |
|
183 Protected method for painting the widget's indicators. |
|
184 |
|
185 Note: This method should be implemented by subclasses. |
|
186 |
|
187 @param painter reference to the painter object (QPainter) |
|
188 """ |
|
189 pass |
|
190 |
|
191 def mousePressEvent(self, event): |
|
192 """ |
|
193 Protected method to handle a mouse button press. |
|
194 |
|
195 @param event mouse event (QMouseEvent) |
|
196 """ |
|
197 if event.button() == Qt.LeftButton and self.__master: |
|
198 vsb = self.__master.verticalScrollBar() |
|
199 value = self.position2Value(event.pos().y() - 1) |
|
200 vsb.setValue(value - 0.5 * vsb.pageStep()) # center on page |
|
201 |
|
202 def calculateGeometry(self): |
|
203 """ |
|
204 Public method to recalculate the map widget's geometry. |
|
205 """ |
|
206 if self.__master: |
|
207 cr = self.__master.contentsRect() |
|
208 vsb = self.__master.verticalScrollBar() |
|
209 if vsb.isVisible(): |
|
210 vsbw = vsb.contentsRect().width() |
|
211 else: |
|
212 vsbw = 0 |
|
213 left, top, right, bottom = self.__master.getContentsMargins() |
|
214 if right > vsbw: |
|
215 vsbw = 0 |
|
216 self.setGeometry(QRect(cr.right() - self.__width - vsbw, cr.top(), |
|
217 self.__width, cr.height())) |
|
218 |
|
219 def scaleFactor(self, slider=False): |
|
220 """ |
|
221 Public method to determine the scrollbar's scale factor. |
|
222 |
|
223 @param slider flag indicating to calculate the result for the slider |
|
224 (boolean) |
|
225 @return scale factor (float) |
|
226 """ |
|
227 if self.__master: |
|
228 delta = 0 if slider else 2 |
|
229 vsb = self.__master.verticalScrollBar() |
|
230 posHeight = vsb.height() - delta - 1 |
|
231 valHeight = vsb.maximum() - vsb.minimum() + vsb.pageStep() |
|
232 return posHeight / valHeight |
|
233 else: |
|
234 return 1.0 |
|
235 |
|
236 def value2Position(self, value, slider=False): |
|
237 """ |
|
238 Public method to convert a scrollbar value into a position. |
|
239 |
|
240 @param value value to convert (integer) |
|
241 @param slider flag indicating to calculate the result for the slider |
|
242 (boolean) |
|
243 @return position (integer) |
|
244 """ |
|
245 if self.__master: |
|
246 offset = 0 if slider else 1 |
|
247 vsb = self.__master.verticalScrollBar() |
|
248 return (value - vsb.minimum()) * self.scaleFactor(slider) + offset |
|
249 else: |
|
250 return value |
|
251 |
|
252 def position2Value(self, position, slider=False): |
|
253 """ |
|
254 Public method to convert a position into a scrollbar value. |
|
255 |
|
256 @param position scrollbar position to convert (integer) |
|
257 @param slider flag indicating to calculate the result for the slider |
|
258 (boolean) |
|
259 @return scrollbar value (integer) |
|
260 """ |
|
261 if self.__master: |
|
262 offset = 0 if slider else 1 |
|
263 vsb = self.__master.verticalScrollBar() |
|
264 return vsb.minimum() + max( |
|
265 0, (position - offset) / self.scaleFactor(slider)) |
|
266 else: |
|
267 return position |
|
268 |
|
269 def generateIndicatorRect(self, position): |
|
270 """ |
|
271 Public method to generate an indicator rectangle. |
|
272 |
|
273 @param position indicator position (integer) |
|
274 @return indicator rectangle (QRect) |
|
275 """ |
|
276 return QRect(self.__lineBorder, position - self.__lineHeight // 2, |
|
277 self.__width - self.__lineBorder, self.__lineHeight) |
|
278 |
|
279 def __generateSliderRange(self, scrollbar): |
|
280 """ |
|
281 Private method to generate the slider rectangle. |
|
282 |
|
283 @param scrollbar reference to the vertical scrollbar (QScrollBar) |
|
284 @return slider rectangle (QRect) |
|
285 """ |
|
286 pos1 = self.value2Position(scrollbar.value(), slider=True) |
|
287 pos2 = self.value2Position(scrollbar.value() + scrollbar.pageStep(), |
|
288 slider=True) |
|
289 return QRect(1, pos1, self.__width - 2, pos2 - pos1 + 1) |
|
290 # TODO: check slider appearance and adjust to self.__width |