layouts.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. from qtpy import QtCore, QtWidgets
  2. from qtpy.QtCore import Qt
  3. class LeftHBoxLayout(QtWidgets.QHBoxLayout):
  4. '''
  5. Left aligned horizontal layout.
  6. Hides items similar to Windows Explorer address bar.
  7. '''
  8. # Signal is emitted when an item is hidden/shown or removed with `takeAt`
  9. widget_state_changed = QtCore.Signal(object, bool)
  10. def __init__(self, parent=None, minimal_space=0.1):
  11. super().__init__(parent)
  12. self.first_visible = 0
  13. self.set_space_widget()
  14. self.set_minimal_space(minimal_space)
  15. def set_space_widget(self, widget=None, stretch=1):
  16. """
  17. Set widget to be used to fill empty space to the right
  18. If `widget`=None the stretch item is used (by default)
  19. """
  20. super().takeAt(self.count())
  21. if widget:
  22. super().addWidget(widget, stretch)
  23. else:
  24. self.addStretch(stretch)
  25. def space_widget(self):
  26. "Widget used to fill free space"
  27. return self[self.count()]
  28. def setGeometry(self, rc:QtCore.QRect):
  29. "`rc` - layout's rectangle w/o margins"
  30. super().setGeometry(rc) # perform the layout
  31. min_sp = self.minimal_space()
  32. if min_sp < 1: # percent
  33. min_sp *= rc.width()
  34. free_space = self[self.count()].geometry().width() - min_sp
  35. if free_space < 0 and self.count_visible() > 1: # hide more items
  36. widget = self[self.first_visible].widget()
  37. widget.hide()
  38. self.first_visible += 1
  39. self.widget_state_changed.emit(widget, False)
  40. elif free_space > 0 and self.count_hidden(): # show more items
  41. widget = self[self.first_visible-1].widget()
  42. w_width = widget.width() + self.spacing()
  43. if w_width <= free_space: # enough space to show next item
  44. # setGeometry is called after show
  45. QtCore.QTimer.singleShot(0, widget.show)
  46. self.first_visible -= 1
  47. self.widget_state_changed.emit(widget, True)
  48. def count_visible(self):
  49. "Count of visible widgets"
  50. return self.count(visible=True)
  51. def count_hidden(self):
  52. "Count of hidden widgets"
  53. return self.count(visible=False)
  54. def minimumSize(self):
  55. margins = self.contentsMargins()
  56. return QtCore.QSize(margins.left() + margins.right(),
  57. margins.top() + 24 + margins.bottom())
  58. def addWidget(self, widget, stretch=0, alignment=None):
  59. "Append widget to layout, make its width fixed"
  60. # widget.setMinimumSize(widget.minimumSizeHint()) # FIXME:
  61. super().insertWidget(self.count(), widget, stretch,
  62. alignment or Qt.Alignment(0))
  63. def count(self, visible=None):
  64. "Count of items in layout: `visible`=True|False(hidden)|None(all)"
  65. cnt = super().count() - 1 # w/o last stretchable item
  66. if visible is None: # all items
  67. return cnt
  68. if visible: # visible items
  69. return cnt - self.first_visible
  70. return self.first_visible # hidden items
  71. def widgets(self, state='all'):
  72. "Iterate over child widgets"
  73. for i in range(self.first_visible if state=='visible' else 0,
  74. self.first_visible if state=='hidden' else self.count()
  75. ):
  76. yield self[i].widget()
  77. def set_minimal_space(self, value):
  78. """
  79. Set minimal size of space area to the right:
  80. [0.0, 1.0) - % of the full width
  81. [1, ...) - size in pixels
  82. """
  83. self._minimal_space = value
  84. self.invalidate()
  85. def minimal_space(self):
  86. "See `set_minimal_space`"
  87. return self._minimal_space
  88. def __getitem__(self, index):
  89. "`itemAt` slices wrapper"
  90. if index < 0:
  91. index = self.count() + index
  92. return self.itemAt(index)
  93. def takeAt(self, index):
  94. "Return an item at the specified `index` and remove it from layout"
  95. if index < self.first_visible:
  96. self.first_visible -= 1
  97. item = super().takeAt(index)
  98. self.widget_state_changed.emit(item.widget(), False)
  99. return item