双击图片预览改走 subprocess open: 5 处统一调系统默认应用
旧实现 Qt.openUrlExternally("file:///" + path) 在 macOS 26+ 不弹
Preview.app — 用户反馈"双击预览大图功能没了"的根因 (Qt URL 解析
+ launch services 双层间接). 中间过渡试过应用内 ImagePreviewDialog,
但缺缩放 / 全屏 / 方向键浏览, 不如系统预览方便.
修法: AppState 加 openExternalFile(path) Slot, 内部 subprocess.Popen
(macOS open / Windows os.startfile / Linux xdg-open). 与
HistoryBridge.revealInExplorer 同款路径, prod 已验.
5 处双击全切到 appState.openExternalFile:
- ImageGenTab 参考图 (TapHandler.onDoubleTapped) + 生成图 (MouseArea)
- StyleDesignerTab 生成图
- HistoryTab 详情大图 + 参考图卡片
中间过渡产物 components/ImagePreviewDialog.qml 已删 (从未 git track),
3 tab 顶部各一个实例化删. 系统 Preview.app 自带缩放 / 全屏 / 方向键 /
标记 / 分享, 远胜自实现 Dialog. Linus 消除特殊情况.
闪退维度复核:
- subprocess.Popen fire-and-forget, 不阻塞主线程
- 不传 bytes, 不动 Qt clipboard / NSPasteboard / NSImage
- path 不存在仅 logger.warning, 不抛
- macOS 用户已实测全部 5 处弹出 Preview.app
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Showing
4 changed files
with
35 additions
and
5 deletions
| ... | @@ -20,6 +20,7 @@ import logging | ... | @@ -20,6 +20,7 @@ import logging |
| 20 | import os | 20 | import os |
| 21 | import platform | 21 | import platform |
| 22 | import shutil | 22 | import shutil |
| 23 | import subprocess | ||
| 23 | import sys | 24 | import sys |
| 24 | from pathlib import Path | 25 | from pathlib import Path |
| 25 | 26 | ||
| ... | @@ -99,6 +100,35 @@ class AppState(QObject): | ... | @@ -99,6 +100,35 @@ class AppState(QObject): |
| 99 | self._current_tab = idx | 100 | self._current_tab = idx |
| 100 | self.currentTabChanged.emit() | 101 | self.currentTabChanged.emit() |
| 101 | 102 | ||
| 103 | @Slot(str) | ||
| 104 | def openExternalFile(self, path: str) -> None: | ||
| 105 | """用系统默认应用打开文件 (双击图片预览的统一入口). | ||
| 106 | |||
| 107 | 比 Qt.openUrlExternally 可靠: 直接 subprocess 把 path 交给系统命令, | ||
| 108 | 绕开 Qt URL 解析 + macOS launch services 双层间接. 旧版 QML 用 | ||
| 109 | Qt.openUrlExternally("file:///" + path) 在 macOS 26+ 不弹 Preview.app | ||
| 110 | (用户反馈"双击预览大图功能没了"的根因). | ||
| 111 | |||
| 112 | 与 HistoryBridge.revealInExplorer 同款 subprocess.Popen + open 路径, | ||
| 113 | prod 已验证. | ||
| 114 | """ | ||
| 115 | if not path: | ||
| 116 | return | ||
| 117 | p = Path(path) | ||
| 118 | if not p.exists(): | ||
| 119 | logger.warning(f"openExternalFile: 文件不存在 {path}") | ||
| 120 | return | ||
| 121 | try: | ||
| 122 | system = platform.system() | ||
| 123 | if system == "Darwin": | ||
| 124 | subprocess.Popen(["open", str(p)]) | ||
| 125 | elif system == "Windows": | ||
| 126 | os.startfile(str(p)) # type: ignore[attr-defined] | ||
| 127 | else: | ||
| 128 | subprocess.Popen(["xdg-open", str(p)]) | ||
| 129 | except Exception: | ||
| 130 | logger.exception(f"openExternalFile 失败 {path}") | ||
| 131 | |||
| 102 | 132 | ||
| 103 | # -------------------- 启动期 helpers -------------------- | 133 | # -------------------- 启动期 helpers -------------------- |
| 104 | 134 | ... | ... |
| ... | @@ -350,7 +350,7 @@ Item { | ... | @@ -350,7 +350,7 @@ Item { |
| 350 | cursorShape: Qt.PointingHandCursor | 350 | cursorShape: Qt.PointingHandCursor |
| 351 | onDoubleClicked: { | 351 | onDoubleClicked: { |
| 352 | if (tab.selectedItem.generatedImagePath) { | 352 | if (tab.selectedItem.generatedImagePath) { |
| 353 | Qt.openUrlExternally("file:///" + tab.selectedItem.generatedImagePath) | 353 | appState.openExternalFile(tab.selectedItem.generatedImagePath) |
| 354 | } | 354 | } |
| 355 | } | 355 | } |
| 356 | } | 356 | } |
| ... | @@ -414,7 +414,7 @@ Item { | ... | @@ -414,7 +414,7 @@ Item { |
| 414 | MouseArea { | 414 | MouseArea { |
| 415 | anchors.fill: parent | 415 | anchors.fill: parent |
| 416 | cursorShape: Qt.PointingHandCursor | 416 | cursorShape: Qt.PointingHandCursor |
| 417 | onDoubleClicked: Qt.openUrlExternally("file:///" + modelData) | 417 | onDoubleClicked: appState.openExternalFile(modelData) |
| 418 | } | 418 | } |
| 419 | } | 419 | } |
| 420 | } | 420 | } | ... | ... |
| ... | @@ -418,7 +418,7 @@ Item { | ... | @@ -418,7 +418,7 @@ Item { |
| 418 | // 一旦超 8px 阈值进入 drag,TapHandler 自动放弃。 | 418 | // 一旦超 8px 阈值进入 drag,TapHandler 自动放弃。 |
| 419 | TapHandler { | 419 | TapHandler { |
| 420 | acceptedButtons: Qt.LeftButton | 420 | acceptedButtons: Qt.LeftButton |
| 421 | onDoubleTapped: Qt.openUrlExternally("file:///" + modelData) | 421 | onDoubleTapped: appState.openExternalFile(modelData) |
| 422 | } | 422 | } |
| 423 | DragHandler { | 423 | DragHandler { |
| 424 | id: dragHandler | 424 | id: dragHandler |
| ... | @@ -816,7 +816,7 @@ Item { | ... | @@ -816,7 +816,7 @@ Item { |
| 816 | cursorShape: Qt.PointingHandCursor | 816 | cursorShape: Qt.PointingHandCursor |
| 817 | onDoubleClicked: { | 817 | onDoubleClicked: { |
| 818 | if (tab.lastResultPath) { | 818 | if (tab.lastResultPath) { |
| 819 | Qt.openUrlExternally("file:///" + tab.lastResultPath) | 819 | appState.openExternalFile(tab.lastResultPath) |
| 820 | } | 820 | } |
| 821 | } | 821 | } |
| 822 | } | 822 | } | ... | ... |
| ... | @@ -509,7 +509,7 @@ Item { | ... | @@ -509,7 +509,7 @@ Item { |
| 509 | cursorShape: Qt.PointingHandCursor | 509 | cursorShape: Qt.PointingHandCursor |
| 510 | onDoubleClicked: { | 510 | onDoubleClicked: { |
| 511 | if (tab.lastResultPath) { | 511 | if (tab.lastResultPath) { |
| 512 | Qt.openUrlExternally("file:///" + tab.lastResultPath) | 512 | appState.openExternalFile(tab.lastResultPath) |
| 513 | } | 513 | } |
| 514 | } | 514 | } |
| 515 | } | 515 | } | ... | ... |
-
Please register or sign in to post a comment