How can I add a button to the right side of QListViewItems? I'm trying to recreate a similar tag editor like the one you see here on Stackoverflow.
Current:
Goal:
import os, sys, re, glob, pprint
from Qt import QtCore, QtGui, QtWidgets
class TagsEditorWidget(QtWidgets.QWidget):
def __init__(self, *args, **kwargs):
QtWidgets.QWidget.__init__(self)
self.setWindowTitle('Tags')
self.resize(640,400)
# privates
self._tags = []
# controls
self.uiLineEdit = QtWidgets.QLineEdit('df, df,d , dfd d, ')
self.uiAdd = QtWidgets.QToolButton()
self.uiAdd.setText('+')
self.uiAdd.setIcon(QtGui.QIcon(StyleUtils.getIconFilepath('add.svg')))
self.uiAdd.setIconSize(QtCore.QSize(16,16))
self.uiAdd.setFixedSize(24,24)
self.uiAdd.setFocusPolicy(QtCore.Qt.ClickFocus)
self.uiListView = QtWidgets.QListView()
self.uiListView.setViewMode(QtWidgets.QListView.IconMode)
self.uiListView.setMovement(QtWidgets.QListView.Static)
self.uiListView.setResizeMode(QtWidgets.QListView.Adjust)
self.uiListView.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection)
self.uiListView.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
model = QtCore.QStringListModel()
self.uiListView.setModel(model)
# layout
self.hLayout = QtWidgets.QHBoxLayout()
self.hLayout.setContentsMargins(0,0,0,0)
self.hLayout.setSpacing(5)
self.hLayout.addWidget(self.uiLineEdit)
self.hLayout.addWidget(self.uiAdd)
self.layout = QtWidgets.QVBoxLayout(self)
self.layout.setContentsMargins(0,0,0,0)
self.layout.setSpacing(0)
self.layout.addLayout(self.hLayout)
self.layout.addWidget(self.uiListView)
self.layout.setStretch(0,1.0)
# Signals
self.uiAdd.clicked.connect(self.slotEnteredTags)
self.uiLineEdit.returnPressed.connect(self.slotEnteredTags)
self.setStyleSheet('''
QListView:item {
background: rgb(200,225,240);
margin: 2px;
padding: 2px;
border-radius: 2px;
}
''')
# Methods
def setTags(self, tags=None):
tags = [] if tags == None else tags
tags = self.getUniqueList(tags)
self._tags = tags
# Update ui
model = self.uiListView.model()
model.setStringList(self._tags)
def getTags(self):
return self._tags
def getUniqueList(self, lst):
result=[]
marker = set()
for l in lst:
lc = l.lower()
if lc not in marker:
marker.add(lc)
result.append(l)
return result
def appendTag(self, tag):
# split by comma and remove leading/trailing spaces and empty strings
tags = filter(None, [x.strip() for x in tag.split(',')])
self.setTags(self.getTags() + tags)
def appendTags(self, tags):
for t in tags:
self.appendTag(t)
# Slots
def slotEnteredTags(self):
self.appendTag(self.uiLineEdit.text())
self.uiLineEdit.clear()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
ex = TagsEditorWidget()
ex.setTags(["Paper", "Plastic", "Aluminum", "Paper", "Tin", "Glass", "Tin", "Polypropylene Plastic"])
ex.show()
sys.exit(app.exec_())
UPDATE #1
I attempted to use the ItemDelegate, however it appears to produce more problems, like spacing and padding around the text within the list. I don't understand why the SizeHint i override doesn't appear to properly work. The X button still overlap when they shouldn't.
import os, sys, re, glob, pprint
from Qt import QtCore, QtGui, QtWidgets
class TagDelegate(QtWidgets.QItemDelegate):
def __init__(self, parent=None, *args):
QtWidgets.QItemDelegate.__init__(self, parent, *args)
def paint(self, painter, option, index):
painter.save()
isw, ish = option.decorationSize.toTuple()
x, y = option.rect.topLeft().toTuple()
dx, dy = option.rect.size().toTuple()
value = index.data(QtCore.Qt.DisplayRole)
rect = QtCore.QRect(x, y, dx, dy)
painter.setPen(QtGui.QPen(QtCore.Qt.NoPen))
painter.setBrush(QtGui.QBrush(QtCore.Qt.blue))
painter.drawRect(rect)
painter.setPen(QtGui.QPen(QtCore.Qt.black))
painter.drawText(rect, QtCore.Qt.AlignLeft, value)
rect = QtCore.QRect(x+dx-5, y, 16, dy)
painter.setPen(QtGui.QPen(QtCore.Qt.NoPen))
painter.setBrush(QtGui.QBrush(QtCore.Qt.gray))
painter.drawRect(rect)
painter.setPen(QtGui.QPen(QtCore.Qt.black))
painter.drawText(rect, QtCore.Qt.AlignCenter, 'x')
painter.restore()
def sizeHint(self, option, index):
if index.data():
font = QtGui.QFontMetrics(option.font)
text = index.data(QtCore.Qt.DisplayRole)
rect = font.boundingRect(option.rect, QtCore.Qt.TextSingleLine, text)
# rect.adjust(0, 0, 15, 0)
return QtCore.QSize(rect.width(), rect.height())
return super(TagDelegate, self).sizeHint(option, index)
class TagListView(QtWidgets.QListView):
def __init__(self, *arg, **kwargs):
super(TagListView, self).__init__()
self.setViewMode(QtWidgets.QListView.IconMode)
self.setMovement(QtWidgets.QListView.Static)
self.setResizeMode(QtWidgets.QListView.Adjust)
self.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection)
self.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
self.setMouseTracking(True)
self.setItemDelegate(TagDelegate())
class TagsEditorWidget(QtWidgets.QWidget):
def __init__(self, *args, **kwargs):
QtWidgets.QWidget.__init__(self)
self.setWindowTitle('Tags')
self.resize(640,400)
# privates
self._tags = []
# controls
self.uiLineEdit = QtWidgets.QLineEdit('df, df,d , dfd d, ')
self.uiAdd = QtWidgets.QToolButton()
self.uiAdd.setText('+')
# self.uiAdd.setIcon(QtGui.QIcon(StyleUtils.getIconFilepath('add.svg')))
self.uiAdd.setIconSize(QtCore.QSize(16,16))
self.uiAdd.setFixedSize(24,24)
self.uiAdd.setFocusPolicy(QtCore.Qt.ClickFocus)
self.uiListView = TagListView()
model = QtCore.QStringListModel()
self.uiListView.setModel(model)
# layout
self.hLayout = QtWidgets.QHBoxLayout()
self.hLayout.setContentsMargins(0,0,0,0)
self.hLayout.setSpacing(5)
self.hLayout.addWidget(self.uiLineEdit)
self.hLayout.addWidget(self.uiAdd)
self.layout = QtWidgets.QVBoxLayout(self)
self.layout.setContentsMargins(0,0,0,0)
self.layout.setSpacing(0)
self.layout.addLayout(self.hLayout)
self.layout.addWidget(self.uiListView)
self.layout.setStretch(0,1.0)
# Signals
self.uiAdd.clicked.connect(self.slotEnteredTags)
self.uiLineEdit.returnPressed.connect(self.slotEnteredTags)
self.setStyleSheet('''
QListView:item {
background: rgb(200,225,240);
margin: 2px;
padding: 2px;
border-radius: 2px;
}
''')
# Methods
def setTags(self, tags=None):
tags = [] if tags == None else tags
tags = self.getUniqueList(tags)
self._tags = tags
# Update ui
model = self.uiListView.model()
model.setStringList(self._tags)
def getTags(self):
return self._tags
def getUniqueList(self, lst):
result=[]
marker = set()
for l in lst:
lc = l.lower()
if lc not in marker:
marker.add(lc)
result.append(l)
return result
def appendTag(self, tag):
# split by comma and remove leading/trailing spaces and empty strings
tags = filter(None, [x.strip() for x in tag.split(',')])
self.setTags(self.getTags() + tags)
def appendTags(self, tags):
for t in tags:
self.appendTag(t)
# Slots
def slotEnteredTags(self):
self.appendTag(self.uiLineEdit.text())
self.uiLineEdit.clear()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
ex = TagsEditorWidget()
ex.setTags(["Paper", "Plastic", "Aluminum", "Paper", "Tin", "Glass", "Tin", "Polypropylene Plastic"])
ex.show()
sys.exit(app.exec_())
The size returned by sizeHint() has to cover the whole item size. In your paintEvent method, QtCore.QRect(x+dx-5, y, 16, dy) defines a rect with 11 pixels outside the items bounding rect (16 - 5).
The easiest way to calculate the right size is to consider the button size in sizeHint:
class TagDelegate(QStyledItemDelegate):
def __init__(self, parent=None):
super().__init__(parent)
self._buttonSize = QSize(16, 16)
def buttonRect(self, boundingRect):
return boundingRect.adjusted(boundingRect.width() - self._buttonSize.width(), 0, 0, 0)
def labelRect(self, boundingRect):
return boundingRect.adjusted(0, 0, -self._buttonSize.width(), 0)
def paint(self, painter, option, index):
text = index.data(QtCore.Qt.DisplayRole)
painter.save()
painter.drawText(self.labelRect(option.rect), QtCore.Qt.AlignLeft, text)
rect = self.buttonRect(option.rect)
painter.setPen(QtGui.QPen(QtCore.Qt.NoPen))
painter.setBrush(QtGui.QBrush(QtCore.Qt.gray))
painter.drawRect(rect)
painter.setPen(QtGui.QPen(QtCore.Qt.black))
painter.drawText(rect, QtCore.Qt.AlignCenter, 'x')
painter.restore()
def sizeHint(self, option, index):
if index.data():
font = QtGui.QFontMetrics(option.font)
text = index.data(QtCore.Qt.DisplayRole)
rect = font.boundingRect(option.rect, QtCore.Qt.TextSingleLine, text)
return QSize(rect.width() + self._buttonSize.width(), max(rect.height(), self._buttonSize.height()))
return super(TagDelegate, self).sizeHint(option, index)
I used two methods (buttonRect and labelRect) to get the position of each part of the item. It will make easier to handle the click on the close button:
def editorEvent(self, event, model, option, index):
if not isinstance(event, QMouseEvent) or event.type() != QEvent.MouseButtonPress:
return super().editorEvent(event, model, option, index)
if self.buttonRect(option.rect).contains(event.pos()):
print("Close button on ", model.data(index, Qt.DisplayRole))
return True
elif self.labelRect(option.rect).contains(event.pos()):
print("Label clicked on ", model.data(index, Qt.DisplayRole))
return True
return False
Related
I am attempting to design a label class that inherits from the PyQt5 base QLabel class that is able to track another widget. Here is the current code for my class:
class AttachedLabel(QLabel):
def __init__(self, attachedTo, *args, side="left", ** kwargs):
super().__init__(*args, **kwargs) # Run parent initialization
# Define instance variables
self.attached = attachedTo
self.side = side
# Update label position
self.updatePos()
def updatePos(self):
# Get "attached widget" position and dimensions
x = self.attached.geometry().x()
y = self.attached.geometry().y()
aWidth = self.attached.geometry().width()
aHeight = self.attached.geometry().height()
# Get own dimensions
width = self.geometry().width()
height = self.geometry().height()
if self.side == "top": # Above of attached widget
self.setGeometry(x, y-height, width, height)
elif self.side == "bottom": # Below attached widget
self.setGeometry(x, y+height+aHeight, width, height)
elif self.side == "right": # Right of attached widget
self.setGeometry(x + width + aWidth, y, width, height)
else: # Left of attached widget
self.setGeometry(x - width, y, width, height)
I want to be able to instantiate the label like so:
AttachedLabel(self.pushButton, self.centralwidget)
where self.pushButton is the widget it is supposed to be following. The issue is that I don't know how to detect when the widget moves in order to run my updatePos() function. I would ideally only update the label position when the other widget moves, but I want to refrain from havign to add extra code to the class of the widget that is being tracked. I have tried overriding the paintEvent, but that only triggers when the object itself needs to be redrawn, so it doesn't even function as a sub-optimal solution.
Is there some built-in method I can use/override to detect when the widget moves or when the screen itself is updated?
You have to use an eventFilter intersecting the QEvent::Move event and you should also track the resize through the QEvent::Resize event.
from dataclasses import dataclass, field
import random
from PyQt5 import QtCore, QtWidgets
class GeometryTracker(QtCore.QObject):
geometryChanged = QtCore.pyqtSignal()
def __init__(self, widget):
super().__init__(widget)
self._widget = widget
self.widget.installEventFilter(self)
#property
def widget(self):
return self._widget
def eventFilter(self, source, event):
if self.widget is source and event.type() in (
QtCore.QEvent.Move,
QtCore.QEvent.Resize,
):
self.geometryChanged.emit()
return super().eventFilter(source, event)
#dataclass
class TrackerManager:
widget1: field(default_factory=QtWidgets.QWidget)
widget2: field(default_factory=QtWidgets.QWidget)
alignment: QtCore.Qt.Alignment = QtCore.Qt.AlignLeft
enabled: bool = True
valid_alignments = (
QtCore.Qt.AlignLeft,
QtCore.Qt.AlignRight,
QtCore.Qt.AlignHCenter,
QtCore.Qt.AlignTop,
QtCore.Qt.AlignBottom,
QtCore.Qt.AlignVCenter,
)
def __post_init__(self):
self._traker = GeometryTracker(self.widget1)
self._traker.geometryChanged.connect(self.update)
if not any(self.alignment & flag for flag in self.valid_alignments):
raise ValueError("alignment is not valid")
def update(self):
if not self.enabled:
return
r = self.widget1.rect()
p1 = r.center()
c1 = r.center()
if self.alignment & QtCore.Qt.AlignLeft:
p1.setX(r.left())
if self.alignment & QtCore.Qt.AlignRight:
p1.setX(r.right())
if self.alignment & QtCore.Qt.AlignTop:
p1.setY(r.top())
if self.alignment & QtCore.Qt.AlignBottom:
p1.setY(r.bottom())
p2 = self.convert_position(p1)
c2 = self.convert_position(c1)
g = self.widget2.geometry()
g.moveCenter(c2)
if self.alignment & QtCore.Qt.AlignLeft:
g.moveRight(p2.x())
if self.alignment & QtCore.Qt.AlignRight:
g.moveLeft(p2.x())
if self.alignment & QtCore.Qt.AlignTop:
g.moveBottom(p2.y())
if self.alignment & QtCore.Qt.AlignBottom:
g.moveTop(p2.y())
self.widget2.setGeometry(g)
def convert_position(self, point):
gp = self.widget1.mapToGlobal(point)
if self.widget2.isWindow():
return gp
return self.widget2.parent().mapFromGlobal(gp)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.button = QtWidgets.QPushButton("Press me", self)
self.label = QtWidgets.QLabel(
"Tracker\nLabel", self, alignment=QtCore.Qt.AlignCenter
)
self.label.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents, True)
self.label.setFixedSize(200, 200)
self.label.setStyleSheet(
"background-color: salmon; border: 1px solid black; font-size: 40pt;"
)
self.resize(640, 480)
self.manager = TrackerManager(
widget1=self.button,
widget2=self.label,
alignment=QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter,
)
self.move_button()
def move_button(self):
pos = QtCore.QPoint(*random.sample(range(400), 2))
animation = QtCore.QPropertyAnimation(
targetObject=self.button,
parent=self,
propertyName=b"pos",
duration=1000,
startValue=self.button.pos(),
endValue=pos,
)
animation.finished.connect(self.move_button)
animation.start(QtCore.QAbstractAnimation.DeleteWhenStopped)
def main():
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
I am attempting to use a QStyledItemDelegate with my QListView to display rich text in the items.
The first time the item is painted its height is too small. If I then mouse over the item it gets repainted with the correct height. Below are screenshots of the initial paint and repaint.
How can I get the initial paint to be the right height?
Example code that demonstrates the issue:
from PySide2 import QtCore, QtGui, QtWidgets
class RichTextItemDelegate(QtWidgets.QStyledItemDelegate):
def __init__(self, parent=None):
super(RichTextItemDelegate, self).__init__(parent)
self.doc = QtGui.QTextDocument(self)
def paint(self, painter, option, index):
painter.save()
self.initStyleOption(option, index)
self.doc.setHtml(option.text)
option_no_text = QtWidgets.QStyleOptionViewItem(option)
option_no_text.text = ''
style = QtWidgets.QApplication.style() if option_no_text.widget is None else option_no_text.widget.style()
style.drawControl(QtWidgets.QStyle.CE_ItemViewItem, option_no_text, painter)
margin_top = (option.rect.height() - self.doc.size().height()) // 2
text_rect = style.subElementRect(QtWidgets.QStyle.SE_ItemViewItemText, option_no_text, None)
text_rect.setTop(text_rect.top() + margin_top)
painter.translate(text_rect.topLeft())
painter.setClipRect(text_rect.translated(-text_rect.topLeft()))
context = QtGui.QAbstractTextDocumentLayout.PaintContext()
self.doc.documentLayout().draw(painter, context)
painter.restore()
def sizeHint(self, option, index):
other = super().sizeHint(option, index)
w = min(self.doc.idealWidth(), other.width())
h = max(self.doc.size().height(), other.height())
return QtCore.QSize(w, h)
class ExampleWidget(QtWidgets.QWidget):
def __init__(self):
super().__init__()
item = QtGui.QStandardItem()
item.setText('Example<br><span style="font-size: 14pt; font-weight: bold;">Example<br>Example<br>Example</span>', )
model = QtGui.QStandardItemModel()
model.appendRow(item)
self.listview = QtWidgets.QListView(parent=self)
self.listview.setModel(model)
delegate = RichTextItemDelegate(self.listview)
self.listview.setItemDelegate(delegate)
app = QtWidgets.QApplication([])
example = ExampleWidget()
example.resize(320, 240)
example.show()
app.exec_()
So I believe the initial paint was sized wrong because the sizeHint() function can't return good values until it knows what the item text is. In my original code the text wasn't set until paint() was called.
I used the following for sizeHint() and it seems to work:
def sizeHint(self, option: QtWidgets.QStyleOptionViewItem, index: QtCore.QModelIndex) -> QtCore.QSize:
self.doc.setHtml(index.data())
doc_size = self.doc.size()
doc_width = int(math.ceil(doc_size.width()))
doc_height = int(math.ceil(doc_size.height()))
item_size = super().sizeHint(option, index)
w = min(doc_width, item_size.width())
h = max(doc_height, item_size.height())
return QtCore.QSize(w, h)
Be warned... I do not know what the implications of calling self.doc.setHtml() inside of sizeHint() are. Also, for some reason super().sizeHint() returns a very large width value for me, and I'm not sure why, which is why I call min() instead of max().
I want to search a QTableWidget-Table by a list of words, if they've been found i want them to bee highlighted.
I tried to modify the code from here so the table is beeing searched by a list of words, not just one. Unfortunatly my results keep getting overwritten. I always only get the result for the last word in the list.
Does anyone know how to modify the code so it will show the result of the whole list of words ?
Here is the code:
from PyQt5 import QtCore, QtGui, QtWidgets
import random
import html
words_1 = ["Hello dseerfd", "world sdfsdf sdfgsdf sdfsdf", "Stack dasdf", "Overflow", "Hello world", """<font color="red">Hello world</font>"""]
class HTMLDelegate(QtWidgets.QStyledItemDelegate):
def __init__(self, parent=None):
super(HTMLDelegate, self).__init__(parent)
self.doc = QtGui.QTextDocument(self)
def paint(self, painter, option, index):
substring = index.data(QtCore.Qt.UserRole)
painter.save()
options = QtWidgets.QStyleOptionViewItem(option)
self.initStyleOption(options, index)
res = ""
color = QtGui.QColor("red")
if substring:
substrings = options.text.split(substring)
res = """<font color="{}">{}</font>""".format(color.name(QtGui.QColor.HexRgb), substring).join(list(map(html.escape, substrings)))
else:
res = html.escape(options.text)
self.doc.setHtml(res)
options.text = ""
style = QtWidgets.QApplication.style() if options.widget is None \
else options.widget.style()
style.drawControl(QtWidgets.QStyle.CE_ItemViewItem, options, painter)
ctx = QtGui.QAbstractTextDocumentLayout.PaintContext()
if option.state & QtWidgets.QStyle.State_Selected:
ctx.palette.setColor(QtGui.QPalette.Text, option.palette.color(
QtGui.QPalette.Active, QtGui.QPalette.HighlightedText))
else:
ctx.palette.setColor(QtGui.QPalette.Text, option.palette.color(
QtGui.QPalette.Active, QtGui.QPalette.Text))
textRect = style.subElementRect(
QtWidgets.QStyle.SE_ItemViewItemText, options)
if index.column() != 0:
textRect.adjust(5, 0, 0, 0)
the_constant = 4
margin = (option.rect.height() - options.fontMetrics.height()) // 2
margin = margin - the_constant
textRect.setTop(textRect.top() + margin)
painter.translate(textRect.topLeft())
painter.setClipRect(textRect.translated(-textRect.topLeft()))
self.doc.documentLayout().draw(painter, ctx)
painter.restore()
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
hlay = QtWidgets.QHBoxLayout()
lay = QtWidgets.QVBoxLayout(self)
self.table = QtWidgets.QTableWidget(5, 5)
lay.addLayout(hlay)
lay.addWidget(self.table)
self.table.setItemDelegate(HTMLDelegate(self.table))
for i in range(self.table.rowCount()):
for j in range(self.table.columnCount()):
it = QtWidgets.QTableWidgetItem(random.choice(words_1))
self.table.setItem(i, j, it)
text_list = ['ello', 'ack']
# clear
allitems = self.table.findItems("", QtCore.Qt.MatchContains)
selected_items =[]
for words in text_list:
for item in allitems:
selected_items = self.table.findItems(words, QtCore.Qt.MatchContains)
selected_items.append(self.table.findItems(words, QtCore.Qt.MatchContains)) ## i tried to make a list which is beeing appened but using this list it returns only the same as the input
item.setData(QtCore.Qt.UserRole, words if item in selected_items else None)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())
In the previous case I wanted to filter the cases so as not to have to paint unnecessarily but in this case because it was more complex I decided to implement the highlight logic using QTextCharFormat and not HTML as I show below:
from PyQt5 import QtCore, QtGui, QtWidgets
import random
words = ["Hello dseerfd", "world sdfsdf sdfgsdf sdfsdf", "Stack dasdf", "Overflow", "Hello world", """<font color="red">Hello world</font>"""]
class HighlightDelegate(QtWidgets.QStyledItemDelegate):
def __init__(self, parent=None):
super(HighlightDelegate, self).__init__(parent)
self.doc = QtGui.QTextDocument(self)
self._filters = []
def paint(self, painter, option, index):
painter.save()
options = QtWidgets.QStyleOptionViewItem(option)
self.initStyleOption(options, index)
self.doc.setPlainText(options.text)
self.apply_highlight()
options.text = ""
style = QtWidgets.QApplication.style() if options.widget is None \
else options.widget.style()
style.drawControl(QtWidgets.QStyle.CE_ItemViewItem, options, painter)
ctx = QtGui.QAbstractTextDocumentLayout.PaintContext()
if option.state & QtWidgets.QStyle.State_Selected:
ctx.palette.setColor(QtGui.QPalette.Text, option.palette.color(
QtGui.QPalette.Active, QtGui.QPalette.HighlightedText))
else:
ctx.palette.setColor(QtGui.QPalette.Text, option.palette.color(
QtGui.QPalette.Active, QtGui.QPalette.Text))
textRect = style.subElementRect(
QtWidgets.QStyle.SE_ItemViewItemText, options)
if index.column() != 0:
textRect.adjust(5, 0, 0, 0)
the_constant = 4
margin = (option.rect.height() - options.fontMetrics.height()) // 2
margin = margin - the_constant
textRect.setTop(textRect.top() + margin)
painter.translate(textRect.topLeft())
painter.setClipRect(textRect.translated(-textRect.topLeft()))
self.doc.documentLayout().draw(painter, ctx)
painter.restore()
def apply_highlight(self):
cursor = QtGui.QTextCursor(self.doc)
cursor.beginEditBlock()
fmt = QtGui.QTextCharFormat()
fmt.setForeground(QtCore.Qt.red)
for f in self.filters():
highlightCursor = QtGui.QTextCursor(self.doc)
while not highlightCursor.isNull() and not highlightCursor.atEnd():
highlightCursor = self.doc.find(f, highlightCursor)
if not highlightCursor.isNull():
highlightCursor.mergeCharFormat(fmt)
cursor.endEditBlock()
#QtCore.pyqtSlot(list)
def setFilters(self, filters):
if self._filters == filters: return
self._filters = filters
def filters(self):
return self._filters
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
self.table = QtWidgets.QTableWidget(30, 6)
self._delegate = HighlightDelegate(self.table)
self.table.setItemDelegate(self._delegate)
for i in range(self.table.rowCount()):
for j in range(self.table.columnCount()):
it = QtWidgets.QTableWidgetItem(random.choice(words))
self.table.setItem(i, j, it)
self.table.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents)
le = QtWidgets.QLineEdit()
le.textChanged.connect(self.on_textChanged)
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(le)
lay.addWidget(self.table)
le.setText("ello ack")
#QtCore.pyqtSlot(str)
def on_textChanged(self, text):
self._delegate.setFilters(list(set(text.split())))
self.table.viewport().update()
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.showMaximized()
sys.exit(app.exec_())
I would like to get values from Qdialog window into Qmainwindow after closing Qdailog or Qwidget window. Actually I do not know how to do this.
The idea is when user selects a root value from QtableWidget, as shown below in the figure, Data display on the QWidget and I want to transform or pass these values into my Qmainwindow, and my second window in this case is Circular.py would disappear, but my values should be available in the Qmainwindow.
Visualisation of windows.
The Code, "main.py"
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from Circular import *
class Foo(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(Foo, self).__init__(parent)
self.setGeometry(QtCore.QRect(200, 100, 600, 360))
self.boo = Boo()
self.setCentralWidget(self.boo)
class Boo(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Boo, self).__init__(parent)
Openbutton = QtWidgets.QPushButton('Getting values')
Alay = QtWidgets.QVBoxLayout(self)
Alay.addWidget(Openbutton)
Openbutton.clicked.connect(self.buttonfunc)
def buttonfunc(self):
app.setStyleSheet(QSS)
subwindow=CircularDialog()
subwindow.setWindowModality(QtCore.Qt.ApplicationModal)
subwindow.show()
subwindow.exec_()
print('Test')
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
w = Foo()
w.show()
sys.exit(app.exec_())
Second window code "Circular.py"
Please note that this code is previuosly posted here.
import sys
import os
from PyQt5 import QtCore, QtGui, QtWidgets
iconroot = os.path.dirname(__file__)
ORGANIZATION_NAME = 'Circular App'
ORGANIZATION_DOMAIN = 'Circular shape'
APPLICATION_NAME = 'Circulargeometry program'
SETTINGS_TRAY = 'settings/tray'
QSS = """
QTreeWidget{
border:none;
}
QTreeView::branch:has-siblings:!adjoins-item {
border-image: url(images/vline.png) 0;
}
QTreeView::branch:has-siblings:adjoins-item {
border-image: url(images/branch-more.png) 0;
}
QTreeView::branch:!has-children:!has-siblings:adjoins-item {
border-image: url(images/branch-end.png) 0;
}
QTreeView::branch:has-children:!has-siblings:closed,
QTreeView::branch:closed:has-children:has-siblings {
border-image: none;
image: url(images/branch-closed.png);
}
QTreeView::branch:open:has-children:!has-siblings,
QTreeView::branch:open:has-children:has-siblings {
border-image: none;
image: url(images/branch-open.png);
}
"""
class TreeWidget(QtWidgets.QTreeWidget):
currentTextChanged = QtCore.pyqtSignal(str)
def __init__(self, parent=None):
super(TreeWidget, self).__init__(parent)
self.currentItemChanged.connect(self.onCurrentItemChanged)
self.setHeaderLabel('Standard Section Library')
self.setRootIsDecorated(True)
self.setAlternatingRowColors(True)
self.readSettings()
self.expandAll()
def onCurrentItemChanged(self, current, previous):
if current not in [self.topLevelItem(ix) for ix in range(self.topLevelItemCount())]:
self.currentTextChanged.emit(current.text(0))
def readSettings(self):
settings = QtCore.QSettings()
settings.beginGroup("TreeWidget")
values = settings.value("items")
if values is None:
self.loadDefault()
else:
TreeWidget.dataToChild(values, self.invisibleRootItem())
self.customized_item = None
for ix in range(self.topLevelItemCount()):
tlevel_item = self.topLevelItem(ix)
if tlevel_item.text(0) == "Customized":
self.customized_item = tlevel_item
settings.endGroup()
def writeSettings(self):
settings = QtCore.QSettings()
settings.beginGroup("TreeWidget")
settings.setValue("items", TreeWidget.dataFromChild(self.invisibleRootItem()))
settings.endGroup()
def loadDefault(self):
standardsectionlist = ["D100","D150","D200","D250","D300","D350","D400","D450","D500",
"D550","D600","D650","D700","D750","D800","D850","D900","D950","D1000"]
rootItem = QtWidgets.QTreeWidgetItem(self, ['Circular shapes'])
rootItem.setIcon(0, QtGui.QIcon(os.path.join(iconroot,"images/circularcolumnnorebar.png")))
for element in standardsectionlist:
rootItem.addChild(QtWidgets.QTreeWidgetItem([element]))
self.customized_item = QtWidgets.QTreeWidgetItem(self, ["Customized"])
self.customized_item.setIcon(0, QtGui.QIcon(os.path.join(iconroot,"images/circularcolumnnorebar.png")))
#staticmethod
def dataToChild(info, item):
TreeWidget.tupleToItem(info["data"], item)
for val in info["childrens"]:
child = QtWidgets.QTreeWidgetItem()
item.addChild(child)
TreeWidget.dataToChild(val, child)
#staticmethod
def tupleToItem(t, item):
# set values to item
ba, isSelected = t
ds = QtCore.QDataStream(ba)
ds >> item
item.setSelected(isSelected)
#staticmethod
def dataFromChild(item):
l = []
for i in range(item.childCount()):
child = item.child(i)
l.append(TreeWidget.dataFromChild(child))
return {"childrens": l, "data": TreeWidget.itemToTuple(item)}
#staticmethod
def itemToTuple(item):
# return values from item
ba = QtCore.QByteArray()
ds = QtCore.QDataStream(ba, QtCore.QIODevice.WriteOnly)
ds << item
return ba, item.isSelected()
class InfoWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(InfoWidget, self).__init__(parent)
hlay = QtWidgets.QHBoxLayout(self)
plabel = QtWidgets.QLabel()
pixmap = QtGui.QPixmap(os.path.join(iconroot, "images/circularcolumnnorebard.png"))\
.scaled(230, 230, QtCore.Qt.KeepAspectRatio)
plabel.setPixmap(pixmap)
hlay.addWidget(plabel)
self.ilabel = QtWidgets.QLabel()
hlay.addWidget(self.ilabel)
hlay.addStretch()
self.readSettings()
#QtCore.pyqtSlot(str)
def setData(self, text):
try:
circular_section = int(text.translate({ord('D'): ""}))
area = (3.1416/4)*(circular_section**2)
inertia = (3.1416/64)*circular_section**4
fmt = "D = {}mm\nA = {:0.2E}mm2\n I = {:0.2E}mm4"
self.ilabel.setText(fmt.format(circular_section, area, inertia))
except ValueError:
pass
return print(circular_section)
def readSettings(self):
settings = QtCore.QSettings()
settings.beginGroup("InfoWidget")
self.ilabel.setText(settings.value("text", ""))
settings.endGroup()
def writeSettings(self):
settings = QtCore.QSettings()
settings.beginGroup("InfoWidget")
settings.setValue("text", self.ilabel.text())
settings.endGroup()
class CircularDialog(QtWidgets.QDialog):
def __init__(self, parent=None):
super(CircularDialog, self).__init__(parent)
self.setWindowTitle("Frequently used shape")
self.setWindowIcon(QtGui.QIcon(os.path.join(iconroot+"/images/circularcolumnnorebar.png")))
grid = QtWidgets.QGridLayout(self)
self.tree = TreeWidget()
self.infoWidget = InfoWidget()
section_lay = QtWidgets.QHBoxLayout()
section_label = QtWidgets.QLabel("Section name: ")
self.section_edit = QtWidgets.QLineEdit('Define en name to section')
section_lay.addWidget(section_label)
section_lay.addWidget(self.section_edit)
self.tree.currentTextChanged.connect(self.infoWidget.setData)
button_layout = QtWidgets.QVBoxLayout()
add_button = QtWidgets.QPushButton("Add")
add_button.clicked.connect(self.addItem)
delete_button = QtWidgets.QPushButton("Delete")
delete_button.clicked.connect(self.removeItem)
button_layout.addWidget(add_button, alignment=QtCore.Qt.AlignBottom)
button_layout.addWidget(delete_button, alignment=QtCore.Qt.AlignTop)
buttonBox = QtWidgets.QDialogButtonBox()
buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
buttonBox.accepted.connect(self.accept)
buttonBox.rejected.connect(self.reject)
self.accepted.connect(self.save_all_data)
self.rejected.connect(self.save_all_data)
grid.addLayout(section_lay, 0, 0)
grid.addWidget(self.tree, 1, 0)
grid.addLayout(button_layout, 1, 1)
grid.addWidget(self.infoWidget, 2, 0, 1, 2)
grid.addWidget(buttonBox, 3, 0, 1, 2)
self.readSettings()
def readSettings(self):
settings = QtCore.QSettings()
settings.beginGroup("CircularDialog")
self.setGeometry(settings.value("geometry", QtCore.QRect(300, 300, 400, 600)))
self.section_edit.setText(settings.value("SectionInfo", "Define en name to section"))
settings.endGroup()
def writeSettings(self):
settings = QtCore.QSettings()
settings.beginGroup("CircularDialog")
settings.setValue("geometry", self.geometry())
settings.setValue("SectionInfo",self.section_edit.text())
settings.endGroup()
def closeEvent(self, event):
self.save_all_data()
super(CircularDialog, self).closeEvent(event)
def save_all_data(self):
for children in self.findChildren(QtWidgets.QWidget) + [self]:
if hasattr(children, "writeSettings"):
children.writeSettings()
def addItem(self):
text, ok = QtWidgets.QInputDialog.getText(self, "Add custom section",
"Enter section geometry f.ex as D325 or just 325 in mm: ")
if ok:
it = QtWidgets.QTreeWidgetItem([text])
self.tree.customized_item.addChild(it)
def removeItem(self):
it = self.tree.customized_item.takeChild(0)
del it
if __name__ == '__main__':
QtCore.QCoreApplication.setApplicationName(ORGANIZATION_NAME)
QtCore.QCoreApplication.setOrganizationDomain(ORGANIZATION_DOMAIN)
QtCore.QCoreApplication.setApplicationName(APPLICATION_NAME)
app = QtWidgets.QApplication(sys.argv)
app.setStyleSheet(QSS)
w = CircularDialog()
w.show()
sys.exit(app.exec_())
The first thing to do is verify that if you accept or not using exec_ () that returns a code:QDialog::Accepted, if you want to get the text you must use the relationship tree:
def buttonfunc(self):
app.setStyleSheet(QSS)
subwindow=CircularDialog()
subwindow.setWindowModality(QtCore.Qt.ApplicationModal)
if subwindow.exec_() == QtWidgets.QDialog.Accepted:
print('Test', subwindow.infoWidget.ilabel.text())
How can I make my QValidator in pyside only restrict the QLineEdit to only accept text inputs from a predefined list of strings? I've managed to get the Autocomplete to show up ignoring case sensitivity which i want.
import os
import sys
from PySide import QtCore, QtGui
available_tags = ['Animals','Brick','Buildings','Concrete','Decals','Doors','Fabric','Floors','FX','Ground','Grunge','Landscapes','Manmade','Marble','Metal','Nature','Ornaments','Paper','Plaster','Plastic','Roads','Rock','Roofing','Rust','Signs','Soil','Tiles','Various','Windows','Wood',]
class ExampleWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super(ExampleWindow, self).__init__(parent)
self.resize(300, 200)
self.available_tags_model = QtGui.QStringListModel(available_tags)
self.ui_tag_completer = QtGui.QCompleter()
self.ui_tag_completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive)
self.ui_tag_completer.setModel(self.available_tags_model)
self.ui_input = QtGui.QLineEdit()
self.ui_input.setCompleter(self.ui_tag_completer)
self.ui_input.setPlaceholderText('enter description...')
self.ui_input.setValidator(QtGui.QRegExpValidator(QtCore.QRegExp("[A-Za-z0-9_]{0,255}")))
self.tags_model = QtGui.QStringListModel()
self.ui_tags_list = QtGui.QListView()
self.ui_tags_list.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
self.ui_tags_list.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
self.ui_tags_list.setModel(self.tags_model)
self.ui_tags_list.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.ui_tags_list.customContextMenuRequested.connect(self.open_tags_list_menu)
# layout
self.ui_tags_list.setViewMode(QtGui.QListView.IconMode)
self.ui_tags_list.setResizeMode(QtGui.QListView.Adjust)
self.ui_tags_list.setMovement(QtGui.QListView.Static)
self.ui_tags_list.setSpacing(5)
self.ui_tags_list.setStyleSheet('''
QListView::item {
border: 1px solid rgba(0,0,0,60);
background: rgba(0,0,0,30);
border-radius: 2px;
}
QListView::item:selected {
border: 1px solid rgba(70,150,255,255);
background: rgba(70,150,255,200);
border-radius: 2px;
}
''')
# main layout
main_layout = QtGui.QVBoxLayout()
main_layout.setContentsMargins(5,5,5,5)
main_layout.setSpacing(5)
main_layout.addWidget(self.ui_input)
main_layout.addWidget(self.ui_tags_list)
main_widget = QtGui.QWidget()
main_widget.setLayout(main_layout)
self.setCentralWidget(main_widget)
# connections
self.ui_input.returnPressed.connect(self.input_entered)
self.ui_tags_list.doubleClicked.connect(self.double_clicked_item)
self.ui_tag_completer.activated.connect(self.tag_entered)
# menus
self.create_actions()
def create_actions(self):
self.act_delete_tags = QtGui.QAction('Delete', self)
self.act_delete_tags.triggered.connect(self.remove_items)
self.menu_tags_list = QtGui.QMenu()
self.menu_tags_list.addAction(self.act_delete_tags)
def append_tag(self, val):
if not val:
return False
if val.lower() in [x.lower() for x in self.tags_model.stringList()]:
return False
self.tags_model.insertRow(self.tags_model.rowCount())
index = self.tags_model.index(self.tags_model.rowCount()-1)
self.tags_model.setData(index, val)
def remove_items(self):
self.ui_tags_list.setUpdatesEnabled(False)
indexes = self.ui_tags_list.selectionModel().selectedIndexes()
while (len(indexes)):
self.tags_model.removeRow(indexes[0].row())
indexes = self.ui_tags_list.selectionModel().selectedIndexes()
self.ui_tags_list.setUpdatesEnabled(True)
def input_entered(self):
text = self.ui_input.text()
self.append_tag(text)
self.ui_input.clear()
def tag_entered(self, text):
''
def double_clicked_item(self, item):
self.remove_items()
def open_tags_list_menu(self, position):
self.menu_tags_list.exec_(self.ui_tags_list.viewport().mapToGlobal(position))
def main():
app = QtGui.QApplication(sys.argv)
ex = ExampleWindow()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
We create a validator for the tags, the advantage of a validator is that it will restrict you to write text, in the case of the solution of #AakashVerma it is partial since it allows you to write text that is not in the tags, and only checks it when the enter has been pressed.:
class TagsValidator(QtGui.QValidator):
def __init__(self, tags, *args, **kwargs):
QtGui.QValidator.__init__(self, *args, **kwargs)
self._tags = [tag.lower() for tag in tags]
def validate(self, inputText, pos):
if inputText.lower() in self._tags:
return QtGui.QValidator.Acceptable
len_ = len(inputText)
for tag in self._tags:
if tag[:len_] == inputText.lower():
return QtGui.QValidator.Intermediate
return QtGui.QValidator.Invalid
class ExampleWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
[...]
self.ui_input = QtGui.QLineEdit()
self.ui_input.setCompleter(self.ui_tag_completer)
self.ui_input.setPlaceholderText('enter description...')
self.ui_input.setValidator(TagsValidator(available_tags))
[...]
def input_entered(self):
validator = self.ui_input.validator()
text = self.ui_input.text()
state = validator.validate(text, 0)
if state == QtGui.QValidator.Acceptable:
self.append_tag(text)