484 lines
17 KiB
QML
484 lines
17 KiB
QML
// ==============================================
|
||
// =============== 功能页:截图OCR ===============
|
||
// ==============================================
|
||
|
||
import QtQuick 2.15
|
||
|
||
import ".."
|
||
import "../../Widgets"
|
||
import "../../Widgets/ResultLayout"
|
||
import "../../Widgets/ImageViewer"
|
||
|
||
TabPage {
|
||
id: tabPage
|
||
// 配置
|
||
configsComp: ScreenshotOcrConfigs {}
|
||
property string msnState: "none" // OCR任务状态, none run
|
||
|
||
// ========================= 【逻辑】 =========================
|
||
|
||
// 重复截图
|
||
function reScreenshot() {
|
||
qmlapp.imageManager.reScreenshot(screenshotEnd)
|
||
}
|
||
|
||
// 开始截图
|
||
function screenshot() {
|
||
qmlapp.imageManager.screenshot(screenshotEnd)
|
||
}
|
||
|
||
// 截图完毕
|
||
function screenshotEnd(clipID) {
|
||
popMainWindow()
|
||
if(!clipID) { // 截图取消
|
||
tabPage.callPy("ocrImgID", undefined, undefined)
|
||
return
|
||
}
|
||
const configDict = configsComp.getValueDict()
|
||
tabPage.callPy("ocrImgID", clipID, configDict)
|
||
qmlapp.tab.showTabPageObj(tabPage) // 切换标签页
|
||
imageText.showImgID(clipID) // 展示图片
|
||
}
|
||
|
||
// 指定区域截图。rect=[x,y,w,h] screen=屏幕编号 返回"[Success]"为成功
|
||
function autoScreenshot(rect, screen) {
|
||
// 获取截图
|
||
const clipID = qmlapp.imageManager.getScreenshot(rect, screen)
|
||
if(!clipID) {
|
||
tabPage.callPy("ocrImgID", "[Error] Unknow", undefined)
|
||
return
|
||
}
|
||
if(clipID.startsWith("[")) {
|
||
tabPage.callPy("ocrImgID", clipID, undefined)
|
||
return
|
||
}
|
||
// 进行识别
|
||
const configDict = configsComp.getValueDict()
|
||
tabPage.callPy("ocrImgID", clipID, configDict)
|
||
}
|
||
|
||
// 开始粘贴
|
||
function paste() {
|
||
popMainWindow()
|
||
const res = qmlapp.imageManager.getPaste()
|
||
if(res.error) {
|
||
const t = qsTr("获取剪贴板异常")
|
||
qmlapp.popup.simple(t, res.error)
|
||
tabPage.callPy("ocrImgID", `[Error] ${t} ${res.error}`, undefined)
|
||
return
|
||
}
|
||
if(res.text) {
|
||
const t = qsTr("剪贴板中为文本")
|
||
qmlapp.popup.simple(t, res.text)
|
||
tabPage.callPy("ocrImgID", `[Warning] ${t}`, undefined)
|
||
return
|
||
}
|
||
qmlapp.tab.showTabPageObj(tabPage) // 切换标签页
|
||
if(res.imgID) { // 图片
|
||
imageText.showImgID(res.imgID)
|
||
const configDict = configsComp.getValueDict()
|
||
tabPage.callPy("ocrImgID", res.imgID, configDict)
|
||
}
|
||
else if(res.paths) { // 地址
|
||
ocrPaths(res.paths)
|
||
}
|
||
}
|
||
|
||
// 异步扫描一批图像路径
|
||
function ocrPaths(paths) {
|
||
qmlapp.asynFilesLoader.run(paths,"image",false,onAddImages)
|
||
}
|
||
// 完毕后,对合法路径进行OCR
|
||
function onAddImages(paths) {
|
||
if(!paths || paths.length < 1) {
|
||
qmlapp.popup.simple(qsTr("无有效图片"), "")
|
||
return
|
||
}
|
||
const configDict = configsComp.getValueDict()
|
||
const simpleType = configDict["other.simpleNotificationType"]
|
||
qmlapp.popup.simple(qsTr("导入%1条图片路径").arg(paths.length), "", simpleType)
|
||
imageText.showPath(paths[0])
|
||
tabPage.callPy("ocrPaths", paths, configDict)
|
||
}
|
||
|
||
// 停止所有任务
|
||
function msnStop() {
|
||
tabPage.callPy("msnStop")
|
||
}
|
||
|
||
// 关闭页面
|
||
function closePage() {
|
||
if(msnState !== "none") {
|
||
const argd = {yesText: qsTr("依然关闭")}
|
||
const callback = (flag)=>{
|
||
if(flag) {
|
||
msnStop()
|
||
eventUnsub()
|
||
delPage()
|
||
}
|
||
}
|
||
qmlapp.popup.dialog("", qsTr("任务正在进行中。\n要结束任务并关闭页面吗?"), callback, "warning", argd)
|
||
}
|
||
else {
|
||
eventUnsub()
|
||
delPage()
|
||
}
|
||
}
|
||
|
||
// 弹出主窗口
|
||
function popMainWindow() {
|
||
// 若主窗口已经可见,则不处理
|
||
if(qmlapp.mainWin.getVisibility())
|
||
return
|
||
// 等一回合再弹,防止与收回截图窗口相冲突
|
||
if(configsComp.getValue("action.popMainWindow")) {
|
||
Qt.callLater(()=>{
|
||
qmlapp.mainWin.loadGeometry(false)
|
||
qmlapp.mainWin.setVisibility(true)
|
||
})
|
||
}
|
||
}
|
||
|
||
// ========================= 【事件管理】 =========================
|
||
|
||
Component.onCompleted: {
|
||
eventSub() // 订阅事件
|
||
}
|
||
// 订阅事件
|
||
function eventSub() {
|
||
qmlapp.pubSub.subscribeGroup("<<reScreenshot>>", this, "reScreenshot", ctrlKey)
|
||
qmlapp.pubSub.subscribeGroup("<<screenshot>>", this, "screenshot", ctrlKey)
|
||
qmlapp.pubSub.subscribeGroup("<<paste>>", this, "paste", ctrlKey)
|
||
qmlapp.systemTray.addMenuItem("<<screenshot>>", qsTr("屏幕截图"), screenshot)
|
||
qmlapp.systemTray.addMenuItem("<<paste>>", qsTr("粘贴图片"), paste)
|
||
}
|
||
// 取消订阅事件
|
||
function eventUnsub() {
|
||
qmlapp.pubSub.unsubscribeGroup(ctrlKey)
|
||
qmlapp.systemTray.delMenuItem("<<screenshot>>")
|
||
qmlapp.systemTray.delMenuItem("<<paste>>")
|
||
}
|
||
|
||
// ========================= 【python调用qml】 =========================
|
||
|
||
// 设置任务状态
|
||
function setMsnState(flag) {
|
||
msnState = flag
|
||
}
|
||
|
||
// 获取一个OCR的返回值
|
||
function onOcrGet(res, imgID="", imgPath="") {
|
||
// 添加到结果
|
||
const resText = resultsTableView.addOcrResult(res)
|
||
if(imgID) // 图片类型
|
||
imageText.showImgID(imgID)
|
||
else if(imgPath) // 地址类型
|
||
imageText.showPath(imgPath)
|
||
imageText.showTextBoxes(res)
|
||
// 若tabPanel面板的下标没有变化过,则切换到记录页
|
||
if(tabPanel.indexChangeNum < 2)
|
||
tabPanel.currentIndex = 1
|
||
// 复制到剪贴板
|
||
const copy = configsComp.getValue("action.copy")
|
||
if(copy && resText!="")
|
||
qmlapp.utilsConnector.copyText(resText)
|
||
// 弹出通知
|
||
showSimple(res, resText, copy)
|
||
// 升起主窗口
|
||
popMainWindow()
|
||
}
|
||
|
||
// 一组OCR任务完毕
|
||
function onOcrEnd(msg) {
|
||
if(msg.startsWith("[Error]")) {
|
||
qmlapp.popup.message(qsTr("截图识别任务异常"), msg, "error")
|
||
}
|
||
}
|
||
|
||
// ========================= 【后处理】 =========================
|
||
|
||
// 任务完成后发送通知
|
||
function showSimple(res, resText, isCopy) {
|
||
// 获取弹窗类型
|
||
let simpleType = configsComp.getValue("other.simpleNotificationType")
|
||
if(simpleType==="default") {
|
||
simpleType = qmlapp.globalConfigs.getValue("window.simpleNotificationType")
|
||
}
|
||
const code = res.code
|
||
const time = res.time.toFixed(2)
|
||
let title = ""
|
||
resText = resText.replace(/\n/g, " ") // 换行符替换空格
|
||
if(code === 100 || code === 101) { // 成功时,不发送内部弹窗
|
||
if(simpleType==="inside" || simpleType==="onlyInside")
|
||
if(qmlapp.mainWin.getVisibility())
|
||
return
|
||
}
|
||
if(code === 100) {
|
||
if(isCopy) title = qsTr("已复制到剪贴板")
|
||
else title = qsTr("识图完成")
|
||
}
|
||
else if(code === 101) {
|
||
title = qsTr("无文字")
|
||
resText = ""
|
||
}
|
||
else {
|
||
title = qsTr("识别失败")
|
||
}
|
||
title += ` - ${time}s`
|
||
qmlapp.popup.simple(title, resText, simpleType)
|
||
}
|
||
|
||
// ========================= 【布局】 =========================
|
||
|
||
// 左侧栏。主区域为左右双栏且左栏隐藏时,才显示左侧栏。
|
||
Item {
|
||
id: leftCtrlPanel
|
||
anchors.left: parent.left
|
||
anchors.top: parent.top
|
||
anchors.bottom: parent.bottom
|
||
// 展示条件
|
||
visible: doubleLayout.isRow && doubleLayout.hideAB === 1
|
||
anchors.leftMargin: visible ? size_.smallSpacing : 0
|
||
width: visible ? size_.line * 1.5 : 0
|
||
|
||
Column {
|
||
anchors.top: parent.top
|
||
anchors.left: parent.left
|
||
anchors.right: parent.right
|
||
anchors.rightMargin: -size_.smallSpacing // 负间距,增加空间利用
|
||
spacing: size_.spacing
|
||
|
||
Item {
|
||
anchors.left: parent.left
|
||
anchors.right: parent.right
|
||
height: width
|
||
}
|
||
|
||
IconButton {
|
||
anchors.left: parent.left
|
||
anchors.right: parent.right
|
||
height: width
|
||
icon_: "screenshot"
|
||
color: theme.textColor
|
||
toolTip: qsTr("屏幕截图")
|
||
onClicked: tabPage.screenshot()
|
||
}
|
||
IconButton {
|
||
anchors.left: parent.left
|
||
anchors.right: parent.right
|
||
height: width
|
||
icon_: "paste"
|
||
color: theme.textColor
|
||
toolTip: qsTr("粘贴图片")
|
||
onClicked: tabPage.paste()
|
||
}
|
||
IconButton {
|
||
visible: msnState==="run"
|
||
anchors.left: parent.left
|
||
anchors.right: parent.right
|
||
height: width
|
||
icon_: "stop"
|
||
color: theme.noColor
|
||
toolTip: qsTr("停止任务")
|
||
onClicked: tabPage.msnStop()
|
||
}
|
||
}
|
||
}
|
||
|
||
// 主区域:可切换双栏面板
|
||
DoubleSwitchableLayout {
|
||
id: doubleLayout
|
||
saveKey: "ScreenshotOCR_1"
|
||
anchors.left: leftCtrlPanel.right
|
||
anchors.top: parent.top
|
||
anchors.bottom: parent.bottom
|
||
anchors.right: parent.right
|
||
|
||
// 面板A:图像展示
|
||
itemA: Panel {
|
||
anchors.fill: parent
|
||
clip: true
|
||
// 顶部控制栏
|
||
Item {
|
||
id: dLeftTop
|
||
anchors.top: parent.top
|
||
anchors.left: parent.left
|
||
anchors.right: parent.right
|
||
anchors.margins: size_.spacing
|
||
anchors.bottomMargin: 0
|
||
height: size_.line * 1.5
|
||
clip: true
|
||
|
||
// 靠右
|
||
Row {
|
||
id: dLeftTopR
|
||
anchors.top: parent.top
|
||
anchors.right: parent.right
|
||
anchors.bottom: parent.bottom
|
||
spacing: size_.smallSpacing
|
||
|
||
// 显示文字
|
||
CheckButton {
|
||
anchors.top: parent.top
|
||
anchors.bottom: parent.bottom
|
||
text_: qsTr("文字")
|
||
toolTip: qsTr("在图片上叠加显示识别文字\n可在全局设置中设为默认关闭")
|
||
checked: imageText.showOverlay
|
||
enabledAnime: true
|
||
onCheckedChanged: imageText.showOverlay = checked
|
||
}
|
||
IconButtonBar {
|
||
anchors.top: parent.top
|
||
anchors.bottom: parent.bottom
|
||
btnList: [
|
||
{
|
||
icon: "menu",
|
||
onClicked: imageText.popupMenu,
|
||
toolTip: tr("右键菜单"),
|
||
},
|
||
{
|
||
icon: "save",
|
||
onClicked: imageText.saveImage,
|
||
toolTip: tr("保存图片"),
|
||
},
|
||
{
|
||
icon: "full_screen",
|
||
onClicked: imageText.imageFullFit,
|
||
toolTip: tr("图片大小:适应窗口"),
|
||
},
|
||
{
|
||
icon: "one_to_one",
|
||
onClicked: imageText.imageScaleAddSub,
|
||
toolTip: tr("图片大小:实际"),
|
||
},
|
||
]
|
||
}
|
||
// 百分比显示
|
||
Text_ {
|
||
anchors.top: parent.top
|
||
anchors.bottom: parent.bottom
|
||
verticalAlignment: Text.AlignVCenter
|
||
horizontalAlignment: Text.AlignRight
|
||
text: (imageText.scale*100).toFixed(0) + "%"
|
||
color: theme.subTextColor
|
||
width: size_.line * 2.5
|
||
}
|
||
}
|
||
|
||
// 靠左
|
||
Rectangle { // 背景
|
||
anchors.left: parent.left
|
||
anchors.top: parent.top
|
||
anchors.bottom: parent.bottom
|
||
width: dLeftTopL.width
|
||
color: theme.bgColor
|
||
Rectangle {
|
||
anchors.fill: parent
|
||
color: theme.coverColor1
|
||
}
|
||
}
|
||
Row { // 按钮栏
|
||
id: dLeftTopL
|
||
anchors.left: parent.left
|
||
anchors.top: parent.top
|
||
anchors.bottom: parent.bottom
|
||
spacing: size_.smallSpacing
|
||
|
||
IconButtonBar {
|
||
anchors.top: parent.top
|
||
anchors.bottom: parent.bottom
|
||
btnList: [
|
||
{
|
||
icon: "screenshot",
|
||
onClicked: tabPage.screenshot,
|
||
color: theme.textColor,
|
||
bgColor: theme.bgColor,
|
||
text: tr("截图"),
|
||
toolTip: tr("屏幕截图"),
|
||
},
|
||
{
|
||
icon: "paste",
|
||
onClicked: tabPage.paste,
|
||
color: theme.textColor,
|
||
bgColor: theme.bgColor,
|
||
text: tr("粘贴"),
|
||
toolTip: tr("粘贴图片"),
|
||
},
|
||
]
|
||
}
|
||
// 停止任务
|
||
Button_ {
|
||
visible: msnState==="run"
|
||
anchors.top: parent.top
|
||
anchors.bottom: parent.bottom
|
||
text_: qsTr("停止任务")
|
||
textColor_: theme.noColor
|
||
onClicked: tabPage.msnStop()
|
||
}
|
||
}
|
||
}
|
||
// 图片预览区域
|
||
ImageWithText {
|
||
id: imageText
|
||
anchors.top: dLeftTop.bottom
|
||
anchors.bottom: parent.bottom
|
||
anchors.left: parent.left
|
||
anchors.right: parent.right
|
||
anchors.margins: size_.spacing
|
||
anchors.topMargin: size_.smallSpacing
|
||
|
||
// 加载中 动态图标
|
||
Loading {
|
||
visible: msnState==="run"
|
||
anchors.centerIn: parent
|
||
}
|
||
|
||
// 提示
|
||
DefaultTips {
|
||
visibleFlag: msnState
|
||
anchors.fill: parent
|
||
tips: qsTr("截图、拖入或粘贴图片")
|
||
}
|
||
}
|
||
}
|
||
|
||
// 面板B:结果
|
||
itemB: Panel {
|
||
anchors.fill: parent
|
||
|
||
TabPanel {
|
||
id: tabPanel
|
||
anchors.fill: parent
|
||
anchors.margins: size_.spacing
|
||
isMenuTop: doubleLayout.isRow // 左右布局时,菜单在顶部;上下布局时菜单在底部
|
||
menuHeight: size_.line * 1.5
|
||
|
||
// 结果面板
|
||
ResultsTableView {
|
||
id: resultsTableView
|
||
anchors.fill: parent
|
||
visible: false
|
||
}
|
||
|
||
tabsModel: [
|
||
{
|
||
"key": "configs",
|
||
"title": qsTr("设置"),
|
||
"component": configsComp.panelComponent,
|
||
},
|
||
{
|
||
"key": "ocrResult",
|
||
"title": qsTr("记录"),
|
||
"component": resultsTableView,
|
||
},
|
||
]
|
||
}
|
||
}
|
||
}
|
||
|
||
// 鼠标拖入图片
|
||
DropArea_ {
|
||
anchors.fill: parent
|
||
callback: tabPage.ocrPaths
|
||
}
|
||
} |