src/eric7/EricWidgets/EricIconBar.py

branch
eric7
changeset 9209
b99e7fd55fd3
parent 8881
54e42bc2437a
child 9221
bf71ee032bb4
equal deleted inserted replaced
9208:3fc8dfeb6ebe 9209:b99e7fd55fd3
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2021 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a bar widget showing just icons.
8 """
9
10 import contextlib
11
12 from PyQt6.QtCore import pyqtSignal, pyqtSlot, Qt, QCoreApplication
13 from PyQt6.QtGui import QColor, QIcon, QCursor, QPalette
14 from PyQt6.QtWidgets import QWidget, QBoxLayout, QWIDGETSIZE_MAX, QMenu
15
16 from EricWidgets.EricApplication import ericApp
17
18 from .EricClickableLabel import EricClickableLabel
19
20 import UI.PixmapCache
21
22
23 class EricIconBar(QWidget):
24 """
25 Class implementing a bar widget showing just icons.
26
27 @signal currentChanged(index) emitted to indicate a change of the current
28 index
29 @signal currentClicked(index) emitted to indicate, that the current icon
30 was clicked
31 """
32 BarSizes = {
33 # tuples with (icon size, border size, translated size string)
34 "xs": (
35 16, 1,
36 QCoreApplication.translate("EricIconBar", "extra small")
37 ),
38 "sm": (
39 22, 1,
40 QCoreApplication.translate("EricIconBar", "small")
41 ),
42 "md": (
43 32, 2,
44 QCoreApplication.translate("EricIconBar", "medium")
45 ),
46 "lg": (
47 48, 2,
48 QCoreApplication.translate("EricIconBar", "large")
49 ),
50 "xl": (
51 64, 3,
52 QCoreApplication.translate("EricIconBar", "extra large")
53 ),
54 "xxl": (
55 96, 3,
56 QCoreApplication.translate("EricIconBar", "very large")
57 ),
58 }
59 DefaultBarSize = "md"
60
61 MoreLabelAspect = 36 / 96
62
63 MenuStyleSheetTemplate = (
64 "QMenu {{ background-color: {0}; "
65 "selection-background-color: {1}; "
66 "border: 1px solid; }}"
67 )
68 WidgetStyleSheetTemplate = "QWidget {{ background-color: {0}; }}"
69 LabelStyleSheetTemplate = "QLabel {{ background-color: {0}; }}"
70
71 currentChanged = pyqtSignal(int)
72 currentClicked = pyqtSignal(int)
73
74 def __init__(self, orientation=Qt.Orientation.Horizontal,
75 barSize=DefaultBarSize, parent=None):
76 """
77 Constructor
78
79 @param orientation orientation for the widget
80 @type Qt.Orientation
81 @param barSize size category for the bar (one of 'xs', 'sm', 'md',
82 'lg', 'xl', 'xxl')
83 @type str
84 @param parent reference to the parent widget (defaults to None)
85 @type QWidget (optional)
86 """
87 super().__init__(parent)
88
89 try:
90 self.__barSize, self.__borderSize = (
91 EricIconBar.BarSizes[barSize][:2])
92 self.__barSizeKey = barSize
93 except KeyError:
94 self.__barSize, self.__borderSize = (
95 EricIconBar.BarSizes[EricIconBar.DefaultBarSize][:2])
96 self.__fixedHeightWidth = (
97 self.__barSize + 2 * self.__borderSize
98 )
99 self.__minimumHeightWidth = int(
100 self.__barSize * self.MoreLabelAspect) + 2 * self.__borderSize
101
102 # set initial values
103 self.__color = QColor("#008800")
104 self.__orientation = Qt.Orientation.Horizontal
105 self.__currentIndex = -1
106 self.__icons = []
107
108 # initialize with horizontal layout and change later if needed
109 self.setAttribute(Qt.WidgetAttribute.WA_StyledBackground, True)
110 self.setFixedHeight(self.__fixedHeightWidth)
111 self.setMinimumWidth(self.__minimumHeightWidth)
112
113 self.__layout = QBoxLayout(QBoxLayout.Direction.LeftToRight)
114 self.__layout.setContentsMargins(
115 self.__borderSize, self.__borderSize,
116 self.__borderSize, self.__borderSize)
117
118 self.__layout.addStretch()
119
120 self.setLayout(self.__layout)
121
122 if orientation != self.__orientation:
123 self.setOrientation(orientation)
124
125 self.setColor(self.__color)
126
127 self.__createAndAddMoreLabel()
128
129 self.__adjustIconLabels()
130
131 def setOrientation(self, orientation):
132 """
133 Public method to set the widget orientation.
134
135 @param orientation orientation to be set
136 @type Qt.Orientation
137 """
138 # reset list widget size constraints
139 self.setFixedSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX)
140
141 # remove the 'More' icon
142 itm = self.__layout.takeAt(self.__layout.count() - 1)
143 itm.widget().deleteLater()
144 del itm
145
146 if orientation == Qt.Orientation.Horizontal:
147 self.setFixedHeight(self.__fixedHeightWidth)
148 self.setMinimumWidth(self.__minimumHeightWidth)
149 self.__layout.setDirection(QBoxLayout.Direction.LeftToRight)
150 elif orientation == Qt.Orientation.Vertical:
151 self.setFixedWidth(self.__fixedHeightWidth)
152 self.setMinimumHeight(self.__minimumHeightWidth)
153 self.__layout.setDirection(QBoxLayout.Direction.TopToBottom)
154
155 self.__orientation = orientation
156
157 self.__createAndAddMoreLabel()
158
159 self.__adjustIconLabels()
160
161 def orientation(self):
162 """
163 Public method to get the orientation of the widget.
164
165 @return orientation of the widget
166 @rtype Qt.Orientation
167 """
168 return self.__orientation
169
170 def setBarSize(self, barSize):
171 """
172 Public method to set the icon bar size.
173
174 @param barSize size category for the bar (one of 'xs', 'sm', 'md',
175 'lg', 'xl', 'xxl')
176 @type str
177 """
178 # remove the 'More' icon
179 itm = self.__layout.takeAt(self.__layout.count() - 1)
180 itm.widget().deleteLater()
181 del itm
182
183 self.__barSize, self.__borderSize = (
184 EricIconBar.BarSizes[barSize][:2])
185 self.__barSizeKey = barSize
186 self.__fixedHeightWidth = (
187 self.__barSize + 2 * self.__borderSize
188 )
189 self.__minimumHeightWidth = int(
190 self.__barSize * self.MoreLabelAspect) + 2 * self.__borderSize
191
192 if self.__orientation == Qt.Orientation.Horizontal:
193 self.setFixedHeight(self.__fixedHeightWidth)
194 self.setMinimumWidth(self.__minimumHeightWidth)
195 elif self.__orientation == Qt.Orientation.Vertical:
196 self.setFixedWidth(self.__fixedHeightWidth)
197 self.setMinimumHeight(self.__minimumHeightWidth)
198
199 self.__layout.setContentsMargins(
200 self.__borderSize, self.__borderSize,
201 self.__borderSize, self.__borderSize)
202
203 for index, icon in enumerate(self.__icons):
204 iconLabel = self.__layout.itemAt(index)
205 if iconLabel:
206 widget = iconLabel.widget()
207 widget.setFixedSize(self.__barSize, self.__barSize)
208 widget.setPixmap(
209 icon.pixmap(self.__barSize, self.__barSize))
210
211 self.__createAndAddMoreLabel()
212
213 self.__adjustIconLabels()
214
215 def barSize(self):
216 """
217 Public method to get the icon bar size.
218
219 @return barSize size category for the bar (one of 'xs', 'sm', 'md',
220 'lg', 'xl', 'xxl')
221 @rtype str
222 """
223 return self.__barSizeKey
224
225 def setColor(self, color):
226 """
227 Public method to set the color of the widget.
228
229 @param color color of the widget
230 @type QColor
231 """
232 self.__color = color
233 self.__highlightColor = color.darker()
234
235 self.setStyleSheet(
236 EricIconBar.WidgetStyleSheetTemplate.format(color.name()))
237
238 label = self.__layout.itemAt(self.__currentIndex)
239 if label:
240 label.widget().setStyleSheet(
241 EricIconBar.LabelStyleSheetTemplate
242 .format(self.__highlightColor.name())
243 )
244
245 def color(self):
246 """
247 Public method to return the current color.
248
249 @return current color
250 @rtype QColor
251 """
252 return self.__color
253
254 def __createIcon(self, icon, label=""):
255 """
256 Private method to creat an icon label.
257
258 @param icon reference to the icon
259 @type QIcon
260 @param label label text to be shown as a tooltip (defaults to "")
261 @type str (optional)
262 @return created and connected label
263 @rtype EricClickableLabel
264 """
265 iconLabel = EricClickableLabel(self)
266 iconLabel.setFixedSize(self.__barSize, self.__barSize)
267 iconLabel.setAlignment(Qt.AlignmentFlag.AlignCenter)
268 iconLabel.setPixmap(icon.pixmap(self.__barSize, self.__barSize))
269 if label:
270 iconLabel.setToolTip(label)
271
272 iconLabel.clicked.connect(lambda: self.__iconClicked(iconLabel))
273
274 return iconLabel
275
276 def __createAndAddMoreLabel(self):
277 """
278 Private method to create the label to be shown for too many icons.
279 """
280 self.__moreLabel = EricClickableLabel(self)
281 self.__moreLabel.setAlignment(Qt.AlignmentFlag.AlignCenter)
282 if self.__orientation == Qt.Orientation.Horizontal:
283 self.__moreLabel.setFixedSize(
284 int(self.__barSize * self.MoreLabelAspect), self.__barSize)
285 self.__moreLabel.setPixmap(
286 UI.PixmapCache.getIcon("sbDotsH96").pixmap(
287 int(self.__barSize * self.MoreLabelAspect), self.__barSize
288 )
289 )
290 else:
291 self.__moreLabel.setFixedSize(
292 self.__barSize, int(self.__barSize * self.MoreLabelAspect))
293 self.__moreLabel.setPixmap(
294 UI.PixmapCache.getIcon("sbDotsV96").pixmap(
295 self.__barSize, int(self.__barSize * self.MoreLabelAspect)
296 )
297 )
298
299 self.__layout.addWidget(self.__moreLabel)
300
301 self.__moreLabel.clicked.connect(self.__moreLabelClicked)
302
303 def addIcon(self, icon, label=""):
304 """
305 Public method to add an icon to the bar.
306
307 @param icon reference to the icon
308 @type QIcon
309 @param label label text to be shown as a tooltip (defaults to "")
310 @type str (optional)
311 """
312 # the stretch item is always the last one
313 self.insertIcon(self.count(), icon, label=label)
314
315 def insertIcon(self, index, icon, label=""):
316 """
317 Public method to insert an icon into the bar.
318
319 @param index position to insert the icon at
320 @type int
321 @param icon reference to the icon
322 @type QIcon
323 @param label label text to be shown as a tooltip (defaults to "")
324 @type str (optional)
325 """
326 iconLabel = self.__createIcon(icon, label=label)
327 self.__layout.insertWidget(index, iconLabel)
328 self.__icons.insert(index, QIcon(icon))
329
330 if self.__currentIndex < 0:
331 self.setCurrentIndex(index)
332 elif index <= self.__currentIndex:
333 self.setCurrentIndex(self.__currentIndex + 1)
334
335 self.__adjustIconLabels()
336
337 def removeIcon(self, index):
338 """
339 Public method to remove an icon from the bar.
340
341 @param index index of the icon to be removed
342 @type int
343 """
344 label = self.__layout.itemAt(index)
345 if label:
346 with contextlib.suppress(IndexError):
347 del self.__icons[index]
348 itm = self.__layout.takeAt(index)
349 itm.widget().deleteLater()
350 del itm
351
352 if index == self.__currentIndex:
353 self.setCurrentIndex(index)
354 elif index < self.__currentIndex:
355 self.setCurrentIndex(self.__currentIndex - 1)
356
357 self.__adjustIconLabels()
358
359 @pyqtSlot()
360 def __iconClicked(self, label):
361 """
362 Private slot to handle an icon been clicked.
363
364 @param label reference to the clicked label
365 @type EricClickableLabel
366 """
367 index = self.__layout.indexOf(label)
368 if index >= 0:
369 if index == self.__currentIndex:
370 self.currentClicked.emit(self.__currentIndex)
371 else:
372 self.setCurrentIndex(index)
373
374 def setCurrentIndex(self, index):
375 """
376 Public method to set the current index.
377
378 @param index current index to be set
379 @type int
380 """
381 if index >= self.count():
382 index = -1
383
384 if index != self.__currentIndex and index >= 0:
385 # reset style of previous current icon
386 oldLabel = self.__layout.itemAt(self.__currentIndex)
387 if oldLabel:
388 widget = oldLabel.widget()
389 if widget is not None:
390 widget.setStyleSheet("")
391
392 # set style of new current icon
393 newLabel = self.__layout.itemAt(index)
394 if newLabel:
395 newLabel.widget().setStyleSheet(
396 EricIconBar.LabelStyleSheetTemplate
397 .format(self.__highlightColor.name())
398 )
399
400 self.__currentIndex = index
401 self.currentChanged.emit(self.__currentIndex)
402
403 def currentIndex(self):
404 """
405 Public method to get the current index.
406
407 @return current index
408 @rtype int
409 """
410 return self.__currentIndex
411
412 def count(self):
413 """
414 Public method to get the number of icon labels.
415
416 @return number of icon labels
417 @rtype int
418 """
419 return len(self.__icons)
420
421 def wheelEvent(self, evt):
422 """
423 Protected method to handle a wheel event.
424
425 @param evt reference to the wheel event
426 @type QWheelEvent
427 """
428 delta = evt.angleDelta().y()
429 if delta > 0:
430 self.previousIcon()
431 else:
432 self.nextIcon()
433
434 @pyqtSlot()
435 def previousIcon(self):
436 """
437 Public slot to set the icon before the current one.
438 """
439 index = self.__currentIndex - 1
440 if index < 0:
441 # wrap around
442 index = self.count() - 1
443
444 self.setCurrentIndex(index)
445
446 @pyqtSlot()
447 def nextIcon(self):
448 """
449 Public slot to set the icon after the current one.
450 """
451 index = self.__currentIndex + 1
452 if index == self.count():
453 # wrap around
454 index = 0
455
456 self.setCurrentIndex(index)
457
458 @pyqtSlot()
459 def __moreLabelClicked(self):
460 """
461 Private slot to handle a click onto the 'More' label.
462 """
463 menu = QMenu(self)
464 baseColor = ericApp().palette().color(
465 QPalette.ColorRole.Base)
466 highlightColor = ericApp().palette().color(
467 QPalette.ColorRole.Highlight)
468 menu.setStyleSheet(
469 EricIconBar.MenuStyleSheetTemplate.format(
470 baseColor.name(), highlightColor.name()))
471
472 for index in range(self.count()):
473 iconLabel = self.__layout.itemAt(index)
474 if iconLabel:
475 widget = iconLabel.widget()
476 if not widget.isVisible():
477 act = menu.addAction(widget.toolTip())
478 act.setData(index)
479
480 selectedAction = menu.exec(QCursor.pos())
481 if selectedAction is not None:
482 index = selectedAction.data()
483 if index >= 0:
484 if index == self.__currentIndex:
485 self.currentClicked.emit(self.__currentIndex)
486 else:
487 self.setCurrentIndex(index)
488
489 def resizeEvent(self, evt):
490 """
491 Protected method to handle resizing of the icon bar.
492
493 @param evt reference to the event object
494 @type QResizeEvent
495 """
496 self.__adjustIconLabels()
497
498 def __adjustIconLabels(self):
499 """
500 Private method to adjust the visibility of the icon labels.
501 """
502 size = (
503 self.width()
504 if self.orientation() == Qt.Orientation.Horizontal else
505 self.height()
506 ) - 2 * self.__borderSize
507
508 iconsSize = self.count() * self.__barSize
509
510 if size < iconsSize:
511 self.__moreLabel.show()
512 iconsSize += int(self.__barSize * self.MoreLabelAspect)
513 for index in range(self.count() - 1, -1, -1):
514 iconLabel = self.__layout.itemAt(index)
515 if iconLabel:
516 if size < iconsSize:
517 iconLabel.widget().hide()
518 iconsSize -= self.__barSize
519 else:
520 iconLabel.widget().show()
521 else:
522 self.__moreLabel.hide()
523 for index in range(self.count()):
524 iconLabel = self.__layout.itemAt(index)
525 if iconLabel:
526 iconLabel.widget().show()

eric ide

mercurial