NotYAFC 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- font_converter/__init__.py +4 -0
- font_converter/__main__.py +45 -0
- font_converter/assets/ico.png +0 -0
- font_converter/assets/notyafc-1024.png +0 -0
- font_converter/assets/notyafc.ico +0 -0
- font_converter/assets/notyafc.svg +15 -0
- font_converter/cli.py +543 -0
- font_converter/constants.py +27 -0
- font_converter/contours.py +336 -0
- font_converter/css_kit.py +77 -0
- font_converter/finder.py +213 -0
- font_converter/font.py +530 -0
- font_converter/gui/__init__.py +1 -0
- font_converter/gui/conversion_dialog.py +110 -0
- font_converter/gui/file_list.py +411 -0
- font_converter/gui/instance_tree.py +583 -0
- font_converter/gui/logs.py +51 -0
- font_converter/gui/organization_dialog.py +417 -0
- font_converter/gui/profile_options.py +316 -0
- font_converter/gui/profiles.py +17 -0
- font_converter/gui/scanner.py +42 -0
- font_converter/gui/styles.py +658 -0
- font_converter/gui/widgets.py +495 -0
- font_converter/gui/window.py +1768 -0
- font_converter/inspection.py +169 -0
- font_converter/merge_curves.py +499 -0
- font_converter/otf_builder.py +144 -0
- font_converter/qu2cu.py +117 -0
- font_converter/service.py +729 -0
- font_converter/ttf_builder.py +107 -0
- notyafc-1.0.0.dist-info/METADATA +256 -0
- notyafc-1.0.0.dist-info/RECORD +34 -0
- notyafc-1.0.0.dist-info/WHEEL +4 -0
- notyafc-1.0.0.dist-info/entry_points.txt +6 -0
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
"""Reusable widgets for the NotYAFC window."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from PySide6.QtCore import QLocale, QSize, Qt, Signal
|
|
6
|
+
from PySide6.QtGui import (
|
|
7
|
+
QColor,
|
|
8
|
+
QDragEnterEvent,
|
|
9
|
+
QDragLeaveEvent,
|
|
10
|
+
QDragMoveEvent,
|
|
11
|
+
QDropEvent,
|
|
12
|
+
QIcon,
|
|
13
|
+
QKeyEvent,
|
|
14
|
+
QPainter,
|
|
15
|
+
QPainterPath,
|
|
16
|
+
QPaintEvent,
|
|
17
|
+
QPixmap,
|
|
18
|
+
QResizeEvent,
|
|
19
|
+
QValidator,
|
|
20
|
+
)
|
|
21
|
+
from PySide6.QtWidgets import (
|
|
22
|
+
QAbstractSpinBox,
|
|
23
|
+
QBoxLayout,
|
|
24
|
+
QButtonGroup,
|
|
25
|
+
QDoubleSpinBox,
|
|
26
|
+
QFrame,
|
|
27
|
+
QHBoxLayout,
|
|
28
|
+
QHeaderView,
|
|
29
|
+
QLabel,
|
|
30
|
+
QPushButton,
|
|
31
|
+
QRadioButton,
|
|
32
|
+
QSizePolicy,
|
|
33
|
+
QSpinBox,
|
|
34
|
+
QTableView,
|
|
35
|
+
QToolButton,
|
|
36
|
+
QVBoxLayout,
|
|
37
|
+
QWidget,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
from font_converter.gui.styles import DARK, LIGHT
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class ElidedLabel(QLabel):
|
|
44
|
+
"""Single-line label that preserves its full text and tooltip when space is limited."""
|
|
45
|
+
|
|
46
|
+
def setText(self, text: str) -> None:
|
|
47
|
+
super().setText(text)
|
|
48
|
+
self.setToolTip(text)
|
|
49
|
+
|
|
50
|
+
def display_text(self) -> str:
|
|
51
|
+
return self.fontMetrics().elidedText(
|
|
52
|
+
self.text(), Qt.TextElideMode.ElideRight, self.contentsRect().width()
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
def paintEvent(self, event: QPaintEvent) -> None:
|
|
56
|
+
painter = QPainter(self)
|
|
57
|
+
rect = self.contentsRect()
|
|
58
|
+
self.style().drawItemText(
|
|
59
|
+
painter,
|
|
60
|
+
rect,
|
|
61
|
+
int(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter),
|
|
62
|
+
self.palette(),
|
|
63
|
+
self.isEnabled(),
|
|
64
|
+
self.display_text(),
|
|
65
|
+
self.foregroundRole(),
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
COMMON_UPM_VALUES = (256, 512, 1000, 1024, 2000, 2048, 4096)
|
|
70
|
+
UPM_STEP_VALUES = (
|
|
71
|
+
16,
|
|
72
|
+
32,
|
|
73
|
+
64,
|
|
74
|
+
128,
|
|
75
|
+
256,
|
|
76
|
+
512,
|
|
77
|
+
1000,
|
|
78
|
+
1024,
|
|
79
|
+
2000,
|
|
80
|
+
2048,
|
|
81
|
+
4000,
|
|
82
|
+
4096,
|
|
83
|
+
8000,
|
|
84
|
+
8192,
|
|
85
|
+
16000,
|
|
86
|
+
16384,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _theme_icon(symbol: str, color: QColor) -> QIcon:
|
|
91
|
+
pixmap = QPixmap(24, 24)
|
|
92
|
+
pixmap.fill(Qt.GlobalColor.transparent)
|
|
93
|
+
painter = QPainter(pixmap)
|
|
94
|
+
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
|
|
95
|
+
painter.setPen(color)
|
|
96
|
+
painter.setBrush(color)
|
|
97
|
+
|
|
98
|
+
if symbol == "sun":
|
|
99
|
+
painter.drawEllipse(9, 9, 6, 6)
|
|
100
|
+
painter.setPen(Qt.PenStyle.NoPen)
|
|
101
|
+
for angle in range(0, 360, 45):
|
|
102
|
+
painter.save()
|
|
103
|
+
painter.translate(12, 12)
|
|
104
|
+
painter.rotate(angle)
|
|
105
|
+
painter.drawRoundedRect(-1, -11, 2, 4, 1, 1)
|
|
106
|
+
painter.restore()
|
|
107
|
+
else:
|
|
108
|
+
crescent = QPainterPath()
|
|
109
|
+
crescent.addEllipse(4, 3, 16, 16)
|
|
110
|
+
cutout = QPainterPath()
|
|
111
|
+
cutout.addEllipse(10, 1, 14, 14)
|
|
112
|
+
painter.setPen(Qt.PenStyle.NoPen)
|
|
113
|
+
painter.drawPath(crescent.subtracted(cutout))
|
|
114
|
+
|
|
115
|
+
painter.end()
|
|
116
|
+
return QIcon(pixmap)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class ThemeToggle(QToolButton):
|
|
120
|
+
themeChanged = Signal(bool)
|
|
121
|
+
|
|
122
|
+
def __init__(self) -> None:
|
|
123
|
+
super().__init__()
|
|
124
|
+
self.setObjectName("themeToggle")
|
|
125
|
+
self.setFixedSize(92, 44)
|
|
126
|
+
self.setIconSize(QSize(82, 36))
|
|
127
|
+
self.setCheckable(True)
|
|
128
|
+
self.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
|
|
129
|
+
self.setAccessibleName("Dark theme")
|
|
130
|
+
self.toggled.connect(self._update_appearance)
|
|
131
|
+
self.clicked.connect(lambda checked: self.themeChanged.emit(checked))
|
|
132
|
+
self.set_dark_theme(False)
|
|
133
|
+
|
|
134
|
+
def set_dark_theme(self, enabled: bool) -> None:
|
|
135
|
+
self.setChecked(enabled)
|
|
136
|
+
self._update_appearance(enabled)
|
|
137
|
+
|
|
138
|
+
def _update_appearance(self, enabled: bool) -> None:
|
|
139
|
+
palette = DARK if enabled else LIGHT
|
|
140
|
+
current = "dark" if enabled else "light"
|
|
141
|
+
target = "light" if enabled else "dark"
|
|
142
|
+
self.setAccessibleDescription(f"Current theme: {current}. Activate to use {target} theme.")
|
|
143
|
+
self.setToolTip(f"Current theme: {current}. Switch to {target} theme.")
|
|
144
|
+
|
|
145
|
+
pixmap = QPixmap(82, 36)
|
|
146
|
+
pixmap.fill(Qt.GlobalColor.transparent)
|
|
147
|
+
painter = QPainter(pixmap)
|
|
148
|
+
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
|
|
149
|
+
painter.setPen(Qt.PenStyle.NoPen)
|
|
150
|
+
painter.setBrush(QColor(palette.toggle_checked))
|
|
151
|
+
painter.drawRoundedRect(44 if enabled else 2, 0, 36, 36, 18, 18)
|
|
152
|
+
for x, symbol, active in ((8, "sun", not enabled), (50, "moon", enabled)):
|
|
153
|
+
color = palette.icon_active if active else palette.icon_inactive
|
|
154
|
+
painter.drawPixmap(x, 6, _theme_icon(symbol, QColor(color)).pixmap(24, 24))
|
|
155
|
+
painter.end()
|
|
156
|
+
self.setIcon(QIcon(pixmap))
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class KeyboardPushButton(QPushButton):
|
|
160
|
+
"""Push button that activates on Return/Enter as well as Space."""
|
|
161
|
+
|
|
162
|
+
def keyPressEvent(self, event: QKeyEvent) -> None:
|
|
163
|
+
if event.key() in (Qt.Key.Key_Return, Qt.Key.Key_Enter):
|
|
164
|
+
if not event.isAutoRepeat():
|
|
165
|
+
self.click()
|
|
166
|
+
event.accept()
|
|
167
|
+
return
|
|
168
|
+
super().keyPressEvent(event)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class DecimalSpinBox(QDoubleSpinBox):
|
|
172
|
+
"""Double spin box that accepts both '.' and ',' as decimal separator."""
|
|
173
|
+
|
|
174
|
+
def __init__(self) -> None:
|
|
175
|
+
super().__init__()
|
|
176
|
+
self.setLocale(QLocale.c())
|
|
177
|
+
|
|
178
|
+
def validate(self, text: str, pos: int) -> tuple[QValidator.State, str, int]:
|
|
179
|
+
return super().validate(text.replace(",", "."), pos)
|
|
180
|
+
|
|
181
|
+
def valueFromText(self, text: str) -> float:
|
|
182
|
+
return super().valueFromText(text.replace(",", "."))
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class UpmSpinBox(QSpinBox):
|
|
186
|
+
"""Spin box whose steps jump between supported units-per-em values."""
|
|
187
|
+
|
|
188
|
+
def __init__(self) -> None:
|
|
189
|
+
super().__init__()
|
|
190
|
+
self.setRange(16, 16384)
|
|
191
|
+
self.setValue(1000)
|
|
192
|
+
|
|
193
|
+
def stepBy(self, steps: int) -> None:
|
|
194
|
+
value = self.value()
|
|
195
|
+
for _ in range(abs(steps)):
|
|
196
|
+
if steps > 0:
|
|
197
|
+
value = next((v for v in UPM_STEP_VALUES if v > value), self.maximum())
|
|
198
|
+
else:
|
|
199
|
+
value = next((v for v in reversed(UPM_STEP_VALUES) if v < value), self.minimum())
|
|
200
|
+
self.setValue(value)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
class StepperField(QWidget):
|
|
204
|
+
"""Spin box flanked by app-styled − / + buttons instead of native arrows."""
|
|
205
|
+
|
|
206
|
+
def __init__(self, spin_box: QAbstractSpinBox, *, tab_buttons: bool = False) -> None:
|
|
207
|
+
super().__init__()
|
|
208
|
+
self.spin_box = spin_box
|
|
209
|
+
spin_box.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
210
|
+
layout = QHBoxLayout(self)
|
|
211
|
+
layout.setContentsMargins(0, 0, 0, 0)
|
|
212
|
+
layout.setSpacing(6)
|
|
213
|
+
self.decrement_button = KeyboardPushButton("−")
|
|
214
|
+
self.increment_button = KeyboardPushButton("+")
|
|
215
|
+
for button, direction in ((self.decrement_button, -1), (self.increment_button, 1)):
|
|
216
|
+
button.setObjectName("stepperButton")
|
|
217
|
+
button.setAutoRepeat(True)
|
|
218
|
+
if not tab_buttons:
|
|
219
|
+
button.setFocusPolicy(Qt.FocusPolicy.NoFocus)
|
|
220
|
+
button.clicked.connect(lambda _=False, step=direction: spin_box.stepBy(step))
|
|
221
|
+
layout.addWidget(self.decrement_button)
|
|
222
|
+
layout.addWidget(spin_box, 1)
|
|
223
|
+
layout.addWidget(self.increment_button)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
class ConversionSelector(QWidget):
|
|
227
|
+
currentIndexChanged = Signal(int)
|
|
228
|
+
|
|
229
|
+
def __init__(
|
|
230
|
+
self,
|
|
231
|
+
conversions: dict[str, str],
|
|
232
|
+
*,
|
|
233
|
+
select_first: bool = True,
|
|
234
|
+
group_headings: dict[str, str] | None = None,
|
|
235
|
+
) -> None:
|
|
236
|
+
super().__init__()
|
|
237
|
+
self._keys = list(conversions)
|
|
238
|
+
self._labels = list(conversions.values())
|
|
239
|
+
self._group = QButtonGroup(self)
|
|
240
|
+
layout = QVBoxLayout(self)
|
|
241
|
+
layout.setContentsMargins(0, 0, 0, 0)
|
|
242
|
+
layout.setSpacing(8)
|
|
243
|
+
for index, (key, label) in enumerate(conversions.items()):
|
|
244
|
+
if group_headings is not None and key in group_headings:
|
|
245
|
+
if index:
|
|
246
|
+
layout.addSpacing(3)
|
|
247
|
+
heading = QLabel(group_headings[key])
|
|
248
|
+
heading.setObjectName("conversionGroupHeading")
|
|
249
|
+
layout.addWidget(heading)
|
|
250
|
+
button = QRadioButton(label)
|
|
251
|
+
self._group.addButton(button, index)
|
|
252
|
+
layout.addWidget(button)
|
|
253
|
+
layout.addStretch()
|
|
254
|
+
if select_first:
|
|
255
|
+
self._group.button(0).setChecked(True)
|
|
256
|
+
self._group.idClicked.connect(self.currentIndexChanged)
|
|
257
|
+
|
|
258
|
+
def currentIndex(self) -> int:
|
|
259
|
+
return self._group.checkedId()
|
|
260
|
+
|
|
261
|
+
def currentData(self) -> str | None:
|
|
262
|
+
index = self.currentIndex()
|
|
263
|
+
return self._keys[index] if index >= 0 else None
|
|
264
|
+
|
|
265
|
+
def findData(self, key: str) -> int:
|
|
266
|
+
return self._keys.index(key) if key in self._keys else -1
|
|
267
|
+
|
|
268
|
+
def setCurrentIndex(self, index: int) -> None:
|
|
269
|
+
button = self._group.button(index)
|
|
270
|
+
if button is not None and not button.isChecked():
|
|
271
|
+
button.setChecked(True)
|
|
272
|
+
self.currentIndexChanged.emit(index)
|
|
273
|
+
|
|
274
|
+
def set_compatibility_counts(self, counts: dict[str, int] | None, total: int) -> None:
|
|
275
|
+
for index, (key, label) in enumerate(zip(self._keys, self._labels, strict=True)):
|
|
276
|
+
button = self._group.button(index)
|
|
277
|
+
if button is not None:
|
|
278
|
+
button.setText(
|
|
279
|
+
f"{label} · {counts[key]}/{total} compatible" if counts is not None else label
|
|
280
|
+
)
|
|
281
|
+
layout = self.layout()
|
|
282
|
+
if layout is not None:
|
|
283
|
+
layout.invalidate()
|
|
284
|
+
self.updateGeometry()
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def accept_local_files(event: QDragEnterEvent | QDragMoveEvent) -> None:
|
|
288
|
+
if event.mimeData().hasUrls() and any(url.isLocalFile() for url in event.mimeData().urls()):
|
|
289
|
+
event.acceptProposedAction()
|
|
290
|
+
else:
|
|
291
|
+
event.ignore()
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def dropped_paths(event: QDropEvent) -> list[str]:
|
|
295
|
+
return [
|
|
296
|
+
url.toLocalFile()
|
|
297
|
+
for url in event.mimeData().urls()
|
|
298
|
+
if url.isLocalFile()
|
|
299
|
+
and (Path(url.toLocalFile()).is_file() or Path(url.toLocalFile()).is_dir())
|
|
300
|
+
]
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def set_drag_over(widget: QWidget, enabled: bool) -> None:
|
|
304
|
+
value = "true" if enabled else "false"
|
|
305
|
+
if widget.property("dragOver") == value:
|
|
306
|
+
return
|
|
307
|
+
widget.setProperty("dragOver", value)
|
|
308
|
+
widget.style().unpolish(widget)
|
|
309
|
+
widget.style().polish(widget)
|
|
310
|
+
widget.update()
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
class DropIcon(QWidget):
|
|
314
|
+
def __init__(self) -> None:
|
|
315
|
+
super().__init__()
|
|
316
|
+
self.setFixedSize(32, 32)
|
|
317
|
+
self._color = QColor(LIGHT.text_muted)
|
|
318
|
+
|
|
319
|
+
def set_dark_theme(self, enabled: bool) -> None:
|
|
320
|
+
self._color = QColor((DARK if enabled else LIGHT).text_muted)
|
|
321
|
+
self.update()
|
|
322
|
+
|
|
323
|
+
def paintEvent(self, event: QPaintEvent) -> None:
|
|
324
|
+
painter = QPainter(self)
|
|
325
|
+
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
|
|
326
|
+
painter.setPen(self._color)
|
|
327
|
+
pen = painter.pen()
|
|
328
|
+
pen.setWidth(2)
|
|
329
|
+
pen.setCapStyle(Qt.PenCapStyle.RoundCap)
|
|
330
|
+
pen.setJoinStyle(Qt.PenJoinStyle.RoundJoin)
|
|
331
|
+
painter.setPen(pen)
|
|
332
|
+
painter.drawLine(16, 3, 16, 20)
|
|
333
|
+
painter.drawLine(10, 14, 16, 20)
|
|
334
|
+
painter.drawLine(16, 20, 22, 14)
|
|
335
|
+
painter.drawLine(6, 21, 6, 27)
|
|
336
|
+
painter.drawLine(6, 27, 26, 27)
|
|
337
|
+
painter.drawLine(26, 27, 26, 21)
|
|
338
|
+
painter.end()
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
class DropZone(QFrame):
|
|
342
|
+
paths_dropped = Signal(list)
|
|
343
|
+
addFilesRequested = Signal()
|
|
344
|
+
addFolderRequested = Signal()
|
|
345
|
+
|
|
346
|
+
def __init__(self) -> None:
|
|
347
|
+
super().__init__()
|
|
348
|
+
self.setObjectName("dropZone")
|
|
349
|
+
self.setAccessibleName("Font drop area")
|
|
350
|
+
self.setAcceptDrops(True)
|
|
351
|
+
self.setSizePolicy(QSizePolicy.Policy.Ignored, QSizePolicy.Policy.Expanding)
|
|
352
|
+
self.setProperty("dragOver", "false")
|
|
353
|
+
self._scanning = False
|
|
354
|
+
layout = QVBoxLayout(self)
|
|
355
|
+
layout.setContentsMargins(12, 12, 12, 12)
|
|
356
|
+
layout.setSpacing(5)
|
|
357
|
+
layout.addStretch()
|
|
358
|
+
self.icon = DropIcon()
|
|
359
|
+
layout.addWidget(self.icon, 0, Qt.AlignmentFlag.AlignHCenter)
|
|
360
|
+
self.title = QLabel("Drop fonts or folders here")
|
|
361
|
+
self.title.setObjectName("dropZoneTitle")
|
|
362
|
+
self.title.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
363
|
+
self.title.setWordWrap(True)
|
|
364
|
+
self.title.setSizePolicy(QSizePolicy.Policy.Ignored, QSizePolicy.Policy.Preferred)
|
|
365
|
+
layout.addWidget(self.title)
|
|
366
|
+
self.formats = QLabel("TTF · OTF · WOFF · WOFF2 · TTC · OTC")
|
|
367
|
+
self.folder_hint = QLabel("Folders are scanned for fonts")
|
|
368
|
+
for label in (self.formats, self.folder_hint):
|
|
369
|
+
label.setObjectName("hint")
|
|
370
|
+
label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
371
|
+
label.setWordWrap(True)
|
|
372
|
+
label.setSizePolicy(QSizePolicy.Policy.Ignored, QSizePolicy.Policy.Preferred)
|
|
373
|
+
layout.addWidget(label)
|
|
374
|
+
self.buttons = QWidget()
|
|
375
|
+
self.buttons_layout = QBoxLayout(QBoxLayout.Direction.LeftToRight, self.buttons)
|
|
376
|
+
self.buttons_layout.setContentsMargins(0, 0, 0, 0)
|
|
377
|
+
self.buttons_layout.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
378
|
+
self.add_files_button = KeyboardPushButton("Add files…")
|
|
379
|
+
self.add_folder_button = KeyboardPushButton("Add folder…")
|
|
380
|
+
self.buttons_layout.addWidget(self.add_files_button)
|
|
381
|
+
self.buttons_layout.addWidget(self.add_folder_button)
|
|
382
|
+
self.add_files_button.clicked.connect(self.addFilesRequested.emit)
|
|
383
|
+
self.add_folder_button.clicked.connect(self.addFolderRequested.emit)
|
|
384
|
+
layout.addWidget(self.buttons)
|
|
385
|
+
layout.addStretch()
|
|
386
|
+
for widget in (self.formats, self.folder_hint, self.buttons):
|
|
387
|
+
policy = widget.sizePolicy()
|
|
388
|
+
policy.setRetainSizeWhenHidden(True)
|
|
389
|
+
widget.setSizePolicy(policy)
|
|
390
|
+
|
|
391
|
+
def set_dark_theme(self, enabled: bool) -> None:
|
|
392
|
+
self.icon.set_dark_theme(enabled)
|
|
393
|
+
|
|
394
|
+
def resizeEvent(self, event: QResizeEvent) -> None:
|
|
395
|
+
super().resizeEvent(event)
|
|
396
|
+
required = (
|
|
397
|
+
self.add_files_button.sizeHint().width()
|
|
398
|
+
+ self.add_folder_button.sizeHint().width()
|
|
399
|
+
+ self.buttons_layout.spacing()
|
|
400
|
+
)
|
|
401
|
+
self.buttons_layout.setDirection(
|
|
402
|
+
QBoxLayout.Direction.LeftToRight
|
|
403
|
+
if self.contentsRect().width() - 24 >= required
|
|
404
|
+
else QBoxLayout.Direction.TopToBottom
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
def set_scanning(self, scanning: bool) -> None:
|
|
408
|
+
self._scanning = scanning
|
|
409
|
+
self._update_state()
|
|
410
|
+
|
|
411
|
+
def _update_state(self) -> None:
|
|
412
|
+
dragging = self.property("dragOver") == "true"
|
|
413
|
+
self.title.setText(
|
|
414
|
+
"Release to add"
|
|
415
|
+
if dragging
|
|
416
|
+
else "Scanning…"
|
|
417
|
+
if self._scanning
|
|
418
|
+
else "Drop fonts or folders here"
|
|
419
|
+
)
|
|
420
|
+
for widget in (self.formats, self.folder_hint, self.buttons):
|
|
421
|
+
widget.setVisible(not (dragging or self._scanning))
|
|
422
|
+
|
|
423
|
+
def dragEnterEvent(self, event: QDragEnterEvent) -> None:
|
|
424
|
+
accept_local_files(event)
|
|
425
|
+
set_drag_over(self, event.isAccepted())
|
|
426
|
+
self._update_state()
|
|
427
|
+
|
|
428
|
+
def dragMoveEvent(self, event: QDragMoveEvent) -> None:
|
|
429
|
+
accept_local_files(event)
|
|
430
|
+
|
|
431
|
+
def dragLeaveEvent(self, event: QDragLeaveEvent) -> None:
|
|
432
|
+
set_drag_over(self, False)
|
|
433
|
+
self._update_state()
|
|
434
|
+
event.accept()
|
|
435
|
+
|
|
436
|
+
def dropEvent(self, event: QDropEvent) -> None:
|
|
437
|
+
set_drag_over(self, False)
|
|
438
|
+
self._update_state()
|
|
439
|
+
paths = dropped_paths(event)
|
|
440
|
+
if paths:
|
|
441
|
+
event.acceptProposedAction()
|
|
442
|
+
self.paths_dropped.emit(paths)
|
|
443
|
+
else:
|
|
444
|
+
event.ignore()
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
class FileTable(QTableView):
|
|
448
|
+
paths_dropped = Signal(list)
|
|
449
|
+
|
|
450
|
+
def __init__(self) -> None:
|
|
451
|
+
super().__init__()
|
|
452
|
+
self.setAcceptDrops(True)
|
|
453
|
+
self.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
|
|
454
|
+
self.setSelectionMode(QTableView.SelectionMode.ExtendedSelection)
|
|
455
|
+
self.setSelectionBehavior(QTableView.SelectionBehavior.SelectRows)
|
|
456
|
+
self.setTabKeyNavigation(False)
|
|
457
|
+
self.setSortingEnabled(True)
|
|
458
|
+
self.setAlternatingRowColors(True)
|
|
459
|
+
self.setProperty("dragOver", "false")
|
|
460
|
+
self.muted_color = QColor(LIGHT.text_disabled)
|
|
461
|
+
|
|
462
|
+
def set_dark_theme(self, enabled: bool) -> None:
|
|
463
|
+
palette = DARK if enabled else LIGHT
|
|
464
|
+
self.muted_color = QColor(palette.text_disabled)
|
|
465
|
+
self.viewport().update()
|
|
466
|
+
|
|
467
|
+
def configure_columns(self) -> None:
|
|
468
|
+
header = self.horizontalHeader()
|
|
469
|
+
header.setStretchLastSection(False)
|
|
470
|
+
header.setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch)
|
|
471
|
+
for column, width in ((1, 80), (2, 110), (3, 70), (4, 140), (5, 140)):
|
|
472
|
+
header.setSectionResizeMode(column, QHeaderView.ResizeMode.Interactive)
|
|
473
|
+
header.resizeSection(column, width)
|
|
474
|
+
for column in (4, 5):
|
|
475
|
+
self.setColumnHidden(column, True)
|
|
476
|
+
|
|
477
|
+
def dragEnterEvent(self, event: QDragEnterEvent) -> None:
|
|
478
|
+
accept_local_files(event)
|
|
479
|
+
set_drag_over(self, event.isAccepted())
|
|
480
|
+
|
|
481
|
+
def dragMoveEvent(self, event: QDragMoveEvent) -> None:
|
|
482
|
+
accept_local_files(event)
|
|
483
|
+
|
|
484
|
+
def dragLeaveEvent(self, event: QDragLeaveEvent) -> None:
|
|
485
|
+
set_drag_over(self, False)
|
|
486
|
+
event.accept()
|
|
487
|
+
|
|
488
|
+
def dropEvent(self, event: QDropEvent) -> None:
|
|
489
|
+
set_drag_over(self, False)
|
|
490
|
+
paths = dropped_paths(event)
|
|
491
|
+
if paths:
|
|
492
|
+
self.paths_dropped.emit(paths)
|
|
493
|
+
event.acceptProposedAction()
|
|
494
|
+
else:
|
|
495
|
+
event.ignore()
|