label_list_widget.py 5.6 KB

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