I've been searching all morning for what should be the simplest thing ever but I'm either not looking in the right places or not understanding the answers I'm reading as everything I've tried has failed so far.
I'm creating a dialog with a label, a text input box and a Save button. The user enters text in the box, clicks on save and the text is saved to a variable and the dialogue should close. I can't get the last bit to happen. The closest I've come is destroying the button and the grey background but leaving the panel itself intact. I'm looking for some sort of KillParent solution... My main issue is that when I click the button I need two separate things to happen (save and exit) so when I call the on click function once I have done the variable saving I no longer have control to kill the main window. I know it's really elementary stuff but I just can't figure it out.
class ShowConnString(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.saveButton =wx.Button(self, label="Save", pos=(360, 50))
self.lblname = wx.StaticText(self, label="ConnectionString:", pos=(20,20))
self.editname = wx.TextCtrl(self, value="server='(local)', database='Audit', uid='sa', pwd='_PWD4sa_'", pos=(125, 18), size=(600,-1))
self.saveButton.Bind(wx.EVT_BUTTON, self.SaveConnString)
def SaveConnString(self, event):
self.editname.SelectAll()
self.connstringtext = self.editname.GetStringSelection()
print (self.connstringtext)
Its a bit messy but I don't have time to clean it up at the moment, it should use a sizer in the showconnstring class.
import wx
class MyFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, -1, "Dialog Test",size=(500,400))
self.panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL)
self.log = wx.TextCtrl(self.panel, wx.ID_ANY, size=(400,300),style = wx.TE_MULTILINE|wx.TE_READONLY|wx.VSCROLL)
self.button = wx.Button(self.panel, label="Click me")
sizer.Add(self.log, 0, wx.EXPAND | wx.ALL, 10)
sizer.Add(self.button, 0, wx.EXPAND | wx.ALL, 10)
self.panel.SetSizer(sizer)
self.Bind(wx.EVT_BUTTON, self.OnButton)
def OnButton(self,event):
dlg = ShowConnString(parent = self.panel)
dlg.ShowModal()
if dlg.result_name:
self.log.AppendText("Name: "+dlg.result_name+"\n")
self.log.AppendText("Spin: "+str(dlg.result_spin)+"\n")
self.log.AppendText("Choice: "+str(dlg.result_choice)+"\n")
else:
self.log.AppendText("No selection made\n")
dlg.Destroy()
class ShowConnString(wx.Dialog):
def __init__(self, parent):
wx.Dialog.__init__(self, parent, wx.ID_ANY, "Save", size= (650,220))
self.panel = wx.Panel(self,wx.ID_ANY)
self.lblname = wx.StaticText(self.panel, label="Connection", pos=(20,20))
self.editname = wx.TextCtrl(self.panel, value="server=127.0.0.1", pos=(110,20), size=(500,-1))
self.lbl_1 = wx.StaticText(self.panel, wx.ID_ANY, ("Spin Control"), pos=(20,60))
self.spin = wx.SpinCtrl(self.panel, wx.ID_ANY, "", min=1, max=10, pos=(110,60))
self.lbl_2 = wx.StaticText(self.panel, wx.ID_ANY, ("Choice"),pos=(20,100))
self.choice = wx.Choice(self.panel, wx.ID_ANY, choices=[("Choice 1"), ("Choice 2"), ("Choice 3")], pos=(110,100))
self.saveButton =wx.Button(self.panel, label="Save", pos=(110,160))
self.closeButton =wx.Button(self.panel, label="Cancel", pos=(210,160))
self.saveButton.Bind(wx.EVT_BUTTON, self.SaveConnString)
self.closeButton.Bind(wx.EVT_BUTTON, self.OnQuit)
self.Bind(wx.EVT_CLOSE, self.OnQuit)
self.spin.SetValue(0)
self.choice.SetSelection(0)
self.Show()
def OnQuit(self, event):
self.result_name = None
self.Destroy()
def SaveConnString(self, event):
self.result_name = self.editname.GetValue()
self.result_spin = self.spin.GetValue()
self.result_choice = self.choice.GetSelection()
self.Destroy()
app = wx.App()
frame = MyFrame(None)
frame.Show()
app.MainLoop()
It appears that you are attempting to sub-class a wx.TextEntryDialog but using wx.Panel.
You will probably find much simpler to just use a wx.TextEntryDialog directly i.e.
import wx
app = wx.App()
dlg = wx.TextEntryDialog(None,"Connection String:","Save","server='(local)', database='Audit', uid='sa', pwd='_PWD4sa_'")
dlg.SetSize((600,180))
if dlg.ShowModal() == wx.ID_OK:
text = dlg.GetValue()
print text
dlg.Destroy()
Related
I want to know if there is any workaround to the missing flow() property "BottomToTop"?
I'm currently working on a little pet project. Simply a QListWidget, containing a custom made QWidget item, added by the user. No problem with that part. I just want the item to be listed "BottomToTop". Any suggestions?
Do just need to insert the new elements at the bottom instead of the top of your list?
I assume what you call bottom in the beginning for your list?
If so, you can use QListWidget::insertItem
yourListWidget->(0, yourNewWidgetItem);
This is my closest solution to what I've been asking (Credit to -ymoreau for the Spacer idea).
But I'm not completely happy with it. When enough items are added and the scrollbar appears, an annoying (to my eye) white-space appears at the bottom. Any solution to that? And ofc. is there a more elegant solution to this? Seems abit overkill to me...
from PyQt5.QtWidgets import (QApplication, QWidget, QListWidget,
QListWidgetItem, QVBoxLayout, QHBoxLayout,
QPushButton, QSpacerItem, QLabel, QSizePolicy)
from PyQt5.QtCore import QSize
from PyQt5 import QtCore
import sys
class SpacerWidget(QWidget):
def __init__(self, parent=None):
super(SpacerWidget, self).__init__(parent)
self.spacer = QSpacerItem(self.width(), self.height(),
QSizePolicy.Expanding,
QSizePolicy.Expanding)
self.setStyleSheet("border: none;")
self.layout = QHBoxLayout()
self.layout.setSpacing(0)
self.layout.setContentsMargins(0, 0, 0, 0)
self.layout.addItem(self.spacer)
self.setLayout(self.layout)
class SpacerWidgetItem(QListWidgetItem):
def __init__(self, parent=None):
super(SpacerWidgetItem, self).__init__(parent)
def setSize(self, width, new_height):
if new_height < 10:
self.setSizeHint(QSize(0, 0))
else:
self.setSizeHint(QSize(width, new_height))
class ItemWidget(QWidget):
def __init__(self, parent=None):
super(ItemWidget, self).__init__(parent)
self.left_label = QLabel("Some info!")
self.left_label.setMaximumHeight(35)
self.left_label.setAlignment(QtCore.Qt.AlignLeft)
self.right_label = QLabel("Some other info!")
self.right_label.setMaximumHeight(35)
self.right_label.setAlignment(QtCore.Qt.AlignRight)
self.layout = QHBoxLayout()
self.layout.setSpacing(0)
self.layout.addWidget(self.left_label)
self.layout.addWidget(self.right_label)
self.setLayout(self.layout)
class Widget(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.resize(300, 320)
self.itemsinlist = 0
self.layout = QVBoxLayout()
self.add_btn = QPushButton("Add Item")
self.add_btn.setMinimumSize(QSize(300, 50))
self.add_btn.clicked.connect(self.addListItem)
self.list_w = QListWidget()
self.list_w.setMinimumSize(300, 270)
self.list_w.resize(300, 270)
self.list_w.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.list_w.setResizeMode(1)
self.spacer = SpacerWidgetItem(self.list_w)
self.spacer.setSizeHint(QSize(self.list_w.minimumWidth(),
self.list_w.minimumHeight()))
self.spacer_widget = SpacerWidget()
self.list_w.addItem(self.spacer)
self.list_w.setItemWidget(self.spacer, self.spacer_widget)
self.layout.addWidget(self.list_w)
self.layout.addWidget(self.add_btn)
self.layout.setContentsMargins(1, 1, 1, 1)
self.layout.setSpacing(0)
self.setLayout(self.layout)
self.show()
def addListItem(self):
new_label = QListWidgetItem(self.list_w)
new_label.setSizeHint(QSize(self.list_w.minimumWidth(), 35))
new_label_widget = ItemWidget()
self.list_w.addItem(new_label)
self.list_w.setItemWidget(new_label, new_label_widget)
self.list_w.scrollToBottom()
self.itemsinlist += 1
self.spacer.setSize(self.width(),
self.list_w.height() -
(self.itemsinlist * 35)-2)
def resizeEvent(self, event):
self.spacer.setSizeHint(QSize(self.width(),
self.list_w.height() -
(self.itemsinlist * 35)-2))
self.list_w.scrollToBottom()
def main():
app = QApplication(sys.argv)
wid = Widget()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
After reading about finite state machines I found the the QState/QStateMachine API in QT.
But now I can't find out how to use it to run different code based on which state I'm currently in.
Let's take the following example:
import sys
from PySide2 import QtCore, QtWidgets
class Form(QtWidgets.QDialog):
def action_a(self):
print("I'm in mode A")
def action_b(self):
print("Mode B is the current mode")
def __init__(self, parent=None):
super(Form, self).__init__(parent)
self.button1 = QtWidgets.QPushButton("Run action")
self.button2 = QtWidgets.QPushButton("Change State")
self.layout = QtWidgets.QVBoxLayout()
self.layout.addWidget(self.button1)
self.layout.addWidget(self.button2)
self.setLayout(self.layout)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
form = Form()
form.show()
state_a = QtCore.QState()
state_b = QtCore.QState()
state_a.assignProperty(form.button2, "text", "To state B")
state_b.assignProperty(form.button2, "text", "To state A")
state_a.addTransition(form.button2, QtCore.SIGNAL("clicked()"), state_b)
state_b.addTransition(form.button2, QtCore.SIGNAL("clicked()"), state_a)
machine = QtCore.QStateMachine()
machine.addState(state_a)
machine.addState(state_b)
machine.setInitialState(state_a)
machine.start()
sys.exit(app.exec_())
What would I have to add to this code so that button1.clicked connects to action_a when in state_a, but to action_b when in state_b?
I want a BitMapButton Class with some toggle functionality:
first press ON, OFF next press etc.
This should be visible in the color of the Button.
I tried the following (needs some tiny 'off.gif', 'on.gif' files):
import wx
class BitmapToggleButton(wx.BitmapButton):
"""make a Button, inherits wx.BitmapButton, add a toggleState"""
#----------------------------------------------------------------------
def __init__(self, parent, filename1, filename2):
"""Constructor"""
self.state = False
self.image1 = wx.Image(filename1, wx.BITMAP_TYPE_ANY).ConvertToBitmap()
self.image2 = wx.Image(filename2, wx.BITMAP_TYPE_ANY).ConvertToBitmap()
#wx.BitmapButton.__init__(self, id=-1, bitmap=self.image2, pos=(10, 20), size = (300, 400))
self.image1 = wx.Image(filename1, wx.BITMAP_TYPE_ANY).ConvertToBitmap()
self.image2 = wx.Image(filename2, wx.BITMAP_TYPE_ANY).ConvertToBitmap()
self.button = wx.BitmapButton(parent, id=-1, bitmap=self.image1, pos=(10, 20), size = (self.image1.GetWidth()+5, self.image1.GetHeight()+5))
def OnClick(self, event):
"""does the toggling"""
if self.state:
self.state = False
self.button.SetBitmapLabel(self.image2)
else:
self.state = True
self.button.SetBitmapLabel(self.image1)
self.Refresh()
class MyFrame(wx.Frame):
"""make a frame, inherits wx.Frame, add a panel and button"""
def __init__(self):
# create a frame, no parent, default to wxID_ANY
wx.Frame.__init__(self, None, wx.ID_ANY, 'wxBitmapButton', pos=(300, 150), size=(300, 350))
#panel to display button
self.panel1 = wx.Panel(self, -1)
#test it
self.button = BitmapToggleButton(self, 'off.gif', 'on.gif')
self.Bind(wx.EVT_BUTTON, self.button.OnClick, self.button)
# show the frame
self.Show(True)
application = wx.PySimpleApp()
# call class MyFrame
f = MyFrame()
# start the event loop
application.MainLoop()
I confess to have some lost track, I have to inherit from BitmapButton class, right, What is wrong?
joh
I have wxPython 2.8. There is no GenBitmapToggleButton
or miss I something?
You don't. Just use GenBitmapToggleButton, see wxPython demo for an example (in Custom Controls/GenericButtons).
EDIT: Present since at least 2.8.9.1, see Link. Have a closer look at the wxPython demo if unsure how to use.
I would like to send multiple POST requests using single QNetworkAccessManager, but it works only once. The second and further requests got an empty reply.
I know I can use multiple instances of manager, but I read, that it's an architectural flaw.
I have also found this: using QNetworkAccessManager GET multiple times, but I do not understand the eventual solution.
Here's my code:
class Networker(QObject):
gotResponse = pyqtSignal(str)
def proc(self, reply):
s = bytes(reply.readAll()).decode('utf-8')
self.gotResponse.emit(s)
def __init__(self, parent=None):
super(Networker, self).__init__(parent)
self.manager = QNetworkAccessManager()
def send(self):
req = QNetworkRequest(QUrl("http://127.0.0.1:8000/"))
req.setHeader(QNetworkRequest.ContentTypeHeader, "text/json;charset=utf-8")
data = QByteArray('{"id":1}')
self.manager.finished.connect(self.proc)
self.manager.post(req, data)
Main (imports excluded):
def main():
app = QtWidgets.QApplication(sys.argv)
n = Networker(app)
w = QtWidgets.QWidget()
w.setWindowTitle("main")
layout = QBoxLayout(QBoxLayout.LeftToRight, w)
l1 = QTextEdit("Result", w)
l1.resize(300, 600)
n.gotResponse.connect(l1.setText)
layout.addWidget(l1)
w.show()
l2 = QTextEdit("Idle", w)
l2.resize(250, 600)
layout.addWidget(l2)
w.resize(500, 600)
btn = QPushButton("Send", w)
layout.addWidget(btn)
btn.clicked.connect(n.send)
w.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
I am wondering how to best truncate text in a QLabel based on it's maximum width/height.
The incoming text could be any length, but in order to keep a tidy layout I'd like to truncate long strings to fill a maximum amount of space (widget's maximum width/height).
E.g.:
'A very long string where there should only be a short one, but I can't control input to the widget as it's a user given value'
would become:
'A very long string where there should only be a short one, but ...'
based on the required space the current font needs.
How can I achieve this best?
Here is a simple example of what I'm after, though this is based on word count, not available space:
import sys
from PySide.QtGui import *
from PySide.QtCore import *
def truncateText(text):
maxWords = 10
words = text.split(' ')
return ' '.join(words[:maxWords]) + ' ...'
app = QApplication(sys.argv)
mainWindow = QWidget()
layout = QHBoxLayout()
mainWindow.setLayout(layout)
text = 'this is a very long string, '*10
label = QLabel(truncateText(text))
label.setWordWrap(True)
label.setFixedWidth(200)
layout.addWidget(label)
mainWindow.show()
sys.exit(app.exec_())
Even easier - use the QFontMetrics.elidedText method and overload the paintEvent, here's an example:
from PyQt4.QtCore import Qt
from PyQt4.QtGui import QApplication,\
QLabel,\
QFontMetrics,\
QPainter
class MyLabel(QLabel):
def paintEvent( self, event ):
painter = QPainter(self)
metrics = QFontMetrics(self.font())
elided = metrics.elidedText(self.text(), Qt.ElideRight, self.width())
painter.drawText(self.rect(), self.alignment(), elided)
if ( __name__ == '__main__' ):
app = None
if ( not QApplication.instance() ):
app = QApplication([])
label = MyLabel()
label.setText('This is a really, long and poorly formatted runon sentence used to illustrate a point')
label.setWindowFlags(Qt.Dialog)
label.show()
if ( app ):
app.exec_()
I found that #Eric Hulser's answer, while great, didn't work when the label was put into another widget.
I came up with this by hacking together Eric's response with the Qt Elided Label Example. It should behave just like a regular label, yet elide horizontally when the text width exceeds the widget width. It has an extra argument for different elide modes. I also wrote some tests for fun :)
If you want to use PyQt5...
Change "PySide2" to "PyQt5"
Change "Signal" to "pyqtSignal"
Enjoy!
Eliding Label
# eliding_label.py
from PySide2 import QtCore, QtWidgets, QtGui
class ElidingLabel(QtWidgets.QLabel):
"""Label with text elision.
QLabel which will elide text too long to fit the widget. Based on:
https://doc-snapshots.qt.io/qtforpython-5.15/overviews/qtwidgets-widgets-elidedlabel-example.html
Parameters
----------
text : str
Label text.
mode : QtCore.Qt.TextElideMode
Specify where ellipsis should appear when displaying texts that
don’t fit.
Default is QtCore.Qt.ElideMiddle.
Possible modes:
QtCore.Qt.ElideLeft
QtCore.Qt.ElideMiddle
QtCore.Qt.ElideRight
parent : QWidget
Parent widget. Default is None.
f : Qt.WindowFlags()
https://doc-snapshots.qt.io/qtforpython-5.15/PySide2/QtCore/Qt.html#PySide2.QtCore.PySide2.QtCore.Qt.WindowType
"""
elision_changed = QtCore.Signal(bool)
def __init__(self, text='', mode=QtCore.Qt.ElideMiddle, **kwargs):
super().__init__(**kwargs)
self._mode = mode
self.is_elided = False
self.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred)
self.setText(text)
def setText(self, text):
self._contents = text
# This line set for testing. Its value is the return value of
# QFontMetrics.elidedText, set in paintEvent. The variable
# must be initialized for testing. The value should always be
# the same as contents when not elided.
self._elided_line = text
self.update()
def text(self):
return self._contents
def paintEvent(self, event):
super().paintEvent(event)
did_elide = False
painter = QtGui.QPainter(self)
font_metrics = painter.fontMetrics()
text_width = font_metrics.horizontalAdvance(self.text())
# layout phase
text_layout = QtGui.QTextLayout(self._contents, painter.font())
text_layout.beginLayout()
while True:
line = text_layout.createLine()
if not line.isValid():
break
line.setLineWidth(self.width())
if text_width >= self.width():
self._elided_line = font_metrics.elidedText(self._contents, self._mode, self.width())
painter.drawText(QtCore.QPoint(0, font_metrics.ascent()), self._elided_line)
did_elide = line.isValid()
break
else:
line.draw(painter, QtCore.QPoint(0, 0))
text_layout.endLayout()
if did_elide != self.is_elided:
self.is_elided = did_elide
self.elision_changed.emit(did_elide)
if __name__ == '__main__':
app = QtWidgets.QApplication([])
long_text = "this is some long text, wouldn't you say?"
elabel = ElidingLabel(long_text)
elabel.show()
app.exec_()
Test Eliding Label
# test_eliding_label.py.py
#
# Run tests with
#
# python3 -m unittest test_eliding_label.py --failfast --quiet
import unittest
import unittest.mock
from PySide2 import QtCore, QtWidgets, QtGui, QtTest
import eliding_label
if not QtWidgets.QApplication.instance():
APP = QtWidgets.QApplication([]) # pragma: no cover
class TestElidingLabelArguments(unittest.TestCase):
def test_optional_text_argument(self):
elabel = eliding_label.ElidingLabel()
self.assertEqual(elabel.text(), "")
def test_text_argument_sets_label_text(self):
elabel = eliding_label.ElidingLabel(text="Test text")
self.assertEqual(elabel.text(), "Test text")
def test_optional_elision_mode_argument(self):
elabel = eliding_label.ElidingLabel()
self.assertEqual(elabel._mode, QtCore.Qt.ElideMiddle)
class TestElidingLabel(unittest.TestCase):
def setUp(self):
self.elabel = eliding_label.ElidingLabel()
def test_elabel_is_a_label(self):
self.assertIsInstance(self.elabel, QtWidgets.QLabel)
def test_has_elision_predicate(self):
self.assertEqual(self.elabel.is_elided, False)
def test_elision_predicate_changes_when_text_width_exceeds_widget_width(self):
# NOTE: This is a bit of a stretch, inducing a paint event
# when the event loop isn't running. Throws a bunch of C++
# sourced text which can't be (easily) caught.
self.elabel.setFixedWidth(25)
self.assertEqual(self.elabel.width(), 25)
long_text = "This is line is definely longer than 25 pixels."
painter = QtGui.QPainter()
font_metrics = painter.fontMetrics()
long_text_width = font_metrics.horizontalAdvance(long_text)
self.assertGreater(long_text_width, 25)
self.elabel.setText(long_text)
x = self.elabel.x()
y = self.elabel.y()
w = self.elabel.width()
h = self.elabel.height()
paint_event = QtGui.QPaintEvent(QtGui.QRegion(x, y, w, h))
self.elabel.paintEvent(paint_event)
self.assertEqual(self.elabel.is_elided, True)
def test_text_is_elided_when_text_width_exceeds_widget_width(self):
# NOTE: This is a bit of a stretch, inducing a paint event
# when the event loop isn't running. Throws a bunch of C++
# sourced text which can't be (easily) caught.
self.elabel.setFixedWidth(25)
self.assertEqual(self.elabel.width(), 25)
long_text = "This is line is definely longer than 25 pixels."
painter = QtGui.QPainter()
font_metrics = painter.fontMetrics()
long_text_width = font_metrics.horizontalAdvance(long_text)
self.assertGreater(long_text_width, 25)
self.elabel.setText(long_text)
x = self.elabel.x()
y = self.elabel.y()
w = self.elabel.width()
h = self.elabel.height()
paint_event = QtGui.QPaintEvent(QtGui.QRegion(x, y, w, h))
self.elabel.paintEvent(paint_event)
# PySide2.QtGui.QFontMetrics.elidedText states, "If the string
# text is wider than width , returns an elided version of the
# string (i.e., a string with '…' in it). Otherwise, returns
# the original string."
self.assertEqual(self.elabel._elided_line, '…')
def test_text_is_not_elided_when_text_width_is_less_than_widget_width(self):
# NOTE: This is a bit of a stretch, inducing a paint event
# when the event loop isn't running. Throws a bunch of C++
# sourced text which can't be (easily) caught.
self.elabel.setFixedWidth(500)
self.assertEqual(self.elabel.width(), 500)
short_text = "Less than 500"
painter = QtGui.QPainter()
font_metrics = painter.fontMetrics()
short_text_width = font_metrics.horizontalAdvance(short_text)
self.assertLess(short_text_width, 500)
self.elabel.setText(short_text)
x = self.elabel.x()
y = self.elabel.y()
w = self.elabel.width()
h = self.elabel.height()
paint_event = QtGui.QPaintEvent(QtGui.QRegion(x, y, w, h))
self.elabel.paintEvent(paint_event)
# PySide2.QtGui.QFontMetrics.elidedText states, "If the string
# text is wider than width , returns an elided version of the
# string (i.e., a string with '…' in it). Otherwise, returns
# the original string."
self.assertEqual(self.elabel._elided_line, short_text)
def test_stores_full_text_even_when_elided(self):
# NOTE: This is a bit of a stretch, inducing a paint event
# when the event loop isn't running. Throws a bunch of C++
# sourced text which can't be (easily) caught.
self.elabel.setFixedWidth(25)
self.assertEqual(self.elabel.width(), 25)
long_text = "This is line is definely longer than 25 pixels."
painter = QtGui.QPainter()
font_metrics = painter.fontMetrics()
long_text_width = font_metrics.horizontalAdvance(long_text)
self.assertGreater(long_text_width, 25)
self.elabel.setText(long_text)
x = self.elabel.x()
y = self.elabel.y()
w = self.elabel.width()
h = self.elabel.height()
paint_event = QtGui.QPaintEvent(QtGui.QRegion(x, y, w, h))
self.elabel.paintEvent(paint_event)
# PySide2.QtGui.QFontMetrics.elidedText states, "If the string
# text is wider than width , returns an elided version of the
# string (i.e., a string with '…' in it). Otherwise, returns
# the original string."
self.assertEqual(self.elabel._elided_line, '…')
self.assertEqual(self.elabel.text(), long_text)
def test_has_elision_changed_signal(self):
self.assertIsInstance(self.elabel.elision_changed, QtCore.Signal)
def test_elision_changed_signal_emits_on_change_to_is_elided_predicate(self):
mock = unittest.mock.Mock()
self.elabel.elision_changed.connect(mock.method)
# NOTE: This is a bit of a stretch, inducing a paint event
# when the event loop isn't running. Throws a bunch of C++
# sourced text which can't be (easily) caught.
# Induce elision
self.elabel.setFixedWidth(150)
self.assertEqual(self.elabel.width(), 150)
long_text = "This line is definitely going to be more than 150 pixels"
painter = QtGui.QPainter()
font_metrics = painter.fontMetrics()
long_text_width = font_metrics.horizontalAdvance(long_text)
self.assertGreater(long_text_width, 150)
self.elabel.setText(long_text)
self.assertEqual(self.elabel.is_elided, False) # no elide until painting
x = self.elabel.x()
y = self.elabel.y()
w = self.elabel.width()
h = self.elabel.height()
paint_event = QtGui.QPaintEvent(QtGui.QRegion(x, y, w, h))
self.elabel.paintEvent(paint_event)
self.assertEqual(self.elabel.is_elided, True)
mock.method.assert_called_once()
# Remove elision
short_text = "Less than 150"
painter = QtGui.QPainter()
font_metrics = painter.fontMetrics()
short_text_width = font_metrics.horizontalAdvance(short_text)
self.assertLess(short_text_width, 150)
self.elabel.setText(short_text)
self.assertEqual(self.elabel.is_elided, True) # still elided until painting
x = self.elabel.x()
y = self.elabel.y()
w = self.elabel.width()
h = self.elabel.height()
paint_event = QtGui.QPaintEvent(QtGui.QRegion(x, y, w, h))
self.elabel.paintEvent(paint_event)
self.assertEqual(self.elabel.is_elided, False)
self.assertEqual(mock.method.call_count, 2)
You can achieves this through determining the width with QFontMetrics, see this answer.
You would probably want to use or create some algorithm which finds the place to cut quickly, unless doing it in a simple for loop would be sufficient.
simpler solution if you want show QLabel in center in provided area
label.setAlignment(Qt.AlignmentFlag.AlignCenter)
label.minimumSizeHint = lambda self=label: QSize(0, QLabel.minimumSizeHint(self).height() )