mirror of
https://github.com/pyapp-kit/superqt.git
synced 2026-01-06 04:14:21 +01:00
Add QElidingLabel (#16)
* wip * single class implementation * fix init * improve implementation * improve sizeHint * wrap * update docs * rename * remove overloads * review changes * docs and reformat * remove width from _elided text * add tests
This commit is contained in:
12
examples/eliding_label.py
Normal file
12
examples/eliding_label.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from superqt import QElidingLabel
|
||||
from superqt.qtcompat.QtWidgets import QApplication
|
||||
|
||||
app = QApplication([])
|
||||
|
||||
widget = QElidingLabel(
|
||||
"a skj skjfskfj sdlf sdfl sdlfk jsdf sdlkf jdsf dslfksdl sdlfk sdf sdl "
|
||||
"fjsdlf kjsdlfk laskdfsal as lsdfjdsl kfjdslf asfd dslkjfldskf sdlkfj"
|
||||
)
|
||||
widget.setWordWrap(True)
|
||||
widget.show()
|
||||
app.exec_()
|
||||
@@ -5,6 +5,7 @@ except ImportError:
|
||||
__version__ = "unknown"
|
||||
|
||||
|
||||
from ._eliding_label import QElidingLabel
|
||||
from .combobox import QEnumComboBox
|
||||
from .sliders import (
|
||||
QDoubleRangeSlider,
|
||||
@@ -20,6 +21,7 @@ from .spinbox import QLargeIntSpinBox
|
||||
__all__ = [
|
||||
"QDoubleRangeSlider",
|
||||
"QDoubleSlider",
|
||||
"QElidingLabel",
|
||||
"QLabeledDoubleRangeSlider",
|
||||
"QLabeledDoubleSlider",
|
||||
"QLabeledRangeSlider",
|
||||
|
||||
110
superqt/_eliding_label.py
Normal file
110
superqt/_eliding_label.py
Normal file
@@ -0,0 +1,110 @@
|
||||
from typing import List
|
||||
|
||||
from superqt.qtcompat.QtCore import QPoint, QRect, QSize, Qt
|
||||
from superqt.qtcompat.QtGui import QFont, QFontMetrics, QResizeEvent, QTextLayout
|
||||
from superqt.qtcompat.QtWidgets import QLabel
|
||||
|
||||
|
||||
class QElidingLabel(QLabel):
|
||||
"""A QLabel variant that will elide text (add '…') to fit width.
|
||||
|
||||
QElidingLabel()
|
||||
QElidingLabel(parent: Optional[QWidget], f: Qt.WindowFlags = ...)
|
||||
QElidingLabel(text: str, parent: Optional[QWidget] = None, f: Qt.WindowFlags = ...)
|
||||
|
||||
For a multiline eliding label, use `setWordWrap(True)`. In this case, text
|
||||
will wrap to fit the width, and only the last line will be elided.
|
||||
When `wordWrap()` is True, `sizeHint()` will return the size required to fit
|
||||
the full text.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
self._elide_mode = Qt.TextElideMode.ElideRight
|
||||
super().__init__(*args, **kwargs)
|
||||
self.setText(args[0] if args and isinstance(args[0], str) else "")
|
||||
|
||||
# New Public methods
|
||||
|
||||
def elideMode(self) -> Qt.TextElideMode:
|
||||
"""The current Qt.TextElideMode."""
|
||||
return self._elide_mode
|
||||
|
||||
def setElideMode(self, mode: Qt.TextElideMode):
|
||||
"""Set the elide mode to a Qt.TextElideMode."""
|
||||
self._elide_mode = Qt.TextElideMode(mode)
|
||||
super().setText(self._elidedText())
|
||||
|
||||
@staticmethod
|
||||
def wrapText(text, width, font=None) -> List[str]:
|
||||
"""Returns `text`, split as it would be wrapped for `width`, given `font`.
|
||||
|
||||
Static method.
|
||||
"""
|
||||
tl = QTextLayout(text, font or QFont())
|
||||
tl.beginLayout()
|
||||
lines = []
|
||||
while True:
|
||||
ln = tl.createLine()
|
||||
if not ln.isValid():
|
||||
break
|
||||
ln.setLineWidth(width)
|
||||
start = ln.textStart()
|
||||
lines.append(text[start : start + ln.textLength()])
|
||||
tl.endLayout()
|
||||
return lines
|
||||
|
||||
# Reimplemented QT methods
|
||||
|
||||
def text(self) -> str:
|
||||
"""This property holds the label's text.
|
||||
|
||||
If no text has been set this will return an empty string.
|
||||
"""
|
||||
return self._text
|
||||
|
||||
def setText(self, txt: str):
|
||||
"""Set the label's text.
|
||||
|
||||
Setting the text clears any previous content.
|
||||
NOTE: we set the QLabel private text to the elided version
|
||||
"""
|
||||
self._text = txt
|
||||
super().setText(self._elidedText())
|
||||
|
||||
def resizeEvent(self, ev: QResizeEvent) -> None:
|
||||
ev.accept()
|
||||
super().setText(self._elidedText())
|
||||
|
||||
def setWordWrap(self, wrap: bool) -> None:
|
||||
super().setWordWrap(wrap)
|
||||
super().setText(self._elidedText())
|
||||
|
||||
def sizeHint(self) -> QSize:
|
||||
if not self.wordWrap():
|
||||
return super().sizeHint()
|
||||
fm = QFontMetrics(self.font())
|
||||
flags = self.alignment() | Qt.TextFlag.TextWordWrap
|
||||
r = fm.boundingRect(QRect(QPoint(0, 0), self.size()), flags, self._text)
|
||||
return QSize(self.width(), r.height())
|
||||
|
||||
# private implementation methods
|
||||
|
||||
def _elidedText(self) -> str:
|
||||
"""Return `self._text` elided to `width`"""
|
||||
fm = QFontMetrics(self.font())
|
||||
# the 2 is a magic number that prevents the ellipses from going missing
|
||||
# in certain cases (?)
|
||||
width = self.width() - 2
|
||||
if not self.wordWrap():
|
||||
return fm.elidedText(self._text, self._elide_mode, width)
|
||||
|
||||
# get number of lines we can fit without eliding
|
||||
nlines = self.height() // fm.height() - 1
|
||||
# get the last line (elided)
|
||||
text = self._wrappedText()
|
||||
last_line = fm.elidedText("".join(text[nlines:]), self._elide_mode, width)
|
||||
# join them
|
||||
return "".join(text[:nlines] + [last_line])
|
||||
|
||||
def _wrappedText(self) -> List[str]:
|
||||
return QElidingLabel.wrapText(self._text, self.width(), self.font())
|
||||
68
superqt/_tests/test_eliding_label.py
Normal file
68
superqt/_tests/test_eliding_label.py
Normal file
@@ -0,0 +1,68 @@
|
||||
from superqt import QElidingLabel
|
||||
from superqt.qtcompat.QtCore import QSize, Qt
|
||||
from superqt.qtcompat.QtGui import QResizeEvent
|
||||
|
||||
TEXT = (
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do "
|
||||
"eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad "
|
||||
"minim ven iam, quis nostrud exercitation ullamco laborisnisi ut aliquip "
|
||||
"ex ea commodo consequat. Duis aute irure dolor inreprehenderit in voluptate "
|
||||
"velit esse cillum dolore eu fugiat nullapariatur."
|
||||
)
|
||||
ELLIPSIS = "…"
|
||||
|
||||
|
||||
def test_eliding_label(qtbot):
|
||||
wdg = QElidingLabel(TEXT)
|
||||
qtbot.addWidget(wdg)
|
||||
assert wdg._elidedText().endswith(ELLIPSIS)
|
||||
oldsize = wdg.size()
|
||||
newsize = QSize(200, 20)
|
||||
wdg.resize(newsize)
|
||||
wdg.resizeEvent(QResizeEvent(oldsize, newsize)) # for test coverage
|
||||
assert wdg.text() == TEXT
|
||||
|
||||
|
||||
def test_wrapped_eliding_label(qtbot):
|
||||
wdg = QElidingLabel(TEXT)
|
||||
qtbot.addWidget(wdg)
|
||||
assert not wdg.wordWrap()
|
||||
assert wdg.sizeHint() == QSize(633, 16)
|
||||
assert wdg._elidedText() == (
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do "
|
||||
"eiusmod tempor incididunt ut labore et d…"
|
||||
)
|
||||
wdg.resize(QSize(200, 100))
|
||||
assert wdg.text() == TEXT
|
||||
assert wdg._elidedText() == "Lorem ipsum dolor sit amet, co…"
|
||||
wdg.setWordWrap(True)
|
||||
assert wdg.wordWrap()
|
||||
assert wdg.text() == TEXT
|
||||
assert wdg._elidedText() == (
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do "
|
||||
"eiusmod tempor incididunt ut labore et dolore magna aliqua. "
|
||||
"Ut enim ad minim ven iam, quis nostrud exercitation ullamco la…"
|
||||
)
|
||||
assert wdg.sizeHint() == QSize(200, 176)
|
||||
wdg.resize(wdg.sizeHint())
|
||||
assert wdg._elidedText() == TEXT
|
||||
|
||||
|
||||
def test_shorter_eliding_label(qtbot):
|
||||
short = "asd a ads sd flksdf dsf lksfj sd lsdjf sd lsdfk sdlkfj s"
|
||||
wdg = QElidingLabel()
|
||||
qtbot.addWidget(wdg)
|
||||
wdg.setText(short)
|
||||
assert not wdg._elidedText().endswith(ELLIPSIS)
|
||||
wdg.resize(100, 20)
|
||||
assert wdg._elidedText().endswith(ELLIPSIS)
|
||||
wdg.setElideMode(Qt.TextElideMode.ElideLeft)
|
||||
assert wdg._elidedText().startswith(ELLIPSIS)
|
||||
assert wdg.elideMode() == Qt.TextElideMode.ElideLeft
|
||||
|
||||
|
||||
def test_wrap_text():
|
||||
wrap = QElidingLabel.wrapText(TEXT, 200)
|
||||
assert isinstance(wrap, list)
|
||||
assert all(isinstance(x, str) for x in wrap)
|
||||
assert len(wrap) == 11
|
||||
Reference in New Issue
Block a user