Hover effect for QTextDocument - python

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

Related

update QStyle in EnterEvent

i wanna update the QStyle of the LineEdit while the mouse is in\out the widget (enterEvent\leaveEvent ) i tried to add a bool variable to the drawPrimitive function but i get this error
TypeError: drawPrimitive(self, QStyle.PrimitiveElement, QStyleOption, QPainter, widget: QWidget = None): 'a' is not a valid keyword argument
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PushButton_2 import Push_Button_
import sys
class LineEditStyle(QProxyStyle):
def drawPrimitive(self, element, option, painter, widget,a=None):
if a :
self.pen = QPen(QColor('green'))
else :
self.pen = QPen(QColor('red'))
self.pen.setWidth(4)
if element == QStyle.PE_FrameLineEdit:
painter.setRenderHint(QPainter.Antialiasing)
painter.setPen(self.pen)
painter.drawRoundedRect(QRect(0,0,400,40), 10, 10)
else:
super().drawPrimitive(element, option, painter, widget)
class LineEdit(QLineEdit):
def __init__(self,*args,**kwargs):
QLineEdit.__init__(self,*args,**kwargs)
self.a = 0
def enterEvent(self, a0):
self.a = 1
def leaveEvent(self, a0):
self.a = 0
def paintEvent(self,event):
option = QStyleOption()
option.initFrom(self)
self.style().drawPrimitive(QStyle.PE_FrameLineEdit,option,a=self.a)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = QMainWindow()
window.setGeometry(500,500,400,400)
window.setStyleSheet('background-color:#373737')
line = LineEdit(parent=window)
line.setGeometry(20,200,400,40)
style = LineEditStyle()
line.setStyle(style)
window.show()
sys.exit(app.exec())
You mustn't use the QStyleSheet with the QStyle because it makes a confusion and you have to set the default parameter Widget as None
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PushButton_2 import Push_Button_
import sys
class LineEditStyle(QProxyStyle):
def drawPrimitive(self, element, option, painter, widget=None,a=None):
if a :
self.pen = QPen(QColor('green'))
else :
self.pen = QPen(QColor('red'))
self.pen.setWidth(4)
if element == QStyle.PE_FrameLineEdit:
painter.setRenderHint(QPainter.Antialiasing)
painter.setPen(self.pen)
painter.drawRoundedRect(QRect(0,0,400,40), 10, 10)
else:
super().drawPrimitive(element, option, painter, widget)
def subElementRect(self, element, option, widget):
if element == QStyle.SE_LineEditContents :
return QRect(0,0,50,30)
else :
return super().subElementRect(element, option, widget)
def drawItemText(self, painter, rect, flags, pal, enabled, text, textRole):
rect_ = QRect(20,20,50,50)
text = text.upper()
painter.drawText(text,rect_,Qt.AlignCenter)
class LineEdit(QLineEdit):
def __init__(self,*args,**kwargs):
QLineEdit.__init__(self,*args,**kwargs)
self.a = 0
def enterEvent(self, a0):
self.a = 1
def leaveEvent(self, a0):
self.a = 0
def paintEvent(self,event):
option = QStyleOption()
option.initFrom(self)
painter = QPainter(self)
self.style().drawPrimitive(QStyle.PE_FrameLineEdit,option,painter,a=self.a)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = QMainWindow()
window.setGeometry(500,500,400,400)
#window.setStyleSheet('background-color:#373737')
line = LineEdit(parent=window)
line.setGeometry(20,200,400,40)
style = LineEditStyle()
line.setStyle(style)
window.show()
sys.exit(app.exec())
You might find QStyleSheets useful for something like this.
I mocked this up in Qt Designer by entering the following in styleSheet property (in code you would do mywidget.setStyleSheet('<parameters>') ):
QLineEdit {
border: 3px solid red;
}
QLineEdit:focus {
border: 3px solid green;
}
Edit: styleSheet string above works on focus, but the original question was about enterEvent/leaveEvent, which trigger on mouse hover. As #musicamente rightfully points out, to work on mouse hover the :hover pseudo state can be used instead of :focus:
QLineEdit {
border: 3px solid red;
}
QLineEdit:hover {
border: 3px solid green;
}
I made a QMainWindow with 2 QLineEdits: 1 has styleSheet set and the other is default. When the focus moves to the regular QLineEdit, the modified QLineEdit turns red.
You can make ALL of the QLineEdits behave the same by editing the styleSheet of their parent. In your case, you could use window.setStyleSheet('..... Here's another mockup in Qt Designer:

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

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

QValidator only accepts strings from list

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)

Python + Qt: pyqtProperty, stylesheets and event handlers

Recently, I have been struggling with understanding of pyqtProperty usage. In following snippet I try to create a custom button which would change it's text and appearance after clicking on it. I wanted to separate logic and appearance, so I've created custom property 'state', which is used in the stylesheet.
The result looks kind of strange to me. Program throws 'CustomButton' object has no attribute '_state' exception, but runs anyway. Button's text is changing after clicking on it, but color stays the same.
#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys
from PyQt4 import QtGui, QtCore
STYLE = '''
CustomButton {
margin: 50px;
padding: 10px;
}
CustomButton[state='true'] {
background: yellowgreen;
}
CustomButton[state='false'] {
background: orangered;
}
'''
class CustomButton(QtGui.QPushButton):
def __init__(self):
super(CustomButton, self).__init__()
self.setText('ON')
self.setStyleSheet(STYLE)
self._state = True
def g_state(self):
return self._state
def s_state(self, value):
self._state = value
state = QtCore.pyqtProperty(bool, fget=g_state, fset=s_state)
def mousePressEvent(self, event):
print 'clicked'
if self.state == True:
self.state = False
self.setText('OFF')
else:
self.state = True
self.setText('ON')
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
b = CustomButton()
b.show()
sys.exit(app.exec_())
There are a couple of issues here.
Firstly, the re-implemented mousePressEvent should call the base-class implementation in order to retain the normal behaviour.
Secondly, changes to properties do not automatically trigger style changes, so you need to explicitly do that in your code.
Also, its worth noting that pyqtProperty can be used as a decorator, which might improve the readability of the code a little.
So, with these changes, the code might look something like this:
...
#QtCore.pyqtProperty(bool)
def state(self):
return self._state
#state.setter
def state(self, value):
self._state = value
def mousePressEvent(self, event):
print 'clicked'
if self.state:
self.state = False
self.setText('OFF')
else:
self.state = True
self.setText('ON')
self.style().unpolish(self)
self.style().polish(self)
self.update()
super(CustomButton, self).mousePressEvent(event)

Categories