label_list_widget.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. from qtpy import QtCore
  2. from qtpy.QtCore import Qt
  3. from qtpy import QtGui
  4. from qtpy.QtGui import QPalette
  5. from qtpy import QtWidgets
  6. from qtpy.QtWidgets import QStyle
  7. # https://stackoverflow.com/a/2039745/4158863
  8. class HTMLDelegate(QtWidgets.QStyledItemDelegate):
  9. def __init__(self, parent=None):
  10. super(HTMLDelegate, self).__init__()
  11. self.doc = QtGui.QTextDocument(self)
  12. def paint(self, painter, option, index):
  13. painter.save()
  14. options = QtWidgets.QStyleOptionViewItem(option)
  15. self.initStyleOption(options, index)
  16. self.doc.setHtml(options.text)
  17. options.text = ""
  18. style = (
  19. QtWidgets.QApplication.style()
  20. if options.widget is None
  21. else options.widget.style()
  22. )
  23. style.drawControl(QStyle.CE_ItemViewItem, options, painter)
  24. ctx = QtGui.QAbstractTextDocumentLayout.PaintContext()
  25. if option.state & QStyle.State_Selected:
  26. ctx.palette.setColor(
  27. QPalette.Text,
  28. option.palette.color(
  29. QPalette.Active, QPalette.HighlightedText
  30. ),
  31. )
  32. else:
  33. ctx.palette.setColor(
  34. QPalette.Text,
  35. option.palette.color(QPalette.Active, QPalette.Text),
  36. )
  37. textRect = style.subElementRect(QStyle.SE_ItemViewItemText, options)
  38. if index.column() != 0:
  39. textRect.adjust(5, 0, 0, 0)
  40. thefuckyourshitup_constant = 4
  41. margin = (option.rect.height() - options.fontMetrics.height()) // 2
  42. margin = margin - thefuckyourshitup_constant
  43. textRect.setTop(textRect.top() + margin)
  44. painter.translate(textRect.topLeft())
  45. painter.setClipRect(textRect.translated(-textRect.topLeft()))
  46. self.doc.documentLayout().draw(painter, ctx)
  47. painter.restore()
  48. def sizeHint(self, option, index):
  49. thefuckyourshitup_constant = 4
  50. return QtCore.QSize(
  51. self.doc.idealWidth(),
  52. self.doc.size().height() - thefuckyourshitup_constant,
  53. )
  54. class LabelListWidgetItem(QtGui.QStandardItem):
  55. def __init__(self, text=None, shape=None):
  56. super(LabelListWidgetItem, self).__init__()
  57. self.setText(text)
  58. self.setShape(shape)
  59. self.setCheckable(True)
  60. self.setCheckState(Qt.Checked)
  61. self.setEditable(False)
  62. self.setTextAlignment(Qt.AlignBottom)
  63. def clone(self):
  64. return LabelListWidgetItem(self.text(), self.shape())
  65. def setShape(self, shape):
  66. self.setData(shape, Qt.UserRole)
  67. def shape(self):
  68. return self.data(Qt.UserRole)
  69. def __hash__(self):
  70. return id(self)
  71. def __repr__(self):
  72. return '{}("{}")'.format(self.__class__.__name__, self.text())
  73. class StandardItemModel(QtGui.QStandardItemModel):
  74. itemDropped = QtCore.Signal()
  75. def removeRows(self, *args, **kwargs):
  76. ret = super().removeRows(*args, **kwargs)
  77. self.itemDropped.emit()
  78. return ret
  79. class LabelListWidget(QtWidgets.QListView):
  80. itemDoubleClicked = QtCore.Signal(LabelListWidgetItem)
  81. itemSelectionChanged = QtCore.Signal(list, list)
  82. def __init__(self):
  83. super(LabelListWidget, self).__init__()
  84. self._selectedItems = []
  85. self.setWindowFlags(Qt.Window)
  86. self.setModel(StandardItemModel())
  87. self.model().setItemPrototype(LabelListWidgetItem())
  88. self.setItemDelegate(HTMLDelegate())
  89. self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
  90. self.setDragDropMode(QtWidgets.QAbstractItemView.InternalMove)
  91. self.setDefaultDropAction(Qt.MoveAction)
  92. self.doubleClicked.connect(self.itemDoubleClickedEvent)
  93. self.selectionModel().selectionChanged.connect(
  94. self.itemSelectionChangedEvent
  95. )
  96. def __len__(self):
  97. return self.model().rowCount()
  98. def __getitem__(self, i):
  99. return self.model().item(i)
  100. def __iter__(self):
  101. for i in range(len(self)):
  102. yield self[i]
  103. @property
  104. def itemDropped(self):
  105. return self.model().itemDropped
  106. @property
  107. def itemChanged(self):
  108. return self.model().itemChanged
  109. def itemSelectionChangedEvent(self, selected, deselected):
  110. selected = [self.model().itemFromIndex(i) for i in selected.indexes()]
  111. deselected = [
  112. self.model().itemFromIndex(i) for i in deselected.indexes()
  113. ]
  114. self.itemSelectionChanged.emit(selected, deselected)
  115. def itemDoubleClickedEvent(self, index):
  116. self.itemDoubleClicked.emit(self.model().itemFromIndex(index))
  117. def selectedItems(self):
  118. return [self.model().itemFromIndex(i) for i in self.selectedIndexes()]
  119. def scrollToItem(self, item):
  120. self.scrollTo(self.model().indexFromItem(item))
  121. def addItem(self, item):
  122. if not isinstance(item, LabelListWidgetItem):
  123. raise TypeError("item must be LabelListWidgetItem")
  124. self.model().setItem(self.model().rowCount(), 0, item)
  125. item.setSizeHint(self.itemDelegate().sizeHint(None, None))
  126. def removeItem(self, item):
  127. index = self.model().indexFromItem(item)
  128. self.model().removeRows(index.row(), 1)
  129. def selectItem(self, item):
  130. index = self.model().indexFromItem(item)
  131. self.selectionModel().select(index, QtCore.QItemSelectionModel.Select)
  132. def findItemByShape(self, shape):
  133. for row in range(self.model().rowCount()):
  134. item = self.model().item(row, 0)
  135. if item.shape() == shape:
  136. return item
  137. raise ValueError("cannot find shape: {}".format(shape))
  138. def clear(self):
  139. self.model().clear()