diff --git a/app/view/__init__.py b/app/view/__init__.py index 9ce3925..dd1847d 100644 --- a/app/view/__init__.py +++ b/app/view/__init__.py @@ -18,6 +18,7 @@ from .students.student_interface import StudentInterface from .video.video_interface import VideoInterface from .camera.camera_interface import CameraInterface from .web_view.web_view_interface import WebViewInterface +from .markdown.markdown_interface import MarkdownInterface def resource_path(relative_path): @@ -35,6 +36,7 @@ class Window(SplitFluentWindow): self.video_interface = VideoInterface() self.camera_interface = CameraInterface() self.web_view_interface = WebViewInterface(self) + self.markdown_interface = MarkdownInterface() self.initNavigation() self.resize(900, 700) @@ -61,6 +63,7 @@ class Window(SplitFluentWindow): self.addSubInterface(self.video_interface, FIF.VIDEO, "Video") self.addSubInterface(self.camera_interface, FIF.CAMERA, "Camera") self.addSubInterface(self.web_view_interface, FIF.FIT_PAGE, "Web View") + self.addSubInterface(self.markdown_interface, FIF.DOCUMENT, "Markdown Editor") # 使用资源路径函数 self.navigationInterface.addWidget( diff --git a/app/view/markdown/__init__.py b/app/view/markdown/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/view/markdown/markdown_interface.py b/app/view/markdown/markdown_interface.py new file mode 100644 index 0000000..5830b1a --- /dev/null +++ b/app/view/markdown/markdown_interface.py @@ -0,0 +1,775 @@ +import os +import sys +import markdown +from pathlib import Path + +from PySide6.QtCore import Qt, QTimer, Slot +from PySide6.QtWidgets import ( + QWidget, + QVBoxLayout, + QHBoxLayout, + QSplitter, + QFileDialog, + QMessageBox, +) +from PySide6.QtGui import ( + QFont, + QTextCursor, + QSyntaxHighlighter, + QKeySequence, + QShortcut, + QTextCharFormat, +) +from qfluentwidgets import ( + CardWidget, + PushButton, + TextEdit, + ScrollArea, + setTheme, + Theme, + InfoBar, + InfoBarPosition, + StrongBodyLabel, + BodyLabel, + ToolButton, +) +from qfluentwidgets import FluentIcon as FIF +from PySide6.QtWebEngineWidgets import QWebEngineView +from PySide6.QtWebChannel import QWebChannel + + +def resource_path(relative_path): + """获取资源的绝对路径""" + if hasattr(sys, "_MEIPASS"): + return os.path.join(sys._MEIPASS, relative_path) # type: ignore + return os.path.join(os.path.abspath("."), relative_path) + + +class MarkdownHighlighter(QSyntaxHighlighter): + """Markdown语法高亮""" + + def __init__(self, parent=None): + super().__init__(parent) # type: ignore + self.highlighting_rules = [] + + # 标题规则 + header_format = QTextCharFormat() + header_format.setForeground(Qt.GlobalColor.darkBlue) + header_format.setFontWeight(QFont.Weight.Bold) + + # 不同级别的标题 + for pattern in [ + r"^# .+$", + r"^## .+$", + r"^### .+$", + r"^#### .+$", + r"^##### .+$", + r"^###### .+$", + ]: + self.highlighting_rules.append((pattern, header_format)) + + # 粗体 + bold_format = QTextCharFormat() + bold_format.setFontWeight(QFont.Weight.Bold) + self.highlighting_rules.append((r"\*\*.+?\*\*", bold_format)) + self.highlighting_rules.append((r"__.+?__", bold_format)) + + # 斜体 + italic_format = QTextCharFormat() + italic_format.setFontItalic(True) + self.highlighting_rules.append((r"\*.+?\*", italic_format)) + self.highlighting_rules.append((r"_.+?_", italic_format)) + + # 代码 + code_format = QTextCharFormat() + code_format.setForeground(Qt.GlobalColor.darkGreen) + code_format.setFontFamily("Consolas") + self.highlighting_rules.append((r"`.+?`", code_format)) + + # 链接 + link_format = QTextCharFormat() + link_format.setForeground(Qt.GlobalColor.blue) + link_format.setUnderlineStyle(QTextCharFormat.UnderlineStyle.SingleUnderline) + self.highlighting_rules.append((r"\[.+?\]\(.+?\)", link_format)) + + # 列表 + list_format = QTextCharFormat() + list_format.setForeground(Qt.GlobalColor.darkMagenta) + self.highlighting_rules.append((r"^[\*\-\+] .+$", list_format)) + self.highlighting_rules.append((r"^\d+\. .+$", list_format)) + + def highlightBlock(self, text): + for pattern, format in self.highlighting_rules: + import re + + expression = re.compile(pattern) + for match in expression.finditer(text): + start, end = match.span() + self.setFormat(start, end - start, format) + + +class MarkdownPreview(ScrollArea): + """Markdown预览组件""" + + def __init__(self, parent=None): + super().__init__(parent) + self.setObjectName("markdownPreview") + self.setMinimumWidth(300) + self.parent = parent # type: ignore + self.current_scroll_percent = 0 + + # 创建预览容器 + self.preview_widget = QWidget() + self.preview_layout = QVBoxLayout(self.preview_widget) + + # 预览内容区域 + self.web_view = QWebEngineView() + self.web_view.setHtml(self.get_preview_html("")) + + self.channel = QWebChannel() + self.web_view.page().setWebChannel(self.channel) + self.channel.registerObject("preview", self) + + self.preview_layout.addWidget(self.web_view) + self.setWidget(self.preview_widget) + self.setWidgetResizable(True) + + # 设置样式 + self.setStyleSheet( + """ + MarkdownPreview { + background-color: white; + border: 1px solid #e1e5e9; + border-radius: 8px; + } + """ + ) + self.web_view.loadFinished.connect(self.on_load_finished) + + @Slot(bool) + def on_load_finished(self): + """页面加载完成后注入JavaScript代码""" + js_code = """ + // 监听预览区域的滚动事件 + let isScrolling = false; + window.addEventListener('scroll', function() { + if (isScrolling) return; + + var scrollPercent = (window.scrollY / (document.documentElement.scrollHeight - window.innerHeight)) * 100; + if (window.pyHandler && window.pyHandler.onPreviewScroll) { + window.pyHandler.onPreviewScroll(scrollPercent); + } + }); + + // 用于从Python端设置滚动位置的函数 + window.scrollToPercent = function(percent) { + isScrolling = true; + var scrollY = (percent / 100) * (document.documentElement.scrollHeight - window.innerHeight); + window.scrollTo(0, scrollY); + setTimeout(() => { isScrolling = false; }, 100); + }; + + // 保存当前滚动位置 + window.saveScrollPosition = function() { + return (window.scrollY / (document.documentElement.scrollHeight - window.innerHeight)) * 100; + }; + + // 恢复滚动位置 + window.restoreScrollPosition = function(percent) { + window.scrollToPercent(percent); + }; + """ + self.web_view.page().runJavaScript(js_code) + + # 加载完成后恢复滚动位置 + if self.current_scroll_percent > 0: + QTimer.singleShot( + 100, lambda: self.scroll_to_percent(self.current_scroll_percent) + ) + + @Slot(float) + def onPreviewScroll(self, percent): + """从JavaScript接收预览区域的滚动位置""" + self.current_scroll_percent = percent + if self.parent and hasattr(self.parent, "sync_editor_to_preview"): + self.parent.sync_editor_to_preview(percent) # type: ignore + + def scroll_to_percent(self, percent): + """滚动预览到指定百分比位置""" + js_code = f"if (window.scrollToPercent) window.scrollToPercent({percent});" + self.web_view.page().runJavaScript(js_code) + + def _on_save_scroll_position(self, result): + """保存滚动位置的回调""" + if result is not None: + self.current_scroll_percent = int(result) + + def restore_scroll_position(self): + """恢复保存的滚动位置""" + if self.current_scroll_percent > 0: + self.scroll_to_percent(self.current_scroll_percent) + + def save_scroll_position(self): + """保存当前滚动位置""" + js_code = "if (window.saveScrollPosition) window.saveScrollPosition();" + self.web_view.page().runJavaScript(js_code, self._on_save_scroll_position) + + def get_preview_html(self, markdown_text): + """使用markdown库生成预览HTML""" + try: + # 使用markdown库转换Markdown + html_content = markdown.markdown( + markdown_text, + extensions=[ + "extra", # 支持表格、缩写等 + "codehilite", # 代码高亮 + "toc", # 目录 + "nl2br", # 换行转
+ "sane_lists", # 更合理的列表 + "smarty", # 智能引号等 + ], + extension_configs={ + "codehilite": {"css_class": "highlight", "linenums": False} + }, + ) + except Exception as e: + html_content = f"

