QValidator only accepts strings from list - python

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)

Related

Hover effect for QTextDocument

I wanted to have some hover effect over p tag inside QTextDocument.
I have tried to set QSS for QTextDocument using QTextDocument().setDefaultStyleSheet().
Here's what the result I have obtained;
Script;
from PySide2 import QtWidgets
class Test(QtWidgets.QTextBrowser):
def __init__(self):
super().__init__()
self.document().setDefaultStyleSheet(
"""
p.out{
background-color: "orange";
color: "black";
}
p.out:hover{
background-color: "yellow";
}
"""
)
self.setStyleSheet(
"""
QTextBrowser{
background-color: "black";
color: "white";
}
""")
self.setHtml(
"""
<p class='out'> Checking this </p>
"""
)
test = QtWidgets.QApplication([])
sample = Test()
sample.show()
test.exec_()
Color attribute inside qss worked but the hover doesn't work.
Is there any way of achieving hover effect over fragments of text inside document?
After Understanding, QTextDocument's structure and Syntax Highlighter. We can modify QTextBlock's Format whenever we want. I tried to change it's format using enterEvent, and restore format in leaveEvent.
we can modify box model using QTextFrame using QTextFrameFormat.
from PySide2 import QtWidgets, QtGui
class High(QtGui.QSyntaxHighlighter):
def __init__(self, doc):
super().__init__(doc)
self._formats = tuple(
QtGui.QTextCharFormat() for _ in range(2)
)
self._formats[1].setForeground(
QtGui.QBrush(QtGui.QColor("orange"))
)
self._formats[1].setFontWeight(3)
self._formats[1].setFontUnderline(True)
self._formats[1].setFontPointSize(12)
def highlightBlock(self, text: str):
self.setFormat(
0, len(text), self._formats[self.currentBlockState() if self.currentBlockState() > -1 else 0]
)
class Sample(QtWidgets.QTextBrowser):
def __init__(self):
super().__init__()
self.lighter = High(self.document())
self._prev = None
self.insertBlock("This is a sample line\n")
self.insertBlock("Can also be a\nparagraph thing\n")
self.insertBlock("it's a block, Bye!")
def insertBlock(self, text):
cur = self.textCursor()
cur.block().setUserState(0) # default: -1
cur.insertText(text)
def mouseMoveEvent(self, eve):
super().mouseMoveEvent(eve)
cur = self.cursorForPosition(eve.pos())
if cur.block() == self._prev:
return
self.restore()
cur.block().setUserState(1)
self.lighter.rehighlightBlock(cur.block())
self._prev = cur.block()
def restore(self):
if not self._prev:
return
self._prev.setUserState(0)
self.lighter.rehighlightBlock(self._prev)
self._prev = None
def leaveEvent(self, eve):
self.restore()
super().leaveEvent(eve)
testing = QtWidgets.QApplication([])
sample = Sample()
sample.show()
testing.exec_()

Run Method on Widget Move

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()

Add Button to Right of ListView Item

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

PyQt5 Getting values from Qdialog window to mainwindow after close

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())

I want to create a color animation for a button with pyqt5

