fry-qt6-progress-widget 0.1.0__tar.gz

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.
@@ -0,0 +1,5 @@
1
+ include README.md
2
+ include pyproject.toml
3
+ recursive-include docs *.md
4
+ recursive-include examples *.py
5
+ recursive-include tests *.py
@@ -0,0 +1,126 @@
1
+ Metadata-Version: 2.4
2
+ Name: fry-qt6-progress-widget
3
+ Version: 0.1.0
4
+ Summary: A reusable PyQt6 QWidget for displaying task progress, messages, pause state, and time estimates.
5
+ Author: fry
6
+ Project-URL: Homepage, https://gitlab.com/fanrenyi33/fry_python_lib_d150_fry_qt6_progress_widget
7
+ Project-URL: Source, https://gitlab.com/fanrenyi33/fry_python_lib_d150_fry_qt6_progress_widget
8
+ Keywords: pyqt6,qt,progress,widget,task
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Environment :: X11 Applications :: Qt
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Software Development :: User Interfaces
19
+ Requires-Python: >=3.9
20
+ Description-Content-Type: text/markdown
21
+ Requires-Dist: PyQt6>=6.5
22
+ Provides-Extra: dev
23
+ Requires-Dist: build>=1.0; extra == "dev"
24
+ Requires-Dist: pytest>=7.0; extra == "dev"
25
+ Requires-Dist: twine>=4.0; extra == "dev"
26
+
27
+ # fry-qt6-progress-widget
28
+
29
+ `fry-qt6-progress-widget` 是一个可复用的 PyQt6 任务进度组件。核心类是 `GeneralTaskProgressWidget`,它继承自 `QWidget`,适合直接放进任意布局;如果你的程序需要 dock,可以使用附带的 `GeneralTaskProgressDock` 薄包装器,或者自己把 widget 放进 `QDockWidget`。
30
+
31
+ ## 安装
32
+
33
+ 发布到 PyPI 后:
34
+
35
+ ```bash
36
+ pip install fry-qt6-progress-widget
37
+ ```
38
+
39
+ 本地开发安装:
40
+
41
+ ```bash
42
+ pip install -e ".[dev]"
43
+ ```
44
+
45
+ ## 最小用法
46
+
47
+ ```python
48
+ import sys
49
+
50
+ from PyQt6.QtWidgets import QApplication
51
+ from fry_qt6_progress_widget import GeneralTaskProgressWidget
52
+
53
+ app = QApplication(sys.argv)
54
+
55
+ widget = GeneralTaskProgressWidget()
56
+ widget.begin_task(project_id="demo", task_name="导入数据", total=100)
57
+ widget.update_progress(current=30, total=100, detail_info="已完成 30 条")
58
+ widget.show()
59
+
60
+ sys.exit(app.exec())
61
+ ```
62
+
63
+ ## 字典信号兼容用法
64
+
65
+ 如果你已有任务系统会发出字典,可以直接调用 `handle_task_progress`:
66
+
67
+ ```python
68
+ widget.handle_task_progress(
69
+ {
70
+ "project_id_str": "demo",
71
+ "task_name_str": "导入数据",
72
+ "now_progress_num": 30,
73
+ "total_num": 100,
74
+ "info_type": "信息",
75
+ "detail_info": "已完成 30 条",
76
+ }
77
+ )
78
+ ```
79
+
80
+ ## Dock 用法
81
+
82
+ ```python
83
+ from PyQt6.QtCore import Qt
84
+ from PyQt6.QtWidgets import QMainWindow
85
+ from fry_qt6_progress_widget import GeneralTaskProgressDock
86
+
87
+ window = QMainWindow()
88
+ dock = GeneralTaskProgressDock(window)
89
+ window.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, dock)
90
+ dock.begin_task(project_id="demo", task_name="后台任务", total=10)
91
+ ```
92
+
93
+ ## 常用 API
94
+
95
+ - `begin_task(project_id="", task_name="任务", total=None)`: 开始任务并重置显示。
96
+ - `update_progress(current=None, total=None, task_name=None, project_id=None, info_type="信息", detail_info="")`: 更新进度和详情消息。
97
+ - `finish_task(project_id=None, message="任务已完成", hide=False)`: 标记任务完成。
98
+ - `set_paused_state(is_paused)`: 切换暂停/恢复状态。
99
+ - `append_message(info_type, message)`: 追加详情日志。
100
+ - `set_project_filter(project_id)`: 只接收指定项目 ID 的进度更新。
101
+ - `get_params_dict()` / `load_params_dict(snapshot)`: 保存和恢复组件状态。
102
+
103
+ ## 示例和文档
104
+
105
+ - [examples/widget_demo.py](examples/widget_demo.py): 直接使用 QWidget。
106
+ - [examples/dock_demo.py](examples/dock_demo.py): 放进 QDockWidget。
107
+ - [docs/core-concepts.md](docs/core-concepts.md): 组件核心关键点。
108
+
109
+ ## 开发与验证
110
+
111
+ ```bash
112
+ pytest
113
+ python -m build
114
+ ```
115
+
116
+ 构建后会生成 `dist/`,发布前建议检查:
117
+
118
+ ```bash
119
+ twine check dist/*
120
+ ```
121
+
122
+ 真正发布到 PyPI 需要维护者账号和 token:
123
+
124
+ ```bash
125
+ twine upload dist/*
126
+ ```
@@ -0,0 +1,100 @@
1
+ # fry-qt6-progress-widget
2
+
3
+ `fry-qt6-progress-widget` 是一个可复用的 PyQt6 任务进度组件。核心类是 `GeneralTaskProgressWidget`,它继承自 `QWidget`,适合直接放进任意布局;如果你的程序需要 dock,可以使用附带的 `GeneralTaskProgressDock` 薄包装器,或者自己把 widget 放进 `QDockWidget`。
4
+
5
+ ## 安装
6
+
7
+ 发布到 PyPI 后:
8
+
9
+ ```bash
10
+ pip install fry-qt6-progress-widget
11
+ ```
12
+
13
+ 本地开发安装:
14
+
15
+ ```bash
16
+ pip install -e ".[dev]"
17
+ ```
18
+
19
+ ## 最小用法
20
+
21
+ ```python
22
+ import sys
23
+
24
+ from PyQt6.QtWidgets import QApplication
25
+ from fry_qt6_progress_widget import GeneralTaskProgressWidget
26
+
27
+ app = QApplication(sys.argv)
28
+
29
+ widget = GeneralTaskProgressWidget()
30
+ widget.begin_task(project_id="demo", task_name="导入数据", total=100)
31
+ widget.update_progress(current=30, total=100, detail_info="已完成 30 条")
32
+ widget.show()
33
+
34
+ sys.exit(app.exec())
35
+ ```
36
+
37
+ ## 字典信号兼容用法
38
+
39
+ 如果你已有任务系统会发出字典,可以直接调用 `handle_task_progress`:
40
+
41
+ ```python
42
+ widget.handle_task_progress(
43
+ {
44
+ "project_id_str": "demo",
45
+ "task_name_str": "导入数据",
46
+ "now_progress_num": 30,
47
+ "total_num": 100,
48
+ "info_type": "信息",
49
+ "detail_info": "已完成 30 条",
50
+ }
51
+ )
52
+ ```
53
+
54
+ ## Dock 用法
55
+
56
+ ```python
57
+ from PyQt6.QtCore import Qt
58
+ from PyQt6.QtWidgets import QMainWindow
59
+ from fry_qt6_progress_widget import GeneralTaskProgressDock
60
+
61
+ window = QMainWindow()
62
+ dock = GeneralTaskProgressDock(window)
63
+ window.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, dock)
64
+ dock.begin_task(project_id="demo", task_name="后台任务", total=10)
65
+ ```
66
+
67
+ ## 常用 API
68
+
69
+ - `begin_task(project_id="", task_name="任务", total=None)`: 开始任务并重置显示。
70
+ - `update_progress(current=None, total=None, task_name=None, project_id=None, info_type="信息", detail_info="")`: 更新进度和详情消息。
71
+ - `finish_task(project_id=None, message="任务已完成", hide=False)`: 标记任务完成。
72
+ - `set_paused_state(is_paused)`: 切换暂停/恢复状态。
73
+ - `append_message(info_type, message)`: 追加详情日志。
74
+ - `set_project_filter(project_id)`: 只接收指定项目 ID 的进度更新。
75
+ - `get_params_dict()` / `load_params_dict(snapshot)`: 保存和恢复组件状态。
76
+
77
+ ## 示例和文档
78
+
79
+ - [examples/widget_demo.py](examples/widget_demo.py): 直接使用 QWidget。
80
+ - [examples/dock_demo.py](examples/dock_demo.py): 放进 QDockWidget。
81
+ - [docs/core-concepts.md](docs/core-concepts.md): 组件核心关键点。
82
+
83
+ ## 开发与验证
84
+
85
+ ```bash
86
+ pytest
87
+ python -m build
88
+ ```
89
+
90
+ 构建后会生成 `dist/`,发布前建议检查:
91
+
92
+ ```bash
93
+ twine check dist/*
94
+ ```
95
+
96
+ 真正发布到 PyPI 需要维护者账号和 token:
97
+
98
+ ```bash
99
+ twine upload dist/*
100
+ ```
@@ -0,0 +1,97 @@
1
+ # fry-qt6-progress-widget 核心关键点
2
+
3
+ ## 1. Widget 是核心,Dock 只是包装
4
+
5
+ 这个包的主类是 `GeneralTaskProgressWidget`,继承自 `QWidget`。它不依赖 `QMainWindow`、全局数据中心或任何业务工程里的基类,所以可以直接放到普通布局、弹窗、侧边栏、Tab 页或自定义容器里。
6
+
7
+ 如果调用方需要 dock,可以使用 `GeneralTaskProgressDock`,也可以自己写:
8
+
9
+ ```python
10
+ dock = QDockWidget("任务进度", main_window)
11
+ dock.setWidget(GeneralTaskProgressWidget())
12
+ ```
13
+
14
+ ## 2. 进度数据模型
15
+
16
+ 组件内部维护四类状态:
17
+
18
+ - `current_project_id`: 当前项目 ID。
19
+ - `current_task_name`: 当前任务名称。
20
+ - `current_progress` / `total_progress`: 当前进度和总量。
21
+ - `is_paused`: 暂停状态。
22
+
23
+ `total_progress <= 0` 时,进度条会进入 Qt 的 busy/indeterminate 模式,适合总量未知的任务。
24
+
25
+ ## 3. 推荐调用流程
26
+
27
+ ```python
28
+ widget.begin_task(project_id="p1", task_name="导入数据", total=100)
29
+ widget.update_progress(current=20, detail_info="读取文件")
30
+ widget.update_progress(current=80, detail_info="写入数据库")
31
+ widget.finish_task(message="导入完成")
32
+ ```
33
+
34
+ 如果任务系统已经按字典广播进度,可以使用兼容入口:
35
+
36
+ ```python
37
+ widget.handle_task_progress(
38
+ {
39
+ "project_id_str": "p1",
40
+ "task_name_str": "导入数据",
41
+ "now_progress_num": 20,
42
+ "total_num": 100,
43
+ "info_type": "信息",
44
+ "detail_info": "读取文件",
45
+ }
46
+ )
47
+ ```
48
+
49
+ ## 4. 项目过滤
50
+
51
+ 多项目场景下可以设置项目过滤器:
52
+
53
+ ```python
54
+ widget.set_project_filter("p1")
55
+ ```
56
+
57
+ 设置后,只有 `project_id == "p1"` 的 begin/update/finish 调用会被处理,其他项目会返回 `ProgressResult(status="info", ...)`,组件 UI 不会被改动。
58
+
59
+ ## 5. 返回对象
60
+
61
+ 公开的变更方法返回 `ProgressResult`:
62
+
63
+ - `status`: `success`、`info`、`warning`、`failed`。
64
+ - `message`: 便于日志记录的说明。
65
+ - `data`: 通常是 `get_params_dict()` 的快照。
66
+ - `ok`: 只要不是 `failed` 就为 `True`。
67
+
68
+ 这样调用方不需要依赖原工程里的 `FryReturnObj`。
69
+
70
+ ## 6. 信号
71
+
72
+ 组件提供这些 Qt 信号,方便调用方联动其他 UI:
73
+
74
+ - `taskStarted(project_id, task_name)`
75
+ - `progressChanged(snapshot_dict)`
76
+ - `taskFinished(project_id)`
77
+ - `messageAppended(info_type, message)`
78
+ - `pausedChanged(is_paused)`
79
+
80
+ ## 7. HTML 安全
81
+
82
+ 详情区使用 `QTextEdit.insertHtml()` 展示彩色消息。组件会对 `info_type` 和 `message` 做 `html.escape()`,所以外部传入的 `<script>`、`<b>` 等文本会按普通文本显示,不会当作 HTML 执行或改变排版。
83
+
84
+ ## 8. 时间预估
85
+
86
+ 组件按任务名记录第一次进度和当前进度,通过消耗时间估算:
87
+
88
+ ```text
89
+ 平均耗时 = 已用时间 / 已完成单位数
90
+ 预计剩余 = 平均耗时 * 剩余单位数
91
+ ```
92
+
93
+ 首次进度、进度未增长、总量未知时会显示“正在计算预估时间...”。
94
+
95
+ ## 9. 状态保存与恢复
96
+
97
+ `get_params_dict()` 会返回可序列化快照;`load_params_dict(snapshot)` 可以恢复主要 UI 状态。这个能力适合应用重载、切换页面、保存窗口状态等场景。
@@ -0,0 +1,42 @@
1
+ import sys
2
+
3
+ from PyQt6.QtCore import Qt, QTimer
4
+ from PyQt6.QtWidgets import QApplication, QLabel, QMainWindow
5
+
6
+ from fry_qt6_progress_widget import GeneralTaskProgressDock
7
+
8
+
9
+ class MainWindow(QMainWindow):
10
+ def __init__(self) -> None:
11
+ super().__init__()
12
+ self.setWindowTitle("Dock demo")
13
+ self.resize(720, 480)
14
+ self.setCentralWidget(QLabel("主窗口内容区域"))
15
+
16
+ self.progress_dock = GeneralTaskProgressDock(self)
17
+ self.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, self.progress_dock)
18
+
19
+ self.current = 0
20
+ self.timer = QTimer(self)
21
+ self.timer.timeout.connect(self._tick)
22
+
23
+ self.progress_dock.begin_task(project_id="dock-demo", task_name="后台同步", total=50)
24
+ self.timer.start(150)
25
+
26
+ def _tick(self) -> None:
27
+ self.current += 1
28
+ self.progress_dock.update_progress(
29
+ current=self.current,
30
+ total=50,
31
+ detail_info=f"同步第 {self.current} 批数据",
32
+ )
33
+ if self.current >= 50:
34
+ self.timer.stop()
35
+ self.progress_dock.finish_task(message="后台同步完成")
36
+
37
+
38
+ if __name__ == "__main__":
39
+ app = QApplication(sys.argv)
40
+ window = MainWindow()
41
+ window.show()
42
+ sys.exit(app.exec())
@@ -0,0 +1,59 @@
1
+ import sys
2
+
3
+ from PyQt6.QtCore import QTimer
4
+ from PyQt6.QtWidgets import QApplication, QPushButton, QVBoxLayout, QWidget
5
+
6
+ from fry_qt6_progress_widget import GeneralTaskProgressWidget
7
+
8
+
9
+ class DemoWindow(QWidget):
10
+ def __init__(self) -> None:
11
+ super().__init__()
12
+ self.setWindowTitle("fry-qt6-progress-widget demo")
13
+ self.resize(460, 560)
14
+
15
+ self.progress = GeneralTaskProgressWidget()
16
+ self.start_button = QPushButton("开始示例任务")
17
+ self.pause_button = QPushButton("暂停/恢复")
18
+
19
+ layout = QVBoxLayout(self)
20
+ layout.addWidget(self.progress)
21
+ layout.addWidget(self.start_button)
22
+ layout.addWidget(self.pause_button)
23
+
24
+ self.timer = QTimer(self)
25
+ self.timer.timeout.connect(self._tick)
26
+ self.current = 0
27
+
28
+ self.start_button.clicked.connect(self.start_task)
29
+ self.pause_button.clicked.connect(self.toggle_pause)
30
+
31
+ def start_task(self) -> None:
32
+ self.current = 0
33
+ self.progress.begin_task(project_id="demo", task_name="生成报表", total=100)
34
+ self.timer.start(120)
35
+
36
+ def toggle_pause(self) -> None:
37
+ self.progress.set_paused_state(not self.progress.is_paused)
38
+
39
+ def _tick(self) -> None:
40
+ if self.progress.is_paused:
41
+ return
42
+
43
+ self.current += 5
44
+ self.progress.update_progress(
45
+ current=self.current,
46
+ total=100,
47
+ detail_info=f"已处理 {self.current} / 100",
48
+ )
49
+
50
+ if self.current >= 100:
51
+ self.timer.stop()
52
+ self.progress.finish_task(message="示例任务完成")
53
+
54
+
55
+ if __name__ == "__main__":
56
+ app = QApplication(sys.argv)
57
+ window = DemoWindow()
58
+ window.show()
59
+ sys.exit(app.exec())
@@ -0,0 +1,51 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "fry-qt6-progress-widget"
7
+ version = "0.1.0"
8
+ description = "A reusable PyQt6 QWidget for displaying task progress, messages, pause state, and time estimates."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ authors = [
12
+ { name = "fry" }
13
+ ]
14
+ keywords = ["pyqt6", "qt", "progress", "widget", "task"]
15
+ classifiers = [
16
+ "Development Status :: 3 - Alpha",
17
+ "Environment :: X11 Applications :: Qt",
18
+ "Intended Audience :: Developers",
19
+ "Operating System :: OS Independent",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.9",
22
+ "Programming Language :: Python :: 3.10",
23
+ "Programming Language :: Python :: 3.11",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Topic :: Software Development :: User Interfaces",
26
+ ]
27
+ dependencies = [
28
+ "PyQt6>=6.5",
29
+ ]
30
+
31
+ [project.optional-dependencies]
32
+ dev = [
33
+ "build>=1.0",
34
+ "pytest>=7.0",
35
+ "twine>=4.0",
36
+ ]
37
+
38
+ [project.urls]
39
+ Homepage = "https://gitlab.com/fanrenyi33/fry_python_lib_d150_fry_qt6_progress_widget"
40
+ Source = "https://gitlab.com/fanrenyi33/fry_python_lib_d150_fry_qt6_progress_widget"
41
+
42
+ [tool.setuptools.packages.find]
43
+ where = ["src"]
44
+
45
+ [tool.setuptools.package-data]
46
+ fry_qt6_progress_widget = ["py.typed"]
47
+
48
+ [tool.pytest.ini_options]
49
+ testpaths = ["tests"]
50
+ pythonpath = ["src"]
51
+ addopts = "-q"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,16 @@
1
+ from .dock import GeneralTaskProgressDock
2
+ from .progress_widget import GeneralTaskProgressWidget, InfoTypeConfig, ProgressResult
3
+
4
+ __version__ = "0.1.0"
5
+
6
+ TaskProgressWidget = GeneralTaskProgressWidget
7
+ TaskProgressDock = GeneralTaskProgressDock
8
+
9
+ __all__ = [
10
+ "GeneralTaskProgressWidget",
11
+ "GeneralTaskProgressDock",
12
+ "TaskProgressWidget",
13
+ "TaskProgressDock",
14
+ "InfoTypeConfig",
15
+ "ProgressResult",
16
+ ]
@@ -0,0 +1,74 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Dict, Mapping, Optional
4
+
5
+ from PyQt6.QtCore import Qt, pyqtSlot
6
+ from PyQt6.QtWidgets import QDockWidget, QWidget
7
+
8
+ from .progress_widget import GeneralTaskProgressWidget, ProgressResult
9
+
10
+
11
+ class GeneralTaskProgressDock(QDockWidget):
12
+ """Thin QDockWidget wrapper around GeneralTaskProgressWidget."""
13
+
14
+ def __init__(
15
+ self,
16
+ parent: Optional[QWidget] = None,
17
+ *,
18
+ title: str = "任务进度",
19
+ widget: Optional[GeneralTaskProgressWidget] = None,
20
+ allowed_areas: Optional[Qt.DockWidgetArea] = None,
21
+ **widget_kwargs: Any,
22
+ ) -> None:
23
+ super().__init__(title, parent)
24
+ self.progress_widget = widget or GeneralTaskProgressWidget(parent=self, title=title, **widget_kwargs)
25
+ self.setWidget(self.progress_widget)
26
+ self.setAllowedAreas(
27
+ allowed_areas
28
+ or (Qt.DockWidgetArea.LeftDockWidgetArea | Qt.DockWidgetArea.RightDockWidgetArea)
29
+ )
30
+
31
+ def begin_task(self, *args: Any, **kwargs: Any) -> ProgressResult:
32
+ return self.progress_widget.begin_task(*args, **kwargs)
33
+
34
+ def update_progress(self, *args: Any, **kwargs: Any) -> ProgressResult:
35
+ return self.progress_widget.update_progress(*args, **kwargs)
36
+
37
+ def finish_task(self, *args: Any, **kwargs: Any) -> ProgressResult:
38
+ return self.progress_widget.finish_task(*args, **kwargs)
39
+
40
+ def append_message(self, *args: Any, **kwargs: Any) -> ProgressResult:
41
+ return self.progress_widget.append_message(*args, **kwargs)
42
+
43
+ def clear_info(self) -> ProgressResult:
44
+ return self.progress_widget.clear_info()
45
+
46
+ def set_paused_state(self, is_paused: bool) -> ProgressResult:
47
+ return self.progress_widget.set_paused_state(is_paused)
48
+
49
+ def set_project_filter(self, project_id: Optional[str]) -> ProgressResult:
50
+ return self.progress_widget.set_project_filter(project_id)
51
+
52
+ def get_params_dict(self) -> Dict[str, Any]:
53
+ return self.progress_widget.get_params_dict()
54
+
55
+ def load_params_dict(self, params_dict: Mapping[str, Any]) -> ProgressResult:
56
+ return self.progress_widget.load_params_dict(params_dict)
57
+
58
+ def fry_reload(self, params_dict: Optional[Mapping[str, Any]] = None) -> ProgressResult:
59
+ return self.progress_widget.fry_reload(params_dict)
60
+
61
+ def fry_reset(self) -> ProgressResult:
62
+ return self.progress_widget.fry_reset()
63
+
64
+ @pyqtSlot(str, str)
65
+ def handle_task_begin(self, project_id: str, total_task_name: str) -> ProgressResult:
66
+ return self.progress_widget.handle_task_begin(project_id, total_task_name)
67
+
68
+ @pyqtSlot(dict)
69
+ def handle_task_progress(self, task_data_dict: Dict[str, Any]) -> ProgressResult:
70
+ return self.progress_widget.handle_task_progress(task_data_dict)
71
+
72
+ @pyqtSlot(str)
73
+ def handle_task_finished(self, project_id: str) -> ProgressResult:
74
+ return self.progress_widget.handle_task_finished(project_id)