Migrate sticky windows back to QWidget
This commit is contained in:
1 parent
965d05634a
commit
79658afee2
51 files changed
+2552
-4777
No files matched your search
@@ -56,6 +56,10 @@ set(SOURCES
|
||||
src/instance_activation.cpp
|
||||
src/instance_activation.h
|
||||
src/singleton.h
|
||||
src/widgets/sticky_pin_widget.cpp
|
||||
src/widgets/sticky_pin_widget.h
|
||||
src/widgets/sticky_canvas_widget.cpp
|
||||
src/widgets/sticky_canvas_widget.h
|
||||
|
||||
# Model
|
||||
src/model/desktop_snapshot.cpp
|
||||
@@ -104,6 +108,8 @@ set(SOURCES
|
||||
src/paint_board/shape/two_point_shape.h
|
||||
src/paint_board/shape/arrow_shape.cpp
|
||||
src/paint_board/shape/arrow_shape.h
|
||||
src/paint_board/shape/blur_shape.cpp
|
||||
src/paint_board/shape/blur_shape.h
|
||||
src/paint_board/shape/ellipse_shape.cpp
|
||||
src/paint_board/shape/ellipse_shape.h
|
||||
src/paint_board/shape/mosaic_shape.cpp
|
||||
|
||||
+99
-99
@@ -93,37 +93,37 @@
|
||||
<context>
|
||||
<name>TZChat</name>
|
||||
<message>
|
||||
<location filename="../qml/TZChat.qml" line="83" />
|
||||
<location filename="../qml/Windows/TZChat.qml" line="83" />
|
||||
<source>AI 图像编辑</source>
|
||||
<translation>AI Image Edit</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZChat.qml" line="162" />
|
||||
<location filename="../qml/Windows/TZChat.qml" line="162" />
|
||||
<source>当前贴图</source>
|
||||
<translation>Current Image</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZChat.qml" line="171" />
|
||||
<location filename="../qml/Windows/TZChat.qml" line="171" />
|
||||
<source>未关联贴图</source>
|
||||
<translation>No Image Linked</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZChat.qml" line="178" />
|
||||
<location filename="../qml/Windows/TZChat.qml" line="178" />
|
||||
<source>发送后将对此图执行 AI 编辑</source>
|
||||
<translation>AI edit will be applied to this image after sending</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZChat.qml" line="203" />
|
||||
<location filename="../qml/Windows/TZChat.qml" line="203" />
|
||||
<source>描述您想要的修改...</source>
|
||||
<translation>Describe the changes you want...</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZChat.qml" line="228" />
|
||||
<location filename="../qml/Windows/TZChat.qml" line="228" />
|
||||
<source>取消</source>
|
||||
<translation>Cancel</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZChat.qml" line="245" />
|
||||
<location filename="../qml/Windows/TZChat.qml" line="245" />
|
||||
<source>发送</source>
|
||||
<translation>Send</translation>
|
||||
</message>
|
||||
@@ -189,7 +189,7 @@
|
||||
<context>
|
||||
<name>TZGifRecordBar</name>
|
||||
<message>
|
||||
<location filename="../qml/TZGifRecordBar.qml" line="186" />
|
||||
<location filename="../qml/Capture/TZGifRecordBar.qml" line="186" />
|
||||
<source>正在生成 GIF...</source>
|
||||
<translation type="unfinished" />
|
||||
</message>
|
||||
@@ -266,52 +266,52 @@ If it conflicts with system or other app shortcuts, change to a different combin
|
||||
<context>
|
||||
<name>TZMarkToolInfo</name>
|
||||
<message>
|
||||
<location filename="../qml/TZMarkToolInfo.qml" line="13" />
|
||||
<location filename="../qml/Capture/TZMarkToolInfo.qml" line="13" />
|
||||
<source>文本</source>
|
||||
<translation type="unfinished" />
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZMarkToolInfo.qml" line="62" />
|
||||
<location filename="../qml/Capture/TZMarkToolInfo.qml" line="62" />
|
||||
<source>细</source>
|
||||
<translation>Thin</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZMarkToolInfo.qml" line="63" />
|
||||
<location filename="../qml/Capture/TZMarkToolInfo.qml" line="63" />
|
||||
<source>中</source>
|
||||
<translation>Medium</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZMarkToolInfo.qml" line="64" />
|
||||
<location filename="../qml/Capture/TZMarkToolInfo.qml" line="64" />
|
||||
<source>粗</source>
|
||||
<translation>Thick</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZMarkToolInfo.qml" line="65" />
|
||||
<location filename="../qml/Capture/TZMarkToolInfo.qml" line="65" />
|
||||
<source>特粗</source>
|
||||
<translation>Extra Thick</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZMarkToolInfo.qml" line="133" />
|
||||
<location filename="../qml/Capture/TZMarkToolInfo.qml" line="133" />
|
||||
<source>输入文字</source>
|
||||
<translation type="unfinished" />
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZMarkToolInfo.qml" line="140" />
|
||||
<location filename="../qml/Capture/TZMarkToolInfo.qml" line="140" />
|
||||
<source>背景</source>
|
||||
<translation type="unfinished" />
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZMarkToolInfo.qml" line="152" />
|
||||
<location filename="../qml/Capture/TZMarkToolInfo.qml" line="152" />
|
||||
<source>模糊</source>
|
||||
<translation type="unfinished" />
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZMarkToolInfo.qml" line="177" />
|
||||
<location filename="../qml/Capture/TZMarkToolInfo.qml" line="177" />
|
||||
<source>自动递增</source>
|
||||
<translation type="unfinished" />
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZMarkToolInfo.qml" line="187" />
|
||||
<location filename="../qml/Capture/TZMarkToolInfo.qml" line="187" />
|
||||
<source>数字</source>
|
||||
<translation type="unfinished" />
|
||||
</message>
|
||||
@@ -319,32 +319,32 @@ If it conflicts with system or other app shortcuts, change to a different combin
|
||||
<context>
|
||||
<name>TZOcrResult</name>
|
||||
<message>
|
||||
<location filename="../qml/TZOcrResult.qml" line="67" />
|
||||
<location filename="../qml/Windows/TZOcrResult.qml" line="67" />
|
||||
<source>OCR 识别结果</source>
|
||||
<translation>OCR Results</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZOcrResult.qml" line="167" />
|
||||
<location filename="../qml/Windows/TZOcrResult.qml" line="167" />
|
||||
<source>正在识别中…</source>
|
||||
<translation>Recognizing…</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZOcrResult.qml" line="190" />
|
||||
<location filename="../qml/Windows/TZOcrResult.qml" line="190" />
|
||||
<source>识别结果将显示在这里…</source>
|
||||
<translation>Recognition results will appear here…</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZOcrResult.qml" line="239" />
|
||||
<location filename="../qml/Windows/TZOcrResult.qml" line="239" />
|
||||
<source>复制全部</source>
|
||||
<translation>Copy All</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZOcrResult.qml" line="265" />
|
||||
<location filename="../qml/Windows/TZOcrResult.qml" line="265" />
|
||||
<source>关闭</source>
|
||||
<translation>Close</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZOcrResult.qml" line="293" />
|
||||
<location filename="../qml/Windows/TZOcrResult.qml" line="293" />
|
||||
<source>已复制!</source>
|
||||
<translation>Copied!</translation>
|
||||
</message>
|
||||
@@ -378,46 +378,46 @@ If it conflicts with system or other app shortcuts, change to a different combin
|
||||
<context>
|
||||
<name>TZStickyWindow</name>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="515" />
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="515" />
|
||||
<source>当前透明度 %1%</source>
|
||||
<translation>Opacity: %1%</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="518" />
|
||||
<location filename="../qml/TZStickyWindow.qml" line="572" />
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="518" />
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="572" />
|
||||
<source>覆盖贴图(合并标注)</source>
|
||||
<translation>Overwrite Image (Merge Annotations)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="523" />
|
||||
<location filename="../qml/TZStickyWindow.qml" line="558" />
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="523" />
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="558" />
|
||||
<source>显示工具栏</source>
|
||||
<translation>Show Toolbar</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="534" />
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="534" />
|
||||
<source>透明度 +5%</source>
|
||||
<translation>Opacity +5%</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="535" />
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="535" />
|
||||
<source>透明度 -5%</source>
|
||||
<translation>Opacity -5%</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="541" />
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="541" />
|
||||
<source>旋转 90°</source>
|
||||
<translation>Rotate 90°</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="548" />
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="548" />
|
||||
<source>重置位置</source>
|
||||
<translation>Reset Position</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="520" />
|
||||
<location filename="../qml/TZStickyWindow.qml" line="566" />
|
||||
<location filename="../qml/TZStickyWindow.qml" line="581" />
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="520" />
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="566" />
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="581" />
|
||||
<source>AI 编辑</source>
|
||||
<translation>AI Edit</translation>
|
||||
</message>
|
||||
@@ -426,120 +426,120 @@ If it conflicts with system or other app shortcuts, change to a different combin
|
||||
<translation type="vanished">Save Image</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="519" />
|
||||
<location filename="../qml/TZStickyWindow.qml" line="573" />
|
||||
<location filename="../qml/TZStickyWindow.qml" line="580" />
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="519" />
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="573" />
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="580" />
|
||||
<source>复制贴图</source>
|
||||
<translation>Copy Image</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="530" />
|
||||
<location filename="../qml/TZStickyWindow.qml" line="583" />
|
||||
<location filename="../qml/TZStickyWindow.qml" line="588" />
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="530" />
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="583" />
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="588" />
|
||||
<source>关闭窗口</source>
|
||||
<translation>Close Window</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="570" />
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="570" />
|
||||
<source>保存原图</source>
|
||||
<translation>Save Original Image</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="571" />
|
||||
<location filename="../qml/TZStickyWindow.qml" line="602" />
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="571" />
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="602" />
|
||||
<source>另存为(合并标注)</source>
|
||||
<translation>Save As (Merge Annotations)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="578" />
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="578" />
|
||||
<source>常用项置顶</source>
|
||||
<translation>Pin Common Items</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="579" />
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="579" />
|
||||
<source>覆盖贴图</source>
|
||||
<translation>Overwrite Image</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="582" />
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="582" />
|
||||
<source>显示/隐藏工具栏</source>
|
||||
<translation>Show/Hide Toolbar</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="593" />
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="593" />
|
||||
<source>选择保存位置</source>
|
||||
<translation>Choose Save Location</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="341" />
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="341" />
|
||||
<source>Pencil</source>
|
||||
<translation>Pencil</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="350" />
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="350" />
|
||||
<source>Rectangle</source>
|
||||
<translation>Rectangle</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="359" />
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="359" />
|
||||
<source>Circle</source>
|
||||
<translation>Circle</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="368" />
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="368" />
|
||||
<source>Arrow</source>
|
||||
<translation>Arrow</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="382" />
|
||||
<location filename="../qml/TZStickyWindow.qml" line="383" />
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="382" />
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="383" />
|
||||
<source>Mosaic</source>
|
||||
<translation>Mosaic</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="404" />
|
||||
<location filename="../qml/TZStickyWindow.qml" line="539" />
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="404" />
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="539" />
|
||||
<source>缩小</source>
|
||||
<translation>Zoom Out</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="406" />
|
||||
<location filename="../qml/TZStickyWindow.qml" line="540" />
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="406" />
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="540" />
|
||||
<source>放大</source>
|
||||
<translation>Zoom In</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="407" />
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="407" />
|
||||
<source>旋转90°</source>
|
||||
<translation>Rotate 90°</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="408" />
|
||||
<location filename="../qml/TZStickyWindow.qml" line="542" />
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="408" />
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="542" />
|
||||
<source>镜像</source>
|
||||
<translation>Mirror</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="409" />
|
||||
<location filename="../qml/TZStickyWindow.qml" line="543" />
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="409" />
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="543" />
|
||||
<source>恢复原始大小</source>
|
||||
<translation>Reset Size</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="415" />
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="415" />
|
||||
<source>完成并复制</source>
|
||||
<translation>Apply and Copy</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="422" />
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="422" />
|
||||
<source>撤销</source>
|
||||
<translation>Undo</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="434" />
|
||||
<location filename="../qml/TZStickyWindow.qml" line="435" />
|
||||
<location filename="../qml/TZStickyWindow.qml" line="523" />
|
||||
<location filename="../qml/TZStickyWindow.qml" line="558" />
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="434" />
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="435" />
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="523" />
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="558" />
|
||||
<source>隐藏工具栏</source>
|
||||
<translation>Hide Toolbar</translation>
|
||||
</message>
|
||||
@@ -560,8 +560,8 @@ If it conflicts with system or other app shortcuts, change to a different combin
|
||||
<translation>Hide Toolbar</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="324" />
|
||||
<location filename="../qml/TZStickyWindow.qml" line="325" />
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="324" />
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="325" />
|
||||
<source>OCR</source>
|
||||
<translation>OCR</translation>
|
||||
</message>
|
||||
@@ -573,7 +573,7 @@ If it conflicts with system or other app shortcuts, change to a different combin
|
||||
<translation type="vanished">Confirm</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZTool.qml" line="83" />
|
||||
<location filename="../qml/Capture/TZTool.qml" line="83" />
|
||||
<source>取消</source>
|
||||
<translation>Cancel</translation>
|
||||
</message>
|
||||
@@ -598,67 +598,67 @@ If it conflicts with system or other app shortcuts, change to a different combin
|
||||
<translation type="vanished">Arrow</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZTool.qml" line="76" />
|
||||
<location filename="../qml/Capture/TZTool.qml" line="76" />
|
||||
<source>完成</source>
|
||||
<translation>Done</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZTool.qml" line="100" />
|
||||
<location filename="../qml/Capture/TZTool.qml" line="100" />
|
||||
<source>录制GIF</source>
|
||||
<translation>Record GIF</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZTool.qml" line="109" />
|
||||
<location filename="../qml/Capture/TZTool.qml" line="109" />
|
||||
<source>保存</source>
|
||||
<translation>Save</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZTool.qml" line="116" />
|
||||
<location filename="../qml/Capture/TZTool.qml" line="116" />
|
||||
<source>贴图</source>
|
||||
<translation>Pin</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZTool.qml" line="127" />
|
||||
<location filename="../qml/Capture/TZTool.qml" line="127" />
|
||||
<source>矩形</source>
|
||||
<translation>Rectangle</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZTool.qml" line="136" />
|
||||
<location filename="../qml/Capture/TZTool.qml" line="136" />
|
||||
<source>圆形</source>
|
||||
<translation>Circle</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZTool.qml" line="145" />
|
||||
<location filename="../qml/Capture/TZTool.qml" line="145" />
|
||||
<source>箭头</source>
|
||||
<translation>Arrow</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZTool.qml" line="154" />
|
||||
<location filename="../qml/Capture/TZTool.qml" line="154" />
|
||||
<source>画笔</source>
|
||||
<translation>Pen</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZTool.qml" line="163" />
|
||||
<location filename="../qml/Capture/TZTool.qml" line="163" />
|
||||
<source>高亮</source>
|
||||
<translation>Highlight</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZTool.qml" line="172" />
|
||||
<location filename="../qml/Capture/TZTool.qml" line="172" />
|
||||
<source>马赛克</source>
|
||||
<translation>Mosaic</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZTool.qml" line="181" />
|
||||
<location filename="../qml/Capture/TZTool.qml" line="181" />
|
||||
<source>文字</source>
|
||||
<translation>Text</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZTool.qml" line="190" />
|
||||
<location filename="../qml/Capture/TZTool.qml" line="190" />
|
||||
<source>序号</source>
|
||||
<translation>Number</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZTool.qml" line="197" />
|
||||
<location filename="../qml/Capture/TZTool.qml" line="197" />
|
||||
<source>撤销</source>
|
||||
<translation>Undo</translation>
|
||||
</message>
|
||||
@@ -667,7 +667,7 @@ If it conflicts with system or other app shortcuts, change to a different combin
|
||||
<translation>Number</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZTool.qml" line="92" />
|
||||
<location filename="../qml/Capture/TZTool.qml" line="92" />
|
||||
<source>OCR</source>
|
||||
<translation>OCR</translation>
|
||||
</message>
|
||||
@@ -703,7 +703,7 @@ If it conflicts with system or other app shortcuts, change to a different combin
|
||||
<translation type="vanished">Failed to save screenshot file</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZWindow.qml" line="563" />
|
||||
<location filename="../qml/Capture/TZWindow.qml" line="563" />
|
||||
<source>%1 * %2</source>
|
||||
<translation>%1 * %2</translation>
|
||||
</message>
|
||||
@@ -724,57 +724,57 @@ If it conflicts with system or other app shortcuts, change to a different combin
|
||||
<translation type="vanished">Failed to copy screenshot to clipboard</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZWindow.qml" line="134" />
|
||||
<location filename="../qml/Capture/TZWindow.qml" line="134" />
|
||||
<source>Capture done</source>
|
||||
<translation>Capture done</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZWindow.qml" line="134" />
|
||||
<location filename="../qml/Capture/TZWindow.qml" line="134" />
|
||||
<source>Copied to clipboard</source>
|
||||
<translation>Copied to clipboard</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZWindow.qml" line="136" />
|
||||
<location filename="../qml/Capture/TZWindow.qml" line="136" />
|
||||
<source>Capture failed</source>
|
||||
<translation>Capture failed</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZWindow.qml" line="136" />
|
||||
<location filename="../qml/Capture/TZWindow.qml" line="136" />
|
||||
<source>Failed to copy image</source>
|
||||
<translation>Failed to copy image</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZWindow.qml" line="434" />
|
||||
<location filename="../qml/Capture/TZWindow.qml" line="434" />
|
||||
<source>Select save location</source>
|
||||
<translation>Select save location</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZWindow.qml" line="442" />
|
||||
<location filename="../qml/Capture/TZWindow.qml" line="442" />
|
||||
<source>Save success</source>
|
||||
<translation>Save success</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZWindow.qml" line="442" />
|
||||
<location filename="../qml/Capture/TZWindow.qml" line="442" />
|
||||
<source>Screenshot saved to file</source>
|
||||
<translation>Screenshot saved to file</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZWindow.qml" line="445" />
|
||||
<location filename="../qml/Capture/TZWindow.qml" line="445" />
|
||||
<source>Save failed</source>
|
||||
<translation>Save failed</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZWindow.qml" line="445" />
|
||||
<location filename="../qml/Capture/TZWindow.qml" line="445" />
|
||||
<source>Failed to save screenshot file</source>
|
||||
<translation>Failed to save screenshot file</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZWindow.qml" line="705" />
|
||||
<location filename="../qml/Capture/TZWindow.qml" line="705" />
|
||||
<source>Cancel</source>
|
||||
<translation>Cancel</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZWindow.qml" line="727" />
|
||||
<location filename="../qml/Capture/TZWindow.qml" line="727" />
|
||||
<source>OK</source>
|
||||
<translation>OK</translation>
|
||||
</message>
|
||||
|
||||
+99
-99
@@ -82,37 +82,37 @@
|
||||
<context>
|
||||
<name>TZChat</name>
|
||||
<message>
|
||||
<location filename="../qml/TZChat.qml" line="83"/>
|
||||
<location filename="../qml/Windows/TZChat.qml" line="83"/>
|
||||
<source>AI 图像编辑</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZChat.qml" line="162"/>
|
||||
<location filename="../qml/Windows/TZChat.qml" line="162"/>
|
||||
<source>当前贴图</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZChat.qml" line="171"/>
|
||||
<location filename="../qml/Windows/TZChat.qml" line="171"/>
|
||||
<source>未关联贴图</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZChat.qml" line="178"/>
|
||||
<location filename="../qml/Windows/TZChat.qml" line="178"/>
|
||||
<source>发送后将对此图执行 AI 编辑</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZChat.qml" line="203"/>
|
||||
<location filename="../qml/Windows/TZChat.qml" line="203"/>
|
||||
<source>描述您想要的修改...</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZChat.qml" line="228"/>
|
||||
<location filename="../qml/Windows/TZChat.qml" line="228"/>
|
||||
<source>取消</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZChat.qml" line="245"/>
|
||||
<location filename="../qml/Windows/TZChat.qml" line="245"/>
|
||||
<source>发送</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
@@ -178,7 +178,7 @@
|
||||
<context>
|
||||
<name>TZGifRecordBar</name>
|
||||
<message>
|
||||
<location filename="../qml/TZGifRecordBar.qml" line="186"/>
|
||||
<location filename="../qml/Capture/TZGifRecordBar.qml" line="186"/>
|
||||
<source>正在生成 GIF...</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
@@ -254,52 +254,52 @@
|
||||
<context>
|
||||
<name>TZMarkToolInfo</name>
|
||||
<message>
|
||||
<location filename="../qml/TZMarkToolInfo.qml" line="13"/>
|
||||
<location filename="../qml/Capture/TZMarkToolInfo.qml" line="13"/>
|
||||
<source>文本</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZMarkToolInfo.qml" line="62"/>
|
||||
<location filename="../qml/Capture/TZMarkToolInfo.qml" line="62"/>
|
||||
<source>细</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZMarkToolInfo.qml" line="63"/>
|
||||
<location filename="../qml/Capture/TZMarkToolInfo.qml" line="63"/>
|
||||
<source>中</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZMarkToolInfo.qml" line="64"/>
|
||||
<location filename="../qml/Capture/TZMarkToolInfo.qml" line="64"/>
|
||||
<source>粗</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZMarkToolInfo.qml" line="65"/>
|
||||
<location filename="../qml/Capture/TZMarkToolInfo.qml" line="65"/>
|
||||
<source>特粗</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZMarkToolInfo.qml" line="133"/>
|
||||
<location filename="../qml/Capture/TZMarkToolInfo.qml" line="133"/>
|
||||
<source>输入文字</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZMarkToolInfo.qml" line="140"/>
|
||||
<location filename="../qml/Capture/TZMarkToolInfo.qml" line="140"/>
|
||||
<source>背景</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZMarkToolInfo.qml" line="152"/>
|
||||
<location filename="../qml/Capture/TZMarkToolInfo.qml" line="152"/>
|
||||
<source>模糊</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZMarkToolInfo.qml" line="177"/>
|
||||
<location filename="../qml/Capture/TZMarkToolInfo.qml" line="177"/>
|
||||
<source>自动递增</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZMarkToolInfo.qml" line="187"/>
|
||||
<location filename="../qml/Capture/TZMarkToolInfo.qml" line="187"/>
|
||||
<source>数字</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
@@ -307,32 +307,32 @@
|
||||
<context>
|
||||
<name>TZOcrResult</name>
|
||||
<message>
|
||||
<location filename="../qml/TZOcrResult.qml" line="67"/>
|
||||
<location filename="../qml/Windows/TZOcrResult.qml" line="67"/>
|
||||
<source>OCR 识别结果</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZOcrResult.qml" line="167"/>
|
||||
<location filename="../qml/Windows/TZOcrResult.qml" line="167"/>
|
||||
<source>正在识别中…</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZOcrResult.qml" line="190"/>
|
||||
<location filename="../qml/Windows/TZOcrResult.qml" line="190"/>
|
||||
<source>识别结果将显示在这里…</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZOcrResult.qml" line="239"/>
|
||||
<location filename="../qml/Windows/TZOcrResult.qml" line="239"/>
|
||||
<source>复制全部</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZOcrResult.qml" line="265"/>
|
||||
<location filename="../qml/Windows/TZOcrResult.qml" line="265"/>
|
||||
<source>关闭</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZOcrResult.qml" line="293"/>
|
||||
<location filename="../qml/Windows/TZOcrResult.qml" line="293"/>
|
||||
<source>已复制!</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
@@ -366,170 +366,170 @@
|
||||
<context>
|
||||
<name>TZStickyWindow</name>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="324"/>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="325"/>
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="324"/>
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="325"/>
|
||||
<source>OCR</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="341"/>
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="341"/>
|
||||
<source>Pencil</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="350"/>
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="350"/>
|
||||
<source>Rectangle</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="359"/>
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="359"/>
|
||||
<source>Circle</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="368"/>
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="368"/>
|
||||
<source>Arrow</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="382"/>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="383"/>
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="382"/>
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="383"/>
|
||||
<source>Mosaic</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="404"/>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="539"/>
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="404"/>
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="539"/>
|
||||
<source>缩小</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="406"/>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="540"/>
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="406"/>
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="540"/>
|
||||
<source>放大</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="407"/>
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="407"/>
|
||||
<source>旋转90°</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="408"/>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="542"/>
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="408"/>
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="542"/>
|
||||
<source>镜像</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="409"/>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="543"/>
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="409"/>
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="543"/>
|
||||
<source>恢复原始大小</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="415"/>
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="415"/>
|
||||
<source>完成并复制</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="422"/>
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="422"/>
|
||||
<source>撤销</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="434"/>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="435"/>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="523"/>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="558"/>
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="434"/>
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="435"/>
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="523"/>
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="558"/>
|
||||
<source>隐藏工具栏</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="515"/>
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="515"/>
|
||||
<source>当前透明度 %1%</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="518"/>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="572"/>
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="518"/>
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="572"/>
|
||||
<source>覆盖贴图(合并标注)</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="519"/>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="573"/>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="580"/>
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="519"/>
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="573"/>
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="580"/>
|
||||
<source>复制贴图</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="520"/>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="566"/>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="581"/>
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="520"/>
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="566"/>
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="581"/>
|
||||
<source>AI 编辑</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="523"/>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="558"/>
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="523"/>
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="558"/>
|
||||
<source>显示工具栏</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="530"/>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="583"/>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="588"/>
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="530"/>
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="583"/>
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="588"/>
|
||||
<source>关闭窗口</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="534"/>
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="534"/>
|
||||
<source>透明度 +5%</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="535"/>
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="535"/>
|
||||
<source>透明度 -5%</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="541"/>
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="541"/>
|
||||
<source>旋转 90°</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="548"/>
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="548"/>
|
||||
<source>重置位置</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="570"/>
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="570"/>
|
||||
<source>保存原图</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="571"/>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="602"/>
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="571"/>
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="602"/>
|
||||
<source>另存为(合并标注)</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="578"/>
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="578"/>
|
||||
<source>常用项置顶</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="579"/>
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="579"/>
|
||||
<source>覆盖贴图</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="582"/>
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="582"/>
|
||||
<source>显示/隐藏工具栏</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZStickyWindow.qml" line="593"/>
|
||||
<location filename="../qml/Windows/TZStickyWindow.qml" line="593"/>
|
||||
<source>选择保存位置</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
@@ -537,77 +537,77 @@
|
||||
<context>
|
||||
<name>TZTool</name>
|
||||
<message>
|
||||
<location filename="../qml/TZTool.qml" line="83"/>
|
||||
<location filename="../qml/Capture/TZTool.qml" line="83"/>
|
||||
<source>取消</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZTool.qml" line="76"/>
|
||||
<location filename="../qml/Capture/TZTool.qml" line="76"/>
|
||||
<source>完成</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZTool.qml" line="92"/>
|
||||
<location filename="../qml/Capture/TZTool.qml" line="92"/>
|
||||
<source>OCR</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZTool.qml" line="100"/>
|
||||
<location filename="../qml/Capture/TZTool.qml" line="100"/>
|
||||
<source>录制GIF</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZTool.qml" line="109"/>
|
||||
<location filename="../qml/Capture/TZTool.qml" line="109"/>
|
||||
<source>保存</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZTool.qml" line="116"/>
|
||||
<location filename="../qml/Capture/TZTool.qml" line="116"/>
|
||||
<source>贴图</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZTool.qml" line="127"/>
|
||||
<location filename="../qml/Capture/TZTool.qml" line="127"/>
|
||||
<source>矩形</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZTool.qml" line="136"/>
|
||||
<location filename="../qml/Capture/TZTool.qml" line="136"/>
|
||||
<source>圆形</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZTool.qml" line="145"/>
|
||||
<location filename="../qml/Capture/TZTool.qml" line="145"/>
|
||||
<source>箭头</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZTool.qml" line="154"/>
|
||||
<location filename="../qml/Capture/TZTool.qml" line="154"/>
|
||||
<source>画笔</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZTool.qml" line="163"/>
|
||||
<location filename="../qml/Capture/TZTool.qml" line="163"/>
|
||||
<source>高亮</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZTool.qml" line="172"/>
|
||||
<location filename="../qml/Capture/TZTool.qml" line="172"/>
|
||||
<source>马赛克</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZTool.qml" line="181"/>
|
||||
<location filename="../qml/Capture/TZTool.qml" line="181"/>
|
||||
<source>文字</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZTool.qml" line="190"/>
|
||||
<location filename="../qml/Capture/TZTool.qml" line="190"/>
|
||||
<source>序号</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZTool.qml" line="197"/>
|
||||
<location filename="../qml/Capture/TZTool.qml" line="197"/>
|
||||
<source>撤销</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
@@ -615,62 +615,62 @@
|
||||
<context>
|
||||
<name>TZWindow</name>
|
||||
<message>
|
||||
<location filename="../qml/TZWindow.qml" line="134"/>
|
||||
<location filename="../qml/Capture/TZWindow.qml" line="134"/>
|
||||
<source>Capture done</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZWindow.qml" line="134"/>
|
||||
<location filename="../qml/Capture/TZWindow.qml" line="134"/>
|
||||
<source>Copied to clipboard</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZWindow.qml" line="136"/>
|
||||
<location filename="../qml/Capture/TZWindow.qml" line="136"/>
|
||||
<source>Capture failed</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZWindow.qml" line="136"/>
|
||||
<location filename="../qml/Capture/TZWindow.qml" line="136"/>
|
||||
<source>Failed to copy image</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZWindow.qml" line="434"/>
|
||||
<location filename="../qml/Capture/TZWindow.qml" line="434"/>
|
||||
<source>Select save location</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZWindow.qml" line="442"/>
|
||||
<location filename="../qml/Capture/TZWindow.qml" line="442"/>
|
||||
<source>Save success</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZWindow.qml" line="442"/>
|
||||
<location filename="../qml/Capture/TZWindow.qml" line="442"/>
|
||||
<source>Screenshot saved to file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZWindow.qml" line="445"/>
|
||||
<location filename="../qml/Capture/TZWindow.qml" line="445"/>
|
||||
<source>Save failed</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZWindow.qml" line="445"/>
|
||||
<location filename="../qml/Capture/TZWindow.qml" line="445"/>
|
||||
<source>Failed to save screenshot file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZWindow.qml" line="563"/>
|
||||
<location filename="../qml/Capture/TZWindow.qml" line="563"/>
|
||||
<source>%1 * %2</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZWindow.qml" line="705"/>
|
||||
<location filename="../qml/Capture/TZWindow.qml" line="705"/>
|
||||
<source>Cancel</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/TZWindow.qml" line="727"/>
|
||||
<location filename="../qml/Capture/TZWindow.qml" line="727"/>
|
||||
<source>OK</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
||||
@@ -19,16 +19,13 @@
|
||||
<file>qml/Capture/TZMagnifyingGlass.qml</file>
|
||||
<file>qml/Capture/TZGifRecordBar.qml</file>
|
||||
<file>qml/Windows/TZStickyWindow.qml</file>
|
||||
<file>qml/Windows/TZStickyExtraToolBar.qml</file>
|
||||
<file>qml/Windows/TZOcrResult.qml</file>
|
||||
<file>qml/Windows/TZChat.qml</file>
|
||||
<file>qml/Windows/TZLoadingSpinner.qml</file>
|
||||
<file>qml/Setting/TZSettingWin.qml</file>
|
||||
<file>qml/Setting/TZGeneralSettings.qml</file>
|
||||
<file>qml/Setting/TZRightPanel.qml</file>
|
||||
<file>qml/TZControls/qmldir</file>
|
||||
<file>qml/TZControls/TZComboBox.qml</file>
|
||||
<file>qml/TZStickyContextMenu.qml</file>
|
||||
<file>qml/Setting/TZHotkeySettings.qml</file>
|
||||
<file>qml/Setting/TZAboutWin.qml</file>
|
||||
<file>resource/img/lc_check.svg</file>
|
||||
@@ -39,6 +36,7 @@
|
||||
<file>resource/img/lc_pencil.svg</file>
|
||||
<file>resource/img/lc_circle.svg</file>
|
||||
<file>resource/img/lc_arrow.svg</file>
|
||||
<file>resource/img/lc_blur.svg</file>
|
||||
<file>resource/img/lc_mosaic.svg</file>
|
||||
<file>resource/img/lc_text.svg</file>
|
||||
<file>resource/img/lc_list_ordered.svg</file>
|
||||
@@ -47,8 +45,6 @@
|
||||
<file>resource/img/tray_icon.svg</file>
|
||||
<file>resource/img/lc_close.svg</file>
|
||||
<file>resource/img/lc_chat.svg</file>
|
||||
<file>resource/img/lc_emoji.svg</file>
|
||||
<file>resource/img/lc_file.svg</file>
|
||||
<file>resource/img/lc_send.svg</file>
|
||||
<file>qml/LongCapture/TZLongCaptureBar.qml</file>
|
||||
<file>qml/LongCapture/TZLongCapturePreview.qml</file>
|
||||
@@ -57,5 +53,6 @@
|
||||
<file>resource/img/lc_gif_stop.svg</file>
|
||||
<file>resource/img/lc_ocr.svg</file>
|
||||
<file>resource/img/lc_longshot.svg</file>
|
||||
<file>resource/qss/sticky_pin_widget.qss</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
+59
-46
@@ -1,17 +1,14 @@
|
||||
import QtQuick 2.12
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.5
|
||||
|
||||
Rectangle {
|
||||
id: tool
|
||||
radius: 10
|
||||
color: "#FFFFFF"
|
||||
visible: false
|
||||
border.color: "#E2E8F0"
|
||||
border.width: 1
|
||||
layer.enabled: true
|
||||
implicitWidth: toolRow.implicitWidth + 12
|
||||
implicitHeight: 44
|
||||
|
||||
signal signalScreenshot
|
||||
signal signalCancel
|
||||
@@ -42,6 +39,18 @@ Rectangle {
|
||||
property bool showMosaicButton: true
|
||||
property bool showTextButton: true
|
||||
property bool showNumberButton: true
|
||||
readonly property int buttonSize: 32
|
||||
readonly property int buttonRadius: 7
|
||||
readonly property int iconSize: 18
|
||||
readonly property int dividerHeight: 20
|
||||
readonly property int outerRadius: 10
|
||||
readonly property int outerMargin: 6
|
||||
readonly property int bubbleRadius: 6
|
||||
readonly property int bubbleHeight: 24
|
||||
readonly property int bubblePadding: 14
|
||||
readonly property int bubbleGap: 6
|
||||
readonly property int tipFontSize: 11
|
||||
readonly property int rowSpacing: 2
|
||||
|
||||
readonly property int shapeType: {
|
||||
switch (activeTool) {
|
||||
@@ -82,161 +91,165 @@ Rectangle {
|
||||
hoverTipText = ""
|
||||
}
|
||||
|
||||
radius: outerRadius
|
||||
implicitWidth: toolRow.implicitWidth + outerMargin * 2
|
||||
implicitHeight: Math.max(buttonSize + outerMargin * 2, 44)
|
||||
|
||||
RowLayout {
|
||||
id: toolRow
|
||||
anchors.fill: parent
|
||||
anchors.margins: 6
|
||||
anchors.margins: tool.outerMargin
|
||||
layoutDirection: Qt.RightToLeft
|
||||
spacing: 2
|
||||
spacing: tool.rowSpacing
|
||||
|
||||
Rectangle {
|
||||
visible: tool.showScreenshotButton
|
||||
width: 32; height: 32; radius: 7
|
||||
width: tool.buttonSize; height: tool.buttonSize; radius: tool.buttonRadius
|
||||
color: checkArea.containsMouse ? "#DCFCE7" : "transparent"
|
||||
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_check.svg"; width: 18; height: 18 }
|
||||
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_check.svg"; width: tool.iconSize; height: tool.iconSize }
|
||||
MouseArea { id: checkArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("完成"), parent); onExited: tool.hideTip(qsTr("完成")); onPressed: signalScreenshot() }
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: tool.showCancelButton
|
||||
width: 32; height: 32; radius: 7
|
||||
width: tool.buttonSize; height: tool.buttonSize; radius: tool.buttonRadius
|
||||
color: closeArea.containsMouse ? "#FEE2E2" : "transparent"
|
||||
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_x.svg"; width: 18; height: 18 }
|
||||
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_x.svg"; width: tool.iconSize; height: tool.iconSize }
|
||||
MouseArea { id: closeArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("取消"), parent); onExited: tool.hideTip(qsTr("取消")); onPressed: signalCancel() }
|
||||
}
|
||||
|
||||
Rectangle { visible: tool.showScreenshotButton || tool.showCancelButton; width: 1; height: 20; color: "#E2E8F0"; Layout.alignment: Qt.AlignVCenter }
|
||||
Rectangle { visible: tool.showScreenshotButton || tool.showCancelButton; width: 1; height: tool.dividerHeight; color: "#E2E8F0"; Layout.alignment: Qt.AlignVCenter }
|
||||
|
||||
Rectangle {
|
||||
visible: tool.showOcrButton
|
||||
width: 32; height: 32; radius: 7
|
||||
width: tool.buttonSize; height: tool.buttonSize; radius: tool.buttonRadius
|
||||
color: ocrArea.containsMouse ? "#F0FDF4" : "transparent"
|
||||
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_ocr.svg"; width: 18; height: 18 }
|
||||
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_ocr.svg"; width: tool.iconSize; height: tool.iconSize }
|
||||
MouseArea { id: ocrArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("OCR"), parent); onExited: tool.hideTip(qsTr("OCR")); onPressed: signalOcr() }
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: gifRecordBtn
|
||||
visible: tool.showGifRecordButton
|
||||
width: 32; height: 32; radius: 7
|
||||
width: tool.buttonSize; height: tool.buttonSize; radius: tool.buttonRadius
|
||||
color: gifArea.containsMouse ? "#FFF7ED" : "transparent"
|
||||
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_gif_record.svg"; width: 18; height: 18 }
|
||||
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_gif_record.svg"; width: tool.iconSize; height: tool.iconSize }
|
||||
MouseArea { id: gifArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("录制GIF"), parent); onExited: tool.hideTip(qsTr("录制GIF")); onPressed: signalGifRecord() }
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: tool.showLongScreenshotButton
|
||||
width: 32; height: 32; radius: 7
|
||||
width: tool.buttonSize; height: tool.buttonSize; radius: tool.buttonRadius
|
||||
color: longShotArea.containsMouse ? "#FFF7ED" : "transparent"
|
||||
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_longshot.svg"; width: 18; height: 18 }
|
||||
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_longshot.svg"; width: tool.iconSize; height: tool.iconSize }
|
||||
MouseArea { id: longShotArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("长截图"), parent); onExited: tool.hideTip(qsTr("长截图")); onPressed: signalLongScreenshot() }
|
||||
}
|
||||
|
||||
Rectangle { visible: tool.showOcrButton || tool.showGifRecordButton || tool.showLongScreenshotButton; width: 1; height: 20; color: "#E2E8F0"; Layout.alignment: Qt.AlignVCenter }
|
||||
Rectangle { visible: tool.showOcrButton || tool.showGifRecordButton || tool.showLongScreenshotButton; width: 1; height: tool.dividerHeight; color: "#E2E8F0"; Layout.alignment: Qt.AlignVCenter }
|
||||
|
||||
Rectangle {
|
||||
visible: tool.showSaveButton
|
||||
width: 32; height: 32; radius: 7
|
||||
width: tool.buttonSize; height: tool.buttonSize; radius: tool.buttonRadius
|
||||
color: saveArea.containsMouse ? "#EFF6FF" : "transparent"
|
||||
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_save.svg"; width: 18; height: 18 }
|
||||
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_save.svg"; width: tool.iconSize; height: tool.iconSize }
|
||||
MouseArea { id: saveArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("保存"), parent); onExited: tool.hideTip(qsTr("保存")); onPressed: signalSave() }
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: tool.showStickyButton
|
||||
width: 32; height: 32; radius: 7
|
||||
width: tool.buttonSize; height: tool.buttonSize; radius: tool.buttonRadius
|
||||
color: fixArea.containsMouse ? "#EFF6FF" : "transparent"
|
||||
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_pin.svg"; width: 18; height: 18 }
|
||||
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_pin.svg"; width: tool.iconSize; height: tool.iconSize }
|
||||
MouseArea { id: fixArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("贴图"), parent); onExited: tool.hideTip(qsTr("贴图")); onPressed: signalSticky() }
|
||||
}
|
||||
|
||||
Rectangle { visible: tool.showSaveButton || tool.showStickyButton; width: 1; height: 20; color: "#E2E8F0"; Layout.alignment: Qt.AlignVCenter }
|
||||
Rectangle { visible: tool.showSaveButton || tool.showStickyButton; width: 1; height: tool.dividerHeight; color: "#E2E8F0"; Layout.alignment: Qt.AlignVCenter }
|
||||
|
||||
Rectangle {
|
||||
visible: tool.showRectButton
|
||||
width: 32; height: 32; radius: 7
|
||||
width: tool.buttonSize; height: tool.buttonSize; radius: tool.buttonRadius
|
||||
color: (tool.activeTool === "rect") ? "#EFF6FF" : (rectArea.containsMouse ? "#F1F5F9" : "transparent")
|
||||
border.color: (tool.activeTool === "rect") ? "#3B82F6" : "transparent"
|
||||
border.width: 1
|
||||
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_square.svg"; width: 18; height: 18 }
|
||||
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_square.svg"; width: tool.iconSize; height: tool.iconSize }
|
||||
MouseArea { id: rectArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("矩形"), parent); onExited: tool.hideTip(qsTr("矩形")); onPressed: tool.setActiveTool("rect") }
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: tool.showCircleButton
|
||||
width: 32; height: 32; radius: 7
|
||||
width: tool.buttonSize; height: tool.buttonSize; radius: tool.buttonRadius
|
||||
color: (tool.activeTool === "circle") ? "#EFF6FF" : (circleArea.containsMouse ? "#F1F5F9" : "transparent")
|
||||
border.color: (tool.activeTool === "circle") ? "#3B82F6" : "transparent"
|
||||
border.width: 1
|
||||
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_circle.svg"; width: 18; height: 18 }
|
||||
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_circle.svg"; width: tool.iconSize; height: tool.iconSize }
|
||||
MouseArea { id: circleArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("圆形"), parent); onExited: tool.hideTip(qsTr("圆形")); onPressed: tool.setActiveTool("circle") }
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: tool.showArrowButton
|
||||
width: 32; height: 32; radius: 7
|
||||
width: tool.buttonSize; height: tool.buttonSize; radius: tool.buttonRadius
|
||||
color: (tool.activeTool === "arrow") ? "#EFF6FF" : (arrowArea.containsMouse ? "#F1F5F9" : "transparent")
|
||||
border.color: (tool.activeTool === "arrow") ? "#3B82F6" : "transparent"
|
||||
border.width: 1
|
||||
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_arrow.svg"; width: 18; height: 18 }
|
||||
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_arrow.svg"; width: tool.iconSize; height: tool.iconSize }
|
||||
MouseArea { id: arrowArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("箭头"), parent); onExited: tool.hideTip(qsTr("箭头")); onPressed: tool.setActiveTool("arrow") }
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: tool.showPencilButton
|
||||
width: 32; height: 32; radius: 7
|
||||
width: tool.buttonSize; height: tool.buttonSize; radius: tool.buttonRadius
|
||||
color: (tool.activeTool === "pencil") ? "#EFF6FF" : (pencilArea.containsMouse ? "#F1F5F9" : "transparent")
|
||||
border.color: (tool.activeTool === "pencil") ? "#3B82F6" : "transparent"
|
||||
border.width: 1
|
||||
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_pencil.svg"; width: 18; height: 18 }
|
||||
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_pencil.svg"; width: tool.iconSize; height: tool.iconSize }
|
||||
MouseArea { id: pencilArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("画笔"), parent); onExited: tool.hideTip(qsTr("画笔")); onPressed: tool.setActiveTool("pencil") }
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: tool.showHighlightButton
|
||||
width: 32; height: 32; radius: 7
|
||||
width: tool.buttonSize; height: tool.buttonSize; radius: tool.buttonRadius
|
||||
color: (tool.activeTool === "highlight") ? "#EFF6FF" : (highlightArea.containsMouse ? "#F1F5F9" : "transparent")
|
||||
border.color: (tool.activeTool === "highlight") ? "#3B82F6" : "transparent"
|
||||
border.width: 1
|
||||
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_highlighter.svg"; width: 18; height: 18 }
|
||||
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_highlighter.svg"; width: tool.iconSize; height: tool.iconSize }
|
||||
MouseArea { id: highlightArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("高亮"), parent); onExited: tool.hideTip(qsTr("高亮")); onPressed: tool.setActiveTool("highlight") }
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: tool.showMosaicButton
|
||||
width: 32; height: 32; radius: 7
|
||||
width: tool.buttonSize; height: tool.buttonSize; radius: tool.buttonRadius
|
||||
color: (tool.activeTool === "mosaic") ? "#EFF6FF" : (mosaicArea.containsMouse ? "#F1F5F9" : "transparent")
|
||||
border.color: (tool.activeTool === "mosaic") ? "#3B82F6" : "transparent"
|
||||
border.width: 1
|
||||
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_mosaic.svg"; width: 18; height: 18 }
|
||||
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_mosaic.svg"; width: tool.iconSize; height: tool.iconSize }
|
||||
MouseArea { id: mosaicArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("马赛克"), parent); onExited: tool.hideTip(qsTr("马赛克")); onPressed: tool.setActiveTool("mosaic") }
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: tool.showTextButton
|
||||
width: 32; height: 32; radius: 7
|
||||
width: tool.buttonSize; height: tool.buttonSize; radius: tool.buttonRadius
|
||||
color: (tool.activeTool === "text") ? "#EFF6FF" : (textArea.containsMouse ? "#F1F5F9" : "transparent")
|
||||
border.color: (tool.activeTool === "text") ? "#3B82F6" : "transparent"
|
||||
border.width: 1
|
||||
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_text.svg"; width: 18; height: 18 }
|
||||
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_text.svg"; width: tool.iconSize; height: tool.iconSize }
|
||||
MouseArea { id: textArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("文字"), parent); onExited: tool.hideTip(qsTr("文字")); onPressed: tool.setActiveTool("text") }
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: tool.showNumberButton
|
||||
width: 32; height: 32; radius: 7
|
||||
width: tool.buttonSize; height: tool.buttonSize; radius: tool.buttonRadius
|
||||
color: (tool.activeTool === "number") ? "#EFF6FF" : (numberArea.containsMouse ? "#F1F5F9" : "transparent")
|
||||
border.color: (tool.activeTool === "number") ? "#3B82F6" : "transparent"
|
||||
border.width: 1
|
||||
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_list_ordered.svg"; width: 18; height: 18 }
|
||||
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_list_ordered.svg"; width: tool.iconSize; height: tool.iconSize }
|
||||
MouseArea { id: numberArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("序号"), parent); onExited: tool.hideTip(qsTr("序号")); onPressed: tool.setActiveTool("number") }
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: tool.showUndoButton
|
||||
width: 32; height: 32; radius: 7
|
||||
width: tool.buttonSize; height: tool.buttonSize; radius: tool.buttonRadius
|
||||
color: undoArea.containsMouse ? "#EFF6FF" : "transparent"
|
||||
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_undo.svg"; width: 18; height: 18 }
|
||||
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_undo.svg"; width: tool.iconSize; height: tool.iconSize }
|
||||
MouseArea { id: undoArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("撤销"), parent); onExited: tool.hideTip(qsTr("撤销")); onPressed: signalUndo() }
|
||||
}
|
||||
}
|
||||
@@ -246,20 +259,20 @@ Rectangle {
|
||||
visible: tool.visible && tool.hoverTipText.length > 0
|
||||
z: 100
|
||||
parent: tool.parent
|
||||
width: Math.max(44, tipText.implicitWidth + 14)
|
||||
height: 24
|
||||
radius: 6
|
||||
width: Math.max(36, tipText.implicitWidth + tool.bubblePadding)
|
||||
height: tool.bubbleHeight
|
||||
radius: tool.bubbleRadius
|
||||
color: "#0F172A"
|
||||
opacity: 0.92
|
||||
x: Math.max(tool.x, Math.min(tool.x + tool.width - width, tool.x + tool.hoverTipCenterX - width / 2))
|
||||
y: tool.y + tool.height + 6
|
||||
y: tool.y + tool.height + tool.bubbleGap
|
||||
|
||||
Text {
|
||||
id: tipText
|
||||
anchors.centerIn: parent
|
||||
text: tool.hoverTipText
|
||||
color: "#FFFFFF"
|
||||
font.pixelSize: 11
|
||||
font.pixelSize: tool.tipFontSize
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -233,7 +233,6 @@ Window {
|
||||
)
|
||||
|
||||
function createStickyWindow(imageUrl, imgRect) {
|
||||
console.log("imgRect-->",imgRect)
|
||||
return windowFactory.createStickyWindow(imageUrl, imgRect)
|
||||
}
|
||||
|
||||
@@ -550,7 +549,6 @@ Window {
|
||||
Qt.callLater(function() {
|
||||
var captureRect = normalizedScreenshotArea
|
||||
var url = O_ScreenCapture.captureRectToStickyUrl(paintBoard, captureRect)
|
||||
console.log("[Sticky] imageUrl:", url)
|
||||
O_StickyViewModel.requestSticky(url, captureRect)
|
||||
resetArea()
|
||||
})
|
||||
|
||||
-264
@@ -1,264 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Window
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls.Basic
|
||||
|
||||
QtObject {
|
||||
id: chatPanel
|
||||
|
||||
// 对应贴图的图片 URL,由外部绑定
|
||||
property string imageUrl: ""
|
||||
|
||||
signal signalSendPrompt(var text)
|
||||
|
||||
// 公开方法
|
||||
function open(px, py) {
|
||||
win.x = px
|
||||
win.y = py
|
||||
win.visible = true
|
||||
win.raise()
|
||||
win.requestActivate()
|
||||
}
|
||||
|
||||
function close() {
|
||||
win.visible = false
|
||||
}
|
||||
|
||||
// ── 内部 Window ─────────────────────────────────────
|
||||
property var win: Window {
|
||||
id: win
|
||||
flags: Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.Tool
|
||||
color: "transparent"
|
||||
width: 420
|
||||
height: 380
|
||||
visible: false
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: "#ffffff"
|
||||
radius: 10
|
||||
border.color: "#E2E8F0"
|
||||
border.width: 1
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 12
|
||||
spacing: 8
|
||||
|
||||
// ── 顶部标题栏 ────────────────────────────────────
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: 36
|
||||
color: "transparent"
|
||||
|
||||
// 拖动区域(z:-1 让关闭按钮的点击不被拦截)
|
||||
MouseArea {
|
||||
id: dragHandle
|
||||
anchors.fill: parent
|
||||
z: -1
|
||||
property point startPos
|
||||
|
||||
onPressed: startPos = Qt.point(mouseX, mouseY)
|
||||
onPositionChanged: {
|
||||
if (pressed) {
|
||||
win.x += mouseX - startPos.x
|
||||
win.y += mouseY - startPos.y
|
||||
}
|
||||
}
|
||||
cursorShape: pressed ? Qt.ClosedHandCursor : Qt.OpenHandCursor
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 8
|
||||
|
||||
Image {
|
||||
source: "qrc:/resource/img/lc_chat.svg"
|
||||
width: 20
|
||||
height: 20
|
||||
fillMode: Image.PreserveAspectFit
|
||||
}
|
||||
|
||||
Text {
|
||||
text: qsTr("AI 图像编辑")
|
||||
font.pixelSize: 14
|
||||
font.weight: Font.Medium
|
||||
color: "#1E293B"
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
// 关闭按钮
|
||||
Rectangle {
|
||||
width: 28
|
||||
height: 28
|
||||
radius: 6
|
||||
color: clearMouse.containsMouse ? "#FEE2E2" : "transparent"
|
||||
|
||||
Image {
|
||||
anchors.centerIn: parent
|
||||
source: "qrc:/resource/img/lc_close.svg"
|
||||
width: 14
|
||||
height: 14
|
||||
fillMode: Image.PreserveAspectFit
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: clearMouse
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: chatPanel.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 标题栏底部分割线
|
||||
Rectangle {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
height: 1
|
||||
color: "#E2E8F0"
|
||||
}
|
||||
}
|
||||
|
||||
// ── 贴图缩略图预览 ────────────────────────────────
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: 64
|
||||
radius: 6
|
||||
color: "#F1F5F9"
|
||||
border.color: "#E2E8F0"
|
||||
border.width: 1
|
||||
clip: true
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 8
|
||||
spacing: 10
|
||||
|
||||
// 缩略图
|
||||
Rectangle {
|
||||
width: 48
|
||||
height: 48
|
||||
radius: 4
|
||||
color: "#E2E8F0"
|
||||
clip: true
|
||||
|
||||
Image {
|
||||
anchors.fill: parent
|
||||
source: chatPanel.imageUrl
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
cache: false
|
||||
}
|
||||
}
|
||||
|
||||
// 文字说明
|
||||
Column {
|
||||
Layout.fillWidth: true
|
||||
spacing: 3
|
||||
|
||||
Text {
|
||||
text: qsTr("当前贴图")
|
||||
font.pixelSize: 11
|
||||
color: "#94A3B8"
|
||||
}
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
text: chatPanel.imageUrl !== ""
|
||||
? chatPanel.imageUrl.replace(/^image:\/\/[^/]+\//, "ID: ")
|
||||
: qsTr("未关联贴图")
|
||||
font.pixelSize: 12
|
||||
color: "#475569"
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Text {
|
||||
text: qsTr("发送后将对此图执行 AI 编辑")
|
||||
font.pixelSize: 10
|
||||
color: "#94A3B8"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── 输入区域 ──────────────────────────────────────
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
color: "#F8FAFC"
|
||||
radius: 8
|
||||
border.color: "#E2E8F0"
|
||||
border.width: 1
|
||||
|
||||
TextArea {
|
||||
id: input
|
||||
anchors.fill: parent
|
||||
anchors.margins: 12
|
||||
font.family: "Segoe UI"
|
||||
font.pixelSize: 13
|
||||
color: "#1E293B"
|
||||
wrapMode: Text.Wrap
|
||||
placeholderText: qsTr("描述您想要的修改...")
|
||||
background: Rectangle { color: "transparent" }
|
||||
selectByMouse: true
|
||||
focus: true
|
||||
}
|
||||
}
|
||||
|
||||
// ── 底部工具栏 ────────────────────────────────────
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: 40
|
||||
color: "transparent"
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 8
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
// 取消按钮
|
||||
Rectangle {
|
||||
width: 70; height: 32; radius: 6
|
||||
color: cancelMouse.containsMouse ? "#F1F5F9" : "#F8FAFC"
|
||||
border.color: "#E2E8F0"; border.width: 1
|
||||
|
||||
Text { anchors.centerIn: parent; text: qsTr("取消"); font.pixelSize: 13; color: "#64748B" }
|
||||
|
||||
MouseArea {
|
||||
id: cancelMouse
|
||||
anchors.fill: parent; hoverEnabled: true
|
||||
onClicked: chatPanel.close()
|
||||
}
|
||||
}
|
||||
|
||||
// 发送按钮
|
||||
Rectangle {
|
||||
width: 70; height: 32; radius: 6
|
||||
color: sendMouse.pressed ? "#0066cc" : (sendMouse.containsMouse ? "#3399ff" : "#0088ff")
|
||||
|
||||
RowLayout {
|
||||
anchors.centerIn: parent; spacing: 4
|
||||
Image { source: "qrc:/resource/img/lc_send.svg"; width: 14; height: 14 }
|
||||
Text { text: qsTr("发送"); font.pixelSize: 13; color: "white" }
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: sendMouse
|
||||
anchors.fill: parent; hoverEnabled: true
|
||||
onClicked: {
|
||||
if (input.text.trim() !== "") {
|
||||
chatPanel.signalSendPrompt(input.text)
|
||||
chatPanel.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,194 +0,0 @@
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.5
|
||||
|
||||
// GIF 录制控制栏:在截图选区确定后显示
|
||||
Rectangle {
|
||||
id: gifBar
|
||||
radius: 10
|
||||
color: "#FFFFFF"
|
||||
border.color: "#E2E8F0"
|
||||
border.width: 1
|
||||
layer.enabled: true
|
||||
|
||||
// 外部传入的录制区域
|
||||
property rect captureRect: Qt.rect(0, 0, 0, 0)
|
||||
|
||||
signal signalCancel
|
||||
signal signalRecordFinished
|
||||
|
||||
readonly property bool isRecording: O_GifRecordVM.isRecording
|
||||
readonly property bool isEncoding: O_GifRecordVM.isEncoding
|
||||
readonly property int elapsedSecs: O_GifRecordVM.elapsedSecs
|
||||
readonly property int frameCount: O_GifRecordVM.frameCount
|
||||
|
||||
Connections {
|
||||
target: O_GifRecordVM
|
||||
|
||||
function onEncodingFinished(savedPath) {
|
||||
gifBar.signalRecordFinished()
|
||||
}
|
||||
|
||||
function onEncodingFailed(error) {
|
||||
console.warn("[GifRecordBar] 录制失败:", error)
|
||||
gifBar.signalRecordFinished()
|
||||
}
|
||||
}
|
||||
|
||||
SequentialAnimation {
|
||||
id: blinkAnim
|
||||
running: gifBar.isRecording
|
||||
loops: Animation.Infinite
|
||||
PropertyAnimation { target: recDot; property: "opacity"; to: 0.2; duration: 500 }
|
||||
PropertyAnimation { target: recDot; property: "opacity"; to: 1.0; duration: 500 }
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 6
|
||||
layoutDirection: Qt.RightToLeft
|
||||
spacing: 4
|
||||
|
||||
// 取消按钮
|
||||
Rectangle {
|
||||
width: 32; height: 32; radius: 7
|
||||
visible: !gifBar.isEncoding
|
||||
color: cancelArea.containsMouse ? "#FEE2E2" : "transparent"
|
||||
Behavior on color { ColorAnimation { duration: 100 } }
|
||||
Image {
|
||||
anchors.centerIn: parent
|
||||
source: "qrc:/resource/img/lc_x.svg"
|
||||
width: 18; height: 18
|
||||
fillMode: Image.PreserveAspectFit
|
||||
smooth: true
|
||||
}
|
||||
MouseArea {
|
||||
id: cancelArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onPressed: {
|
||||
O_GifRecordVM.cancelRecording()
|
||||
gifBar.signalCancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 停止录制按钮
|
||||
Rectangle {
|
||||
width: 32; height: 32; radius: 7
|
||||
visible: gifBar.isRecording && !gifBar.isEncoding
|
||||
color: stopArea.containsMouse ? "#FEF3C7" : "transparent"
|
||||
Behavior on color { ColorAnimation { duration: 100 } }
|
||||
Image {
|
||||
anchors.centerIn: parent
|
||||
source: "qrc:/resource/img/lc_gif_stop.svg"
|
||||
width: 18; height: 18
|
||||
fillMode: Image.PreserveAspectFit
|
||||
smooth: true
|
||||
layer.enabled: true
|
||||
layer.effect: null
|
||||
}
|
||||
MouseArea {
|
||||
id: stopArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onPressed: O_GifRecordVM.stopRecording()
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 1; height: 20
|
||||
visible: gifBar.isRecording && !gifBar.isEncoding
|
||||
color: "#E2E8F0"
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
// 录制中:时间 + 帧数
|
||||
Item {
|
||||
visible: gifBar.isRecording && !gifBar.isEncoding
|
||||
Layout.fillWidth: true
|
||||
height: 32
|
||||
implicitWidth: recRow.implicitWidth + 8
|
||||
|
||||
Row {
|
||||
id: recRow
|
||||
anchors.centerIn: parent
|
||||
spacing: 6
|
||||
|
||||
Rectangle {
|
||||
id: recDot
|
||||
width: 8; height: 8; radius: 4
|
||||
color: "#EF4444"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
text: {
|
||||
var m = Math.floor(gifBar.elapsedSecs / 60)
|
||||
var s = gifBar.elapsedSecs % 60
|
||||
return (m < 10 ? "0" + m : m) + ":" + (s < 10 ? "0" + s : s)
|
||||
}
|
||||
font.pixelSize: 13
|
||||
font.family: "monospace"
|
||||
color: "#1E293B"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
text: gifBar.frameCount + "f"
|
||||
font.pixelSize: 11
|
||||
color: "#94A3B8"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 编码中:进度提示
|
||||
Item {
|
||||
visible: gifBar.isEncoding
|
||||
Layout.fillWidth: true
|
||||
height: 32
|
||||
implicitWidth: encodingRow.implicitWidth + 16
|
||||
|
||||
Row {
|
||||
id: encodingRow
|
||||
anchors.centerIn: parent
|
||||
spacing: 8
|
||||
|
||||
Rectangle {
|
||||
width: 16; height: 16
|
||||
color: "transparent"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
Rectangle {
|
||||
width: 16; height: 16
|
||||
radius: 8
|
||||
color: "transparent"
|
||||
border.color: "#3B82F6"
|
||||
border.width: 2
|
||||
Rectangle {
|
||||
width: 6; height: 6
|
||||
color: "#3B82F6"
|
||||
radius: 3
|
||||
x: 5; y: -1
|
||||
}
|
||||
RotationAnimation on rotation {
|
||||
running: gifBar.isEncoding
|
||||
from: 0; to: 360
|
||||
duration: 800
|
||||
loops: Animation.Infinite
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: qsTr("正在生成 GIF...")
|
||||
font.pixelSize: 13
|
||||
color: "#1E293B"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
import QtQuick
|
||||
|
||||
// 显示当前截图大小
|
||||
Rectangle {
|
||||
property string lableText: ""
|
||||
|
||||
radius: 6
|
||||
color: "#1E293B"
|
||||
opacity: 0.85
|
||||
border.color: "#334155"
|
||||
border.width: 1
|
||||
|
||||
implicitWidth: labelContent.implicitWidth + 16
|
||||
implicitHeight: labelContent.implicitHeight + 8
|
||||
|
||||
Text {
|
||||
id: labelContent
|
||||
anchors.centerIn: parent
|
||||
font.pixelSize: 12
|
||||
font.family: "Microsoft YaHei"
|
||||
color: "#F1F5F9"
|
||||
text: lableText
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
import QtQuick
|
||||
|
||||
// ── AI 加载动画层 ─────────────────────────────────────
|
||||
Rectangle {
|
||||
|
||||
width: 100
|
||||
height: 100
|
||||
radius: 2
|
||||
color: "#99000000" // 半透明黑色遮罩
|
||||
|
||||
|
||||
// 旋转圆弧
|
||||
Item {
|
||||
id: spinnerRoot
|
||||
anchors.centerIn: parent
|
||||
width: 48
|
||||
height: 48
|
||||
|
||||
RotationAnimator {
|
||||
target: spinnerRoot
|
||||
from: 0; to: 360
|
||||
duration: 900
|
||||
loops: Animation.Infinite
|
||||
running: O_AIViewModel.isLoading
|
||||
}
|
||||
|
||||
// 四个小圆点模拟旋转圆弧
|
||||
Repeater {
|
||||
model: 8
|
||||
Rectangle {
|
||||
width: 6
|
||||
height: 6
|
||||
radius: 3
|
||||
color: "white"
|
||||
opacity: (index + 1) / 8
|
||||
x: spinnerRoot.width / 2 + 18 * Math.cos(index * Math.PI / 4) - width / 2
|
||||
y: spinnerRoot.height / 2 + 18 * Math.sin(index * Math.PI / 4) - height / 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 提示文字
|
||||
Text {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: spinnerRoot.bottom
|
||||
anchors.topMargin: 10
|
||||
text: "AI 生成中..."
|
||||
color: "white"
|
||||
font.pixelSize: 13
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Window
|
||||
import QtQuick.Layouts
|
||||
|
||||
Window {
|
||||
id: longBar
|
||||
width: 500
|
||||
height: 56
|
||||
visible: false
|
||||
color: "transparent"
|
||||
flags: Qt.Tool | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.WindowDoesNotAcceptFocus
|
||||
|
||||
signal startRequested()
|
||||
signal stopRequested()
|
||||
|
||||
property bool running: false
|
||||
property int frameCount: 0
|
||||
property string message: qsTr("点击开始后,滑动内容自动拼接")
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: 12
|
||||
color: "#111827"
|
||||
border.color: "#1F2937"
|
||||
border.width: 1
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 10
|
||||
spacing: 10
|
||||
|
||||
Rectangle {
|
||||
width: 26
|
||||
height: 26
|
||||
radius: 13
|
||||
color: "#F97316"
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "长"
|
||||
color: "#FFFFFF"
|
||||
font.pixelSize: 14
|
||||
font.bold: true
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
text: longBar.message
|
||||
color: "#E5E7EB"
|
||||
font.pixelSize: 13
|
||||
elide: Text.ElideRight
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
text: qsTr("%1 帧").arg(longBar.frameCount)
|
||||
color: "#D1D5DB"
|
||||
font.pixelSize: 12
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 72
|
||||
height: 30
|
||||
radius: 6
|
||||
visible: !longBar.running
|
||||
color: startArea.containsMouse ? "#1D4ED8" : "#2563EB"
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Behavior on color { ColorAnimation { duration: 100 } }
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: qsTr("开始")
|
||||
color: "#FFFFFF"
|
||||
font.pixelSize: 13
|
||||
}
|
||||
MouseArea {
|
||||
id: startArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: longBar.startRequested()
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 72
|
||||
height: 30
|
||||
radius: 6
|
||||
color: stopArea.containsMouse ? "#DC2626" : "#EF4444"
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Behavior on color { ColorAnimation { duration: 100 } }
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: longBar.running ? qsTr("完成") : qsTr("关闭")
|
||||
color: "#FFFFFF"
|
||||
font.pixelSize: 13
|
||||
}
|
||||
MouseArea {
|
||||
id: stopArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: longBar.stopRequested()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Window
|
||||
|
||||
Window {
|
||||
id: longFrame
|
||||
visible: false
|
||||
color: "transparent"
|
||||
flags: Qt.Tool | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.WindowTransparentForInput
|
||||
|
||||
property rect captureRect: Qt.rect(0, 0, 0, 0)
|
||||
|
||||
x: captureRect.x - 2
|
||||
y: captureRect.y - 2
|
||||
width: Math.max(0, captureRect.width) + 4
|
||||
height: Math.max(0, captureRect.height) + 4
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: "transparent"
|
||||
border.color: "#0EA5E9"
|
||||
border.width: 2
|
||||
radius: 2
|
||||
|
||||
Rectangle {
|
||||
x: 0
|
||||
y: 0
|
||||
width: 6
|
||||
height: 6
|
||||
color: "#0EA5E9"
|
||||
}
|
||||
Rectangle {
|
||||
x: parent.width - width
|
||||
y: 0
|
||||
width: 6
|
||||
height: 6
|
||||
color: "#0EA5E9"
|
||||
}
|
||||
Rectangle {
|
||||
x: 0
|
||||
y: parent.height - height
|
||||
width: 6
|
||||
height: 6
|
||||
color: "#0EA5E9"
|
||||
}
|
||||
Rectangle {
|
||||
x: parent.width - width
|
||||
y: parent.height - height
|
||||
width: 6
|
||||
height: 6
|
||||
color: "#0EA5E9"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Window
|
||||
|
||||
Window {
|
||||
id: longPreview
|
||||
width: 260
|
||||
height: 420
|
||||
visible: false
|
||||
color: "transparent"
|
||||
flags: Qt.Tool | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.WindowDoesNotAcceptFocus
|
||||
|
||||
property string previewUrl: ""
|
||||
property string statusText: ""
|
||||
property int frameCount: 0
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: 12
|
||||
color: "#111827"
|
||||
border.color: "#1F2937"
|
||||
border.width: 1
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 10
|
||||
spacing: 8
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: 8
|
||||
|
||||
Text {
|
||||
text: qsTr("长截图预览")
|
||||
color: "#F9FAFB"
|
||||
font.pixelSize: 13
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
Text {
|
||||
text: qsTr("%1 帧").arg(longPreview.frameCount)
|
||||
color: "#9CA3AF"
|
||||
font.pixelSize: 12
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: parent.height - 58
|
||||
radius: 8
|
||||
color: "#0B1220"
|
||||
border.color: "#334155"
|
||||
border.width: 1
|
||||
clip: true
|
||||
|
||||
Image {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 6
|
||||
source: longPreview.previewUrl
|
||||
fillMode: Image.PreserveAspectFit
|
||||
smooth: true
|
||||
cache: false
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
visible: longPreview.previewUrl.length === 0
|
||||
text: qsTr("暂无预览")
|
||||
color: "#64748B"
|
||||
font.pixelSize: 12
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
text: longPreview.statusText
|
||||
color: "#CBD5E1"
|
||||
font.pixelSize: 12
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Window
|
||||
|
||||
// ── 放大镜 ───────────────────────────────────────────────
|
||||
Rectangle {
|
||||
id: magnifier
|
||||
width: 130
|
||||
height: 170 // 额外 40px 供颜色信息显示
|
||||
z: 10
|
||||
color: "transparent"
|
||||
visible: root.visibility === Window.FullScreen
|
||||
|
||||
// 跟随鼠标移动,始终显示(不随截图状态切换)
|
||||
property int magnifierMouseX: 0
|
||||
property int magnifierMouseY: 0
|
||||
property int magnifierAbsX: 0
|
||||
property int magnifierAbsY: 0
|
||||
property color magnifierPixelColor: "#000000"
|
||||
|
||||
// 跟随鼠标,偏移 +15px,防止被遮挡;超出屏幕时翻转
|
||||
x: {
|
||||
var px = magnifierMouseX + 15
|
||||
if (px + width > root.width) px = magnifierMouseX - width - 15
|
||||
return px
|
||||
}
|
||||
y: {
|
||||
var py = magnifierMouseY + 15
|
||||
if (py + height > root.height) py = magnifierMouseY - height - 15
|
||||
return py
|
||||
}
|
||||
|
||||
// 外框
|
||||
Rectangle {
|
||||
id: magnifierFrame
|
||||
anchors.top: parent.top
|
||||
width: 130
|
||||
height: 130
|
||||
radius: 4
|
||||
color: "#1a1a1a"
|
||||
border.color: "#ffffff"
|
||||
border.width: 1
|
||||
clip: true
|
||||
|
||||
// 放大图像(来自 ImageProvider)
|
||||
Image {
|
||||
id: magnifierImage
|
||||
anchors.centerIn: parent
|
||||
width: 110
|
||||
height: 110
|
||||
// cache: false 确保每次坐标变化时重新请求图像
|
||||
cache: false
|
||||
// source 用时间戳 +坐标,强制 QML 重新加载
|
||||
source: root.visibility === Window.FullScreen
|
||||
? ("image://magnifier/" + magnifierAbsX + "," + magnifierAbsY + "?t=" + Date.now())
|
||||
: ""
|
||||
fillMode: Image.Stretch
|
||||
}
|
||||
|
||||
// 中心像素颜色色块(8×8,叠在图像中心上方,不遮挡准线)
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: 8
|
||||
height: 8
|
||||
color: magnifierPixelColor
|
||||
border.color: "#ffffff"
|
||||
border.width: 1
|
||||
visible: false // 色块已在 RGB 文本行体现,避免遮挡准线
|
||||
}
|
||||
}
|
||||
|
||||
// 颜色信息栏
|
||||
Rectangle {
|
||||
id: colorInfoBar
|
||||
anchors.top: magnifierFrame.bottom
|
||||
anchors.topMargin: 2
|
||||
width: 130
|
||||
height: 38
|
||||
radius: 4
|
||||
color: "#cc1a1a1a"
|
||||
border.color: "#555555"
|
||||
border.width: 1
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: 6
|
||||
|
||||
// 色块
|
||||
Rectangle {
|
||||
width: 16
|
||||
height: 16
|
||||
radius: 2
|
||||
color: magnifierPixelColor
|
||||
border.color: "#ffffff"
|
||||
border.width: 1
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
// RGB 文本
|
||||
Text {
|
||||
id: rgbText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: "#ffffff"
|
||||
font.pixelSize: 11
|
||||
font.family: "monospace"
|
||||
text: {
|
||||
var c = magnifierPixelColor
|
||||
var r = Math.round(c.r * 255)
|
||||
var g = Math.round(c.g * 255)
|
||||
var b = Math.round(c.b * 255)
|
||||
return "R:" + r + " G:" + g + " B:" + b
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// ── 放大镜 end ───────────────────────────────────────────
|
||||
@@ -1,257 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
|
||||
Rectangle {
|
||||
id: panel
|
||||
|
||||
property color currentColor: "#F43F5E"
|
||||
property int currentSize: 6
|
||||
property bool showColorRow: true
|
||||
property string toolName: ""
|
||||
|
||||
property string annotationText: qsTr("文本")
|
||||
property bool textBackgroundEnabled: true
|
||||
property int mosaicBlurLevel: 2
|
||||
property bool numberAutoIncrement: true
|
||||
property int numberValue: 1
|
||||
|
||||
width: 260
|
||||
height: toolName === "text" ? 128
|
||||
: (toolName === "number" ? 132
|
||||
: ((toolName === "mosaic") ? 92 : (showColorRow ? 84 : 50)))
|
||||
radius: 10
|
||||
color: "#FFFFFF"
|
||||
border.color: "#E2E8F0"
|
||||
border.width: 1
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 10
|
||||
spacing: 8
|
||||
|
||||
RowLayout {
|
||||
spacing: 6
|
||||
Layout.fillWidth: true
|
||||
visible: panel.showColorRow
|
||||
height: panel.showColorRow ? implicitHeight : 0
|
||||
|
||||
Repeater {
|
||||
model: ["#F43F5E","#EF4444","#F97316","#EAB308",
|
||||
"#22C55E","#3B82F6","#8B5CF6","#000000","#FFFFFF"]
|
||||
delegate: Rectangle {
|
||||
width: 18; height: 18; radius: 9
|
||||
color: modelData
|
||||
border.color: panel.currentColor === modelData ? "#3B82F6" : "#CBD5E1"
|
||||
border.width: panel.currentColor === modelData ? 2 : 1
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onPressed: panel.currentColor = modelData
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: 6
|
||||
Layout.fillWidth: true
|
||||
|
||||
Repeater {
|
||||
model: [
|
||||
{ size: 2, label: qsTr("细") },
|
||||
{ size: 4, label: qsTr("中") },
|
||||
{ size: 6, label: qsTr("粗") },
|
||||
{ size: 10, label: qsTr("特粗") }
|
||||
]
|
||||
delegate: Rectangle {
|
||||
width: 48; height: 28
|
||||
radius: 6
|
||||
color: panel.currentSize === modelData.size ? "#EFF6FF" : (sizeArea.containsMouse ? "#F1F5F9" : "transparent")
|
||||
border.color: panel.currentSize === modelData.size ? "#3B82F6" : "#E2E8F0"
|
||||
border.width: 1
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: 3
|
||||
Rectangle {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
width: 24
|
||||
height: Math.min(modelData.size, 6)
|
||||
radius: height / 2
|
||||
color: panel.currentSize === modelData.size ? "#3B82F6" : "#94A3B8"
|
||||
}
|
||||
Text {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: modelData.label
|
||||
font.pixelSize: 10
|
||||
color: panel.currentSize === modelData.size ? "#3B82F6" : "#64748B"
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: sizeArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onPressed: panel.currentSize = modelData.size
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
visible: panel.toolName === "text"
|
||||
Layout.fillWidth: true
|
||||
spacing: 8
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 34
|
||||
radius: 6
|
||||
color: "#F8FAFC"
|
||||
border.color: textInput.activeFocus ? "#3B82F6" : "#E2E8F0"
|
||||
border.width: 1
|
||||
|
||||
TextInput {
|
||||
id: textInput
|
||||
anchors.fill: parent
|
||||
anchors.margins: 8
|
||||
verticalAlignment: TextInput.AlignVCenter
|
||||
color: "#0F172A"
|
||||
font.pixelSize: 13
|
||||
selectByMouse: true
|
||||
text: panel.annotationText
|
||||
onTextChanged: panel.annotationText = text
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 8
|
||||
visible: textInput.text.length === 0 && !textInput.activeFocus
|
||||
text: qsTr("输入文字")
|
||||
color: "#94A3B8"
|
||||
font.pixelSize: 13
|
||||
}
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
text: qsTr("背景")
|
||||
checked: panel.textBackgroundEnabled
|
||||
onToggled: panel.textBackgroundEnabled = checked
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
visible: panel.toolName === "mosaic"
|
||||
Layout.fillWidth: true
|
||||
spacing: 8
|
||||
|
||||
Text {
|
||||
text: qsTr("模糊")
|
||||
color: "#334155"
|
||||
}
|
||||
|
||||
Slider {
|
||||
Layout.fillWidth: true
|
||||
from: 1
|
||||
to: 4
|
||||
stepSize: 1
|
||||
value: panel.mosaicBlurLevel
|
||||
onMoved: panel.mosaicBlurLevel = Math.round(value)
|
||||
}
|
||||
|
||||
Text {
|
||||
text: panel.mosaicBlurLevel
|
||||
color: "#64748B"
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
visible: panel.toolName === "number"
|
||||
Layout.fillWidth: true
|
||||
spacing: 8
|
||||
|
||||
CheckBox {
|
||||
text: qsTr("自动递增")
|
||||
checked: panel.numberAutoIncrement
|
||||
onToggled: panel.numberAutoIncrement = checked
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 8
|
||||
|
||||
Text {
|
||||
text: qsTr("数字")
|
||||
color: "#334155"
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 28
|
||||
height: 28
|
||||
radius: 6
|
||||
color: minusArea.containsMouse ? "#F1F5F9" : "transparent"
|
||||
border.color: "#CBD5E1"
|
||||
border.width: 1
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "-"
|
||||
color: "#334155"
|
||||
font.pixelSize: 16
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: minusArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onPressed: panel.numberValue = Math.max(1, panel.numberValue - 1)
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: 28
|
||||
radius: 6
|
||||
color: "#F8FAFC"
|
||||
border.color: "#E2E8F0"
|
||||
border.width: 1
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: panel.numberValue
|
||||
color: "#0F172A"
|
||||
font.pixelSize: 13
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 28
|
||||
height: 28
|
||||
radius: 6
|
||||
color: plusArea.containsMouse ? "#F1F5F9" : "transparent"
|
||||
border.color: "#CBD5E1"
|
||||
border.width: 1
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "+"
|
||||
color: "#334155"
|
||||
font.pixelSize: 16
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: plusArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onPressed: panel.numberValue = Math.min(9999, panel.numberValue + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,328 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Window
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
// TZOcrResult
|
||||
// -----------
|
||||
// 无边框悬浮窗,展示 OCR 识别结果。
|
||||
// 由 TZWindow.qml 通过 Qt.createComponent 动态创建并显示。
|
||||
|
||||
Window {
|
||||
id: ocrWin
|
||||
flags: Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.Tool
|
||||
color: "transparent"
|
||||
width: 420
|
||||
height: 320
|
||||
|
||||
// 初始居中(创建后由调用方设置位置,此处给默认值)
|
||||
x: (Screen.width - width) / 2
|
||||
y: (Screen.height - height) / 2
|
||||
|
||||
Component.onCompleted: {
|
||||
ocrWin.raise()
|
||||
ocrWin.requestActivate()
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: "Esc"
|
||||
onActivated: ocrWin.close()
|
||||
}
|
||||
|
||||
// ── 阴影容器 ──────────────────────────────────────────
|
||||
Rectangle {
|
||||
id: shadow
|
||||
anchors.fill: parent
|
||||
anchors.margins: 10
|
||||
radius: 12
|
||||
color: "white"
|
||||
layer.enabled: true
|
||||
border.color: "#D1D5DB"
|
||||
border.width: 1
|
||||
|
||||
// ── 标题栏 ─────────────────────────────────────────
|
||||
Rectangle {
|
||||
id: titleBar
|
||||
anchors.top: shadow.top
|
||||
anchors.left: shadow.left
|
||||
anchors.right: shadow.right
|
||||
height: 40
|
||||
radius: 12
|
||||
color: "#F8FAFC"
|
||||
|
||||
// 圆角只在上半部分(下方盖住)
|
||||
Rectangle {
|
||||
anchors.bottom: titleBar.bottom
|
||||
anchors.left: titleBar.left
|
||||
anchors.right: titleBar.right
|
||||
height: titleBar.radius
|
||||
color: titleBar.color
|
||||
}
|
||||
|
||||
// 标题文字
|
||||
Text {
|
||||
anchors.verticalCenter: titleBar.verticalCenter
|
||||
anchors.left: titleBar.left
|
||||
anchors.leftMargin: 14
|
||||
text: qsTr("OCR 识别结果")
|
||||
font.pixelSize: 14
|
||||
font.weight: Font.Medium
|
||||
color: "#1E293B"
|
||||
}
|
||||
|
||||
// 关闭按钮
|
||||
Rectangle {
|
||||
id: closeBtn
|
||||
anchors.right: titleBar.right
|
||||
anchors.rightMargin: 8
|
||||
anchors.verticalCenter: titleBar.verticalCenter
|
||||
width: 26; height: 26; radius: 5
|
||||
color: closeArea.containsMouse ? "#FEE2E2" : "transparent"
|
||||
Behavior on color { ColorAnimation { duration: 80 } }
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "✕"
|
||||
color: closeArea.containsMouse ? "#DC2626" : "#94A3B8"
|
||||
font.pixelSize: 13
|
||||
}
|
||||
MouseArea {
|
||||
id: closeArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: ocrWin.close()
|
||||
}
|
||||
}
|
||||
|
||||
// 拖动标题栏移动窗口
|
||||
MouseArea {
|
||||
anchors.fill: titleBar
|
||||
anchors.rightMargin: 40
|
||||
property point pressPos
|
||||
onPressed: function(mouse) {
|
||||
pressPos = Qt.point(mouse.x, mouse.y)
|
||||
}
|
||||
onPositionChanged: function(mouse) {
|
||||
if (pressed) {
|
||||
ocrWin.x += mouse.x - pressPos.x
|
||||
ocrWin.y += mouse.y - pressPos.y
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── 分割线 ─────────────────────────────────────────
|
||||
Rectangle {
|
||||
id: divider
|
||||
anchors.top: titleBar.bottom
|
||||
anchors.left: shadow.left
|
||||
anchors.right: shadow.right
|
||||
height: 1
|
||||
color: "#E2E8F0"
|
||||
}
|
||||
|
||||
// ── 内容区 ─────────────────────────────────────────
|
||||
Item {
|
||||
anchors.top: divider.bottom
|
||||
anchors.left: shadow.left
|
||||
anchors.right: shadow.right
|
||||
anchors.bottom: bottomBar.top
|
||||
|
||||
// Loading 遮罩
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: "#F8FAFC"
|
||||
visible: O_OcrVM.isRecognizing
|
||||
radius: 6
|
||||
|
||||
Item {
|
||||
id: spinnerRoot
|
||||
anchors.centerIn: parent
|
||||
width: 48; height: 48
|
||||
|
||||
RotationAnimator {
|
||||
target: spinnerRoot
|
||||
from: 0; to: 360
|
||||
duration: 900
|
||||
loops: Animation.Infinite
|
||||
running: O_OcrVM.isRecognizing
|
||||
}
|
||||
Repeater {
|
||||
model: 8
|
||||
Rectangle {
|
||||
width: 6; height: 6; radius: 3
|
||||
color: "#3B82F6"
|
||||
opacity: (index + 1) / 8
|
||||
x: spinnerRoot.width / 2 + 18 * Math.cos(index * Math.PI / 4) - width / 2
|
||||
y: spinnerRoot.height / 2 + 18 * Math.sin(index * Math.PI / 4) - height / 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: spinnerRoot.bottom
|
||||
anchors.topMargin: 12
|
||||
text: qsTr("正在识别中…")
|
||||
color: "#64748B"
|
||||
font.pixelSize: 13
|
||||
}
|
||||
}
|
||||
|
||||
// 文本区域(识别完成后显示)
|
||||
ScrollView {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 8
|
||||
visible: !O_OcrVM.isRecognizing
|
||||
clip: true
|
||||
|
||||
TextArea {
|
||||
id: resultArea
|
||||
text: O_OcrVM.resultText
|
||||
readOnly: false // 允许用户二次编辑
|
||||
wrapMode: TextEdit.Wrap
|
||||
font.pixelSize: 14
|
||||
color: "#1E293B"
|
||||
background: null
|
||||
padding: 4
|
||||
selectByMouse: true
|
||||
placeholderText: qsTr("识别结果将显示在这里…")
|
||||
placeholderTextColor: "#94A3B8"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── 底部操作栏 ─────────────────────────────────────
|
||||
Rectangle {
|
||||
id: bottomBar
|
||||
anchors.bottom: shadow.bottom
|
||||
anchors.left: shadow.left
|
||||
anchors.right: shadow.right
|
||||
height: 44
|
||||
radius: 12
|
||||
color: "#F8FAFC"
|
||||
|
||||
// 圆角只在下半部分
|
||||
Rectangle {
|
||||
anchors.top: bottomBar.top
|
||||
anchors.left: bottomBar.left
|
||||
anchors.right: bottomBar.right
|
||||
height: bottomBar.radius
|
||||
color: bottomBar.color
|
||||
}
|
||||
|
||||
// 分割线
|
||||
Rectangle {
|
||||
anchors.top: bottomBar.top
|
||||
anchors.left: bottomBar.left
|
||||
anchors.right: bottomBar.right
|
||||
height: 1
|
||||
color: "#E2E8F0"
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: 10
|
||||
|
||||
// 一键复制
|
||||
Rectangle {
|
||||
id: copyBtn
|
||||
width: 100; height: 30; radius: 6
|
||||
color: copyArea.containsMouse ? "#DBEAFE" : "#EFF6FF"
|
||||
border.color: "#3B82F6"
|
||||
border.width: 1
|
||||
Behavior on color { ColorAnimation { duration: 80 } }
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: qsTr("复制全部")
|
||||
color: "#3B82F6"
|
||||
font.pixelSize: 13
|
||||
}
|
||||
MouseArea {
|
||||
id: copyArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
O_OcrVM.copyResultToClipboard()
|
||||
copyFeedbackTimer.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭
|
||||
Rectangle {
|
||||
width: 70; height: 30; radius: 6
|
||||
color: closeBtn2Area.containsMouse ? "#F1F5F9" : "transparent"
|
||||
border.color: "#CBD5E1"
|
||||
border.width: 1
|
||||
Behavior on color { ColorAnimation { duration: 80 } }
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: qsTr("关闭")
|
||||
color: "#64748B"
|
||||
font.pixelSize: 13
|
||||
}
|
||||
MouseArea {
|
||||
id: closeBtn2Area
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: ocrWin.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 复制成功短暂提示
|
||||
Rectangle {
|
||||
id: copyFeedback
|
||||
anchors.horizontalCenter: shadow.horizontalCenter
|
||||
anchors.bottom: bottomBar.top
|
||||
anchors.bottomMargin: 6
|
||||
width: 100; height: 28; radius: 6
|
||||
color: "#22C55E"
|
||||
visible: false
|
||||
opacity: 0
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: qsTr("已复制!")
|
||||
color: "white"
|
||||
font.pixelSize: 12
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: copyFeedbackTimer
|
||||
interval: 1200
|
||||
onTriggered: copyFeedback.visible = false
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
if (visible) opacity = 1
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation { duration: 300 }
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: copyFeedbackTimer
|
||||
function onTriggered() {
|
||||
copyFeedback.opacity = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 启动计时后立即显示反馈
|
||||
Connections {
|
||||
target: copyArea
|
||||
function onClicked() {
|
||||
copyFeedback.visible = true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import QtQuick
|
||||
|
||||
Rectangle {
|
||||
// id:leftTop
|
||||
// x: screenshotArea.x - handleSize/2
|
||||
// y: screenshotArea.y - handleSize/2
|
||||
property int handleSize: 8
|
||||
width: handleSize
|
||||
height: handleSize
|
||||
radius: handleSize/2
|
||||
color: "#0088FF"
|
||||
border.color: "white"
|
||||
border.width: 1
|
||||
}
|
||||
@@ -1,296 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Window
|
||||
|
||||
|
||||
Rectangle{
|
||||
id:handleGroup
|
||||
property rect area: Qt.rect(0,0,0,0)
|
||||
property rect initialArea: Qt.rect(0,0,0,0)
|
||||
property var mousePointStart: Qt.point(0,0)
|
||||
x:area.x
|
||||
y:area.y
|
||||
width: area.width
|
||||
height: area.height
|
||||
color: "transparent"
|
||||
//左上角控制点
|
||||
TZResizeHandle
|
||||
{
|
||||
id:leftTop
|
||||
x: - handleSize/2
|
||||
y: - handleSize/2
|
||||
// 仅当选区宽度/高度非零时显示
|
||||
visible: area.width !== 0 || area.height !== 0
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.SizeFDiagCursor
|
||||
onPressed: {
|
||||
let globalPos = leftTop.mapToItem(null, mouseX, mouseY);
|
||||
mousePointStart.x = globalPos.x
|
||||
mousePointStart.y = globalPos.y
|
||||
initialArea = area
|
||||
}
|
||||
|
||||
onReleased: {
|
||||
// 重置所有临时变量
|
||||
mousePointStart = Qt.point(0,0)
|
||||
initialArea = area
|
||||
}
|
||||
|
||||
onPositionChanged: {
|
||||
if (pressed) {
|
||||
let globalPos = leftTop.mapToItem(null, mouseX, mouseY);
|
||||
let dx = globalPos.x - mousePointStart.x
|
||||
let dy = globalPos.y - mousePointStart.y
|
||||
area.x = initialArea.x + dx
|
||||
area.y = initialArea.y + dy
|
||||
area.width = initialArea.width - dx
|
||||
area.height = initialArea.height - dy
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//正上方控制点
|
||||
TZResizeHandle
|
||||
{
|
||||
id: top
|
||||
x: (area.width - handleSize/2)/2
|
||||
y: -handleSize/2
|
||||
// 仅当选区宽度/高度非零时显示
|
||||
visible: area.width !== 0 || area.height !== 0
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.SizeVerCursor
|
||||
onPressed: {
|
||||
let globalPos = top.mapToItem(null, mouseX, mouseY);
|
||||
mousePointStart.y = globalPos.y
|
||||
initialArea = area
|
||||
}
|
||||
|
||||
onReleased: {
|
||||
// 重置所有临时变量
|
||||
mousePointStart = Qt.point(0,0)
|
||||
initialArea = area
|
||||
}
|
||||
|
||||
onPositionChanged: {
|
||||
if (pressed) {
|
||||
let globalPos = top.mapToItem(null, mouseX, mouseY);
|
||||
let dy = globalPos.y - mousePointStart.y
|
||||
area.y = initialArea.y + dy
|
||||
area.height = initialArea.height - dy
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//右上角控制点
|
||||
TZResizeHandle
|
||||
{
|
||||
id:rightTop
|
||||
x: area.width - handleSize/2
|
||||
y: -handleSize/2
|
||||
visible: area.width !== 0 || area.height !== 0
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.SizeBDiagCursor
|
||||
onPressed: {
|
||||
let globalPos = rightTop.mapToItem(null, mouseX, mouseY);
|
||||
mousePointStart.x = globalPos.x
|
||||
mousePointStart.y = globalPos.y
|
||||
initialArea = area
|
||||
}
|
||||
|
||||
onReleased: {
|
||||
mousePointStart = Qt.point(0,0)
|
||||
initialArea = area
|
||||
}
|
||||
|
||||
onPositionChanged: {
|
||||
if (pressed) {
|
||||
let globalPos = rightTop.mapToItem(null, mouseX, mouseY);
|
||||
let dx = globalPos.x - mousePointStart.x
|
||||
let dy = globalPos.y - mousePointStart.y
|
||||
area.y = initialArea.y + dy
|
||||
area.width = initialArea.width + dx
|
||||
area.height = initialArea.height - dy
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
//右边控制点
|
||||
TZResizeHandle
|
||||
{
|
||||
id: right
|
||||
x: area.width - handleSize/2
|
||||
y: (area.height -handleSize/2)/2
|
||||
visible: area.width !== 0 || area.height !== 0
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.SizeHorCursor
|
||||
onPressed: {
|
||||
let globalPos = right.mapToItem(null, mouseX, mouseY);
|
||||
mousePointStart.x = globalPos.x
|
||||
initialArea = area
|
||||
}
|
||||
|
||||
onReleased: {
|
||||
mousePointStart = Qt.point(0,0)
|
||||
initialArea = area
|
||||
}
|
||||
|
||||
onPositionChanged: {
|
||||
if (pressed) {
|
||||
let globalPos = right.mapToItem(null, mouseX, mouseY);
|
||||
let dx = globalPos.x - mousePointStart.x
|
||||
area.width = initialArea.width + dx
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
//右下角控制点
|
||||
TZResizeHandle
|
||||
{
|
||||
id:rightButtom
|
||||
x: area.width - handleSize/2
|
||||
y: area.height - handleSize/2
|
||||
visible: area.width !== 0 || area.height !== 0
|
||||
z:2
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.SizeFDiagCursor
|
||||
onPressed: {
|
||||
let globalPos = rightButtom.mapToItem(null, mouseX, mouseY);
|
||||
mousePointStart.x = globalPos.x
|
||||
mousePointStart.y = globalPos.y
|
||||
initialArea = area
|
||||
}
|
||||
|
||||
onReleased: {
|
||||
mousePointStart = Qt.point(0,0)
|
||||
initialArea = area
|
||||
}
|
||||
|
||||
onPositionChanged: {
|
||||
if (pressed) {
|
||||
let globalPos = rightButtom.mapToItem(null, mouseX, mouseY);
|
||||
let dx = globalPos.x - mousePointStart.x
|
||||
let dy = globalPos.y - mousePointStart.y
|
||||
area.width = initialArea.width + dx
|
||||
area.height = initialArea.height + dy
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//正下方控制点
|
||||
TZResizeHandle
|
||||
{
|
||||
id:buttom
|
||||
x: (area.width - handleSize/2)/2
|
||||
y: area.height - handleSize/2
|
||||
visible: area.width !== 0 || area.height !== 0
|
||||
z:2
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.SizeVerCursor
|
||||
onPressed: {
|
||||
let globalPos = buttom.mapToItem(null, mouseX, mouseY);
|
||||
mousePointStart.y = globalPos.y
|
||||
initialArea = area
|
||||
}
|
||||
|
||||
onReleased: {
|
||||
mousePointStart = Qt.point(0,0)
|
||||
initialArea = area
|
||||
}
|
||||
|
||||
onPositionChanged: {
|
||||
if (pressed) {
|
||||
let globalPos = buttom.mapToItem(null, mouseX, mouseY);
|
||||
let dy = globalPos.y - mousePointStart.y
|
||||
area.height = initialArea.height + dy
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//左下角控制点
|
||||
TZResizeHandle
|
||||
{
|
||||
id:leftBottom
|
||||
x: -handleSize/2
|
||||
y: area.height - handleSize/2
|
||||
visible: area.width !== 0 || area.height !== 0
|
||||
z:2
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.SizeBDiagCursor
|
||||
onPressed: {
|
||||
let globalPos = leftBottom.mapToItem(null, mouseX, mouseY);
|
||||
mousePointStart.x = globalPos.x
|
||||
mousePointStart.y = globalPos.y
|
||||
initialArea = area
|
||||
}
|
||||
|
||||
onReleased: {
|
||||
mousePointStart = Qt.point(0,0)
|
||||
initialArea = area
|
||||
}
|
||||
|
||||
onPositionChanged: {
|
||||
if (pressed) {
|
||||
let globalPos = leftBottom.mapToItem(null, mouseX, mouseY);
|
||||
let dx = globalPos.x - mousePointStart.x
|
||||
let dy = globalPos.y - mousePointStart.y
|
||||
area.x = initialArea.x + dx
|
||||
area.width = initialArea.width - dx
|
||||
area.height = initialArea.height + dy
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//左边控制点
|
||||
TZResizeHandle
|
||||
{
|
||||
id:left
|
||||
x: -handleSize/2
|
||||
y: (area.height - handleSize/2)/2
|
||||
visible: area.width !== 0 || area.height !== 0
|
||||
z:2
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.SizeHorCursor
|
||||
onPressed: {
|
||||
let globalPos = left.mapToItem(null, mouseX, mouseY);
|
||||
mousePointStart.x = globalPos.x
|
||||
initialArea = area
|
||||
}
|
||||
|
||||
onReleased: {
|
||||
mousePointStart = Qt.point(0,0)
|
||||
initialArea = area
|
||||
}
|
||||
|
||||
onPositionChanged: {
|
||||
if (pressed) {
|
||||
let globalPos = left.mapToItem(null, mouseX, mouseY);
|
||||
let dx = globalPos.x - mousePointStart.x
|
||||
area.x = initialArea.x + dx
|
||||
area.width = initialArea.width - dx
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
import QtQuick
|
||||
import Qt.labs.platform 1.0 as Platform
|
||||
import QtCore
|
||||
import QtQml
|
||||
|
||||
Item {
|
||||
id: root
|
||||
visible: false
|
||||
|
||||
required property var menuPrefs
|
||||
required property string imageUrl
|
||||
required property real imgOpacity
|
||||
required property real zoomFactor
|
||||
required property bool toolbarVisible
|
||||
required property url saveFolder
|
||||
|
||||
required property var overwriteWithEdits
|
||||
required property var copyImageToClipboard
|
||||
required property var openChatPanel
|
||||
required property var hideOrToggleToolbar
|
||||
required property var setOpacityValue
|
||||
required property var setZoomValue
|
||||
required property var rotateCurrent
|
||||
required property var mirrorCurrent
|
||||
required property var resetDisplaySize
|
||||
required property var resetWindowPosition
|
||||
required property var saveOriginalImage
|
||||
required property var saveMergedTo
|
||||
required property var closeWindow
|
||||
|
||||
property bool contextMenuOpen: false
|
||||
|
||||
function open() {
|
||||
contextMenu.open()
|
||||
}
|
||||
|
||||
Platform.Menu {
|
||||
id: contextMenu
|
||||
onAboutToShow: root.contextMenuOpen = true
|
||||
onAboutToHide: root.contextMenuOpen = false
|
||||
|
||||
Platform.MenuItem { text: qsTr("当前透明度 %1%").arg(Math.round(root.imgOpacity * 100)); enabled: false }
|
||||
Platform.MenuSeparator {}
|
||||
|
||||
Platform.MenuItem { visible: root.menuPrefs.pinSaveMerged; text: qsTr("覆盖贴图(合并标注)"); onTriggered: root.overwriteWithEdits() }
|
||||
Platform.MenuItem { visible: root.menuPrefs.pinCopy; text: qsTr("复制贴图"); onTriggered: root.copyImageToClipboard(root.imageUrl) }
|
||||
Platform.MenuItem { visible: root.menuPrefs.pinAi; text: qsTr("AI 编辑"); onTriggered: root.openChatPanel() }
|
||||
Platform.MenuItem {
|
||||
visible: root.menuPrefs.pinToolbar
|
||||
text: root.toolbarVisible ? qsTr("隐藏工具栏") : qsTr("显示工具栏")
|
||||
onTriggered: root.hideOrToggleToolbar()
|
||||
}
|
||||
Platform.MenuItem { visible: root.menuPrefs.pinClose; text: qsTr("关闭窗口"); onTriggered: root.closeWindow() }
|
||||
|
||||
Platform.MenuSeparator {}
|
||||
|
||||
Platform.MenuItem { text: qsTr("透明度 +5%"); onTriggered: root.setOpacityValue(root.imgOpacity + 0.05) }
|
||||
Platform.MenuItem { text: qsTr("透明度 -5%"); onTriggered: root.setOpacityValue(root.imgOpacity - 0.05) }
|
||||
|
||||
Platform.MenuSeparator {}
|
||||
|
||||
Platform.MenuItem { text: qsTr("缩小"); onTriggered: root.setZoomValue(root.zoomFactor - 0.1) }
|
||||
Platform.MenuItem { text: qsTr("放大"); onTriggered: root.setZoomValue(root.zoomFactor + 0.1) }
|
||||
Platform.MenuItem { text: qsTr("旋转 90°"); onTriggered: root.rotateCurrent(90) }
|
||||
Platform.MenuItem { text: qsTr("镜像"); onTriggered: root.mirrorCurrent() }
|
||||
Platform.MenuItem { text: qsTr("恢复原始大小"); onTriggered: root.resetDisplaySize() }
|
||||
|
||||
Platform.MenuSeparator {}
|
||||
|
||||
Platform.MenuItem {
|
||||
text: qsTr("重置位置")
|
||||
onTriggered: root.resetWindowPosition()
|
||||
}
|
||||
|
||||
Platform.MenuSeparator {}
|
||||
|
||||
Platform.MenuItem {
|
||||
text: root.toolbarVisible ? qsTr("隐藏工具栏") : qsTr("显示工具栏")
|
||||
onTriggered: root.hideOrToggleToolbar()
|
||||
}
|
||||
|
||||
Platform.MenuItem { text: qsTr("AI 编辑"); onTriggered: root.openChatPanel() }
|
||||
|
||||
Platform.MenuSeparator {}
|
||||
|
||||
Platform.MenuItem { text: qsTr("保存原图"); onTriggered: saveDialog.open() }
|
||||
Platform.MenuItem { text: qsTr("另存为(合并标注)"); onTriggered: mergedSaveDialog.open() }
|
||||
Platform.MenuItem { text: qsTr("覆盖贴图(合并标注)"); onTriggered: root.overwriteWithEdits() }
|
||||
Platform.MenuItem { text: qsTr("复制贴图"); onTriggered: root.copyImageToClipboard(root.imageUrl) }
|
||||
|
||||
Platform.MenuSeparator {}
|
||||
|
||||
Platform.Menu {
|
||||
title: qsTr("常用项置顶")
|
||||
Platform.MenuItem { text: qsTr("覆盖贴图"); checkable: true; checked: root.menuPrefs.pinSaveMerged; onTriggered: root.menuPrefs.pinSaveMerged = !root.menuPrefs.pinSaveMerged }
|
||||
Platform.MenuItem { text: qsTr("复制贴图"); checkable: true; checked: root.menuPrefs.pinCopy; onTriggered: root.menuPrefs.pinCopy = !root.menuPrefs.pinCopy }
|
||||
Platform.MenuItem { text: qsTr("AI 编辑"); checkable: true; checked: root.menuPrefs.pinAi; onTriggered: root.menuPrefs.pinAi = !root.menuPrefs.pinAi }
|
||||
Platform.MenuItem { text: qsTr("显示/隐藏工具栏"); checkable: true; checked: root.menuPrefs.pinToolbar; onTriggered: root.menuPrefs.pinToolbar = !root.menuPrefs.pinToolbar }
|
||||
Platform.MenuItem { text: qsTr("关闭窗口"); checkable: true; checked: root.menuPrefs.pinClose; onTriggered: root.menuPrefs.pinClose = !root.menuPrefs.pinClose }
|
||||
}
|
||||
|
||||
Platform.MenuSeparator {}
|
||||
|
||||
Platform.MenuItem { text: qsTr("关闭窗口"); onTriggered: root.closeWindow() }
|
||||
}
|
||||
|
||||
Platform.FileDialog {
|
||||
id: saveDialog
|
||||
title: qsTr("选择保存位置")
|
||||
folder: root.saveFolder
|
||||
fileMode: Platform.FileDialog.SaveFile
|
||||
nameFilters: ["PNG (*.png)", "JPG (*.jpg)", "BMP (*.bmp)"]
|
||||
onAccepted: root.saveOriginalImage(root.imageUrl, saveDialog.file)
|
||||
}
|
||||
|
||||
Platform.FileDialog {
|
||||
id: mergedSaveDialog
|
||||
title: qsTr("另存为(合并标注)")
|
||||
folder: root.saveFolder
|
||||
fileMode: Platform.FileDialog.SaveFile
|
||||
nameFilters: ["PNG (*.png)", "JPG (*.jpg)", "BMP (*.bmp)"]
|
||||
onAccepted: root.saveMergedTo(mergedSaveDialog.file)
|
||||
}
|
||||
}
|
||||
@@ -1,747 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Window
|
||||
import Qt.labs.platform 1.0 as Platform
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Controls.Basic
|
||||
import QtCore
|
||||
import CustomComponents 1.0
|
||||
|
||||
Window {
|
||||
id: stickyWin
|
||||
flags: Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.Tool
|
||||
color: "transparent"
|
||||
|
||||
readonly property real minOpacity: 0.1
|
||||
readonly property real maxOpacity: 1.0
|
||||
readonly property real minZoomFactor: 0.3
|
||||
readonly property real maxZoomFactor: 4.0
|
||||
readonly property int toolbarButtonSize: 30
|
||||
readonly property int toolbarIconSize: 18
|
||||
readonly property color toolbarHoverColor: "#F1F5F9"
|
||||
readonly property color toolbarActiveColor: "#EFF6FF"
|
||||
readonly property color toolbarActiveBorderColor: "#3B82F6"
|
||||
|
||||
property string imageUrl: ""
|
||||
property rect imgRect: Qt.rect(0, 0, 0, 0)
|
||||
property bool contentActive: true
|
||||
property bool cleanedUp: false
|
||||
|
||||
property real imgOpacity: 1.0
|
||||
property string activeTool: ""
|
||||
property bool toolbarVisible: true
|
||||
property string toolbarHoverTipText: ""
|
||||
property real toolbarHoverTipCenterX: 0
|
||||
property bool contextMenuOpen: stickyContextMenu.contextMenuOpen
|
||||
|
||||
property int imageRevision: 0
|
||||
property real zoomFactor: 1.0
|
||||
property real dpiScale: 1.0
|
||||
property size sourceImageSize: Qt.size(1, 1)
|
||||
property int imageLogicalWidth: 1
|
||||
property int imageLogicalHeight: 1
|
||||
|
||||
readonly property int toolbarHeight: 40
|
||||
readonly property int toolbarGap: 8
|
||||
readonly property int toolbarTipGap: 6
|
||||
readonly property int toolbarTipHeight: 24
|
||||
readonly property int shadowRadius: 12
|
||||
readonly property int shadowOffset: 4
|
||||
readonly property int imageLeft: imgRect.width >= 0 ? imgRect.x : imgRect.x + imgRect.width
|
||||
readonly property int imageTop: imgRect.height >= 0 ? imgRect.y : imgRect.y + imgRect.height
|
||||
|
||||
readonly property int displayW: Math.max(1, Math.round(imageLogicalWidth * zoomFactor))
|
||||
readonly property int displayH: Math.max(1, Math.round(imageLogicalHeight * zoomFactor))
|
||||
readonly property int toolbarContentWidth: Math.max(560, stickyRow.implicitWidth + 16)
|
||||
readonly property string displaySourceUrl: {
|
||||
if (typeof stickyWin.imageUrl !== "string" || stickyWin.imageUrl.length === 0)
|
||||
return ""
|
||||
return stickyWin.imageUrl + "?rev=" + stickyWin.imageRevision
|
||||
}
|
||||
readonly property var stickyShapeMap: ({
|
||||
pencil: 0,
|
||||
rect: 1,
|
||||
circle: 2,
|
||||
arrow: 3,
|
||||
mosaic: 4
|
||||
})
|
||||
readonly property var drawToolButtons: [
|
||||
{ tool: "pencil", tip: qsTr("Pencil"), icon: "qrc:/resource/img/lc_pencil.svg" },
|
||||
{ tool: "rect", tip: qsTr("Rectangle"), icon: "qrc:/resource/img/lc_square.svg" },
|
||||
{ tool: "circle", tip: qsTr("Circle"), icon: "qrc:/resource/img/lc_circle.svg" },
|
||||
{ tool: "arrow", tip: qsTr("Arrow"), icon: "qrc:/resource/img/lc_arrow.svg" },
|
||||
{ tool: "mosaic", tip: qsTr("Mosaic"), icon: "qrc:/resource/img/lc_mosaic.svg" }
|
||||
]
|
||||
readonly property var zoomActionButtons: [
|
||||
{ action: "zoomOut", tip: qsTr("Zoom out"), text: "-", textSize: 16 },
|
||||
{ action: "zoomIn", tip: qsTr("Zoom in"), text: "+", textSize: 16 }
|
||||
]
|
||||
readonly property var transformActionButtons: [
|
||||
{ action: "rotate", tip: qsTr("Rotate 90°"), text: "↻", textSize: 14 },
|
||||
{ action: "mirror", tip: qsTr("Mirror"), text: "⇋", textSize: 14 },
|
||||
{ action: "resetSize", tip: qsTr("Reset size"), text: "1:1", textSize: 10 }
|
||||
]
|
||||
readonly property var editActionButtons: [
|
||||
{ action: "doneAndCopy", tip: qsTr("Done and copy"), icon: "qrc:/resource/img/lc_check.svg", hoverColor: "#E0F2FE" },
|
||||
{ action: "undo", tip: qsTr("Undo"), icon: "qrc:/resource/img/lc_undo.svg", hoverColor: toolbarActiveColor },
|
||||
{ action: "hideToolbar", tip: qsTr("Hide toolbar"), icon: "qrc:/resource/img/lc_x.svg", hoverColor: "#FEE2E2" }
|
||||
]
|
||||
|
||||
readonly property int stickyShapeType: {
|
||||
return stickyShapeMap[activeTool] !== undefined ? stickyShapeMap[activeTool] : 1
|
||||
}
|
||||
|
||||
width: Math.max(displayW + shadowRadius * 2, toolbarContentWidth + 16)
|
||||
height: displayH + shadowRadius * 2 + shadowOffset + toolbarGap + toolbarHeight + toolbarTipGap + toolbarTipHeight + 4
|
||||
x: imageLeft - shadowRadius
|
||||
y: imageTop - shadowRadius
|
||||
|
||||
Settings {
|
||||
id: menuPrefs
|
||||
category: "sticky_menu"
|
||||
property bool pinSaveMerged: true
|
||||
property bool pinCopy: true
|
||||
property bool pinAi: false
|
||||
property bool pinToolbar: false
|
||||
property bool pinClose: false
|
||||
}
|
||||
|
||||
function setActiveTool(name) {
|
||||
if (activeTool === name)
|
||||
activeTool = ""
|
||||
else
|
||||
activeTool = name
|
||||
}
|
||||
|
||||
function clamp(value, minValue, maxValue) {
|
||||
return Math.max(minValue, Math.min(maxValue, value))
|
||||
}
|
||||
|
||||
function hasValidImage(image) {
|
||||
return image && image.width > 0 && image.height > 0
|
||||
}
|
||||
|
||||
function clearActiveTool() {
|
||||
stickyWin.activeTool = ""
|
||||
}
|
||||
|
||||
function setToolbarVisible(visible) {
|
||||
stickyWin.toolbarVisible = visible
|
||||
if (!visible)
|
||||
clearActiveTool()
|
||||
}
|
||||
|
||||
function toggleToolbarVisible() {
|
||||
setToolbarVisible(!stickyWin.toolbarVisible)
|
||||
}
|
||||
|
||||
function showToolbarTip(text, item) {
|
||||
toolbarHoverTipText = text
|
||||
if (item) {
|
||||
var p = stickyToolbar.mapFromItem(item, item.width / 2, item.height)
|
||||
toolbarHoverTipCenterX = p.x
|
||||
} else {
|
||||
toolbarHoverTipCenterX = stickyToolbar.width / 2
|
||||
}
|
||||
}
|
||||
|
||||
function hideToolbarTip(text) {
|
||||
if (text === undefined || toolbarHoverTipText === text)
|
||||
toolbarHoverTipText = ""
|
||||
}
|
||||
|
||||
function setOpacityValue(v) {
|
||||
stickyWin.imgOpacity = clamp(v, minOpacity, maxOpacity)
|
||||
}
|
||||
|
||||
function setZoomValue(v) {
|
||||
zoomFactor = clamp(v, minZoomFactor, maxZoomFactor)
|
||||
}
|
||||
|
||||
function resetDisplaySize() {
|
||||
zoomFactor = 1.0
|
||||
}
|
||||
|
||||
function updateImageMetricsFromStore() {
|
||||
var img = O_StickyViewModel.getImageByUrl(stickyWin.imageUrl)
|
||||
if (hasValidImage(img)) {
|
||||
stickyPaintBoard.setBackgroundImg(img)
|
||||
} else {
|
||||
syncPaintboardBackgroundFromDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
function syncPaintboardBackgroundFromDisplay() {
|
||||
if (!stickyWin.visible || !stickyWin.contentActive)
|
||||
return
|
||||
if (stickyImage.status !== Image.Ready)
|
||||
return
|
||||
stickyImage.grabToImage(function(result) {
|
||||
if (!hasValidImage(result && result.image))
|
||||
return
|
||||
stickyPaintBoard.setBackgroundImg(result.image)
|
||||
}, Qt.size(Math.max(1, Math.abs(imgRect.width)), Math.max(1, Math.abs(imgRect.height))))
|
||||
}
|
||||
|
||||
function refreshAfterImageChanged() {
|
||||
imageRevision += 1
|
||||
stickyPaintBoard.reset()
|
||||
updateImageMetricsFromStore()
|
||||
}
|
||||
|
||||
function renderAnnotationLayer() {
|
||||
return stickyPaintBoard.renderToImage()
|
||||
}
|
||||
|
||||
function overwriteWithEdits() {
|
||||
var layer = renderAnnotationLayer()
|
||||
var ok = O_StickyViewModel.overwriteWithAnnotations(stickyWin.imageUrl, layer)
|
||||
if (ok)
|
||||
refreshAfterImageChanged()
|
||||
}
|
||||
|
||||
function rotateCurrent(degrees) {
|
||||
if (O_StickyViewModel.rotateImage(stickyWin.imageUrl, degrees))
|
||||
refreshAfterImageChanged()
|
||||
}
|
||||
|
||||
function mirrorCurrent() {
|
||||
if (O_StickyViewModel.mirrorImage(stickyWin.imageUrl, true, false))
|
||||
refreshAfterImageChanged()
|
||||
}
|
||||
|
||||
function saveMergedTo(pathUrl) {
|
||||
var layer = renderAnnotationLayer()
|
||||
O_StickyViewModel.saveMergedImage(stickyWin.imageUrl, layer, pathUrl)
|
||||
}
|
||||
|
||||
function completeEditsAndCopy() {
|
||||
var layer = renderAnnotationLayer()
|
||||
if (hasValidImage(layer))
|
||||
overwriteWithEdits()
|
||||
O_StickyViewModel.copyImageToClipboard(stickyWin.imageUrl)
|
||||
setToolbarVisible(false)
|
||||
}
|
||||
|
||||
function ensureMosaicBackground() {
|
||||
var img = O_StickyViewModel.getImageByUrl(stickyWin.imageUrl)
|
||||
if (hasValidImage(img))
|
||||
stickyPaintBoard.setBackgroundImg(img)
|
||||
|
||||
setActiveTool("mosaic")
|
||||
if (stickyWin.activeTool !== "mosaic" || stickyPaintBoard.hasBackgroundImg())
|
||||
return
|
||||
|
||||
Qt.callLater(function() {
|
||||
var retryImg = O_StickyViewModel.getImageByUrl(stickyWin.imageUrl)
|
||||
if (hasValidImage(retryImg))
|
||||
stickyPaintBoard.setBackgroundImg(retryImg)
|
||||
if (!stickyPaintBoard.hasBackgroundImg())
|
||||
stickyWin.syncPaintboardBackgroundFromDisplay()
|
||||
})
|
||||
}
|
||||
|
||||
function handleToolbarAction(action) {
|
||||
switch (action) {
|
||||
case "zoomOut":
|
||||
setZoomValue(stickyWin.zoomFactor - 0.1)
|
||||
break
|
||||
case "zoomIn":
|
||||
setZoomValue(stickyWin.zoomFactor + 0.1)
|
||||
break
|
||||
case "rotate":
|
||||
rotateCurrent(90)
|
||||
break
|
||||
case "mirror":
|
||||
mirrorCurrent()
|
||||
break
|
||||
case "resetSize":
|
||||
resetDisplaySize()
|
||||
break
|
||||
case "doneAndCopy":
|
||||
completeEditsAndCopy()
|
||||
break
|
||||
case "undo":
|
||||
stickyPaintBoard.undo()
|
||||
break
|
||||
case "hideToolbar":
|
||||
setToolbarVisible(false)
|
||||
break
|
||||
case "ocr":
|
||||
var img = O_StickyViewModel.getImageByUrl(stickyWin.imageUrl)
|
||||
O_OcrVM.recognize(img)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
function handleDrawTool(tool) {
|
||||
if (tool === "mosaic") {
|
||||
ensureMosaicBackground()
|
||||
return
|
||||
}
|
||||
setActiveTool(tool)
|
||||
}
|
||||
|
||||
function resetWindowPosition() {
|
||||
stickyWin.x = stickyWin.imageLeft - stickyWin.shadowRadius
|
||||
stickyWin.y = stickyWin.imageTop - stickyWin.shadowRadius
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
if (cleanedUp)
|
||||
return
|
||||
cleanedUp = true
|
||||
|
||||
stickyWin.visible = false
|
||||
contentActive = false
|
||||
stickyWin.releaseResources()
|
||||
O_StickyViewModel.releaseImage(imageUrl)
|
||||
imageUrl = ""
|
||||
}
|
||||
|
||||
function updateDpiScale() {
|
||||
dpiScale = (screen && screen.devicePixelRatio > 0) ? screen.devicePixelRatio : 1.0
|
||||
}
|
||||
|
||||
function updateImageLogicalSize() {
|
||||
sourceImageSize = O_StickyViewModel.getImageSizeByUrl(stickyWin.imageUrl)
|
||||
if (sourceImageSize.width > 0 && sourceImageSize.height > 0) {
|
||||
imageLogicalWidth = Math.max(1, Math.round(sourceImageSize.width / dpiScale))
|
||||
imageLogicalHeight = Math.max(1, Math.round(sourceImageSize.height / dpiScale))
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
updateDpiScale()
|
||||
updateImageMetricsFromStore()
|
||||
updateImageLogicalSize()
|
||||
stickyWin.raise()
|
||||
stickyWin.requestActivate()
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: "Esc"
|
||||
onActivated: {
|
||||
if (stickyWin.activeTool !== "") {
|
||||
clearActiveTool()
|
||||
return
|
||||
}
|
||||
if (stickyWin.toolbarVisible) {
|
||||
setToolbarVisible(false)
|
||||
return
|
||||
}
|
||||
stickyWin.close()
|
||||
}
|
||||
}
|
||||
|
||||
onImageUrlChanged: {
|
||||
imageRevision = 0
|
||||
sourceImageSize = Qt.size(1, 1)
|
||||
imageLogicalWidth = 1
|
||||
imageLogicalHeight = 1
|
||||
resetDisplaySize()
|
||||
refreshAfterImageChanged()
|
||||
}
|
||||
|
||||
onActiveToolChanged: {
|
||||
if (activeTool === "mosaic")
|
||||
updateImageMetricsFromStore()
|
||||
}
|
||||
|
||||
onScreenChanged: {
|
||||
updateDpiScale()
|
||||
updateImageLogicalSize()
|
||||
}
|
||||
|
||||
Component.onDestruction: cleanup()
|
||||
|
||||
function onClosing(close) {
|
||||
close.accepted = true
|
||||
chatPanel.close()
|
||||
cleanup()
|
||||
Qt.callLater(function() { stickyWin.destroy() })
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: imgContainer
|
||||
visible: stickyWin.contentActive
|
||||
z: 10
|
||||
x: stickyWin.shadowRadius
|
||||
y: stickyWin.shadowRadius + stickyWin.shadowOffset / 2
|
||||
width: stickyWin.displayW
|
||||
height: stickyWin.displayH
|
||||
radius: 2
|
||||
clip: true
|
||||
color: "transparent"
|
||||
|
||||
Item {
|
||||
id: visualStage
|
||||
anchors.centerIn: parent
|
||||
width: stickyWin.imageLogicalWidth
|
||||
height: stickyWin.imageLogicalHeight
|
||||
scale: stickyWin.zoomFactor
|
||||
|
||||
Image {
|
||||
id: stickyImage
|
||||
anchors.fill: parent
|
||||
source: stickyWin.displaySourceUrl
|
||||
fillMode: Image.Stretch
|
||||
opacity: stickyWin.imgOpacity
|
||||
cache: false
|
||||
onStatusChanged: {
|
||||
if (status === Image.Ready)
|
||||
stickyWin.updateImageLogicalSize()
|
||||
if (status === Image.Ready && stickyWin.activeTool === "mosaic" && !stickyPaintBoard.hasBackgroundImg())
|
||||
stickyWin.syncPaintboardBackgroundFromDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
PaintBoard {
|
||||
id: stickyPaintBoard
|
||||
anchors.fill: parent
|
||||
z: 20
|
||||
visible: stickyWin.contentActive
|
||||
enabled: stickyWin.activeTool !== ""
|
||||
shapeType: stickyWin.stickyShapeType
|
||||
penColor: stickyMarkTool.currentColor
|
||||
penSize: stickyMarkTool.currentSize
|
||||
mosaicBlurLevel: stickyMarkTool.mosaicBlurLevel
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MultiEffect {
|
||||
visible: stickyWin.contentActive
|
||||
z: 5
|
||||
source: stickyImage
|
||||
anchors.fill: imgContainer
|
||||
shadowEnabled: true
|
||||
shadowColor: "#55000000"
|
||||
shadowBlur: stickyWin.shadowRadius / 20.0
|
||||
shadowVerticalOffset: stickyWin.shadowOffset
|
||||
shadowHorizontalOffset: 0
|
||||
opacity: stickyWin.imgOpacity
|
||||
}
|
||||
|
||||
TZLoadingSpinner {
|
||||
anchors.centerIn: imgContainer
|
||||
width: stickyWin.displayW
|
||||
height: stickyWin.displayH
|
||||
visible: O_AIViewModel.isLoading && stickyWin.contentActive
|
||||
}
|
||||
|
||||
TZMarkToolInfo {
|
||||
id: stickyMarkTool
|
||||
z: 32
|
||||
visible: stickyWin.toolbarVisible && stickyWin.activeTool !== ""
|
||||
toolName: stickyWin.activeTool
|
||||
showColorRow: stickyWin.activeTool !== "mosaic"
|
||||
x: Math.max(8, stickyToolbar.x)
|
||||
y: stickyToolbar.y - height - 6
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: stickyToolbar
|
||||
z: 30
|
||||
visible: stickyWin.toolbarVisible
|
||||
width: stickyWin.toolbarContentWidth
|
||||
height: stickyWin.toolbarHeight
|
||||
radius: 10
|
||||
color: "#FFFFFF"
|
||||
border.color: "#E2E8F0"
|
||||
border.width: 1
|
||||
x: Math.max(8, Math.min(stickyWin.width - width - 8, stickyWin.shadowRadius + (stickyWin.displayW - width) / 2))
|
||||
y: stickyWin.shadowRadius + stickyWin.shadowOffset / 2 + stickyWin.displayH + stickyWin.toolbarGap
|
||||
|
||||
Row {
|
||||
id: stickyRow
|
||||
anchors.centerIn: parent
|
||||
spacing: 6
|
||||
|
||||
Rectangle {
|
||||
width: stickyWin.toolbarButtonSize; height: stickyWin.toolbarButtonSize; radius: 7
|
||||
color: ocrArea.containsMouse ? "#F0FDF4" : "transparent"
|
||||
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_ocr.svg"; width: stickyWin.toolbarIconSize; height: stickyWin.toolbarIconSize }
|
||||
MouseArea {
|
||||
id: ocrArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onEntered: stickyWin.showToolbarTip(qsTr("OCR"), parent)
|
||||
onExited: stickyWin.hideToolbarTip(qsTr("OCR"))
|
||||
onPressed: stickyWin.handleToolbarAction("ocr")
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle { width: 1; height: 18; color: "#E2E8F0"; anchors.verticalCenter: parent.verticalCenter }
|
||||
|
||||
Repeater {
|
||||
model: stickyWin.drawToolButtons
|
||||
|
||||
delegate: Rectangle {
|
||||
property var buttonData: modelData
|
||||
width: stickyWin.toolbarButtonSize
|
||||
height: stickyWin.toolbarButtonSize
|
||||
radius: 7
|
||||
color: stickyWin.activeTool === buttonData.tool ? stickyWin.toolbarActiveColor : (hoverArea.containsMouse ? stickyWin.toolbarHoverColor : "transparent")
|
||||
border.color: stickyWin.activeTool === buttonData.tool ? stickyWin.toolbarActiveBorderColor : "transparent"
|
||||
border.width: 1
|
||||
|
||||
Image {
|
||||
anchors.centerIn: parent
|
||||
source: buttonData.icon
|
||||
width: stickyWin.toolbarIconSize
|
||||
height: stickyWin.toolbarIconSize
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: hoverArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onEntered: stickyWin.showToolbarTip(parent.buttonData.tip, parent)
|
||||
onExited: stickyWin.hideToolbarTip(parent.buttonData.tip)
|
||||
onPressed: stickyWin.handleDrawTool(parent.buttonData.tool)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle { width: 1; height: 18; color: "#E2E8F0"; anchors.verticalCenter: parent.verticalCenter }
|
||||
|
||||
Repeater {
|
||||
model: [stickyWin.zoomActionButtons[0]]
|
||||
|
||||
delegate: Rectangle {
|
||||
property var buttonData: modelData
|
||||
width: stickyWin.toolbarButtonSize
|
||||
height: stickyWin.toolbarButtonSize
|
||||
radius: 7
|
||||
color: hoverArea.containsMouse ? stickyWin.toolbarHoverColor : "transparent"
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: parent.buttonData.text
|
||||
color: "#334155"
|
||||
font.pixelSize: parent.buttonData.textSize
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: hoverArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onEntered: stickyWin.showToolbarTip(parent.buttonData.tip, parent)
|
||||
onExited: stickyWin.hideToolbarTip(parent.buttonData.tip)
|
||||
onPressed: stickyWin.handleToolbarAction(parent.buttonData.action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle { width: 40; height: 30; radius: 7; color: "transparent"; Text { anchors.centerIn: parent; text: Math.round(stickyWin.zoomFactor * 100) + "%"; color: "#64748B"; font.pixelSize: 11 } }
|
||||
|
||||
Repeater {
|
||||
model: [stickyWin.zoomActionButtons[1]].concat(stickyWin.transformActionButtons)
|
||||
|
||||
delegate: Rectangle {
|
||||
property var buttonData: modelData
|
||||
width: stickyWin.toolbarButtonSize
|
||||
height: stickyWin.toolbarButtonSize
|
||||
radius: 7
|
||||
color: hoverArea.containsMouse ? stickyWin.toolbarHoverColor : "transparent"
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: parent.buttonData.text
|
||||
color: "#334155"
|
||||
font.pixelSize: parent.buttonData.textSize
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: hoverArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onEntered: stickyWin.showToolbarTip(parent.buttonData.tip, parent)
|
||||
onExited: stickyWin.hideToolbarTip(parent.buttonData.tip)
|
||||
onPressed: stickyWin.handleToolbarAction(parent.buttonData.action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: stickyWin.editActionButtons
|
||||
|
||||
delegate: Rectangle {
|
||||
property var buttonData: modelData
|
||||
width: stickyWin.toolbarButtonSize
|
||||
height: stickyWin.toolbarButtonSize
|
||||
radius: 7
|
||||
color: hoverArea.containsMouse ? buttonData.hoverColor : "transparent"
|
||||
|
||||
Image {
|
||||
anchors.centerIn: parent
|
||||
source: buttonData.icon
|
||||
width: stickyWin.toolbarIconSize
|
||||
height: stickyWin.toolbarIconSize
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: hoverArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onEntered: stickyWin.showToolbarTip(parent.buttonData.tip, parent)
|
||||
onExited: stickyWin.hideToolbarTip(parent.buttonData.tip)
|
||||
onPressed: stickyWin.handleToolbarAction(parent.buttonData.action)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: stickyToolbarTip
|
||||
parent: stickyWin.contentItem
|
||||
visible: stickyWin.toolbarVisible && stickyWin.toolbarHoverTipText.length > 0
|
||||
z: 40
|
||||
width: Math.min(stickyWin.width - 8, Math.max(64, stickyToolbarTipText.implicitWidth + 16))
|
||||
height: 24
|
||||
radius: 6
|
||||
color: "#0F172A"
|
||||
opacity: 0.92
|
||||
x: Math.max(4, Math.min(stickyWin.width - width - 4, stickyToolbar.x + stickyWin.toolbarHoverTipCenterX - width / 2))
|
||||
y: Math.min(stickyWin.height - height - 4, stickyToolbar.y + stickyToolbar.height + stickyWin.toolbarTipGap)
|
||||
|
||||
Text {
|
||||
id: stickyToolbarTipText
|
||||
anchors.centerIn: parent
|
||||
text: stickyWin.toolbarHoverTipText
|
||||
color: "#FFFFFF"
|
||||
font.pixelSize: 11
|
||||
}
|
||||
}
|
||||
|
||||
readonly property int chatPanelGap: 6
|
||||
|
||||
TZChat {
|
||||
id: chatPanel
|
||||
imageUrl: stickyWin.imageUrl
|
||||
onSignalSendPrompt: function(text) {
|
||||
O_AIViewModel.sendPrompt(text, stickyWin.imageUrl)
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: O_AIViewModel
|
||||
function onSignalRequestComplete(oldImageUrl, newImageUrl) {
|
||||
if (oldImageUrl !== stickyWin.imageUrl)
|
||||
return
|
||||
var old = oldImageUrl
|
||||
stickyWin.imageUrl = newImageUrl
|
||||
Qt.callLater(function() { O_StickyViewModel.releaseImage(old) })
|
||||
}
|
||||
function onSignalRequestFailed(errorMsg) {
|
||||
console.warn("[TZStickyWindow] AI request failed: " + errorMsg)
|
||||
}
|
||||
}
|
||||
|
||||
function openChatPanel() {
|
||||
var imgLeft = stickyWin.x + stickyWin.shadowRadius
|
||||
var imgTop = stickyWin.y + stickyWin.shadowRadius
|
||||
var imgBottom = imgTop + stickyWin.displayH
|
||||
|
||||
var screenH = Screen.desktopAvailableHeight
|
||||
var screenW = Screen.desktopAvailableWidth
|
||||
|
||||
var cx = imgLeft
|
||||
if (cx + chatPanel.win.width > screenW)
|
||||
cx = imgLeft + stickyWin.displayW - chatPanel.win.width
|
||||
|
||||
var cy
|
||||
if (imgBottom + chatPanelGap + chatPanel.win.height <= screenH)
|
||||
cy = imgBottom + chatPanelGap
|
||||
else
|
||||
cy = imgTop - chatPanelGap - chatPanel.win.height
|
||||
|
||||
chatPanel.open(Math.max(0, cx), Math.max(0, cy))
|
||||
}
|
||||
|
||||
TZStickyContextMenu {
|
||||
id: stickyContextMenu
|
||||
menuPrefs: menuPrefs
|
||||
imageUrl: stickyWin.imageUrl
|
||||
imgOpacity: stickyWin.imgOpacity
|
||||
zoomFactor: stickyWin.zoomFactor
|
||||
toolbarVisible: stickyWin.toolbarVisible
|
||||
saveFolder: O_ImageSaver.savePath
|
||||
overwriteWithEdits: function() { stickyWin.overwriteWithEdits() }
|
||||
copyImageToClipboard: function(url) { O_StickyViewModel.copyImageToClipboard(url) }
|
||||
openChatPanel: function() { stickyWin.openChatPanel() }
|
||||
hideOrToggleToolbar: function() { stickyWin.toggleToolbarVisible() }
|
||||
setOpacityValue: function(value) { stickyWin.setOpacityValue(value) }
|
||||
setZoomValue: function(value) { stickyWin.setZoomValue(value) }
|
||||
rotateCurrent: function(degrees) { stickyWin.rotateCurrent(degrees) }
|
||||
mirrorCurrent: function() { stickyWin.mirrorCurrent() }
|
||||
resetDisplaySize: function() { stickyWin.resetDisplaySize() }
|
||||
resetWindowPosition: function() { stickyWin.resetWindowPosition() }
|
||||
saveOriginalImage: function(url, pathUrl) { O_StickyViewModel.saveImage(url, pathUrl) }
|
||||
saveMergedTo: function(pathUrl) { stickyWin.saveMergedTo(pathUrl) }
|
||||
closeWindow: function() { stickyWin.close() }
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
enabled: stickyWin.activeTool === "" && !stickyWin.contextMenuOpen
|
||||
property point dragPos: Qt.point(0, 0)
|
||||
property bool leftDragActive: false
|
||||
z: 0
|
||||
|
||||
function inImageArea(mx, my) {
|
||||
return mx >= stickyWin.shadowRadius
|
||||
&& mx <= stickyWin.shadowRadius + stickyWin.displayW
|
||||
&& my >= stickyWin.shadowRadius + stickyWin.shadowOffset / 2
|
||||
&& my <= stickyWin.shadowRadius + stickyWin.shadowOffset / 2 + stickyWin.displayH
|
||||
}
|
||||
|
||||
onPressed: function(mouse) {
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
dragPos = Qt.point(mouseX, mouseY)
|
||||
leftDragActive = true
|
||||
} else {
|
||||
leftDragActive = false
|
||||
}
|
||||
}
|
||||
|
||||
onPositionChanged: function(mouse) {
|
||||
if (pressed && leftDragActive && (mouse.buttons & Qt.LeftButton) && stickyWin.activeTool === "" && !stickyWin.contextMenuOpen) {
|
||||
stickyWin.x += mouseX - dragPos.x
|
||||
stickyWin.y += mouseY - dragPos.y
|
||||
}
|
||||
}
|
||||
|
||||
onReleased: function(mouse) {
|
||||
if (!(mouse.buttons & Qt.LeftButton))
|
||||
leftDragActive = false
|
||||
}
|
||||
|
||||
onDoubleClicked: function(mouse) {
|
||||
if (mouse.button === Qt.LeftButton && inImageArea(mouseX, mouseY))
|
||||
resetDisplaySize()
|
||||
}
|
||||
|
||||
onClicked: function(mouse) {
|
||||
if (mouse.button === Qt.RightButton) {
|
||||
leftDragActive = false
|
||||
stickyContextMenu.open()
|
||||
}
|
||||
}
|
||||
|
||||
onWheel: function(wheel) {
|
||||
if (wheel.angleDelta.y > 0)
|
||||
setZoomValue(stickyWin.zoomFactor + 0.1)
|
||||
else if (wheel.angleDelta.y < 0)
|
||||
setZoomValue(stickyWin.zoomFactor - 0.1)
|
||||
wheel.accepted = true
|
||||
}
|
||||
}
|
||||
}
|
||||
-230
@@ -1,230 +0,0 @@
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.5
|
||||
|
||||
Rectangle {
|
||||
id: tool
|
||||
radius: 10
|
||||
color: "#FFFFFF"
|
||||
visible: false
|
||||
border.color: "#E2E8F0"
|
||||
border.width: 1
|
||||
layer.enabled: true
|
||||
|
||||
signal signalScreenshot
|
||||
signal signalCancel
|
||||
signal signalSave
|
||||
signal signalSticky
|
||||
signal signalUndo
|
||||
signal signalGifRecord
|
||||
signal signalLongScreenshot
|
||||
signal signalOcr
|
||||
|
||||
property string activeTool: ""
|
||||
property bool markToolInfoVisible: false
|
||||
property string hoverTipText: ""
|
||||
property real hoverTipCenterX: width / 2
|
||||
|
||||
readonly property int shapeType: {
|
||||
switch (activeTool) {
|
||||
case "pencil": return 0
|
||||
case "rect": return 1
|
||||
case "circle": return 2
|
||||
case "arrow": return 3
|
||||
case "mosaic": return 4
|
||||
case "text": return 5
|
||||
case "number": return 6
|
||||
case "highlight": return 7
|
||||
default: return 1
|
||||
}
|
||||
}
|
||||
|
||||
function setActiveTool(name) {
|
||||
if (activeTool === name) {
|
||||
activeTool = ""
|
||||
markToolInfoVisible = false
|
||||
} else {
|
||||
activeTool = name
|
||||
markToolInfoVisible = true
|
||||
}
|
||||
}
|
||||
|
||||
function showTip(text, targetItem) {
|
||||
hoverTipText = text
|
||||
if (targetItem) {
|
||||
var p = tool.mapFromItem(targetItem, targetItem.width / 2, targetItem.height)
|
||||
hoverTipCenterX = p.x
|
||||
} else {
|
||||
hoverTipCenterX = width / 2
|
||||
}
|
||||
}
|
||||
|
||||
function hideTip(text) {
|
||||
if (text === undefined || hoverTipText === text)
|
||||
hoverTipText = ""
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 6
|
||||
layoutDirection: Qt.RightToLeft
|
||||
spacing: 2
|
||||
|
||||
Rectangle {
|
||||
width: 32; height: 32; radius: 7
|
||||
color: checkArea.containsMouse ? "#DCFCE7" : "transparent"
|
||||
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_check.svg"; width: 18; height: 18 }
|
||||
MouseArea { id: checkArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("完成"), parent); onExited: tool.hideTip(qsTr("完成")); onPressed: signalScreenshot() }
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 32; height: 32; radius: 7
|
||||
color: closeArea.containsMouse ? "#FEE2E2" : "transparent"
|
||||
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_x.svg"; width: 18; height: 18 }
|
||||
MouseArea { id: closeArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("取消"), parent); onExited: tool.hideTip(qsTr("取消")); onPressed: signalCancel() }
|
||||
}
|
||||
|
||||
Rectangle { width: 1; height: 20; color: "#E2E8F0"; Layout.alignment: Qt.AlignVCenter }
|
||||
|
||||
Rectangle {
|
||||
width: 32; height: 32; radius: 7
|
||||
color: ocrArea.containsMouse ? "#F0FDF4" : "transparent"
|
||||
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_ocr.svg"; width: 18; height: 18 }
|
||||
MouseArea { id: ocrArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("OCR"), parent); onExited: tool.hideTip(qsTr("OCR")); onPressed: signalOcr() }
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: gifRecordBtn
|
||||
width: 32; height: 32; radius: 7
|
||||
color: gifArea.containsMouse ? "#FFF7ED" : "transparent"
|
||||
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_gif_record.svg"; width: 18; height: 18 }
|
||||
MouseArea { id: gifArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("录制GIF"), parent); onExited: tool.hideTip(qsTr("录制GIF")); onPressed: signalGifRecord() }
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 32; height: 32; radius: 7
|
||||
color: longShotArea.containsMouse ? "#FFF7ED" : "transparent"
|
||||
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_longshot.svg"; width: 18; height: 18 }
|
||||
MouseArea { id: longShotArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("长截图"), parent); onExited: tool.hideTip(qsTr("长截图")); onPressed: signalLongScreenshot() }
|
||||
}
|
||||
|
||||
Rectangle { width: 1; height: 20; color: "#E2E8F0"; Layout.alignment: Qt.AlignVCenter }
|
||||
|
||||
Rectangle {
|
||||
width: 32; height: 32; radius: 7
|
||||
color: saveArea.containsMouse ? "#EFF6FF" : "transparent"
|
||||
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_save.svg"; width: 18; height: 18 }
|
||||
MouseArea { id: saveArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("保存"), parent); onExited: tool.hideTip(qsTr("保存")); onPressed: signalSave() }
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 32; height: 32; radius: 7
|
||||
color: fixArea.containsMouse ? "#EFF6FF" : "transparent"
|
||||
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_pin.svg"; width: 18; height: 18 }
|
||||
MouseArea { id: fixArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("贴图"), parent); onExited: tool.hideTip(qsTr("贴图")); onPressed: signalSticky() }
|
||||
}
|
||||
|
||||
Rectangle { width: 1; height: 20; color: "#E2E8F0"; Layout.alignment: Qt.AlignVCenter }
|
||||
|
||||
Rectangle {
|
||||
width: 32; height: 32; radius: 7
|
||||
color: (tool.activeTool === "rect") ? "#EFF6FF" : (rectArea.containsMouse ? "#F1F5F9" : "transparent")
|
||||
border.color: (tool.activeTool === "rect") ? "#3B82F6" : "transparent"
|
||||
border.width: 1
|
||||
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_square.svg"; width: 18; height: 18 }
|
||||
MouseArea { id: rectArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("矩形"), parent); onExited: tool.hideTip(qsTr("矩形")); onPressed: tool.setActiveTool("rect") }
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 32; height: 32; radius: 7
|
||||
color: (tool.activeTool === "circle") ? "#EFF6FF" : (circleArea.containsMouse ? "#F1F5F9" : "transparent")
|
||||
border.color: (tool.activeTool === "circle") ? "#3B82F6" : "transparent"
|
||||
border.width: 1
|
||||
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_circle.svg"; width: 18; height: 18 }
|
||||
MouseArea { id: circleArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("圆形"), parent); onExited: tool.hideTip(qsTr("圆形")); onPressed: tool.setActiveTool("circle") }
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 32; height: 32; radius: 7
|
||||
color: (tool.activeTool === "arrow") ? "#EFF6FF" : (arrowArea.containsMouse ? "#F1F5F9" : "transparent")
|
||||
border.color: (tool.activeTool === "arrow") ? "#3B82F6" : "transparent"
|
||||
border.width: 1
|
||||
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_arrow.svg"; width: 18; height: 18 }
|
||||
MouseArea { id: arrowArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("箭头"), parent); onExited: tool.hideTip(qsTr("箭头")); onPressed: tool.setActiveTool("arrow") }
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 32; height: 32; radius: 7
|
||||
color: (tool.activeTool === "pencil") ? "#EFF6FF" : (pencilArea.containsMouse ? "#F1F5F9" : "transparent")
|
||||
border.color: (tool.activeTool === "pencil") ? "#3B82F6" : "transparent"
|
||||
border.width: 1
|
||||
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_pencil.svg"; width: 18; height: 18 }
|
||||
MouseArea { id: pencilArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("画笔"), parent); onExited: tool.hideTip(qsTr("画笔")); onPressed: tool.setActiveTool("pencil") }
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 32; height: 32; radius: 7
|
||||
color: (tool.activeTool === "highlight") ? "#EFF6FF" : (highlightArea.containsMouse ? "#F1F5F9" : "transparent")
|
||||
border.color: (tool.activeTool === "highlight") ? "#3B82F6" : "transparent"
|
||||
border.width: 1
|
||||
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_highlighter.svg"; width: 18; height: 18 }
|
||||
MouseArea { id: highlightArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("高亮"), parent); onExited: tool.hideTip(qsTr("高亮")); onPressed: tool.setActiveTool("highlight") }
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 32; height: 32; radius: 7
|
||||
color: (tool.activeTool === "mosaic") ? "#EFF6FF" : (mosaicArea.containsMouse ? "#F1F5F9" : "transparent")
|
||||
border.color: (tool.activeTool === "mosaic") ? "#3B82F6" : "transparent"
|
||||
border.width: 1
|
||||
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_mosaic.svg"; width: 18; height: 18 }
|
||||
MouseArea { id: mosaicArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("马赛克"), parent); onExited: tool.hideTip(qsTr("马赛克")); onPressed: tool.setActiveTool("mosaic") }
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 32; height: 32; radius: 7
|
||||
color: (tool.activeTool === "text") ? "#EFF6FF" : (textArea.containsMouse ? "#F1F5F9" : "transparent")
|
||||
border.color: (tool.activeTool === "text") ? "#3B82F6" : "transparent"
|
||||
border.width: 1
|
||||
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_text.svg"; width: 18; height: 18 }
|
||||
MouseArea { id: textArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("文字"), parent); onExited: tool.hideTip(qsTr("文字")); onPressed: tool.setActiveTool("text") }
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 32; height: 32; radius: 7
|
||||
color: (tool.activeTool === "number") ? "#EFF6FF" : (numberArea.containsMouse ? "#F1F5F9" : "transparent")
|
||||
border.color: (tool.activeTool === "number") ? "#3B82F6" : "transparent"
|
||||
border.width: 1
|
||||
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_list_ordered.svg"; width: 18; height: 18 }
|
||||
MouseArea { id: numberArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("序号"), parent); onExited: tool.hideTip(qsTr("序号")); onPressed: tool.setActiveTool("number") }
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 32; height: 32; radius: 7
|
||||
color: undoArea.containsMouse ? "#EFF6FF" : "transparent"
|
||||
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_undo.svg"; width: 18; height: 18 }
|
||||
MouseArea { id: undoArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("撤销"), parent); onExited: tool.hideTip(qsTr("撤销")); onPressed: signalUndo() }
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: tipBubble
|
||||
visible: tool.visible && tool.hoverTipText.length > 0
|
||||
z: 100
|
||||
parent: tool.parent
|
||||
width: Math.max(44, tipText.implicitWidth + 14)
|
||||
height: 24
|
||||
radius: 6
|
||||
color: "#0F172A"
|
||||
opacity: 0.92
|
||||
x: Math.max(tool.x, Math.min(tool.x + tool.width - width, tool.x + tool.hoverTipCenterX - width / 2))
|
||||
y: tool.y + tool.height + 6
|
||||
|
||||
Text {
|
||||
id: tipText
|
||||
anchors.centerIn: parent
|
||||
text: tool.hoverTipText
|
||||
color: "#FFFFFF"
|
||||
font.pixelSize: 11
|
||||
}
|
||||
}
|
||||
}
|
||||
-1054
File diff suppressed because it is too large.
Load diff
+55
-9
@@ -12,7 +12,9 @@ QtObject {
|
||||
signal signalSendPrompt(var text)
|
||||
|
||||
// 公开方法
|
||||
function open(px, py) {
|
||||
function open(px, py, targetScreen) {
|
||||
if (targetScreen)
|
||||
win.screen = targetScreen
|
||||
win.x = px
|
||||
win.y = py
|
||||
win.visible = true
|
||||
@@ -56,14 +58,10 @@ QtObject {
|
||||
id: dragHandle
|
||||
anchors.fill: parent
|
||||
z: -1
|
||||
property point startPos
|
||||
|
||||
onPressed: startPos = Qt.point(mouseX, mouseY)
|
||||
onPositionChanged: {
|
||||
if (pressed) {
|
||||
win.x += mouseX - startPos.x
|
||||
win.y += mouseY - startPos.y
|
||||
}
|
||||
onPressed: function(mouse) {
|
||||
mouse.accepted = true
|
||||
win.startSystemMove()
|
||||
}
|
||||
cursorShape: pressed ? Qt.ClosedHandCursor : Qt.OpenHandCursor
|
||||
}
|
||||
@@ -187,6 +185,8 @@ QtObject {
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.minimumHeight: 140
|
||||
Layout.preferredHeight: 180
|
||||
color: "#F8FAFC"
|
||||
radius: 8
|
||||
border.color: "#E2E8F0"
|
||||
@@ -259,7 +259,53 @@ QtObject {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: resizeHandle
|
||||
width: 16
|
||||
height: 16
|
||||
radius: 8
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.rightMargin: 8
|
||||
anchors.bottomMargin: 8
|
||||
color: resizeMouse.pressed ? "#0369A1" : "#0EA5E9"
|
||||
border.color: "white"
|
||||
border.width: 1
|
||||
z: 10
|
||||
|
||||
Canvas {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 3
|
||||
onPaint: {
|
||||
var ctx = getContext("2d")
|
||||
ctx.reset()
|
||||
ctx.strokeStyle = "white"
|
||||
ctx.lineWidth = 1.2
|
||||
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(width * 0.25, height)
|
||||
ctx.lineTo(width, height * 0.25)
|
||||
ctx.stroke()
|
||||
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(width * 0.55, height)
|
||||
ctx.lineTo(width, height * 0.55)
|
||||
ctx.stroke()
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: resizeMouse
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.SizeFDiagCursor
|
||||
|
||||
onPressed: function(mouse) {
|
||||
mouse.accepted = true
|
||||
win.startSystemResize(Qt.RightEdge | Qt.BottomEdge)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
import QtQuick
|
||||
|
||||
// ── AI 加载动画层 ─────────────────────────────────────
|
||||
Rectangle {
|
||||
|
||||
width: 100
|
||||
height: 100
|
||||
radius: 2
|
||||
color: "#99000000" // 半透明黑色遮罩
|
||||
|
||||
|
||||
// 旋转圆弧
|
||||
Item {
|
||||
id: spinnerRoot
|
||||
anchors.centerIn: parent
|
||||
width: 48
|
||||
height: 48
|
||||
|
||||
RotationAnimator {
|
||||
target: spinnerRoot
|
||||
from: 0; to: 360
|
||||
duration: 900
|
||||
loops: Animation.Infinite
|
||||
running: O_AIViewModel.isLoading
|
||||
}
|
||||
|
||||
// 四个小圆点模拟旋转圆弧
|
||||
Repeater {
|
||||
model: 8
|
||||
Rectangle {
|
||||
width: 6
|
||||
height: 6
|
||||
radius: 3
|
||||
color: "white"
|
||||
opacity: (index + 1) / 8
|
||||
x: spinnerRoot.width / 2 + 18 * Math.cos(index * Math.PI / 4) - width / 2
|
||||
y: spinnerRoot.height / 2 + 18 * Math.sin(index * Math.PI / 4) - height / 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 提示文字
|
||||
Text {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: spinnerRoot.bottom
|
||||
anchors.topMargin: 10
|
||||
text: "AI 生成中..."
|
||||
color: "white"
|
||||
font.pixelSize: 13
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
import QtQuick
|
||||
|
||||
Rectangle {
|
||||
id: bar
|
||||
radius: 10
|
||||
color: "#FFFFFF"
|
||||
border.color: "#E2E8F0"
|
||||
border.width: 1
|
||||
|
||||
signal zoomOut()
|
||||
signal zoomIn()
|
||||
signal rotate90()
|
||||
signal mirror()
|
||||
signal resetSize()
|
||||
signal doneAndCopy()
|
||||
signal hideToolbar()
|
||||
signal hoverEntered(string text, Item targetItem)
|
||||
signal hoverExited(string text)
|
||||
|
||||
property real zoomFactor: 1.0
|
||||
|
||||
width: extraRow.implicitWidth + 16
|
||||
height: 40
|
||||
|
||||
Row {
|
||||
id: extraRow
|
||||
anchors.centerIn: parent
|
||||
spacing: 6
|
||||
|
||||
Rectangle { width: 30; height: 30; radius: 7; color: zoomOutArea.containsMouse ? "#F1F5F9" : "transparent"; Text { anchors.centerIn: parent; text: "-"; color: "#334155"; font.pixelSize: 16 } MouseArea { id: zoomOutArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: bar.hoverEntered(qsTr("Zoom out"), parent); onExited: bar.hoverExited(qsTr("Zoom out")); onPressed: bar.zoomOut() } }
|
||||
Rectangle { width: 40; height: 30; radius: 7; color: "transparent"; Text { anchors.centerIn: parent; text: Math.round(bar.zoomFactor * 100) + "%"; color: "#64748B"; font.pixelSize: 11 } }
|
||||
Rectangle { width: 30; height: 30; radius: 7; color: zoomInArea.containsMouse ? "#F1F5F9" : "transparent"; Text { anchors.centerIn: parent; text: "+"; color: "#334155"; font.pixelSize: 16 } MouseArea { id: zoomInArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: bar.hoverEntered(qsTr("Zoom in"), parent); onExited: bar.hoverExited(qsTr("Zoom in")); onPressed: bar.zoomIn() } }
|
||||
Rectangle { width: 30; height: 30; radius: 7; color: rotateArea.containsMouse ? "#F1F5F9" : "transparent"; Text { anchors.centerIn: parent; text: "↻"; color: "#334155"; font.pixelSize: 14 } MouseArea { id: rotateArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: bar.hoverEntered(qsTr("Rotate 90°"), parent); onExited: bar.hoverExited(qsTr("Rotate 90°")); onPressed: bar.rotate90() } }
|
||||
Rectangle { width: 30; height: 30; radius: 7; color: mirrorArea.containsMouse ? "#F1F5F9" : "transparent"; Text { anchors.centerIn: parent; text: "⇋"; color: "#334155"; font.pixelSize: 14 } MouseArea { id: mirrorArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: bar.hoverEntered(qsTr("Mirror"), parent); onExited: bar.hoverExited(qsTr("Mirror")); onPressed: bar.mirror() } }
|
||||
Rectangle { width: 30; height: 30; radius: 7; color: resetSizeArea.containsMouse ? "#F1F5F9" : "transparent"; Text { anchors.centerIn: parent; text: "1:1"; color: "#334155"; font.pixelSize: 10 } MouseArea { id: resetSizeArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: bar.hoverEntered(qsTr("Reset size"), parent); onExited: bar.hoverExited(qsTr("Reset size")); onPressed: bar.resetSize() } }
|
||||
Rectangle { width: 1; height: 18; color: "#E2E8F0"; anchors.verticalCenter: parent.verticalCenter }
|
||||
Rectangle { width: 30; height: 30; radius: 7; color: overwriteArea.containsMouse ? "#E0F2FE" : "transparent"; Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_check.svg"; width: 18; height: 18 } MouseArea { id: overwriteArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: bar.hoverEntered(qsTr("Done and copy"), parent); onExited: bar.hoverExited(qsTr("Done and copy")); onPressed: bar.doneAndCopy() } }
|
||||
Rectangle { width: 30; height: 30; radius: 7; color: clearToolArea.containsMouse ? "#FEE2E2" : "transparent"; Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_x.svg"; width: 18; height: 18 } MouseArea { id: clearToolArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: bar.hoverEntered(qsTr("Hide toolbar"), parent); onExited: bar.hoverExited(qsTr("Hide toolbar")); onPressed: bar.hideToolbar() } }
|
||||
}
|
||||
}
|
||||
+199
-402
@@ -1,317 +1,173 @@
|
||||
import QtQuick
|
||||
import QtQuick.Window
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import Qt.labs.platform 1.0 as Platform
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Controls.Basic
|
||||
import QtCore
|
||||
import CustomComponents 1.0
|
||||
import ".."
|
||||
import "../Capture"
|
||||
import CustomComponents 1.0
|
||||
|
||||
Window {
|
||||
id: stickyWin
|
||||
flags: Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.Tool
|
||||
color: "transparent"
|
||||
visible: true
|
||||
|
||||
property string imageUrl: ""
|
||||
property rect imgRect: Qt.rect(0, 0, 0, 0)
|
||||
property bool contentActive: true
|
||||
property bool cleanedUp: false
|
||||
|
||||
property real imgOpacity: 1.0
|
||||
property string activeTool: ""
|
||||
property bool toolbarVisible: true
|
||||
property string toolbarHoverTipText: ""
|
||||
property real toolbarHoverTipCenterX: 0
|
||||
property bool contextMenuOpen: stickyContextMenu.contextMenuOpen
|
||||
|
||||
property int imageRevision: 0
|
||||
property real zoomFactor: 1.0
|
||||
property real dpiScale: 1.0
|
||||
property size sourceImageSize: Qt.size(1, 1)
|
||||
property int imageLogicalWidth: 1
|
||||
property int imageLogicalHeight: 1
|
||||
|
||||
readonly property int toolbarHeight: 40
|
||||
property bool toolbarVisible: true
|
||||
property real zoomFactor: 1.0
|
||||
property int windowWidthValue: 1
|
||||
property int windowHeightValue: 1
|
||||
readonly property int shadowPadding: 12
|
||||
readonly property color shadowColor: "#2F8FFF"
|
||||
readonly property int toolbarHeight: toolBar.implicitHeight
|
||||
readonly property int toolbarGap: 8
|
||||
readonly property int toolbarTipGap: 6
|
||||
readonly property int toolbarTipHeight: 24
|
||||
readonly property int shadowRadius: 12
|
||||
readonly property int shadowOffset: 4
|
||||
readonly property int displayW: Math.max(1, Math.round(imageLogicalWidth * zoomFactor))
|
||||
readonly property int displayH: Math.max(1, Math.round(imageLogicalHeight * zoomFactor))
|
||||
readonly property int extraToolbarWidth: stickyExtraBar.width
|
||||
readonly property int toolbarContentWidth: stickyTool.implicitWidth + extraToolbarWidth + 6
|
||||
readonly property int toolbarBlockX: Math.max(8, Math.min(stickyWin.width - toolbarContentWidth - 8, imgContainer.x + (imgContainer.width - toolbarContentWidth) / 2))
|
||||
readonly property int toolbarTopY: imgContainer.y + imgContainer.height + stickyWin.toolbarGap
|
||||
readonly property int toolbarTipReserve: 30
|
||||
readonly property int markToolGap: 8
|
||||
readonly property int contentX: stickyWin.shadowPadding
|
||||
readonly property int contentY: stickyWin.shadowPadding
|
||||
readonly property int contentWidth: Math.max(1, stickyWin.width - stickyWin.shadowPadding * 2)
|
||||
readonly property int contentHeight: Math.max(1, stickyWin.height - stickyWin.shadowPadding * 2
|
||||
- (stickyWin.toolbarVisible ? (stickyWin.toolbarHeight + stickyWin.toolbarGap + stickyWin.toolbarTipReserve) : 0))
|
||||
|
||||
readonly property int stickyShapeType: {
|
||||
switch (activeTool) {
|
||||
case "pencil": return 0
|
||||
case "rect": return 1
|
||||
case "circle": return 2
|
||||
case "arrow": return 3
|
||||
case "mosaic": return 4
|
||||
default: return 1
|
||||
}
|
||||
width: windowWidthValue
|
||||
height: windowHeightValue
|
||||
minimumWidth: windowWidthValue
|
||||
maximumWidth: windowWidthValue
|
||||
minimumHeight: windowHeightValue
|
||||
maximumHeight: windowHeightValue
|
||||
|
||||
function updateImageSize() {
|
||||
sourceImageSize = O_StickyViewModel.getImageSizeByUrl(imageUrl)
|
||||
if (!sourceImageSize || sourceImageSize.width <= 0 || sourceImageSize.height <= 0)
|
||||
sourceImageSize = Qt.size(1, 1)
|
||||
updateWindowSize()
|
||||
}
|
||||
|
||||
width: Math.max(displayW, toolbarContentWidth + 16)
|
||||
height: displayH + toolbarGap + toolbarHeight + toolbarTipGap + toolbarTipHeight + 4
|
||||
function syncInitialPosition() {
|
||||
O_StickyViewModel.positionStickyWindow(stickyWin, imgRect)
|
||||
function updateWindowSize() {
|
||||
var imageWidth = Math.max(1, Math.floor(sourceImageSize.width * zoomFactor))
|
||||
var imageHeight = Math.max(1, Math.floor(sourceImageSize.height * zoomFactor))
|
||||
windowWidthValue = imageWidth + shadowPadding * 2
|
||||
windowHeightValue = imageHeight + shadowPadding * 2
|
||||
+ (toolbarVisible ? (toolbarHeight + toolbarGap + toolbarTipReserve) : 0)
|
||||
Qt.callLater(applyNativeSize)
|
||||
}
|
||||
|
||||
function updateDpiScale() {
|
||||
dpiScale = (screen && screen.devicePixelRatio > 0) ? screen.devicePixelRatio : 1.0
|
||||
function applyNativeSize() {
|
||||
O_StickyViewModel.resizeStickyWindow(stickyWin, windowWidthValue, windowHeightValue)
|
||||
}
|
||||
|
||||
function updateImageLogicalSize() {
|
||||
sourceImageSize = O_StickyViewModel.getImageSizeByUrl(stickyWin.imageUrl)
|
||||
if (sourceImageSize.width > 0 && sourceImageSize.height > 0) {
|
||||
imageLogicalWidth = Math.max(1, Math.round(sourceImageSize.width / dpiScale))
|
||||
imageLogicalHeight = Math.max(1, Math.round(sourceImageSize.height / dpiScale))
|
||||
}
|
||||
}
|
||||
|
||||
Settings {
|
||||
id: menuPrefs
|
||||
category: "sticky_menu"
|
||||
property bool pinSaveMerged: true
|
||||
property bool pinCopy: true
|
||||
property bool pinAi: false
|
||||
property bool pinToolbar: false
|
||||
property bool pinClose: false
|
||||
}
|
||||
|
||||
function showToolbarTip(text, item) {
|
||||
toolbarHoverTipText = text
|
||||
if (item) {
|
||||
var p = stickyExtraBar.mapFromItem(item, item.width / 2, item.height)
|
||||
toolbarHoverTipCenterX = p.x
|
||||
} else {
|
||||
toolbarHoverTipCenterX = stickyExtraBar.width / 2
|
||||
}
|
||||
}
|
||||
|
||||
function hideToolbarTip(text) {
|
||||
if (text === undefined || toolbarHoverTipText === text)
|
||||
toolbarHoverTipText = ""
|
||||
}
|
||||
|
||||
function displaySourceUrl() {
|
||||
if (typeof stickyWin.imageUrl !== "string" || stickyWin.imageUrl.length === 0)
|
||||
return ""
|
||||
return stickyWin.imageUrl + "?rev=" + stickyWin.imageRevision
|
||||
}
|
||||
|
||||
function setOpacityValue(v) {
|
||||
stickyWin.imgOpacity = Math.max(0.1, Math.min(1.0, v))
|
||||
}
|
||||
|
||||
function setZoomValue(v) {
|
||||
zoomFactor = Math.max(0.3, Math.min(4.0, v))
|
||||
function setZoomFactor(value) {
|
||||
zoomFactor = Math.max(0.3, Math.min(4.0, value))
|
||||
updateWindowSize()
|
||||
}
|
||||
|
||||
function resetDisplaySize() {
|
||||
zoomFactor = 1.0
|
||||
setZoomFactor(1.0)
|
||||
}
|
||||
|
||||
function updateImageMetricsFromStore() {
|
||||
var img = O_StickyViewModel.getImageByUrl(stickyWin.imageUrl)
|
||||
if (img && img.width > 0 && img.height > 0) {
|
||||
stickyPaintBoard.setBackgroundImg(img)
|
||||
} else {
|
||||
syncPaintboardBackgroundFromDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
function syncPaintboardBackgroundFromDisplay() {
|
||||
if (!stickyWin.visible || !stickyWin.contentActive)
|
||||
return
|
||||
if (stickyImage.status !== Image.Ready)
|
||||
return
|
||||
stickyImage.grabToImage(function(result) {
|
||||
if (!result || !result.image || result.image.width <= 0 || result.image.height <= 0)
|
||||
return
|
||||
stickyPaintBoard.setBackgroundImg(result.image)
|
||||
}, Qt.size(Math.max(1, Math.abs(imgRect.width)), Math.max(1, Math.abs(imgRect.height))))
|
||||
}
|
||||
|
||||
function refreshAfterImageChanged() {
|
||||
imageRevision += 1
|
||||
stickyPaintBoard.reset()
|
||||
updateImageMetricsFromStore()
|
||||
}
|
||||
|
||||
function overwriteWithEdits() {
|
||||
var layer = stickyPaintBoard.renderToImage()
|
||||
var ok = O_StickyViewModel.overwriteWithAnnotations(stickyWin.imageUrl, layer)
|
||||
if (ok)
|
||||
refreshAfterImageChanged()
|
||||
}
|
||||
|
||||
function rotateCurrent(degrees) {
|
||||
if (O_StickyViewModel.rotateImage(stickyWin.imageUrl, degrees))
|
||||
refreshAfterImageChanged()
|
||||
}
|
||||
|
||||
function mirrorCurrent() {
|
||||
if (O_StickyViewModel.mirrorImage(stickyWin.imageUrl, true, false))
|
||||
refreshAfterImageChanged()
|
||||
}
|
||||
|
||||
function saveMergedTo(pathUrl) {
|
||||
var layer = stickyPaintBoard.renderToImage()
|
||||
O_StickyViewModel.saveMergedImage(stickyWin.imageUrl, layer, pathUrl)
|
||||
}
|
||||
|
||||
function completeEditsAndCopy() {
|
||||
var layer = stickyPaintBoard.renderToImage()
|
||||
if (layer && layer.width > 0 && layer.height > 0)
|
||||
overwriteWithEdits()
|
||||
O_StickyViewModel.copyImageToClipboard(stickyWin.imageUrl)
|
||||
stickyTool.activeTool = ""
|
||||
stickyWin.activeTool = ""
|
||||
stickyWin.toolbarVisible = false
|
||||
function syncInitialPosition() {
|
||||
O_StickyViewModel.positionStickyWindow(stickyWin,
|
||||
Qt.rect(imgRect.x - shadowPadding,
|
||||
imgRect.y - shadowPadding,
|
||||
imgRect.width + shadowPadding * 2,
|
||||
imgRect.height + shadowPadding * 2))
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
if (cleanedUp)
|
||||
return
|
||||
cleanedUp = true
|
||||
|
||||
stickyWin.visible = false
|
||||
contentActive = false
|
||||
stickyWin.releaseResources()
|
||||
chatPanel.close()
|
||||
O_StickyViewModel.releaseImage(imageUrl)
|
||||
imageUrl = ""
|
||||
}
|
||||
|
||||
function syncPaintBoardBackground() {
|
||||
if (!imageUrl) {
|
||||
paintBoard.reset()
|
||||
return
|
||||
}
|
||||
|
||||
var bg = O_StickyViewModel.getImageByUrl(imageUrl)
|
||||
paintBoard.reset()
|
||||
paintBoard.setBackgroundImg(bg)
|
||||
}
|
||||
|
||||
function openChatPanel() {
|
||||
var targetScreen = stickyWin.screen
|
||||
if (!targetScreen || !targetScreen.availableGeometry) {
|
||||
chatPanel.open(stickyWin.x, stickyWin.y + stickyWin.height + 6, targetScreen)
|
||||
return
|
||||
}
|
||||
|
||||
var available = targetScreen.availableGeometry
|
||||
var panelX = stickyWin.x
|
||||
var panelY = stickyWin.y + stickyWin.height + 6
|
||||
|
||||
if (panelY + chatPanel.win.height > available.y + available.height)
|
||||
panelY = stickyWin.y - chatPanel.win.height - 6
|
||||
|
||||
panelX = Math.max(available.x, Math.min(panelX, available.x + available.width - chatPanel.win.width))
|
||||
panelY = Math.max(available.y, Math.min(panelY, available.y + available.height - chatPanel.win.height))
|
||||
|
||||
chatPanel.open(Math.round(panelX), Math.round(panelY), targetScreen)
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
updateDpiScale()
|
||||
updateImageSize()
|
||||
syncPaintBoardBackground()
|
||||
syncInitialPosition()
|
||||
updateImageMetricsFromStore()
|
||||
updateImageLogicalSize()
|
||||
applyNativeSize()
|
||||
stickyWin.raise()
|
||||
stickyWin.requestActivate()
|
||||
}
|
||||
|
||||
onImageUrlChanged: {
|
||||
imageRevision = 0
|
||||
sourceImageSize = Qt.size(1, 1)
|
||||
imageLogicalWidth = 1
|
||||
imageLogicalHeight = 1
|
||||
resetDisplaySize()
|
||||
refreshAfterImageChanged()
|
||||
updateImageSize()
|
||||
syncPaintBoardBackground()
|
||||
}
|
||||
|
||||
onActiveToolChanged: {
|
||||
if (activeTool === "mosaic")
|
||||
updateImageMetricsFromStore()
|
||||
}
|
||||
|
||||
onScreenChanged: {
|
||||
updateDpiScale()
|
||||
updateImageLogicalSize()
|
||||
}
|
||||
|
||||
onImgRectChanged: syncInitialPosition()
|
||||
|
||||
Component.onDestruction: cleanup()
|
||||
|
||||
function onClosing(close) {
|
||||
close.accepted = true
|
||||
chatPanel.close()
|
||||
cleanup()
|
||||
Qt.callLater(function() { stickyWin.destroy() })
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: imgContainer
|
||||
visible: stickyWin.contentActive
|
||||
z: 10
|
||||
x: 0
|
||||
y: 0
|
||||
width: stickyWin.displayW
|
||||
height: stickyWin.displayH
|
||||
radius: 2
|
||||
clip: true
|
||||
color: "transparent"
|
||||
|
||||
Item {
|
||||
id: visualStage
|
||||
anchors.centerIn: parent
|
||||
width: stickyWin.imageLogicalWidth
|
||||
height: stickyWin.imageLogicalHeight
|
||||
scale: stickyWin.zoomFactor
|
||||
|
||||
Image {
|
||||
id: stickyImage
|
||||
anchors.fill: parent
|
||||
source: displaySourceUrl()
|
||||
fillMode: Image.Stretch
|
||||
opacity: stickyWin.imgOpacity
|
||||
cache: false
|
||||
onStatusChanged: {
|
||||
if (status === Image.Ready)
|
||||
stickyWin.updateImageLogicalSize()
|
||||
if (status === Image.Ready && stickyWin.activeTool === "mosaic" && !stickyPaintBoard.hasBackgroundImg())
|
||||
stickyWin.syncPaintboardBackgroundFromDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
PaintBoard {
|
||||
id: stickyPaintBoard
|
||||
anchors.fill: parent
|
||||
z: 20
|
||||
visible: stickyWin.contentActive
|
||||
enabled: stickyWin.activeTool !== ""
|
||||
shapeType: stickyWin.stickyShapeType
|
||||
penColor: stickyMarkTool.currentColor
|
||||
penSize: stickyMarkTool.currentSize
|
||||
mosaicBlurLevel: stickyMarkTool.mosaicBlurLevel
|
||||
}
|
||||
}
|
||||
RectangularGlow {
|
||||
id: stickyShadow
|
||||
x: stickyWin.contentX
|
||||
y: stickyWin.contentY
|
||||
width: stickyWin.contentWidth
|
||||
height: stickyWin.contentHeight
|
||||
glowRadius: 10
|
||||
spread: 0.08
|
||||
color: stickyWin.shadowColor
|
||||
cornerRadius: 2
|
||||
opacity: 0.45
|
||||
cached: true
|
||||
}
|
||||
|
||||
MultiEffect {
|
||||
visible: stickyWin.contentActive
|
||||
PaintBoard {
|
||||
id: paintBoard
|
||||
x: stickyWin.contentX
|
||||
y: stickyWin.contentY
|
||||
width: stickyWin.contentWidth
|
||||
height: stickyWin.contentHeight
|
||||
z: 5
|
||||
source: stickyImage
|
||||
anchors.fill: imgContainer
|
||||
shadowEnabled: true
|
||||
shadowColor: "#55000000"
|
||||
shadowBlur: stickyWin.shadowRadius / 20.0
|
||||
shadowVerticalOffset: stickyWin.shadowOffset
|
||||
shadowHorizontalOffset: 0
|
||||
opacity: stickyWin.imgOpacity
|
||||
}
|
||||
|
||||
TZLoadingSpinner {
|
||||
anchors.centerIn: imgContainer
|
||||
width: stickyWin.displayW
|
||||
height: stickyWin.displayH
|
||||
visible: O_AIViewModel.isLoading && stickyWin.contentActive
|
||||
}
|
||||
|
||||
TZMarkToolInfo {
|
||||
id: stickyMarkTool
|
||||
z: 32
|
||||
visible: stickyWin.toolbarVisible && stickyWin.activeTool !== ""
|
||||
toolName: stickyWin.activeTool
|
||||
showColorRow: stickyWin.activeTool !== "mosaic"
|
||||
x: Math.max(8, toolbarBlockX)
|
||||
y: stickyTool.y - height - 6
|
||||
visible: true
|
||||
enabled: toolBar.activeTool !== ""
|
||||
shapeType: toolBar.shapeType
|
||||
penColor: markToolInfo.currentColor
|
||||
penSize: markToolInfo.currentSize
|
||||
}
|
||||
|
||||
TZTool {
|
||||
id: stickyTool
|
||||
z: 30
|
||||
id: toolBar
|
||||
visible: stickyWin.toolbarVisible
|
||||
width: implicitWidth
|
||||
height: stickyWin.toolbarHeight + 4
|
||||
x: stickyWin.toolbarBlockX
|
||||
y: stickyWin.toolbarTopY
|
||||
x: Math.max(0, Math.floor((stickyWin.width - width) / 2))
|
||||
y: stickyWin.contentY + stickyWin.contentHeight + stickyWin.toolbarGap
|
||||
showScreenshotButton: false
|
||||
showCancelButton: false
|
||||
showSaveButton: false
|
||||
@@ -321,76 +177,31 @@ Window {
|
||||
showHighlightButton: false
|
||||
showTextButton: false
|
||||
showNumberButton: false
|
||||
showRectButton: false
|
||||
showCircleButton: false
|
||||
showArrowButton: false
|
||||
showMosaicButton: false
|
||||
showPencilButton: true
|
||||
showUndoButton: true
|
||||
|
||||
onActiveToolChanged: {
|
||||
stickyWin.activeTool = activeTool
|
||||
if (activeTool === "mosaic" && !stickyPaintBoard.hasBackgroundImg()) {
|
||||
Qt.callLater(function() {
|
||||
var retryImg = O_StickyViewModel.getImageByUrl(stickyWin.imageUrl)
|
||||
if (retryImg && retryImg.width > 0 && retryImg.height > 0)
|
||||
stickyPaintBoard.setBackgroundImg(retryImg)
|
||||
if (!stickyPaintBoard.hasBackgroundImg())
|
||||
stickyWin.syncPaintboardBackgroundFromDisplay()
|
||||
})
|
||||
}
|
||||
}
|
||||
onSignalOcr: {
|
||||
var img = O_StickyViewModel.getImageByUrl(stickyWin.imageUrl)
|
||||
O_OcrVM.recognize(img)
|
||||
}
|
||||
onSignalUndo: stickyPaintBoard.undo()
|
||||
onSignalOcr: {}
|
||||
onSignalUndo: paintBoard.undo()
|
||||
}
|
||||
|
||||
TZStickyExtraToolBar {
|
||||
id: stickyExtraBar
|
||||
z: 30
|
||||
visible: stickyWin.toolbarVisible
|
||||
height: stickyWin.toolbarHeight
|
||||
x: stickyWin.toolbarBlockX + stickyTool.width + 6
|
||||
y: stickyTool.y + 2
|
||||
zoomFactor: stickyWin.zoomFactor
|
||||
onZoomOut: setZoomValue(stickyWin.zoomFactor - 0.1)
|
||||
onZoomIn: setZoomValue(stickyWin.zoomFactor + 0.1)
|
||||
onRotate90: rotateCurrent(90)
|
||||
onMirror: mirrorCurrent()
|
||||
onResetSize: resetDisplaySize()
|
||||
onDoneAndCopy: completeEditsAndCopy()
|
||||
onHideToolbar: {
|
||||
stickyTool.activeTool = ""
|
||||
stickyWin.activeTool = ""
|
||||
stickyWin.toolbarVisible = false
|
||||
}
|
||||
onHoverEntered: function(text, targetItem) { stickyWin.showToolbarTip(text, targetItem) }
|
||||
onHoverExited: function(text) { stickyWin.hideToolbarTip(text) }
|
||||
TZMarkToolInfo {
|
||||
id: markToolInfo
|
||||
visible: toolBar.visible && toolBar.markToolInfoVisible && toolBar.activeTool === "pencil"
|
||||
z: 6
|
||||
x: Math.max(stickyWin.shadowPadding, Math.min(stickyWin.width - width - stickyWin.shadowPadding, toolBar.x))
|
||||
y: Math.max(stickyWin.shadowPadding, toolBar.y - height - stickyWin.markToolGap)
|
||||
toolName: toolBar.activeTool
|
||||
showColorRow: true
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: stickyToolbarTip
|
||||
parent: stickyWin.contentItem
|
||||
visible: stickyWin.toolbarVisible && stickyWin.toolbarHoverTipText.length > 0
|
||||
z: 40
|
||||
width: Math.min(stickyWin.width - 8, Math.max(64, stickyToolbarTipText.implicitWidth + 16))
|
||||
height: 24
|
||||
radius: 6
|
||||
color: "#0F172A"
|
||||
opacity: 0.92
|
||||
x: Math.max(4, Math.min(stickyWin.width - width - 4, stickyExtraBar.x + stickyWin.toolbarHoverTipCenterX - width / 2))
|
||||
y: Math.min(stickyWin.height - height - 4, stickyExtraBar.y + stickyExtraBar.height + stickyWin.toolbarTipGap)
|
||||
|
||||
Text {
|
||||
id: stickyToolbarTipText
|
||||
anchors.centerIn: parent
|
||||
text: stickyWin.toolbarHoverTipText
|
||||
color: "#FFFFFF"
|
||||
font.pixelSize: 11
|
||||
}
|
||||
}
|
||||
|
||||
readonly property int chatPanelGap: 6
|
||||
|
||||
TZChat {
|
||||
id: chatPanel
|
||||
imageUrl: stickyWin.imageUrl
|
||||
|
||||
onSignalSendPrompt: function(text) {
|
||||
O_AIViewModel.sendPrompt(text, stickyWin.imageUrl)
|
||||
}
|
||||
@@ -398,123 +209,109 @@ Window {
|
||||
|
||||
Connections {
|
||||
target: O_AIViewModel
|
||||
|
||||
function onSignalRequestComplete(oldImageUrl, newImageUrl) {
|
||||
if (oldImageUrl !== stickyWin.imageUrl)
|
||||
return
|
||||
var old = oldImageUrl
|
||||
stickyWin.imageUrl = newImageUrl
|
||||
syncPaintBoardBackground()
|
||||
Qt.callLater(function() { O_StickyViewModel.releaseImage(old) })
|
||||
}
|
||||
|
||||
function onSignalRequestFailed(errorMsg) {
|
||||
console.warn("[TZStickyWindow] AI request failed: " + errorMsg)
|
||||
}
|
||||
}
|
||||
|
||||
function openChatPanel() {
|
||||
var imgLeft = stickyWin.x
|
||||
var imgTop = stickyWin.y
|
||||
var imgBottom = imgTop + stickyWin.displayH
|
||||
Platform.Menu {
|
||||
id: contextMenu
|
||||
|
||||
var screenH = Screen.desktopAvailableHeight
|
||||
var screenW = Screen.desktopAvailableWidth
|
||||
Platform.MenuItem {
|
||||
text: qsTr("复制")
|
||||
onTriggered: O_StickyViewModel.copyImageToClipboard(stickyWin.imageUrl)
|
||||
}
|
||||
|
||||
var cx = imgLeft
|
||||
if (cx + chatPanel.win.width > screenW)
|
||||
cx = imgLeft + stickyWin.displayW - chatPanel.win.width
|
||||
Platform.MenuItem {
|
||||
text: qsTr("AI 编辑")
|
||||
onTriggered: stickyWin.openChatPanel()
|
||||
}
|
||||
|
||||
var cy
|
||||
if (imgBottom + chatPanelGap + chatPanel.win.height <= screenH)
|
||||
cy = imgBottom + chatPanelGap
|
||||
else
|
||||
cy = imgTop - chatPanelGap - chatPanel.win.height
|
||||
|
||||
chatPanel.open(Math.max(0, cx), Math.max(0, cy))
|
||||
}
|
||||
|
||||
TZStickyContextMenu {
|
||||
id: stickyContextMenu
|
||||
menuPrefs: menuPrefs
|
||||
imageUrl: stickyWin.imageUrl
|
||||
imgOpacity: stickyWin.imgOpacity
|
||||
zoomFactor: stickyWin.zoomFactor
|
||||
toolbarVisible: stickyWin.toolbarVisible
|
||||
saveFolder: O_ImageSaver.savePath
|
||||
overwriteWithEdits: function() { stickyWin.overwriteWithEdits() }
|
||||
copyImageToClipboard: function(url) { O_StickyViewModel.copyImageToClipboard(url) }
|
||||
openChatPanel: function() { stickyWin.openChatPanel() }
|
||||
hideOrToggleToolbar: function() {
|
||||
stickyWin.toolbarVisible = !stickyWin.toolbarVisible
|
||||
if (!stickyWin.toolbarVisible) {
|
||||
stickyTool.activeTool = ""
|
||||
stickyWin.activeTool = ""
|
||||
Platform.MenuItem {
|
||||
text: stickyWin.toolbarVisible ? qsTr("隐藏工具栏") : qsTr("显示工具栏")
|
||||
onTriggered: {
|
||||
stickyWin.toolbarVisible = !stickyWin.toolbarVisible
|
||||
updateWindowSize()
|
||||
}
|
||||
}
|
||||
setOpacityValue: function(value) { stickyWin.setOpacityValue(value) }
|
||||
setZoomValue: function(value) { stickyWin.setZoomValue(value) }
|
||||
rotateCurrent: function(degrees) { stickyWin.rotateCurrent(degrees) }
|
||||
mirrorCurrent: function() { stickyWin.mirrorCurrent() }
|
||||
resetDisplaySize: function() { stickyWin.resetDisplaySize() }
|
||||
resetWindowPosition: function() { stickyWin.syncInitialPosition() }
|
||||
saveOriginalImage: function(url, pathUrl) { O_StickyViewModel.saveImage(url, pathUrl) }
|
||||
saveMergedTo: function(pathUrl) { stickyWin.saveMergedTo(pathUrl) }
|
||||
closeWindow: function() { stickyWin.close() }
|
||||
|
||||
Platform.MenuItem {
|
||||
text: qsTr("恢复原图大小")
|
||||
onTriggered: stickyWin.resetDisplaySize()
|
||||
}
|
||||
|
||||
Platform.MenuSeparator {}
|
||||
|
||||
Platform.MenuItem {
|
||||
text: qsTr("保存原图")
|
||||
onTriggered: saveDialog.open()
|
||||
}
|
||||
|
||||
Platform.MenuItem {
|
||||
text: qsTr("关闭")
|
||||
onTriggered: stickyWin.close()
|
||||
}
|
||||
}
|
||||
|
||||
Platform.FileDialog {
|
||||
id: saveDialog
|
||||
title: qsTr("选择保存位置")
|
||||
folder: O_ImageSaver.savePath
|
||||
fileMode: Platform.FileDialog.SaveFile
|
||||
nameFilters: ["PNG (*.png)", "JPG (*.jpg)", "BMP (*.bmp)"]
|
||||
onAccepted: O_StickyViewModel.saveImage(stickyWin.imageUrl, saveDialog.file)
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
enabled: stickyWin.activeTool === "" && !stickyWin.contextMenuOpen
|
||||
property point dragPos: Qt.point(0, 0)
|
||||
property bool leftDragActive: false
|
||||
z: 0
|
||||
|
||||
function inImageArea(mx, my) {
|
||||
return mx >= 0
|
||||
&& mx <= stickyWin.displayW
|
||||
&& my >= 0
|
||||
&& my <= stickyWin.displayH
|
||||
}
|
||||
id: dragArea
|
||||
x: stickyWin.contentX
|
||||
y: stickyWin.contentY
|
||||
width: stickyWin.contentWidth
|
||||
height: stickyWin.contentHeight
|
||||
enabled: toolBar.activeTool === ""
|
||||
acceptedButtons: Qt.LeftButton
|
||||
property point pressOffset: Qt.point(0, 0)
|
||||
|
||||
onPressed: function(mouse) {
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
dragPos = Qt.point(mouseX, mouseY)
|
||||
leftDragActive = true
|
||||
} else {
|
||||
leftDragActive = false
|
||||
}
|
||||
pressOffset = Qt.point(mouse.x, mouse.y)
|
||||
}
|
||||
|
||||
onPositionChanged: function(mouse) {
|
||||
if (pressed && leftDragActive && (mouse.buttons & Qt.LeftButton) && stickyWin.activeTool === "" && !stickyWin.contextMenuOpen) {
|
||||
stickyWin.x += mouseX - dragPos.x
|
||||
stickyWin.y += mouseY - dragPos.y
|
||||
}
|
||||
}
|
||||
|
||||
onReleased: function(mouse) {
|
||||
if (!(mouse.buttons & Qt.LeftButton))
|
||||
leftDragActive = false
|
||||
}
|
||||
|
||||
onDoubleClicked: function(mouse) {
|
||||
if (mouse.button === Qt.LeftButton && inImageArea(mouseX, mouseY))
|
||||
resetDisplaySize()
|
||||
}
|
||||
|
||||
onClicked: function(mouse) {
|
||||
if (mouse.button === Qt.RightButton) {
|
||||
leftDragActive = false
|
||||
stickyContextMenu.open()
|
||||
if (pressed && (mouse.buttons & Qt.LeftButton)) {
|
||||
stickyWin.x += mouse.x - pressOffset.x
|
||||
stickyWin.y += mouse.y - pressOffset.y
|
||||
}
|
||||
}
|
||||
|
||||
onWheel: function(wheel) {
|
||||
if (wheel.angleDelta.y > 0)
|
||||
setZoomValue(stickyWin.zoomFactor + 0.1)
|
||||
stickyWin.setZoomFactor(stickyWin.zoomFactor + 0.1)
|
||||
else if (wheel.angleDelta.y < 0)
|
||||
setZoomValue(stickyWin.zoomFactor - 0.1)
|
||||
stickyWin.setZoomFactor(stickyWin.zoomFactor - 0.1)
|
||||
wheel.accepted = true
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
x: stickyWin.contentX
|
||||
y: stickyWin.contentY
|
||||
width: stickyWin.contentWidth
|
||||
height: stickyWin.contentHeight + (stickyWin.toolbarVisible ? (stickyWin.toolbarHeight + stickyWin.toolbarGap + stickyWin.toolbarTipReserve) : 0)
|
||||
acceptedButtons: Qt.RightButton
|
||||
|
||||
onClicked: function(mouse) {
|
||||
if (mouse.button === Qt.RightButton)
|
||||
contextMenu.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M3 12h2"/>
|
||||
<path d="M7 12h4"/>
|
||||
<path d="M15 12h6"/>
|
||||
<path d="M3 7h6"/>
|
||||
<path d="M13 7h2"/>
|
||||
<path d="M19 7h2"/>
|
||||
<path d="M3 17h10"/>
|
||||
<path d="M17 17h4"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 367 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-smile-plus-icon lucide-smile-plus"><path d="M22 11v1a10 10 0 1 1-9-10"/><path d="M8 14s1.5 2 4 2 4-2 4-2"/><line x1="9" x2="9.01" y1="9" y2="9"/><line x1="15" x2="15.01" y1="9" y2="9"/><path d="M16 5h6"/><path d="M19 2v6"/></svg>
|
||||
|
Before Width: | Height: | Size: 431 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-file-icon lucide-file"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"/><path d="M14 2v5a1 1 0 0 0 1 1h5"/></svg>
|
||||
|
Before Width: | Height: | Size: 388 B |
@@ -0,0 +1,81 @@
|
||||
#stickyToolbar {
|
||||
background-color: #FFFFFF;
|
||||
border: 1px solid rgba(96, 165, 250, 170);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
#stickyToolbar QToolButton {
|
||||
background-color: transparent;
|
||||
color: #0F172A;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 7px;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
qproperty-iconSize: 18px 18px;
|
||||
}
|
||||
|
||||
#stickyToolbar QToolButton:hover {
|
||||
background-color: #F1F5F9;
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
#stickyToolbar QToolButton:pressed {
|
||||
background-color: #EFF6FF;
|
||||
border-color: #3B82F6;
|
||||
}
|
||||
|
||||
#stickyToolbar QToolButton:checked {
|
||||
background-color: #EFF6FF;
|
||||
border-color: #3B82F6;
|
||||
}
|
||||
|
||||
#stickyToolOptions {
|
||||
background-color: #FFFFFF;
|
||||
border: 1px solid #E2E8F0;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
#stickyToolOptions QToolButton {
|
||||
background-color: transparent;
|
||||
border: 1px solid #E2E8F0;
|
||||
border-radius: 6px;
|
||||
color: #334155;
|
||||
}
|
||||
|
||||
#stickyToolOptions QToolButton:checked {
|
||||
background-color: #EFF6FF;
|
||||
border-color: #3B82F6;
|
||||
color: #3B82F6;
|
||||
}
|
||||
|
||||
#stickyToolOptions QToolButton:hover {
|
||||
background-color: #F1F5F9;
|
||||
}
|
||||
|
||||
#stickyToolOptions QLineEdit,
|
||||
#stickyToolOptions QSpinBox {
|
||||
background-color: #F8FAFC;
|
||||
border: 1px solid #E2E8F0;
|
||||
border-radius: 6px;
|
||||
padding: 4px 6px;
|
||||
color: #0F172A;
|
||||
}
|
||||
|
||||
#stickyToolOptions QCheckBox {
|
||||
color: #334155;
|
||||
}
|
||||
|
||||
#stickyInlineTextEditor {
|
||||
background-color: #FFFFFF;
|
||||
border: 1px solid #3B82F6;
|
||||
border-radius: 6px;
|
||||
padding: 4px 6px;
|
||||
color: #0F172A;
|
||||
}
|
||||
|
||||
#stickyNumberLabel {
|
||||
background-color: #F8FAFC;
|
||||
border: 1px solid #E2E8F0;
|
||||
border-radius: 6px;
|
||||
color: #0F172A;
|
||||
}
|
||||
+5
-3
@@ -29,9 +29,10 @@
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
// 强制让窗口跟随系统DPI,不使用虚拟像素
|
||||
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(
|
||||
Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
|
||||
|
||||
// QGuiApplication::setHighDpiScaleFactorRoundingPolicy(
|
||||
// Qt::HighDpiScaleFactorRoundingPolicy::RoundPreferFloor);
|
||||
QCoreApplication::setAttribute(Qt::AA_DisableHighDpiScaling);
|
||||
QCoreApplication::setAttribute(Qt::AA_UseSoftwareOpenGL);
|
||||
QApplication app(argc, argv);
|
||||
|
||||
// Single-instance activation: second launch notifies the first one and exits.
|
||||
@@ -55,6 +56,7 @@ int main(int argc, char *argv[])
|
||||
ScreenshotViewModel screenshotViewModel(desktopSnapshot, stickyImageStore);
|
||||
StickyViewModel stickyViewModel(stickyImageStore);
|
||||
AIViewModel aiViewModel(appSettings, stickyImageStore);
|
||||
stickyViewModel.setAiViewModel(&aiViewModel);
|
||||
StorageViewModel storageViewModel(appSettings);
|
||||
LanguageManager languageManager(appSettings);
|
||||
GifRecordViewModel gifRecordViewModel(appSettings);
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#include <QPainter>
|
||||
#include <QPixmap>
|
||||
#include <QScreen>
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
@@ -15,6 +17,18 @@
|
||||
|
||||
namespace {
|
||||
|
||||
struct ScreenSortLess
|
||||
{
|
||||
template <typename T>
|
||||
bool operator()(const T &lhs, const T &rhs) const
|
||||
{
|
||||
if (lhs.left() != rhs.left()) {
|
||||
return lhs.left() < rhs.left();
|
||||
}
|
||||
return lhs.top() < rhs.top();
|
||||
}
|
||||
};
|
||||
|
||||
QRect desktopGeometry()
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
@@ -78,6 +92,33 @@ QImage captureVirtualDesktopImage(const QRect& geometry)
|
||||
ReleaseDC(nullptr, screenDC);
|
||||
return image;
|
||||
}
|
||||
|
||||
QVector<QRect> enumerateMonitorRects()
|
||||
{
|
||||
struct MonitorRects {
|
||||
QVector<QRect> rects;
|
||||
} monitors;
|
||||
|
||||
EnumDisplayMonitors(nullptr,
|
||||
nullptr,
|
||||
[](HMONITOR monitor, HDC, LPRECT, LPARAM data) -> BOOL {
|
||||
auto *rects = reinterpret_cast<MonitorRects*>(data);
|
||||
MONITORINFOEXW info{};
|
||||
info.cbSize = sizeof(info);
|
||||
if (GetMonitorInfoW(monitor, &info)) {
|
||||
const RECT &rc = info.rcMonitor;
|
||||
rects->rects.append(QRect(rc.left,
|
||||
rc.top,
|
||||
rc.right - rc.left,
|
||||
rc.bottom - rc.top));
|
||||
}
|
||||
return TRUE;
|
||||
},
|
||||
reinterpret_cast<LPARAM>(&monitors));
|
||||
|
||||
std::sort(monitors.rects.begin(), monitors.rects.end(), ScreenSortLess{});
|
||||
return monitors.rects;
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
@@ -123,6 +164,7 @@ void DesktopSnapshot::grab()
|
||||
#endif
|
||||
|
||||
m_virtualGeometry = geometry;
|
||||
buildScreenEntries();
|
||||
buildWindowList();
|
||||
}
|
||||
|
||||
@@ -131,6 +173,102 @@ void DesktopSnapshot::release()
|
||||
m_snapshot = QPixmap();
|
||||
m_virtualGeometry = QRect();
|
||||
m_windowRects.clear();
|
||||
m_screenEntries.clear();
|
||||
}
|
||||
|
||||
void DesktopSnapshot::buildScreenEntries()
|
||||
{
|
||||
m_screenEntries.clear();
|
||||
|
||||
QList<QScreen*> screens = QGuiApplication::screens();
|
||||
if (screens.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
const QVector<QRect> monitorRects = enumerateMonitorRects();
|
||||
std::sort(screens.begin(), screens.end(), [](QScreen *lhs, QScreen *rhs) {
|
||||
if (!lhs || !rhs) {
|
||||
return lhs < rhs;
|
||||
}
|
||||
return ScreenSortLess{}(lhs->geometry(), rhs->geometry());
|
||||
});
|
||||
|
||||
const int pairCount = qMin(screens.size(), monitorRects.size());
|
||||
for (int i = 0; i < pairCount; ++i) {
|
||||
QScreen *screen = screens.at(i);
|
||||
if (!screen) {
|
||||
continue;
|
||||
}
|
||||
ScreenEntry entry;
|
||||
entry.logicalGeometry = screen->geometry();
|
||||
entry.physicalRect = monitorRects.at(i);
|
||||
entry.dpr = screen->devicePixelRatio();
|
||||
if (entry.dpr <= 0.0) {
|
||||
entry.dpr = 1.0;
|
||||
}
|
||||
m_screenEntries.append(entry);
|
||||
}
|
||||
|
||||
for (int i = pairCount; i < screens.size(); ++i) {
|
||||
QScreen *screen = screens.at(i);
|
||||
if (!screen) {
|
||||
continue;
|
||||
}
|
||||
ScreenEntry entry;
|
||||
entry.logicalGeometry = screen->geometry();
|
||||
entry.dpr = screen->devicePixelRatio();
|
||||
if (entry.dpr <= 0.0) {
|
||||
entry.dpr = 1.0;
|
||||
}
|
||||
entry.physicalRect = QRect(qRound(entry.logicalGeometry.x() * entry.dpr),
|
||||
qRound(entry.logicalGeometry.y() * entry.dpr),
|
||||
qRound(entry.logicalGeometry.width() * entry.dpr),
|
||||
qRound(entry.logicalGeometry.height() * entry.dpr));
|
||||
m_screenEntries.append(entry);
|
||||
}
|
||||
#else
|
||||
for (QScreen *screen : screens) {
|
||||
if (!screen) {
|
||||
continue;
|
||||
}
|
||||
ScreenEntry entry;
|
||||
entry.logicalGeometry = screen->geometry();
|
||||
entry.physicalRect = entry.logicalGeometry;
|
||||
entry.dpr = screen->devicePixelRatio();
|
||||
if (entry.dpr <= 0.0) {
|
||||
entry.dpr = 1.0;
|
||||
}
|
||||
m_screenEntries.append(entry);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
const DesktopSnapshot::ScreenEntry *DesktopSnapshot::screenEntryForPoint(const QPoint &logicalPoint) const
|
||||
{
|
||||
for (const ScreenEntry &entry : m_screenEntries) {
|
||||
if (entry.logicalGeometry.contains(logicalPoint)) {
|
||||
return &entry;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_screenEntries.isEmpty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const ScreenEntry *nearest = &m_screenEntries.first();
|
||||
qreal bestDistance = (std::numeric_limits<qreal>::max)();
|
||||
for (const ScreenEntry &entry : m_screenEntries) {
|
||||
const QPoint center = entry.logicalGeometry.center();
|
||||
const qreal dx = logicalPoint.x() - center.x();
|
||||
const qreal dy = logicalPoint.y() - center.y();
|
||||
const qreal distance = dx * dx + dy * dy;
|
||||
if (distance < bestDistance) {
|
||||
bestDistance = distance;
|
||||
nearest = &entry;
|
||||
}
|
||||
}
|
||||
return nearest;
|
||||
}
|
||||
|
||||
QPoint DesktopSnapshot::mapLogicalPointToPhysical(const QPoint &logicalPoint) const
|
||||
@@ -138,7 +276,16 @@ QPoint DesktopSnapshot::mapLogicalPointToPhysical(const QPoint &logicalPoint) co
|
||||
if (m_virtualGeometry.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
return logicalPoint - m_virtualGeometry.topLeft();
|
||||
|
||||
const ScreenEntry *entry = screenEntryForPoint(logicalPoint);
|
||||
if (!entry) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const QPoint localPoint = logicalPoint - entry->logicalGeometry.topLeft();
|
||||
const QPoint physicalPoint(entry->physicalRect.x() + qRound(localPoint.x() * entry->dpr),
|
||||
entry->physicalRect.y() + qRound(localPoint.y() * entry->dpr));
|
||||
return physicalPoint - m_virtualGeometry.topLeft();
|
||||
}
|
||||
|
||||
QRect DesktopSnapshot::mapLogicalRectToPhysical(const QRect &logicalRect) const
|
||||
@@ -147,7 +294,18 @@ QRect DesktopSnapshot::mapLogicalRectToPhysical(const QRect &logicalRect) const
|
||||
if (normalized.isEmpty() || m_virtualGeometry.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
QRect translated = normalized.translated(-m_virtualGeometry.topLeft());
|
||||
|
||||
const ScreenEntry *entry = screenEntryForPoint(normalized.center());
|
||||
if (!entry) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const QPoint localTopLeft = normalized.topLeft() - entry->logicalGeometry.topLeft();
|
||||
const QPoint absolutePhysicalTopLeft(entry->physicalRect.x() + qRound(localTopLeft.x() * entry->dpr),
|
||||
entry->physicalRect.y() + qRound(localTopLeft.y() * entry->dpr));
|
||||
QRect translated(absolutePhysicalTopLeft - m_virtualGeometry.topLeft(),
|
||||
QSize(qMax(1, qRound(normalized.width() * entry->dpr)),
|
||||
qMax(1, qRound(normalized.height() * entry->dpr))));
|
||||
return translated.intersected(m_snapshot.rect());
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ private:
|
||||
qreal dpr = 1.0;
|
||||
};
|
||||
|
||||
void buildScreenEntries();
|
||||
void buildWindowList();
|
||||
const ScreenEntry* screenEntryForPoint(const QPoint &logicalPoint) const;
|
||||
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
#include "paint_board.h"
|
||||
#include "paint_board.h"
|
||||
#include "shape/mosaic_shape.h"
|
||||
#include <QDebug>
|
||||
|
||||
PaintBoard::PaintBoard(QQuickItem *parent)
|
||||
: QQuickPaintedItem(parent)
|
||||
{
|
||||
setRenderTarget(QQuickPaintedItem::FramebufferObject);
|
||||
setAcceptedMouseButtons(Qt::LeftButton);
|
||||
}
|
||||
|
||||
void PaintBoard::setBackgroundImg(QImage img)
|
||||
{
|
||||
// if (!img.isNull()) {
|
||||
// img.setDevicePixelRatio(1.0);
|
||||
// }
|
||||
m_background_img = img;
|
||||
update();
|
||||
}
|
||||
@@ -125,7 +129,8 @@ QImage PaintBoard::renderToImage() const
|
||||
if (m_shapes.isEmpty())
|
||||
return QImage();
|
||||
|
||||
QImage img(qRound(width()), qRound(height()), QImage::Format_ARGB32);
|
||||
QImage img(qRound(width() ), qRound(height()), QImage::Format_ARGB32);
|
||||
img.setDevicePixelRatio(1.0);
|
||||
img.fill(Qt::transparent);
|
||||
|
||||
QPainter painter(&img);
|
||||
@@ -140,13 +145,16 @@ QImage PaintBoard::renderToImage() const
|
||||
|
||||
void PaintBoard::paint(QPainter *painter)
|
||||
{
|
||||
// painter->save();
|
||||
// painter->scale(1.0 / m_canvasScale, 1.0 / m_canvasScale);
|
||||
drawBkImg(painter, m_background_img);
|
||||
drawShape(painter);
|
||||
// painter->restore();
|
||||
}
|
||||
|
||||
void PaintBoard::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
m_startPoint = event->position().toPoint();
|
||||
m_startPoint = (event->position()).toPoint();
|
||||
m_drawing = true;
|
||||
|
||||
if (m_currentType == TEXT) {
|
||||
@@ -206,7 +214,7 @@ void PaintBoard::mouseMoveEvent(QMouseEvent *event)
|
||||
if (!m_drawing || !m_currentShape)
|
||||
return;
|
||||
|
||||
m_currentShape->setEndPoint(event->position().toPoint());
|
||||
m_currentShape->setEndPoint((event->position()).toPoint());
|
||||
update();
|
||||
}
|
||||
|
||||
@@ -215,7 +223,7 @@ void PaintBoard::mouseReleaseEvent(QMouseEvent *event)
|
||||
if (!m_drawing || !m_currentShape)
|
||||
return;
|
||||
|
||||
m_currentShape->setEndPoint(event->position().toPoint());
|
||||
m_currentShape->setEndPoint((event->position()).toPoint());
|
||||
m_shapes.append(m_currentShape);
|
||||
m_currentShape = nullptr;
|
||||
m_drawing = false;
|
||||
@@ -232,9 +240,7 @@ bool PaintBoard::drawBkImg(QPainter *painter, QImage img)
|
||||
if (!painter || img.isNull())
|
||||
return false;
|
||||
|
||||
QRectF drawRect = img.rect();
|
||||
drawRect.moveCenter(boundingRect().center());
|
||||
painter->drawImage(drawRect, img);
|
||||
painter->drawImage(QRectF(0, 0, width() , height() ), img);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,37 +12,36 @@ void ArrowShape::draw(QPainter *painter)
|
||||
if (m_startPoint == m_endPoint) return;
|
||||
|
||||
painter->save();
|
||||
const QPointF startPoint(m_startPoint);
|
||||
const QPointF endPoint(m_endPoint);
|
||||
QPen pen(m_color, m_size);
|
||||
pen.setCapStyle(Qt::RoundCap);
|
||||
pen.setJoinStyle(Qt::RoundJoin);
|
||||
painter->setPen(pen);
|
||||
painter->setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
// 绘制箭杆
|
||||
painter->drawLine(m_startPoint, m_endPoint);
|
||||
painter->drawLine(startPoint, endPoint);
|
||||
|
||||
// 计算箭头方向角
|
||||
const double angle = std::atan2(
|
||||
m_endPoint.y() - m_startPoint.y(),
|
||||
m_endPoint.x() - m_startPoint.x()
|
||||
endPoint.y() - startPoint.y(),
|
||||
endPoint.x() - startPoint.x()
|
||||
);
|
||||
|
||||
// 计算箭头两侧的点
|
||||
QPoint arrowP1(
|
||||
m_endPoint.x() - static_cast<int>(std::cos(angle + ARROW_HEAD_ANGLE) * ARROW_HEAD_LENGTH),
|
||||
m_endPoint.y() - static_cast<int>(std::sin(angle + ARROW_HEAD_ANGLE) * ARROW_HEAD_LENGTH)
|
||||
const qreal headLength = ARROW_HEAD_LENGTH;
|
||||
QPointF arrowP1(
|
||||
endPoint.x() - std::cos(angle + ARROW_HEAD_ANGLE) * headLength,
|
||||
endPoint.y() - std::sin(angle + ARROW_HEAD_ANGLE) * headLength
|
||||
);
|
||||
QPoint arrowP2(
|
||||
m_endPoint.x() - static_cast<int>(std::cos(angle - ARROW_HEAD_ANGLE) * ARROW_HEAD_LENGTH),
|
||||
m_endPoint.y() - static_cast<int>(std::sin(angle - ARROW_HEAD_ANGLE) * ARROW_HEAD_LENGTH)
|
||||
QPointF arrowP2(
|
||||
endPoint.x() - std::cos(angle - ARROW_HEAD_ANGLE) * headLength,
|
||||
endPoint.y() - std::sin(angle - ARROW_HEAD_ANGLE) * headLength
|
||||
);
|
||||
|
||||
// 绘制实心箭头头部
|
||||
QPolygon arrowHead;
|
||||
arrowHead << m_endPoint << arrowP1 << arrowP2;
|
||||
QPolygonF arrowHead;
|
||||
arrowHead << endPoint << arrowP1 << arrowP2;
|
||||
painter->setBrush(QBrush(m_color));
|
||||
painter->drawPolygon(arrowHead);
|
||||
|
||||
painter->restore();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
#include "blur_shape.h"
|
||||
|
||||
#include <QPainter>
|
||||
#include <QPainterPath>
|
||||
|
||||
BlurShape::BlurShape(const QPoint &startPoint, int brushRadius, int blurRadius)
|
||||
: Shape(Qt::transparent, blurRadius)
|
||||
, m_brushRadius(qMax(4, brushRadius))
|
||||
, m_blurRadius(qMax(2, blurRadius))
|
||||
{
|
||||
m_points.append(startPoint);
|
||||
}
|
||||
|
||||
void BlurShape::setEndPoint(const QPoint &point)
|
||||
{
|
||||
if (!m_points.isEmpty() && m_points.last() == point) {
|
||||
return;
|
||||
}
|
||||
m_points.append(point);
|
||||
}
|
||||
|
||||
void BlurShape::draw(QPainter *painter)
|
||||
{
|
||||
if (!painter || m_blurredSnapshot.isNull() || m_points.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
painter->save();
|
||||
painter->setRenderHint(QPainter::Antialiasing, true);
|
||||
for (const QPoint &point : m_points) {
|
||||
QPainterPath path;
|
||||
path.addEllipse(QPointF(point), m_brushRadius, m_brushRadius);
|
||||
painter->save();
|
||||
painter->setClipPath(path);
|
||||
painter->drawImage(QPoint(0, 0), m_blurredSnapshot);
|
||||
painter->restore();
|
||||
}
|
||||
painter->restore();
|
||||
}
|
||||
|
||||
void BlurShape::setCanvasSnapshot(const QImage &snapshot)
|
||||
{
|
||||
if (snapshot.isNull()) {
|
||||
return;
|
||||
}
|
||||
m_canvasSnapshot = snapshot.convertToFormat(QImage::Format_ARGB32);
|
||||
m_blurredSnapshot = makeBlurredImage(m_canvasSnapshot);
|
||||
}
|
||||
|
||||
QImage BlurShape::makeBlurredImage(const QImage &source) const
|
||||
{
|
||||
if (source.isNull()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const int downscale = qMax(1, m_blurRadius);
|
||||
const QSize smallSize(qMax(1, source.width() / downscale),
|
||||
qMax(1, source.height() / downscale));
|
||||
const QImage small = source.scaled(smallSize,
|
||||
Qt::IgnoreAspectRatio,
|
||||
Qt::SmoothTransformation);
|
||||
return small.scaled(source.size(),
|
||||
Qt::IgnoreAspectRatio,
|
||||
Qt::SmoothTransformation);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
#ifndef BLUR_SHAPE_H
|
||||
#define BLUR_SHAPE_H
|
||||
|
||||
#include "shape.h"
|
||||
#include <QImage>
|
||||
#include <QVector>
|
||||
|
||||
class BlurShape : public Shape
|
||||
{
|
||||
public:
|
||||
BlurShape(const QPoint& startPoint,
|
||||
int brushRadius = 20,
|
||||
int blurRadius = 8);
|
||||
|
||||
void setEndPoint(const QPoint& point) override;
|
||||
void draw(QPainter* painter) override;
|
||||
void setCanvasSnapshot(const QImage& snapshot);
|
||||
|
||||
private:
|
||||
QImage makeBlurredImage(const QImage& source) const;
|
||||
|
||||
QVector<QPoint> m_points;
|
||||
QImage m_canvasSnapshot;
|
||||
QImage m_blurredSnapshot;
|
||||
int m_brushRadius;
|
||||
int m_blurRadius;
|
||||
};
|
||||
|
||||
#endif // BLUR_SHAPE_H
|
||||
@@ -12,8 +12,6 @@ void EllipseShape::draw(QPainter *painter)
|
||||
painter->setPen(pen);
|
||||
painter->setBrush(Qt::NoBrush);
|
||||
painter->setRenderHint(QPainter::Antialiasing);
|
||||
// 用两点构造外接矩形来绘制椭圆,normalized() 处理反向拖动
|
||||
painter->drawEllipse(QRect(m_startPoint, m_endPoint).normalized());
|
||||
painter->restore();
|
||||
}
|
||||
|
||||
@@ -57,7 +57,10 @@ void MosaicShape::draw(QPainter* painter)
|
||||
const QPoint& blockOrigin = it.key();
|
||||
const QColor& color = it.value();
|
||||
|
||||
QRect blockRect(blockOrigin.x(), blockOrigin.y(), m_blockSize, m_blockSize);
|
||||
QRectF blockRect(blockOrigin.x(),
|
||||
blockOrigin.y(),
|
||||
m_blockSize,
|
||||
m_blockSize);
|
||||
|
||||
// 无需裁剪到背景图范围,因为我们不再依赖背景图
|
||||
// 但为了保证安全,可以检查坐标是否为正
|
||||
|
||||
@@ -25,9 +25,11 @@ void NumberShape::draw(QPainter* painter)
|
||||
painter->save();
|
||||
painter->setRenderHint(QPainter::Antialiasing, true);
|
||||
painter->setRenderHint(QPainter::TextAntialiasing, true);
|
||||
|
||||
const int r = qMax(10, m_size * 2 + 6);
|
||||
const QRect circleRect(m_center.x() - r, m_center.y() - r, r * 2, r * 2);
|
||||
const QRect circleRect(m_center.x() - r,
|
||||
m_center.y() - r,
|
||||
r * 2,
|
||||
r * 2);
|
||||
|
||||
painter->setPen(QPen(Qt::white, qMax(2, m_size / 2)));
|
||||
painter->setBrush(m_color);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "pen_shape.h"
|
||||
#include "pen_shape.h"
|
||||
|
||||
PenShape::PenShape(const QPoint &startPoint, const QColor &color, int size)
|
||||
: Shape(color, size)
|
||||
@@ -25,14 +25,11 @@ void PenShape::draw(QPainter *painter)
|
||||
return;
|
||||
|
||||
painter->save();
|
||||
|
||||
QPen pen(m_color, m_size);
|
||||
pen.setCapStyle(Qt::RoundCap); // 端点圆滑
|
||||
pen.setJoinStyle(Qt::RoundJoin); // 转折点圆滑
|
||||
painter->setPen(pen);
|
||||
painter->setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
// 将所有点连成折线
|
||||
painter->drawPolyline(QPolygon(m_points));
|
||||
|
||||
painter->restore();
|
||||
|
||||
@@ -12,8 +12,6 @@ void RectShape::draw(QPainter *painter)
|
||||
painter->setPen(pen);
|
||||
painter->setBrush(Qt::NoBrush);
|
||||
painter->setRenderHint(QPainter::Antialiasing);
|
||||
// normalized() 处理从右下往左上拖动的情况
|
||||
painter->drawRect(QRect(m_startPoint, m_endPoint).normalized());
|
||||
painter->restore();
|
||||
}
|
||||
|
||||
@@ -10,7 +10,8 @@ enum Shapeype
|
||||
MOSAIC,
|
||||
TEXT,
|
||||
NUMBER,
|
||||
HIGHLIGHT
|
||||
HIGHLIGHT,
|
||||
BLUR
|
||||
};
|
||||
|
||||
#endif // SHAPETYPE_H
|
||||
@@ -27,7 +27,6 @@ void TextShape::draw(QPainter* painter)
|
||||
painter->save();
|
||||
painter->setRenderHint(QPainter::Antialiasing, true);
|
||||
painter->setRenderHint(QPainter::TextAntialiasing, true);
|
||||
|
||||
QFont f = painter->font();
|
||||
f.setPixelSize(qMax(12, m_size * 4));
|
||||
painter->setFont(f);
|
||||
|
||||
@@ -10,13 +10,32 @@
|
||||
#include <QScreen>
|
||||
#include <QWindow>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <shellscalingapi.h>
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
qreal captureDprForRect(const QRect& rect)
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
if (QScreen* screen = QGuiApplication::screenAt(rect.center())) {
|
||||
return screen->devicePixelRatio();
|
||||
const POINT centerPoint {
|
||||
rect.center().x(),
|
||||
rect.center().y()
|
||||
};
|
||||
if (HMONITOR monitor = MonitorFromPoint(centerPoint, MONITOR_DEFAULTTONEAREST)) {
|
||||
using GetDpiForMonitorFn = HRESULT (WINAPI *)(HMONITOR, MONITOR_DPI_TYPE, UINT *, UINT *);
|
||||
static const auto getDpiForMonitor = reinterpret_cast<GetDpiForMonitorFn>(
|
||||
GetProcAddress(LoadLibraryW(L"Shcore.dll"), "GetDpiForMonitor"));
|
||||
if (getDpiForMonitor) {
|
||||
UINT dpiX = 0;
|
||||
UINT dpiY = 0;
|
||||
if (SUCCEEDED(getDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY)) && dpiX > 0) {
|
||||
return qreal(dpiX) / 96.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (QScreen* screen = QGuiApplication::primaryScreen()) {
|
||||
@@ -25,6 +44,20 @@ qreal captureDprForRect(const QRect& rect)
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
qreal effectiveCaptureDpr(QQuickItem *paintBoard, const QRect &rect)
|
||||
{
|
||||
if (!rect.isEmpty()) {
|
||||
return captureDprForRect(rect);
|
||||
}
|
||||
if (paintBoard && paintBoard->window()) {
|
||||
return paintBoard->window()->devicePixelRatio();
|
||||
}
|
||||
if (QScreen *screen = QGuiApplication::primaryScreen()) {
|
||||
return screen->devicePixelRatio();
|
||||
}
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
QRect captureRectInSnapshot(const DesktopSnapshot& snapshot, const QRect& rect, qreal dpr)
|
||||
{
|
||||
if (snapshot.isNull()) {
|
||||
@@ -35,6 +68,11 @@ QRect captureRectInSnapshot(const DesktopSnapshot& snapshot, const QRect& rect,
|
||||
return snapshot.image().rect();
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
Q_UNUSED(dpr);
|
||||
const QRect localRect = rect.translated(-snapshot.virtualGeometry().topLeft());
|
||||
return localRect.intersected(snapshot.image().rect());
|
||||
#else
|
||||
const QRect localRect = rect.translated(-snapshot.virtualGeometry().topLeft());
|
||||
const QPoint topLeft = QPoint(qRound(localRect.left() * dpr),
|
||||
qRound(localRect.top() * dpr));
|
||||
@@ -43,6 +81,7 @@ QRect captureRectInSnapshot(const DesktopSnapshot& snapshot, const QRect& rect,
|
||||
QRect target;
|
||||
target.setCoords(topLeft.x(), topLeft.y(), bottomRight.x(), bottomRight.y());
|
||||
return target.intersected(snapshot.image().rect());
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
@@ -140,6 +179,11 @@ QString ScreenshotViewModel::captureRectToStickyUrl(QQuickItem *paintBoard, cons
|
||||
return {};
|
||||
}
|
||||
|
||||
const qreal dpr = effectiveCaptureDpr(paintBoard, r);
|
||||
if (dpr > 0.0) {
|
||||
img.setDevicePixelRatio(dpr);
|
||||
}
|
||||
|
||||
return m_stickyStore.storeImage(img);
|
||||
}
|
||||
|
||||
@@ -199,11 +243,7 @@ QImage ScreenshotViewModel::captureScreen(QQuickItem *paintBoard,
|
||||
qreal dpr = 1.0;
|
||||
|
||||
if (!m_snapshot.isNull()) {
|
||||
if (board && board->window()) {
|
||||
dpr = board->window()->devicePixelRatio();
|
||||
} else if (!rect.isEmpty()) {
|
||||
dpr = captureDprForRect(rect);
|
||||
}
|
||||
dpr = effectiveCaptureDpr(paintBoard, rect);
|
||||
const QRect captureRect = captureRectInSnapshot(m_snapshot, rect, dpr);
|
||||
screenshot = m_snapshot.image().copy(captureRect).toImage();
|
||||
} else {
|
||||
@@ -249,9 +289,9 @@ QImage ScreenshotViewModel::captureScreen(QQuickItem *paintBoard,
|
||||
|
||||
if (setBackground) {
|
||||
QImage background = screenshot;
|
||||
if (board->window() && board->window()->devicePixelRatio() > 1.0) {
|
||||
const QSize logicalSize(qRound(background.width() / board->window()->devicePixelRatio()),
|
||||
qRound(background.height() / board->window()->devicePixelRatio()));
|
||||
if (dpr > 1.0) {
|
||||
const QSize logicalSize(qRound(background.width() / dpr),
|
||||
qRound(background.height() / dpr));
|
||||
if (logicalSize.isValid() && logicalSize != background.size()) {
|
||||
background = background.scaled(logicalSize,
|
||||
Qt::IgnoreAspectRatio,
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
#include "sticky_view_model.h"
|
||||
#include <QPainter>
|
||||
#include <QTransform>
|
||||
#include "sticky_view_model.h"
|
||||
|
||||
#include "ai_view_model.h"
|
||||
#include "widgets/sticky_pin_widget.h"
|
||||
|
||||
#include <QGuiApplication>
|
||||
#include <QPainter>
|
||||
#include <QScreen>
|
||||
#include <QTransform>
|
||||
#include <QWindow>
|
||||
#include <QDebug>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <windows.h>
|
||||
@@ -16,6 +19,11 @@ StickyViewModel::StickyViewModel(StickyImageStore &store, QObject *parent)
|
||||
{
|
||||
}
|
||||
|
||||
void StickyViewModel::setAiViewModel(AIViewModel *aiViewModel)
|
||||
{
|
||||
m_aiViewModel = aiViewModel;
|
||||
}
|
||||
|
||||
void StickyViewModel::requestSticky(const QString &imageUrl, const QRect &imgRect)
|
||||
{
|
||||
if (imageUrl.isEmpty()) {
|
||||
@@ -23,16 +31,14 @@ void StickyViewModel::requestSticky(const QString &imageUrl, const QRect &imgRec
|
||||
}
|
||||
|
||||
QRect targetRect = imgRect.normalized();
|
||||
QScreen *screen = QGuiApplication::screenAt(targetRect.topLeft());
|
||||
if (!screen) {
|
||||
screen = QGuiApplication::screenAt(targetRect.center());
|
||||
#ifdef Q_OS_WIN
|
||||
const QImage image = m_store.getImageByUrl(imageUrl);
|
||||
if (!image.isNull()) {
|
||||
auto *widget = new StickyPinWidget(imageUrl, targetRect, image, &m_store, m_aiViewModel);
|
||||
Q_UNUSED(widget);
|
||||
return;
|
||||
}
|
||||
if (!screen) {
|
||||
screen = QGuiApplication::primaryScreen();
|
||||
}
|
||||
|
||||
QImage image = m_store.getImageByUrl(imageUrl);
|
||||
|
||||
#endif
|
||||
emit stickyReady(imageUrl, targetRect);
|
||||
}
|
||||
|
||||
@@ -51,22 +57,58 @@ void StickyViewModel::positionStickyWindow(QObject *windowObject, const QRect &i
|
||||
const QRect targetRect = imgRect.normalized();
|
||||
const QPoint topLeft = targetRect.topLeft();
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
if (HWND hwnd = reinterpret_cast<HWND>(window->winId())) {
|
||||
SetWindowPos(hwnd,
|
||||
HWND_TOPMOST,
|
||||
topLeft.x(),
|
||||
topLeft.y(),
|
||||
0,
|
||||
0,
|
||||
SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOOWNERZORDER);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
QScreen *screen = QGuiApplication::screenAt(topLeft);
|
||||
if (!screen) {
|
||||
screen = QGuiApplication::screenAt(targetRect.center());
|
||||
}
|
||||
if (!screen) {
|
||||
screen = QGuiApplication::primaryScreen();
|
||||
}
|
||||
|
||||
if (screen && window->screen() != screen) {
|
||||
window->setScreen(screen);
|
||||
}
|
||||
|
||||
window->setPosition(topLeft);
|
||||
}
|
||||
|
||||
void StickyViewModel::resizeStickyWindow(QObject *windowObject, int physicalWidth, int physicalHeight) const
|
||||
{
|
||||
auto *window = qobject_cast<QWindow*>(windowObject);
|
||||
if (!window) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int targetWidth = qMax(1, physicalWidth);
|
||||
const int targetHeight = qMax(1, physicalHeight);
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
if (HWND hwnd = reinterpret_cast<HWND>(window->winId())) {
|
||||
RECT rect{};
|
||||
GetWindowRect(hwnd, &rect);
|
||||
qDebug() << "[StickyHWND] qt-pos:"
|
||||
<< topLeft
|
||||
<< "hwnd:"
|
||||
<< QRect(rect.left,
|
||||
rect.top,
|
||||
rect.right - rect.left,
|
||||
rect.bottom - rect.top)
|
||||
<< "qmlPos:" << window->x() << window->y();
|
||||
SetWindowPos(hwnd,
|
||||
nullptr,
|
||||
0,
|
||||
0,
|
||||
targetWidth,
|
||||
targetHeight,
|
||||
SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOOWNERZORDER);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
window->resize(targetWidth, targetHeight);
|
||||
}
|
||||
|
||||
bool StickyViewModel::saveImage(const QString &imageUrl, const QUrl &targetUrl)
|
||||
@@ -86,7 +128,11 @@ QImage StickyViewModel::getImageByUrl(const QString &imageUrl) const
|
||||
|
||||
QSize StickyViewModel::getImageSizeByUrl(const QString &imageUrl) const
|
||||
{
|
||||
return m_store.getImageByUrl(imageUrl).size();
|
||||
const QImage image = m_store.getImageByUrl(imageUrl);
|
||||
if (image.isNull()) {
|
||||
return {};
|
||||
}
|
||||
return image.size();
|
||||
}
|
||||
|
||||
QImage StickyViewModel::mergeLayers(const QString &imageUrl, const QImage &annotationLayer) const
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "sticky_image_store.h"
|
||||
|
||||
class QScreen;
|
||||
class AIViewModel;
|
||||
|
||||
// StickyViewModel
|
||||
// ---------------
|
||||
@@ -21,6 +22,7 @@ class StickyViewModel : public QObject
|
||||
|
||||
public:
|
||||
explicit StickyViewModel(StickyImageStore &store, QObject *parent = nullptr);
|
||||
void setAiViewModel(AIViewModel *aiViewModel);
|
||||
|
||||
// 通知 QML 创建贴图窗口,由 ScreenshotViewModel::captureRectToStickyUrl 产生的 URL 触发
|
||||
// QML 监听 stickyReady(imageUrl, imgRect) 信号后创建 TZStickyWindow
|
||||
@@ -33,6 +35,7 @@ public:
|
||||
Q_INVOKABLE QImage getImageByUrl(const QString &imageUrl) const;
|
||||
Q_INVOKABLE QSize getImageSizeByUrl(const QString &imageUrl) const;
|
||||
Q_INVOKABLE void positionStickyWindow(QObject *windowObject, const QRect &imgRect) const;
|
||||
Q_INVOKABLE void resizeStickyWindow(QObject *windowObject, int physicalWidth, int physicalHeight) const;
|
||||
Q_INVOKABLE bool overwriteWithAnnotations(const QString &imageUrl, const QImage &annotationLayer);
|
||||
Q_INVOKABLE bool saveMergedImage(const QString &imageUrl, const QImage &annotationLayer, const QUrl &targetUrl);
|
||||
Q_INVOKABLE bool rotateImage(const QString &imageUrl, int degreesClockwise);
|
||||
@@ -46,6 +49,7 @@ private:
|
||||
QImage mergeLayers(const QString &imageUrl, const QImage &annotationLayer) const;
|
||||
|
||||
StickyImageStore &m_store;
|
||||
AIViewModel *m_aiViewModel = nullptr;
|
||||
};
|
||||
|
||||
#endif // STICKY_VIEW_MODEL_H
|
||||
@@ -0,0 +1,262 @@
|
||||
#include "widgets/sticky_canvas_widget.h"
|
||||
|
||||
#include "paint_board/shape/blur_shape.h"
|
||||
#include "paint_board/shape/mosaic_shape.h"
|
||||
|
||||
#include <QMouseEvent>
|
||||
#include <QPainter>
|
||||
|
||||
StickyCanvasWidget::StickyCanvasWidget(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
setAttribute(Qt::WA_OpaquePaintEvent, false);
|
||||
setAttribute(Qt::WA_TransparentForMouseEvents, true);
|
||||
setMouseTracking(true);
|
||||
}
|
||||
|
||||
StickyCanvasWidget::~StickyCanvasWidget()
|
||||
{
|
||||
clearShapes();
|
||||
}
|
||||
|
||||
void StickyCanvasWidget::setBackgroundImage(const QImage &image)
|
||||
{
|
||||
m_backgroundImage = image;
|
||||
for (Shape *shape : m_shapes) {
|
||||
if (auto *mosaic = dynamic_cast<MosaicShape*>(shape)) {
|
||||
mosaic->setCanvasSnapshot(m_backgroundImage);
|
||||
} else if (auto *blur = dynamic_cast<BlurShape*>(shape)) {
|
||||
blur->setCanvasSnapshot(m_backgroundImage);
|
||||
}
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
void StickyCanvasWidget::setViewScale(qreal scale)
|
||||
{
|
||||
const qreal next = qMax<qreal>(0.01, scale);
|
||||
if (qFuzzyCompare(m_viewScale, next)) {
|
||||
return;
|
||||
}
|
||||
m_viewScale = next;
|
||||
update();
|
||||
}
|
||||
|
||||
void StickyCanvasWidget::setActiveShapeType(Shapeype type)
|
||||
{
|
||||
m_shapeType = type;
|
||||
}
|
||||
|
||||
void StickyCanvasWidget::setPenColor(const QColor &color)
|
||||
{
|
||||
m_penColor = color;
|
||||
}
|
||||
|
||||
void StickyCanvasWidget::setPenSize(int size)
|
||||
{
|
||||
m_penSize = qMax(1, size);
|
||||
}
|
||||
|
||||
void StickyCanvasWidget::setDrawingEnabled(bool enabled)
|
||||
{
|
||||
m_drawingEnabled = enabled;
|
||||
setAttribute(Qt::WA_TransparentForMouseEvents, !m_drawingEnabled);
|
||||
if (!m_drawingEnabled) {
|
||||
finishCurrentShape();
|
||||
}
|
||||
}
|
||||
|
||||
void StickyCanvasWidget::setAnnotationText(const QString &text)
|
||||
{
|
||||
m_annotationText = text;
|
||||
}
|
||||
|
||||
void StickyCanvasWidget::setTextBackgroundEnabled(bool enabled)
|
||||
{
|
||||
m_textBackgroundEnabled = enabled;
|
||||
}
|
||||
|
||||
void StickyCanvasWidget::setNumberValue(int value)
|
||||
{
|
||||
m_numberValue = qMax(1, value);
|
||||
}
|
||||
|
||||
void StickyCanvasWidget::setNumberAutoIncrement(bool enabled)
|
||||
{
|
||||
m_numberAutoIncrement = enabled;
|
||||
}
|
||||
|
||||
void StickyCanvasWidget::addTextAnnotation(const QPoint &point, const QString &text)
|
||||
{
|
||||
const QString resolved = text.trimmed();
|
||||
if (resolved.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QPoint imagePoint(qRound(point.x() / m_viewScale),
|
||||
qRound(point.y() / m_viewScale));
|
||||
|
||||
Shape *textShape = m_factory.createTextShape(imagePoint,
|
||||
resolved,
|
||||
m_penColor,
|
||||
m_penSize,
|
||||
m_textBackgroundEnabled);
|
||||
if (!textShape) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_shapes.append(textShape);
|
||||
m_annotationText = resolved;
|
||||
update();
|
||||
}
|
||||
|
||||
void StickyCanvasWidget::undo()
|
||||
{
|
||||
if (!m_shapes.isEmpty()) {
|
||||
delete m_shapes.takeLast();
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
void StickyCanvasWidget::reset()
|
||||
{
|
||||
finishCurrentShape();
|
||||
clearShapes();
|
||||
m_backgroundImage = QImage();
|
||||
update();
|
||||
}
|
||||
|
||||
void StickyCanvasWidget::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
Q_UNUSED(event);
|
||||
QPainter painter(this);
|
||||
painter.setRenderHint(QPainter::Antialiasing, true);
|
||||
painter.fillRect(rect(), Qt::white);
|
||||
|
||||
if (!m_backgroundImage.isNull()) {
|
||||
painter.drawImage(rect(), m_backgroundImage);
|
||||
}
|
||||
|
||||
painter.save();
|
||||
painter.scale(m_viewScale, m_viewScale);
|
||||
for (Shape *shape : m_shapes) {
|
||||
if (shape) {
|
||||
shape->draw(&painter);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_currentShape) {
|
||||
m_currentShape->draw(&painter);
|
||||
}
|
||||
painter.restore();
|
||||
}
|
||||
|
||||
void StickyCanvasWidget::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
if (!m_drawingEnabled || event->button() != Qt::LeftButton) {
|
||||
QWidget::mousePressEvent(event);
|
||||
return;
|
||||
}
|
||||
|
||||
const QPoint displayPoint = event->position().toPoint();
|
||||
m_startPoint = QPoint(qRound(displayPoint.x() / m_viewScale),
|
||||
qRound(displayPoint.y() / m_viewScale));
|
||||
|
||||
if (m_shapeType == TEXT) {
|
||||
emit textPlacementRequested(displayPoint);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_shapeType == NUMBER) {
|
||||
Shape *numberShape = m_factory.createNumberShape(m_startPoint,
|
||||
m_numberValue,
|
||||
m_penColor,
|
||||
m_penSize);
|
||||
if (numberShape) {
|
||||
m_shapes.append(numberShape);
|
||||
if (m_numberAutoIncrement) {
|
||||
++m_numberValue;
|
||||
emit numberValueChanged(m_numberValue);
|
||||
}
|
||||
update();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
delete m_currentShape;
|
||||
m_currentShape = nullptr;
|
||||
|
||||
if (m_shapeType == MOSAIC) {
|
||||
auto *mosaic = new MosaicShape(m_startPoint,
|
||||
qMax(10, m_penSize * 5),
|
||||
qMax(2, m_penSize));
|
||||
mosaic->setCanvasSnapshot(m_backgroundImage);
|
||||
m_currentShape = mosaic;
|
||||
m_draggingShape = true;
|
||||
update();
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_shapeType == BLUR) {
|
||||
auto *blur = new BlurShape(m_startPoint,
|
||||
qMax(10, m_penSize * 5),
|
||||
qMax(2, m_penSize));
|
||||
blur->setCanvasSnapshot(m_backgroundImage);
|
||||
m_currentShape = blur;
|
||||
m_draggingShape = true;
|
||||
update();
|
||||
return;
|
||||
}
|
||||
|
||||
m_currentShape = m_factory.createShape(m_shapeType,
|
||||
m_startPoint,
|
||||
m_startPoint,
|
||||
m_penColor,
|
||||
m_penSize);
|
||||
m_draggingShape = (m_currentShape != nullptr);
|
||||
update();
|
||||
}
|
||||
|
||||
void StickyCanvasWidget::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
if (!m_draggingShape || !m_currentShape) {
|
||||
QWidget::mouseMoveEvent(event);
|
||||
return;
|
||||
}
|
||||
|
||||
const QPoint imagePoint(qRound(event->position().x() / m_viewScale),
|
||||
qRound(event->position().y() / m_viewScale));
|
||||
m_currentShape->setEndPoint(imagePoint);
|
||||
update();
|
||||
}
|
||||
|
||||
void StickyCanvasWidget::mouseReleaseEvent(QMouseEvent *event)
|
||||
{
|
||||
if (!m_draggingShape || !m_currentShape || event->button() != Qt::LeftButton) {
|
||||
QWidget::mouseReleaseEvent(event);
|
||||
return;
|
||||
}
|
||||
|
||||
const QPoint imagePoint(qRound(event->position().x() / m_viewScale),
|
||||
qRound(event->position().y() / m_viewScale));
|
||||
m_currentShape->setEndPoint(imagePoint);
|
||||
m_shapes.append(m_currentShape);
|
||||
m_currentShape = nullptr;
|
||||
m_draggingShape = false;
|
||||
update();
|
||||
}
|
||||
|
||||
void StickyCanvasWidget::finishCurrentShape()
|
||||
{
|
||||
if (m_currentShape) {
|
||||
delete m_currentShape;
|
||||
m_currentShape = nullptr;
|
||||
}
|
||||
m_draggingShape = false;
|
||||
}
|
||||
|
||||
void StickyCanvasWidget::clearShapes()
|
||||
{
|
||||
qDeleteAll(m_shapes);
|
||||
m_shapes.clear();
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
#ifndef STICKY_CANVAS_WIDGET_H
|
||||
#define STICKY_CANVAS_WIDGET_H
|
||||
|
||||
#include <QColor>
|
||||
#include <QImage>
|
||||
#include <QPoint>
|
||||
#include <QWidget>
|
||||
|
||||
#include "paint_board/shape/shape.h"
|
||||
#include "paint_board/shape/shape_factory.h"
|
||||
#include "paint_board/shape/shape_type.h"
|
||||
|
||||
class StickyCanvasWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit StickyCanvasWidget(QWidget *parent = nullptr);
|
||||
~StickyCanvasWidget() override;
|
||||
|
||||
void setBackgroundImage(const QImage &image);
|
||||
void setViewScale(qreal scale);
|
||||
void setActiveShapeType(Shapeype type);
|
||||
void setPenColor(const QColor &color);
|
||||
void setPenSize(int size);
|
||||
void setDrawingEnabled(bool enabled);
|
||||
void setAnnotationText(const QString &text);
|
||||
void setTextBackgroundEnabled(bool enabled);
|
||||
void setNumberValue(int value);
|
||||
void setNumberAutoIncrement(bool enabled);
|
||||
bool drawingEnabled() const { return m_drawingEnabled; }
|
||||
Shapeype activeShapeType() const { return m_shapeType; }
|
||||
void addTextAnnotation(const QPoint &point, const QString &text);
|
||||
void undo();
|
||||
void reset();
|
||||
|
||||
signals:
|
||||
void numberValueChanged(int value);
|
||||
void textPlacementRequested(const QPoint &point);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
void mouseMoveEvent(QMouseEvent *event) override;
|
||||
void mouseReleaseEvent(QMouseEvent *event) override;
|
||||
|
||||
private:
|
||||
void finishCurrentShape();
|
||||
void clearShapes();
|
||||
|
||||
ShapeFactory m_factory;
|
||||
QList<Shape*> m_shapes;
|
||||
Shape *m_currentShape = nullptr;
|
||||
QImage m_backgroundImage;
|
||||
QColor m_penColor = Qt::red;
|
||||
int m_penSize = 6;
|
||||
Shapeype m_shapeType = PEN;
|
||||
QString m_annotationText = QStringLiteral("Text");
|
||||
bool m_textBackgroundEnabled = true;
|
||||
bool m_numberAutoIncrement = true;
|
||||
int m_numberValue = 1;
|
||||
bool m_drawingEnabled = false;
|
||||
bool m_draggingShape = false;
|
||||
qreal m_viewScale = 1.0;
|
||||
QPoint m_startPoint;
|
||||
};
|
||||
|
||||
#endif // STICKY_CANVAS_WIDGET_H
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,116 @@
|
||||
#ifndef STICKY_PIN_WIDGET_H
|
||||
#define STICKY_PIN_WIDGET_H
|
||||
|
||||
#include <QImage>
|
||||
#include <QPointer>
|
||||
#include <QRect>
|
||||
#include <QSize>
|
||||
#include <QVector>
|
||||
#include <QWidget>
|
||||
|
||||
#include "paint_board/shape/shape_type.h"
|
||||
|
||||
class StickyImageStore;
|
||||
class AIViewModel;
|
||||
class QCheckBox;
|
||||
class QLabel;
|
||||
class QLineEdit;
|
||||
class QSpinBox;
|
||||
class QToolButton;
|
||||
class StickyCanvasWidget;
|
||||
|
||||
class StickyPinWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
StickyPinWidget(const QString &imageUrl,
|
||||
const QRect &physicalRect,
|
||||
const QImage &image,
|
||||
StickyImageStore *store,
|
||||
AIViewModel *aiViewModel = nullptr,
|
||||
QWidget *parent = nullptr);
|
||||
~StickyPinWidget() override;
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
void closeEvent(QCloseEvent *event) override;
|
||||
void contextMenuEvent(QContextMenuEvent *event) override;
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
void wheelEvent(QWheelEvent *event) override;
|
||||
|
||||
private:
|
||||
void setActiveTool(Shapeype type, QToolButton *activeButton);
|
||||
void syncCanvasToolSettings();
|
||||
void updateToolOptionsPanel();
|
||||
void updateLayoutAndSize();
|
||||
QToolButton *currentActiveToolButton() const;
|
||||
void setZoomFactor(qreal value);
|
||||
void openAiEditor();
|
||||
void applyAiImage(const QString &oldImageUrl, const QString &newImageUrl);
|
||||
void releaseStoredImage();
|
||||
bool saveCurrentImage();
|
||||
qreal screenScaleForRect(const QRect &physicalRect) const;
|
||||
void applyNativePosition(const QRect &physicalRect);
|
||||
QPoint currentContentTopLeftPhysical() const;
|
||||
QRect contentRect() const;
|
||||
QRect toolOptionsRect() const;
|
||||
QRect toolbarRect() const;
|
||||
int shadowPadding() const;
|
||||
int toolbarGap() const;
|
||||
int toolOptionsGap() const;
|
||||
int toolOptionsHeight() const;
|
||||
int toolOptionsWidth() const;
|
||||
int toolbarHeight() const;
|
||||
int toolbarWidth() const;
|
||||
|
||||
QString m_imageUrl;
|
||||
QRect m_physicalRect;
|
||||
QImage m_image;
|
||||
QSize m_baseImageDisplaySize;
|
||||
QSize m_imageDisplaySize;
|
||||
QPointer<StickyImageStore> m_store;
|
||||
QPointer<AIViewModel> m_aiViewModel;
|
||||
StickyCanvasWidget *m_canvas = nullptr;
|
||||
QWidget *m_toolOptions = nullptr;
|
||||
QWidget *m_toolbar = nullptr;
|
||||
QLabel *m_zoomLabel = nullptr;
|
||||
QVector<QToolButton*> m_colorButtons;
|
||||
QVector<QToolButton*> m_sizeButtons;
|
||||
QLineEdit *m_inlineTextEditor = nullptr;
|
||||
QLineEdit *m_textInput = nullptr;
|
||||
QCheckBox *m_textBackgroundCheck = nullptr;
|
||||
QCheckBox *m_numberAutoIncrementCheck = nullptr;
|
||||
QToolButton *m_numberMinusButton = nullptr;
|
||||
QLabel *m_numberLabel = nullptr;
|
||||
QToolButton *m_numberPlusButton = nullptr;
|
||||
QSpinBox *m_numberValueSpin = nullptr;
|
||||
QToolButton *m_pencilButton = nullptr;
|
||||
QToolButton *m_rectButton = nullptr;
|
||||
QToolButton *m_circleButton = nullptr;
|
||||
QToolButton *m_arrowButton = nullptr;
|
||||
QToolButton *m_highlightButton = nullptr;
|
||||
QToolButton *m_mosaicButton = nullptr;
|
||||
QToolButton *m_blurButton = nullptr;
|
||||
QToolButton *m_textButton = nullptr;
|
||||
QToolButton *m_numberButton = nullptr;
|
||||
QToolButton *m_undoButton = nullptr;
|
||||
QToolButton *m_aiButton = nullptr;
|
||||
QToolButton *m_copyButton = nullptr;
|
||||
QToolButton *m_saveButton = nullptr;
|
||||
QToolButton *m_toggleToolbarButton = nullptr;
|
||||
QToolButton *m_closeButton = nullptr;
|
||||
QColor m_currentPenColor = QColor("#F43F5E");
|
||||
int m_currentPenSize = 6;
|
||||
QString m_annotationText = QStringLiteral("文本");
|
||||
QPoint m_pendingTextPoint;
|
||||
bool m_textBackgroundEnabled = true;
|
||||
bool m_numberAutoIncrement = true;
|
||||
int m_numberValue = 1;
|
||||
bool m_toolbarVisible = true;
|
||||
bool m_shadowVisible = true;
|
||||
qreal m_zoomFactor = 1.0;
|
||||
bool m_released = false;
|
||||
};
|
||||
|
||||
#endif // STICKY_PIN_WIDGET_H
|
||||
Reference in new issue
Block a user