label_list_widget.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  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 LabelListWidget(QtWidgets.QListView):
  74. itemDoubleClicked = QtCore.Signal(LabelListWidgetItem)
  75. itemSelectionChanged = QtCore.Signal(list, list)
  76. itemDropped = QtCore.Signal()
  77. def __init__(self):
  78. super(LabelListWidget, self).__init__()
  79. self._selectedItems = []
  80. self.setWindowFlags(Qt.Window)
  81. self.setModel(QtGui.QStandardItemModel())
  82. self.model().setItemPrototype(LabelListWidgetItem())
  83. self.setItemDelegate(HTMLDelegate())
  84. self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
  85. self.setDragDropMode(QtWidgets.QAbstractItemView.InternalMove)
  86. self.setDefaultDropAction(Qt.MoveAction)
  87. self.doubleClicked.connect(self.itemDoubleClickedEvent)
  88. self.selectionModel().selectionChanged.connect(
  89. self.itemSelectionChangedEvent
  90. )
  91. def __len__(self):
  92. return self.model().rowCount()
  93. def __getitem__(self, i):
  94. return self.model().item(i)
  95. def __iter__(self):
  96. for i in range(len(self)):
  97. yield self[i]
  98. def dropEvent(self, event):
  99. super(LabelListWidget, self).dropEvent(event)
  100. self.itemDropped.emit()
  101. @property
  102. def itemChanged(self):
  103. return self.model().itemChanged
  104. def itemSelectionChangedEvent(self, selected, deselected):
  105. selected = [self.model().itemFromIndex(i) for i in selected.indexes()]
  106. deselected = [
  107. self.model().itemFromIndex(i) for i in deselected.indexes()
  108. ]
  109. self.itemSelectionChanged.emit(selected, deselected)
  110. def itemDoubleClickedEvent(self, index):
  111. self.itemDoubleClicked.emit(self.model().itemFromIndex(index))
  112. def selectedItems(self):
  113. return [self.model().itemFromIndex(i) for i in self.selectedIndexes()]
  114. def scrollToItem(self, item):
  115. self.scrollTo(self.model().indexFromItem(item))
  116. def addItem(self, item):
  117. if not isinstance(item, LabelListWidgetItem):
  118. raise TypeError("item must be LabelListWidgetItem")
  119. self.model().setItem(self.model().rowCount(), 0, item)
  120. item.setSizeHint(self.itemDelegate().sizeHint(None, None))
  121. def selectItem(self, item):
  122. index = self.model().indexFromItem(item)
  123. self.selectionModel().select(index, QtCore.QItemSelectionModel.Select)
  124. def findItemByShape(self, shape):
  125. for row in range(self.model().rowCount()):
  126. item = self.model().item(row, 0)
  127. if item.shape() == shape:
  128. return item
  129. def clear(self):
  130. self.model().clear()