Coverage for vibefpmgui/console_view.py: 95%

47 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-11-01 20:51 +0000

1"""The Output panel's text: fpm's build progress ("[ 25%]" at a line's 

2start) drawn as a rounded badge, "25%", its color going from blue at 0% 

3to green at 100%. Drawn over the text, which stays as fpm wrote it (so 

4it's selected and copied as such). 

5""" 

6 

7from __future__ import annotations 

8 

9import re 

10 

11from qtpy.QtCore import QRectF, Qt 

12from qtpy.QtGui import QColor, QFont, QPainter, QTextCursor 

13from qtpy.QtWidgets import QPlainTextEdit 

14 

15PROGRESS_RE = re.compile(r"^\[\s*(\d{1,3})%\]") 

16 

17 

18def progress_color(percent: int) -> QColor: 

19 """Blue at 0%, green at 100% (through teal), bright enough for white text.""" 

20 fraction = max(0, min(100, percent)) / 100 

21 return QColor.fromHsvF(0.60 - 0.24 * fraction, 0.70, 0.78) 

22 

23 

24class ConsoleView(QPlainTextEdit): 

25 """A read-only text view for command output, with fpm's progress as badges.""" 

26 

27 def progress_badges(self) -> list[tuple[int, QRectF]]: 

28 """(percent, where its badge is drawn) for the lines on view.""" 

29 out = [] 

30 block = self.firstVisibleBlock() 

31 offset = self.contentOffset() 

32 height = self.viewport().height() 

33 while block.isValid(): 

34 geometry = self.blockBoundingGeometry(block).translated(offset) 

35 if geometry.top() > height: 35 ↛ 36line 35 didn't jump to line 36 because the condition on line 35 was never true

36 break 

37 match = PROGRESS_RE.match(block.text()) 

38 if match and block.isVisible(): 

39 start, end = QTextCursor(block), QTextCursor(block) 

40 start.setPosition(block.position() + match.start()) 

41 end.setPosition(block.position() + match.end()) 

42 left, right = self.cursorRect(start), self.cursorRect(end) 

43 if left.top() == right.top(): # (not wrapped in the middle) 43 ↛ 46line 43 didn't jump to line 46 because the condition on line 43 was always true

44 out.append((int(match.group(1)), QRectF(left.left(), left.top() + 1, right.left() - left.left(), 

45 left.height() - 2))) 

46 block = block.next() 

47 return out 

48 

49 def paintEvent(self, event): 

50 super().paintEvent(event) 

51 badges = self.progress_badges() 

52 if not badges: 

53 return 

54 painter = QPainter(self.viewport()) 

55 painter.setRenderHint(QPainter.Antialiasing) 

56 font = QFont(self.font()) 

57 font.setBold(True) 

58 font.setPointSizeF(max(6.0, font.pointSizeF() * 0.85)) 

59 painter.setFont(font) 

60 for percent, rect in badges: 

61 painter.setPen(Qt.NoPen) 

62 painter.setBrush(progress_color(percent)) 

63 painter.drawRoundedRect(rect, rect.height() / 2, rect.height() / 2) 

64 painter.setPen(QColor("white")) 

65 painter.drawText(rect, Qt.AlignCenter, f"{percent}%") 

66 painter.end()