I created a virtual button keyboard using pyqt5 QpushButton and made the ok button active when the length of the QLineEdit character is satisfied. I want to create a color animated effect (beautifully drawn) when the button is activated. Using QPropertyAnimation I do not know if you think I made QT manual.
import sys
from functools import partial
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.center_widget = QStackedWidget()
self.setCentralWidget(self.center_widget)
self.SearchUI()
def SearchUI(self):
widget = QWidget()
mainlayout = QVBoxLayout(widget)
button_gruop = QGroupBox()
gridlayout = QGridLayout()
count = 0
self.numberSave = ''
self.buttons = []
self.search = QLineEdit('')
self.search.setAlignment(Qt.AlignCenter)
self.search.setStyleSheet('font: bold 50pt')
self.search.setMaxLength(13)
self.search.setEchoMode(QLineEdit.Password)
virtualkeypad = [
'7','8','9',
'4','5','6',
'1','2','3',
'BACK','0','OK'
]
positions = [(i, j) for i in range(4) for j in range(3)]
for position, name in zip(positions, virtualkeypad):
self.buttons.append(QPushButton(name))
gridlayout.addWidget(self.buttons[count], *position)
self.buttons[count].setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding)
count += 1
for i in [0,1,2,3,4,5,6,7,8,10]:
self.buttons[i].clicked.connect(partial(self.ButtonNumberSelect, '{}'.format(virtualkeypad[i])))
self.buttons[i].setStyleSheet('background-color: orange; font: bold 50pt;')
self.buttons[9].clicked.connect(self.ButtonBackSpace)
self.buttons[11].clicked.connect(self.ButtonEnter)
self.buttons[9].setStyleSheet('background-color: orange; font: 50pt;')
self.buttons[11].setStyleSheet('background-color: none; font: 50pt;')
self.buttons[11].setEnabled(False)
button_gruop.setLayout(gridlayout)
mainlayout.addWidget(self.search)
mainlayout.addWidget(button_gruop)
mainlayout.setContentsMargins(150,150,150,150)
mainlayout.contentsMargins()
self.center_widget.addWidget(widget)
def ButtonNumberSelect(self, button):
self.numberSave += button
self.search.setText(self.numberSave)
if len(self.numberSave) == 13:
a = QGraphicsColorizeEffect(self.buttons[11])
self.buttons[11].setGraphicsEffect(a)
animation_button = QPropertyAnimation(a)
animation_button.setStartValue(QColor(Qt.cyan))
animation_button.setKeyValueAt(0.10(Qt.darkCyan))
animation_button.setKeyValueAt(0.10(Qt.darkBlue))
animation_button.setKeyValueAt(0.10(Qt.darkGray))
animation_button.setKeyValueAt(0.10(Qt.darkGreen))
animation_button.setKeyValueAt(0.10(Qt.darkMagenta))
animation_button.setKeyValueAt(0.10(Qt.darkYellow))
animation_button.setEndValue(QColor(255,255,255))
animation_button.setDuration(5000)
animation_button.setLoopCount(5)
animation_button.start()
self.buttons[11].setEnabled(True)
self.buttons[11].setStyleSheet('background-color: orange; font: 50pt;')
self.numberSave = ''
else:
self.buttons[11].setEnabled(False)
self.buttons[11].setStyleSheet('background-color: white; font: 50pt;')
def ButtonBackSpace(self):
self.numberSave = self.numberSave[:-1]
self.search.setText(self.numberSave)
def ButtonEnter(self):
self.center_widget.setCurrentIndex(0)
if __name__ == "__main__":
app = QApplication(sys.argv)
fream = MainWindow()
fream.show()
app.exec_()
=add edit=
I get an error when I run my code and generate 13 numbers with the number buttons.
Problem Event Name: APPCRASH
Application name: python.exe
Application version: 3.6.6150.1013
Application timestamp: 5b32fb86
Error Module Name: ucrtbase.DLL
Error Module Version: 10.0.10586.1171
Error module timestamp: 59ae5046
Exception code: 40000015
Exception offset: 000846fa
OS version: 6.1.7601.2.1.0.256.48
Locale ID: 1042
MORE INFORMATION 1: 5951
Additional information 2: 59510a33f844cfe7fca7e6582e6da18f
MORE INFORMATION 3: 99f9
MORE INFORMATION 4: 99f926c0d0d283713191de33752f806a
def ButtonNumberSelect (self, button): The part seems to have a problem.
a = QGraphicsColorizeEffect (self.buttons [11])
self.buttons [11] .setGraphicsEffect (a)
animation_button = QPropertyAnimation (a)
animation_button.setStartValue (QColor (Qt.darkBlue))
animation_button.setEndValue (QColor (255,255,255))
animation_button.setDuration (5000)
animation_button.setLoopCount (5)
animation_button.start ()
As I point out in my comments, I still do not know what you tried to write:
animation_button.setKeyValueAt(0.10(Qt.XXX))
Something more suitable would be:
animation_button.setKeyValueAt(0.10, Qt.XXX)
but to put all to the 0.1 percentage does not make sense.
On the other hand QPropertyAnimation does not tell you what property to modify, assuming you want to change the color should be something like:
animation_button = QPropertyAnimation(a, b"color")
By rearranging and cleaning up your code, you get the following:
from PyQt5 import QtCore, QtGui, QtWidgets
from functools import partial
class BeautifulButton(QtWidgets.QPushButton):
def __init__(self, *args, **kwargs):
super(BeautifulButton, self).__init__(*args, **kwargs)
effect = QtWidgets.QGraphicsColorizeEffect(self)
self.setGraphicsEffect(effect)
self.animation = QtCore.QPropertyAnimation(effect, b"color")
self.animation.setStartValue(QtGui.QColor(QtCore.Qt.cyan))
self.animation.setEndValue(QtGui.QColor(255,255,255))
self.animation.setLoopCount(5)
self.animation.setDuration(5000)
class Page(QtWidgets.QWidget):
okClicked = QtCore.pyqtSignal()
def __init__(self, parent=None):
super(Page, self).__init__(parent)
mainlayout = QtWidgets.QVBoxLayout(self)
self.keypad = QtWidgets.QGroupBox()
self.search = QtWidgets.QLineEdit()
self.search.setProperty("last_text", "")
self.search.setAlignment(QtCore.Qt.AlignCenter)
self.search.setStyleSheet('font: bold 50pt')
self.search.setMaxLength(13)
self.search.setEchoMode(QtWidgets.QLineEdit.Password)
mainlayout.addWidget(self.search)
mainlayout.addWidget(self.keypad)
mainlayout.setContentsMargins(150,150,150,150)
lay = QtWidgets.QGridLayout(self.keypad)
virtualkeypad = [
'7','8','9',
'4','5','6',
'1','2','3',
'BACK','0','OK'
]
positions = [(i, j) for i in range(4) for j in range(3)]
self.buttons = {}
for position, name in zip(positions, virtualkeypad):
if name == "OK":
btn = BeautifulButton(name)
btn.setStyleSheet('background-color: none; font: 50pt;')
btn.setDisabled(True)
else:
btn = QtWidgets.QPushButton(name)
btn.setStyleSheet('background-color: orange; font: bold 50pt;')
self.buttons[name] = btn
btn.clicked.connect(partial(self.on_clicked, name))
btn.setSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Expanding)
lay.addWidget(btn, *position)
def on_clicked(self, text):
if text in map(str, range(0, 10)):
if len(self.search.text()) == 13:
self.search.clear()
self.search.insert(text)
btn = self.buttons["OK"]
if len(self.search.text()) == 13:
btn.setEnabled(True)
btn.setStyleSheet('background-color: orange; font: bold 50pt;')
btn.animation.start()
else:
btn.setEnabled(False)
btn.setStyleSheet('background-color: white; font: 50pt;')
btn.animation.stop()
elif text == "BACK":
self.search.backspace()
elif text == "OK":
self.okClicked.emit()
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.center_widget = QtWidgets.QStackedWidget()
self.setCentralWidget(self.center_widget)
self.SearchUI()
def SearchUI(self):
page = Page()
page.okClicked.connect(partial(self.center_widget.setCurrentIndex, 0))
self.center_widget.addWidget(page)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())

Categories