Markdown解析错误: {str(e)}

" + + return f""" + + + + + + + +
+ {html_content} +
+ + + """ + + def update_preview(self, markdown_text): + """更新预览内容""" + # 保存当前滚动位置 + self.save_scroll_position() + # 更新HTML内容 + self.web_view.setHtml(self.get_preview_html(markdown_text)) + + +class MarkdownEditor(TextEdit): + """增强的Markdown编辑器""" + + def __init__(self, parent=None): + super().__init__(parent) + self.setPlaceholderText("在此输入Markdown内容...") + self.setFont(QFont("Consolas", 11)) + + # 启用语法高亮 + self.highlighter = MarkdownHighlighter(self.document()) + + # 设置制表符宽度 + self.setTabStopDistance(40) + + +class MarkdownInterface(QWidget): + """Markdown编辑器主界面""" + + def __init__(self): + super().__init__() + self.setObjectName("markdown_interface") + self.current_file = None + # self.editor = None + self.is_syncing = False + self.text_change_timer = QTimer() # 用于延迟更新预览 + self.text_change_timer.setSingleShot(True) + self.text_change_timer.timeout.connect(self.delayed_preview_update) + + self.setup_ui() + self.setup_shortcuts() + + def setup_ui(self): + """设置界面布局""" + main_layout = QVBoxLayout(self) + main_layout.setContentsMargins(20, 50, 20, 20) + main_layout.setSpacing(15) + + # 标题 + title_label = StrongBodyLabel("Markdown编辑器") + title_label.setStyleSheet("font-size: 24px; padding: 10px 0;") + main_layout.addWidget(title_label) + + # 编辑器和预览器 + editor_preview_layout = QHBoxLayout() + + # 编辑器 + self.editor = MarkdownEditor() + self.editor.textChanged.connect(self.on_text_changed) + + # 连接编辑器的滚动事件 + self.editor.verticalScrollBar().valueChanged.connect(self.on_editor_scroll) + + # 工具栏 + toolbar = self.create_toolbar() + main_layout.addWidget(toolbar) + + # 预览器 + self.preview = MarkdownPreview(self) + + # 分割器 + splitter = QSplitter(Qt.Orientation.Horizontal) + splitter.addWidget(self.editor) + splitter.addWidget(self.preview) + splitter.setSizes([400, 400]) + + editor_preview_layout.addWidget(splitter) + main_layout.addLayout(editor_preview_layout, 1) + + # 状态栏 + self.status_label = BodyLabel("就绪") + self.status_label.setStyleSheet("color: #666; font-size: 12px;") + main_layout.addWidget(self.status_label) + + def create_toolbar(self): + """创建工具栏""" + toolbar_card = CardWidget() + toolbar_layout = QHBoxLayout(toolbar_card) + + # 文件操作按钮 + self.new_btn = PushButton(FIF.ADD, "新建", self) + self.new_btn.clicked.connect(self.new_file) + + self.open_btn = PushButton(FIF.FOLDER, "打开", self) + self.open_btn.clicked.connect(self.open_file) + + self.save_btn = PushButton(FIF.SAVE, "保存", self) + self.save_btn.clicked.connect(self.save_file) + + self.save_as_btn = PushButton(FIF.SAVE_AS, "另存为", self) + self.save_as_btn.clicked.connect(self.save_as_file) + + # 编辑操作按钮 + self.undo_btn = PushButton(FIF.LEFT_ARROW, "撤销", self) + self.undo_btn.clicked.connect(self.editor.undo) + + self.redo_btn = PushButton(FIF.RIGHT_ARROW, "反撤销", self) + self.redo_btn.clicked.connect(self.editor.redo) + + # 格式化按钮 + self.bold_btn = ToolButton(self) + self.bold_btn.setText("B") + self.bold_btn.setToolTip("粗体") + self.bold_btn.clicked.connect(self.insert_bold) + + self.italic_btn = ToolButton(self) + self.italic_btn.setText("I") + self.italic_btn.setToolTip("斜体") + self.italic_btn.clicked.connect(self.insert_italic) + + self.code_btn = ToolButton(self) + self.code_btn.setText("") + self.code_btn.setToolTip("代码") + self.code_btn.clicked.connect(self.insert_code) + + # 添加按钮到工具栏 + toolbar_layout.addWidget(self.new_btn) + toolbar_layout.addWidget(self.open_btn) + toolbar_layout.addWidget(self.save_btn) + toolbar_layout.addWidget(self.save_as_btn) + toolbar_layout.addWidget(self.undo_btn) + toolbar_layout.addWidget(self.redo_btn) + toolbar_layout.addWidget(self.bold_btn) + toolbar_layout.addWidget(self.italic_btn) + toolbar_layout.addWidget(self.code_btn) + toolbar_layout.addStretch() + + return toolbar_card + + def setup_shortcuts(self): + """设置快捷键""" + # 文件操作 + QShortcut(QKeySequence("Ctrl+S"), self).activated.connect(self.save_file) + QShortcut(QKeySequence("Ctrl+O"), self).activated.connect(self.open_file) + QShortcut(QKeySequence("Ctrl+N"), self).activated.connect(self.new_file) + + # 编辑操作 + QShortcut(QKeySequence("Ctrl+Z"), self).activated.connect(self.editor.undo) + QShortcut(QKeySequence("Ctrl+Y"), self).activated.connect(self.editor.redo) + QShortcut(QKeySequence("Ctrl+Shift+Z"), self).activated.connect( + self.editor.redo + ) + + # 格式化快捷键 + QShortcut(QKeySequence("Ctrl+B"), self).activated.connect(self.insert_bold) + QShortcut(QKeySequence("Ctrl+I"), self).activated.connect(self.insert_italic) + QShortcut(QKeySequence("Ctrl+K"), self).activated.connect(self.insert_code) + + def on_editor_scroll(self): + """编辑器滚动时同步预览""" + if self.is_syncing: + return + + self.is_syncing = True + + # 计算编辑器的滚动百分比 + scroll_bar = self.editor.verticalScrollBar() + max_scroll = scroll_bar.maximum() + if max_scroll > 0: + scroll_percent = (scroll_bar.value() / max_scroll) * 100 + # 同步预览滚动 + self.preview.scroll_to_percent(scroll_percent) + + self.is_syncing = False + + def sync_editor_to_preview(self, preview_percent): + """从预览同步到编辑器""" + if self.is_syncing: + return + + self.is_syncing = True + + # 同步编辑器滚动 + scroll_bar = self.editor.verticalScrollBar() + max_scroll = scroll_bar.maximum() + if max_scroll > 0: + scroll_value = (preview_percent / 100) * max_scroll + scroll_bar.setValue(int(scroll_value)) + + self.is_syncing = False + + def insert_bold(self): + """插入粗体""" + cursor = self.editor.textCursor() + selected_text = cursor.selectedText() + if selected_text: + cursor.insertText(f"**{selected_text}**") + else: + cursor.insertText("****") + cursor.movePosition( + QTextCursor.MoveOperation.Left, QTextCursor.MoveMode.MoveAnchor, 2 + ) + self.editor.setTextCursor(cursor) + + def insert_italic(self): + """插入斜体""" + cursor = self.editor.textCursor() + selected_text = cursor.selectedText() + if selected_text: + cursor.insertText(f"*{selected_text}*") + else: + cursor.insertText("**") + cursor.movePosition( + QTextCursor.MoveOperation.Left, QTextCursor.MoveMode.MoveAnchor, 1 + ) + self.editor.setTextCursor(cursor) + + def insert_code(self): + """插入代码""" + cursor = self.editor.textCursor() + selected_text = cursor.selectedText() + if selected_text: + cursor.insertText(f"`{selected_text}`") + else: + cursor.insertText("``") + cursor.movePosition( + QTextCursor.MoveOperation.Left, QTextCursor.MoveMode.MoveAnchor, 1 + ) + self.editor.setTextCursor(cursor) + + def on_text_changed(self): + """文本变化时延迟更新预览""" + # 停止之前的计时器 + self.text_change_timer.stop() + + # 重新启动计时器(延迟300毫秒更新预览) + self.text_change_timer.start(300) + + # 更新状态 + text = self.editor.toPlainText() + self.update_status("已修改" if text else "就绪") + + def delayed_preview_update(self): + """延迟更新预览,避免频繁刷新""" + text = self.editor.toPlainText() + self.preview.update_preview(text) + + def new_file(self): + """新建文件""" + if self.current_file or self.editor.toPlainText(): + reply = QMessageBox.question( + self, + "确认", + "是否保存当前文件的更改?", + QMessageBox.StandardButton.Yes + | QMessageBox.StandardButton.No + | QMessageBox.StandardButton.Cancel, + ) + if reply == QMessageBox.StandardButton.Yes: + if not self.save_file(): + return + elif reply == QMessageBox.StandardButton.Cancel: + return + + if self.editor: + self.editor.clear() + self.current_file = None + self.update_status("新建文件") + self.show_info("已创建新文件", "success") + + def open_file(self): + """打开文件""" + file_path, _ = QFileDialog.getOpenFileName( + self, + "打开Markdown文件", + "", + "Markdown文件 (*.md *.markdown);;所有文件 (*.*)", + ) + + if file_path: + try: + with open(file_path, "r", encoding="utf-8") as file: + content = file.read() + self.editor.setPlainText(content) + self.current_file = file_path + self.update_status(f"已打开: {Path(file_path).name}") + self.show_info(f"已打开文件: {Path(file_path).name}", "success") + except Exception as e: + self.show_info(f"打开文件失败: {str(e)}", "error") + + def save_file(self): + """保存文件""" + if self.current_file: + return self._save_to_file(self.current_file) + else: + return self.save_as_file() + + def save_as_file(self): + """另存为文件""" + file_path, _ = QFileDialog.getSaveFileName( + self, + "保存Markdown文件", + self.current_file or "未命名文档.md", + "Markdown文件 (*.md *.markdown);;所有文件 (*.*)", + ) + + if file_path: + return self._save_to_file(file_path) + return False + + def _save_to_file(self, file_path): + """保存内容到文件""" + try: + with open(file_path, "w", encoding="utf-8") as file: + file.write(self.editor.toPlainText()) + self.current_file = file_path + self.update_status(f"已保存: {Path(file_path).name}") + self.show_info(f"文件已保存: {Path(file_path).name}", "success") + return True + except Exception as e: + self.show_info(f"保存文件失败: {str(e)}", "error") + return False + + def update_status(self, message): + """更新状态栏""" + self.status_label.setText(message) + + def show_info(self, content, type="info"): + """显示信息条""" + if type == "success": + InfoBar.success( + "", content, parent=self, duration=2000, position=InfoBarPosition.TOP + ) + elif type == "warning": + InfoBar.warning( + "", content, parent=self, duration=2000, position=InfoBarPosition.TOP + ) + elif type == "error": + InfoBar.error( + "", content, parent=self, duration=3000, position=InfoBarPosition.TOP + ) + else: + InfoBar.info( + "", content, parent=self, duration=2000, position=InfoBarPosition.TOP + ) + + +if __name__ == "__main__": + import sys + from PySide6.QtWidgets import QApplication + + app = QApplication(sys.argv) + setTheme(Theme.LIGHT) + + window = MarkdownInterface() + window.resize(1000, 700) + window.show() + + sys.exit(app.exec()) diff --git a/app/view/video/video_interface.py b/app/view/video/video_interface.py index 2657f5d..204270b 100644 --- a/app/view/video/video_interface.py +++ b/app/view/video/video_interface.py @@ -1,7 +1,7 @@ from PySide6.QtCore import QUrl, Qt from PySide6.QtGui import QFont # 导入QFont from PySide6.QtWidgets import QWidget, QVBoxLayout, QLabel, QFileDialog, QHBoxLayout -from qfluentwidgets import setTheme, Theme, FluentIcon, PushButton, PrimaryPushButton +from qfluentwidgets import FluentIcon, PushButton from qfluentwidgets.multimedia import VideoWidget diff --git a/pyproject.toml b/pyproject.toml index e67abee..0bc40aa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,11 +5,13 @@ description = "A PySide6 application that uses queue-sqlite to manage tasks" readme = "README.md" requires-python = ">=3.12" dependencies = [ + "markdown>=3.9", "opencv-python>=4.12.0.88", "opencv-python-headless>=4.12.0.88", "pyside6>=6.9.1", "pyside6-fluent-widgets>=1.8.6", "queue-sqlite>=0.2.0", + "requests>=2.32.5", "toml>=0.10.2", ] # 是否是生产环境 diff --git a/uv.lock b/uv.lock index 54901ef..c1a1c12 100644 --- a/uv.lock +++ b/uv.lock @@ -11,6 +11,15 @@ wheels = [ { url = "https://mirrors.aliyun.com/pypi/packages/4d/3f/3bc3f1d83f6e4a7fcb834d3720544ca597590425be5ba9db032b2bf322a2/altgraph-0.17.4-py2.py3-none-any.whl", hash = "sha256:642743b4750de17e655e6711601b077bc6598dbfa3ba5fa2b2a35ce12b508dff" }, ] +[[package]] +name = "certifi" +version = "2025.8.3" +source = { registry = "https://mirrors.aliyun.com/pypi/simple" } +sdist = { url = "https://mirrors.aliyun.com/pypi/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407" } +wheels = [ + { url = "https://mirrors.aliyun.com/pypi/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5" }, +] + [[package]] name = "cffi" version = "1.17.1" @@ -44,6 +53,48 @@ wheels = [ { url = "https://mirrors.aliyun.com/pypi/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a" }, ] +[[package]] +name = "charset-normalizer" +version = "3.4.3" +source = { registry = "https://mirrors.aliyun.com/pypi/simple" } +sdist = { url = "https://mirrors.aliyun.com/pypi/packages/83/2d/5fd176ceb9b2fc619e63405525573493ca23441330fcdaee6bef9460e924/charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14" } +wheels = [ + { url = "https://mirrors.aliyun.com/pypi/packages/e9/5e/14c94999e418d9b87682734589404a25854d5f5d0408df68bc15b6ff54bb/charset_normalizer-3.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1" }, + { url = "https://mirrors.aliyun.com/pypi/packages/7d/a8/c6ec5d389672521f644505a257f50544c074cf5fc292d5390331cd6fc9c3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884" }, + { url = "https://mirrors.aliyun.com/pypi/packages/fc/eb/a2ffb08547f4e1e5415fb69eb7db25932c52a52bed371429648db4d84fb1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018" }, + { url = "https://mirrors.aliyun.com/pypi/packages/82/10/0fd19f20c624b278dddaf83b8464dcddc2456cb4b02bb902a6da126b87a1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392" }, + { url = "https://mirrors.aliyun.com/pypi/packages/16/ab/0233c3231af734f5dfcf0844aa9582d5a1466c985bbed6cedab85af9bfe3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f" }, + { url = "https://mirrors.aliyun.com/pypi/packages/ae/02/e29e22b4e02839a0e4a06557b1999d0a47db3567e82989b5bb21f3fbbd9f/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154" }, + { url = "https://mirrors.aliyun.com/pypi/packages/05/6b/e2539a0a4be302b481e8cafb5af8792da8093b486885a1ae4d15d452bcec/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491" }, + { url = "https://mirrors.aliyun.com/pypi/packages/31/e7/883ee5676a2ef217a40ce0bffcc3d0dfbf9e64cbcfbdf822c52981c3304b/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93" }, + { url = "https://mirrors.aliyun.com/pypi/packages/c1/35/6525b21aa0db614cf8b5792d232021dca3df7f90a1944db934efa5d20bb1/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f" }, + { url = "https://mirrors.aliyun.com/pypi/packages/50/ee/f4704bad8201de513fdc8aac1cabc87e38c5818c93857140e06e772b5892/charset_normalizer-3.4.3-cp312-cp312-win32.whl", hash = "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37" }, + { url = "https://mirrors.aliyun.com/pypi/packages/39/f5/3b3836ca6064d0992c58c7561c6b6eee1b3892e9665d650c803bd5614522/charset_normalizer-3.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc" }, + { url = "https://mirrors.aliyun.com/pypi/packages/65/ca/2135ac97709b400c7654b4b764daf5c5567c2da45a30cdd20f9eefe2d658/charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe" }, + { url = "https://mirrors.aliyun.com/pypi/packages/71/11/98a04c3c97dd34e49c7d247083af03645ca3730809a5509443f3c37f7c99/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8" }, + { url = "https://mirrors.aliyun.com/pypi/packages/60/f5/4659a4cb3c4ec146bec80c32d8bb16033752574c20b1252ee842a95d1a1e/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9" }, + { url = "https://mirrors.aliyun.com/pypi/packages/86/9e/f552f7a00611f168b9a5865a1414179b2c6de8235a4fa40189f6f79a1753/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31" }, + { url = "https://mirrors.aliyun.com/pypi/packages/7e/95/42aa2156235cbc8fa61208aded06ef46111c4d3f0de233107b3f38631803/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f" }, + { url = "https://mirrors.aliyun.com/pypi/packages/c2/a9/3865b02c56f300a6f94fc631ef54f0a8a29da74fb45a773dfd3dcd380af7/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927" }, + { url = "https://mirrors.aliyun.com/pypi/packages/77/d9/cbcf1a2a5c7d7856f11e7ac2d782aec12bdfea60d104e60e0aa1c97849dc/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9" }, + { url = "https://mirrors.aliyun.com/pypi/packages/f6/42/6f45efee8697b89fda4d50580f292b8f7f9306cb2971d4b53f8914e4d890/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5" }, + { url = "https://mirrors.aliyun.com/pypi/packages/70/99/f1c3bdcfaa9c45b3ce96f70b14f070411366fa19549c1d4832c935d8e2c3/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc" }, + { url = "https://mirrors.aliyun.com/pypi/packages/a3/ad/b0081f2f99a4b194bcbb1934ef3b12aa4d9702ced80a37026b7607c72e58/charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce" }, + { url = "https://mirrors.aliyun.com/pypi/packages/9a/8f/ae790790c7b64f925e5c953b924aaa42a243fb778fed9e41f147b2a5715a/charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef" }, + { url = "https://mirrors.aliyun.com/pypi/packages/8e/91/b5a06ad970ddc7a0e513112d40113e834638f4ca1120eb727a249fb2715e/charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15" }, + { url = "https://mirrors.aliyun.com/pypi/packages/ce/ec/1edc30a377f0a02689342f214455c3f6c2fbedd896a1d2f856c002fc3062/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db" }, + { url = "https://mirrors.aliyun.com/pypi/packages/17/e5/5e67ab85e6d22b04641acb5399c8684f4d37caf7558a53859f0283a650e9/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d" }, + { url = "https://mirrors.aliyun.com/pypi/packages/f1/e5/38421987f6c697ee3722981289d554957c4be652f963d71c5e46a262e135/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096" }, + { url = "https://mirrors.aliyun.com/pypi/packages/a0/e4/5a075de8daa3ec0745a9a3b54467e0c2967daaaf2cec04c845f73493e9a1/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa" }, + { url = "https://mirrors.aliyun.com/pypi/packages/02/f7/3611b32318b30974131db62b4043f335861d4d9b49adc6d57c1149cc49d4/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049" }, + { url = "https://mirrors.aliyun.com/pypi/packages/7e/61/19b36f4bd67f2793ab6a99b979b4e4f3d8fc754cbdffb805335df4337126/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0" }, + { url = "https://mirrors.aliyun.com/pypi/packages/06/57/84722eefdd338c04cf3030ada66889298eaedf3e7a30a624201e0cbe424a/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92" }, + { url = "https://mirrors.aliyun.com/pypi/packages/72/2a/aff5dd112b2f14bcc3462c312dce5445806bfc8ab3a7328555da95330e4b/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16" }, + { url = "https://mirrors.aliyun.com/pypi/packages/b7/8c/9839225320046ed279c6e839d51f028342eb77c91c89b8ef2549f951f3ec/charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce" }, + { url = "https://mirrors.aliyun.com/pypi/packages/ee/7a/36fbcf646e41f710ce0a563c1c9a343c6edf9be80786edeb15b6f62e17db/charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c" }, + { url = "https://mirrors.aliyun.com/pypi/packages/8a/1f/f041989e93b001bc4e44bb1669ccdcf54d3f00e628229a85b08d330615c5/charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a" }, +] + [[package]] name = "darkdetect" version = "0.8.0" @@ -53,6 +104,15 @@ wheels = [ { url = "https://mirrors.aliyun.com/pypi/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl", hash = "sha256:a7509ccf517eaad92b31c214f593dbcf138ea8a43b2935406bbd565e15527a85" }, ] +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://mirrors.aliyun.com/pypi/simple" } +sdist = { url = "https://mirrors.aliyun.com/pypi/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9" } +wheels = [ + { url = "https://mirrors.aliyun.com/pypi/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3" }, +] + [[package]] name = "macholib" version = "1.16.3" @@ -65,6 +125,15 @@ wheels = [ { url = "https://mirrors.aliyun.com/pypi/packages/d1/5d/c059c180c84f7962db0aeae7c3b9303ed1d73d76f2bfbc32bc231c8be314/macholib-1.16.3-py2.py3-none-any.whl", hash = "sha256:0e315d7583d38b8c77e815b1ecbdbf504a8258d8b3e17b61165c6feb60d18f2c" }, ] +[[package]] +name = "markdown" +version = "3.9" +source = { registry = "https://mirrors.aliyun.com/pypi/simple" } +sdist = { url = "https://mirrors.aliyun.com/pypi/packages/8d/37/02347f6d6d8279247a5837082ebc26fc0d5aaeaf75aa013fcbb433c777ab/markdown-3.9.tar.gz", hash = "sha256:d2900fe1782bd33bdbbd56859defef70c2e78fc46668f8eb9df3128138f2cb6a" } +wheels = [ + { url = "https://mirrors.aliyun.com/pypi/packages/70/ae/44c4a6a4cbb496d93c6257954260fe3a6e91b7bed2240e5dad2a717f5111/markdown-3.9-py3-none-any.whl", hash = "sha256:9f4d91ed810864ea88a6f32c07ba8bee1346c0cc1f6b1f9f6c822f2a9667d280" }, +] + [[package]] name = "maturin" version = "1.9.4" @@ -2908,11 +2977,13 @@ name = "pyside-queue-sqlite" version = "0.1.0" source = { virtual = "." } dependencies = [ + { name = "markdown" }, { name = "opencv-python" }, { name = "opencv-python-headless" }, { name = "pyside6" }, { name = "pyside6-fluent-widgets" }, { name = "queue-sqlite" }, + { name = "requests" }, { name = "toml" }, ] @@ -2925,11 +2996,13 @@ dev = [ [package.metadata] requires-dist = [ + { name = "markdown", specifier = ">=3.9" }, { name = "opencv-python", specifier = ">=4.12.0.88" }, { name = "opencv-python-headless", specifier = ">=4.12.0.88" }, { name = "pyside6", specifier = ">=6.9.1" }, { name = "pyside6-fluent-widgets", specifier = ">=1.8.6" }, { name = "queue-sqlite", specifier = ">=0.2.0" }, + { name = "requests", specifier = ">=2.32.5" }, { name = "toml", specifier = ">=0.10.2" }, ] @@ -3065,6 +3138,21 @@ wheels = [ { url = "https://mirrors.aliyun.com/pypi/packages/21/bb/2abf6dbd3d08f0d5f7aac576168be78b957e283d9d8461bf2afcad75b86c/queue_sqlite_core-0.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a9d19840a9363bc29568b7c0e735c1ffe610b9618641c298d9647816aad8306b" }, ] +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://mirrors.aliyun.com/pypi/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://mirrors.aliyun.com/pypi/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf" } +wheels = [ + { url = "https://mirrors.aliyun.com/pypi/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6" }, +] + [[package]] name = "setuptools" version = "80.9.0" @@ -3095,6 +3183,15 @@ wheels = [ { url = "https://mirrors.aliyun.com/pypi/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" }, ] +[[package]] +name = "urllib3" +version = "2.5.0" +source = { registry = "https://mirrors.aliyun.com/pypi/simple" } +sdist = { url = "https://mirrors.aliyun.com/pypi/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760" } +wheels = [ + { url = "https://mirrors.aliyun.com/pypi/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc" }, +] + [[package]] name = "zstandard" version = "0.23.0"