rgpdf 0.0.1__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.
- rgpdf/__init__.py +3 -0
- rgpdf/__main__.py +6 -0
- rgpdf/app.py +163 -0
- rgpdf/assets/__init__.py +2 -0
- rgpdf/assets/rgpdf.svg +11 -0
- rgpdf/i18n.py +114 -0
- rgpdf/main_window.py +754 -0
- rgpdf/models.py +69 -0
- rgpdf/preview.py +271 -0
- rgpdf/resources.py +11 -0
- rgpdf/search.py +275 -0
- rgpdf/worker.py +44 -0
- rgpdf-0.0.1.dist-info/METADATA +75 -0
- rgpdf-0.0.1.dist-info/RECORD +16 -0
- rgpdf-0.0.1.dist-info/WHEEL +4 -0
- rgpdf-0.0.1.dist-info/entry_points.txt +3 -0
rgpdf/__init__.py
ADDED
rgpdf/__main__.py
ADDED
rgpdf/app.py
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
from PySide6.QtCore import QSettings, Qt
|
|
6
|
+
from PySide6.QtGui import QColor, QPalette
|
|
7
|
+
from PySide6.QtWidgets import QApplication
|
|
8
|
+
|
|
9
|
+
from rgpdf.main_window import MainWindow
|
|
10
|
+
from rgpdf.resources import application_icon
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
BASE_STYLE = """
|
|
14
|
+
QWidget { font-size: 13px; color: palette(window-text); }
|
|
15
|
+
QMainWindow, QWidget#centralWidget, QMenuBar, QMenu { background: palette(window); }
|
|
16
|
+
QToolBar#settingsToolbar {
|
|
17
|
+
background: palette(alternate-base);
|
|
18
|
+
border: 0;
|
|
19
|
+
border-bottom: 1px solid palette(mid);
|
|
20
|
+
padding: 2px 7px;
|
|
21
|
+
spacing: 1px;
|
|
22
|
+
}
|
|
23
|
+
QToolBar#settingsToolbar::separator {
|
|
24
|
+
background: palette(mid);
|
|
25
|
+
width: 1px;
|
|
26
|
+
margin: 7px 6px;
|
|
27
|
+
}
|
|
28
|
+
QLineEdit, QTreeWidget, QScrollArea {
|
|
29
|
+
border: 1px solid palette(mid);
|
|
30
|
+
border-radius: 5px;
|
|
31
|
+
padding: 5px;
|
|
32
|
+
background: palette(base);
|
|
33
|
+
}
|
|
34
|
+
QToolButton {
|
|
35
|
+
border: 0;
|
|
36
|
+
border-radius: 8px;
|
|
37
|
+
background: transparent;
|
|
38
|
+
}
|
|
39
|
+
QToolButton:hover { background: palette(midlight); }
|
|
40
|
+
QToolButton:pressed { background: palette(mid); }
|
|
41
|
+
QToolButton:checked {
|
|
42
|
+
background: palette(midlight);
|
|
43
|
+
border: 1px solid palette(mid);
|
|
44
|
+
}
|
|
45
|
+
QPushButton {
|
|
46
|
+
border: 0;
|
|
47
|
+
border-radius: 7px;
|
|
48
|
+
padding: 6px 12px;
|
|
49
|
+
background: palette(button);
|
|
50
|
+
}
|
|
51
|
+
QPushButton:hover { background: palette(midlight); }
|
|
52
|
+
QPushButton:pressed { background: palette(midlight); }
|
|
53
|
+
QPushButton:disabled { color: palette(mid); }
|
|
54
|
+
QGroupBox {
|
|
55
|
+
border: 1px solid palette(mid);
|
|
56
|
+
border-radius: 7px;
|
|
57
|
+
margin-top: 10px;
|
|
58
|
+
padding-top: 7px;
|
|
59
|
+
font-weight: 600;
|
|
60
|
+
}
|
|
61
|
+
QGroupBox::title { subcontrol-origin: margin; left: 9px; padding: 0 4px; }
|
|
62
|
+
QFrame#highlightPanel {
|
|
63
|
+
background: palette(alternate-base);
|
|
64
|
+
border: 1px solid palette(mid);
|
|
65
|
+
border-radius: 6px;
|
|
66
|
+
}
|
|
67
|
+
QFrame#searchArea {
|
|
68
|
+
background: palette(alternate-base);
|
|
69
|
+
border: 1px solid palette(mid);
|
|
70
|
+
border-radius: 9px;
|
|
71
|
+
}
|
|
72
|
+
QHeaderView::section {
|
|
73
|
+
background: palette(alternate-base);
|
|
74
|
+
color: palette(text);
|
|
75
|
+
border: 0;
|
|
76
|
+
border-bottom: 1px solid palette(mid);
|
|
77
|
+
padding: 5px 7px;
|
|
78
|
+
font-weight: 600;
|
|
79
|
+
}
|
|
80
|
+
QProgressBar { border: 0; border-radius: 3px; text-align: center; min-width: 180px; }
|
|
81
|
+
QProgressBar::chunk { background: palette(highlight); border-radius: 3px; }
|
|
82
|
+
QSplitter::handle { background: transparent; }
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _light_palette(app: QApplication) -> QPalette:
|
|
87
|
+
palette = app.style().standardPalette()
|
|
88
|
+
palette.setColor(QPalette.ColorRole.Window, QColor("#f5f6f8"))
|
|
89
|
+
palette.setColor(QPalette.ColorRole.Base, QColor("#ffffff"))
|
|
90
|
+
palette.setColor(QPalette.ColorRole.AlternateBase, QColor("#eef0f3"))
|
|
91
|
+
palette.setColor(QPalette.ColorRole.Button, QColor("#ffffff"))
|
|
92
|
+
palette.setColor(QPalette.ColorRole.ButtonText, QColor("#475569"))
|
|
93
|
+
palette.setColor(QPalette.ColorRole.Mid, QColor("#c8ced8"))
|
|
94
|
+
palette.setColor(QPalette.ColorRole.Midlight, QColor("#e2e6ed"))
|
|
95
|
+
palette.setColor(QPalette.ColorRole.PlaceholderText, QColor("#718096"))
|
|
96
|
+
palette.setColor(QPalette.ColorRole.Highlight, QColor("#3578e5"))
|
|
97
|
+
palette.setColor(QPalette.ColorRole.HighlightedText, QColor("#ffffff"))
|
|
98
|
+
return palette
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _dark_palette() -> QPalette:
|
|
102
|
+
palette = QPalette()
|
|
103
|
+
palette.setColor(QPalette.ColorRole.Window, QColor("#202124"))
|
|
104
|
+
palette.setColor(QPalette.ColorRole.WindowText, QColor("#eceff4"))
|
|
105
|
+
palette.setColor(QPalette.ColorRole.Base, QColor("#292b2f"))
|
|
106
|
+
palette.setColor(QPalette.ColorRole.AlternateBase, QColor("#32353a"))
|
|
107
|
+
palette.setColor(QPalette.ColorRole.ToolTipBase, QColor("#292b2f"))
|
|
108
|
+
palette.setColor(QPalette.ColorRole.ToolTipText, QColor("#eceff4"))
|
|
109
|
+
palette.setColor(QPalette.ColorRole.Text, QColor("#eceff4"))
|
|
110
|
+
palette.setColor(QPalette.ColorRole.Button, QColor("#303236"))
|
|
111
|
+
palette.setColor(QPalette.ColorRole.ButtonText, QColor("#eceff4"))
|
|
112
|
+
palette.setColor(QPalette.ColorRole.BrightText, QColor("#ffffff"))
|
|
113
|
+
palette.setColor(QPalette.ColorRole.Link, QColor("#78a9ff"))
|
|
114
|
+
palette.setColor(QPalette.ColorRole.Highlight, QColor("#4f8ef7"))
|
|
115
|
+
palette.setColor(QPalette.ColorRole.HighlightedText, QColor("#ffffff"))
|
|
116
|
+
palette.setColor(QPalette.ColorRole.Mid, QColor("#55585e"))
|
|
117
|
+
palette.setColor(QPalette.ColorRole.Midlight, QColor("#45484d"))
|
|
118
|
+
palette.setColor(QPalette.ColorRole.PlaceholderText, QColor("#aeb6c4"))
|
|
119
|
+
palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.WindowText, QColor("#777b82"))
|
|
120
|
+
palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Text, QColor("#777b82"))
|
|
121
|
+
palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.ButtonText, QColor("#777b82"))
|
|
122
|
+
return palette
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def apply_application_theme(app: QApplication, theme: str) -> None:
|
|
126
|
+
selected = theme
|
|
127
|
+
if theme == "system":
|
|
128
|
+
scheme = app.styleHints().colorScheme()
|
|
129
|
+
selected = "dark" if scheme == Qt.ColorScheme.Dark else "light"
|
|
130
|
+
app.setPalette(_dark_palette() if selected == "dark" else _light_palette(app))
|
|
131
|
+
# Qt caches palette() values referenced by stylesheets, so force a full repolish.
|
|
132
|
+
app.setStyleSheet("")
|
|
133
|
+
app.setStyleSheet(BASE_STYLE)
|
|
134
|
+
for widget in app.topLevelWidgets():
|
|
135
|
+
widget.style().unpolish(widget)
|
|
136
|
+
widget.style().polish(widget)
|
|
137
|
+
widget.update()
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def main() -> int:
|
|
141
|
+
QApplication.setHighDpiScaleFactorRoundingPolicy(
|
|
142
|
+
Qt.HighDpiScaleFactorRoundingPolicy.PassThrough
|
|
143
|
+
)
|
|
144
|
+
app = QApplication(sys.argv)
|
|
145
|
+
app.setApplicationName("rgpdf")
|
|
146
|
+
app.setOrganizationName("rgpdf")
|
|
147
|
+
app.setWindowIcon(application_icon())
|
|
148
|
+
app.setStyle("Fusion")
|
|
149
|
+
app.setStyleSheet(BASE_STYLE)
|
|
150
|
+
|
|
151
|
+
def apply_theme(theme: str) -> None:
|
|
152
|
+
apply_application_theme(app, theme)
|
|
153
|
+
|
|
154
|
+
settings = QSettings("rgpdf", "rgpdf")
|
|
155
|
+
apply_theme(settings.value("appearance/theme", "system", type=str))
|
|
156
|
+
window = MainWindow(apply_theme)
|
|
157
|
+
app.styleHints().colorSchemeChanged.connect(
|
|
158
|
+
lambda _scheme: apply_theme("system")
|
|
159
|
+
if settings.value("appearance/theme", "system", type=str) == "system"
|
|
160
|
+
else None
|
|
161
|
+
)
|
|
162
|
+
window.show()
|
|
163
|
+
return app.exec()
|
rgpdf/assets/__init__.py
ADDED
rgpdf/assets/rgpdf.svg
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" role="img" aria-label="Regular expression document search">
|
|
3
|
+
<path d="M120 36h190l92 92v318c0 20-16 36-36 36H120c-20 0-36-16-36-36V72c0-20 16-36 36-36z" fill="#315EFB"/>
|
|
4
|
+
<path d="M310 36v92h92z" fill="#BFD0FF"/>
|
|
5
|
+
<circle cx="180" cy="268" r="20" fill="#FFFFFF"/>
|
|
6
|
+
<g stroke="#FFFFFF" stroke-width="22" stroke-linecap="round">
|
|
7
|
+
<path d="M286 210v116"/>
|
|
8
|
+
<path d="M236 239l100 58"/>
|
|
9
|
+
<path d="M236 297l100-58"/>
|
|
10
|
+
</g>
|
|
11
|
+
</svg>
|
rgpdf/i18n.py
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
TRANSLATIONS: dict[str, dict[str, str]] = {
|
|
5
|
+
"zh_TW": {
|
|
6
|
+
"app_title": "rgpdf — PDF 搜尋",
|
|
7
|
+
"folder": "PDF 資料夾",
|
|
8
|
+
"browse": "瀏覽…",
|
|
9
|
+
"pattern": "搜尋樣式",
|
|
10
|
+
"pattern_hint": "輸入文字或正規表示式",
|
|
11
|
+
"plain": "純文字",
|
|
12
|
+
"regex": "規則表達式",
|
|
13
|
+
"case": "區分大小寫",
|
|
14
|
+
"recursive": "包含子資料夾",
|
|
15
|
+
"highlight_settings": "螢光筆顏色",
|
|
16
|
+
"common_colors": "常用顏色",
|
|
17
|
+
"custom_color": "自訂顏色…",
|
|
18
|
+
"choose_highlight_color": "選擇螢光筆顏色",
|
|
19
|
+
"settings": "設定",
|
|
20
|
+
"enabled": "啟用",
|
|
21
|
+
"disabled": "停用",
|
|
22
|
+
"search": "搜尋",
|
|
23
|
+
"cancel": "取消",
|
|
24
|
+
"files": "檔案",
|
|
25
|
+
"matches": "命中項目",
|
|
26
|
+
"filename": "檔名",
|
|
27
|
+
"count": "筆數",
|
|
28
|
+
"page_number": "頁碼",
|
|
29
|
+
"paragraph": "段落",
|
|
30
|
+
"preview": "頁面預覽",
|
|
31
|
+
"ready": "選取資料夾並輸入搜尋樣式",
|
|
32
|
+
"searching": "正在搜尋 {current}/{total}:{name}",
|
|
33
|
+
"done": "完成:搜尋 {scanned} 份 PDF,{files} 份有結果,共 {matches} 筆命中",
|
|
34
|
+
"cancelled": "搜尋已取消:已處理 {scanned} 份 PDF",
|
|
35
|
+
"no_results": "找不到符合項目",
|
|
36
|
+
"choose_folder": "選取 PDF 資料夾",
|
|
37
|
+
"invalid": "無法搜尋",
|
|
38
|
+
"warnings": "部分檔案無法搜尋",
|
|
39
|
+
"warning_body": "略過 {count} 份檔案:\n\n{details}",
|
|
40
|
+
"language": "語言",
|
|
41
|
+
"theme": "主題",
|
|
42
|
+
"system": "跟隨系統",
|
|
43
|
+
"light": "淺色",
|
|
44
|
+
"dark": "深色",
|
|
45
|
+
"traditional_chinese": "繁體中文",
|
|
46
|
+
"english": "English",
|
|
47
|
+
"page": "第 {page} 頁,共 {total} 頁",
|
|
48
|
+
"previous_page": "上一個命中頁",
|
|
49
|
+
"next_page": "下一個命中頁",
|
|
50
|
+
"zoom_out": "縮小",
|
|
51
|
+
"zoom_in": "放大",
|
|
52
|
+
"fit": "符合寬度",
|
|
53
|
+
"page_single": "第 {start} 頁",
|
|
54
|
+
"page_range": "第 {start}–{end} 頁",
|
|
55
|
+
"match_count": "{count} 筆",
|
|
56
|
+
},
|
|
57
|
+
"en": {
|
|
58
|
+
"app_title": "rgpdf — PDF Search",
|
|
59
|
+
"folder": "PDF folder",
|
|
60
|
+
"browse": "Browse…",
|
|
61
|
+
"pattern": "Search pattern",
|
|
62
|
+
"pattern_hint": "Enter text or a regular expression",
|
|
63
|
+
"plain": "Plain text",
|
|
64
|
+
"regex": "Regular Expression",
|
|
65
|
+
"case": "Case sensitive",
|
|
66
|
+
"recursive": "Include subfolders",
|
|
67
|
+
"highlight_settings": "Highlight color",
|
|
68
|
+
"common_colors": "Common colors",
|
|
69
|
+
"custom_color": "Custom color…",
|
|
70
|
+
"choose_highlight_color": "Choose highlight color",
|
|
71
|
+
"settings": "Settings",
|
|
72
|
+
"enabled": "On",
|
|
73
|
+
"disabled": "Off",
|
|
74
|
+
"search": "Search",
|
|
75
|
+
"cancel": "Cancel",
|
|
76
|
+
"files": "Files",
|
|
77
|
+
"matches": "Matches",
|
|
78
|
+
"filename": "File name",
|
|
79
|
+
"count": "Count",
|
|
80
|
+
"page_number": "Page",
|
|
81
|
+
"paragraph": "Paragraph",
|
|
82
|
+
"preview": "Page preview",
|
|
83
|
+
"ready": "Choose a folder and enter a search pattern",
|
|
84
|
+
"searching": "Searching {current}/{total}: {name}",
|
|
85
|
+
"done": "Done: searched {scanned} PDFs, {files} files matched, {matches} matches",
|
|
86
|
+
"cancelled": "Search cancelled after {scanned} PDFs",
|
|
87
|
+
"no_results": "No matches found",
|
|
88
|
+
"choose_folder": "Choose PDF folder",
|
|
89
|
+
"invalid": "Cannot search",
|
|
90
|
+
"warnings": "Some files could not be searched",
|
|
91
|
+
"warning_body": "Skipped {count} files:\n\n{details}",
|
|
92
|
+
"language": "Language",
|
|
93
|
+
"theme": "Theme",
|
|
94
|
+
"system": "Follow system",
|
|
95
|
+
"light": "Light",
|
|
96
|
+
"dark": "Dark",
|
|
97
|
+
"traditional_chinese": "繁體中文",
|
|
98
|
+
"english": "English",
|
|
99
|
+
"page": "Page {page} of {total}",
|
|
100
|
+
"previous_page": "Previous matched page",
|
|
101
|
+
"next_page": "Next matched page",
|
|
102
|
+
"zoom_out": "Zoom out",
|
|
103
|
+
"zoom_in": "Zoom in",
|
|
104
|
+
"fit": "Fit width",
|
|
105
|
+
"page_single": "Page {start}",
|
|
106
|
+
"page_range": "Pages {start}–{end}",
|
|
107
|
+
"match_count": "{count} matches",
|
|
108
|
+
},
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def translate(language: str, key: str, **values: object) -> str:
|
|
113
|
+
catalog = TRANSLATIONS.get(language, TRANSLATIONS["en"])
|
|
114
|
+
return catalog.get(key, TRANSLATIONS["en"].get(key, key)).format(**values)
|