vibefpmgui.punch_card
A hidden command (type "punch card" in the Command Palette): the line at the cursor as an IBM punched card, as an 029 keypunch would have punched it.
Fixed-form Fortran's columns come from these cards: 1–5 the statement number, 6 a continuation mark, 7–72 the statement, 73–80 left for a sequence number (so a dropped deck could be sorted again). Each column holds one character, as holes in its rows (12, 11, 0–9, top to bottom): the Hollerith code of the IBM 029 (1964). The 029 had capital letters only, and no [ ] { } ~ ^ \ ` — those columns are left blank.
1"""A hidden command (type "punch card" in the Command Palette): the line at 2the cursor as an IBM punched card, as an 029 keypunch would have punched it. 3 4Fixed-form Fortran's columns come from these cards: 1–5 the statement 5number, 6 a continuation mark, 7–72 the statement, 73–80 left for a 6sequence number (so a dropped deck could be sorted again). Each column 7holds one character, as holes in its rows (12, 11, 0–9, top to bottom): 8the Hollerith code of the IBM 029 (1964). The 029 had capital letters 9only, and no [ ] { } ~ ^ \\ ` — those columns are left blank. 10""" 11 12from __future__ import annotations 13 14from qtpy.QtCore import QPointF, QRectF, Qt 15from qtpy.QtGui import QColor, QFont, QPainter, QPen, QPolygonF 16from qtpy.QtWidgets import ( 17 QDialog, 18 QDialogButtonBox, 19 QLabel, 20 QSizePolicy, 21 QVBoxLayout, 22 QWidget, 23) 24 25COLUMNS = 80 26ROWS = (12, 11, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9) # top to bottom 27TAB = 8 # (a tab: spaces to the next multiple of 8, as a keypunch operator would have typed) 28 29# Fortran's fields on the card: (first column, last column, name, short name for a narrow card). 30FIELDS = ((1, 5, "STATEMENT NUMBER", "STMT NO."), (6, 6, "CONT", "C"), (7, 72, "FORTRAN STATEMENT", "FORTRAN"), 31 (73, 80, "IDENTIFICATION", "IDENT.")) 32 33_SPECIAL = { 34 " ": (), "&": (12,), "-": (11,), "/": (0, 1), 35 "¢": (12, 2, 8), ".": (12, 3, 8), "<": (12, 4, 8), "(": (12, 5, 8), "+": (12, 6, 8), "|": (12, 7, 8), 36 "!": (11, 2, 8), "$": (11, 3, 8), "*": (11, 4, 8), ")": (11, 5, 8), ";": (11, 6, 8), "¬": (11, 7, 8), 37 ",": (0, 3, 8), "%": (0, 4, 8), "_": (0, 5, 8), ">": (0, 6, 8), "?": (0, 7, 8), 38 ":": (2, 8), "#": (3, 8), "@": (4, 8), "'": (5, 8), "=": (6, 8), '"': (7, 8), 39} 40 41 42def holes(ch: str) -> tuple[int, ...] | None: 43 """The rows an IBM 029 punches for a character (upper case: it had no 44 lower case); None: it can't punch it.""" 45 ch = ch.upper() 46 if ch.isdigit() and ch.isascii(): 47 return (int(ch),) 48 if "A" <= ch <= "I": 49 return 12, ord(ch) - ord("A") + 1 50 if "J" <= ch <= "R": 51 return 11, ord(ch) - ord("J") + 1 52 if "S" <= ch <= "Z": 53 return 0, ord(ch) - ord("S") + 2 54 return _SPECIAL.get(ch) 55 56 57def card_text(line: str) -> tuple[str, list[str], str]: 58 """A line as punched: its first 80 columns (tabs as spaces, upper case), 59 the characters an 029 couldn't punch (left blank), and what didn't fit.""" 60 expanded = "" 61 for ch in line: 62 expanded += " " * (TAB - len(expanded) % TAB) if ch == "\t" else ch 63 punched, left_off = expanded[:COLUMNS], expanded[COLUMNS:] 64 unpunchable = sorted({ch for ch in punched if holes(ch) is None}) 65 return punched.upper(), unpunchable, left_off 66 67 68class PunchCard(QWidget): 69 """The card: cream card stock, the cut corner, each row's digits printed 70 faintly, the holes, the characters printed along the top edge (as the 71 029 did), Fortran's fields ruled and named, and the column numbers.""" 72 73 STOCK = QColor("#efe4c2") 74 INK = QColor("#2f2a22") 75 PRINT = QColor(160, 120, 70, 150) # (the card's own printing: faint) 76 RULE = QColor(170, 60, 50, 160) # (Fortran's fields) 77 78 def __init__(self, text: str = "", parent=None): 79 super().__init__(parent) 80 self.text = text 81 self.setMinimumSize(760, 340) 82 self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) 83 84 def set_text(self, text: str): 85 self.text = text 86 self.update() 87 88 def hole_rects(self) -> list[tuple[int, int, QRectF]]: 89 """Each hole: (column (1-based), row, where it's drawn).""" 90 card, column_width, row_height, top = self._layout() 91 out = [] 92 for i, ch in enumerate(self.text[:COLUMNS]): 93 for row in holes(ch) or (): 94 x = card.left() + 0.035 * card.width() + i * column_width 95 y = top + ROWS.index(row) * row_height 96 out.append((i + 1, row, QRectF(x + column_width * 0.22, y + row_height * 0.18, 97 column_width * 0.56, row_height * 0.64))) 98 return out 99 100 def _layout(self) -> tuple[QRectF, float, float, float]: 101 """The card (7⅜ × 3¼ inches), a column's width, a row's height, row 12's top.""" 102 ratio = 7.375 / 3.25 103 width = min(self.width() - 8.0, (self.height() - 8.0) * ratio) 104 card = QRectF((self.width() - width) / 2, (self.height() - width / ratio) / 2, width, width / ratio) 105 column_width = card.width() * 0.93 / COLUMNS 106 top = card.top() + card.height() * 0.13 # (room above row 12 for the printed characters) 107 row_height = card.height() * 0.78 / len(ROWS) # (and below row 9 for the fields' names) 108 return card, column_width, row_height, top 109 110 def paintEvent(self, _event): 111 painter = QPainter(self) 112 painter.setRenderHint(QPainter.Antialiasing) 113 card, column_width, row_height, top = self._layout() 114 left = card.left() + 0.035 * card.width() 115 # The card, its top-left corner cut (to see at once that a card in the deck is the wrong way round). 116 cut = card.height() * 0.12 117 outline = QPolygonF([QPointF(card.left() + cut, card.top()), card.topRight(), card.bottomRight(), 118 card.bottomLeft(), QPointF(card.left(), card.top() + cut)]) 119 painter.setPen(QPen(QColor(0, 0, 0, 60), 1)) 120 painter.setBrush(self.STOCK) 121 painter.drawPolygon(outline) 122 # Fortran's fields: ruled, and named under row 9 (the short name where the long one doesn't fit). 123 small = QFont("Courier New") 124 small.setStyleHint(QFont.TypeWriter) 125 small.setPixelSize(max(5, int(column_width * 0.62))) 126 painter.setFont(small) 127 bottom = top + len(ROWS) * row_height 128 for first, last, name, short in FIELDS: 129 x0, x1 = left + (first - 1) * column_width, left + last * column_width 130 painter.setPen(QPen(self.RULE, 1)) 131 if first > 1: 132 painter.drawLine(QPointF(x0, top - row_height * 0.2), QPointF(x0, bottom + row_height * 0.55)) 133 label = QRectF(x0, bottom + row_height * 0.05, x1 - x0, row_height * 0.5) 134 fits = [n for n in (name, short) if painter.fontMetrics().horizontalAdvance(n) <= label.width() + 2] 135 if fits: 136 painter.drawText(label, Qt.AlignCenter, fits[0]) 137 # Each row's digit printed in every column (rows 0–9), and the column numbers under row 0. 138 painter.setPen(self.PRINT) 139 for row in range(10): 140 y = top + ROWS.index(row) * row_height 141 for column in range(COLUMNS): 142 painter.drawText(QRectF(left + column * column_width, y, column_width, row_height), 143 Qt.AlignCenter, str(row)) 144 tiny = QFont(small) 145 tiny.setPixelSize(max(4, int(column_width * 0.42))) 146 painter.setFont(tiny) 147 for row in (0,): # (under row 0: under row 9, the fields' names) 148 y = top + (ROWS.index(row) + 0.72) * row_height 149 for column in range(COLUMNS): 150 painter.drawText(QRectF(left + column * column_width, y, column_width, row_height * 0.4), 151 Qt.AlignCenter, str(column + 1)) 152 # The holes: the dark behind the card, through it. 153 painter.setPen(Qt.NoPen) 154 painter.setBrush(self.INK) 155 for _column, _row, rect in self.hole_rects(): 156 painter.drawRect(rect) 157 # The characters, printed along the top edge as the 029 printed them. 158 typed = QFont(small) 159 typed.setPixelSize(max(6, int(column_width * 0.95))) 160 typed.setBold(True) 161 painter.setFont(typed) 162 painter.setPen(self.INK) 163 for i, ch in enumerate(self.text[:COLUMNS]): 164 if holes(ch): 165 painter.drawText(QRectF(left + i * column_width, card.top() + card.height() * 0.02, column_width, 166 card.height() * 0.09), Qt.AlignCenter, ch) 167 painter.end() 168 169 170class PunchCardDialog(QDialog): 171 """The card for a line of a file; ↑ and ↓ go through the file's lines.""" 172 173 def __init__(self, lines: list[str], index: int, name: str, parent=None): 174 super().__init__(parent) 175 self.setWindowTitle("Punch Card") 176 self.lines, self.index, self.name = lines, index, name 177 self.card = PunchCard() 178 self.caption = QLabel() 179 self.caption.setWordWrap(True) 180 self.caption.setTextFormat(Qt.RichText) 181 buttons = QDialogButtonBox(QDialogButtonBox.Close) 182 buttons.rejected.connect(self.reject) 183 layout = QVBoxLayout(self) 184 layout.addWidget(self.card, 1) 185 layout.addWidget(self.caption) 186 layout.addWidget(buttons) 187 self.resize(1000, 520) 188 self.show_line(index) 189 190 def show_line(self, index: int): 191 self.index = max(0, min(index, len(self.lines) - 1)) if self.lines else 0 192 line = self.lines[self.index] if self.lines else "" 193 punched, unpunchable, left_off = card_text(line) 194 self.card.set_text(punched) 195 notes = [(f"Line {self.index + 1} of {self.name}, as an IBM 029 keypunch would have punched it " 196 "(1964: capital letters only; ↑ ↓ for the other lines).")] 197 if unpunchable: 198 notes.append(f"Not on an 029's keyboard, left blank: {' '.join(unpunchable)}") 199 if left_off: 200 notes.append(f"Past column 80, on the next card: {len(left_off)} more characters") 201 self.caption.setText("<br>".join(f"<span style='color:gray'>{_escape(n)}</span>" for n in notes)) 202 203 def keyPressEvent(self, event): 204 if event.key() in (Qt.Key_Up, Qt.Key_Down): 205 self.show_line(self.index + (1 if event.key() == Qt.Key_Down else -1)) 206 return 207 super().keyPressEvent(event) 208 209 210def _escape(text: str) -> str: 211 return text.replace("&", "&").replace("<", "<").replace(">", ">")
43def holes(ch: str) -> tuple[int, ...] | None: 44 """The rows an IBM 029 punches for a character (upper case: it had no 45 lower case); None: it can't punch it.""" 46 ch = ch.upper() 47 if ch.isdigit() and ch.isascii(): 48 return (int(ch),) 49 if "A" <= ch <= "I": 50 return 12, ord(ch) - ord("A") + 1 51 if "J" <= ch <= "R": 52 return 11, ord(ch) - ord("J") + 1 53 if "S" <= ch <= "Z": 54 return 0, ord(ch) - ord("S") + 2 55 return _SPECIAL.get(ch)
The rows an IBM 029 punches for a character (upper case: it had no lower case); None: it can't punch it.
58def card_text(line: str) -> tuple[str, list[str], str]: 59 """A line as punched: its first 80 columns (tabs as spaces, upper case), 60 the characters an 029 couldn't punch (left blank), and what didn't fit.""" 61 expanded = "" 62 for ch in line: 63 expanded += " " * (TAB - len(expanded) % TAB) if ch == "\t" else ch 64 punched, left_off = expanded[:COLUMNS], expanded[COLUMNS:] 65 unpunchable = sorted({ch for ch in punched if holes(ch) is None}) 66 return punched.upper(), unpunchable, left_off
A line as punched: its first 80 columns (tabs as spaces, upper case), the characters an 029 couldn't punch (left blank), and what didn't fit.
69class PunchCard(QWidget): 70 """The card: cream card stock, the cut corner, each row's digits printed 71 faintly, the holes, the characters printed along the top edge (as the 72 029 did), Fortran's fields ruled and named, and the column numbers.""" 73 74 STOCK = QColor("#efe4c2") 75 INK = QColor("#2f2a22") 76 PRINT = QColor(160, 120, 70, 150) # (the card's own printing: faint) 77 RULE = QColor(170, 60, 50, 160) # (Fortran's fields) 78 79 def __init__(self, text: str = "", parent=None): 80 super().__init__(parent) 81 self.text = text 82 self.setMinimumSize(760, 340) 83 self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) 84 85 def set_text(self, text: str): 86 self.text = text 87 self.update() 88 89 def hole_rects(self) -> list[tuple[int, int, QRectF]]: 90 """Each hole: (column (1-based), row, where it's drawn).""" 91 card, column_width, row_height, top = self._layout() 92 out = [] 93 for i, ch in enumerate(self.text[:COLUMNS]): 94 for row in holes(ch) or (): 95 x = card.left() + 0.035 * card.width() + i * column_width 96 y = top + ROWS.index(row) * row_height 97 out.append((i + 1, row, QRectF(x + column_width * 0.22, y + row_height * 0.18, 98 column_width * 0.56, row_height * 0.64))) 99 return out 100 101 def _layout(self) -> tuple[QRectF, float, float, float]: 102 """The card (7⅜ × 3¼ inches), a column's width, a row's height, row 12's top.""" 103 ratio = 7.375 / 3.25 104 width = min(self.width() - 8.0, (self.height() - 8.0) * ratio) 105 card = QRectF((self.width() - width) / 2, (self.height() - width / ratio) / 2, width, width / ratio) 106 column_width = card.width() * 0.93 / COLUMNS 107 top = card.top() + card.height() * 0.13 # (room above row 12 for the printed characters) 108 row_height = card.height() * 0.78 / len(ROWS) # (and below row 9 for the fields' names) 109 return card, column_width, row_height, top 110 111 def paintEvent(self, _event): 112 painter = QPainter(self) 113 painter.setRenderHint(QPainter.Antialiasing) 114 card, column_width, row_height, top = self._layout() 115 left = card.left() + 0.035 * card.width() 116 # The card, its top-left corner cut (to see at once that a card in the deck is the wrong way round). 117 cut = card.height() * 0.12 118 outline = QPolygonF([QPointF(card.left() + cut, card.top()), card.topRight(), card.bottomRight(), 119 card.bottomLeft(), QPointF(card.left(), card.top() + cut)]) 120 painter.setPen(QPen(QColor(0, 0, 0, 60), 1)) 121 painter.setBrush(self.STOCK) 122 painter.drawPolygon(outline) 123 # Fortran's fields: ruled, and named under row 9 (the short name where the long one doesn't fit). 124 small = QFont("Courier New") 125 small.setStyleHint(QFont.TypeWriter) 126 small.setPixelSize(max(5, int(column_width * 0.62))) 127 painter.setFont(small) 128 bottom = top + len(ROWS) * row_height 129 for first, last, name, short in FIELDS: 130 x0, x1 = left + (first - 1) * column_width, left + last * column_width 131 painter.setPen(QPen(self.RULE, 1)) 132 if first > 1: 133 painter.drawLine(QPointF(x0, top - row_height * 0.2), QPointF(x0, bottom + row_height * 0.55)) 134 label = QRectF(x0, bottom + row_height * 0.05, x1 - x0, row_height * 0.5) 135 fits = [n for n in (name, short) if painter.fontMetrics().horizontalAdvance(n) <= label.width() + 2] 136 if fits: 137 painter.drawText(label, Qt.AlignCenter, fits[0]) 138 # Each row's digit printed in every column (rows 0–9), and the column numbers under row 0. 139 painter.setPen(self.PRINT) 140 for row in range(10): 141 y = top + ROWS.index(row) * row_height 142 for column in range(COLUMNS): 143 painter.drawText(QRectF(left + column * column_width, y, column_width, row_height), 144 Qt.AlignCenter, str(row)) 145 tiny = QFont(small) 146 tiny.setPixelSize(max(4, int(column_width * 0.42))) 147 painter.setFont(tiny) 148 for row in (0,): # (under row 0: under row 9, the fields' names) 149 y = top + (ROWS.index(row) + 0.72) * row_height 150 for column in range(COLUMNS): 151 painter.drawText(QRectF(left + column * column_width, y, column_width, row_height * 0.4), 152 Qt.AlignCenter, str(column + 1)) 153 # The holes: the dark behind the card, through it. 154 painter.setPen(Qt.NoPen) 155 painter.setBrush(self.INK) 156 for _column, _row, rect in self.hole_rects(): 157 painter.drawRect(rect) 158 # The characters, printed along the top edge as the 029 printed them. 159 typed = QFont(small) 160 typed.setPixelSize(max(6, int(column_width * 0.95))) 161 typed.setBold(True) 162 painter.setFont(typed) 163 painter.setPen(self.INK) 164 for i, ch in enumerate(self.text[:COLUMNS]): 165 if holes(ch): 166 painter.drawText(QRectF(left + i * column_width, card.top() + card.height() * 0.02, column_width, 167 card.height() * 0.09), Qt.AlignCenter, ch) 168 painter.end()
The card: cream card stock, the cut corner, each row's digits printed faintly, the holes, the characters printed along the top edge (as the 029 did), Fortran's fields ruled and named, and the column numbers.
79 def __init__(self, text: str = "", parent=None): 80 super().__init__(parent) 81 self.text = text 82 self.setMinimumSize(760, 340) 83 self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
__init__(self, /, parent: PySide6.QtWidgets.QWidget | None = None, f: PySide6.QtCore.Qt.WindowType = Default(Qt.WindowFlags), *, modal: bool | None = None, windowModality: PySide6.QtCore.Qt.WindowModality | None = None, enabled: bool | None = None, geometry: PySide6.QtCore.QRect | None = None, frameGeometry: PySide6.QtCore.QRect | None = None, normalGeometry: PySide6.QtCore.QRect | None = None, x: int | None = None, y: int | None = None, pos: PySide6.QtCore.QPoint | None = None, frameSize: PySide6.QtCore.QSize | None = None, size: PySide6.QtCore.QSize | None = None, width: int | None = None, height: int | None = None, rect: PySide6.QtCore.QRect | None = None, childrenRect: PySide6.QtCore.QRect | None = None, childrenRegion: PySide6.QtGui.QRegion | None = None, sizePolicy: PySide6.QtWidgets.QSizePolicy | None = None, minimumSize: PySide6.QtCore.QSize | None = None, maximumSize: PySide6.QtCore.QSize | None = None, minimumWidth: int | None = None, minimumHeight: int | None = None, maximumWidth: int | None = None, maximumHeight: int | None = None, sizeIncrement: PySide6.QtCore.QSize | None = None, baseSize: PySide6.QtCore.QSize | None = None, palette: PySide6.QtGui.QPalette | None = None, font: PySide6.QtGui.QFont | None = None, cursor: PySide6.QtGui.QCursor | None = None, mouseTracking: bool | None = None, tabletTracking: bool | None = None, isActiveWindow: bool | None = None, focusPolicy: PySide6.QtCore.Qt.FocusPolicy | None = None, focus: bool | None = None, contextMenuPolicy: PySide6.QtCore.Qt.ContextMenuPolicy | None = None, updatesEnabled: bool | None = None, visible: bool | None = None, minimized: bool | None = None, maximized: bool | None = None, fullScreen: bool | None = None, sizeHint: PySide6.QtCore.QSize | None = None, minimumSizeHint: PySide6.QtCore.QSize | None = None, acceptDrops: bool | None = None, windowTitle: str | None = None, windowIcon: PySide6.QtGui.QIcon | None = None, windowIconText: str | None = None, windowOpacity: float | None = None, windowModified: bool | None = None, toolTip: str | None = None, toolTipDuration: int | None = None, statusTip: str | None = None, whatsThis: str | None = None, accessibleName: str | None = None, accessibleDescription: str | None = None, accessibleIdentifier: str | None = None, layoutDirection: PySide6.QtCore.Qt.LayoutDirection | None = None, autoFillBackground: bool | None = None, styleSheet: str | None = None, locale: PySide6.QtCore.QLocale | None = None, windowFilePath: str | None = None, inputMethodHints: PySide6.QtCore.Qt.InputMethodHint | None = None) -> None
Initialize self. See help(type(self)) for accurate signature.
89 def hole_rects(self) -> list[tuple[int, int, QRectF]]: 90 """Each hole: (column (1-based), row, where it's drawn).""" 91 card, column_width, row_height, top = self._layout() 92 out = [] 93 for i, ch in enumerate(self.text[:COLUMNS]): 94 for row in holes(ch) or (): 95 x = card.left() + 0.035 * card.width() + i * column_width 96 y = top + ROWS.index(row) * row_height 97 out.append((i + 1, row, QRectF(x + column_width * 0.22, y + row_height * 0.18, 98 column_width * 0.56, row_height * 0.64))) 99 return out
Each hole: (column (1-based), row, where it's drawn).
111 def paintEvent(self, _event): 112 painter = QPainter(self) 113 painter.setRenderHint(QPainter.Antialiasing) 114 card, column_width, row_height, top = self._layout() 115 left = card.left() + 0.035 * card.width() 116 # The card, its top-left corner cut (to see at once that a card in the deck is the wrong way round). 117 cut = card.height() * 0.12 118 outline = QPolygonF([QPointF(card.left() + cut, card.top()), card.topRight(), card.bottomRight(), 119 card.bottomLeft(), QPointF(card.left(), card.top() + cut)]) 120 painter.setPen(QPen(QColor(0, 0, 0, 60), 1)) 121 painter.setBrush(self.STOCK) 122 painter.drawPolygon(outline) 123 # Fortran's fields: ruled, and named under row 9 (the short name where the long one doesn't fit). 124 small = QFont("Courier New") 125 small.setStyleHint(QFont.TypeWriter) 126 small.setPixelSize(max(5, int(column_width * 0.62))) 127 painter.setFont(small) 128 bottom = top + len(ROWS) * row_height 129 for first, last, name, short in FIELDS: 130 x0, x1 = left + (first - 1) * column_width, left + last * column_width 131 painter.setPen(QPen(self.RULE, 1)) 132 if first > 1: 133 painter.drawLine(QPointF(x0, top - row_height * 0.2), QPointF(x0, bottom + row_height * 0.55)) 134 label = QRectF(x0, bottom + row_height * 0.05, x1 - x0, row_height * 0.5) 135 fits = [n for n in (name, short) if painter.fontMetrics().horizontalAdvance(n) <= label.width() + 2] 136 if fits: 137 painter.drawText(label, Qt.AlignCenter, fits[0]) 138 # Each row's digit printed in every column (rows 0–9), and the column numbers under row 0. 139 painter.setPen(self.PRINT) 140 for row in range(10): 141 y = top + ROWS.index(row) * row_height 142 for column in range(COLUMNS): 143 painter.drawText(QRectF(left + column * column_width, y, column_width, row_height), 144 Qt.AlignCenter, str(row)) 145 tiny = QFont(small) 146 tiny.setPixelSize(max(4, int(column_width * 0.42))) 147 painter.setFont(tiny) 148 for row in (0,): # (under row 0: under row 9, the fields' names) 149 y = top + (ROWS.index(row) + 0.72) * row_height 150 for column in range(COLUMNS): 151 painter.drawText(QRectF(left + column * column_width, y, column_width, row_height * 0.4), 152 Qt.AlignCenter, str(column + 1)) 153 # The holes: the dark behind the card, through it. 154 painter.setPen(Qt.NoPen) 155 painter.setBrush(self.INK) 156 for _column, _row, rect in self.hole_rects(): 157 painter.drawRect(rect) 158 # The characters, printed along the top edge as the 029 printed them. 159 typed = QFont(small) 160 typed.setPixelSize(max(6, int(column_width * 0.95))) 161 typed.setBold(True) 162 painter.setFont(typed) 163 painter.setPen(self.INK) 164 for i, ch in enumerate(self.text[:COLUMNS]): 165 if holes(ch): 166 painter.drawText(QRectF(left + i * column_width, card.top() + card.height() * 0.02, column_width, 167 card.height() * 0.09), Qt.AlignCenter, ch) 168 painter.end()
paintEvent(self, event: PySide6.QtGui.QPaintEvent, /) -> None
171class PunchCardDialog(QDialog): 172 """The card for a line of a file; ↑ and ↓ go through the file's lines.""" 173 174 def __init__(self, lines: list[str], index: int, name: str, parent=None): 175 super().__init__(parent) 176 self.setWindowTitle("Punch Card") 177 self.lines, self.index, self.name = lines, index, name 178 self.card = PunchCard() 179 self.caption = QLabel() 180 self.caption.setWordWrap(True) 181 self.caption.setTextFormat(Qt.RichText) 182 buttons = QDialogButtonBox(QDialogButtonBox.Close) 183 buttons.rejected.connect(self.reject) 184 layout = QVBoxLayout(self) 185 layout.addWidget(self.card, 1) 186 layout.addWidget(self.caption) 187 layout.addWidget(buttons) 188 self.resize(1000, 520) 189 self.show_line(index) 190 191 def show_line(self, index: int): 192 self.index = max(0, min(index, len(self.lines) - 1)) if self.lines else 0 193 line = self.lines[self.index] if self.lines else "" 194 punched, unpunchable, left_off = card_text(line) 195 self.card.set_text(punched) 196 notes = [(f"Line {self.index + 1} of {self.name}, as an IBM 029 keypunch would have punched it " 197 "(1964: capital letters only; ↑ ↓ for the other lines).")] 198 if unpunchable: 199 notes.append(f"Not on an 029's keyboard, left blank: {' '.join(unpunchable)}") 200 if left_off: 201 notes.append(f"Past column 80, on the next card: {len(left_off)} more characters") 202 self.caption.setText("<br>".join(f"<span style='color:gray'>{_escape(n)}</span>" for n in notes)) 203 204 def keyPressEvent(self, event): 205 if event.key() in (Qt.Key_Up, Qt.Key_Down): 206 self.show_line(self.index + (1 if event.key() == Qt.Key_Down else -1)) 207 return 208 super().keyPressEvent(event)
The card for a line of a file; ↑ and ↓ go through the file's lines.
174 def __init__(self, lines: list[str], index: int, name: str, parent=None): 175 super().__init__(parent) 176 self.setWindowTitle("Punch Card") 177 self.lines, self.index, self.name = lines, index, name 178 self.card = PunchCard() 179 self.caption = QLabel() 180 self.caption.setWordWrap(True) 181 self.caption.setTextFormat(Qt.RichText) 182 buttons = QDialogButtonBox(QDialogButtonBox.Close) 183 buttons.rejected.connect(self.reject) 184 layout = QVBoxLayout(self) 185 layout.addWidget(self.card, 1) 186 layout.addWidget(self.caption) 187 layout.addWidget(buttons) 188 self.resize(1000, 520) 189 self.show_line(index)
__init__(self, /, parent: PySide6.QtWidgets.QWidget | None = None, f: PySide6.QtCore.Qt.WindowType = Default(Qt.WindowFlags), *, sizeGripEnabled: bool | None = None, modal: bool | None = None) -> None
Initialize self. See help(type(self)) for accurate signature.
191 def show_line(self, index: int): 192 self.index = max(0, min(index, len(self.lines) - 1)) if self.lines else 0 193 line = self.lines[self.index] if self.lines else "" 194 punched, unpunchable, left_off = card_text(line) 195 self.card.set_text(punched) 196 notes = [(f"Line {self.index + 1} of {self.name}, as an IBM 029 keypunch would have punched it " 197 "(1964: capital letters only; ↑ ↓ for the other lines).")] 198 if unpunchable: 199 notes.append(f"Not on an 029's keyboard, left blank: {' '.join(unpunchable)}") 200 if left_off: 201 notes.append(f"Past column 80, on the next card: {len(left_off)} more characters") 202 self.caption.setText("<br>".join(f"<span style='color:gray'>{_escape(n)}</span>" for n in notes))