docs: 添加涉密文件自检工具实施计划
This commit is contained in:
137
UmiOCR-data/qt_res/qml/TabPages/About/About.qml
Normal file
137
UmiOCR-data/qt_res/qml/TabPages/About/About.qml
Normal file
@@ -0,0 +1,137 @@
|
||||
// ===================================================
|
||||
// =============== 功能页:关于/检查更新 ===============
|
||||
// ===================================================
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
|
||||
import ".."
|
||||
import "../../Widgets"
|
||||
|
||||
TabPage {
|
||||
id: tabPage
|
||||
|
||||
Panel{
|
||||
anchors.fill: parent
|
||||
anchors.margins: size_.line
|
||||
|
||||
ScrollView {
|
||||
anchors.fill: parent
|
||||
anchors.margins: size_.spacing
|
||||
anchors.leftMargin: size_.line * 2
|
||||
anchors.rightMargin: size_.line * 2
|
||||
contentWidth: width
|
||||
clip: true
|
||||
|
||||
Column {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: size_.spacing * 2
|
||||
spacing: size_.spacing
|
||||
clip: true
|
||||
|
||||
// ==================== 标题 ====================
|
||||
|
||||
Image {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
height: size_.line * 10
|
||||
fillMode: Image.PreserveAspectFit
|
||||
source: "../../../images/Umi-OCR_logo_full.png"
|
||||
}
|
||||
Text_ {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: qsTr("开源、免费的离线OCR软件")
|
||||
}
|
||||
SplitLine {}
|
||||
|
||||
// ==================== 软件 / 项目信息 ====================
|
||||
|
||||
Text_ {text: qsTr("当前版本") + " • " + UmiAbout.version.string}
|
||||
UrlList {
|
||||
title: qsTr("项目链接")
|
||||
urlList: [
|
||||
{text:qsTr("官方网站"), url:UmiAbout.url.home},
|
||||
{text:qsTr("插件拓展"), url:UmiAbout.url.plugins},
|
||||
{text:qsTr("问题反馈"), url:UmiAbout.url.issue},
|
||||
]
|
||||
}
|
||||
UrlList {
|
||||
title: qsTr("发布地址")
|
||||
urlList: [
|
||||
{text:"Github", url:"https://github.com/hiroi-sora/Umi-OCR/releases/latest"},
|
||||
{text:"Source Forge", url:"https://sourceforge.net/projects/umi-ocr"},
|
||||
{text:"Lanzou (蓝奏云)", url:"https://hiroi-sora.lanzoul.com/s/umi-ocr"},
|
||||
]
|
||||
}
|
||||
UrlList {
|
||||
title: qsTr("许可协议")
|
||||
urlList: [
|
||||
{text:UmiAbout.license.type, url:UmiAbout.license.url},
|
||||
]
|
||||
}
|
||||
SplitLine {}
|
||||
|
||||
// ==================== 开发者 ====================
|
||||
|
||||
UrlList {
|
||||
title: qsTr("作者")
|
||||
urlList: (() => {
|
||||
let as = UmiAbout.authors, t = []
|
||||
for(const i in as)
|
||||
t.push({ text: as[i].name, url: as[i].url, })
|
||||
return t
|
||||
})()
|
||||
}
|
||||
Text_ {text: qsTr("译者")}
|
||||
Repeater {
|
||||
model: UmiAbout.localization
|
||||
|
||||
UrlList {
|
||||
anchors.leftMargin: size_.line * 2
|
||||
textSize: size_.smallText
|
||||
title: UmiAbout.localization[index].language
|
||||
urlList: (() => {
|
||||
let as = UmiAbout.localization[index].translators, t = []
|
||||
for(const i in as)
|
||||
t.push({ text: as[i].name, url: as[i].url, })
|
||||
return t
|
||||
})()
|
||||
}
|
||||
}
|
||||
SplitLine {}
|
||||
|
||||
// ==================== 系统 / Debug信息 ====================
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
Text_ {
|
||||
font.pixelSize: size_.smallText
|
||||
height: size_.smallLine + size_.spacing*2
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
text: qsTr("运行环境信息(如需请求协助,请提供给开发者)")
|
||||
}
|
||||
Button_ {
|
||||
height: size_.smallLine + size_.spacing*2
|
||||
textSize: size_.smallText
|
||||
text_: qsTr("复制")
|
||||
textColor_: theme.yesColor
|
||||
onClicked: {
|
||||
const info = JSON.stringify(UmiAbout.app, null, 2)
|
||||
qmlapp.utilsConnector.copyText(info)
|
||||
}
|
||||
}
|
||||
}
|
||||
Text_ {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: size_.line * 2
|
||||
wrapMode: Text.WordWrap
|
||||
font.pixelSize: size_.smallText
|
||||
text: JSON.stringify(UmiAbout.app, null, 2)
|
||||
}
|
||||
|
||||
Item {width: 1; height: size_.line}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
17
UmiOCR-data/qt_res/qml/TabPages/About/SplitLine.qml
Normal file
17
UmiOCR-data/qt_res/qml/TabPages/About/SplitLine.qml
Normal file
@@ -0,0 +1,17 @@
|
||||
// 分割线
|
||||
|
||||
import QtQuick 2.15
|
||||
|
||||
Item {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
height: size_.line * 2
|
||||
|
||||
Rectangle {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
height: 2
|
||||
color: theme.coverColor2
|
||||
}
|
||||
}
|
||||
35
UmiOCR-data/qt_res/qml/TabPages/About/UrlButton.qml
Normal file
35
UmiOCR-data/qt_res/qml/TabPages/About/UrlButton.qml
Normal file
@@ -0,0 +1,35 @@
|
||||
// 网页链接按钮
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
|
||||
import "../../Widgets"
|
||||
|
||||
Button_ {
|
||||
id: btn
|
||||
property string url: ""
|
||||
// toolTip: url
|
||||
height: size_.text + size_.spacing * 2
|
||||
bgHoverColor_: theme.coverColor1
|
||||
|
||||
contentItem: Text_ {
|
||||
text: btn.text_
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
color: theme.specialTextColor
|
||||
|
||||
// 手动绘制下划线,减少抖动现象。不使用 font.underline
|
||||
Rectangle {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: -2
|
||||
height: 1
|
||||
color: theme.specialTextColor
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
Qt.openUrlExternally(url)
|
||||
}
|
||||
}
|
||||
45
UmiOCR-data/qt_res/qml/TabPages/About/UrlList.qml
Normal file
45
UmiOCR-data/qt_res/qml/TabPages/About/UrlList.qml
Normal file
@@ -0,0 +1,45 @@
|
||||
// 链接水平列表
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
|
||||
import ".."
|
||||
import "../../Widgets"
|
||||
|
||||
Item {
|
||||
id: uRoot
|
||||
property string title: ""
|
||||
property var urlList: [] // {"text", "url"}
|
||||
property int textSize: size_.text
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
height: flow.height
|
||||
|
||||
Text_ {
|
||||
id: lText
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
height: textSize + size_.spacing * 2
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
text: title + " • "
|
||||
font.pixelSize: textSize
|
||||
}
|
||||
|
||||
Flow {
|
||||
id: flow
|
||||
anchors.left: lText.right
|
||||
anchors.right: parent.right
|
||||
spacing: 0
|
||||
Repeater {
|
||||
model: urlList
|
||||
|
||||
UrlButton {
|
||||
height: textSize + size_.spacing * 2
|
||||
text_: urlList[index].text
|
||||
url: urlList[index].url
|
||||
textSize: uRoot.textSize
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
312
UmiOCR-data/qt_res/qml/TabPages/BatchDOC/BatchDOC.qml
Normal file
312
UmiOCR-data/qt_res/qml/TabPages/BatchDOC/BatchDOC.qml
Normal file
@@ -0,0 +1,312 @@
|
||||
// ==================================================
|
||||
// =============== 功能页:批量文档处理 ===============
|
||||
// ==================================================
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
|
||||
import ".."
|
||||
import "../../Widgets"
|
||||
import "../../Widgets/ResultLayout"
|
||||
import "../../Widgets/IgnoreArea"
|
||||
|
||||
/* 文档参数:
|
||||
path 路径
|
||||
pages 页数显示
|
||||
state 状态显示
|
||||
page_count 总页数
|
||||
range_start 范围开始
|
||||
range_end 范围结束
|
||||
is_encrypted 需要密码
|
||||
is_authenticate 密码正确
|
||||
password 密码
|
||||
*/
|
||||
|
||||
TabPage {
|
||||
id: tabPage
|
||||
|
||||
// ========================= 【逻辑】 =========================
|
||||
|
||||
property string msnID: "" // 当前任务ID
|
||||
|
||||
// 异步加载一批文档路径
|
||||
function addDocs(paths) {
|
||||
if(paths.length <= 0) return
|
||||
const isRecurrence = configsComp.getValue("mission.recurrence")
|
||||
qmlapp.asynFilesLoader.run(paths,"doc",isRecurrence,onAddDocs)
|
||||
}
|
||||
// 完毕后,将合法表格加入表格
|
||||
function onAddDocs(docs) {
|
||||
if(docs.length <= 0) return
|
||||
let encryptedCount = 0
|
||||
for(let i in docs) {
|
||||
const info = docs[i]
|
||||
filesTableView.add({
|
||||
// 显示:路径,状态,页范围
|
||||
path: info.path,
|
||||
pages: `${info.page_count}`, // 如果范围为整本,只显示总页数。否则显示 起始-结束
|
||||
state: info.is_encrypted ? qsTr("加密") : "" ,
|
||||
// 数据
|
||||
page_count: info.page_count,
|
||||
range_start: 1,
|
||||
range_end: info.page_count,
|
||||
is_encrypted: info.is_encrypted, // 有密码
|
||||
is_authenticate: !info.is_encrypted, // 已解密(密码正确)
|
||||
password: "",
|
||||
})
|
||||
if(info.is_encrypted) encryptedCount++
|
||||
}
|
||||
if(encryptedCount > 0) {
|
||||
qmlapp.popup.simple(qsTr("%1个加密文档").arg(encryptedCount),
|
||||
qsTr("请点击文件名填写密码"))
|
||||
}
|
||||
}
|
||||
|
||||
// 运行文档任务
|
||||
function docStart() {
|
||||
const fileCount = filesTableView.rowCount
|
||||
if(fileCount <= 0) {
|
||||
ctrlPanel.stopFinished()
|
||||
return
|
||||
}
|
||||
// 获取信息
|
||||
const docs = filesTableView.getColumnsValues([
|
||||
"path","range_start", "range_end", "page_count", "is_encrypted", "is_authenticate", "password"])
|
||||
// 第1次遍历:检查密码填写
|
||||
for(let i = 0; i < fileCount; i++) {
|
||||
const d = docs[i]
|
||||
if(d.is_encrypted && !d.is_authenticate) {
|
||||
qmlapp.popup.message(qsTr("文档已加密"), qsTr("【%1】\n请点击文档名,设置密码").arg(d.path), "warning")
|
||||
ctrlPanel.stopFinished()
|
||||
return
|
||||
}
|
||||
}
|
||||
// 第2次遍历:刷新信息
|
||||
let allPages = 0 // 页总数
|
||||
for(let i = 0; i < fileCount; i++) {
|
||||
const d = docs[i]
|
||||
allPages += d.range_end - d.range_start + 1
|
||||
filesTableView.setProperty(d.path, "state", qsTr("排队"))
|
||||
}
|
||||
// 若tabPanel面板的下标没有变化过,则切换到记录页
|
||||
if(tabPanel.indexChangeNum < 2)
|
||||
tabPanel.currentIndex = 1
|
||||
// 任务进度 开始计时
|
||||
ctrlPanel.runFinished(allPages)
|
||||
// 提交任务
|
||||
const argd = configsComp.getValueDict()
|
||||
tabPage.callPy("msnDocs", docs, argd)
|
||||
}
|
||||
|
||||
// 停止文档任务
|
||||
function docStop() {
|
||||
tabPage.callPy("msnStop")
|
||||
// 刷新表格,清空未执行的任务的状态
|
||||
let msnLength = filesTableView.rowCount
|
||||
for(let i = 0; i < msnLength; i++) {
|
||||
const row = filesTableView.get(i)
|
||||
const s = row.state
|
||||
if(s.length > 0 && s[0] !== "√" && s[0] !== "×") {
|
||||
filesTableView.setProperty(i, "state", "")
|
||||
}
|
||||
}
|
||||
ctrlPanel.stopFinished()
|
||||
}
|
||||
|
||||
// 文件表格中单击文档
|
||||
function onClickDoc(index) {
|
||||
if(ctrlPanel.state_ !== "stop") return
|
||||
const info = filesTableView.get(index)
|
||||
if(Object.keys(info).length > 0)
|
||||
previewDoc.show(info)
|
||||
}
|
||||
|
||||
// 关闭页面
|
||||
function closePage() {
|
||||
if(ctrlPanel.state_ !== "stop") {
|
||||
const argd = { yesText: qsTr("依然关闭") }
|
||||
const callback = (flag)=>{
|
||||
if(flag) {
|
||||
docStop()
|
||||
delPage()
|
||||
}
|
||||
}
|
||||
qmlapp.popup.dialog("", qsTr("任务正在进行中。\n要结束任务并关闭页面吗?"), callback, "warning", argd)
|
||||
}
|
||||
else {
|
||||
delPage()
|
||||
}
|
||||
}
|
||||
|
||||
// ========================= 【python调用qml】 =========================
|
||||
|
||||
// 准备开始处理一个文档
|
||||
function onDocStart(path) {
|
||||
// 刷新表格显示
|
||||
const d = filesTableView.get(path)
|
||||
let state = `0/${d.range_end - d.range_start + 1}`
|
||||
filesTableView.setProperty(path, "state", state)
|
||||
}
|
||||
|
||||
// 获取一个文档的一页的结果
|
||||
function onDocGet(path, page, res) {
|
||||
// 刷新单个文档的信息
|
||||
const d = filesTableView.get(path)
|
||||
const state = `${page - d.range_start + 1}/${d.range_end - d.range_start + 1}`
|
||||
filesTableView.setProperty(path, "state", state)
|
||||
// 提取文字,添加到结果表格
|
||||
const title = path2name(path)
|
||||
res.title = `${title} - ${page}`
|
||||
resultsTableView.addOcrResult(res)
|
||||
ctrlPanel.msnStep(1)
|
||||
}
|
||||
|
||||
// 一个文档处理完毕。 isAll==true 时所有文档处理完毕。
|
||||
function onDocEnd(path, msg, isAll) {
|
||||
const errTitle = qsTr("文档识别异常")
|
||||
// 成功结束
|
||||
if(msg.startsWith("[Success]")) {
|
||||
filesTableView.setProperty(path, "state", "√")
|
||||
msg = ""
|
||||
}
|
||||
// 单个文档任务失败,总体未结束
|
||||
else if(!isAll) {
|
||||
filesTableView.setProperty(path, "state", "× "+ qsTr("失败"))
|
||||
qmlapp.popup.simple(errTitle, msg)
|
||||
}
|
||||
// 所有文档处理完毕
|
||||
if(isAll) {
|
||||
const simpleType = configsComp.getValue("other.simpleNotificationType")
|
||||
qmlapp.popup.simple(qsTr("批量识别完成"), "", simpleType)
|
||||
if(msg) // 如果有异常,则弹窗
|
||||
qmlapp.popup.message(errTitle, msg, "error")
|
||||
ctrlPanel.stopFinished()
|
||||
// 任务完成后续操作
|
||||
qmlapp.globalConfigs.utilsDicts.postTaskHardwareCtrl(
|
||||
configsComp.getValue("postTaskActions.system")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 路径转文件名
|
||||
function path2name(path) {
|
||||
const parts = path.split("/")
|
||||
return parts[parts.length - 1]
|
||||
}
|
||||
|
||||
// ========================= 【布局】 =========================
|
||||
|
||||
// 配置
|
||||
configsComp: BatchDOCConfigs {}
|
||||
|
||||
// 主区域:可切换双栏面板
|
||||
DoubleSwitchableLayout {
|
||||
id: doubleLayout
|
||||
saveKey: "BatchDOC_1"
|
||||
anchors.fill: parent
|
||||
|
||||
// 面板A:控制板+文件表格
|
||||
itemA: Panel {
|
||||
anchors.fill: parent
|
||||
|
||||
// 上方控制板
|
||||
MissionCtrlPanel {
|
||||
id: ctrlPanel
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: size_.spacing
|
||||
height: size_.line * 2
|
||||
|
||||
onRunClicked: tabPage.docStart()
|
||||
onPauseClicked: {
|
||||
tabPage.callPy("msnPause")
|
||||
pauseFinished()
|
||||
}
|
||||
onResumeClicked: {
|
||||
tabPage.callPy("msnResume")
|
||||
resumeFinished()
|
||||
}
|
||||
onStopClicked: tabPage.docStop()
|
||||
}
|
||||
|
||||
// 下方文件表格
|
||||
FilesTableView {
|
||||
id: filesTableView
|
||||
anchors.top: ctrlPanel.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.margins: size_.spacing
|
||||
anchors.topMargin: size_.smallSpacing
|
||||
headers: [
|
||||
{key: "path", title: qsTr("文档"), left: true, display: path2name,
|
||||
btn: true, onClicked:onClickDoc},
|
||||
{key: "state", title: qsTr("状态"), btn: true, onClicked:onClickDoc},
|
||||
{key: "pages", title: qsTr("页数"), btn: true, onClicked:onClickDoc},
|
||||
]
|
||||
openBtnText: qsTr("打开文档")
|
||||
clearBtnText: qsTr("清空")
|
||||
defaultTips: qsTr("拖入文档或文件夹")
|
||||
fileDialogTitle: qsTr("请选择文档")
|
||||
fileDialogNameFilters: [qsTr("文档")+" (*.pdf *.xps *.epub *.mobi *.fb2 *.cbz)"]
|
||||
isLock: ctrlPanel.state_ !== "stop"
|
||||
onAddPaths: {
|
||||
tabPage.addDocs(paths)
|
||||
}
|
||||
}
|
||||
}
|
||||
// 面板B:文字输出 & 设置
|
||||
itemB: Panel {
|
||||
anchors.fill: parent
|
||||
|
||||
// 配置项控制板
|
||||
TabPanel {
|
||||
id: tabPanel
|
||||
anchors.fill: parent
|
||||
anchors.margins: size_.spacing
|
||||
isMenuTop: doubleLayout.isRow // 左右布局时,菜单在顶部;上下布局时菜单在底部
|
||||
menuHeight: size_.line * 2
|
||||
|
||||
// 结果面板
|
||||
ResultsTableView {
|
||||
id: resultsTableView
|
||||
anchors.fill: parent
|
||||
visible: false
|
||||
}
|
||||
|
||||
tabsModel: [
|
||||
{
|
||||
"key": "configs",
|
||||
"title": qsTr("设置"),
|
||||
"component": configsComp.panelComponent,
|
||||
},
|
||||
{
|
||||
"key": "ocrResult",
|
||||
"title": qsTr("记录"),
|
||||
"component": resultsTableView,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 鼠标拖入文档
|
||||
DropArea_ {
|
||||
id: "addDocsDropArea"
|
||||
anchors.fill: parent
|
||||
callback: tabPage.addDocs
|
||||
}
|
||||
|
||||
// 预览面板
|
||||
PreviewDoc {
|
||||
id: previewDoc
|
||||
anchors.fill: parent
|
||||
configsComp: tabPage.configsComp
|
||||
updateInfo: (path, info) => {
|
||||
let infoA = filesTableView.get(path)
|
||||
Object.assign(infoA, info)
|
||||
filesTableView.set(path, infoA)
|
||||
}
|
||||
}
|
||||
}
|
||||
149
UmiOCR-data/qt_res/qml/TabPages/BatchDOC/BatchDOCConfigs.qml
Normal file
149
UmiOCR-data/qt_res/qml/TabPages/BatchDOC/BatchDOCConfigs.qml
Normal file
@@ -0,0 +1,149 @@
|
||||
// ==============================================
|
||||
// =============== 批量PDF的配置项 ===============
|
||||
// ==============================================
|
||||
|
||||
import QtQuick 2.15
|
||||
import "../../Configs"
|
||||
|
||||
Configs {
|
||||
category_: "BatchPDF"
|
||||
signal clickIgnoreArea() // 打开忽略区域
|
||||
|
||||
configDict: {
|
||||
// OCR参数
|
||||
"ocr": qmlapp.globalConfigs.ocrManager.deploy(this, "ocr"),
|
||||
|
||||
// 后处理
|
||||
"tbpu": {
|
||||
"title": qsTr("OCR文本后处理"),
|
||||
"type": "group",
|
||||
|
||||
"parser": qmlapp.globalConfigs.utilsDicts.getTbpuParser(),
|
||||
"btns": {
|
||||
"title": "👈"+qsTr("点击表格,可设置更多内容"),
|
||||
"btnsList": [],
|
||||
},
|
||||
"ignoreArea": {
|
||||
"type": "var",
|
||||
"save": false,
|
||||
},
|
||||
"ignoreRangeStart": { // 忽略区域范围
|
||||
"default": 1,
|
||||
"save": false,
|
||||
},
|
||||
"ignoreRangeEnd": {
|
||||
"default": -1,
|
||||
"save": false,
|
||||
},
|
||||
},
|
||||
|
||||
// 文档参数
|
||||
"doc": {
|
||||
"title": qsTr("文档处理"),
|
||||
"type": "group",
|
||||
|
||||
"extractionMode": {
|
||||
"title": qsTr("内容提取模式"),
|
||||
"toolTip": qsTr("若一页文档既存在图片又存在文本,如何进行处理"),
|
||||
"optionsList": [
|
||||
["mixed", qsTr("混合OCR/原文本")],
|
||||
["fullPage", qsTr("整页强制OCR")],
|
||||
["imageOnly", qsTr("仅OCR图片")],
|
||||
["textOnly", qsTr("仅拷贝原有文本")],
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
// 任务参数
|
||||
"mission": {
|
||||
"title": qsTr("批量任务"),
|
||||
"type": "group",
|
||||
|
||||
"recurrence": {
|
||||
"title": qsTr("递归读取子文件夹"),
|
||||
"toolTip": qsTr("导入文件夹时,导入子文件夹中全部文档"),
|
||||
"default": false,
|
||||
},
|
||||
"dirType": {
|
||||
"title": qsTr("保存到"),
|
||||
"optionsList": [
|
||||
["source", qsTr("文档原目录")],
|
||||
["specify", qsTr("指定目录")],
|
||||
],
|
||||
},
|
||||
"dir": {
|
||||
"title": qsTr("指定目录"),
|
||||
"toolTip": qsTr("必须先指定“保存到指定目录”才生效"),
|
||||
"type": "file",
|
||||
"selectExisting": true, // 选择现有
|
||||
"selectFolder": true, // 选择文件夹
|
||||
"dialogTitle": qsTr("OCR结果保存目录"),
|
||||
},
|
||||
"fileNameFormat": {
|
||||
"title": qsTr("文件名格式"),
|
||||
"toolTip": qsTr("无需填写拓展名。支持插入以下占位符:\n%date 日期时间\n%name 原文档名\n%range 识别页数范围。只有识别页数小于总页数时才会显示。\n举例:[OCR]_%name%range_%date\n生成:[OCR]_文档A(p2-10)_20230901_1213.txt\n添加占位符可以避免旧文件被新文件覆盖。"),
|
||||
"default": "[OCR]_%name%range_%date",
|
||||
"advanced": true,
|
||||
},
|
||||
"datetimeFormat": {
|
||||
"title": qsTr("日期时间格式"),
|
||||
"toolTip": qsTr("文件名中 %date 的日期格式。支持插入以下占位符:\n%Y 年、 %m 月、 %d 日、 %H 小时、 \n%M 分钟、 %S 秒 、 %unix 时间戳 \n举例:%Y年%m月%d日_%H-%M\n生成:2023年09月01日_12-13.txt"),
|
||||
"default": "%Y%m%d_%H%M",
|
||||
"advanced": true,
|
||||
},
|
||||
|
||||
"filesType": {
|
||||
"title": qsTr("保存文件类型"),
|
||||
"type": "group",
|
||||
"enabledFold": true,
|
||||
"fold": false,
|
||||
|
||||
"pdfLayered": {
|
||||
"title": qsTr("layered.pdf 双层可搜索文档"),
|
||||
"toolTip": qsTr("保留原有图片,叠加一层透明文字,可以搜索和复制"),
|
||||
"default": true,
|
||||
},
|
||||
"pdfOneLayer": {
|
||||
"title": qsTr("text.pdf 单层纯文本文档"),
|
||||
"toolTip": qsTr("创建空白PDF文档,只写入识别文字,不含图片"),
|
||||
"default": false,
|
||||
},
|
||||
"txt": {
|
||||
"title": qsTr("txt 标准格式"),
|
||||
"toolTip": qsTr("含识别文字和页数信息"),
|
||||
"default": false,
|
||||
},
|
||||
"txtPlain": {
|
||||
"title": qsTr("p.txt 纯文字格式"),
|
||||
"toolTip": qsTr("输出所有识别文字"),
|
||||
"default": false,
|
||||
},
|
||||
"csv": {
|
||||
"title": qsTr("csv 表格文件(Excel)"),
|
||||
"toolTip": qsTr("将页数信息和识别内容写入csv表格文件。可用Excel打开,另存为xlsx格式。"),
|
||||
"default": false,
|
||||
},
|
||||
"jsonl": {
|
||||
"title": qsTr("jsonl 原始信息"),
|
||||
"toolTip": qsTr("每行为一条json数据,便于第三方程序读取操作"),
|
||||
"default": false,
|
||||
},
|
||||
},
|
||||
|
||||
"ignoreBlank": {
|
||||
"title": qsTr("忽略空白页"),
|
||||
"toolTip": qsTr("若某一页没有文字或识别失败,也不会输出错误提示信息"),
|
||||
"default": true,
|
||||
},
|
||||
},
|
||||
|
||||
// 任务完成后续操作
|
||||
"postTaskActions": qmlapp.globalConfigs.utilsDicts.getPostTaskActions(),
|
||||
|
||||
"other": {
|
||||
"title": qsTr("其它"),
|
||||
"type": "group",
|
||||
"simpleNotificationType": qmlapp.globalConfigs.utilsDicts.getSimpleNotificationType()
|
||||
},
|
||||
}
|
||||
}
|
||||
431
UmiOCR-data/qt_res/qml/TabPages/BatchDOC/PreviewDoc.qml
Normal file
431
UmiOCR-data/qt_res/qml/TabPages/BatchDOC/PreviewDoc.qml
Normal file
@@ -0,0 +1,431 @@
|
||||
// ===========================================
|
||||
// =============== 文档预览面板 ===============
|
||||
// ===========================================
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import DocPreviewConnector 1.0
|
||||
|
||||
import "../../Widgets"
|
||||
import "../../Widgets/IgnoreArea"
|
||||
|
||||
ModalLayer {
|
||||
id: pRoot
|
||||
property var updateInfo // 更新信息函数
|
||||
property var configsComp: undefined // 设置组件
|
||||
|
||||
property string previewPath: ""
|
||||
property string password: ""
|
||||
property bool isEncrypted: false // 已加密
|
||||
property bool isAuthenticate: false // 密码正确
|
||||
property int previewPage: -1
|
||||
property int pageCount: -1
|
||||
property int rangeStart: -1
|
||||
property int rangeEnd: -1
|
||||
property int ignoreRangeStart: 1
|
||||
property int ignoreRangeEnd: -1
|
||||
property bool previewOCR: false // 是否预览OCR
|
||||
property bool ocrRunning: false // 是否预览OCR正在执行
|
||||
|
||||
// 展示文档
|
||||
// info: path, page_count, range_start, range_end, is_encrypted, password, is_authenticate
|
||||
function show(info) {
|
||||
imgViewer.clear()
|
||||
visible = true
|
||||
previewPath = info.path
|
||||
pageCount = info.page_count
|
||||
previewPage = info.range_start
|
||||
rangeStart = info.range_start
|
||||
rangeEnd = info.range_end
|
||||
password = info.password
|
||||
isEncrypted = info.is_encrypted
|
||||
isAuthenticate = info.is_authenticate
|
||||
// 读取忽略区域设置
|
||||
let initArea = configsComp.getValue("tbpu.ignoreArea")
|
||||
if(initArea && initArea.length>0) {
|
||||
// 读取设置,反格式化
|
||||
let ig1 = []
|
||||
for(let i=0,l=initArea.length; i<l; i++) {
|
||||
const b = initArea[i]
|
||||
ig1.push({
|
||||
"x": b[0][0],
|
||||
"y": b[0][1],
|
||||
"width": b[2][0] - b[0][0],
|
||||
"height": b[2][1] - b[0][1],
|
||||
})
|
||||
}
|
||||
imgViewer.ig1Boxes = ig1
|
||||
}
|
||||
ignoreRangeStart = configsComp.getValue("tbpu.ignoreRangeStart")
|
||||
ignoreRangeEnd = configsComp.getValue("tbpu.ignoreRangeEnd")
|
||||
toPreview()
|
||||
}
|
||||
|
||||
// 返回上层,更新信息
|
||||
onVisibleChanged: {
|
||||
if(visible) return
|
||||
// 负数转倒数
|
||||
if(rangeStart < 0) rangeStart += pageCount + 1
|
||||
if(rangeEnd < 0) rangeEnd += pageCount + 1
|
||||
// 范围检查
|
||||
if(rangeStart < 1) rangeStart = 1
|
||||
if(rangeStart > pageCount) rangeStart = pageCount
|
||||
if(rangeEnd < rangeStart) rangeEnd = rangeStart
|
||||
if(rangeEnd > pageCount) rangeEnd = pageCount
|
||||
let pages_ = `${pageCount}` // 如果范围为整本,只显示总页数。否则显示 起始-结束
|
||||
if(rangeEnd - rangeStart + 1 < pageCount)
|
||||
pages_ = `${rangeStart}-${rangeEnd}`
|
||||
if(updateInfo) {
|
||||
updateInfo(previewPath, {
|
||||
pages: pages_,
|
||||
state: isAuthenticate ? "" : qsTr("加密"),
|
||||
range_start: rangeStart,
|
||||
range_end: rangeEnd,
|
||||
password: password,
|
||||
is_authenticate: isAuthenticate,
|
||||
})
|
||||
}
|
||||
// 更新忽略区域
|
||||
if(imgViewer.ig1Boxes.length > 0) {
|
||||
// 格式化,存入设置
|
||||
let ig1 = []
|
||||
for(let i=0,l=imgViewer.ig1Boxes.length; i<l; i++) {
|
||||
const b = imgViewer.ig1Boxes[i]
|
||||
const x = Math.round(b.x)
|
||||
const y = Math.round(b.y)
|
||||
const w = Math.round(b.width)
|
||||
const h = Math.round(b.height)
|
||||
ig1.push([[x, y], [x+w, y], [x+w, y+h], [x, y+h]])
|
||||
}
|
||||
configsComp.setValue("tbpu.ignoreArea", ig1)
|
||||
}
|
||||
else {
|
||||
configsComp.setValue("tbpu.ignoreArea", undefined)
|
||||
}
|
||||
configsComp.setValue("tbpu.ignoreRangeStart", ignoreRangeStart)
|
||||
configsComp.setValue("tbpu.ignoreRangeEnd", ignoreRangeEnd)
|
||||
imgViewer.clear()
|
||||
prevConn.clear() // 清除文档缓存
|
||||
previewPath = ""
|
||||
}
|
||||
|
||||
// 翻页。to直接翻页,flag加减页。
|
||||
function changePage(to, flag=0) {
|
||||
if (typeof to === "string") {
|
||||
to = parseInt(to, 10)
|
||||
}
|
||||
if(flag != 0) {
|
||||
to = previewPage + flag
|
||||
}
|
||||
if(previewPage != to && to > 0 && to <= pageCount) {
|
||||
previewPage = to
|
||||
toPreview()
|
||||
}
|
||||
}
|
||||
Keys.onLeftPressed: changePage(-1, -1) // 上一页
|
||||
Keys.onUpPressed: changePage(-1, -1)
|
||||
Keys.onRightPressed: changePage(-1, 1) // 下一页
|
||||
Keys.onDownPressed: changePage(-1, 1)
|
||||
|
||||
// 预览一页文档
|
||||
function toPreview() {
|
||||
if(!previewPath) return
|
||||
if(previewPage < 1) previewPage = 1
|
||||
if(previewPage > pageCount) previewPage = pageCount
|
||||
prevConn.preview(previewPath, previewPage, password)
|
||||
if(previewOCR) { // 预览OCR
|
||||
ocrRunning = true
|
||||
const argd = configsComp.getValueDict()
|
||||
argd["tbpu.parser"] = "None" // 去除排版解析
|
||||
prevConn.ocr(previewPath, previewPage, password, argd)
|
||||
}
|
||||
}
|
||||
// 预览连接器
|
||||
DocPreviewConnector {
|
||||
id: prevConn
|
||||
// 图片渲染的回调
|
||||
onPreviewImg: function(imgID) {
|
||||
const title = qsTr("打开文档失败")
|
||||
if(imgID === "[Warning] is_encrypted") {
|
||||
qmlapp.popup.simple(title, qsTr("请填写正确的密码"))
|
||||
isAuthenticate = false
|
||||
}
|
||||
else if(imgID.startsWith("[Error]")) {
|
||||
qmlapp.popup.message(title, imgID, "error")
|
||||
}
|
||||
else {
|
||||
imgViewer.showImgID(imgID)
|
||||
if(!isAuthenticate) {
|
||||
qmlapp.popup.simple(qsTr("密码正确"), password)
|
||||
isAuthenticate = true
|
||||
}
|
||||
}
|
||||
}
|
||||
// ocr预览的回调
|
||||
onPreviewOcr: function(info) {
|
||||
let path = info[0], page = info[1], res = info[2]
|
||||
if(res.code!=100&&res.code!=101) { // 遇到异常
|
||||
qmlapp.popup.message(qsTr("文档预览异常"), res.data, "error")
|
||||
return
|
||||
}
|
||||
if(path != previewPath || page != previewPage) {
|
||||
console.warn("文档OCR预览回调不匹配")
|
||||
return
|
||||
}
|
||||
ocrRunning = false
|
||||
imgViewer.showTextBoxes(res)
|
||||
}
|
||||
}
|
||||
|
||||
property string ignoreTips: qsTr("忽略区域说明:\n右键拖拽,绘制矩形区域,包含在区域内的文字框将被忽略。可用于排除水印、页眉页脚。\n范围允许填写负数,表示倒数第x页。如-1表示最后一页,-2表示倒数第2页。\n忽略区域的设置对所有文档生效。")
|
||||
|
||||
contentItem: DoubleRowLayout {
|
||||
anchors.fill: parent
|
||||
initSplitterX: size_.line * 13
|
||||
// 左:控制面板
|
||||
leftItem: Panel {
|
||||
anchors.fill: parent
|
||||
|
||||
ScrollView {
|
||||
anchors.fill: parent
|
||||
contentWidth: width // 内容宽度
|
||||
clip: true // 溢出隐藏
|
||||
|
||||
Column {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: size_.spacing
|
||||
spacing: size_.smallSpacing
|
||||
// ===== 文件名 =====
|
||||
Text_ {
|
||||
text: previewPath
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
wrapMode: TextEdit.WrapAnywhere // 任意换行
|
||||
maximumLineCount: 4 // 限制行数
|
||||
color: theme.subTextColor
|
||||
font.pixelSize: size_.smallText
|
||||
}
|
||||
// ===== 密码 =====
|
||||
Row {
|
||||
visible: isEncrypted && !isAuthenticate // 已加密,未填密码,才显示
|
||||
spacing: size_.spacing
|
||||
height: size_.line + size_.spacing * 2
|
||||
Text_ {
|
||||
color: theme.noColor
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: qsTr("密码:")
|
||||
}
|
||||
TextField_ {
|
||||
width: size_.line * 6
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
text: password
|
||||
onTextChanged: password = text
|
||||
}
|
||||
IconButton {
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
width: height
|
||||
icon_: "yes"
|
||||
onClicked: toPreview()
|
||||
}
|
||||
}
|
||||
// ===== 控制项 =====
|
||||
Column {
|
||||
visible: !isEncrypted || isAuthenticate
|
||||
spacing: size_.smallSpacing
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
// ===== 页数 =====
|
||||
Rectangle {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
height: 1
|
||||
color: theme.coverColor4
|
||||
}
|
||||
Row {
|
||||
spacing: size_.line
|
||||
height: size_.line
|
||||
Text_ {
|
||||
text: qsTr("预览页面")
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
CheckButton {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
height: size_.line
|
||||
enabledAnime: true
|
||||
checked: previewOCR
|
||||
textColor_: theme.subTextColor
|
||||
onCheckedChanged: {
|
||||
if(!previewOCR&&checked) {
|
||||
previewOCR = true
|
||||
toPreview()
|
||||
}
|
||||
else {
|
||||
previewOCR = ocrRunning = false
|
||||
}
|
||||
}
|
||||
text_: "OCR"
|
||||
toolTip: qsTr("预览PDF时,是否预览OCR结果")
|
||||
}
|
||||
}
|
||||
Row {
|
||||
spacing: size_.spacing
|
||||
height: size_.line + size_.smallSpacing * 2
|
||||
Button_ {
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
text_: "<"
|
||||
onClicked: changePage(0, -1)
|
||||
}
|
||||
Button_ {
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
text_: ">"
|
||||
onClicked: changePage(0, 1)
|
||||
}
|
||||
TextField_ {
|
||||
width: size_.line * 3
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
validator: IntValidator{bottom: 1; top: pageCount;}
|
||||
text: previewPage
|
||||
onTextChanged: changePage(text)
|
||||
}
|
||||
Text_ {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: "/ "+pageCount
|
||||
}
|
||||
}
|
||||
// ===== OCR范围 =====
|
||||
Rectangle {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
height: 1
|
||||
color: theme.coverColor4
|
||||
}
|
||||
Text_ {
|
||||
text: qsTr("OCR页数")
|
||||
}
|
||||
Row {
|
||||
height: size_.line + size_.smallSpacing * 2
|
||||
spacing: size_.spacing
|
||||
Text_ {
|
||||
font.pixelSize: size_.smallText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: qsTr("范围")
|
||||
}
|
||||
TextField_ {
|
||||
width: size_.line * 3
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
validator: IntValidator{bottom: -pageCount; top: pageCount;}
|
||||
text: rangeStart
|
||||
onTextChanged: {
|
||||
if(text !== "" && text !== "-") rangeStart = text
|
||||
}
|
||||
}
|
||||
Text_ {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: "-"
|
||||
}
|
||||
TextField_ {
|
||||
width: size_.line * 3
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
validator: IntValidator{bottom: -pageCount; top: pageCount;}
|
||||
text: rangeEnd
|
||||
onTextChanged: {
|
||||
if(text !== "" && text !== "-") rangeEnd = text
|
||||
}
|
||||
}
|
||||
}
|
||||
// ===== 忽略区域 =====
|
||||
Rectangle {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
height: 1
|
||||
color: theme.coverColor4
|
||||
}
|
||||
Text_ {
|
||||
text: qsTr("忽略区域页数(全局)")
|
||||
}
|
||||
Row {
|
||||
height: size_.line + size_.smallSpacing * 2
|
||||
spacing: size_.spacing
|
||||
Text_ {
|
||||
font.pixelSize: size_.smallText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: qsTr("范围")
|
||||
}
|
||||
TextField_ {
|
||||
width: size_.line * 3
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
validator: IntValidator {}
|
||||
text: ignoreRangeStart
|
||||
onTextChanged: {
|
||||
if(text !== "" && text !== "-") ignoreRangeStart = text
|
||||
}
|
||||
}
|
||||
Text_ {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: "-"
|
||||
}
|
||||
TextField_ {
|
||||
width: size_.line * 3
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
validator: IntValidator {}
|
||||
text: ignoreRangeEnd
|
||||
onTextChanged: {
|
||||
if(text !== "" && text !== "-") ignoreRangeEnd = text
|
||||
}
|
||||
}
|
||||
}
|
||||
Row {
|
||||
spacing: size_.spacing
|
||||
height: size_.smallLine + size_.smallSpacing * 2
|
||||
Button_ {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
height: size_.smallLine + size_.smallSpacing
|
||||
bgColor_: theme.coverColor1
|
||||
text_: qsTr("撤销")
|
||||
onClicked: imgViewer.revokeIg()
|
||||
textSize: size_.smallText
|
||||
}
|
||||
Button_ {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
height: size_.smallLine + size_.smallSpacing
|
||||
bgColor_: theme.coverColor1
|
||||
textColor_: theme.noColor
|
||||
text_: qsTr("清空")
|
||||
onClicked: imgViewer.clearIg()
|
||||
textSize: size_.smallText
|
||||
}
|
||||
}
|
||||
Text_ {
|
||||
text: ignoreTips
|
||||
color: theme.subTextColor
|
||||
font.pixelSize: size_.smallText
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
wrapMode: TextEdit.Wrap
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 右:图片查看面板
|
||||
rightItem: ImageWithIgnore {
|
||||
id: imgViewer
|
||||
anchors.fill: parent
|
||||
// 加载中 动态图标
|
||||
Loading {
|
||||
visible: ocrRunning
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
312
UmiOCR-data/qt_res/qml/TabPages/BatchOCR/BatchOCR.qml
Normal file
312
UmiOCR-data/qt_res/qml/TabPages/BatchOCR/BatchOCR.qml
Normal file
@@ -0,0 +1,312 @@
|
||||
// ==============================================
|
||||
// =============== 功能页:批量OCR ===============
|
||||
// ==============================================
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
|
||||
import ".."
|
||||
import "../../Widgets"
|
||||
import "../../Widgets/ResultLayout"
|
||||
import "../../Widgets/IgnoreArea"
|
||||
|
||||
TabPage {
|
||||
id: tabPage
|
||||
|
||||
// ========================= 【逻辑】 =========================
|
||||
|
||||
property int errorNum: 0 // 异常的任务个数
|
||||
property string msnID: "" // 当前任务ID
|
||||
|
||||
Component.onCompleted: {
|
||||
}
|
||||
|
||||
// 异步加载一批图像路径
|
||||
function addImages(paths) {
|
||||
if(ctrlPanel.state_ !== "stop") return
|
||||
if(paths.length <= 0) return
|
||||
const isRecurrence = configsComp.getValue("mission.recurrence")
|
||||
qmlapp.asynFilesLoader.run(paths,"image",isRecurrence,onAddImages)
|
||||
}
|
||||
// 完毕后,将合法路径加入表格
|
||||
function onAddImages(paths) {
|
||||
for(let i in paths) {
|
||||
filesTableView.add({ path: paths[i], time: "", state: "" })
|
||||
}
|
||||
}
|
||||
|
||||
// 运行OCR
|
||||
function ocrStart() {
|
||||
let msnLength = filesTableView.rowCount
|
||||
if(msnLength <= 0) {
|
||||
ctrlPanel.stopFinished()
|
||||
return
|
||||
}
|
||||
// 刷新表格
|
||||
for(let i = 0; i < msnLength; i++) {
|
||||
filesTableView.set(i, { time: "", state: qsTr("排队") })
|
||||
}
|
||||
// 刷新计数
|
||||
errorNum = 0 // 异常任务个数
|
||||
// 开始运行
|
||||
const paths = filesTableView.getColumnsValue("path")
|
||||
const argd = configsComp.getValueDict()
|
||||
msnID = tabPage.callPy("msnPaths", paths, argd)
|
||||
// 若tabPanel面板的下标没有变化过,则切换到记录页
|
||||
if(tabPanel.indexChangeNum < 2)
|
||||
tabPanel.currentIndex = 1
|
||||
ctrlPanel.runFinished(msnLength)
|
||||
}
|
||||
|
||||
// 停止OCR
|
||||
function ocrStop() {
|
||||
_ocrStop()
|
||||
tabPage.callPy("msnStop")
|
||||
ctrlPanel.stopFinished()
|
||||
}
|
||||
|
||||
function _ocrStop() {
|
||||
msnID = "" // 清除任务ID
|
||||
// 刷新表格,清空未执行的任务的状态
|
||||
let msnLength = filesTableView.rowCount
|
||||
for(let i = 0; i < msnLength; i++) {
|
||||
const row = filesTableView.get(i)
|
||||
if(row.time === "") {
|
||||
filesTableView.setProperty(i, "state", "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭页面
|
||||
function closePage() {
|
||||
if(ctrlPanel.state_ !== "stop") {
|
||||
const argd = { yesText: qsTr("依然关闭") }
|
||||
const callback = (flag)=>{
|
||||
if(flag) {
|
||||
ocrStop()
|
||||
delPage()
|
||||
}
|
||||
}
|
||||
qmlapp.popup.dialog("", qsTr("任务正在进行中。\n要结束任务并关闭页面吗?"), callback, "warning", argd)
|
||||
}
|
||||
else {
|
||||
delPage()
|
||||
}
|
||||
}
|
||||
|
||||
// 预览
|
||||
function msnPreview(path) {
|
||||
const argd = configsComp.getValueDict()
|
||||
tabPage.callPy("msnPreview", path, argd)
|
||||
}
|
||||
|
||||
// ========================= 【python调用qml】 =========================
|
||||
|
||||
// 准备开始一个任务
|
||||
function onOcrReady(path) {
|
||||
// 刷新表格显示
|
||||
filesTableView.setProperty(path, "state", qsTr("处理"))
|
||||
}
|
||||
|
||||
// 获取一个OCR的返回值
|
||||
function onOcrGet(path, res) {
|
||||
const time = res.time.toFixed(2)
|
||||
let state = ""
|
||||
switch(res.code){
|
||||
case 100:
|
||||
state = "√ "+res.score.toFixed(2);break
|
||||
case 101:
|
||||
state = "√ ---- ";break
|
||||
default:
|
||||
state = "× "+res.code
|
||||
errorNum++ // 异常任务数量+1
|
||||
break
|
||||
}
|
||||
// 刷新表格显示
|
||||
filesTableView.set(path, { "time": time, "state": state })
|
||||
// 提取文字,添加到结果表格
|
||||
res.title = res.fileName
|
||||
resultsTableView.addOcrResult(res)
|
||||
ctrlPanel.msnStep(1) // 任务计数器步进
|
||||
}
|
||||
|
||||
// 任务队列完毕
|
||||
function onOcrEnd(msg, thisMsnID) {
|
||||
// msg: [Success] [Warning] [Error]
|
||||
if(msnID !== thisMsnID) { // 返回的任务ID不等于前端展示的任务ID,则不处理
|
||||
return
|
||||
}
|
||||
_ocrStop()
|
||||
// 任务成功
|
||||
if(msg.startsWith("[Success]")) {
|
||||
let errMsg = ""
|
||||
if(errorNum > 0) { // 有异常任务
|
||||
errMsg = qsTr("%1 张图片识别失败!").arg(errorNum)
|
||||
}
|
||||
const simpleType = configsComp.getValue("other.simpleNotificationType")
|
||||
qmlapp.popup.simple(qsTr("批量识别完成"), errMsg, simpleType)
|
||||
// 任务完成后续操作
|
||||
qmlapp.globalConfigs.utilsDicts.postTaskHardwareCtrl(
|
||||
configsComp.getValue("postTaskActions.system")
|
||||
)
|
||||
}
|
||||
// 任务失败
|
||||
else if(msg.startsWith("[Error]")) {
|
||||
qmlapp.popup.message(qsTr("批量识别任务异常"), msg, "error")
|
||||
}
|
||||
ctrlPanel.stopFinished()
|
||||
}
|
||||
|
||||
// 预览
|
||||
function onPreview(path, res) {
|
||||
ignoreArea.getPreview(res, path, "")
|
||||
}
|
||||
// 路径转文件名
|
||||
function path2name(path) {
|
||||
const parts = path.split("/")
|
||||
return parts[parts.length - 1]
|
||||
}
|
||||
// 文件表格中单击路径
|
||||
function onClickPath(index) {
|
||||
let info = filesTableView.get(index)
|
||||
let fileName = path2name(info.path)
|
||||
let res = resultsTableView.getResult(fileName)
|
||||
let data = undefined, text = undefined
|
||||
if(res) {
|
||||
if(res.source)
|
||||
data = JSON.parse(res.source)
|
||||
if(res.resText)
|
||||
text = res.resText
|
||||
}
|
||||
previewImage.show(info.path, data, text)
|
||||
}
|
||||
|
||||
// ========================= 【布局】 =========================
|
||||
|
||||
// 配置
|
||||
configsComp: BatchOCRConfigs {
|
||||
// 点按钮打开忽略区域
|
||||
onClickIgnoreArea: {
|
||||
if(filesTableView.rowCount > 0) {
|
||||
const path = filesTableView.get(0).path
|
||||
console.log("打开路径", path)
|
||||
ignoreArea.showPath(path)
|
||||
}
|
||||
else {
|
||||
ignoreArea.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
// 主区域:可切换双栏面板
|
||||
DoubleSwitchableLayout {
|
||||
id: doubleLayout
|
||||
saveKey: "BatchOCR_1"
|
||||
anchors.fill: parent
|
||||
|
||||
// 面板A:控制板+文件表格
|
||||
itemA: Panel {
|
||||
anchors.fill: parent
|
||||
|
||||
// 上方控制板
|
||||
MissionCtrlPanel {
|
||||
id: ctrlPanel
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: size_.spacing
|
||||
height: size_.line * 2
|
||||
|
||||
onRunClicked: tabPage.ocrStart()
|
||||
onPauseClicked: {
|
||||
tabPage.callPy("msnPause")
|
||||
pauseFinished()
|
||||
}
|
||||
onResumeClicked: {
|
||||
tabPage.callPy("msnResume")
|
||||
resumeFinished()
|
||||
}
|
||||
onStopClicked: tabPage.ocrStop()
|
||||
}
|
||||
|
||||
// 下方文件表格
|
||||
FilesTableView {
|
||||
id: filesTableView
|
||||
anchors.top: ctrlPanel.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.margins: size_.spacing
|
||||
anchors.topMargin: size_.smallSpacing
|
||||
headers: [
|
||||
{key: "path", title: qsTr("图片"), left: true, display:path2name,
|
||||
btn: true, onClicked:onClickPath},
|
||||
{key: "time", title: qsTr("耗时"), },
|
||||
{key: "state", title: qsTr("状态"), },
|
||||
]
|
||||
openBtnText: qsTr("打开图片")
|
||||
clearBtnText: qsTr("清空")
|
||||
defaultTips: qsTr("拖入图片或文件夹")
|
||||
fileDialogTitle: qsTr("请选择图片")
|
||||
fileDialogNameFilters: [qsTr("图片")+" (*.jpg *.jpe *.jpeg *.jfif *.png *.webp *.bmp *.tif *.tiff)"]
|
||||
isLock: ctrlPanel.state_ !== "stop"
|
||||
onAddPaths: {
|
||||
tabPage.addImages(paths)
|
||||
}
|
||||
}
|
||||
}
|
||||
// 面板B:文字输出 & 设置
|
||||
itemB: Panel {
|
||||
anchors.fill: parent
|
||||
|
||||
// 配置项控制板
|
||||
TabPanel {
|
||||
id: tabPanel
|
||||
anchors.fill: parent
|
||||
anchors.margins: size_.spacing
|
||||
isMenuTop: doubleLayout.isRow // 左右布局时,菜单在顶部;上下布局时菜单在底部
|
||||
menuHeight: size_.line * 2
|
||||
|
||||
// 结果面板
|
||||
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.addImages
|
||||
}
|
||||
|
||||
// 预览面板
|
||||
PreviewImage {
|
||||
id: previewImage
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
// 忽略区域编辑器
|
||||
IgnoreArea {
|
||||
id: ignoreArea
|
||||
anchors.fill: parent
|
||||
pathPreview: msnPreview
|
||||
configsComp: tabPage.configsComp
|
||||
configKey: "tbpu.ignoreArea"
|
||||
}
|
||||
}
|
||||
136
UmiOCR-data/qt_res/qml/TabPages/BatchOCR/BatchOCRConfigs.qml
Normal file
136
UmiOCR-data/qt_res/qml/TabPages/BatchOCR/BatchOCRConfigs.qml
Normal file
@@ -0,0 +1,136 @@
|
||||
// ==============================================
|
||||
// =============== 批量OCR的配置项 ===============
|
||||
// ==============================================
|
||||
|
||||
import QtQuick 2.15
|
||||
import "../../Configs"
|
||||
|
||||
Configs {
|
||||
category_: "BatchOCR"
|
||||
signal clickIgnoreArea() // 打开忽略区域
|
||||
|
||||
configDict: {
|
||||
// OCR参数
|
||||
"ocr": qmlapp.globalConfigs.ocrManager.deploy(this, "ocr"),
|
||||
|
||||
// 后处理
|
||||
"tbpu": {
|
||||
"title": qsTr("OCR文本后处理"),
|
||||
"type": "group",
|
||||
|
||||
"parser": qmlapp.globalConfigs.utilsDicts.getTbpuParser(),
|
||||
"btns": {
|
||||
"title": qsTr("忽略区域"),
|
||||
"btnsList": [
|
||||
{"text":qsTr("进入设置"), "onClicked": clickIgnoreArea},
|
||||
],
|
||||
},
|
||||
"ignoreArea": {
|
||||
"type": "var",
|
||||
"save": false,
|
||||
},
|
||||
},
|
||||
|
||||
// 任务参数
|
||||
"mission": {
|
||||
"title": qsTr("批量任务"),
|
||||
"type": "group",
|
||||
|
||||
"recurrence": {
|
||||
"title": qsTr("递归读取子文件夹"),
|
||||
"toolTip": qsTr("导入文件夹时,导入子文件夹中全部图片"),
|
||||
"default": false,
|
||||
},
|
||||
"dirType": {
|
||||
"title": qsTr("保存到"),
|
||||
"optionsList": [
|
||||
["source", qsTr("图片原目录")],
|
||||
["specify", qsTr("指定目录")],
|
||||
],
|
||||
},
|
||||
"dir": {
|
||||
"title": qsTr("指定目录"),
|
||||
"toolTip": qsTr("必须先指定“保存到指定目录”才生效"),
|
||||
"type": "file",
|
||||
"selectExisting": true, // 选择现有
|
||||
"selectFolder": true, // 选择文件夹
|
||||
"dialogTitle": qsTr("OCR结果保存目录"),
|
||||
},
|
||||
"fileNameFormat": {
|
||||
"title": qsTr("文件名格式"),
|
||||
"toolTip": qsTr("无需填写拓展名。支持插入以下占位符:\n%date 日期时间\n%name 原文件夹名/文件名\n举例:[OCR]_%name_%date\n生成:[OCR]_我的图片_2023-09-01_12-13.txt\n添加占位符可以避免旧文件被新文件覆盖。"),
|
||||
"default": "[OCR]_%name_%date",
|
||||
"advanced": true, // 高级选项
|
||||
},
|
||||
"datetimeFormat": {
|
||||
"title": qsTr("日期时间格式"),
|
||||
"toolTip": qsTr("文件名中 %date 的日期格式。支持插入以下占位符:\n%Y 年、 %m 月、 %d 日、 %H 小时、 \n%M 分钟、 %S 秒 、 %unix 时间戳 \n举例:%Y年%m月%d日_%H-%M\n生成:2023年09月01日_12-13.txt"),
|
||||
"default": "%Y%m%d_%H%M",
|
||||
"advanced": true, // 高级选项
|
||||
},
|
||||
|
||||
"filesType": {
|
||||
"title": qsTr("保存文件类型"),
|
||||
"type": "group",
|
||||
"enabledFold": true,
|
||||
"fold": false,
|
||||
|
||||
"txt": {
|
||||
"title": qsTr("txt 标准格式"),
|
||||
"toolTip": qsTr("含原图片文件名和识别文字"),
|
||||
"default": true,
|
||||
},
|
||||
"txtPlain": {
|
||||
"title": qsTr("p.txt 纯文字格式"),
|
||||
"toolTip": qsTr("仅输出识别文字,不含图片标题"),
|
||||
"default": false,
|
||||
},
|
||||
"txtIndividual": {
|
||||
"title": qsTr("txt 单独文件"),
|
||||
"toolTip": qsTr("对每张图片,生成同名txt文件,仅输出识别文字"),
|
||||
"default": false,
|
||||
},
|
||||
"md": {
|
||||
"title": qsTr("md 图文混排"),
|
||||
"toolTip": qsTr("Markdown图文混排格式,可用Markdown阅读器浏览文件"),
|
||||
"default": false,
|
||||
},
|
||||
"csv": {
|
||||
"title": qsTr("csv 表格文件(Excel)"),
|
||||
"toolTip": qsTr("将图片信息和识别内容写入csv表格文件。可用Excel打开,另存为xlsx格式。"),
|
||||
"default": false,
|
||||
},
|
||||
"jsonl": {
|
||||
"title": qsTr("jsonl 原始信息"),
|
||||
"toolTip": qsTr("每行为一条json数据,便于第三方程序读取操作"),
|
||||
"default": false,
|
||||
},
|
||||
},
|
||||
|
||||
"ignoreBlank": {
|
||||
"title": qsTr("输出忽略空白图片"),
|
||||
"toolTip": qsTr("若图片没有文字或识别失败,也不会输出错误提示信息"),
|
||||
"default": true,
|
||||
},
|
||||
},
|
||||
|
||||
// 任务完成后续操作
|
||||
"postTaskActions": qmlapp.globalConfigs.utilsDicts.getPostTaskActions(),
|
||||
|
||||
"other": {
|
||||
"title": qsTr("其它"),
|
||||
"type": "group",
|
||||
|
||||
"simpleNotificationType": qmlapp.globalConfigs.utilsDicts.getSimpleNotificationType()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
输出文件类型
|
||||
.txt 标准格式
|
||||
.txt 纯文本格式
|
||||
.txt 多个独立文件
|
||||
.jsonl 原始信息
|
||||
*/
|
||||
145
UmiOCR-data/qt_res/qml/TabPages/BatchOCR/PreviewImage.qml
Normal file
145
UmiOCR-data/qt_res/qml/TabPages/BatchOCR/PreviewImage.qml
Normal file
@@ -0,0 +1,145 @@
|
||||
// ===========================================
|
||||
// =============== 图片预览面板 ===============
|
||||
// ===========================================
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
|
||||
import "../../Widgets"
|
||||
import "../../Widgets/ImageViewer"
|
||||
|
||||
ModalLayer {
|
||||
id: pRoot
|
||||
closeText: ""
|
||||
|
||||
// 展示图片/文本
|
||||
function show(path, data, text) {
|
||||
visible = true
|
||||
imageText.showPath(path)
|
||||
if(data) {
|
||||
console.log("展示data", data)
|
||||
imageText.showTextBoxes(data)
|
||||
}
|
||||
if(text) {
|
||||
textEdit.text = text
|
||||
}
|
||||
else {
|
||||
textEdit.text = ""
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
contentItem: DoubleRowLayout {
|
||||
anchors.fill: parent
|
||||
initSplitterX: 0.7
|
||||
leftItem: Panel {
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
// 顶部栏
|
||||
Item {
|
||||
id: leftTop
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: size_.smallSpacing
|
||||
height: size_.line + size_.smallSpacing
|
||||
// 靠右
|
||||
Row {
|
||||
id: leftTopR
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.rightMargin: size_.spacing
|
||||
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: "open_image",
|
||||
onClicked: imageText.openImage,
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
// 图片预览区域
|
||||
ImageWithText {
|
||||
id: imageText
|
||||
anchors.top: leftTop.bottom
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: size_.spacing
|
||||
anchors.topMargin: size_.smallSpacing
|
||||
}
|
||||
}
|
||||
rightItem: Panel {
|
||||
anchors.fill: parent
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.margins: size_.spacing
|
||||
color: theme.bgColor
|
||||
border.width: 1
|
||||
border.color: theme.coverColor4
|
||||
|
||||
ScrollView {
|
||||
id: textView
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: size_.spacing
|
||||
anchors.rightMargin: size_.spacing
|
||||
contentWidth: width // 内容宽度
|
||||
clip: true // 溢出隐藏
|
||||
|
||||
TextEdit_ {
|
||||
id: textEdit
|
||||
width: textView.width
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
228
UmiOCR-data/qt_res/qml/TabPages/GlobalConfigsPage/FontPanel.qml
Normal file
228
UmiOCR-data/qt_res/qml/TabPages/GlobalConfigsPage/FontPanel.qml
Normal file
@@ -0,0 +1,228 @@
|
||||
// ===========================================
|
||||
// =============== 字体修改面板 ===============
|
||||
// ===========================================
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
|
||||
import "../../Widgets"
|
||||
|
||||
ModalLayer {
|
||||
id: fRoot
|
||||
property var fontsList: []
|
||||
|
||||
// 主要UI文字字体,内容可控,可以用裁切的ttf
|
||||
property string fontFamily: ""
|
||||
// 数据显示文字字体,内容不可控,用兼容性好的系统字体
|
||||
property string dataFontFamily: ""
|
||||
// 不可加载的字体
|
||||
property var illegalFonts: ["", "Terminal", "System", "Small Fonts", "Script", "Roman", "MS Serif", "MS Sans Serif", "Modern", "Fixedsys"]
|
||||
|
||||
function setFontFamily(f) {
|
||||
fontFamily = f
|
||||
qmlapp.globalConfigs.setValue("ui.fontFamily", f)
|
||||
}
|
||||
function setDataFontFamily(f) {
|
||||
dataFontFamily = f
|
||||
qmlapp.globalConfigs.setValue("ui.dataFontFamily", f)
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
// 将此组件的引用传入全局设置
|
||||
qmlapp.globalConfigs.fontPanel = this
|
||||
fontFamily = qmlapp.globalConfigs.getValue("ui.fontFamily")
|
||||
dataFontFamily = qmlapp.globalConfigs.getValue("ui.dataFontFamily")
|
||||
}
|
||||
|
||||
contentItem: Item {
|
||||
id: content
|
||||
anchors.fill: parent
|
||||
}
|
||||
Loader {
|
||||
id: panelLoader
|
||||
asynchronous: true
|
||||
sourceComponent: com
|
||||
active: fRoot.visible
|
||||
}
|
||||
Component {
|
||||
id: com
|
||||
DoubleRowLayout {
|
||||
parent: content
|
||||
anchors.fill: parent
|
||||
initSplitterX: 0.5
|
||||
|
||||
Component.onCompleted: {
|
||||
// 获取字体列表,过滤出以中文字符开头的字体
|
||||
let fList = Qt.fontFamilies()
|
||||
let newList = fList.filter(function(str) {
|
||||
return /^[\u4e00-\u9fa5]/.test(str);
|
||||
})
|
||||
// 补充剩余字体
|
||||
for(let i in fList) {
|
||||
if(illegalFonts.includes(fList[i]))
|
||||
continue
|
||||
if(!newList.includes(fList[i]))
|
||||
newList.push(fList[i])
|
||||
}
|
||||
// 将当前选中的移到最前面
|
||||
const i1 = newList.indexOf(dataFontFamily)
|
||||
if (i1 > -1) {
|
||||
newList.splice(i1, 1)
|
||||
newList.unshift(dataFontFamily)
|
||||
}
|
||||
const i2 = newList.indexOf(fontFamily)
|
||||
if (i2 > -1 && i2 !== i1) {
|
||||
newList.splice(i2, 1)
|
||||
newList.unshift(fontFamily)
|
||||
}
|
||||
fRoot.fontsList = newList
|
||||
}
|
||||
|
||||
leftItem: Panel {
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
|
||||
Row {
|
||||
id: leftTop
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
anchors.margins: size_.spacing
|
||||
anchors.topMargin: 0
|
||||
anchors.rightMargin: size_.spacing * 3
|
||||
spacing: size_.spacing
|
||||
height: size_.line * 2
|
||||
|
||||
// Text_ {
|
||||
// anchors.top: parent.top
|
||||
// anchors.bottom: parent.bottom
|
||||
// verticalAlignment: Text.AlignVCenter
|
||||
// text: qsTr("设置为:")
|
||||
// }
|
||||
Text_ {
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
width: size_.line * 3
|
||||
text: qsTr("界面")
|
||||
}
|
||||
Text_ {
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
width: size_.line * 3
|
||||
text: qsTr("内容")
|
||||
font.family: theme.dataFontFamily
|
||||
}
|
||||
}
|
||||
Panel {
|
||||
anchors.top: leftTop.bottom
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: size_.spacing
|
||||
anchors.topMargin: 0
|
||||
color: theme.bgColor
|
||||
|
||||
TableView {
|
||||
id: leftTable
|
||||
anchors.fill: parent
|
||||
anchors.margins: size_.spacing
|
||||
clip: true
|
||||
model: fRoot.fontsList
|
||||
contentWidth: width // 内容宽度
|
||||
rowSpacing: size_.spacing // 行间隔
|
||||
flickableDirection: Flickable.VerticalFlick // 只允许垂直滚动
|
||||
columnWidthProvider: ()=>leftTable.width
|
||||
|
||||
delegate: Rectangle {
|
||||
height: size_.line * 2
|
||||
implicitWidth: 100
|
||||
implicitHeight: height
|
||||
width: leftTable.width
|
||||
color: fontMouseArea.containsMouse?theme.coverColor2:"#00000000"
|
||||
|
||||
Text_ {
|
||||
text: modelData
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: size_.spacing
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
font.family: modelData
|
||||
}
|
||||
MouseArea {
|
||||
id: fontMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
}
|
||||
// 右边,内容字体
|
||||
IconButton {
|
||||
id: btn2
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
anchors.margins: size_.smallSpacing
|
||||
color: theme.yesColor
|
||||
bgColor_: theme.coverColor1
|
||||
width: size_.line * 3
|
||||
borderWidth: 1
|
||||
borderColor: theme.specialTextColor
|
||||
bgHoverColor_: theme.coverColor3
|
||||
icon_: dataFontFamily===modelData?"yes":""
|
||||
onClicked: setDataFontFamily(modelData)
|
||||
}
|
||||
// 左边,界面字体
|
||||
IconButton {
|
||||
id: btn1
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: btn2.left
|
||||
anchors.margins: size_.smallSpacing
|
||||
anchors.rightMargin: size_.spacing
|
||||
color: theme.yesColor
|
||||
bgColor_: theme.coverColor1
|
||||
width: size_.line * 3
|
||||
borderWidth: 1
|
||||
borderColor: theme.specialTextColor
|
||||
bgHoverColor_: theme.coverColor3
|
||||
icon_: fontFamily===modelData?"yes":""
|
||||
onClicked: setFontFamily(modelData)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
rightItem: Panel {
|
||||
anchors.fill: parent
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
anchors.margins: size_.spacing * 3
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
spacing: size_.line
|
||||
|
||||
Text_ {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
wrapMode: Text.Wrap
|
||||
font.family: fontFamily
|
||||
text: qsTr("界面字体:\n软件中大部分UI的字体。")
|
||||
}
|
||||
|
||||
Text_ {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
wrapMode: Text.Wrap
|
||||
font.family: dataFontFamily
|
||||
text: qsTr("内容字体:\n识别结果内容的字体。")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
// ==============================================
|
||||
// =============== 功能页:全局设置 ===============
|
||||
// ==============================================
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
|
||||
import ".."
|
||||
import "../../Widgets"
|
||||
|
||||
TabPage {
|
||||
id: tabPage
|
||||
property QtObject confComp: qmlapp.globalConfigs.panelComponent
|
||||
property var groupList: []
|
||||
onShowPage: {
|
||||
groupList = confComp.getGroupList()
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
const tips = qsTr("1. 关于快捷键、OCR语言等选项,请在各个功能页中进行设置。\n2. 勾选左下角的“高级”按钮,可以显示更多进阶选项。")
|
||||
qmlapp.popup.messageMemory("globalConfigsKey", qsTr("小贴士"), tips)
|
||||
}
|
||||
|
||||
DoubleRowLayout {
|
||||
anchors.fill: parent
|
||||
initSplitterX: size_.line * 15
|
||||
|
||||
// 左面板:设置标题列表
|
||||
leftItem: Panel {
|
||||
anchors.fill: parent
|
||||
// 上:标题栏
|
||||
Item {
|
||||
id: leftTop
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: leftBottom.top
|
||||
anchors.margins: size_.spacing
|
||||
ScrollView {
|
||||
id: scrollView
|
||||
anchors.fill: parent
|
||||
anchors.margins: size_.spacing
|
||||
clip: true
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
Repeater {
|
||||
model: groupList
|
||||
Button_ {
|
||||
visible: !modelData.advanced || confComp.advanced
|
||||
text_: modelData.title
|
||||
width: scrollView.width
|
||||
height: size_.line * 2.5
|
||||
onClicked: {
|
||||
confComp.scrollToGroup(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 下:控制按钮栏
|
||||
Item {
|
||||
id: leftBottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
height: size_.line * 2
|
||||
anchors.margins: size_.spacing
|
||||
}
|
||||
}
|
||||
|
||||
// 右面板:设置面板
|
||||
rightItem: Panel {
|
||||
anchors.fill: parent
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
anchors.margins: size_.spacing
|
||||
Component.onCompleted: { // 将全局设置UI的父级重定向过来
|
||||
// 就算本页面删除,全局UI也不会被删,只会丢失父级
|
||||
confComp.parent = this
|
||||
confComp.ctrlBar.parent = leftBottom
|
||||
confComp.ctrlBar.anchors.fill = leftBottom
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 字体设置面板
|
||||
FontPanel {
|
||||
anchors.fill: parent
|
||||
z: 10
|
||||
}
|
||||
}
|
||||
135
UmiOCR-data/qt_res/qml/TabPages/Navigation/Navigation.qml
Normal file
135
UmiOCR-data/qt_res/qml/TabPages/Navigation/Navigation.qml
Normal file
@@ -0,0 +1,135 @@
|
||||
// ================================================
|
||||
// =============== 导航页(新标签页) ===============
|
||||
// ================================================
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import ".."
|
||||
import "../.."
|
||||
import "../../Widgets"
|
||||
|
||||
TabPage {
|
||||
|
||||
// =============== 逻辑 ===============
|
||||
|
||||
id: naviPage
|
||||
// 页面信息存储
|
||||
ListModel { id: pageModel }
|
||||
// 动态变化的简介文本
|
||||
property string introText: ""
|
||||
// 初始简介(欢迎词)
|
||||
property string welcomeText: `# `+qsTr("欢迎使用 Umi-OCR")+`
|
||||
|
||||
## 👈 `+qsTr("请选择功能页")+`
|
||||
|
||||
|
||||
|
||||
|
||||
`+qsTr("当前版本")+` • ${UmiAbout.version.string}
|
||||
|
||||
`+qsTr("项目链接")+` • [`+qsTr("官方网站")+`](${UmiAbout.url.home}) [`+qsTr("插件拓展")+`](${UmiAbout.url.plugins}) [`+qsTr("问题反馈")+`](${UmiAbout.url.issue})
|
||||
|
||||
`
|
||||
|
||||
// 初始化数据
|
||||
Component.onCompleted: initData()
|
||||
function initData() {
|
||||
introText = welcomeText
|
||||
pageModel.clear()
|
||||
const f = qmlapp.tab.infoList
|
||||
// 遍历所有文件信息(排除第一项自己)
|
||||
for(let i=1,c=f.length; i<c; i++){
|
||||
pageModel.append({
|
||||
"title": f[i].title,
|
||||
"intro": f[i].intro,
|
||||
"infoIndex": i,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// =============== 布局 ===============
|
||||
|
||||
DoubleRowLayout {
|
||||
anchors.fill: parent
|
||||
initSplitterX: size_.line * 15
|
||||
|
||||
// =============== 左侧,展示所有标签页名称 ===============
|
||||
leftItem: Panel {
|
||||
anchors.fill: parent
|
||||
|
||||
Item {
|
||||
id: topLable
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.margins: size_.spacing
|
||||
height: size_.line * 2.5
|
||||
Text_ {
|
||||
anchors.centerIn: parent
|
||||
text: qsTr("功能页")
|
||||
color: theme.subTextColor
|
||||
}
|
||||
MouseAreaBackgroud {
|
||||
onHoveredChanged: naviPage.introText = naviPage.welcomeText
|
||||
}
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
id: scrollView
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.top: topLable.bottom
|
||||
anchors.margins: size_.spacing
|
||||
clip: true
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
spacing: size_.spacing * 0.5
|
||||
|
||||
Repeater {
|
||||
model: pageModel
|
||||
Button_ {
|
||||
text_: title
|
||||
width: scrollView.width
|
||||
height: size_.line * 2.5
|
||||
|
||||
onHoveredChanged: {
|
||||
naviPage.introText = intro
|
||||
}
|
||||
onClicked: {
|
||||
let i = qmlapp.tab.getTabPageIndex(naviPage)
|
||||
if(i < 0){
|
||||
console.error("【Error】导航页"+text+"未找到下标!")
|
||||
}
|
||||
qmlapp.tab.changeTabPage(i, infoIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =============== 右侧,展示功能简介 ===============
|
||||
rightItem: Panel{
|
||||
anchors.fill: parent
|
||||
|
||||
MarkdownView {
|
||||
id: introView
|
||||
anchors.fill: parent
|
||||
anchors.margins: size_.spacing * 2
|
||||
text: introText
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 鼠标拖入文档
|
||||
DropArea_ {
|
||||
id: "addDocsDropArea"
|
||||
anchors.fill: parent
|
||||
tips: qsTr("请打开对应标签页(如批量OCR、批量文档),再拖入文件。")
|
||||
}
|
||||
}
|
||||
232
UmiOCR-data/qt_res/qml/TabPages/PagesManager.qml
Normal file
232
UmiOCR-data/qt_res/qml/TabPages/PagesManager.qml
Normal file
@@ -0,0 +1,232 @@
|
||||
// ===============================================
|
||||
// =============== 页面的逻辑管理器 ===============
|
||||
// ===============================================
|
||||
|
||||
|
||||
import QtQuick 2.15
|
||||
import TagPageConnector 1.0 // Python页面连接器
|
||||
|
||||
Item {
|
||||
|
||||
// ========================= 【列表】 =========================
|
||||
|
||||
property var infoList: [
|
||||
{
|
||||
key: "Navigation", // 页面的唯一标识符。同时也是对应Python模块的名称。
|
||||
url: "", // 页面的qml文件路径。留空时,初始化为 key/key.qml
|
||||
needController: false, // 为true时才需要加载对应Python模块,不需要可留空
|
||||
title: qsTr("新标签页"), // 页面的显示名称。
|
||||
intro: "" // 页面的简介。
|
||||
},
|
||||
{
|
||||
key: "ScreenshotOCR",
|
||||
needController: true,
|
||||
title: qsTr("截图OCR"),
|
||||
intro: qsTr("# 截图OCR\n\n屏幕截图,快捷转文字。也支持粘贴图片。")
|
||||
},
|
||||
{
|
||||
key: "BatchOCR",
|
||||
needController: true,
|
||||
title: qsTr("批量OCR"),
|
||||
intro: qsTr("# 批量OCR\n\n导入本地图片或文件夹,批量转换文字。\n\n支持格式:")
|
||||
+ " `jpg, jpe, jpeg, jfif, png, webp, bmp, tif, tiff`",
|
||||
},
|
||||
{
|
||||
key: "BatchDOC",
|
||||
needController: true,
|
||||
title: qsTr("批量文档"),
|
||||
intro: qsTr("# 批量文档识别\n\n批量导入文档,提取文字,或生成双层可搜索PDF。\n\n支持格式:")
|
||||
+ " `pdf, xps, epub, mobi, fb2, cbz`",
|
||||
},
|
||||
{
|
||||
key: "QRCode",
|
||||
needController: true,
|
||||
title: qsTr("二维码"),
|
||||
intro: qsTr("# 二维码\n\n识别或生成二维码、条形码。\n\n支持协议:")
|
||||
+ " `Aztec, Codabar, Code128, Code39, Code93, DataBar, DataBarExpanded, DataMatrix, EAN13, EAN8, ITF, LinearCodes, MatrixCodes, MaxiCode, MicroQRCode, PDF417, QRCode, UPCA, UPCE`",
|
||||
},
|
||||
{
|
||||
key: "GlobalConfigsPage",
|
||||
title: qsTr("全局设置"),
|
||||
intro: qsTr("# 全局设置\n\n调节全局设置项,对所有页面生效。")
|
||||
},
|
||||
{
|
||||
key: "About",
|
||||
title: qsTr("关于"),
|
||||
intro: qsTr("# 关于")
|
||||
},
|
||||
]
|
||||
|
||||
/* 存放当前已打开的页面
|
||||
obj: 页面组件对象
|
||||
info: 页面信息(infoList中对应项的引用)
|
||||
infoIndex: 页面信息下标(infoList中对应项的引用)
|
||||
*/
|
||||
property var pageList: []
|
||||
|
||||
// Python的页面连接器,手动维护单例状态
|
||||
TagPageConnector { id: connector }
|
||||
|
||||
// ========================= 【增删改查】 =========================
|
||||
|
||||
// 初始化
|
||||
function initListUrl() {
|
||||
for(let i=infoList.length-1; i>=0; i--){
|
||||
const info = infoList[i]
|
||||
if(!info.url) {
|
||||
info.url = `${info.key}/${info.key}.qml`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 创建一个页面的组件类 comp
|
||||
function getComp(infoIndex) {
|
||||
const info = infoList[infoIndex]
|
||||
if(info.comp) return info.comp
|
||||
const url = info.url
|
||||
const comp = Qt.createComponent(url)
|
||||
if (comp.status === Component.Ready) { // 加载成功
|
||||
infoList[infoIndex].comp = comp
|
||||
return comp
|
||||
} else{ // 加载失败
|
||||
if (comp.status === Component.Error) { // 加载失败,提取错误信息
|
||||
let str = comp.errorString()
|
||||
const last = str.lastIndexOf(":")
|
||||
if(last < 0) last = -1
|
||||
str = str.substring(last+1).replace("\n","")
|
||||
console.error(`【Error】加载页面文件失败:【${url}】${str}`)
|
||||
}
|
||||
else{
|
||||
console.error(`【Error】加载页面文件异常:【${url}】`)
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
// 创建并返回一个 infoList[infoIndex] 页面。
|
||||
function newPage(infoIndex){
|
||||
const info = infoList[infoIndex]
|
||||
// 实例化逻辑控制器
|
||||
let ctrlKey = ""
|
||||
if(info.needController){
|
||||
ctrlKey = connector.addPage(info.key)
|
||||
if(!ctrlKey){
|
||||
console.error("【Error】添加页面失败:组件["+info.key+"]创建控制器失败!")
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
else { // 新增一个不带控制器的简单页
|
||||
ctrlKey = connector.addSimplePage(info.key)
|
||||
}
|
||||
// 检查组件
|
||||
let comp = getComp(infoIndex)
|
||||
if(!comp){
|
||||
console.error("【Error】添加页面失败:组件["+info.key+"]的comp无法创建!")
|
||||
return undefined
|
||||
}
|
||||
// 实例化页面,挂到巢下,写入自身参数
|
||||
const obj = comp.createObject(pagesNest, {
|
||||
z: -1, visible: false,
|
||||
ctrlKey: ctrlKey, // Python控制器key
|
||||
connector: connector, // Python控制器对象
|
||||
})
|
||||
// 收集并返回页面对象信息
|
||||
const dic = {
|
||||
obj: obj,
|
||||
info: info,
|
||||
infoIndex: infoIndex,
|
||||
ctrlKey: ctrlKey
|
||||
}
|
||||
// 向控制器传入页面对象
|
||||
connector.setPageQmlObj(ctrlKey, obj)
|
||||
return dic
|
||||
}
|
||||
|
||||
// 增: 在 pageList 的 index 处,插入一个 infoList[infoIndex] 页面。
|
||||
function addPage(index, infoIndex){ // index=-1 代表尾部插入
|
||||
// 列表添加
|
||||
const dic = newPage(infoIndex)
|
||||
if(dic == undefined){
|
||||
return false
|
||||
}
|
||||
pageList.splice(index, 0, dic) // 列表添加
|
||||
return true
|
||||
}
|
||||
|
||||
// 增改: 在 pageList 的 index 处,删除该页面,改为 infoIndex 页。
|
||||
function changePage(index, infoIndex){
|
||||
const page = pageList[index]
|
||||
// 删除旧页的python逻辑控制器
|
||||
const flag = connector.delPage(page.ctrlKey)
|
||||
if(!flag){
|
||||
console.error("【Warning】删除页面失败:控制器["+page.ctrlKey+"]删除失败!")
|
||||
// return false // 暂时不管控制器删除失败
|
||||
}
|
||||
const dic = newPage(infoIndex)
|
||||
if(dic == undefined){
|
||||
return false
|
||||
}
|
||||
page.obj.destroy() // 旧页对象删除
|
||||
pageList[index] = dic // 替换新页
|
||||
return true
|
||||
}
|
||||
|
||||
// 删: 在 pageList 的 index 处,发送关闭指令。
|
||||
function closePage(index){
|
||||
pageList[index].obj.closePage()
|
||||
}
|
||||
|
||||
// 删: 在 pageList 的 index 处,删除该页面。
|
||||
function delPage(index){
|
||||
const page = pageList[index]
|
||||
// 删除python逻辑控制器
|
||||
const flag = connector.delPage(page.ctrlKey)
|
||||
if(!flag){
|
||||
console.error("【Warning】删除页面失败:控制器["+page.ctrlKey+"]删除失败!")
|
||||
// return false // 暂时不管控制器删除失败
|
||||
}
|
||||
page.obj.destroy() // 页对象删除
|
||||
pageList.splice(index, 1) // 列表删除
|
||||
return true
|
||||
}
|
||||
|
||||
// 改: 展示 index 页。
|
||||
function showPage(index){
|
||||
// 遍历,将展示的页面设为可视状态,其他页面设为非可视状态
|
||||
for(let i in pageList){
|
||||
if(i==index){
|
||||
pageList[i].obj.z = 0
|
||||
pageList[i].obj.visible = true
|
||||
pageList[i].obj.showPage()
|
||||
}else{
|
||||
pageList[i].obj.z = -1
|
||||
pageList[i].obj.visible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 改: 将一个原本在 index 的页移到 go 处。
|
||||
function movePage(index, go){
|
||||
var x = pageList.splice(index, 1)[0] // 删除
|
||||
pageList.splice(go, 0, x) // 添加
|
||||
}
|
||||
|
||||
// 查: 传入下标 index 列表 list 报错内容前缀 msg ,返回下标是否合法。
|
||||
function isIndex(index, list, msg=""){
|
||||
if(index<0 || index>=list.length){
|
||||
if(msg)
|
||||
console.error(msg+"下标"+index+"超出范围"+(pageList.length-1)+"!")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// ========================= 【辅助元素】 =========================
|
||||
|
||||
// 页巢,作为已生成的页组件对象的父级。可挂载到可视节点下来展示。
|
||||
Item {
|
||||
id: pagesNest
|
||||
anchors.fill: parent
|
||||
}
|
||||
property var pagesNest: pagesNest
|
||||
}
|
||||
464
UmiOCR-data/qt_res/qml/TabPages/QRCode/QRCode.qml
Normal file
464
UmiOCR-data/qt_res/qml/TabPages/QRCode/QRCode.qml
Normal file
@@ -0,0 +1,464 @@
|
||||
// ==============================================
|
||||
// =============== 功能页:二维码 ===============
|
||||
// ==============================================
|
||||
|
||||
import QtQuick 2.15
|
||||
|
||||
import ".."
|
||||
import "../../Widgets"
|
||||
import "../../Widgets/ResultLayout"
|
||||
import "../../Widgets/ImageViewer"
|
||||
|
||||
TabPage {
|
||||
id: tabPage
|
||||
// 配置
|
||||
configsComp: QRCodeConfigs {
|
||||
// 修改配置信号触发后,延迟一个事件循环,重新生成二维码图片
|
||||
onReBarcode: Qt.callLater(reWriteBarcode)
|
||||
}
|
||||
|
||||
// ========================= 【逻辑】 =========================
|
||||
|
||||
// 开始截图
|
||||
function screenshot() {
|
||||
qmlapp.imageManager.screenshot(screenshotEnd)
|
||||
}
|
||||
// 截图完毕
|
||||
function screenshotEnd(clipID) {
|
||||
popMainWindow()
|
||||
if(!clipID) {
|
||||
return
|
||||
}
|
||||
const configDict = configsComp.getValueDict()
|
||||
tabPage.callPy("scanImgID", clipID, configDict)
|
||||
qmlapp.tab.showTabPageObj(tabPage) // 切换标签页
|
||||
}
|
||||
|
||||
// 开始粘贴
|
||||
function paste() {
|
||||
popMainWindow()
|
||||
const res = qmlapp.imageManager.getPaste()
|
||||
if(res.error) {
|
||||
qmlapp.popup.simple(qsTr("获取剪贴板异常"), res.error)
|
||||
return
|
||||
}
|
||||
if(res.text) {
|
||||
qmlapp.popup.simple(qsTr("剪贴板中为文本"), res.text)
|
||||
return
|
||||
}
|
||||
qmlapp.tab.showTabPageObj(tabPage) // 切换标签页
|
||||
if(res.imgID) { // 图片
|
||||
imageText.showImgID(res.imgID)
|
||||
const configDict = configsComp.getValueDict()
|
||||
tabPage.callPy("scanImgID", res.imgID, configDict)
|
||||
}
|
||||
else if(res.paths) { // 地址
|
||||
scanPaths(res.paths)
|
||||
}
|
||||
}
|
||||
|
||||
// 异步加载一批图像路径
|
||||
function scanPaths(paths) {
|
||||
qmlapp.asynFilesLoader.run(paths,"image",false,onScanPaths)
|
||||
}
|
||||
// 完毕后,对合法路径进行扫码
|
||||
function onScanPaths(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("scanPaths", paths, configDict)
|
||||
}
|
||||
|
||||
// 弹出主窗口
|
||||
function popMainWindow() {
|
||||
// 若主窗口已经可见,则不处理
|
||||
if(qmlapp.mainWin.getVisibility())
|
||||
return
|
||||
// 等一回合再弹,防止与收回截图窗口相冲突
|
||||
if(configsComp.getValue("action.popMainWindow")) {
|
||||
Qt.callLater(()=>{
|
||||
qmlapp.mainWin.loadGeometry(false)
|
||||
qmlapp.mainWin.setVisibility(true)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 生成二维码
|
||||
function writeBarcode(text) {
|
||||
if(!text || text.length===0)
|
||||
return
|
||||
setRunning(true)
|
||||
const configDict = configsComp.getValueDict()
|
||||
const format = configDict["writeBarcode.format"]
|
||||
const w = configDict["writeBarcode.width"]
|
||||
const h = configDict["writeBarcode.height"]
|
||||
const quiet_zone = configDict["writeBarcode.quiet_zone"]
|
||||
const ec_level = configDict["writeBarcode.ec_level"]
|
||||
const imgID = tabPage.callPy("writeBarcode", text, format, w, h, quiet_zone, ec_level)
|
||||
setRunning(false)
|
||||
if(imgID.startsWith("[Error]") || imgID.startsWith("[Warning]")) {
|
||||
if(imgID.startsWith("[Error] [")) {
|
||||
const msg = qsTr("参数有误,或输入内容不合规定。请参照报错指示修改:") +"\n"+ imgID
|
||||
qmlapp.popup.message(qsTr("生成二维码失败"), msg, "error")
|
||||
}
|
||||
else {
|
||||
qmlapp.popup.message(qsTr("生成二维码失败"), imgID, "error")
|
||||
}
|
||||
return
|
||||
}
|
||||
imageText.showImgID(imgID)
|
||||
}
|
||||
// 立刻重新生成二维码图片
|
||||
function reWriteBarcode() {
|
||||
writeBarcode(writeEdit.text)
|
||||
}
|
||||
|
||||
|
||||
// ========================= 【python调用qml】 =========================
|
||||
|
||||
// 获取一个扫码的返回值
|
||||
function onQRCodeGet(res, imgID="", imgPath="") {
|
||||
// 添加到结果
|
||||
if(imgID) // 图片类型
|
||||
imageText.showImgID(imgID)
|
||||
else if(imgPath) // 地址类型
|
||||
imageText.showPath(imgPath)
|
||||
// 路径转文件名
|
||||
const parts = imgPath.split("/")
|
||||
res.title = parts[parts.length - 1]
|
||||
imageText.showTextBoxes(res)
|
||||
const resText = resultsTableView.addOcrResult(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()
|
||||
}
|
||||
|
||||
// 任务完成后发送通知
|
||||
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)
|
||||
}
|
||||
|
||||
// 设置运行状态
|
||||
function setRunning(flag) {
|
||||
running = flag
|
||||
}
|
||||
|
||||
// ========================= 【事件管理】 =========================
|
||||
|
||||
Component.onCompleted: {
|
||||
eventSub() // 订阅事件
|
||||
}
|
||||
// 关闭页面
|
||||
function closePage() {
|
||||
eventUnsub()
|
||||
delPage()
|
||||
}
|
||||
// 订阅事件
|
||||
function eventSub() {
|
||||
qmlapp.pubSub.subscribeGroup("<<qrcode_screenshot>>", this, "screenshot", ctrlKey)
|
||||
qmlapp.pubSub.subscribeGroup("<<qrcode_paste>>", this, "paste", ctrlKey)
|
||||
qmlapp.systemTray.addMenuItem("<<qrcode_screenshot>>", qsTr("扫描二维码"), screenshot)
|
||||
}
|
||||
// 取消订阅事件
|
||||
function eventUnsub() {
|
||||
qmlapp.systemTray.delMenuItem("<<qrcode_screenshot>>")
|
||||
qmlapp.pubSub.unsubscribeGroup(ctrlKey)
|
||||
}
|
||||
|
||||
// ========================= 【布局】 =========================
|
||||
property bool running: false
|
||||
// 主区域:可切换双栏面板
|
||||
DoubleSwitchableLayout {
|
||||
id: doubleLayout
|
||||
saveKey: "QRCode_1"
|
||||
anchors.fill: parent
|
||||
|
||||
// 面板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
|
||||
height: size_.line * 1.5
|
||||
// 靠右
|
||||
Row {
|
||||
id: dLeftTopR
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
spacing: size_.smallSpacing
|
||||
|
||||
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("粘贴图片"),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
// 图片预览区域
|
||||
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 {
|
||||
text: "Running"
|
||||
visible: running
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
// 提示
|
||||
DefaultTips {
|
||||
visibleFlag: running
|
||||
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
|
||||
}
|
||||
|
||||
//生成面板
|
||||
Item {
|
||||
id: writePanel
|
||||
anchors.fill: parent
|
||||
visible: false
|
||||
Item {
|
||||
id: writePanelTop
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
height: size_.line * 1.5
|
||||
|
||||
Button_ {
|
||||
id: writePanelBtn1
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
text_: qsTr("设置")
|
||||
onClicked: {
|
||||
tabPanel.currentIndex = 0 // 转到设置面板
|
||||
configsComp.panelComponent.scrollToGroup(3) // 滚动到生成设置
|
||||
}
|
||||
}
|
||||
Row {
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
CheckButton {
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
text_: qsTr("自动刷新")
|
||||
toolTip: qsTr("修改文字后,自动生成二维码/条形码")
|
||||
visible: writePanelTop.width > writePanelBtn1.width+writePanelBtn2.width+this.width
|
||||
textColor_: theme.textColor
|
||||
checked: writeEdit.autoUpdate
|
||||
enabledAnime: true
|
||||
onCheckedChanged: {
|
||||
writeEdit.autoUpdate = checked
|
||||
}
|
||||
}
|
||||
Button_ {
|
||||
id: writePanelBtn2
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
text_: qsTr("刷新")
|
||||
toolTip: qsTr("生成二维码/条形码")
|
||||
onClicked: reWriteBarcode()
|
||||
}
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
anchors.top: writePanelTop.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.topMargin: size_.smallSpacing
|
||||
color: theme.bgColor
|
||||
border.width: 1
|
||||
border.color: theme.coverColor4
|
||||
TextEdit_ {
|
||||
id: writeEdit
|
||||
anchors.fill: parent
|
||||
anchors.margins: size_.spacing
|
||||
// 自动刷新
|
||||
property bool autoUpdate: true
|
||||
// 文字输入改变时,等待一段时间,自动刷新
|
||||
Timer {
|
||||
id: writeEditTimer
|
||||
interval: 500 // 0.5 秒
|
||||
repeat: false
|
||||
onTriggered: reWriteBarcode()
|
||||
}
|
||||
onTextChanged: {
|
||||
if(autoUpdate) // 重启计时器
|
||||
writeEditTimer.restart()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tabsModel: [
|
||||
{
|
||||
"key": "configs",
|
||||
"title": qsTr("设置"),
|
||||
"component": configsComp.panelComponent,
|
||||
},
|
||||
{
|
||||
"key": "ocrResult",
|
||||
"title": qsTr("记录"),
|
||||
"component": resultsTableView,
|
||||
},
|
||||
{
|
||||
"key": "writePanel",
|
||||
"title": qsTr("生成"),
|
||||
"component": writePanel,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 鼠标拖入图片
|
||||
DropArea_ {
|
||||
anchors.fill: parent
|
||||
callback: tabPage.scanPaths
|
||||
}
|
||||
}
|
||||
181
UmiOCR-data/qt_res/qml/TabPages/QRCode/QRCodeConfigs.qml
Normal file
181
UmiOCR-data/qt_res/qml/TabPages/QRCode/QRCodeConfigs.qml
Normal file
@@ -0,0 +1,181 @@
|
||||
// ==============================================
|
||||
// =============== 截图OCR的配置项 ===============
|
||||
// ==============================================
|
||||
|
||||
import QtQuick 2.15
|
||||
import "../../Configs"
|
||||
|
||||
Configs {
|
||||
category_: "QRCode"
|
||||
|
||||
signal reBarcode() // 重新生成二维码的信号
|
||||
|
||||
configDict: {
|
||||
|
||||
"hotkey": {
|
||||
"title": qsTr("快捷键"),
|
||||
"type": "group",
|
||||
|
||||
"screenshot": {
|
||||
"title": qsTr("屏幕截图"),
|
||||
"type": "hotkey",
|
||||
"default": "", // 默认热键
|
||||
"eventTitle": "<<qrcode_screenshot>>", // 触发事件标题
|
||||
},
|
||||
"paste": {
|
||||
"title": qsTr("粘贴图片"),
|
||||
"type": "hotkey",
|
||||
"default": "",
|
||||
"eventTitle": "<<qrcode_paste>>",
|
||||
},
|
||||
},
|
||||
|
||||
"preprocessing": {
|
||||
"title": qsTr("预处理(一般无需改动)"),
|
||||
"type": "group",
|
||||
"advanced": true,
|
||||
|
||||
"median_filter_size": {
|
||||
"title": qsTr("中值滤波"),
|
||||
"toolTip": qsTr("对图像进行平滑处理\n>0 时生效。可填1~9的奇数(不允许偶数)"),
|
||||
"isInt": true,
|
||||
"default": 0,
|
||||
"max": 10,
|
||||
"min": 0,
|
||||
},
|
||||
"sharpness_factor": {
|
||||
"title": qsTr("锐度增强"),
|
||||
"toolTip": qsTr("增加图像的锐度\n>0.1 时生效。可填0.1~10的小数"),
|
||||
"isInt": false,
|
||||
"default": 0,
|
||||
"max": 10,
|
||||
"min": 0,
|
||||
},
|
||||
"contrast_factor": {
|
||||
"title": qsTr("对比度增强"),
|
||||
"toolTip": qsTr("增加图像的对比度\n>0.1 时生效。可填0.1~10的小数"),
|
||||
"isInt": false,
|
||||
"default": 0,
|
||||
"max": 10,
|
||||
"min": 0,
|
||||
},
|
||||
"grayscale": {
|
||||
"title": qsTr("转为灰度"),
|
||||
"toolTip": qsTr("将图像像素转为灰度"),
|
||||
"default": false,
|
||||
},
|
||||
"threshold": {
|
||||
"title": qsTr("二值化"),
|
||||
"toolTip": qsTr("将图像像素转为纯黑和纯白\n启用了灰度,且二值化 >-1 时生效。可填0~255的整数"),
|
||||
"isInt": true,
|
||||
"default": -1,
|
||||
"max": 255,
|
||||
"min": -1,
|
||||
},
|
||||
},
|
||||
|
||||
"action": {
|
||||
"title": qsTr("扫码后的操作"),
|
||||
"type": "group",
|
||||
|
||||
"copy": {
|
||||
"title": qsTr("复制结果"),
|
||||
"default": false,
|
||||
},
|
||||
"popMainWindow": {
|
||||
"title": qsTr("弹出主窗口"),
|
||||
"toolTip": qsTr("识图后,如果主窗口最小化或处于后台,则弹到前台"),
|
||||
"default": true,
|
||||
},
|
||||
},
|
||||
|
||||
"writeBarcode": {
|
||||
"title": qsTr("生成二维码/条形码"),
|
||||
"type": "group",
|
||||
|
||||
"format": {
|
||||
"title": qsTr("类型"),
|
||||
"toolTip": qsTr("默认二维码:")+"QRCode",
|
||||
"default": "QRCode",
|
||||
"optionsList": [
|
||||
["Aztec", "Aztec"],
|
||||
["Codabar", "Codabar"],
|
||||
["Code128", "Code128"],
|
||||
["Code39", "Code39"],
|
||||
["Code93", "Code93"],
|
||||
["DataBar", "DataBar"],
|
||||
["DataBarExpanded", "DataBarExpanded"],
|
||||
["DataMatrix", "DataMatrix"],
|
||||
["EAN13", "EAN13"],
|
||||
["EAN8", "EAN8"],
|
||||
["ITF", "ITF"],
|
||||
["LinearCodes", "LinearCodes"],
|
||||
["MatrixCodes", "MatrixCodes"],
|
||||
["MaxiCode", "MaxiCode"],
|
||||
["MicroQRCode", "MicroQRCode"],
|
||||
["PDF417", "PDF417"],
|
||||
["QRCode", "QRCode"],
|
||||
["UPCA", "UPCA"],
|
||||
["UPCE", "UPCE"],
|
||||
],
|
||||
"onChanged": (newFlag, oldFlag)=>{
|
||||
if(oldFlag !== undefined) reBarcode()
|
||||
},
|
||||
},
|
||||
"width": {
|
||||
"title": qsTr("宽度"),
|
||||
"toolTip": qsTr("填0:自动选择"),
|
||||
"isInt": true,
|
||||
"default": 256,
|
||||
"min": 0,
|
||||
"unit": qsTr("像素"),
|
||||
"onChanged": (newFlag, oldFlag)=>{
|
||||
if(oldFlag !== undefined) reBarcode()
|
||||
},
|
||||
},
|
||||
"height": {
|
||||
"title": qsTr("高度"),
|
||||
"toolTip": qsTr("填0:自动选择"),
|
||||
"isInt": true,
|
||||
"default": 256,
|
||||
"min": 0,
|
||||
"unit": qsTr("像素"),
|
||||
"onChanged": (newFlag, oldFlag)=>{
|
||||
if(oldFlag !== undefined) reBarcode()
|
||||
},
|
||||
},
|
||||
"quiet_zone": {
|
||||
"title": qsTr("边缘空白"),
|
||||
"toolTip": qsTr("填-1:自动选择"),
|
||||
"isInt": true,
|
||||
"default": -1,
|
||||
"min": -1,
|
||||
"unit": qsTr("像素"),
|
||||
"onChanged": (newFlag, oldFlag)=>{
|
||||
if(oldFlag !== undefined) reBarcode()
|
||||
},
|
||||
},
|
||||
"ec_level": {
|
||||
"title": qsTr("纠错等级"),
|
||||
"toolTip": qsTr("仅适用于:")+"Aztec, PDF417, QRCode",
|
||||
"optionsList": [
|
||||
[-1, qsTr("自动")],
|
||||
[1, "7%"],
|
||||
[0, "15%"],
|
||||
[3, "25%"],
|
||||
[2, "30%"],
|
||||
],
|
||||
"onChanged": (newFlag, oldFlag)=>{
|
||||
if(oldFlag !== undefined) reBarcode()
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
"other": {
|
||||
"title": qsTr("其它"),
|
||||
"type": "group",
|
||||
|
||||
"simpleNotificationType": qmlapp.globalConfigs.utilsDicts.getSimpleNotificationType()
|
||||
},
|
||||
}
|
||||
}
|
||||
484
UmiOCR-data/qt_res/qml/TabPages/ScreenshotOCR/ScreenshotOCR.qml
Normal file
484
UmiOCR-data/qt_res/qml/TabPages/ScreenshotOCR/ScreenshotOCR.qml
Normal file
@@ -0,0 +1,484 @@
|
||||
// ==============================================
|
||||
// =============== 功能页:截图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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
// ==============================================
|
||||
// =============== 截图OCR的配置项 ===============
|
||||
// ==============================================
|
||||
|
||||
import QtQuick 2.15
|
||||
import "../../Configs"
|
||||
|
||||
Configs {
|
||||
category_: "ScreenshotOCR"
|
||||
|
||||
configDict: {
|
||||
// OCR参数
|
||||
"ocr": qmlapp.globalConfigs.ocrManager.deploy(this, "ocr"),
|
||||
|
||||
// 后处理
|
||||
"tbpu": {
|
||||
"title": qsTr("OCR文本后处理"),
|
||||
"type": "group",
|
||||
|
||||
"parser": qmlapp.globalConfigs.utilsDicts.getTbpuParser(),
|
||||
},
|
||||
|
||||
"hotkey": {
|
||||
"title": qsTr("快捷键"),
|
||||
"type": "group",
|
||||
|
||||
"screenshot": {
|
||||
"title": qsTr("屏幕截图"),
|
||||
"type": "hotkey",
|
||||
// 默认热键
|
||||
"default": UmiAbout.app.system==="win32" ?
|
||||
"win+alt+c" : "alt+c",
|
||||
"eventTitle": "<<screenshot>>", // 触发事件标题
|
||||
},
|
||||
"paste": {
|
||||
"title": qsTr("粘贴图片"),
|
||||
"type": "hotkey",
|
||||
"default": UmiAbout.app.system==="win32" ?
|
||||
"win+alt+v" : "alt+v",
|
||||
"eventTitle": "<<paste>>",
|
||||
},
|
||||
"reScreenshot": {
|
||||
"title": qsTr("重复截图"),
|
||||
"toolTip": qsTr("重新截取上一次截图的范围"),
|
||||
"type": "hotkey",
|
||||
"default": "",
|
||||
"eventTitle": "<<reScreenshot>>",
|
||||
},
|
||||
},
|
||||
|
||||
"action": {
|
||||
"title": qsTr("识图后的操作"),
|
||||
"type": "group",
|
||||
|
||||
"copy": {
|
||||
"title": qsTr("复制结果"),
|
||||
"default": false,
|
||||
},
|
||||
"popMainWindow": {
|
||||
"title": qsTr("弹出主窗口"),
|
||||
"toolTip": qsTr("识图后,如果主窗口最小化或处于后台,则弹到前台"),
|
||||
"default": true,
|
||||
},
|
||||
},
|
||||
|
||||
"other": {
|
||||
"title": qsTr("其它"),
|
||||
"type": "group",
|
||||
|
||||
"simpleNotificationType": qmlapp.globalConfigs.utilsDicts.getSimpleNotificationType()
|
||||
},
|
||||
}
|
||||
}
|
||||
58
UmiOCR-data/qt_res/qml/TabPages/TabPage.qml
Normal file
58
UmiOCR-data/qt_res/qml/TabPages/TabPage.qml
Normal file
@@ -0,0 +1,58 @@
|
||||
// ===========================================
|
||||
// =============== 标签页的基类 ===============
|
||||
// ===========================================
|
||||
|
||||
import QtQuick 2.15
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
|
||||
property string ctrlKey // Python连接器的key
|
||||
property var connector // Python连接器的引用
|
||||
property var configsComp: undefined // 该页面的配置组件
|
||||
signal showPage // 页面展示时的信号,用 onShowPage 监听
|
||||
|
||||
// ========================= 【页面控制】 =========================
|
||||
|
||||
// 关闭页面。子类重载后可先向用户弹窗询问,再调用 delPage()
|
||||
function closePage() {
|
||||
delPage()
|
||||
}
|
||||
// 销毁页面
|
||||
function delPage() {
|
||||
const index = qmlapp.tab.getTabPageIndex(this) // 获取当前页下标
|
||||
qmlapp.tab.delTabPage(index) // 销毁页面
|
||||
}
|
||||
// 调用Python连接器的func方法名,传入任意个数的args作为参数
|
||||
function callPy(funcName, ...args) {
|
||||
return connector.callPy(ctrlKey, funcName, args)
|
||||
}
|
||||
// 获取配置项值字典
|
||||
function getValueDict() {
|
||||
// 控制组件存在,且有方法getValueDict
|
||||
if (typeof configsComp === "object" && typeof configsComp.getValueDict === "function") {
|
||||
return configsComp.getValueDict()
|
||||
}
|
||||
console.error("返回空配置项字典")
|
||||
return {}
|
||||
}
|
||||
// 获取原始值字典
|
||||
function getOriginDict() {
|
||||
// 控制组件存在,且有方法getValueDict
|
||||
if (typeof configsComp === "object" && typeof configsComp.getValueDict === "function") {
|
||||
return configsComp.getOriginDict()
|
||||
}
|
||||
console.error("返回空原始值字典")
|
||||
return {}
|
||||
}
|
||||
// 设置配置项值
|
||||
function setValue(key, val) {
|
||||
// 控制组件存在,且有方法getValueDict
|
||||
if (typeof configsComp === "object" && typeof configsComp.setValue === "function") {
|
||||
configsComp.setValue(key, val, true)
|
||||
return
|
||||
}
|
||||
console.error("设置配置项失败", key, val)
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user