Skip to content

Commit 8af8730

Browse files
committed
fix(list): share text rect between sizeHint and paint, elide folded overflow lines
1 parent a3b8769 commit 8af8730

2 files changed

Lines changed: 61 additions & 46 deletions

File tree

‎BlocksScreen/lib/utils/list_model.py‎

Lines changed: 36 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -352,40 +352,32 @@ def sizeHint(
352352
item: ListItem = index.data(QtCore.Qt.ItemDataRole.UserRole)
353353
target_width = option.rect.width()
354354

355-
base_h = item.height
356-
ellipse_size = base_h * 0.8
357-
358-
right_reserved = base_h
359-
360-
left_reserved = 10
361-
if item.left_icon:
362-
left_reserved = (base_h * 0.1) + ellipse_size + 8 + _TEXT_LEFT_PADDING
363-
364355
if item._lfontsize > 0 and item._lfontsize != option.font.pointSize():
365356
f = QtGui.QFont(option.font)
366357
f.setPointSize(item._lfontsize)
367358
fm = QtGui.QFontMetrics(f)
368359
else:
369360
fm = option.fontMetrics
370361

362+
right_text_w = 0
371363
if item.right_text:
372364
if item._rfontsize > 0 and item._rfontsize != option.font.pointSize():
373365
fr = QtGui.QFont(option.font)
374366
fr.setPointSize(item._rfontsize)
375367
fmr = QtGui.QFontMetrics(fr)
376368
else:
377369
fmr = option.fontMetrics
378-
right_reserved += fmr.horizontalAdvance(item.right_text) + 10
379-
380-
if item.right_icon:
381-
right_reserved += ellipse_size
382-
383-
text_avail_width = max(target_width - left_reserved - right_reserved, 50)
370+
right_text_w = fmr.horizontalAdvance(item.right_text)
384371

385372
collapsed_h = int(item.height * 1.1)
373+
text_rect = self._text_rect(
374+
item,
375+
QtCore.QRect(option.rect.x(), option.rect.y(), target_width, collapsed_h),
376+
right_text_w,
377+
)
378+
text_avail_width = max(int(text_rect.width()), 50)
386379
lines = item.text.split("\n")
387-
# paint() insets the row by 2px per side before fitting lines
388-
max_lines = max(1, (collapsed_h - 4) // fm.lineSpacing())
380+
max_lines = max(1, int(text_rect.height()) // fm.lineSpacing())
389381

390382
item.needs_expansion = len(lines) > max_lines or any(
391383
fm.horizontalAdvance(line) > text_avail_width for line in lines
@@ -394,15 +386,34 @@ def sizeHint(
394386
if not item.is_expanded:
395387
return QtCore.QSize(target_width, collapsed_h)
396388

397-
text_rect = fm.boundingRect(
398-
QtCore.QRect(0, 0, int(text_avail_width), 0),
389+
bounds = fm.boundingRect(
390+
QtCore.QRect(0, 0, text_avail_width, 0),
399391
QtCore.Qt.AlignmentFlag.AlignLeft | QtCore.Qt.TextFlag.TextWordWrap,
400392
item.text,
401393
)
402394

403-
final_height = max(item.height, text_rect.height() - 1)
395+
final_height = max(item.height, bounds.height() - 1)
404396
return QtCore.QSize(target_width, int(final_height * 1.2))
405397

398+
@staticmethod
399+
def _text_rect(
400+
item: ListItem, row: QtCore.QRect, right_text_w: int
401+
) -> QtCore.QRectF:
402+
"""Main-text area of *row*, shared by sizeHint and paint so overflow matches elision."""
403+
rect = row.adjusted(2, 2, -2, -2)
404+
icon = item.height * 0.8
405+
left = rect.left() + 10 + (icon + _TEXT_LEFT_PADDING if item.left_icon else 0)
406+
right = rect.right() - item.height * 0.1 - icon - right_text_w - 10
407+
return QtCore.QRectF(left, rect.top(), right - left, rect.height())
408+
409+
@staticmethod
410+
def _collapsed_lines(text: str, max_lines: int) -> list[str]:
411+
"""Lines a collapsed row draws; overflow folds into the last so elision marks the cut."""
412+
lines = text.split("\n")
413+
if len(lines) > max_lines:
414+
lines[max_lines - 1 :] = [" ".join(lines[max_lines - 1 :])]
415+
return lines
416+
406417
def paint(
407418
self,
408419
painter: QtGui.QPainter,
@@ -448,7 +459,6 @@ def paint(
448459
)
449460
painter.drawPixmap(ellipse_rect.toRect(), icon_scaled)
450461

451-
left_margin = 10
452462
left_icon_rect = QtCore.QRectF(
453463
rect.left() + ellipse_margin,
454464
rect.top() + ellipse_margin,
@@ -466,22 +476,6 @@ def paint(
466476
left_pixmap = self._get_scaled(item.left_icon, icon_size)
467477
painter.drawPixmap(left_icon_rect.toRect(), left_pixmap)
468478

469-
text_margin = int(
470-
rect.right() - ellipse_size - ellipse_margin - rect.height() * 0.10
471-
)
472-
473-
text_left = (
474-
rect.left()
475-
+ left_margin
476-
+ (left_icon_rect.width() + _TEXT_LEFT_PADDING if item.left_icon else 0)
477-
)
478-
text_rect = QtCore.QRectF(
479-
text_left,
480-
rect.top(),
481-
text_margin - text_left,
482-
rect.height(),
483-
)
484-
485479
painter.setPen(QtGui.QColor(255, 255, 255))
486480

487481
_font = painter.font()
@@ -497,21 +491,17 @@ def paint(
497491

498492
right_metrics = QtGui.QFontMetrics(right_font)
499493

500-
right_text_x = (
501-
ellipse_rect.right()
502-
- right_metrics.horizontalAdvance(item.right_text)
503-
- left_icon_rect.width()
504-
- left_margin
494+
text_rect = self._text_rect(
495+
item, option.rect, right_metrics.horizontalAdvance(item.right_text)
505496
)
506497

507498
if not item.is_expanded:
508-
max_main_text_width = int(right_text_x - left_margin)
509499
max_lines = max(1, int(text_rect.height()) // metrics.lineSpacing())
510500
text = "\n".join(
511501
metrics.elidedText(
512-
line, QtCore.Qt.TextElideMode.ElideRight, max_main_text_width
502+
line, QtCore.Qt.TextElideMode.ElideRight, int(text_rect.width())
513503
)
514-
for line in item.text.split("\n")[:max_lines]
504+
for line in self._collapsed_lines(item.text, max_lines)
515505
)
516506
painter.drawText(
517507
text_rect,
@@ -531,7 +521,7 @@ def paint(
531521
painter.setFont(right_font)
532522
painter.setPen(QtGui.QColor(160, 160, 160))
533523
painter.drawText(
534-
int(right_text_x),
524+
int(text_rect.right()),
535525
int(
536526
ellipse_rect.top()
537527
+ (ellipse_rect.height() + right_metrics.ascent()) / 2

‎tests/util/test_entry_delegate_unit.py‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,3 +213,28 @@ def test_short_single_line_fits(self, delegate):
213213
def test_more_lines_than_row_holds_needs_expansion(self, delegate):
214214
text = "\n".join("line" for _ in range(20))
215215
assert self._size_hint(delegate, text).needs_expansion is True
216+
217+
def test_overflow_uses_paint_text_width(self, delegate):
218+
item = ListItem(text="", height=ROW_H, left_icon=QtGui.QPixmap(1, 1))
219+
row = QtCore.QRect(0, 0, ROW_W, int(ROW_H * 1.1))
220+
width = int(EntryDelegate._text_rect(item, row, 0).width())
221+
fm = _option(0).fontMetrics
222+
fits = "x"
223+
while fm.horizontalAdvance(fits + "x") <= width:
224+
fits += "x"
225+
model = EntryListModel([item])
226+
for text, expected in ((fits, False), (fits + "x", True)):
227+
item.text = text
228+
delegate.sizeHint(_option(0), model.index(0))
229+
assert item.needs_expansion is expected
230+
231+
232+
class TestCollapsedLines:
233+
def test_lines_that_fit_are_kept(self):
234+
assert EntryDelegate._collapsed_lines("a\nb", 2) == ["a", "b"]
235+
236+
def test_overflow_folds_into_last_line(self):
237+
assert EntryDelegate._collapsed_lines("a\nb\nc\nd", 2) == ["a", "b c d"]
238+
239+
def test_single_line_row_joins_everything(self):
240+
assert EntryDelegate._collapsed_lines("a\nb", 1) == ["a b"]

0 commit comments

Comments
 (0)