diff --git a/.csharpierrc.json b/.csharpierrc.json new file mode 100644 index 00000000..e7740ff1 --- /dev/null +++ b/.csharpierrc.json @@ -0,0 +1,3 @@ +{ + "printWidth": 140 +} \ No newline at end of file diff --git a/Attachments/iconfont/demo.css b/Attachments/iconfont/demo.css new file mode 100644 index 00000000..a67054a0 --- /dev/null +++ b/Attachments/iconfont/demo.css @@ -0,0 +1,539 @@ +/* Logo 字体 */ +@font-face { + font-family: "iconfont logo"; + src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834'); + src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix') format('embedded-opentype'), + url('https://at.alicdn.com/t/font_985780_km7mi63cihi.woff?t=1545807318834') format('woff'), + url('https://at.alicdn.com/t/font_985780_km7mi63cihi.ttf?t=1545807318834') format('truetype'), + url('https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont') format('svg'); +} + +.logo { + font-family: "iconfont logo"; + font-size: 160px; + font-style: normal; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +/* tabs */ +.nav-tabs { + position: relative; +} + +.nav-tabs .nav-more { + position: absolute; + right: 0; + bottom: 0; + height: 42px; + line-height: 42px; + color: #666; +} + +#tabs { + border-bottom: 1px solid #eee; +} + +#tabs li { + cursor: pointer; + width: 100px; + height: 40px; + line-height: 40px; + text-align: center; + font-size: 16px; + border-bottom: 2px solid transparent; + position: relative; + z-index: 1; + margin-bottom: -1px; + color: #666; +} + + +#tabs .active { + border-bottom-color: #f00; + color: #222; +} + +.tab-container .content { + display: none; +} + +/* 页面布局 */ +.main { + padding: 30px 100px; + width: 960px; + margin: 0 auto; +} + +.main .logo { + color: #333; + text-align: left; + margin-bottom: 30px; + line-height: 1; + height: 110px; + margin-top: -50px; + overflow: hidden; + *zoom: 1; +} + +.main .logo a { + font-size: 160px; + color: #333; +} + +.helps { + margin-top: 40px; +} + +.helps pre { + padding: 20px; + margin: 10px 0; + border: solid 1px #e7e1cd; + background-color: #fffdef; + overflow: auto; +} + +.icon_lists { + width: 100% !important; + overflow: hidden; + *zoom: 1; +} + +.icon_lists li { + width: 100px; + margin-bottom: 10px; + margin-right: 20px; + text-align: center; + list-style: none !important; + cursor: default; +} + +.icon_lists li .code-name { + line-height: 1.2; +} + +.icon_lists .icon { + display: block; + height: 100px; + line-height: 100px; + font-size: 42px; + margin: 10px auto; + color: #333; + -webkit-transition: font-size 0.25s linear, width 0.25s linear; + -moz-transition: font-size 0.25s linear, width 0.25s linear; + transition: font-size 0.25s linear, width 0.25s linear; +} + +.icon_lists .icon:hover { + font-size: 100px; +} + +.icon_lists .svg-icon { + /* 通过设置 font-size 来改变图标大小 */ + width: 1em; + /* 图标和文字相邻时,垂直对齐 */ + vertical-align: -0.15em; + /* 通过设置 color 来改变 SVG 的颜色/fill */ + fill: currentColor; + /* path 和 stroke 溢出 viewBox 部分在 IE 下会显示 + normalize.css 中也包含这行 */ + overflow: hidden; +} + +.icon_lists li .name, +.icon_lists li .code-name { + color: #666; +} + +/* markdown 样式 */ +.markdown { + color: #666; + font-size: 14px; + line-height: 1.8; +} + +.highlight { + line-height: 1.5; +} + +.markdown img { + vertical-align: middle; + max-width: 100%; +} + +.markdown h1 { + color: #404040; + font-weight: 500; + line-height: 40px; + margin-bottom: 24px; +} + +.markdown h2, +.markdown h3, +.markdown h4, +.markdown h5, +.markdown h6 { + color: #404040; + margin: 1.6em 0 0.6em 0; + font-weight: 500; + clear: both; +} + +.markdown h1 { + font-size: 28px; +} + +.markdown h2 { + font-size: 22px; +} + +.markdown h3 { + font-size: 16px; +} + +.markdown h4 { + font-size: 14px; +} + +.markdown h5 { + font-size: 12px; +} + +.markdown h6 { + font-size: 12px; +} + +.markdown hr { + height: 1px; + border: 0; + background: #e9e9e9; + margin: 16px 0; + clear: both; +} + +.markdown p { + margin: 1em 0; +} + +.markdown>p, +.markdown>blockquote, +.markdown>.highlight, +.markdown>ol, +.markdown>ul { + width: 80%; +} + +.markdown ul>li { + list-style: circle; +} + +.markdown>ul li, +.markdown blockquote ul>li { + margin-left: 20px; + padding-left: 4px; +} + +.markdown>ul li p, +.markdown>ol li p { + margin: 0.6em 0; +} + +.markdown ol>li { + list-style: decimal; +} + +.markdown>ol li, +.markdown blockquote ol>li { + margin-left: 20px; + padding-left: 4px; +} + +.markdown code { + margin: 0 3px; + padding: 0 5px; + background: #eee; + border-radius: 3px; +} + +.markdown strong, +.markdown b { + font-weight: 600; +} + +.markdown>table { + border-collapse: collapse; + border-spacing: 0px; + empty-cells: show; + border: 1px solid #e9e9e9; + width: 95%; + margin-bottom: 24px; +} + +.markdown>table th { + white-space: nowrap; + color: #333; + font-weight: 600; +} + +.markdown>table th, +.markdown>table td { + border: 1px solid #e9e9e9; + padding: 8px 16px; + text-align: left; +} + +.markdown>table th { + background: #F7F7F7; +} + +.markdown blockquote { + font-size: 90%; + color: #999; + border-left: 4px solid #e9e9e9; + padding-left: 0.8em; + margin: 1em 0; +} + +.markdown blockquote p { + margin: 0; +} + +.markdown .anchor { + opacity: 0; + transition: opacity 0.3s ease; + margin-left: 8px; +} + +.markdown .waiting { + color: #ccc; +} + +.markdown h1:hover .anchor, +.markdown h2:hover .anchor, +.markdown h3:hover .anchor, +.markdown h4:hover .anchor, +.markdown h5:hover .anchor, +.markdown h6:hover .anchor { + opacity: 1; + display: inline-block; +} + +.markdown>br, +.markdown>p>br { + clear: both; +} + + +.hljs { + display: block; + background: white; + padding: 0.5em; + color: #333333; + overflow-x: auto; +} + +.hljs-comment, +.hljs-meta { + color: #969896; +} + +.hljs-string, +.hljs-variable, +.hljs-template-variable, +.hljs-strong, +.hljs-emphasis, +.hljs-quote { + color: #df5000; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-type { + color: #a71d5d; +} + +.hljs-literal, +.hljs-symbol, +.hljs-bullet, +.hljs-attribute { + color: #0086b3; +} + +.hljs-section, +.hljs-name { + color: #63a35c; +} + +.hljs-tag { + color: #333333; +} + +.hljs-title, +.hljs-attr, +.hljs-selector-id, +.hljs-selector-class, +.hljs-selector-attr, +.hljs-selector-pseudo { + color: #795da3; +} + +.hljs-addition { + color: #55a532; + background-color: #eaffea; +} + +.hljs-deletion { + color: #bd2c00; + background-color: #ffecec; +} + +.hljs-link { + text-decoration: underline; +} + +/* 代码高亮 */ +/* PrismJS 1.15.0 +https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */ +/** + * prism.js default theme for JavaScript, CSS and HTML + * Based on dabblet (http://dabblet.com) + * @author Lea Verou + */ +code[class*="language-"], +pre[class*="language-"] { + color: black; + background: none; + text-shadow: 0 1px white; + font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + word-wrap: normal; + line-height: 1.5; + + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; +} + +pre[class*="language-"]::-moz-selection, +pre[class*="language-"] ::-moz-selection, +code[class*="language-"]::-moz-selection, +code[class*="language-"] ::-moz-selection { + text-shadow: none; + background: #b3d4fc; +} + +pre[class*="language-"]::selection, +pre[class*="language-"] ::selection, +code[class*="language-"]::selection, +code[class*="language-"] ::selection { + text-shadow: none; + background: #b3d4fc; +} + +@media print { + + code[class*="language-"], + pre[class*="language-"] { + text-shadow: none; + } +} + +/* Code blocks */ +pre[class*="language-"] { + padding: 1em; + margin: .5em 0; + overflow: auto; +} + +:not(pre)>code[class*="language-"], +pre[class*="language-"] { + background: #f5f2f0; +} + +/* Inline code */ +:not(pre)>code[class*="language-"] { + padding: .1em; + border-radius: .3em; + white-space: normal; +} + +.token.comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: slategray; +} + +.token.punctuation { + color: #999; +} + +.namespace { + opacity: .7; +} + +.token.property, +.token.tag, +.token.boolean, +.token.number, +.token.constant, +.token.symbol, +.token.deleted { + color: #905; +} + +.token.selector, +.token.attr-name, +.token.string, +.token.char, +.token.builtin, +.token.inserted { + color: #690; +} + +.token.operator, +.token.entity, +.token.url, +.language-css .token.string, +.style .token.string { + color: #9a6e3a; + background: hsla(0, 0%, 100%, .5); +} + +.token.atrule, +.token.attr-value, +.token.keyword { + color: #07a; +} + +.token.function, +.token.class-name { + color: #DD4A68; +} + +.token.regex, +.token.important, +.token.variable { + color: #e90; +} + +.token.important, +.token.bold { + font-weight: bold; +} + +.token.italic { + font-style: italic; +} + +.token.entity { + cursor: help; +} diff --git a/Attachments/iconfont/demo_index.html b/Attachments/iconfont/demo_index.html new file mode 100644 index 00000000..7331559e --- /dev/null +++ b/Attachments/iconfont/demo_index.html @@ -0,0 +1,1292 @@ + + + + + iconfont Demo + + + + + + + + + + + + + +
+

+ + +

+ +
+
+
    + +
  • + +
    二维码
    +
    
    +
  • + +
  • + +
    二维码
    +
    
    +
  • + +
  • + +
    二维码
    +
    
    +
  • + +
  • + +
    鼠标划词
    +
    
    +
  • + +
  • + +
    表单文本框
    +
    
    +
  • + +
  • + +
    划词翻译
    +
    
    +
  • + +
  • + +
    鼠标
    +
    
    +
  • + +
  • + +
    清理
    +
    
    +
  • + +
  • + +
    刷新
    +
    
    +
  • + +
  • + +
    backspace-outline
    +
    
    +
  • + +
  • + +
    space
    +
    
    +
  • + +
  • + +
    3
    +
    
    +
  • + +
  • + +
    文本换行
    +
    
    +
  • + +
  • + +
    文字
    +
    
    +
  • + +
  • + +
    系统首选项
    +
    
    +
  • + +
  • + +
    对勾
    +
    
    +
  • + +
  • + +
    禁用
    +
    
    +
  • + +
  • + +
    交换
    +
    
    +
  • + +
  • + +
    icon-基础设置
    +
    
    +
  • + +
  • + +
    可拖拽
    +
    
    +
  • + +
  • + +
    退出
    +
    
    +
  • + +
  • + +
    界面
    +
    
    +
  • + +
  • + +
    符号-单行输入框
    +
    
    +
  • + +
  • + +
    表单组件-输入框
    +
    
    +
  • + +
  • + +
    文本识别
    +
    
    +
  • + +
  • + +
    多行输入
    +
    
    +
  • + +
  • + +
    14C截图
    +
    
    +
  • + +
  • + +
    文字识别
    +
    
    +
  • + +
  • + +
    Maximize-3
    +
    
    +
  • + +
  • + +
    删除
    +
    
    +
  • + +
  • + +
    保存
    +
    
    +
  • + +
  • + +
    历史记录
    +
    
    +
  • + +
  • + +
    常用示例
    +
    
    +
  • + +
  • + +
    服务器
    +
    
    +
  • + +
  • + +
    关于
    +
    
    +
  • + +
  • + +
    收藏
    +
    
    +
  • + +
  • + +
    关闭26
    +
    
    +
  • + +
  • + +
    最大化
    +
    
    +
  • + +
  • + +
    最小化
    +
    
    +
  • + +
  • + +
    图钉
    +
    
    +
  • + +
  • + +
    固定,图钉
    +
    
    +
  • + +
  • + +
    复制
    +
    
    +
  • + +
  • + +
    下拉
    +
    
    +
  • + +
  • + +
    调整,半圆,亮度
    +
    
    +
  • + +
  • + +
    snake
    +
    
    +
  • + +
  • + +
    copy_large_hump
    +
    
    +
  • + +
  • + +
    copy_c
    +
    
    +
  • + +
  • + +
    喇叭
    +
    
    +
  • + +
+
+

Unicode 引用

+
+ +

Unicode 是字体在网页端最原始的应用方式,特点是:

+
    +
  • 支持按字体的方式去动态调整图标大小,颜色等等。
  • +
  • 默认情况下不支持多色,直接添加多色图标会自动去色。
  • +
+
+

注意:新版 iconfont 支持两种方式引用多色图标:SVG symbol 引用方式和彩色字体图标模式。(使用彩色字体图标需要在「编辑项目」中开启「彩色」选项后并重新生成。)

+
+

Unicode 使用步骤如下:

+

第一步:拷贝项目下面生成的 @font-face

+
@font-face {
+  font-family: 'iconfont';
+  src: url('iconfont.woff2?t=1704174060960') format('woff2'),
+       url('iconfont.woff?t=1704174060960') format('woff'),
+       url('iconfont.ttf?t=1704174060960') format('truetype');
+}
+
+

第二步:定义使用 iconfont 的样式

+
.iconfont {
+  font-family: "iconfont" !important;
+  font-size: 16px;
+  font-style: normal;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+

第三步:挑选相应图标并获取字体编码,应用于页面

+
+<span class="iconfont">&#x33;</span>
+
+
+

"iconfont" 是你项目下的 font-family。可以通过编辑项目查看,默认是 "iconfont"。

+
+
+
+
+
    + +
  • + +
    + 二维码 +
    +
    .icon-erweima +
    +
  • + +
  • + +
    + 二维码 +
    +
    .icon-erweima1 +
    +
  • + +
  • + +
    + 二维码 +
    +
    .icon-erweima3 +
    +
  • + +
  • + +
    + 鼠标划词 +
    +
    .icon-a-biaodanzujian-shurukuang-svg6 +
    +
  • + +
  • + +
    + 表单文本框 +
    +
    .icon-textBox +
    +
  • + +
  • + +
    + 划词翻译 +
    +
    .icon-a-biaodanzujian-shurukuang-svg5 +
    +
  • + +
  • + +
    + 鼠标 +
    +
    .icon-shubiao +
    +
  • + +
  • + +
    + 清理 +
    +
    .icon-qingli +
    +
  • + +
  • + +
    + 刷新 +
    +
    .icon-refresh-1-copy +
    +
  • + +
  • + +
    + backspace-outline +
    +
    .icon-backspace-outline +
    +
  • + +
  • + +
    + space +
    +
    .icon-space +
    +
  • + +
  • + +
    + 3 +
    +
    .icon-icon-test +
    +
  • + +
  • + +
    + 文本换行 +
    +
    .icon-wenbenhuanhang +
    +
  • + +
  • + +
    + 文字 +
    +
    .icon-wenzi +
    +
  • + +
  • + +
    + 系统首选项 +
    +
    .icon-xitongshouxuanxiang +
    +
  • + +
  • + +
    + 对勾 +
    +
    .icon-duigoux +
    +
  • + +
  • + +
    + 禁用 +
    +
    .icon-jinyong +
    +
  • + +
  • + +
    + 交换 +
    +
    .icon-jiaohuan +
    +
  • + +
  • + +
    + icon-基础设置 +
    +
    .icon-icon-jichushezhi +
    +
  • + +
  • + +
    + 可拖拽 +
    +
    .icon-ketuozhuai +
    +
  • + +
  • + +
    + 退出 +
    +
    .icon-tuichu +
    +
  • + +
  • + +
    + 界面 +
    +
    .icon-jiemian +
    +
  • + +
  • + +
    + 符号-单行输入框 +
    +
    .icon-danhangshurukuang +
    +
  • + +
  • + +
    + 表单组件-输入框 +
    +
    .icon-biaodanzujian-shurukuang +
    +
  • + +
  • + +
    + 文本识别 +
    +
    .icon-wenbenshibie +
    +
  • + +
  • + +
    + 多行输入 +
    +
    .icon-duohangshuru +
    +
  • + +
  • + +
    + 14C截图 +
    +
    .icon-a-14Cjietu +
    +
  • + +
  • + +
    + 文字识别 +
    +
    .icon-wenzishibie +
    +
  • + +
  • + +
    + Maximize-3 +
    +
    .icon-3zuidahua-3 +
    +
  • + +
  • + +
    + 删除 +
    +
    .icon-shanchu +
    +
  • + +
  • + +
    + 保存 +
    +
    .icon-baocun +
    +
  • + +
  • + +
    + 历史记录 +
    +
    .icon-lishijilu +
    +
  • + +
  • + +
    + 常用示例 +
    +
    .icon-changyongshili +
    +
  • + +
  • + +
    + 服务器 +
    +
    .icon-fuwuqi +
    +
  • + +
  • + +
    + 关于 +
    +
    .icon-guanyu +
    +
  • + +
  • + +
    + 收藏 +
    +
    .icon-shoucang +
    +
  • + +
  • + +
    + 关闭26 +
    +
    .icon-guanbi +
    +
  • + +
  • + +
    + 最大化 +
    +
    .icon-zuidahua1 +
    +
  • + +
  • + +
    + 最小化 +
    +
    .icon-zuixiaohua +
    +
  • + +
  • + +
    + 图钉 +
    +
    .icon-tuding +
    +
  • + +
  • + +
    + 固定,图钉 +
    +
    .icon-relieve-full +
    +
  • + +
  • + +
    + 复制 +
    +
    .icon-fuzhi +
    +
  • + +
  • + +
    + 下拉 +
    +
    .icon-xiala +
    +
  • + +
  • + +
    + 调整,半圆,亮度 +
    +
    .icon-adjust-full +
    +
  • + +
  • + +
    + snake +
    +
    .icon-snake +
    +
  • + +
  • + +
    + copy_large_hump +
    +
    .icon-copy_large_hump +
    +
  • + +
  • + +
    + copy_c +
    +
    .icon-copy_c +
    +
  • + +
  • + +
    + 喇叭 +
    +
    .icon-laba1 +
    +
  • + +
+
+

font-class 引用

+
+ +

font-class 是 Unicode 使用方式的一种变种,主要是解决 Unicode 书写不直观,语意不明确的问题。

+

与 Unicode 使用方式相比,具有如下特点:

+
    +
  • 相比于 Unicode 语意明确,书写更直观。可以很容易分辨这个 icon 是什么。
  • +
  • 因为使用 class 来定义图标,所以当要替换图标时,只需要修改 class 里面的 Unicode 引用。
  • +
+

使用步骤如下:

+

第一步:引入项目下面生成的 fontclass 代码:

+
<link rel="stylesheet" href="./iconfont.css">
+
+

第二步:挑选相应图标并获取类名,应用于页面:

+
<span class="iconfont icon-xxx"></span>
+
+
+

" + iconfont" 是你项目下的 font-family。可以通过编辑项目查看,默认是 "iconfont"。

+
+
+
+
+
    + +
  • + +
    二维码
    +
    #icon-erweima
    +
  • + +
  • + +
    二维码
    +
    #icon-erweima1
    +
  • + +
  • + +
    二维码
    +
    #icon-erweima3
    +
  • + +
  • + +
    鼠标划词
    +
    #icon-a-biaodanzujian-shurukuang-svg6
    +
  • + +
  • + +
    表单文本框
    +
    #icon-textBox
    +
  • + +
  • + +
    划词翻译
    +
    #icon-a-biaodanzujian-shurukuang-svg5
    +
  • + +
  • + +
    鼠标
    +
    #icon-shubiao
    +
  • + +
  • + +
    清理
    +
    #icon-qingli
    +
  • + +
  • + +
    刷新
    +
    #icon-refresh-1-copy
    +
  • + +
  • + +
    backspace-outline
    +
    #icon-backspace-outline
    +
  • + +
  • + +
    space
    +
    #icon-space
    +
  • + +
  • + +
    3
    +
    #icon-icon-test
    +
  • + +
  • + +
    文本换行
    +
    #icon-wenbenhuanhang
    +
  • + +
  • + +
    文字
    +
    #icon-wenzi
    +
  • + +
  • + +
    系统首选项
    +
    #icon-xitongshouxuanxiang
    +
  • + +
  • + +
    对勾
    +
    #icon-duigoux
    +
  • + +
  • + +
    禁用
    +
    #icon-jinyong
    +
  • + +
  • + +
    交换
    +
    #icon-jiaohuan
    +
  • + +
  • + +
    icon-基础设置
    +
    #icon-icon-jichushezhi
    +
  • + +
  • + +
    可拖拽
    +
    #icon-ketuozhuai
    +
  • + +
  • + +
    退出
    +
    #icon-tuichu
    +
  • + +
  • + +
    界面
    +
    #icon-jiemian
    +
  • + +
  • + +
    符号-单行输入框
    +
    #icon-danhangshurukuang
    +
  • + +
  • + +
    表单组件-输入框
    +
    #icon-biaodanzujian-shurukuang
    +
  • + +
  • + +
    文本识别
    +
    #icon-wenbenshibie
    +
  • + +
  • + +
    多行输入
    +
    #icon-duohangshuru
    +
  • + +
  • + +
    14C截图
    +
    #icon-a-14Cjietu
    +
  • + +
  • + +
    文字识别
    +
    #icon-wenzishibie
    +
  • + +
  • + +
    Maximize-3
    +
    #icon-3zuidahua-3
    +
  • + +
  • + +
    删除
    +
    #icon-shanchu
    +
  • + +
  • + +
    保存
    +
    #icon-baocun
    +
  • + +
  • + +
    历史记录
    +
    #icon-lishijilu
    +
  • + +
  • + +
    常用示例
    +
    #icon-changyongshili
    +
  • + +
  • + +
    服务器
    +
    #icon-fuwuqi
    +
  • + +
  • + +
    关于
    +
    #icon-guanyu
    +
  • + +
  • + +
    收藏
    +
    #icon-shoucang
    +
  • + +
  • + +
    关闭26
    +
    #icon-guanbi
    +
  • + +
  • + +
    最大化
    +
    #icon-zuidahua1
    +
  • + +
  • + +
    最小化
    +
    #icon-zuixiaohua
    +
  • + +
  • + +
    图钉
    +
    #icon-tuding
    +
  • + +
  • + +
    固定,图钉
    +
    #icon-relieve-full
    +
  • + +
  • + +
    复制
    +
    #icon-fuzhi
    +
  • + +
  • + +
    下拉
    +
    #icon-xiala
    +
  • + +
  • + +
    调整,半圆,亮度
    +
    #icon-adjust-full
    +
  • + +
  • + +
    snake
    +
    #icon-snake
    +
  • + +
  • + +
    copy_large_hump
    +
    #icon-copy_large_hump
    +
  • + +
  • + +
    copy_c
    +
    #icon-copy_c
    +
  • + +
  • + +
    喇叭
    +
    #icon-laba1
    +
  • + +
+
+

Symbol 引用

+
+ +

这是一种全新的使用方式,应该说这才是未来的主流,也是平台目前推荐的用法。相关介绍可以参考这篇文章 + 这种用法其实是做了一个 SVG 的集合,与另外两种相比具有如下特点:

+
    +
  • 支持多色图标了,不再受单色限制。
  • +
  • 通过一些技巧,支持像字体那样,通过 font-size, color 来调整样式。
  • +
  • 兼容性较差,支持 IE9+,及现代浏览器。
  • +
  • 浏览器渲染 SVG 的性能一般,还不如 png。
  • +
+

使用步骤如下:

+

第一步:引入项目下面生成的 symbol 代码:

+
<script src="./iconfont.js"></script>
+
+

第二步:加入通用 CSS 代码(引入一次就行):

+
<style>
+.icon {
+  width: 1em;
+  height: 1em;
+  vertical-align: -0.15em;
+  fill: currentColor;
+  overflow: hidden;
+}
+</style>
+
+

第三步:挑选相应图标并获取类名,应用于页面:

+
<svg class="icon" aria-hidden="true">
+  <use xlink:href="#icon-xxx"></use>
+</svg>
+
+
+
+ +
+
+ + + diff --git a/Attachments/iconfont/iconfont.css b/Attachments/iconfont/iconfont.css new file mode 100644 index 00000000..673c85e0 --- /dev/null +++ b/Attachments/iconfont/iconfont.css @@ -0,0 +1,207 @@ +@font-face { + font-family: "iconfont"; /* Project id 4294789 */ + src: url('iconfont.woff2?t=1704174060960') format('woff2'), + url('iconfont.woff?t=1704174060960') format('woff'), + url('iconfont.ttf?t=1704174060960') format('truetype'); +} + +.iconfont { + font-family: "iconfont" !important; + font-size: 16px; + font-style: normal; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.icon-erweima:before { + content: "\e607"; +} + +.icon-erweima1:before { + content: "\e642"; +} + +.icon-erweima3:before { + content: "\e619"; +} + +.icon-a-biaodanzujian-shurukuang-svg6:before { + content: "\e606"; +} + +.icon-textBox:before { + content: "\e648"; +} + +.icon-a-biaodanzujian-shurukuang-svg5:before { + content: "\e605"; +} + +.icon-shubiao:before { + content: "\e650"; +} + +.icon-qingli:before { + content: "\e6cb"; +} + +.icon-refresh-1-copy:before { + content: "\e60f"; +} + +.icon-backspace-outline:before { + content: "\e64e"; +} + +.icon-space:before { + content: "\e6ab"; +} + +.icon-icon-test:before { + content: "\e603"; +} + +.icon-wenbenhuanhang:before { + content: "\e6b2"; +} + +.icon-wenzi:before { + content: "\e613"; +} + +.icon-xitongshouxuanxiang:before { + content: "\e656"; +} + +.icon-duigoux:before { + content: "\ec9e"; +} + +.icon-jinyong:before { + content: "\e615"; +} + +.icon-jiaohuan:before { + content: "\e665"; +} + +.icon-icon-jichushezhi:before { + content: "\e604"; +} + +.icon-ketuozhuai:before { + content: "\e617"; +} + +.icon-tuichu:before { + content: "\e66b"; +} + +.icon-jiemian:before { + content: "\e65b"; +} + +.icon-danhangshurukuang:before { + content: "\e694"; +} + +.icon-biaodanzujian-shurukuang:before { + content: "\eb94"; +} + +.icon-wenbenshibie:before { + content: "\e861"; +} + +.icon-duohangshuru:before { + content: "\e64a"; +} + +.icon-a-14Cjietu:before { + content: "\e679"; +} + +.icon-wenzishibie:before { + content: "\e9ce"; +} + +.icon-3zuidahua-3:before { + content: "\e693"; +} + +.icon-shanchu:before { + content: "\e74b"; +} + +.icon-baocun:before { + content: "\e63b"; +} + +.icon-lishijilu:before { + content: "\e63f"; +} + +.icon-changyongshili:before { + content: "\e640"; +} + +.icon-fuwuqi:before { + content: "\e726"; +} + +.icon-guanyu:before { + content: "\e60b"; +} + +.icon-shoucang:before { + content: "\e8b9"; +} + +.icon-guanbi:before { + content: "\e64d"; +} + +.icon-zuidahua1:before { + content: "\e651"; +} + +.icon-zuixiaohua:before { + content: "\e676"; +} + +.icon-tuding:before { + content: "\e637"; +} + +.icon-relieve-full:before { + content: "\e9ba"; +} + +.icon-fuzhi:before { + content: "\e692"; +} + +.icon-xiala:before { + content: "\e61d"; +} + +.icon-adjust-full:before { + content: "\e994"; +} + +.icon-snake:before { + content: "\e600"; +} + +.icon-copy_large_hump:before { + content: "\e601"; +} + +.icon-copy_c:before { + content: "\e602"; +} + +.icon-laba1:before { + content: "\e610"; +} + diff --git a/Attachments/iconfont/iconfont.js b/Attachments/iconfont/iconfont.js new file mode 100644 index 00000000..96290a4e --- /dev/null +++ b/Attachments/iconfont/iconfont.js @@ -0,0 +1 @@ +window._iconfont_svg_string_4294789='',function(l){var c=(c=document.getElementsByTagName("script"))[c.length-1],a=c.getAttribute("data-injectcss"),c=c.getAttribute("data-disable-injectsvg");if(!c){var h,t,i,o,v,p=function(c,a){a.parentNode.insertBefore(c,a)};if(a&&!l.__iconfont__svg__cssinject__){l.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(c){console&&console.log(c)}}h=function(){var c,a=document.createElement("div");a.innerHTML=l._iconfont_svg_string_4294789,(a=a.getElementsByTagName("svg")[0])&&(a.setAttribute("aria-hidden","true"),a.style.position="absolute",a.style.width=0,a.style.height=0,a.style.overflow="hidden",a=a,(c=document.body).firstChild?p(a,c.firstChild):c.appendChild(a))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(h,0):(t=function(){document.removeEventListener("DOMContentLoaded",t,!1),h()},document.addEventListener("DOMContentLoaded",t,!1)):document.attachEvent&&(i=h,o=l.document,v=!1,d(),o.onreadystatechange=function(){"complete"==o.readyState&&(o.onreadystatechange=null,s())})}function s(){v||(v=!0,i())}function d(){try{o.documentElement.doScroll("left")}catch(c){return void setTimeout(d,50)}s()}}(window); \ No newline at end of file diff --git a/Attachments/iconfont/iconfont.json b/Attachments/iconfont/iconfont.json new file mode 100644 index 00000000..e568b868 --- /dev/null +++ b/Attachments/iconfont/iconfont.json @@ -0,0 +1,345 @@ +{ + "id": "4294789", + "name": "STranslate 2.0", + "font_family": "iconfont", + "css_prefix_text": "icon-", + "description": "基于.Net 6 开发的新款翻译工具", + "glyphs": [ + { + "icon_id": "77191", + "name": "二维码", + "font_class": "erweima", + "unicode": "e607", + "unicode_decimal": 58887 + }, + { + "icon_id": "627566", + "name": "二维码", + "font_class": "erweima1", + "unicode": "e642", + "unicode_decimal": 58946 + }, + { + "icon_id": "11937102", + "name": "二维码", + "font_class": "erweima3", + "unicode": "e619", + "unicode_decimal": 58905 + }, + { + "icon_id": "38439764", + "name": "鼠标划词", + "font_class": "a-biaodanzujian-shurukuang-svg6", + "unicode": "e606", + "unicode_decimal": 58886 + }, + { + "icon_id": "16859931", + "name": "表单文本框", + "font_class": "textBox", + "unicode": "e648", + "unicode_decimal": 58952 + }, + { + "icon_id": "38439761", + "name": "划词翻译", + "font_class": "a-biaodanzujian-shurukuang-svg5", + "unicode": "e605", + "unicode_decimal": 58885 + }, + { + "icon_id": "6528456", + "name": "鼠标", + "font_class": "shubiao", + "unicode": "e650", + "unicode_decimal": 58960 + }, + { + "icon_id": "8763657", + "name": "清理", + "font_class": "qingli", + "unicode": "e6cb", + "unicode_decimal": 59083 + }, + { + "icon_id": "1029208", + "name": "刷新", + "font_class": "refresh-1-copy", + "unicode": "e60f", + "unicode_decimal": 58895 + }, + { + "icon_id": "6867447", + "name": "backspace-outline", + "font_class": "backspace-outline", + "unicode": "e64e", + "unicode_decimal": 58958 + }, + { + "icon_id": "19899588", + "name": "space", + "font_class": "space", + "unicode": "e6ab", + "unicode_decimal": 59051 + }, + { + "icon_id": "10754078", + "name": "3", + "font_class": "icon-test", + "unicode": "e603", + "unicode_decimal": 58883 + }, + { + "icon_id": "29570627", + "name": "文本换行", + "font_class": "wenbenhuanhang", + "unicode": "e6b2", + "unicode_decimal": 59058 + }, + { + "icon_id": "18941518", + "name": "文字", + "font_class": "wenzi", + "unicode": "e613", + "unicode_decimal": 58899 + }, + { + "icon_id": "13868185", + "name": "系统首选项", + "font_class": "xitongshouxuanxiang", + "unicode": "e656", + "unicode_decimal": 58966 + }, + { + "icon_id": "6616926", + "name": "对勾", + "font_class": "duigoux", + "unicode": "ec9e", + "unicode_decimal": 60574 + }, + { + "icon_id": "4942647", + "name": "禁用", + "font_class": "jinyong", + "unicode": "e615", + "unicode_decimal": 58901 + }, + { + "icon_id": "1630751", + "name": "交换", + "font_class": "jiaohuan", + "unicode": "e665", + "unicode_decimal": 58981 + }, + { + "icon_id": "4935751", + "name": "icon-基础设置", + "font_class": "icon-jichushezhi", + "unicode": "e604", + "unicode_decimal": 58884 + }, + { + "icon_id": "521494", + "name": "可拖拽", + "font_class": "ketuozhuai", + "unicode": "e617", + "unicode_decimal": 58903 + }, + { + "icon_id": "9454025", + "name": "退出", + "font_class": "tuichu", + "unicode": "e66b", + "unicode_decimal": 58987 + }, + { + "icon_id": "36590127", + "name": "界面", + "font_class": "jiemian", + "unicode": "e65b", + "unicode_decimal": 58971 + }, + { + "icon_id": "1766474", + "name": "符号-单行输入框", + "font_class": "danhangshurukuang", + "unicode": "e694", + "unicode_decimal": 59028 + }, + { + "icon_id": "4354240", + "name": "表单组件-输入框", + "font_class": "biaodanzujian-shurukuang", + "unicode": "eb94", + "unicode_decimal": 60308 + }, + { + "icon_id": "16399025", + "name": "文本识别", + "font_class": "wenbenshibie", + "unicode": "e861", + "unicode_decimal": 59489 + }, + { + "icon_id": "23626694", + "name": "多行输入", + "font_class": "duohangshuru", + "unicode": "e64a", + "unicode_decimal": 58954 + }, + { + "icon_id": "29522620", + "name": "14C截图", + "font_class": "a-14Cjietu", + "unicode": "e679", + "unicode_decimal": 59001 + }, + { + "icon_id": "32538045", + "name": "文字识别", + "font_class": "wenzishibie", + "unicode": "e9ce", + "unicode_decimal": 59854 + }, + { + "icon_id": "1306794", + "name": "Maximize-3", + "font_class": "3zuidahua-3", + "unicode": "e693", + "unicode_decimal": 59027 + }, + { + "icon_id": "577357", + "name": "删除", + "font_class": "shanchu", + "unicode": "e74b", + "unicode_decimal": 59211 + }, + { + "icon_id": "1305399", + "name": "保存", + "font_class": "baocun", + "unicode": "e63b", + "unicode_decimal": 58939 + }, + { + "icon_id": "1305475", + "name": "历史记录", + "font_class": "lishijilu", + "unicode": "e63f", + "unicode_decimal": 58943 + }, + { + "icon_id": "2678581", + "name": "常用示例", + "font_class": "changyongshili", + "unicode": "e640", + "unicode_decimal": 58944 + }, + { + "icon_id": "4933316", + "name": "服务器", + "font_class": "fuwuqi", + "unicode": "e726", + "unicode_decimal": 59174 + }, + { + "icon_id": "8219242", + "name": "关于", + "font_class": "guanyu", + "unicode": "e60b", + "unicode_decimal": 58891 + }, + { + "icon_id": "11372701", + "name": "收藏", + "font_class": "shoucang", + "unicode": "e8b9", + "unicode_decimal": 59577 + }, + { + "icon_id": "2939196", + "name": "关闭26", + "font_class": "guanbi", + "unicode": "e64d", + "unicode_decimal": 58957 + }, + { + "icon_id": "11490920", + "name": "最大化", + "font_class": "zuidahua1", + "unicode": "e651", + "unicode_decimal": 58961 + }, + { + "icon_id": "36077732", + "name": "最小化", + "font_class": "zuixiaohua", + "unicode": "e676", + "unicode_decimal": 58998 + }, + { + "icon_id": "386507", + "name": "图钉", + "font_class": "tuding", + "unicode": "e637", + "unicode_decimal": 58935 + }, + { + "icon_id": "18170426", + "name": "固定,图钉", + "font_class": "relieve-full", + "unicode": "e9ba", + "unicode_decimal": 59834 + }, + { + "icon_id": "16365853", + "name": "复制", + "font_class": "fuzhi", + "unicode": "e692", + "unicode_decimal": 59026 + }, + { + "icon_id": "10031200", + "name": "下拉", + "font_class": "xiala", + "unicode": "e61d", + "unicode_decimal": 58909 + }, + { + "icon_id": "18170245", + "name": "调整,半圆,亮度", + "font_class": "adjust-full", + "unicode": "e994", + "unicode_decimal": 59796 + }, + { + "icon_id": "33564238", + "name": "snake", + "font_class": "snake", + "unicode": "e600", + "unicode_decimal": 58880 + }, + { + "icon_id": "33564325", + "name": "copy_large_hump", + "font_class": "copy_large_hump", + "unicode": "e601", + "unicode_decimal": 58881 + }, + { + "icon_id": "33564377", + "name": "copy_c", + "font_class": "copy_c", + "unicode": "e602", + "unicode_decimal": 58882 + }, + { + "icon_id": "10109104", + "name": "喇叭", + "font_class": "laba1", + "unicode": "e610", + "unicode_decimal": 58896 + } + ] +} diff --git a/Attachments/iconfont/iconfont.ttf b/Attachments/iconfont/iconfont.ttf new file mode 100644 index 00000000..d89ca8ee Binary files /dev/null and b/Attachments/iconfont/iconfont.ttf differ diff --git a/Attachments/iconfont/iconfont.woff b/Attachments/iconfont/iconfont.woff new file mode 100644 index 00000000..e90b5517 Binary files /dev/null and b/Attachments/iconfont/iconfont.woff differ diff --git a/Attachments/iconfont/iconfont.woff2 b/Attachments/iconfont/iconfont.woff2 new file mode 100644 index 00000000..0fd49609 Binary files /dev/null and b/Attachments/iconfont/iconfont.woff2 differ diff --git a/STranslate/ClearCache.bat b/ClearCache.bat similarity index 100% rename from STranslate/ClearCache.bat rename to ClearCache.bat diff --git a/README.md b/README.md index e2b159a9..25825729 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

-STranslate +STranslate

@@ -20,48 +20,17 @@

STranslate

WPF 开发的一款即开即用即用即走的翻译工具 -
中文 | English

-## Preview 2.0 - -- [x] 支持多服务同时翻译 -- [x] 支持完全离线OCR,实用效果ok -- [x] 支持历史记录,方便回溯查找 -- [ ] 后期收尾,即将更新 - -
- -
- -![](./preview_ocr.png) - -![](./preview_preference.png) - - -## 功能 - -- [x] 添加 DeepL API -- [x] 添加划词翻译 -- [x] 添加复制结果蛇形、大小驼峰 -- [x] 软件层面识别语种(中英文) -- [x] 添加开机自启 -- [x] 添加明/暗主题 -- [x] 添加 UI 设置缓存(用户目录下 `AppData\Local\STranslate`) -- [x] 添加离线语音合成 -- [x] 添加离线截图文字识别(支持英文, 中文数据包过大且体验不好) -- [x] 添加检查更新 -- [x] 添加翻译记录缓存功能 - ## 安装 下载最新 [Release](https://github.com/ZGGSONG/STranslate/releases) 版本后解压即可使用 ## 使用 -![previews](./example.png) - -![previews_dark](./example_dark.png) +| 亮色 | 暗色 | +| :-- | :-- | +| ![](./light.png) | ![](./dark.png) | 打开软件后会静默在后台,等待调用 @@ -69,9 +38,11 @@ - `Alt` + `A` 打开软件界面,输入内容按回车翻译 - `Alt` + `D` 复制当前鼠标选中内容并翻译 - `Alt` + `S` 截图选中区域内容并翻译 -- `Alt` + `G` 打开窗口(不清空内容) +- `Alt` + `G` 打开主界面 +- `Alt` + `Shift` + `S` 完全离线文字识别(基于PaddleOCR) +- `Alt` + `Shift` + `D` 打开监听鼠标划词,鼠标滑动选中文字立即翻译 -2. 软件内快捷键 +1. 软件内快捷键 - `ESC` 隐藏界面 - `Ctrl+Shift+Q` 退出程序 - `Ctrl+Shift+R` 切换主题 @@ -79,21 +50,21 @@ 点击软件外部任意处即自动隐藏到后台——即用即走。 - -> STranslate依赖于.NET Framework 4.8 运行环境,如果程序启动时提示“This application requires *** .NETFramework,Version=v4.8”,请点击以下链接下载并安装.NET Framework 4.8 运行环境。 -> [适用于 Windows 的 Microsoft .NET Framework 4.8 脱机安装程序下载](https://download.visualstudio.microsoft.com/download/pr/2d6bb6b2-226a-4baa-bdec-798822606ff1/8494001c276a4b96804cde7829c04d7f/ndp48-x86-x64-allos-enu.exe) | [Microsoft Support](https://support.microsoft.com/zh-cn/topic/%E9%80%82%E7%94%A8%E4%BA%8E-windows-%E7%9A%84-microsoft-net-framework-4-8-%E8%84%B1%E6%9C%BA%E5%AE%89%E8%A3%85%E7%A8%8B%E5%BA%8F-9d23f658-3b97-68ab-d013-aa3c3e7495e0) - ## 卸载 -1. 删除软件运行目录 -2. 打开 cmd 运行下面的命令即可 - +1. 打开 cmd 运行下面的命令即可 +> 或者双击运行目录下的`ClearCache.bat`文件 ```shell rd /s /q "%localappdata%\stranslate" ``` +2. 删除软件运行目录 + + ## 开发历史 +- 2024-01-04 1.0.0.104 全新开发(新更新程序变动较大,`1.*`开始需要全新安装一次) + - 2023-03-02 0.25 添加复制提醒动画 - 2023-02-28 0.24 添加 deepl 接口(已经安装的cmd运行 `del %localappdata%\stranslate\stranslate.json` 后打开即可更新接口) @@ -108,45 +79,20 @@ rd /s /q "%localappdata%\stranslate" - 2023-01-10 0.15 添加离线 OCR 功能,其使用 [tesseract](https://github.com/tesseract-ocr/tesseract) 目前仅支持英文 -
- 自修改提示 - -有经验者可自行下载 [语言包](https://github.com/tesseract-ocr/tessdata) 至 `tessdata` 目录后修改 `Util`中`TesseractGetText`方法即可 - -```C# -public static string TesseractGetText(Bitmap bmp) -{ - try - { - using (var engine = new TesseractEngine(@"./tessdata", "eng", EngineMode.Default)) - //using (var engine = new TesseractEngine(@"./tessdata", "chi_sim", EngineMode.Default)) - { - using(var pix = PixConverter.ToPix(bmp)) - { - using (var page = engine.Process(pix)) - { - return page.GetText(); - } - } - } - } - catch (Exception ex) - { - throw ex; - } -} -``` -
- - 2023-12-28 0.10 添加明暗主题切换功能 - 2022-12-27 0.08 版本添加开机启动 -## 如果接口失效 +## 免费接口 当请求人数较多时,远端接口可能暂时失效,可自行运行翻译接口程序 -1. **【推荐】** 下载对应平台可 [执行文件](https://github.com/ZGGSONG/STranslate/releases/tag/0.01),随后在软件右上角选择 `local` 接口即可 -2. 【进阶】 下载 [docker镜像](https://hub.docker.com/r/zggsong/translate),关闭软件 - cmd 运行 `start %localappdata%\stranslate\stranslate.json` - 修改接口地址 - 重启软件即可 +1. 下载对应平台可 [执行文件](https://github.com/ZGGSONG/STranslate/releases/tag/0.01),随后在软件右上角选择 `local` 接口即可 +2. 下载 [docker镜像](https://hub.docker.com/r/zggsong/translate),关闭软件 - cmd 运行 `start %localappdata%\stranslate\stranslate.json` - 修改接口地址 - 重启软件即可 +3. [https://github.com/OwO-Network/DeepLX](https://github.com/OwO-Network/DeepLX) + +## 付费接口 + +1. 支持[百度翻译API](https://fanyi-api.baidu.com/product/11) ## Author 作者 diff --git a/README_EN.md b/README_EN.md deleted file mode 100644 index 4771dd4f..00000000 --- a/README_EN.md +++ /dev/null @@ -1,142 +0,0 @@ -

- -STranslate - -

-

- - Latest GitHub release - - - Latest GitHub release - - - Docker pull - - - GitHub last commit - -

-

STranslate

- -

-A ready-to-use, ready-to-go translation tool developed by WPF -
中文 | English -

- - - -## Function - -- [x] Add DeepL API -- [x] Add Crossword translation -- [x] Add replication results serpentine, large and small humps -- [x] Software level language recognition (Chinese and English) -- [x] Add Boot Self Start -- [x] Add a light/dark theme -- [x] Add UI settings cache (`AppData\Local\STranslate` in user directory) -- [x] Add offline voice synthesis -- [x] Add offline screenshot text recognition (supports English, Chinese data packets are too large and the experience is not good) -- [x] Add Check for Updates -- [x] Add translation record cache - -## Install - -Download the latest [Release](https://github.com/ZGGSONG/STranslate/releases) version and unzip it to use - -## Useage - -![previews](./example.png) - -![previews_dark](./example_dark.png) - -After opening the software it will be silent in the background, waiting for the call - -1. Global Listening Shortcuts -- `Alt` + `A` Open the software interface, enter the content and press enter to translate -- `Alt` + `D` Copy the current mouse selection and translate it -- `Alt` + `S` Screenshot the content of the selected area and translate it -- `Alt` + `G` Open window (without emptying the contents) - -2. In-software shortcuts -- `ESC` Hide interface -- `Ctrl+Shift+Q` Exit program -- `Ctrl+Shift+R` Switch Theme -- `Ctrl+Shift+T` Top/Cancel Top - -Click anywhere outside of the software to automatically hide it in the background - - -> NET Framework 4.8 runtime environment, if the application starts with the message "This application requires *** .NETFramework,Version=v4.8", please click on the following link NET Framework 4.8 Runtime Environment. -> [NET Framework 4.8 for Windows Offline Installer Download](https://download.visualstudio.microsoft.com/download/pr/2d6bb6b2-226a-4baa-bdec-798822606ff1/8494001c276a4b96804cde7829c04d7f/ndp48-x86-x64-allos-enu.exe) | [Microsoft Support](https://support.microsoft.com/zh-cn/topic/%E9%80%82%E7%94%A8%E4%BA%8E-windows-%E7%9A%84-microsoft-net-framework-4-8-%E8%84%B1%E6%9C%BA%E5%AE%89%E8%A3%85%E7%A8%8B%E5%BA%8F-9d23f658-3b97-68ab-d013-aa3c3e7495e0) - -## Uninstall - -1. Delete the software run directory -2. Just open cmd and run the following command - -```shell -rd /s /q "%localappdata%\stranslate" -``` - -## Development History - -- 2023-03-02 0.25 Add copy alert animation - -- 2023-02-28 0.24 Add Deepl interface(If you have already installed cmd, run `del %localappdata%\stranslate\stranslate.json ` and open it to update the interface) - -- 2023-02-24 0.22 Optimize the problem of blurred tray icon when resolution switching - -- 2023-01-17 0.20 Add translation record caching function, repeat translations are obtained from local database, and the upper limit of local records can be adjusted - -- 2023-01-12 0.18 Optimized GC background silent running memory footprint remains around 4MB - -- 2023-01-12 0.17 Add check update function - -- 2023-01-10 0.15 Add offline OCR functionality, which uses [tesseract](https://github.com/tesseract-ocr/tesseract) currently only supports English - -
- Self-modification tips -If you are experienced, you can download the [language package](https://github.com/tesseract-ocr/tessdata) to the tessdata directory and modify the TesseractGetText method in the Util. - -```C# -public static string TesseractGetText(Bitmap bmp) -{ - try - { - using (var engine = new TesseractEngine(@"./tessdata", "eng", EngineMode.Default)) - //using (var engine = new TesseractEngine(@"./tessdata", "chi_sim", EngineMode.Default)) - { - using(var pix = PixConverter.ToPix(bmp)) - { - using (var page = engine.Process(pix)) - { - return page.GetText(); - } - } - } - } - catch (Exception ex) - { - throw ex; - } -} -``` -
- -- 2023-12-28 0.10 Add light and dark theme switching function - -- 2022-12-27 0.08 Versions add boot up - -## If the interface fails - -When the number of requests is large, the remote interface may temporarily fail, so you can run the translation interface program yourself -1. **【Recommend】** Download the [executable file](https://github.com/ZGGSONG/STranslate/releases/tag/0.01) for the corresponding platform and then select the local interface in the upper right corner of the software. -2. **【Advanced】** Download the [docker image](https://hub.docker.com/r/zggsong/translate), close the software - cmd run `start %localappdata%\stranslate\stranslate.json` - change the interface address - restart the software - -## Author - -**STranslate** © [zggsong](https://github.com/zggsong), Released under the [MIT](https://github.com/ZGGSONG/STranslate/blob/main/LICENSE) License.
-Authored and maintained by zggsong with help from other open source projects [WpfTool](https://github.com/NPCDW/WpfTool) and [Tai](https://github.com/Planshit/Tai). - -> Website [Blog](https://www.zggsong.com) · GitHub [@zggsong](https://github.com/zggsong) diff --git a/STranslate.Log/BaseLogger.cs b/STranslate.Log/BaseLogger.cs new file mode 100644 index 00000000..e54e3225 --- /dev/null +++ b/STranslate.Log/BaseLogger.cs @@ -0,0 +1,56 @@ +using System; + +namespace STranslate.Log +{ + public class BaseLogger : ILogger + { + public virtual void Debug(string message) + { + WriteLine("DBG", message); + } + + public virtual void Info(string message) + { + WriteLine("INF", message); + } + + public virtual void Warn(string message) + { + WriteLine("WRN", message); + } + + public virtual void Error(string message) + { + WriteLine("ERR", message); + } + + public virtual void Error(string message, Exception ex) + { + WriteLine("ERR", message, ex); + } + + public virtual void Fatal(string message) + { + WriteLine("FTL", message); + } + + public virtual void Fatal(string message, Exception ex) + { + WriteLine("FTL", message, ex); + } + + public virtual void Dispose() + { + WriteLine("DBG", $"{nameof(BaseLogger)} Dispose"); + } + + internal static void WriteLine(string type, string message) + { + System.Diagnostics.Debug.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff} [{type}] {message}"); + } + internal static void WriteLine(string type, string message, Exception ex) + { + System.Diagnostics.Debug.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff} [{type}] {message}, Exception: {ex}"); + } + } +} \ No newline at end of file diff --git a/STranslate.Log/ILogger.cs b/STranslate.Log/ILogger.cs new file mode 100644 index 00000000..ecd821a5 --- /dev/null +++ b/STranslate.Log/ILogger.cs @@ -0,0 +1,21 @@ +using System; + +namespace STranslate.Log +{ + public interface ILogger : IDisposable + { + void Debug(string message); + + void Info(string message); + + void Warn(string message); + + void Error(string message); + + void Error(string message, Exception ex); + + void Fatal(string message); + + void Fatal(string message, Exception ex); + } +} \ No newline at end of file diff --git a/STranslate.Log/LogLevel.cs b/STranslate.Log/LogLevel.cs new file mode 100644 index 00000000..46c4f05d --- /dev/null +++ b/STranslate.Log/LogLevel.cs @@ -0,0 +1,35 @@ +namespace STranslate.Log +{ + public enum LogLevel + { + /// + /// Most verbose level. Used for development and seldom enabled in production + /// + Trace = 0, + + /// + /// Debugging the application behavior from internal events of interest + /// + Debug, + + /// + /// Information that highlights progress or application lifetime events + /// + Info, + + /// + /// Warnings about validation issues or temporary failures that can be recovered. + /// + Warn, + + /// + /// Errors where functionality has failed or have been caught. + /// + Error, + + /// + /// Most critical level. Application is about to abort. + /// + Fatal + } +} diff --git a/STranslate.Log/LogService.cs b/STranslate.Log/LogService.cs new file mode 100644 index 00000000..5bf2288a --- /dev/null +++ b/STranslate.Log/LogService.cs @@ -0,0 +1,36 @@ +namespace STranslate.Log +{ + public class LogService + { +#if true + + public static void Register(string name = "", LogLevel minLevel = LogLevel.Debug) + { + _logger = name.ToLower() switch + { + "serilog" => new SerilogLogger(minLevel), + //"nlog" => new NLogLogger(level), + _ => new SerilogLogger(minLevel), + }; + } + + public static void UnRegister() + { + _logger?.Dispose(); + } + + private static ILogger? _logger; + public static ILogger Logger { get => _logger!; set => _logger = value; } + +#else + + private static readonly Lazy _logger = new(() => new SerilogLogger()); + public static ILogger Logger => _logger.Value; + + public static void UnRegister() + { + _logger.Value.Dispose(); + } +#endif + } +} diff --git a/STranslate.Log/NLogLogger.cs b/STranslate.Log/NLogLogger.cs new file mode 100644 index 00000000..62e50bd7 --- /dev/null +++ b/STranslate.Log/NLogLogger.cs @@ -0,0 +1,89 @@ +//using NLog; +//using System; + +//namespace STranslate.Log +//{ +// public class NLogLogger : BaseLogger +// { +// private readonly Logger _logger; + +// public NLogLogger(LogLevel minLevel = LogLevel.Debug) +// { +// var logFileName = $"logs/log{DateTime.Now:yyyyMMdd}.log"; +// var convLevel = ToNLogLevel(minLevel); + +// var conf = new NLog.Config.LoggingConfiguration(); +// conf.AddRule( +// convLevel, +// NLog.LogLevel.Fatal, +// new NLog.Targets.FileTarget("logfile") { FileName = logFileName } +// ); +// LogManager.Configuration = conf; +// _logger = LogManager.GetCurrentClassLogger(); +// } + +// /// +// /// 辅助方法将自定义的LogLevel转换为NLog的NLogLevel +// /// +// /// +// /// +// private NLog.LogLevel ToNLogLevel(LogLevel level) => level switch +// { +// LogLevel.Trace => NLog.LogLevel.Trace, +// LogLevel.Debug => NLog.LogLevel.Debug, +// LogLevel.Info => NLog.LogLevel.Info, +// LogLevel.Warn => NLog.LogLevel.Warn, +// LogLevel.Error => NLog.LogLevel.Error, +// LogLevel.Fatal => NLog.LogLevel.Fatal, +// _ => NLog.LogLevel.Debug, +// }; + +// public override void Debug(string message) +// { +// base.Debug(message); +// _logger.Debug(message); +// } + +// public override void Info(string message) +// { +// base.Info(message); +// _logger.Info(message); +// } + +// public override void Warn(string message) +// { +// base.Warn(message); +// _logger.Warn(message); +// } + +// public override void Error(string message) +// { +// base.Error(message); +// _logger.Error(message); +// } + +// public override void Error(string message, Exception ex) +// { +// base.Error(message); +// _logger.Error(ex, message); +// } + +// public override void Fatal(string message) +// { +// base.Fatal(message); +// _logger.Fatal(message); +// } + +// public override void Fatal(string message, Exception ex) +// { +// base.Fatal(message); +// _logger.Fatal(ex, message); +// } + +// public override void Dispose() +// { +// base.Dispose(); +// LogManager.Shutdown(); +// } +// } +//} diff --git a/STranslate.Log/STranslate.Log.csproj b/STranslate.Log/STranslate.Log.csproj new file mode 100644 index 00000000..0d56b5aa --- /dev/null +++ b/STranslate.Log/STranslate.Log.csproj @@ -0,0 +1,18 @@ + + + + net8.0-windows + enable + true + + + + none + + + + + + + + diff --git a/STranslate.Log/SerilogLogger.cs b/STranslate.Log/SerilogLogger.cs new file mode 100644 index 00000000..daf2e5c0 --- /dev/null +++ b/STranslate.Log/SerilogLogger.cs @@ -0,0 +1,91 @@ +using Serilog; +using Serilog.Core; +using Serilog.Events; +using System; + +namespace STranslate.Log +{ + public class SerilogLogger : BaseLogger + { + private readonly Logger _logger; + + public SerilogLogger(LogLevel minLevel = LogLevel.Debug) + { + var logConfiguration = new LoggerConfiguration() + .WriteTo.File( + "logs/log.log", + rollingInterval: RollingInterval.Day, + restrictedToMinimumLevel: LogEventLevel.Verbose, + outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level:u3}] {Message:lj}{NewLine}{Exception}" + ) + .MinimumLevel.Is(ConvertToSerilogLevel(minLevel)); + + _logger = logConfiguration.CreateLogger(); + } + + /// + /// 将自定义的LogLevel转换为Serilog的LogEventLevel + /// + /// + /// + private LogEventLevel ConvertToSerilogLevel(LogLevel level) => + level switch + { + LogLevel.Trace => LogEventLevel.Verbose, + LogLevel.Debug => LogEventLevel.Debug, + LogLevel.Info => LogEventLevel.Information, + LogLevel.Warn => LogEventLevel.Warning, + LogLevel.Error => LogEventLevel.Error, + LogLevel.Fatal => LogEventLevel.Fatal, + _ => LogEventLevel.Debug, + }; + + public override void Debug(string message) + { + base.Debug(message); + _logger.Debug(message); + } + + public override void Info(string message) + { + base.Info(message); + _logger.Information(message); + } + + public override void Warn(string message) + { + base.Warn(message); + _logger.Warning(message); + } + + public override void Error(string message) + { + base.Error(message); + _logger.Error(message); + } + + public override void Error(string message, Exception ex) + { + base.Error(message); + _logger.Error(ex, message); + } + + public override void Fatal(string message) + { + base.Fatal(message); + _logger.Fatal(message); + } + + public override void Fatal(string message, Exception ex) + { + base.Fatal(message); + _logger.Fatal(ex, message); + } + + public override void Dispose() + { + base.Dispose(); + _logger.Dispose(); + } + } +} diff --git a/STranslate.Model/BaiduModel.cs b/STranslate.Model/BaiduModel.cs deleted file mode 100644 index 82908cb8..00000000 --- a/STranslate.Model/BaiduModel.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Newtonsoft.Json; - -namespace STranslate.Model -{ - public class BaiduModel - { - public string Text { get; set; } - public string From { get; set; } - public string TO { get; set; } - public string AppId { get; set; } - public string Salt { get; set; } - public string Sign { get; set; } - } - - public class BaiduResp - { - [JsonProperty("from")] - public string From { get; set; } - - [JsonProperty("to")] - public string To { get; set; } - - [JsonProperty("trans_result")] - public TransResult[] TransResult { get; set; } - } - - public class TransResult - { - [JsonProperty("src")] - public string Src { get; set; } - - [JsonProperty("dst")] - public string Dst { get; set; } - } -} \ No newline at end of file diff --git a/STranslate.Model/ConfigModel.cs b/STranslate.Model/ConfigModel.cs index f363bc61..a2379ade 100644 --- a/STranslate.Model/ConfigModel.cs +++ b/STranslate.Model/ConfigModel.cs @@ -1,183 +1,86 @@ -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using CommunityToolkit.Mvvm.ComponentModel; +using Newtonsoft.Json; +using System.ComponentModel; +using System.Windows; namespace STranslate.Model { - - public class Hotkeys + public partial class ConfigModel : ObservableObject { - [JsonProperty("inputTranslate")] - public InputTranslate InputTranslate { get; set; } - - [JsonProperty("crosswordTranslate")] - public CrosswordTranslate CrosswordTranslate { get; set; } - - [JsonProperty("screenShotTranslate")] - public ScreenShotTranslate ScreenShotTranslate { get; set; } + /// + /// 开机自启动 + /// + public bool IsStartup { get; set; } - [JsonProperty("openMainWindow")] - public OpenMainWindow OpenMainWindow { get; set; } - } - public class InputTranslate - { - public byte Modifiers { get; set; } - public int Key { get; set; } - public String Text { get; set; } - public bool Conflict { get; set; } - } - public class CrosswordTranslate - { - public byte Modifiers { get; set; } - public int Key { get; set; } - public String Text { get; set; } - public bool Conflict { get; set; } - } - public class ScreenShotTranslate - { - public byte Modifiers { get; set; } - public int Key { get; set; } - public String Text { get; set; } - public bool Conflict { get; set; } - } - public class OpenMainWindow - { - public byte Modifiers { get; set; } - public int Key { get; set; } - public String Text { get; set; } - public bool Conflict { get; set; } - } - public class Server - { - [JsonProperty("name")] - public string Name { get; set; } + /// + /// 是否管理员启动 + /// + public bool NeedAdministrator { get; set; } - [JsonProperty("api")] - public string Api { get; set; } - } - public class ConfigModel - { /// - /// 最大历史记录数量 + /// 历史记录大小 /// - [JsonProperty("maxHistoryCount")] - public int MaxHistoryCount { get; set; } + public long HistorySize { get; set; } + /// /// 自动识别语种标度 /// - [JsonProperty("autoScale")] public double AutoScale { get; set; } - /// - /// 取词间隔 - /// - [JsonProperty("wordPickupInterval")] - public double WordPickupInterval { get; set; } + /// /// 是否亮色模式 /// - [JsonProperty("isBright")] public bool IsBright { get; set; } - [JsonProperty("sourceLanguage")] - public string SourceLanguage { get; set; } + /// + /// 是否跟随鼠标 + /// + public bool IsFollowMouse { get; set; } + + /// + /// OCR结果翻译关闭OCR界面 + /// + public bool CloseUIOcrRetTranslate { get; set; } + + /// + /// 截图出现问题尝试一下 + /// + public bool UnconventionalScreen { get; set; } + + /// + /// OCR时是否自动复制文本 + /// + public bool IsOcrAutoCopyText { get; set; } + + /// + /// 是否调整完语句后翻译 + /// + public bool IsAdjustContentTranslate { get; set; } + + /// + /// 取词时移除换行 + /// + public bool IsRemoveLineBreakGettingWords { get; set; } + + /// + /// 鼠标双击托盘程序功能 + /// + public DoubleTapFuncEnum DoubleTapTrayFunc { get; set; } = DoubleTapFuncEnum.InputFunc; - [JsonProperty("targetLanguage")] - public string TargetLanguage { get; set; } + public string SourceLanguage { get; set; } = string.Empty; - [JsonProperty("selectServer")] - public int SelectServer { get; set; } + public string TargetLanguage { get; set; } = string.Empty; /// /// 服务 /// - [JsonProperty("servers")] - public Server[] Servers { get; set; } + [JsonIgnore] + [ObservableProperty] + public BindingList? _services; /// /// 热键 /// - [JsonProperty("hotkeys")] - public Hotkeys Hotkeys { get; set; } - - - public ConfigModel() - { - } - - public ConfigModel InitialConfig() - { - return new ConfigModel - { - MaxHistoryCount = 100, - AutoScale = 0.8, - WordPickupInterval = 200, - IsBright = true, - SourceLanguage = LanguageEnum.AUTO.GetDescription(), - TargetLanguage = LanguageEnum.AUTO.GetDescription(), - SelectServer = 0, - Servers = new Server[] - { - new Server - { - Name = "zggsong", - Api = "https://dfree.deno.dev/translate" - }, - new Server - { - Name = "iciba", - Api = "https://iciba.deno.dev/translate" - }, - new Server - { - Name = "google", - Api = "https://ggtranslate.deno.dev/translate" - }, - new Server - { - Name = "zu1k", - Api = "https://deepl.deno.dev/translate" - }, - new Server - { - Name = "local", - Api = "http://127.0.0.1:8000/translate" - } - }, - Hotkeys = new Hotkeys - { - InputTranslate = new InputTranslate - { - Modifiers = 1, - Key = 65, - Text = "Alt + A", - Conflict = false, - }, - CrosswordTranslate = new CrosswordTranslate - { - Modifiers = 1, - Key = 68, - Text = "Alt + D", - Conflict = false, - }, - ScreenShotTranslate = new ScreenShotTranslate - { - Modifiers = 1, - Key = 83, - Text = "Alt + S", - Conflict = false, - }, - OpenMainWindow = new OpenMainWindow - { - Modifiers = 1, - Key = 71, - Text = "Alt + G", - Conflict = false, - }, - } - }; - } + public Hotkeys? Hotkeys { get; set; } } } diff --git a/STranslate.Model/ConstStr.cs b/STranslate.Model/ConstStr.cs new file mode 100644 index 00000000..fbbc6b4e --- /dev/null +++ b/STranslate.Model/ConstStr.cs @@ -0,0 +1,31 @@ + +namespace STranslate.Model +{ + public static class ConstStr + { + public const string THEMELIGHT = "pack://application:,,,/STranslate.Style;component/Styles/Themes/ColorLight.xaml"; + public const string THEMEDARK = "pack://application:,,,/STranslate.Style;component/Styles/Themes/ColorDark.xaml"; + + public static System.Uri LIGHTURI = new(THEMELIGHT); + public static System.Uri DARKURI = new(THEMEDARK); + + public const string ICON = "pack://application:,,,/STranslate.Style;component/Resources/favicon.ico"; + public const string ICONFORBIDDEN = "pack://application:,,,/STranslate.Style;component/Resources/forbidden.ico"; + + public const string TAGTRUE = "True"; + public const string TAGFALSE = "False"; + + public const string TOPMOSTCONTENT = "\xe637"; + public const string UNTOPMOSTCONTENT = "\xe9ba"; + + public const string MAXIMIZECONTENT = "\xe651"; + public const string MAXIMIZEBACKCONTENT = "\xe693"; + + public const string DEFAULTINPUTHOTKEY = "Alt + A"; + public const string DEFAULTCROSSWORDHOTKEY = "Alt + D"; + public const string DEFAULTSCREENSHOTHOTKEY = "Alt + S"; + public const string DEFAULTOPENHOTKEY = "Alt + G"; + public const string DEFAULTMOUSEHOOKHOTKEY = "Alt + Shift + D"; + public const string DEFAULTOCRHOTKEY = "Alt + Shift + S"; + } +} \ No newline at end of file diff --git a/STranslate.Model/DeeplModel.cs b/STranslate.Model/DeeplModel.cs deleted file mode 100644 index d28b6776..00000000 --- a/STranslate.Model/DeeplModel.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Newtonsoft.Json; - -namespace STranslate.Model -{ - public class DeeplReq - { - [JsonProperty("text")] - public string Text { get; set; } - - [JsonProperty("source_lang")] - public string SourceLang { get; set; } - - [JsonProperty("target_lang")] - public string TargetLang { get; set; } - } - - public class DeeplResp - { - [JsonProperty("code")] - public int Code { get; set; } - - [JsonProperty("data")] - public string Data { get; set; } - } -} \ No newline at end of file diff --git a/STranslate.Model/Enums.cs b/STranslate.Model/Enums.cs new file mode 100644 index 00000000..e522abc1 --- /dev/null +++ b/STranslate.Model/Enums.cs @@ -0,0 +1,222 @@ +using System; +using System.ComponentModel; + +namespace STranslate.Model +{ + public enum LanguageEnum + { + [Description("自动选择")] + AUTO, //自动 + + [Description("中文")] + ZH, //中文 + + [Description("英语")] + EN, //英语 + + [Description("德语")] + DE, //德语 + + [Description("西班牙语")] + ES, //西班牙语 + + [Description("法语")] + FR, //法语 + + [Description("意大利语")] + IT, //意大利语 + + [Description("日语")] + JA, //日语 + + [Description("荷兰语")] + NL, //荷兰语 + + [Description("波兰语")] + PL, //波兰语 + + [Description("葡萄牙语")] + PT, //葡萄牙语 + + [Description("俄语")] + RU, //俄语 + + [Description("保加利亚语")] + BG, //保加利亚语 + + [Description("捷克语")] + CS, //捷克语 + + [Description("丹麦语")] + DA, //丹麦语 + + [Description("希腊语")] + EL, //希腊语 + + [Description("爱沙尼亚语")] + ET, //爱沙尼亚语 + + [Description("芬兰语")] + FI, //芬兰语 + + [Description("匈牙利语")] + HU, //匈牙利语 + + [Description("立陶宛语")] + LT, //立陶宛语 + + [Description("拉脱维亚语")] + LV, //拉脱维亚语 + + [Description("罗马尼亚语")] + RO, //罗马尼亚语 + + [Description("斯洛伐克语")] + SK, //斯洛伐克语 + + [Description("斯洛文尼亚语")] + SL, //斯洛文尼亚语 + + [Description("瑞典语")] + SV, //瑞典语 + + [Description("土耳其语")] + TR, //土耳其语 + } + + /// + /// ServiceView 重置选中项类型 + /// + public enum ActionType + { + Initialize, + Delete, + Add + } + + /// + /// 服务类型 + /// + public enum ServiceType + { + ApiService = 0, + CloudService + } + + /// + /// 请求方式 + /// + public enum RequestMode + { + GET = 0, + POST + } + + /// + /// 图标类型 + /// + public enum IconType + { + STranslate, + DeepL, + Baidu, + Google, + Iciba, + Youdao, + } + + /// + /// 快捷键修饰键 + /// + public enum KeyModifiers : byte + { + MOD_NONE = 0x0, + MOD_ALT = 0x1, + MOD_CTRL = 0x2, + MOD_SHIFT = 0x4, + MOD_WIN = 0x8 + } + + public enum KeyCodes + { + None = 0, + A = 65, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P, + Q, + R, + S, + T, + U, + V, + W, + X, + Y, + Z + } + + public enum OCRType + { + Chinese, + English + } + + /// + /// 窗体类型-用于通知窗口 + /// + public enum WindowType + { + Main, + Preference, + OCR + } + + /// + /// 托盘功能枚举 + /// + public enum DoubleTapFuncEnum + { + [Description("输入翻译")] + InputFunc, + [Description("截图翻译")] + ScreenFunc, + [Description("鼠标划词")] + MouseHookFunc, + [Description("文字识别")] + OCRFunc, + [Description("显示界面")] + ShowViewFunc, + [Description("偏好设置")] + PreferenceFunc, + [Description("禁用热键")] + ForbidShortcutFunc, + [Description("退出程序")] + ExitFunc + } + + /// + /// 获取Description + /// + public static class EnumExtensions + { + public static string GetDescription(this Enum val) + { + var field = val.GetType().GetField(val.ToString()); + var customAttribute = Attribute.GetCustomAttribute(field!, typeof(DescriptionAttribute)); + return customAttribute == null ? val.ToString() : ((DescriptionAttribute)customAttribute).Description; + } + } +} \ No newline at end of file diff --git a/STranslate.Model/HistoryModel.cs b/STranslate.Model/HistoryModel.cs new file mode 100644 index 00000000..97d93ac4 --- /dev/null +++ b/STranslate.Model/HistoryModel.cs @@ -0,0 +1,48 @@ +using Dapper.Contrib.Extensions; +using System; +using System.Windows.Documents; + +namespace STranslate.Model +{ + [Table("History")] + public class HistoryModel + { + [Key] + public int Id { get; set; } + + /// + /// 记录时间 + /// + public DateTime Time { get; set; } + + /// + /// 源语言 + /// + public string SourceLang { get; set; } = ""; + + /// + /// 目标语言 + /// + public string TargetLang { get; set; } = ""; + + /// + /// 需翻译内容 + /// + public string SourceText { get; set; } = ""; + + /// + /// 收藏 + /// + public bool Favorite { get; set; } + + /// + /// 备注 + /// + public string Remark { get; set; } = ""; + + /// + /// 服务 + /// + public string Data { get; set; } = ""; + } +} diff --git a/STranslate.Model/Hotkeys.cs b/STranslate.Model/Hotkeys.cs new file mode 100644 index 00000000..b5093d62 --- /dev/null +++ b/STranslate.Model/Hotkeys.cs @@ -0,0 +1,53 @@ +namespace STranslate.Model +{ + public class Hotkeys + { + public InputTranslate InputTranslate { get; set; } = new InputTranslate(); + + public CrosswordTranslate CrosswordTranslate { get; set; } = new CrosswordTranslate(); + + public ScreenShotTranslate ScreenShotTranslate { get; set; } = new ScreenShotTranslate(); + + public OpenMainWindow OpenMainWindow { get; set; } = new OpenMainWindow(); + + public MousehookTranslate MousehookTranslate { get; set; } = new MousehookTranslate(); + + public OCR OCR { get; set; } = new OCR(); + } + + public class InputTranslate : HotkeyBase { } + + public class CrosswordTranslate : HotkeyBase { } + + public class ScreenShotTranslate : HotkeyBase { } + + public class OpenMainWindow : HotkeyBase { } + + public class MousehookTranslate : HotkeyBase { } + + public class OCR : HotkeyBase { } + + public class HotkeyBase + { + public KeyModifiers Modifiers { get; set; } + + public KeyCodes Key { get; set; } + + public string? Text { get; set; } + + public bool Conflict { get; set; } + } + + public static class HotkeyExtensions + { + public static T Update(this T t, KeyModifiers modifiers, KeyCodes key, string? text, bool conflict = false) + where T : HotkeyBase + { + t.Modifiers = modifiers; + t.Key = key; + t.Text = text; + t.Conflict = conflict; + return t; + } + } +} diff --git a/STranslate.Model/ITranslator.cs b/STranslate.Model/ITranslator.cs new file mode 100644 index 00000000..7a11ee6c --- /dev/null +++ b/STranslate.Model/ITranslator.cs @@ -0,0 +1,31 @@ +using System.Threading.Tasks; +using System.Threading; +using System; + +namespace STranslate.Model +{ + public interface ITranslator + { + Guid Identify { get; set; } + + ServiceType Type { get; set; } + + public bool IsEnabled { get; set; } + + IconType Icon { get; set; } + + string Name { get; set; } + + string Url { get; set; } + + object Data { get; set; } + + RequestMode HttpMode { get; set; } + + string AppID { get; set; } + + string AppKey { get; set; } + + Task TranslateAsync(object request, CancellationToken token); + } +} diff --git a/STranslate.Model/LanguageEnum.cs b/STranslate.Model/LanguageEnum.cs deleted file mode 100644 index 428f7f1f..00000000 --- a/STranslate.Model/LanguageEnum.cs +++ /dev/null @@ -1,99 +0,0 @@ -using System; -using System.ComponentModel; - -namespace STranslate.Model -{ - public enum LanguageEnum - { - [Description("自动")] - AUTO, //自动 - - [Description("德语")] - DE, //德语 - - [Description("英语")] - EN, //英语 - - [Description("西班牙语")] - ES, //西班牙语 - - [Description("法语")] - FR, //法语 - - [Description("意大利语")] - IT, //意大利语 - - [Description("日语")] - JA, //日语 - - [Description("荷兰语")] - NL, //荷兰语 - - [Description("波兰语")] - PL, //波兰语 - - [Description("葡萄牙语")] - PT, //葡萄牙语 - - [Description("俄语")] - RU, //俄语 - - [Description("中文")] - ZH, //中文 - - [Description("保加利亚语")] - BG, //保加利亚语 - - [Description("捷克语")] - CS, //捷克语 - - [Description("丹麦语")] - DA, //丹麦语 - - [Description("希腊语")] - EL, //希腊语 - - [Description("爱沙尼亚语")] - ET, //爱沙尼亚语 - - [Description("芬兰语")] - FI, //芬兰语 - - [Description("匈牙利语")] - HU, //匈牙利语 - - [Description("立陶宛语")] - LT, //立陶宛语 - - [Description("拉脱维亚语")] - LV, //拉脱维亚语 - - [Description("罗马尼亚语")] - RO, //罗马尼亚语 - - [Description("斯洛伐克语")] - SK, //斯洛伐克语 - - [Description("斯洛文尼亚语")] - SL, //斯洛文尼亚语 - - [Description("瑞典语")] - SV, //瑞典语 - - [Description("土耳其语")] - TR, //土耳其语 - } - - /// - /// 获取Description - /// - public static class EnumExtensions - { - public static string GetDescription(this Enum val) - { - var field = val.GetType().GetField(val.ToString()); - var customAttribute = Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)); - return customAttribute == null ? val.ToString() : ((DescriptionAttribute)customAttribute).Description; - } - } -} \ No newline at end of file diff --git a/STranslate.Model/ModelApi.cs b/STranslate.Model/ModelApi.cs new file mode 100644 index 00000000..3d2f621c --- /dev/null +++ b/STranslate.Model/ModelApi.cs @@ -0,0 +1,27 @@ +using Newtonsoft.Json; + +namespace STranslate.Model +{ + public class RequestApi + { + [JsonProperty("text")] + public string Text { get; set; } = ""; + + [JsonProperty("source_lang")] + public string SourceLang { get; set; } = ""; + + [JsonProperty("target_lang")] + public string TargetLang { get; set; } = ""; + } + + public class ResponseApi + { + [JsonProperty("code")] + public int Code { get; set; } + + [JsonProperty("data")] + public object Data { get; set; } = ""; + + public string ErrMsg { get; set; } = ""; + } +} diff --git a/STranslate.Model/ModelBaidu.cs b/STranslate.Model/ModelBaidu.cs new file mode 100644 index 00000000..cc157ecf --- /dev/null +++ b/STranslate.Model/ModelBaidu.cs @@ -0,0 +1,35 @@ +using Newtonsoft.Json; + +namespace STranslate.Model +{ + public class RequestBaidu + { + public string Text { get; set; } = ""; + public string From { get; set; } = ""; + public string TO { get; set; } = ""; + public string AppId { get; set; } = ""; + public string Salt { get; set; } = ""; + public string Sign { get; set; } = ""; + } + + public class ResponseBaidu + { + [JsonProperty("from")] + public string From { get; set; } = ""; + + [JsonProperty("to")] + public string To { get; set; } = ""; + + [JsonProperty("trans_result")] + public TransResult[]? TransResult { get; set; } + } + + public class TransResult + { + [JsonProperty("src")] + public string Src { get; set; } = ""; + + [JsonProperty("dst")] + public string Dst { get; set; } = ""; + } +} diff --git a/STranslate.Model/NotifyIconModel.cs b/STranslate.Model/NotifyIconModel.cs new file mode 100644 index 00000000..113c156a --- /dev/null +++ b/STranslate.Model/NotifyIconModel.cs @@ -0,0 +1,19 @@ +using CommunityToolkit.Mvvm.ComponentModel; + +namespace STranslate.Model +{ + public partial class NotifyIconModel : ObservableObject + { + /// + /// 托盘程序图标 + /// + [ObservableProperty] + private string _iconSource = ConstStr.ICON; + + /// + /// 文字提示 + /// + [ObservableProperty] + private string _toolTip = string.Empty; + } +} diff --git a/STranslate.Model/Properties/AssemblyInfo.cs b/STranslate.Model/Properties/AssemblyInfo.cs deleted file mode 100644 index e02231cc..00000000 --- a/STranslate.Model/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// 有关程序集的一般信息由以下 -// 控制。更改这些特性值可修改 -// 与程序集关联的信息。 -[assembly: AssemblyTitle("STranslate.Model")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Microsoft")] -[assembly: AssemblyProduct("STranslate.Model")] -[assembly: AssemblyCopyright("Copyright © Microsoft 2023")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// 将 ComVisible 设置为 false 会使此程序集中的类型 -//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型 -//请将此类型的 ComVisible 特性设置为 true。 -[assembly: ComVisible(false)] - -// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID -[assembly: Guid("a65f547f-52e2-4ad5-8c8b-53710e1485fa")] - -// 程序集的版本信息由下列四个值组成: -// -// 主版本 -// 次版本 -// 生成号 -// 修订号 -// -//可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值 -//通过使用 "*",如下所示: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/STranslate.Model/STranslate.Model.csproj b/STranslate.Model/STranslate.Model.csproj index affb91d7..c4278fd9 100644 --- a/STranslate.Model/STranslate.Model.csproj +++ b/STranslate.Model/STranslate.Model.csproj @@ -1,95 +1,19 @@ - - - + + - Debug - AnyCPU - {A65F547F-52E2-4AD5-8C8B-53710E1485FA} - Library - Properties - STranslate.Model - STranslate.Model - v4.7.2 - 512 - true - - + net8.0-windows + enable + true - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 + + + none - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll - - - ..\packages\sqlite-net-pcl.1.8.116\lib\netstandard2.0\SQLite-net.dll - - - ..\packages\SQLitePCLRaw.bundle_green.2.0.4\lib\net461\SQLitePCLRaw.batteries_v2.dll - - - ..\packages\SQLitePCLRaw.core.2.0.4\lib\netstandard2.0\SQLitePCLRaw.core.dll - - - ..\packages\SQLitePCLRaw.bundle_green.2.0.4\lib\net461\SQLitePCLRaw.nativelibrary.dll - - - ..\packages\SQLitePCLRaw.provider.dynamic_cdecl.2.0.4\lib\netstandard2.0\SQLitePCLRaw.provider.dynamic_cdecl.dll - - - - ..\packages\System.Buffers.4.4.0\lib\netstandard2.0\System.Buffers.dll - - - - ..\packages\System.Memory.4.5.3\lib\netstandard2.0\System.Memory.dll - - - - ..\packages\System.Numerics.Vectors.4.4.0\lib\net46\System.Numerics.Vectors.dll - - - ..\packages\System.Runtime.CompilerServices.Unsafe.4.5.2\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll - - - - - - - - - - - - - - - - + - + + + - - - - - 这台计算机上缺少此项目引用的 NuGet 程序包。使用“NuGet 程序包还原”可下载这些程序包。有关更多信息,请参见 http://go.microsoft.com/fwlink/?LinkID=322105。缺少的文件是 {0}。 - - - - \ No newline at end of file + + diff --git a/STranslate.Model/SqliteModel.cs b/STranslate.Model/SqliteModel.cs deleted file mode 100644 index 66624cca..00000000 --- a/STranslate.Model/SqliteModel.cs +++ /dev/null @@ -1,38 +0,0 @@ -using SQLite; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace STranslate.Model -{ - [Table("Histories")] - public class SqliteModel - { - [PrimaryKey, AutoIncrement] - [Column("id")] - public int Id { get; set; } - - [Column("time")] - public DateTime Time { get; set; } - - [Column("source_lang")] - public string SourceLang { get; set; } - - [Column("target_lang")] - public string TargetLang { get; set; } - - [Column("source_text")] - public string SourceText { get; set; } - - [Column("target_text")] - public string TargetText { get; set; } - - [Column("api")] - public string Api { get; set; } - - [Column("remark")] - public string Remark { get; set; } - } -} diff --git a/STranslate.Model/packages.config b/STranslate.Model/packages.config deleted file mode 100644 index bf748577..00000000 --- a/STranslate.Model/packages.config +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/STranslate.Settings/App.config b/STranslate.Settings/App.config deleted file mode 100644 index 56efbc7b..00000000 --- a/STranslate.Settings/App.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/STranslate.Settings/App.xaml b/STranslate.Settings/App.xaml deleted file mode 100644 index 6a119fff..00000000 --- a/STranslate.Settings/App.xaml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - diff --git a/STranslate.Settings/App.xaml.cs b/STranslate.Settings/App.xaml.cs deleted file mode 100644 index dc2b164d..00000000 --- a/STranslate.Settings/App.xaml.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Configuration; -using System.Data; -using System.Linq; -using System.Threading.Tasks; -using System.Windows; - -namespace STranslate.Settings -{ - /// - /// App.xaml 的交互逻辑 - /// - public partial class App : Application - { - } -} diff --git a/STranslate.Settings/Properties/AssemblyInfo.cs b/STranslate.Settings/Properties/AssemblyInfo.cs deleted file mode 100644 index 0299b1ea..00000000 --- a/STranslate.Settings/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System.Reflection; -using System.Resources; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Windows; - -// 有关程序集的一般信息由以下 -// 控制。更改这些特性值可修改 -// 与程序集关联的信息。 -[assembly: AssemblyTitle("STranslate.Settings")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("STranslate.Settings")] -[assembly: AssemblyCopyright("Copyright © 2023")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// 将 ComVisible 设置为 false 会使此程序集中的类型 -//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型 -//请将此类型的 ComVisible 特性设置为 true。 -[assembly: ComVisible(false)] - -//若要开始生成可本地化的应用程序,请设置 -//.csproj 文件中的 CultureYouAreCodingWith -//例如,如果您在源文件中使用的是美国英语, -//使用的是美国英语,请将 设置为 en-US。 然后取消 -//对以下 NeutralResourceLanguage 特性的注释。 更新 -//以下行中的“en-US”以匹配项目文件中的 UICulture 设置。 - -//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] - - -[assembly: ThemeInfo( - ResourceDictionaryLocation.None, //主题特定资源词典所处位置 - //(未在页面中找到资源时使用, - //或应用程序资源字典中找到时使用) - ResourceDictionaryLocation.SourceAssembly //常规资源词典所处位置 - //(未在页面中找到资源时使用, - //、应用程序或任何主题专用资源字典中找到时使用) -)] - - -// 程序集的版本信息由下列四个值组成: -// -// 主版本 -// 次版本 -// 生成号 -// 修订号 -// -//可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值 -//通过使用 "*",如下所示: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/STranslate.Settings/Properties/Resources.Designer.cs b/STranslate.Settings/Properties/Resources.Designer.cs deleted file mode 100644 index ba0ccff6..00000000 --- a/STranslate.Settings/Properties/Resources.Designer.cs +++ /dev/null @@ -1,71 +0,0 @@ -//------------------------------------------------------------------------------ -// -// 此代码由工具生成。 -// 运行时版本: 4.0.30319.42000 -// -// 对此文件的更改可能导致不正确的行为,如果 -// 重新生成代码,则所做更改将丢失。 -// -//------------------------------------------------------------------------------ - -namespace STranslate.Settings.Properties -{ - - - /// - /// 强类型资源类,用于查找本地化字符串等。 - /// - // 此类是由 StronglyTypedResourceBuilder - // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 - // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen - // (以 /str 作为命令选项),或重新生成 VS 项目。 - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources - { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() - { - } - - /// - /// 返回此类使用的缓存 ResourceManager 实例。 - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager - { - get - { - if ((resourceMan == null)) - { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("STranslate.Settings.Properties.Resources", typeof(Resources).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// 重写当前线程的 CurrentUICulture 属性,对 - /// 使用此强类型资源类的所有资源查找执行重写。 - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture - { - get - { - return resourceCulture; - } - set - { - resourceCulture = value; - } - } - } -} diff --git a/STranslate.Settings/Properties/Resources.resx b/STranslate.Settings/Properties/Resources.resx deleted file mode 100644 index af7dbebb..00000000 --- a/STranslate.Settings/Properties/Resources.resx +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file diff --git a/STranslate.Settings/Properties/Settings.Designer.cs b/STranslate.Settings/Properties/Settings.Designer.cs deleted file mode 100644 index 8a849651..00000000 --- a/STranslate.Settings/Properties/Settings.Designer.cs +++ /dev/null @@ -1,30 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace STranslate.Settings.Properties -{ - - - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] - internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase - { - - private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); - - public static Settings Default - { - get - { - return defaultInstance; - } - } - } -} diff --git a/STranslate.Settings/Properties/Settings.settings b/STranslate.Settings/Properties/Settings.settings deleted file mode 100644 index 033d7a5e..00000000 --- a/STranslate.Settings/Properties/Settings.settings +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/STranslate.Settings/STranslate.Settings.csproj b/STranslate.Settings/STranslate.Settings.csproj deleted file mode 100644 index debd74c3..00000000 --- a/STranslate.Settings/STranslate.Settings.csproj +++ /dev/null @@ -1,107 +0,0 @@ - - - - - Debug - AnyCPU - {FE4C2553-B695-4DBF-AE4F-24E865DFB608} - WinExe - STranslate.Settings - STranslate.Settings - v4.7.2 - 512 - {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 4 - true - true - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - 4.0 - - - - - - - - MSBuild:Compile - Designer - - - MSBuild:Compile - Designer - - - App.xaml - Code - - - MainWindow.xaml - Code - - - - - Code - - - True - True - Resources.resx - - - True - Settings.settings - True - - - ResXFileCodeGenerator - Resources.Designer.cs - - - SettingsSingleFileGenerator - Settings.Designer.cs - - - - - - - - - - - {a5f60db2-ef06-4f36-a907-c92a64d0557e} - STranslate.Style - - - - \ No newline at end of file diff --git a/STranslate.Settings/View/MainWindow.xaml b/STranslate.Settings/View/MainWindow.xaml deleted file mode 100644 index df9f37cb..00000000 --- a/STranslate.Settings/View/MainWindow.xaml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - diff --git a/STranslate.Style/Commons/BindingProxy.cs b/STranslate.Style/Commons/BindingProxy.cs new file mode 100644 index 00000000..e8e2babd --- /dev/null +++ b/STranslate.Style/Commons/BindingProxy.cs @@ -0,0 +1,29 @@ +using System.Windows; + +namespace STranslate.Style.Commons +{ + /// + /// 绑定代理,尤其用在ContextMenu的MenuItem和ToolTip的绑定失败上 + /// 出处: https://thomaslevesque.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/ + /// + public sealed class BindingProxy : Freezable + { + protected override Freezable CreateInstanceCore() => new BindingProxy(); + + public object Data + { + get => (object)GetValue(DataProperty); + set => SetValue(DataProperty, value); + } + + public static readonly DependencyProperty DataProperty = DependencyProperty.Register( + nameof(Data), + typeof(object), + typeof(BindingProxy), + new PropertyMetadata(default(object)) + ); + + public override string ToString() => + Data is FrameworkElement fe ? $"{nameof(BindingProxy)}: {fe.Name}" : $"{nameof(BindingProxy)}: {Data?.GetType().FullName}"; + } +} diff --git a/STranslate.Style/Commons/ListBoxSelectionBehavior.cs b/STranslate.Style/Commons/ListBoxSelectionBehavior.cs new file mode 100644 index 00000000..a478904b --- /dev/null +++ b/STranslate.Style/Commons/ListBoxSelectionBehavior.cs @@ -0,0 +1,53 @@ +using System.Collections; +using System.Windows; +using System.Windows.Controls; + +namespace STranslate.Style.Commons +{ + public static class ListBoxSelectionBehavior + { + public static readonly DependencyProperty ClickSelectionProperty = + DependencyProperty.RegisterAttached("ClickSelection", typeof(bool), typeof(ListBoxSelectionBehavior), new UIPropertyMetadata(false, OnClickSelectionChanged)); + + public static bool GetClickSelection(DependencyObject obj) + { + return (bool)obj.GetValue(ClickSelectionProperty); + } + + public static void SetClickSelection(DependencyObject obj, bool value) + { + obj.SetValue(ClickSelectionProperty, value); + } + + private static void OnClickSelectionChanged(DependencyObject dpo, DependencyPropertyChangedEventArgs e) + { + if (dpo is ListBox listBox) + { + if ((bool)e.NewValue == true) + { + listBox.SelectionMode = SelectionMode.Multiple; + listBox.SelectionChanged += OnSelectionChanged; + } + else + { + listBox.SelectionChanged -= OnSelectionChanged; + } + } + } + + private static void OnSelectionChanged(object sender, SelectionChangedEventArgs e) + { + if (e.AddedItems.Count > 0 && sender is ListBox listBox) + { + var valid = e.AddedItems[0]; + foreach (var item in new ArrayList(listBox.SelectedItems)) + { + if (item != valid) + { + listBox.SelectedItems.Remove(item); + } + } + } + } + } +} diff --git a/STranslate.Style/Controls/MessageBox_S.xaml b/STranslate.Style/Controls/MessageBox_S.xaml new file mode 100644 index 00000000..8bf55075 --- /dev/null +++ b/STranslate.Style/Controls/MessageBox_S.xaml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 当前版本号: + + + \ No newline at end of file diff --git a/STranslate.Updater/MainWindow.xaml.cs b/STranslate.Updater/MainWindow.xaml.cs new file mode 100644 index 00000000..127e8a94 --- /dev/null +++ b/STranslate.Updater/MainWindow.xaml.cs @@ -0,0 +1,229 @@ +using System.Diagnostics; +using System.IO; +using System.Net.Http; +using System.Windows; +using System.Windows.Controls; + +namespace STranslate.Updater +{ + /// + /// MainWindow.xaml 的交互逻辑 + /// + public partial class MainWindow : Window + { + /// + /// Releases版本地址 + /// + private readonly string ReleasesURL = "https://api.github.com/repos/zggsong/stranslate/releases/latest"; + + /// + /// 新版本保存目录路径 + /// + private readonly string SaveDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory); + + /// + /// 新版本保存名字 + /// + private readonly string SaveName = "update.zip"; + + /// + /// MainViewModel + /// + private readonly MainViewModel vm; + + /// + /// Github相关类 + /// + private readonly GithubRelease githubRelease; + + /// + /// 新版本下载路径 + /// + private string NewVersionZipURL = ""; + + /// + /// 新版本发布页路径 + /// + private string NewVersionURL = ""; + + public MainWindow(string version) + { + InitializeComponent(); + + vm = (MainViewModel)DataContext; + + githubRelease = new GithubRelease(ReleasesURL, version); + + Loaded += Window_Loaded; + } + + private void Window_Loaded(object sender, RoutedEventArgs e) => Check(); + + /// + /// 下载新版软件 + /// + private async void Download() + { + SetStatus("正在下载新版本文件...", false); + + UpdateBtn.Visibility = Visibility.Collapsed; + ReCheckBtn.Visibility = Visibility.Collapsed; + ProgressBar.Visibility = Visibility.Visible; + vm.ProcessValue = 0; + + var httpClient = new HttpClient(); + + try + { + if (!Directory.Exists(SaveDir)) + { + Directory.CreateDirectory(SaveDir); + } + + using (var response = await httpClient.GetAsync(new Uri(NewVersionZipURL), HttpCompletionOption.ResponseHeadersRead)) + using (var stream = await response.Content.ReadAsStreamAsync()) + using (var fileStream = new FileStream(Path.Combine(SaveDir, SaveName), FileMode.Create)) + { + long totalBytes = response.Content.Headers.ContentLength ?? -1; + long totalDownloadedByte = 0; + byte[] buffer = new byte[1024]; + int bytesRead; + + while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0) + { + await fileStream.WriteAsync(buffer, 0, bytesRead); + + totalDownloadedByte += bytesRead; + double process = Math.Round((double)totalDownloadedByte / totalBytes * 100, 2); + vm.ProcessValue = process; + } + } + + // 下载完成后的处理 + await ProcessDownloadedFile(); + } + catch (Exception) + { + // 下载发生异常 + SetStatus("下载时发生异常,请重试。", false); + UpdateBtn.Visibility = Visibility.Visible; + } + finally + { + httpClient.Dispose(); + } + } + + /// + /// 处理下载好的新版软件 + /// + /// + private async Task ProcessDownloadedFile() + { + // 准备更新 + var process = Process.GetProcessesByName("STranslate"); + if (process != null && process.Length > 0) + { + process[0].Kill(); + } + + SetStatus("下载完成,正在解压请勿关闭此窗口..."); + + string unpath = Directory.GetParent(AppDomain.CurrentDomain.BaseDirectory)!.Parent!.FullName; + + var unresult = await Task.Run(async () => + { + await Task.Delay(3000); + return Unzip.ExtractZipFile(Path.Combine(SaveDir, SaveName), unpath); + }); + + if (unresult) + { + SetStatus("更新完成!", false); + Process.Start(Path.Combine(unpath, "STranslate.exe")); + } + else + { + SetStatus("解压文件时发生异常,请重试!通常情况可能是因为 STranslate 主程序尚未退出。", false); + UpdateBtn.Visibility = Visibility.Visible; + } + } + + /// + /// 检查更新 + /// + private async void Check() + { + NewVersionSP.Visibility = Visibility.Collapsed; + PreTag.Visibility = Visibility.Collapsed; + + SetStatus("正在检查更新"); + UpdateBtn.Visibility = Visibility.Collapsed; + ReCheckBtn.IsEnabled = false; + + var info = await githubRelease.GetRequest(); + + if (info != null) + { + if (githubRelease.IsCanUpdate()) + { + UpdateBtn.Visibility = Visibility.Visible; + + NewVersionSP.Visibility = Visibility.Visible; + Version.Text = info.Version; + VersionTitle.Text = info.Title; + NewVersionZipURL = info.DownloadUrl; + NewVersionURL = info.HtmlUrl; + if (info.IsPre) + { + PreTag.Visibility = Visibility.Visible; + } + SetStatus("检测到新的版本!", false); + } + else + { + SetStatus("目前没有可用的更新。", false); + } + } + else + { + SetStatus("无法获取版本信息,请检查代理或网络。", false); + } + ReCheckBtn.IsEnabled = true; + } + + /// + /// 设定状态 + /// + /// + /// + private void SetStatus(string statusText, bool isLoading = true) + { + StatusLabel.Text = statusText; + ProgressBar.IsIndeterminate = isLoading; + if (isLoading) + { + ProgressBar.Visibility = Visibility.Visible; + } + else + { + ProgressBar.Visibility = Visibility.Collapsed; + } + } + + private void ReCheckBtn_Click(object sender, RoutedEventArgs e) + { + Check(); + } + + private void UpdateBtn_Click(object sender, RoutedEventArgs e) + { + Download(); + } + + private void Hyperlink_Click(object sender, RoutedEventArgs e) + { + Process.Start(new ProcessStartInfo { FileName = NewVersionURL, UseShellExecute = true }); + } + } +} diff --git a/STranslate.Updater/STranslate.Updater.csproj b/STranslate.Updater/STranslate.Updater.csproj new file mode 100644 index 00000000..224611bd --- /dev/null +++ b/STranslate.Updater/STranslate.Updater.csproj @@ -0,0 +1,27 @@ + + + + WinExe + net8.0-windows + enable + enable + true + .\favicon.ico + Updater + + + + none + + + + + + + + + Never + + + + diff --git a/STranslate.Updater/Unzip.cs b/STranslate.Updater/Unzip.cs new file mode 100644 index 00000000..f2662892 --- /dev/null +++ b/STranslate.Updater/Unzip.cs @@ -0,0 +1,82 @@ +using System.Diagnostics; +using System.IO; +using System.IO.Compression; + +namespace STranslate.Updater +{ + public class Unzip + { + /// + /// 忽略的文件列表 + /// + private static readonly string[] IgnoreFiles = []; + + public static bool ExtractZipFile(string zipPath, string extractPath) + { + try + { + if (!extractPath.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)) + extractPath += Path.DirectorySeparatorChar; + + using ZipArchive archive = ZipFile.OpenRead(zipPath); + foreach (ZipArchiveEntry entry in archive.Entries) + { + if (!IsIgnoreFile(entry.FullName)) + { + // Gets the full path to ensure that relative segments are removed. + string destinationPath = Path.GetFullPath(Path.Combine(extractPath, entry.FullName)); + if (!IsDir(destinationPath)) + { + // 判断路径是否存在 + var dir = Path.GetDirectoryName(destinationPath); + if (dir != null && !Directory.Exists(dir)) + { + Directory.CreateDirectory(dir); + } + + if (File.Exists(destinationPath)) + { + File.Delete(destinationPath); + } + // Ordinal match is safest, case-sensitive volumes can be mounted within volumes that + // are case-insensitive. + Debug.WriteLine($"抽取:{destinationPath}"); + if (destinationPath.StartsWith(extractPath, StringComparison.Ordinal)) + entry.ExtractToFile(destinationPath); + } + else + { + //创建目录 + Directory.CreateDirectory(destinationPath); + } + } + } + return true; + } + catch + { + return false; + } + } + + /// + /// 指示文件是否是忽略的 + /// + /// + /// + private static bool IsIgnoreFile(string fileName) + { + return (Array.IndexOf(IgnoreFiles, fileName) != -1); + } + + /// + /// 指示路径是否是目录 + /// + /// + /// + private static bool IsDir(string path) + { + return path.Last() == '\\'; + } + } +} \ No newline at end of file diff --git a/STranslate.Updater/favicon.ico b/STranslate.Updater/favicon.ico new file mode 100644 index 00000000..c6032756 Binary files /dev/null and b/STranslate.Updater/favicon.ico differ diff --git a/STranslate.Util/BitmapUtil.cs b/STranslate.Util/BitmapUtil.cs new file mode 100644 index 00000000..29ffdfb2 --- /dev/null +++ b/STranslate.Util/BitmapUtil.cs @@ -0,0 +1,135 @@ +using System; +using System.Drawing.Imaging; +using System.Drawing; +using System.IO; +using System.Windows.Media.Imaging; +using System.Windows; +using System.Windows.Media; + +namespace STranslate.Util +{ + public class BitmapUtil + { + /// + /// 图像变成背景 + /// + /// + /// + public static ImageBrush ConvertBitmap2ImageBrush(Bitmap bmp) + { + ImageBrush brush = new ImageBrush(); + IntPtr hBitmap = bmp.GetHbitmap(); + ImageSource wpfBitmap = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap( + hBitmap, + IntPtr.Zero, + Int32Rect.Empty, + System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions() + ); + brush.ImageSource = wpfBitmap; + return brush; + } + + /// + /// Bitmap转为BitmapSource + /// + /// + /// + public static BitmapSource ConvertBitmap2BitmapSource(Bitmap bitmap) + { + IntPtr ptr = bitmap.GetHbitmap(); //obtain the Hbitmap + BitmapSource? bitmapSource = null; + try + { + bitmapSource = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap( + ptr, + IntPtr.Zero, + Int32Rect.Empty, + BitmapSizeOptions.FromEmptyOptions() + ); + } + finally + { + CommonUtil.DeleteObject(ptr); //release the HBitmap + } + return bitmapSource; + } + + /// + /// BitmapSource转为Bitmap + /// + /// + /// + public static Bitmap ConvertBitmapSource2Bitmap(BitmapSource source) + { + using var stream = new MemoryStream(); + var encoder = new BmpBitmapEncoder(); + encoder.Frames.Add(BitmapFrame.Create(source)); + encoder.Save(stream); + var bmp = new Bitmap(stream); + return bmp; + } + + /// + /// BitmapSource转为byte[] + /// + /// + /// + public static byte[] ConvertBitmapSource2Bytes(BitmapSource bitmapSource) + { + // 可根据需要选择其他编码器 + BitmapEncoder encoder = new BmpBitmapEncoder(); + // 将BitmapSource转换为byte[] + encoder.Frames.Add(BitmapFrame.Create(bitmapSource)); + + using MemoryStream stream = new MemoryStream(); + encoder.Save(stream); + return stream.ToArray(); + } + + /// + /// Bitmap转byte + /// + /// + /// + public static byte[] ConvertBitmap2Bytes(Bitmap bitmap) + { + using MemoryStream stream = new MemoryStream(); + bitmap.Save(stream, ImageFormat.Bmp); + byte[] data = new byte[stream.Length]; + stream.Seek(0, SeekOrigin.Begin); + stream.Read(data, 0, Convert.ToInt32(stream.Length)); + return data; + } + + /// + /// byte转BitmapSource + /// + /// + /// + public static BitmapSource ConvertBytes2BitmapSource(byte[] bytes) + { + using var stream = new MemoryStream(bytes); + stream.Position = 0; + + var img = new BitmapImage(); + img.BeginInit(); + img.CacheOption = BitmapCacheOption.OnLoad; + img.StreamSource = stream; + img.EndInit(); + img.Freeze(); + return img; + } + + /// + /// 是否为图片文件 + /// + /// + /// + public static bool IsImageFile(string filePath) + { + // 根据文件扩展名检查文件是否为图片文件 + string extension = Path.GetExtension(filePath).ToLower(); + return extension == ".jpg" || extension == ".jpeg" || extension == ".png" || extension == ".bmp"; + } + } +} diff --git a/STranslate/Helper/NativeMethodHelper.cs b/STranslate.Util/CommonUtil.cs similarity index 57% rename from STranslate/Helper/NativeMethodHelper.cs rename to STranslate.Util/CommonUtil.cs index 29014235..463ae214 100644 --- a/STranslate/Helper/NativeMethodHelper.cs +++ b/STranslate.Util/CommonUtil.cs @@ -1,11 +1,23 @@ -using System; +using STranslate.Model; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; using System.Runtime.InteropServices; using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using System.Windows.Threading; -namespace STranslate.Helper +namespace STranslate.Util { - internal class NativeMethodHelper + public class CommonUtil { + #region NativeMethod + + #region 鼠标Hook + /// /// 获取进程句柄 /// @@ -14,6 +26,14 @@ internal class NativeMethodHelper [DllImport("kernel32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] public static extern IntPtr GetModuleHandle(string lpModuleName); + [DllImport("user32.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, SetLastError = true)] + public static extern int UnhookWindowsHookEx(int idHook); + + [DllImport("user32.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto)] + public static extern int CallNextHookEx(int idHook, int nCode, int wParam, IntPtr lParam); + + #endregion 鼠标Hook + /// /// 设置窗口在最前端 /// @@ -101,9 +121,6 @@ internal class NativeMethodHelper [DllImport("user32", EntryPoint = "HideCaret")] public static extern bool HideCaret(IntPtr hWnd); - [DllImport("kernel32.dll")] - public static extern bool SetProcessWorkingSetSize(IntPtr process, int minSize, int maxSize); - /// /// 打开剪切板 /// @@ -163,33 +180,13 @@ internal class NativeMethodHelper [DllImport("gdi32.dll")] private static extern int GetDeviceCaps( - IntPtr hdc, // handle to DC - int nIndex // index of capability + IntPtr hdc, // handle to DC + int nIndex // index of capability ); [DllImport("user32.dll", EntryPoint = "ReleaseDC")] private static extern IntPtr ReleaseDC(IntPtr hWnd, IntPtr hDc); - /// - /// 获取系统dpi - /// - /// - public static double GetDpi() - { - double dDpi = 1; - IntPtr desktopDc = GetDC(IntPtr.Zero); - float horizontalDPI = GetDeviceCaps(desktopDc, LOGPIXELSX); - float verticalDPI = GetDeviceCaps(desktopDc, LOGPIXELSY); - int dpi = (int)(horizontalDPI + verticalDPI) / 2; - dDpi = 1 + ((dpi - 96) / 24) * 0.25; - if (dDpi < 1) - { - dDpi = 1; - } - ReleaseDC(IntPtr.Zero, desktopDc); - return dDpi; - } - /// /// 获取窗口标题 /// @@ -218,45 +215,189 @@ public static double GetDpi() [DllImport("user32.dll")] public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hPos, int x, int y, int cx, int cy, uint nflags); - internal enum KeyModifiers + /// + /// 释放掉对象 + /// + /// + /// + [DllImport("gdi32")] + public static extern int DeleteObject(IntPtr obj); + + #endregion NativeMethod + + #region FindControl + + /// + /// 在UserControl中查找控件的通用方法 + /// + /// + /// + /// + /// + public static T? FindControlByName(DependencyObject parent, string name) + where T : FrameworkElement + { + int childCount = VisualTreeHelper.GetChildrenCount(parent); + + for (int i = 0; i < childCount; i++) + { + DependencyObject child = VisualTreeHelper.GetChild(parent, i); + + if (child is T frameworkElement && frameworkElement.Name == name) + { + return frameworkElement; + } + + T? result = FindControlByName(child, name); + if (result != null) + { + return result; + } + } + + return null; + } + + /// + /// 在窗口中查找UserControl的通用方法 + /// + /// + /// + /// + public static UserControl? FindUserControlByName(DependencyObject parent, string name) + { + int childCount = VisualTreeHelper.GetChildrenCount(parent); + + for (int i = 0; i < childCount; i++) + { + DependencyObject child = VisualTreeHelper.GetChild(parent, i); + + if (child is UserControl userControl && userControl.Name == name) + { + return userControl; + } + + UserControl? result = FindUserControlByName(child, name); + if (result != null) + { + return result; + } + } + + return null; + } + + #endregion FindControl + + #region Other + + /// + /// 使用UI线程执行 + /// + /// + public static void InvokeOnUIThread(Action action) { - MOD_NONE = 0x0, - MOD_ALT = 0x1, - MOD_CTRL = 0x2, - MOD_SHIFT = 0x4, - MOD_WIN = 0x8 + if (Application.Current?.Dispatcher is null) + Dispatcher.CurrentDispatcher.BeginInvoke(action, new object[0]); + else + Application.Current?.Dispatcher.BeginInvoke(action); } - #region Clipboard + /// + /// 获取系统dpi + /// + /// + public static double GetDpi() + { + double dDpi = 1; + IntPtr desktopDc = GetDC(IntPtr.Zero); + float horizontalDPI = GetDeviceCaps(desktopDc, LOGPIXELSX); + float verticalDPI = GetDeviceCaps(desktopDc, LOGPIXELSY); + int dpi = (int)(horizontalDPI + verticalDPI) / 2; + dDpi = 1 + ((dpi - 96) / 24) * 0.25; + if (dDpi < 1) + { + dDpi = 1; + } + ReleaseDC(IntPtr.Zero, desktopDc); + return dDpi; + } - internal static void SetText(string text) + /// + /// 执行程序 + /// + /// + /// + /// + public static bool ExecuteProgram(string filename, string[] args) { - if (!OpenClipboard(IntPtr.Zero)) + try + { + string arguments = ""; + foreach (string arg in args) + { + arguments += $"\"{arg}\" "; + } + arguments = arguments.Trim(); + Process process = new(); + ProcessStartInfo startInfo = new(filename, arguments); + process.StartInfo = startInfo; + process.Start(); + return true; + } + catch (Exception) { - SetText(text); - return; + return false; } - EmptyClipboard(); - SetClipboardData(13, Marshal.StringToHGlobalUni(text)); - CloseClipboard(); } - internal static string GetText() + /// + /// 枚举信息 + /// + /// + /// + public static Dictionary GetEnumList() + where T : Enum + { + var dict = new Dictionary(); + List list = Enum.GetValues(typeof(T)).OfType().ToList(); + list.ForEach(x => + { + dict.Add(x.GetDescription(), x); + }); + return dict; + } + + /// + /// 获取鼠标位置 + /// + /// + public static System.Windows.Point GetMousePositionWindowsForms() { - string value = string.Empty; - OpenClipboard(IntPtr.Zero); - if (IsClipboardFormatAvailable(13)) + // 获取鼠标所在屏幕 + System.Drawing.Point ms = System.Windows.Forms.Control.MousePosition; + Rect bounds = new Rect(); + double dpiScale = 1; + int x = 0, + y = 0, + width = 0, + height = 0; + foreach (WpfScreenHelper.Screen screen in WpfScreenHelper.Screen.AllScreens) { - IntPtr ptr = GetClipboardData(13); - if (ptr != IntPtr.Zero) + bounds = screen.WpfBounds; + dpiScale = screen.ScaleFactor; + x = (int)(bounds.X * dpiScale); + y = (int)(bounds.Y * dpiScale); + width = (int)(bounds.Width * dpiScale); + height = (int)(bounds.Height * dpiScale); + if (x <= ms.X && ms.X < x + width && y <= ms.Y && ms.Y < y + height) { - value = Marshal.PtrToStringUni(ptr); + break; } } - CloseClipboard(); - return value; + return new System.Windows.Point(ms.X / dpiScale, ms.Y / dpiScale); } - #endregion Clipboard + #endregion Other } } diff --git a/STranslate.Util/GetWordsUtil.cs b/STranslate.Util/GetWordsUtil.cs new file mode 100644 index 00000000..8ad7615e --- /dev/null +++ b/STranslate.Util/GetWordsUtil.cs @@ -0,0 +1,115 @@ +using System; +using System.Runtime.InteropServices; +using System.Windows.Input; + +namespace STranslate.Util +{ + public class GetWordsUtil + { + public static string Get() + { + SendCtrlC(); + System.Threading.Thread.Sleep(100); + + return GetText(); + } + + public static string MouseSlidGet() + { + var oldTxt = GetText(); + SendCtrlC(); + System.Threading.Thread.Sleep(100); + + //为了鼠标划词做对比 + var newTxt = GetText(); + return newTxt == oldTxt ? string.Empty : newTxt.Trim(); + } + + [Obsolete] + /// + /// 可能引起崩溃 + /// + /// + /// + public static string WIN32SetText(string text) + { + SetText(text, out string ret); + return ret; + } + + private static void SendCtrlC() + { + //IntPtr hWnd = GetForegroundWindow(); + //SetForegroundWindow(hWnd); + uint KEYEVENTF_KEYUP = 2; + + CommonUtil.keybd_event(System.Windows.Forms.Keys.ControlKey, 0, KEYEVENTF_KEYUP, 0); + CommonUtil.keybd_event(KeyInterop.VirtualKeyFromKey(Key.LeftAlt), 0, KEYEVENTF_KEYUP, 0); + CommonUtil.keybd_event(KeyInterop.VirtualKeyFromKey(Key.RightAlt), 0, KEYEVENTF_KEYUP, 0); + CommonUtil.keybd_event(System.Windows.Forms.Keys.LWin, 0, KEYEVENTF_KEYUP, 0); + CommonUtil.keybd_event(System.Windows.Forms.Keys.RWin, 0, KEYEVENTF_KEYUP, 0); + CommonUtil.keybd_event(System.Windows.Forms.Keys.ShiftKey, 0, KEYEVENTF_KEYUP, 0); + + CommonUtil.keybd_event(System.Windows.Forms.Keys.ControlKey, 0, 0, 0); + CommonUtil.keybd_event(System.Windows.Forms.Keys.C, 0, 0, 0); + CommonUtil.keybd_event(System.Windows.Forms.Keys.C, 0, KEYEVENTF_KEYUP, 0); + CommonUtil.keybd_event(System.Windows.Forms.Keys.ControlKey, 0, KEYEVENTF_KEYUP, 0);// 'Left Control Up + } + + #region Clipboard + + /// + /// 向剪贴板中添加文本 + /// + /// 文本 + internal static void SetText(string text, out string error) + { + try + { + if (!CommonUtil.OpenClipboard(IntPtr.Zero)) + { + throw new InvalidOperationException("Unable to open the clipboard."); + } + CommonUtil.EmptyClipboard(); + + // 获取 Unicode 文本格式的常量 + int cfUnicodeText = 13; // CF_UNICODETEXT + // 将文本分配到非托管内存 + IntPtr hGlobal = Marshal.StringToHGlobalUni(text); + // 将文本添加到剪贴板 + CommonUtil.SetClipboardData(cfUnicodeText, hGlobal); + // 关闭剪贴板 + CommonUtil.CloseClipboard(); + + // 在使用后释放非托管内存 + Marshal.FreeHGlobal(hGlobal); + + error = ""; + } + catch (Exception ex) + { + // 处理异常,例如记录错误信息 + Console.WriteLine($"Error: {ex.Message}"); + error = ex.Message; + } + } + + internal static string GetText() + { + string value = string.Empty; + CommonUtil.OpenClipboard(IntPtr.Zero); + if (CommonUtil.IsClipboardFormatAvailable(13)) + { + IntPtr ptr = CommonUtil.GetClipboardData(13); + if (ptr != IntPtr.Zero) + { + value = Marshal.PtrToStringUni(ptr) ?? string.Empty; + } + } + CommonUtil.CloseClipboard(); + return value; + } + + #endregion Clipboard + } +} \ No newline at end of file diff --git a/STranslate.Util/HttpUtil.cs b/STranslate.Util/HttpUtil.cs new file mode 100644 index 00000000..8c6e863a --- /dev/null +++ b/STranslate.Util/HttpUtil.cs @@ -0,0 +1,148 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Http; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace STranslate.Util +{ + public class HttpUtil + { + /// + /// 첽Get(Token) + /// + /// + /// + /// + public static async Task GetAsync(string url, int timeout = 10) => await GetAsync(url, CancellationToken.None, timeout); + + /// + /// 첽Get + /// + /// + /// + /// + public static async Task GetAsync(string url, CancellationToken token, int timeout = 10) + { + using var client = new HttpClient() + { + Timeout = TimeSpan.FromSeconds(timeout), + }; + + try + { + var respContent = await client.GetAsync(url, token); + + string respStr = await respContent.Content.ReadAsStringAsync(token); + + return respStr; + } + catch (Exception) + { + throw; + } + } + + /// + /// 첽Get󣬴ѯ + /// + /// URL + /// ѯֵ + /// ȡ + /// ʱʱ䣨룩 + /// + public static async Task GetAsync(string url, Dictionary queryParams, CancellationToken token, int timeout = 10) + { + using var client = new HttpClient() + { + Timeout = TimeSpan.FromSeconds(timeout), + }; + + try + { + // ѯURL + if (queryParams != null && queryParams.Count > 0) + { + var queryBuilder = new StringBuilder(); + foreach (var kvp in queryParams) + { + queryBuilder.Append(Uri.EscapeDataString(kvp.Key)); + queryBuilder.Append("="); + queryBuilder.Append(Uri.EscapeDataString(kvp.Value)); + queryBuilder.Append("&"); + } + + string queryString = queryBuilder.ToString().TrimEnd('&'); + url += "?" + queryString; + } + + var respContent = await client.GetAsync(url, token); + + string respStr = await respContent.Content.ReadAsStringAsync(token); + + return respStr; + } + catch (Exception) + { + throw; + } + } + + + /// + /// 첽Post(Token) + /// + /// + /// + /// + public static async Task PostAsync(string url, string req, int timeout = 10) => await PostAsync(url, req, CancellationToken.None, timeout); + + /// + /// 첽Post + /// + /// + /// + /// + /// + public static async Task PostAsync(string url, string req, CancellationToken token, int timeout = 10) + { + using var client = new HttpClient() + { + Timeout = TimeSpan.FromSeconds(timeout), + }; + + try + { + var content = new StringContent(req, Encoding.UTF8, "application/json"); + + var respContent = await client.PostAsync(url, content, token); + + string respStr = await respContent.Content.ReadAsStringAsync(token); + + return respStr; + } + catch (Exception) + { + throw; + } + } + + /// + /// ֧ϵͳ + /// + public static void SupportSystemAgent() + { + WebRequest.DefaultWebProxy = WebRequest.GetSystemWebProxy(); + WebRequest.DefaultWebProxy.Credentials = CredentialCache.DefaultCredentials; + } + //TODO: һʼûпϵͳٴϵͳȻ + //LogService.Logger.Info("START"); + //var ret = await HttpUtil.GetAsync("https://rsshub.zggsong.workers.dev/", timeout: 10); + //LogService.Logger.Info(ret); + //LogService.Logger.Info("END"); + //return; + } +} diff --git a/STranslate.Util/MemoUtil.cs b/STranslate.Util/MemoUtil.cs new file mode 100644 index 00000000..267a8a50 --- /dev/null +++ b/STranslate.Util/MemoUtil.cs @@ -0,0 +1,61 @@ +using System; +using System.Runtime.InteropServices; +using System.Threading; + +namespace STranslate.Util +{ + public class MemoUtil + { + [DllImport("kernel32.dll")] + private static extern bool SetProcessWorkingSetSize(IntPtr process, int minSize, int maxSize); + + /// + /// 释放占用内存并重新分配,将暂时不需要的内容放进虚拟内存 + /// 当应用程序重新激活时,会将虚拟内存的内容重新加载到内存。 + /// 不宜过度频繁的调用该方法,频繁调用会降低使使用性能。 + /// 可在Close、Hide、最小化页面时调用此方法, + /// + public static void FlushMemory() + { + GC.Collect(); + // GC还提供了WaitForPendingFinalizers方法。 + GC.WaitForPendingFinalizers(); + if (Environment.OSVersion.Platform == PlatformID.Win32NT) + { + SetProcessWorkingSetSize(System.Diagnostics.Process.GetCurrentProcess().Handle, -1, -1); + } + } + + /// + /// GC回收,释放占用内存并重新分配 + /// + /// 是否将不需要的内容放进虚拟内存 + /// 定时 + public static void CrackerOnlyGC(bool viltualMemo = false, int sleepSpan = 30) + { + new Thread(s => + { + while (true) + { + try + { + if (viltualMemo) + { + FlushMemory(); + } + else + { + GC.Collect(); + GC.WaitForPendingFinalizers(); + } + Thread.Sleep(TimeSpan.FromSeconds((double)sleepSpan)); + } + catch (Exception) { } + } + }) + { + IsBackground = true + }.Start(); + } + } +} \ No newline at end of file diff --git a/STranslate.Util/MouseHookUtil.cs b/STranslate.Util/MouseHookUtil.cs new file mode 100644 index 00000000..9c77d927 --- /dev/null +++ b/STranslate.Util/MouseHookUtil.cs @@ -0,0 +1,268 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Windows.Forms; + +namespace STranslate.Util +{ + public class MouseHookUtil : GlobalHook + { + private enum MouseEventType + { + None, + MouseDown, + MouseUp, + DoubleClick, + MouseWheel, + MouseMove + } + + public event MouseEventHandler? MouseDown; + + public event MouseEventHandler? MouseUp; + + public event MouseEventHandler? MouseMove; + + public event MouseEventHandler? MouseWheel; + + public event EventHandler? Click; + + public event EventHandler? DoubleClick; + + public MouseHookUtil() + { + _hookType = 14; + } + + protected override int HookCallbackProcedure(int nCode, int wParam, IntPtr lParam) + { + if (nCode > -1 && (MouseDown != null || MouseUp != null || MouseMove != null)) + { + MouseLLHookStruct mouseLLHookStruct = (MouseLLHookStruct)Marshal.PtrToStructure(lParam, typeof(MouseLLHookStruct))!; + MouseButtons button = GetButton(wParam); + MouseEventType mouseEventType = GetEventType(wParam); + MouseEventArgs e = new MouseEventArgs( + button, + (mouseEventType != MouseEventType.DoubleClick) ? 1 : 2, + mouseLLHookStruct.pt.x, + mouseLLHookStruct.pt.y, + (mouseEventType == MouseEventType.MouseWheel) ? ((short)((mouseLLHookStruct.mouseData >> 16) & 0xFFFF)) : 0 + ); + if (button == MouseButtons.Right && mouseLLHookStruct.flags != 0) + { + mouseEventType = MouseEventType.None; + } + switch (mouseEventType) + { + case MouseEventType.MouseDown: + MouseDown?.Invoke(this, e); + break; + + case MouseEventType.MouseUp: + Click?.Invoke(this, new EventArgs()); + MouseUp?.Invoke(this, e); + break; + + case MouseEventType.DoubleClick: + DoubleClick?.Invoke(this, new EventArgs()); + break; + + case MouseEventType.MouseWheel: + MouseWheel?.Invoke(this, e); + break; + + case MouseEventType.MouseMove: + MouseMove?.Invoke(this, e); + break; + } + } + return CommonUtil.CallNextHookEx(_handleToHook, nCode, wParam, lParam); + } + + private MouseButtons GetButton(int wParam) => + wParam switch + { + 513 or 514 or 515 => MouseButtons.Left, + 516 or 517 or 518 => MouseButtons.Right, + 519 or 520 or 521 => MouseButtons.Middle, + _ => MouseButtons.None, + }; + + private MouseEventType GetEventType(int wParam) => + wParam switch + { + 513 or 516 or 519 => MouseEventType.MouseDown, + 514 or 517 or 520 => MouseEventType.MouseUp, + 515 or 518 or 521 => MouseEventType.DoubleClick, + 522 => MouseEventType.MouseWheel, + 512 => MouseEventType.MouseMove, + _ => MouseEventType.None, + }; + } + + public abstract class GlobalHook + { + [StructLayout(LayoutKind.Sequential)] + protected class POINT + { + public int x; + + public int y; + } + + [StructLayout(LayoutKind.Sequential)] + protected class MouseHookStruct + { + public POINT pt = new(); + + public int hwnd; + + public int wHitTestCode; + + public int dwExtraInfo; + } + + [StructLayout(LayoutKind.Sequential)] + protected class MouseLLHookStruct + { + public POINT pt = new(); + + public int mouseData; + + public int flags; + + public int time; + + public int dwExtraInfo; + } + + [StructLayout(LayoutKind.Sequential)] + protected class KeyboardHookStruct + { + public int vkCode; + + public int scanCode; + + public int flags; + + public int time; + + public int dwExtraInfo; + } + + protected const int WH_MOUSE_LL = 14; + + protected const int WH_KEYBOARD_LL = 13; + + protected const int WH_MOUSE = 7; + + protected const int WH_KEYBOARD = 2; + + protected const int WM_MOUSEMOVE = 512; + + protected const int WM_LBUTTONDOWN = 513; + + protected const int WM_RBUTTONDOWN = 516; + + protected const int WM_MBUTTONDOWN = 519; + + protected const int WM_LBUTTONUP = 514; + + protected const int WM_RBUTTONUP = 517; + + protected const int WM_MBUTTONUP = 520; + + protected const int WM_LBUTTONDBLCLK = 515; + + protected const int WM_RBUTTONDBLCLK = 518; + + protected const int WM_MBUTTONDBLCLK = 521; + + protected const int WM_MOUSEWHEEL = 522; + + protected const int WM_KEYDOWN = 256; + + protected const int WM_KEYUP = 257; + + protected const int WM_SYSKEYDOWN = 260; + + protected const int WM_SYSKEYUP = 261; + + protected const byte VK_SHIFT = 16; + + protected const byte VK_CAPITAL = 20; + + protected const byte VK_NUMLOCK = 144; + + protected const byte VK_LSHIFT = 160; + + protected const byte VK_RSHIFT = 161; + + protected const byte VK_LCONTROL = 162; + + protected const byte VK_RCONTROL = 3; + + protected const byte VK_LALT = 164; + + protected const byte VK_RALT = 165; + + protected const byte LLKHF_ALTDOWN = 32; + + protected int _hookType; + + protected int _handleToHook; + + public bool _isStarted; + + protected HookProc? _hookCallback; + + protected delegate int HookProc(int nCode, int wParam, IntPtr lParam); + + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] + private static extern IntPtr SetWindowsHookExW(int idHook, HookProc lpfn, IntPtr hmod, uint dwThreadID); + + public bool IsStarted => _isStarted; + + public GlobalHook() + { + Application.ApplicationExit += Application_ApplicationExit; + } + + public void Start() + { + if (_isStarted || _hookType == 0) + { + return; + } + _hookCallback = HookCallbackProcedure; + using (Process process = Process.GetCurrentProcess()) + { + using ProcessModule processModule = process.MainModule!; + _handleToHook = (int)SetWindowsHookExW(_hookType, _hookCallback, CommonUtil.GetModuleHandle(processModule.ModuleName), 0u); + } + if (_handleToHook != 0) + { + _isStarted = true; + } + } + + public void Stop() + { + if (_isStarted) + { + CommonUtil.UnhookWindowsHookEx(_handleToHook); + _isStarted = false; + } + } + + protected virtual int HookCallbackProcedure(int nCode, int wParam, IntPtr lParam) => 0; + + protected void Application_ApplicationExit(object? sender, EventArgs e) + { + if (_isStarted) + { + Stop(); + } + } + } +} diff --git a/STranslate.Util/ObjectExtensions.cs b/STranslate.Util/ObjectExtensions.cs new file mode 100644 index 00000000..6302023a --- /dev/null +++ b/STranslate.Util/ObjectExtensions.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; + +namespace STranslate.Util +{ + public static class ObjectExtensions + { + public static T DeepClone(this T source) + { + if (source == null) + return default!; + + var json = JsonConvert.SerializeObject(source); + return JsonConvert.DeserializeObject(json)!; + } + } +} diff --git a/STranslate.Util/STranslate.Util.csproj b/STranslate.Util/STranslate.Util.csproj new file mode 100644 index 00000000..fb1fda01 --- /dev/null +++ b/STranslate.Util/STranslate.Util.csproj @@ -0,0 +1,36 @@ + + + + net8.0-windows + enable + true + true + + + + none + + + + + tlbimp + 0 + 1 + f935dc20-1cf0-11d0-adb9-00c04fd58a0b + 0 + false + true + + + + + + + + + + + + + + diff --git a/STranslate/Helper/ShortcutHelper.cs b/STranslate.Util/ShortcutUtil.cs similarity index 88% rename from STranslate/Helper/ShortcutHelper.cs rename to STranslate.Util/ShortcutUtil.cs index ba45df43..d492f9fb 100644 --- a/STranslate/Helper/ShortcutHelper.cs +++ b/STranslate.Util/ShortcutUtil.cs @@ -1,15 +1,15 @@ -using System; +using IWshRuntimeLibrary; +using System; using System.Collections.Generic; using System.IO; using System.Reflection; -using IWshRuntimeLibrary; -namespace STranslate.Helper +namespace STranslate.Util { - public class ShortcutHelper + public class ShortcutUtil { - #region public method + /// /// 设置开机自启 /// @@ -17,6 +17,7 @@ public static void SetStartup() { ShortCutCreate(); } + /// /// 检查是否已经设置开机自启 /// @@ -25,6 +26,7 @@ public static bool IsStartup() { return ShortCutExist(appPath, StartUpPath); } + /// /// 取消开机自启 /// @@ -32,6 +34,7 @@ public static void UnSetStartup() { ShortCutDelete(appPath, StartUpPath); } + /// /// 设置桌面快捷方式 /// @@ -39,9 +42,11 @@ public static void SetDesktopShortcut() { ShortCutCreate(true); } - #endregion + + #endregion public method #region params + /// /// 开机启动目录 /// @@ -55,24 +60,32 @@ public static void SetDesktopShortcut() /// /// 当前程序二进制文件路径 /// - private static readonly string appPath = Assembly.GetEntryAssembly().Location; + private static readonly string appPath = Assembly.GetEntryAssembly()!.Location.Replace(".dll", ".exe"); /// /// 组合的开机启动目录中的快捷方式路径 /// - private static readonly string appShortcutPath = Path.Combine(StartUpPath, Path.GetFileNameWithoutExtension(appPath) + ".lnk"); + private static readonly string appShortcutPath = Path.Combine( + StartUpPath, + Path.GetFileNameWithoutExtension(appPath) + ".lnk" + ); + + private static readonly string desktopShortcutPath = Path.Combine( + DesktopPath, + Path.GetFileNameWithoutExtension(appPath) + ".lnk" + ); - private static readonly string desktopShortcutPath = Path.Combine(DesktopPath, Path.GetFileNameWithoutExtension(appPath) + ".lnk"); - #endregion + #endregion params #region native method + /// /// 获取快捷方式中的目标(可执行文件的绝对路径) /// /// 快捷方式的绝对路径 /// /// 需引入 COM 组件 Windows Script Host Object Model - private static string GetAppPathViaShortCut(string shortCutPath) + private static string? GetAppPathViaShortCut(string shortCutPath) { try { @@ -149,6 +162,7 @@ private static bool ShortCutDelete(string path, string target) } return Result; } + /// /// 为本程序创建一个快捷方式 /// @@ -156,20 +170,20 @@ private static bool ShortCutDelete(string path, string target) /// private static bool ShortCutCreate(bool isDesktop = false) { - bool Result = false; + bool Result; try { if (!isDesktop) ShortCutDelete(appPath, StartUpPath); - var shellType = Type.GetTypeFromProgID("WScript.Shell"); - dynamic shell = Activator.CreateInstance(shellType); + var shellType = Type.GetTypeFromProgID("WScript.Shell")!; + dynamic shell = Activator.CreateInstance(shellType)!; IWshShortcut shortcut; if (!isDesktop) - shortcut = shell.CreateShortcut(appShortcutPath); + shortcut = shell!.CreateShortcut(appShortcutPath); else - shortcut = shell.CreateShortcut(desktopShortcutPath); - shortcut.TargetPath = Assembly.GetEntryAssembly().Location; + shortcut = shell!.CreateShortcut(desktopShortcutPath); + shortcut.TargetPath = Assembly.GetEntryAssembly()!.Location.Replace(".dll", ".exe"); shortcut.Arguments = string.Empty; shortcut.WorkingDirectory = AppDomain.CurrentDomain.SetupInformation.ApplicationBase; shortcut.Save(); @@ -181,6 +195,7 @@ private static bool ShortCutCreate(bool isDesktop = false) } return Result; } - #endregion + + #endregion native method } -} +} \ No newline at end of file diff --git a/STranslate.Util/SingletonUtil.cs b/STranslate.Util/SingletonUtil.cs new file mode 100644 index 00000000..f85c1839 --- /dev/null +++ b/STranslate.Util/SingletonUtil.cs @@ -0,0 +1,15 @@ +using System; + +namespace STranslate.Util +{ + /// + /// 单例模式 + /// + /// + public class Singleton where T : class, new() + { + private static readonly Lazy _instance = new(() => (T)Activator.CreateInstance(typeof(T), true)!, true); + + public static T Instance => _instance.Value; + } +} diff --git a/STranslate.Util/StringUtil.cs b/STranslate.Util/StringUtil.cs new file mode 100644 index 00000000..6337ff1d --- /dev/null +++ b/STranslate.Util/StringUtil.cs @@ -0,0 +1,171 @@ +using STranslate.Model; +using System; +using System.Collections.Generic; +using System.Security.Cryptography; +using System.Text; +using System.Text.RegularExpressions; + +namespace STranslate.Util +{ + public class StringUtil + { + /// + /// 计算MD5值 + /// + /// + /// + public static string EncryptString(string str) + { + MD5 md5 = MD5.Create(); + // 将字符串转换成字节数组 + byte[] byteOld = Encoding.UTF8.GetBytes(str); + // 调用加密方法 + byte[] byteNew = md5.ComputeHash(byteOld); + // 将加密结果转换为字符串 + StringBuilder sb = new StringBuilder(); + foreach (byte b in byteNew) + { + // 将字节转换成16进制表示的字符串, + sb.Append(b.ToString("x2")); + } + // 返回加密的字符串 + return sb.ToString(); + } + + /// + /// 构造蛇形结果 + /// + /// + /// + public static string GenSnakeString(List req) + { + var ret = string.Empty; + + req.ForEach(x => + { + ret += "_" + x.ToLower(); + }); + return ret[1..]; + } + + /// + /// 构造驼峰结果 + /// + /// + /// 是否为小驼峰 + /// + public static string GenHumpString(List req, bool isSmallHump) + { + try + { + string ret = string.Empty; + var array = req.ToArray(); + for (var j = 0; j < array.Length; j++) + { + char[] chars = array[j].ToCharArray(); + //判断chars是否为空 + if (chars.Length == 0) + continue; + if (j == 0 && isSmallHump) + chars[0] = char.ToLower(chars[0]); + else + chars[0] = char.ToUpper(chars[0]); + for (int i = 1; i < chars.Length; i++) + { + chars[i] = char.ToLower(chars[i]); + } + ret += new string(chars); + } + return ret; + } + catch (Exception ex) + { + throw new Exception("[GENHUMP] 构造驼峰异常", ex); + } + } + + /// + /// 提取英文 + /// + /// + /// + public static string ExtractEngString(string str) + { + Regex regex = new Regex("[a-zA-Z]+"); + + MatchCollection mMactchCol = regex.Matches(str); + string strA_Z = string.Empty; + foreach (Match mMatch in mMactchCol) + { + strA_Z += mMatch.Value; + } + return strA_Z; + } + + /// + /// 划词文本预处理,例如PDF文字复制出来总含有很多多余的空格 + /// 使用正则表达式[\\s]+匹配连续的空白字符(包括空格、制表符、换行符等),并将其替换为单个空格字符 + /// + /// + /// + public static string PreProcessTexts(string text) + { + try + { + text = new Regex("[\\s]+").Replace(text, " "); + } + catch (Exception) + { + text = string.Empty; + } + return text.Trim(); + } + + /// + /// 自动识别语种 + /// + /// 输入语言 + /// 英文占比 + /// + /// Item1: SourceLang + /// Item2: TargetLang + /// + public static Tuple AutomaticLanguageRecognition(string text, double scale = 0.8) + { + //1. 首先去除所有数字、标点及特殊符号 + //https://www.techiedelight.com/zh/strip-punctuations-from-a-string-in-csharp/ + text = Regex.Replace(text, + "[1234567890!\"#$%&'()*+,-./:;<=>?@\\[\\]^_`{|}~,。、《》?;‘’:“”【】、{}|·!@#¥%……&*()——+~\\\\]", + string.Empty).Replace(Environment.NewLine, "").Replace(" ", ""); + + //2. 取出上一步中所有英文字符 + var engStr = ExtractEngString(text); + + var ratio = (double)engStr.Length / text.Length; + + //3. 判断英文字符个数占第一步所有字符个数比例,若超过一定比例则判定原字符串为英文字符串,否则为中文字符串 + if (ratio > scale) + { + return new Tuple(LanguageEnum.EN.GetDescription(), LanguageEnum.ZH.GetDescription()); + } + else + { + return new Tuple(LanguageEnum.ZH.GetDescription(), LanguageEnum.EN.GetDescription()); + } + } + + /// + /// 移除换行 + /// + /// + /// + public static string RemoveLineBreaks(string content) => content.Replace("\r\n", " ").Replace("\n", " ").Replace("\r", " "); + + /// + /// 移除空格 + /// + /// + /// + public static string RemoveSpace(string content) => content.Replace(" ", ""); + } +} \ No newline at end of file diff --git a/STranslate.sln b/STranslate.sln index 22444b08..471764c7 100644 --- a/STranslate.sln +++ b/STranslate.sln @@ -1,17 +1,26 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 -VisualStudioVersion = 17.5.33516.290 +VisualStudioVersion = 17.7.34024.191 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "STranslate", "STranslate\STranslate.csproj", "{2597B480-185C-4D6D-94EC-8641BB6777F5}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "STranslate", "STranslate\STranslate.csproj", "{054D2232-9544-4461-BEB7-B0565A66D27C}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Updater", "Updater\Updater.csproj", "{A49C9514-81CA-4FB6-A586-17477E4F28A8}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionItems", "SolutionItems", "{8B472084-6B63-44BA-9D4A-B97576810AD2}" + ProjectSection(SolutionItems) = preProject + .csharpierrc.json = .csharpierrc.json + .gitignore = .gitignore + LICENSE = LICENSE + EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "STranslate.Settings", "STranslate.Settings\STranslate.Settings.csproj", "{FE4C2553-B695-4DBF-AE4F-24E865DFB608}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "STranslate.Style", "STranslate.Style\STranslate.Style.csproj", "{420EAA87-5CA1-4117-94E0-B070FCCD06C2}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "STranslate.Style", "STranslate.Style\STranslate.Style.csproj", "{A5F60DB2-EF06-4F36-A907-C92A64D0557E}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "STranslate.Model", "STranslate.Model\STranslate.Model.csproj", "{B16A7D67-D3A5-418B-8E49-2C85269566D8}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "STranslate.Model", "STranslate.Model\STranslate.Model.csproj", "{A65F547F-52E2-4AD5-8C8B-53710E1485FA}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "STranslate.Util", "STranslate.Util\STranslate.Util.csproj", "{74E3C5AB-6702-4AAC-8D56-2A72EFD437D2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "STranslate.Log", "STranslate.Log\STranslate.Log.csproj", "{92255394-9202-40FF-941F-C617A9CC8739}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "STranslate.Updater", "STranslate.Updater\STranslate.Updater.csproj", "{409A6A79-30E0-42EF-B4AF-C409980FC4AF}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -19,31 +28,35 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {2597B480-185C-4D6D-94EC-8641BB6777F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2597B480-185C-4D6D-94EC-8641BB6777F5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2597B480-185C-4D6D-94EC-8641BB6777F5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2597B480-185C-4D6D-94EC-8641BB6777F5}.Release|Any CPU.Build.0 = Release|Any CPU - {A49C9514-81CA-4FB6-A586-17477E4F28A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A49C9514-81CA-4FB6-A586-17477E4F28A8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A49C9514-81CA-4FB6-A586-17477E4F28A8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A49C9514-81CA-4FB6-A586-17477E4F28A8}.Release|Any CPU.Build.0 = Release|Any CPU - {FE4C2553-B695-4DBF-AE4F-24E865DFB608}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FE4C2553-B695-4DBF-AE4F-24E865DFB608}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FE4C2553-B695-4DBF-AE4F-24E865DFB608}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FE4C2553-B695-4DBF-AE4F-24E865DFB608}.Release|Any CPU.Build.0 = Release|Any CPU - {A5F60DB2-EF06-4F36-A907-C92A64D0557E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A5F60DB2-EF06-4F36-A907-C92A64D0557E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A5F60DB2-EF06-4F36-A907-C92A64D0557E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A5F60DB2-EF06-4F36-A907-C92A64D0557E}.Release|Any CPU.Build.0 = Release|Any CPU - {A65F547F-52E2-4AD5-8C8B-53710E1485FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A65F547F-52E2-4AD5-8C8B-53710E1485FA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A65F547F-52E2-4AD5-8C8B-53710E1485FA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A65F547F-52E2-4AD5-8C8B-53710E1485FA}.Release|Any CPU.Build.0 = Release|Any CPU + {054D2232-9544-4461-BEB7-B0565A66D27C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {054D2232-9544-4461-BEB7-B0565A66D27C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {054D2232-9544-4461-BEB7-B0565A66D27C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {054D2232-9544-4461-BEB7-B0565A66D27C}.Release|Any CPU.Build.0 = Release|Any CPU + {420EAA87-5CA1-4117-94E0-B070FCCD06C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {420EAA87-5CA1-4117-94E0-B070FCCD06C2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {420EAA87-5CA1-4117-94E0-B070FCCD06C2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {420EAA87-5CA1-4117-94E0-B070FCCD06C2}.Release|Any CPU.Build.0 = Release|Any CPU + {B16A7D67-D3A5-418B-8E49-2C85269566D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B16A7D67-D3A5-418B-8E49-2C85269566D8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B16A7D67-D3A5-418B-8E49-2C85269566D8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B16A7D67-D3A5-418B-8E49-2C85269566D8}.Release|Any CPU.Build.0 = Release|Any CPU + {74E3C5AB-6702-4AAC-8D56-2A72EFD437D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {74E3C5AB-6702-4AAC-8D56-2A72EFD437D2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {74E3C5AB-6702-4AAC-8D56-2A72EFD437D2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {74E3C5AB-6702-4AAC-8D56-2A72EFD437D2}.Release|Any CPU.Build.0 = Release|Any CPU + {92255394-9202-40FF-941F-C617A9CC8739}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {92255394-9202-40FF-941F-C617A9CC8739}.Debug|Any CPU.Build.0 = Debug|Any CPU + {92255394-9202-40FF-941F-C617A9CC8739}.Release|Any CPU.ActiveCfg = Release|Any CPU + {92255394-9202-40FF-941F-C617A9CC8739}.Release|Any CPU.Build.0 = Release|Any CPU + {409A6A79-30E0-42EF-B4AF-C409980FC4AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {409A6A79-30E0-42EF-B4AF-C409980FC4AF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {409A6A79-30E0-42EF-B4AF-C409980FC4AF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {409A6A79-30E0-42EF-B4AF-C409980FC4AF}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {F3CF0068-36FA-46E7-9975-3E633E2406C8} + SolutionGuid = {BC3F2BB2-F582-4F51-95DF-661E334363FD} EndGlobalSection EndGlobal diff --git a/STranslate/App.config b/STranslate/App.config deleted file mode 100644 index 04fcaeb0..00000000 --- a/STranslate/App.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/STranslate/App.xaml b/STranslate/App.xaml index cad7ba80..8f38dbbe 100644 --- a/STranslate/App.xaml +++ b/STranslate/App.xaml @@ -1,13 +1,40 @@  + xmlns:local="clr-namespace:STranslate"> - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - \ No newline at end of file + diff --git a/STranslate/App.xaml.cs b/STranslate/App.xaml.cs index c184194f..74b40e81 100644 --- a/STranslate/App.xaml.cs +++ b/STranslate/App.xaml.cs @@ -1,32 +1,157 @@ -using System; +using STranslate.Log; +using STranslate.Style.Controls; +using STranslate.Util; +using STranslate.Views; +using System; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Security.Principal; +using System.Threading.Tasks; using System.Windows; +using System.Windows.Threading; namespace STranslate { - /// - /// App.xaml 的交互逻辑 - /// public partial class App : Application { - private static System.Threading.Mutex mutex; - - //系统能够识别有名称的互斥,因此可以使用它禁止应用程序启动两次 - //第二个参数可以设置为产品的名称:Application.ProductName - // 每次启动应用程序,都会验证名称为OnlyRun的互斥是否存在 protected override void OnStartup(StartupEventArgs e) { - mutex = new System.Threading.Mutex(true, "CE252DD8-179F-4544-9989-453F5DEA378D"); - if (mutex.WaitOne(0, false)) + base.OnStartup(e); + + // 1. 检查是否已经具有管理员权限 + if (NeedAdministrator()) + { + // 如果没有管理员权限,可以提示用户提升权限 + if (TryRunAsAdministrator()) + { + // 如果提升权限成功,关闭当前实例 + Application.Current.Shutdown(); + return; + } + } + + // 2. 多开检测 + if (IsAnotherInstanceRunning()) + { + MessageBox_S.Show($"{Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly()!.Location)} 应用程序已经在运行中。", "多开检测"); + Application.Current.Shutdown(); + return; + } + + // 3. 启动应用程序 +#if DEBUG + LogService.Register(); +#elif !DEBUG + LogService.Register(minLevel: LogLevel.Info); +#endif + //TODO: 支持系统代理,仍需优化热重载系统代理切换 + HttpUtil.SupportSystemAgent(); + + StartProgram(); + + ExceptionHandler(); + } + private bool NeedAdministrator() + { + //加载配置 + var isRole = Singleton.Instance.CurrentConfig?.NeedAdministrator ?? false; + + if (!isRole) return false; + + WindowsIdentity identity = WindowsIdentity.GetCurrent(); + WindowsPrincipal principal = new WindowsPrincipal(identity); + return !principal.IsInRole(WindowsBuiltInRole.Administrator); + } + + private bool TryRunAsAdministrator() + { + var dll = Assembly.GetExecutingAssembly().Location; + ProcessStartInfo startInfo = new ProcessStartInfo + { + FileName = dll.Substring(0, dll.Length - 3) + "exe", + UseShellExecute = true, + Verb = "runas" // 提升权限 + }; + + try + { + Process.Start(startInfo); + return true; + } + catch (Exception) + { + return false; + } + } + + private bool IsAnotherInstanceRunning() + { + //Process currentProcess = Process.GetCurrentProcess(); + //string currentProcessName = Path.GetFileNameWithoutExtension(currentProcess.MainModule.FileName); + //var currentProcessName = "CE252DD8-179F-4544-9989-453F5DEA378D"; + var currentProcessName = "STranslate"; + Process[] runningProcesses = Process.GetProcessesByName(currentProcessName); + return runningProcesses.Length > 1; + } + + private void StartProgram() + { + LogService.Logger.Info($"{Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly()!.Location)} Opened..."); + new MainView()!.Show(); + } + + /// + /// 异常处理监听 + /// + private void ExceptionHandler() + { + //UI线程未捕获异常处理事件(UI主线程) + this.DispatcherUnhandledException += App_DispatcherUnhandledException; + //非UI线程未捕获异常处理事件(例如自己创建的一个子线程) + AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; + //Task线程内未捕获异常处理事件 + TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException; + } + + protected override void OnExit(ExitEventArgs e) + { + if (LogService.Logger != null) { - base.OnStartup(e); - - Util.FlushMemory(); + LogService.Logger.Info($"{Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly()!.Location)} Closed..."); + LogService.UnRegister(); } - else + base.OnExit(e); + } + + //UI线程未捕获异常处理事件(UI主线程) + private void App_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e) + { + Exception ex = e.Exception; + //异常信息 和 调用堆栈信息 + //string msg = String.Format("{0}\n\n{1}", ex.Message, ex.StackTrace); + LogService.Logger.Error("UI线程异常", ex); + e.Handled = true;//表示异常已处理,可以继续运行 + } + + //非UI线程未捕获异常处理事件(例如自己创建的一个子线程) + //如果UI线程异常DispatcherUnhandledException未注册,则如果发生了UI线程未处理异常也会触发此异常事件 + //此机制的异常捕获后应用程序会直接终止。没有像DispatcherUnhandledException事件中的Handler=true的处理方式,可以通过比如Dispatcher.Invoke将子线程异常丢在UI主线程异常处理机制中处理 + private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) + { + if (e.ExceptionObject is Exception ex && ex != null) { - MessageBox.Show("STranslate 已在运行...", "提示"); - Environment.Exit(0); + //string msg = String.Format("{0}\n\n{1}", ex.Message, ex.StackTrace); + LogService.Logger.Error("非UI线程异常", ex); } } + + //Task线程内未捕获异常处理事件 + private void TaskScheduler_UnobservedTaskException(object? sender, UnobservedTaskExceptionEventArgs e) + { + Exception ex = e.Exception; + //string msg = String.Format("{0}\n\n{1}", ex.Message, ex.StackTrace); + LogService.Logger.Error("Task异常", ex); + } } -} \ No newline at end of file +} diff --git a/STranslate/AssemblyInfo.cs b/STranslate/AssemblyInfo.cs new file mode 100644 index 00000000..8b5504ec --- /dev/null +++ b/STranslate/AssemblyInfo.cs @@ -0,0 +1,10 @@ +using System.Windows; + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] diff --git a/STranslate/Helper/ConfigHelper.cs b/STranslate/Helper/ConfigHelper.cs index 852aee9f..e1f20f2e 100644 --- a/STranslate/Helper/ConfigHelper.cs +++ b/STranslate/Helper/ConfigHelper.cs @@ -1,58 +1,281 @@ using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using STranslate.Log; using STranslate.Model; +using STranslate.Util; +using STranslate.ViewModels.Preference; +using STranslate.ViewModels.Preference.Services; using System; -using System.Collections.Generic; +using System.ComponentModel; using System.IO; using System.Linq; using System.Reflection; -using System.Text; -using System.Threading.Tasks; -namespace STranslate.Helper +namespace STranslate { - public class ConfigHelper : Singleton + public class ConfigHelper { - public T ReadConfig() + #region 公共方法 + + public ConfigHelper() { - try + if (!Directory.Exists(ApplicationData)) //判断是否存在 { - return JsonConvert.DeserializeObject(File.ReadAllText(_CnfName)); + Directory.CreateDirectory(ApplicationData); //创建新路径 + ShortcutUtil.SetDesktopShortcut(); //创建桌面快捷方式 } - catch (Exception) + if (!File.Exists(CnfName)) //文件不存在 { - throw new Exception("读取配置错误,请检查配置文件"); - } + FileStream fs = new(CnfName, FileMode.Create, FileAccess.ReadWrite); + fs.Close(); + WriteConfig(InitialConfig()); + } + + //初始化时将初始值赋给Config属性 + CurrentConfig = ResetConfig; + + //初始化主题 + System.Windows.Application.Current.Resources.MergedDictionaries.First().Source = CurrentConfig.IsBright + ? ConstStr.LIGHTURI + : ConstStr.DARKURI; } - public void WriteConfig(object obj) + /// + /// 写入服务到配置 + /// + /// + /// + public bool WriteConfig(BindingList services) { - File.WriteAllText(_CnfName, JsonConvert.SerializeObject(obj, Formatting.Indented)); + bool isSuccess = false; + if (CurrentConfig is not null) + { + CurrentConfig.Services = services; + WriteConfig(CurrentConfig); + isSuccess = true; + } + return isSuccess; } - public ConfigHelper() + /// + /// 写入源语言、目标语言到配置 + /// + /// + /// + /// + public bool WriteConfig(string source, string target) + { + bool isSuccess = false; + if (CurrentConfig is not null) + { + CurrentConfig.SourceLanguage = source; + CurrentConfig.TargetLanguage = target; + WriteConfig(CurrentConfig); + isSuccess = true; + } + return isSuccess; + } + + /// + /// 写入热键到配置 + /// + /// + /// + /// + public bool WriteConfig(Hotkeys hotkeys) + { + bool isSuccess = false; + if (CurrentConfig is not null) + { + CurrentConfig.Hotkeys = hotkeys; + WriteConfig(CurrentConfig); + isSuccess = true; + } + return isSuccess; + } + + /// + /// 写入常规配置项到当前配置 + /// + /// + /// + public bool WriteConfig(CommonViewModel model) + { + bool isSuccess = false; + if (CurrentConfig is not null) + { + CurrentConfig.IsStartup = model.IsStartup; + CurrentConfig.NeedAdministrator = model.NeedAdmin; + CurrentConfig.HistorySize = model.HistorySize; + CurrentConfig.AutoScale = model.AutoScale; + CurrentConfig.IsBright = model.IsBright; + CurrentConfig.IsFollowMouse = model.IsFollowMouse; + CurrentConfig.CloseUIOcrRetTranslate = model.CloseUIOcrRetTranslate; + CurrentConfig.UnconventionalScreen = model.UnconventionalScreen; + CurrentConfig.IsOcrAutoCopyText = model.IsOcrAutoCopyText; + CurrentConfig.IsAdjustContentTranslate = model.IsAdjustContentTranslate; + CurrentConfig.IsRemoveLineBreakGettingWords = model.IsRemoveLineBreakGettingWords; + CurrentConfig.DoubleTapTrayFunc = model.DoubleTapTrayFunc; + + WriteConfig(CurrentConfig); + isSuccess = true; + } + return isSuccess; + } + + #endregion 公共方法 + + #region 私有方法 + + internal ConfigModel ReadConfig() + { + try + { + var settings = new JsonSerializerSettings { Converters = { new TranslatorConverter() } }; + var content = File.ReadAllText(CnfName); + return JsonConvert.DeserializeObject(content, settings) ?? throw new Exception("反序列化失败..."); + } + catch (Exception) + { + LogService.Logger.Error("[READCONFIG] 读取配置错误,本次运行加载初始化配置..."); + return InitialConfig(); + } + } + + internal T ReadConfig() + where T : class { - if (!Directory.Exists(_ApplicationData))//判断是否存在 + try { - Directory.CreateDirectory(_ApplicationData);//创建新路径 - ShortcutHelper.SetDesktopShortcut();//创建桌面快捷方式 + var settings = new JsonSerializerSettings { Converters = { new TranslatorConverter() } }; + var content = File.ReadAllText(CnfName); + return JsonConvert.DeserializeObject(content, settings) ?? throw new Exception("反序列化失败..."); } - if (!File.Exists(_CnfName))//文件不存在 + catch (Exception ex) { - FileStream fs1 = new FileStream(_CnfName, FileMode.Create, FileAccess.ReadWrite); - fs1.Close(); - WriteConfig(new ConfigModel().InitialConfig()); + throw new Exception("[READCONFIG] 读取配置错误,请检查配置文件", ex); } } + internal void WriteConfig(object obj) + { + File.WriteAllText(CnfName, JsonConvert.SerializeObject(obj, Formatting.Indented)); + } + + #endregion 私有方法 + + #region 字段 && 属性 + + /// + /// 重置Config + /// + public ConfigModel ResetConfig => ReadConfig(); + + /// + /// 初始Config + /// + private ConfigModel InitialConfig() + { + var hk = new Hotkeys(); + hk.InputTranslate.Update(KeyModifiers.MOD_ALT, KeyCodes.A, ConstStr.DEFAULTINPUTHOTKEY); + hk.CrosswordTranslate.Update(KeyModifiers.MOD_ALT, KeyCodes.D, ConstStr.DEFAULTCROSSWORDHOTKEY); + hk.ScreenShotTranslate.Update(KeyModifiers.MOD_ALT, KeyCodes.S, ConstStr.DEFAULTSCREENSHOTHOTKEY); + hk.OpenMainWindow.Update(KeyModifiers.MOD_ALT, KeyCodes.G, ConstStr.DEFAULTOPENHOTKEY); + hk.MousehookTranslate.Update(KeyModifiers.MOD_ALT | KeyModifiers.MOD_SHIFT, KeyCodes.D, ConstStr.DEFAULTMOUSEHOOKHOTKEY); + hk.OCR.Update(KeyModifiers.MOD_ALT | KeyModifiers.MOD_SHIFT, KeyCodes.S, ConstStr.DEFAULTOCRHOTKEY); + return new ConfigModel + { + HistorySize = 100, + AutoScale = 0.8, + Hotkeys = hk, + IsBright = true, + IsStartup = false, + IsFollowMouse = false, + IsOcrAutoCopyText = false, + UnconventionalScreen = false, + CloseUIOcrRetTranslate = false, + IsAdjustContentTranslate = false, + IsRemoveLineBreakGettingWords = false, + DoubleTapTrayFunc = DoubleTapFuncEnum.InputFunc, + SourceLanguage = LanguageEnum.AUTO.GetDescription(), + TargetLanguage = LanguageEnum.AUTO.GetDescription(), + Services = + [ + new TranslatorApi(Guid.NewGuid(), "https://deepl.deno.dev/translate", "zu1k/deepl"), + new TranslatorApi(Guid.NewGuid(), "https://deeplx.deno.dev/translate", "zggsong/deepl"), + new TranslatorApi(Guid.NewGuid(), "https://iciba.deno.dev/translate", "爱词霸", IconType.Iciba, isEnabled: false), + new TranslatorApi(Guid.NewGuid(), "https://ggtranslate.deno.dev/translate", "谷歌翻译", IconType.Google, isEnabled: false) + ] + }; + } + + private ConfigModel? _config; + + public ConfigModel? CurrentConfig + { + get => _config; + private set => _config = value; + } + /// /// 配置文件 /// - private static string _CnfName => $"{_ApplicationData}\\{_AppName.ToLower()}.json"; + private string CnfName => $"{ApplicationData}\\{_AppName.ToLower()}.json"; + +#if true /// /// C:\Users\user\AppData\Local\STranslate /// - private static string _ApplicationData => $"{Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)}\\{_AppName}"; - private static readonly string _AppName = Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly().Location); + private string ApplicationData => $"{Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)}\\{_AppName}"; + +#else + /// + /// 当前目录 + /// + private string ApplicationData => $"{AppDomain.CurrentDomain.BaseDirectory}"; +#endif + + private readonly string _AppName = Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly()!.Location); + + #endregion 字段 && 属性 } -} + + #region JsonConvert + + public class TranslatorConverter : JsonConverter + { + public override ITranslator ReadJson( + JsonReader reader, + Type objectType, + ITranslator? existingValue, + bool hasExistingValue, + JsonSerializer serializer + ) + { + JObject jsonObject = JObject.Load(reader); + + // 根据Type字段的值来决定具体实现类 + var type = jsonObject["Type"]!.Value(); + ITranslator translator; + + translator = type switch + { + (int)ServiceType.ApiService => new TranslatorApi(), + (int)ServiceType.CloudService => new TranslatorBaidu(), + //TODO: 更多其他服务在这里添加 + _ => throw new NotSupportedException($"Unsupported ServiceType: {type}") + }; + + serializer.Populate(jsonObject.CreateReader(), translator); + return translator; + } + + public override void WriteJson(JsonWriter writer, ITranslator? value, JsonSerializer serializer) + { + throw new NotImplementedException(); + } + } + + #endregion JsonConvert +} \ No newline at end of file diff --git a/STranslate/Helper/GetWordsHelper.cs b/STranslate/Helper/GetWordsHelper.cs deleted file mode 100644 index 692f7263..00000000 --- a/STranslate/Helper/GetWordsHelper.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Threading; -using System.Windows.Input; - -namespace STranslate.Helper -{ - public class GetWordsHelper - { - public static String Get() - { - SendCtrlC(); - System.Diagnostics.Debug.Print(STranslate.ViewModel.SettingsVM.Instance.WordPickupInterval.ToString()); - Thread.Sleep((int)STranslate.ViewModel.SettingsVM.Instance.WordPickupInterval); - return NativeMethodHelper.GetText(); - } - - private static void SendCtrlC() - { - //IntPtr hWnd = GetForegroundWindow(); - //SetForegroundWindow(hWnd); - uint KEYEVENTF_KEYUP = 2; - - NativeMethodHelper.keybd_event(System.Windows.Forms.Keys.ControlKey, 0, KEYEVENTF_KEYUP, 0); - NativeMethodHelper.keybd_event(KeyInterop.VirtualKeyFromKey(Key.LeftAlt), 0, KEYEVENTF_KEYUP, 0); - NativeMethodHelper.keybd_event(KeyInterop.VirtualKeyFromKey(Key.RightAlt), 0, KEYEVENTF_KEYUP, 0); - NativeMethodHelper.keybd_event(System.Windows.Forms.Keys.LWin, 0, KEYEVENTF_KEYUP, 0); - NativeMethodHelper.keybd_event(System.Windows.Forms.Keys.RWin, 0, KEYEVENTF_KEYUP, 0); - NativeMethodHelper.keybd_event(System.Windows.Forms.Keys.ShiftKey, 0, KEYEVENTF_KEYUP, 0); - - NativeMethodHelper.keybd_event(System.Windows.Forms.Keys.ControlKey, 0, 0, 0); - NativeMethodHelper.keybd_event(System.Windows.Forms.Keys.C, 0, 0, 0); - NativeMethodHelper.keybd_event(System.Windows.Forms.Keys.C, 0, KEYEVENTF_KEYUP, 0); - NativeMethodHelper.keybd_event(System.Windows.Forms.Keys.ControlKey, 0, KEYEVENTF_KEYUP, 0);// 'Left Control Up - } - } -} diff --git a/STranslate/Helper/HotkeyHelper.cs b/STranslate/Helper/HotkeyHelper.cs new file mode 100644 index 00000000..80dbe060 --- /dev/null +++ b/STranslate/Helper/HotkeyHelper.cs @@ -0,0 +1,331 @@ +using STranslate.Model; +using STranslate.Util; +using System; +using System.Collections.Generic; +using System.Windows; +using System.Windows.Interop; + +namespace STranslate.Helper +{ + public class HotkeyHelper + { + public static Hotkeys? Hotkeys; + + public static IntPtr MainIntPtr; + + public static int InputTranslateId = 854; + public static KeyModifiers InputTranslateModifiers; + public static KeyCodes InputTranslateKey; + + public static int CrosswordTranslateId = 855; + public static KeyModifiers CrosswordTranslateModifiers; + public static KeyCodes CrosswordTranslateKey; + + public static int ScreenShotTranslateId = 856; + public static KeyModifiers ScreenShotTranslateModifiers; + public static KeyCodes ScreenShotTranslateKey; + + public static int OpenMainWindowId = 857; + public static KeyModifiers OpenMainWindowModifiers; + public static KeyCodes OpenMainWindowKey; + + public static int MousehookTranslateId = 858; + public static KeyModifiers MousehookTranslateModifiers; + public static KeyCodes MousehookTranslateKey; + + public static int OCRId = 859; + public static KeyModifiers OCRModifiers; + public static KeyCodes OCRKey; + + public delegate void HotKeyCallBackHanlder(); + + private static readonly Dictionary keymap = new(); + + private static HwndSource? _hwndSource; + + /// + /// 初始化Hook + /// + /// + public static void InitialHook(Window window) + { + var hwnd = new WindowInteropHelper(window).Handle; + + RegisterHotKey(hwnd); + _hwndSource = HwndSource.FromHwnd(hwnd); + _hwndSource.AddHook(WndProc); + } + + /// + /// 注册快捷键 + /// + /// + /// + public static void Register(int id, HotKeyCallBackHanlder callBack) + { + keymap[id] = callBack; + } + + /// + /// 屏幕分辨率以及文本显示比例变更对应的消息标志 + /// + private const int WmDisplayChange = 0x007e; + + /// + /// 文本显示比例变更消息标志 + /// + private const int WmTextChange = 0x02E0; + + /// + /// window消息定义的 注册的热键消息标志 + /// + private const int WmHotkeys = 0x0312; + + /// + /// 快捷键消息处理 + /// + private static IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) + { + switch (msg) + { + case WmHotkeys: + int id = wParam.ToInt32(); + if (keymap.TryGetValue(id, out var callback)) + { + callback(); + } + break; + } + return IntPtr.Zero; + } + + /// + /// 注册快捷键 + /// + /// + private static void RegisterHotKey(IntPtr handle) + { + MainIntPtr = handle; + + InputTranslateModifiers = Hotkeys!.InputTranslate.Modifiers; + InputTranslateKey = Hotkeys!.InputTranslate.Key; + + CrosswordTranslateModifiers = Hotkeys!.CrosswordTranslate.Modifiers; + CrosswordTranslateKey = Hotkeys!.CrosswordTranslate.Key; + + ScreenShotTranslateModifiers = Hotkeys!.ScreenShotTranslate.Modifiers; + ScreenShotTranslateKey = Hotkeys!.ScreenShotTranslate.Key; + + OpenMainWindowModifiers = Hotkeys!.OpenMainWindow.Modifiers; + OpenMainWindowKey = Hotkeys!.OpenMainWindow.Key; + + MousehookTranslateModifiers = Hotkeys!.MousehookTranslate.Modifiers; + MousehookTranslateKey = Hotkeys!.MousehookTranslate.Key; + + OCRModifiers = Hotkeys!.OCR.Modifiers; + OCRKey = Hotkeys!.OCR.Key; + + if (Hotkeys!.ScreenShotTranslate.Key != 0) + { + Hotkeys!.ScreenShotTranslate.Conflict = !CommonUtil.RegisterHotKey( + handle, + ScreenShotTranslateId, + (byte)Hotkeys!.ScreenShotTranslate.Modifiers, + (int)Hotkeys!.ScreenShotTranslate.Key + ); + } + + if (Hotkeys!.InputTranslate.Key != 0) + { + Hotkeys!.InputTranslate.Conflict = !CommonUtil.RegisterHotKey( + handle, + InputTranslateId, + (byte)Hotkeys!.InputTranslate.Modifiers, + (int)Hotkeys!.InputTranslate.Key + ); + } + if (Hotkeys!.CrosswordTranslate.Key != 0) + { + Hotkeys!.CrosswordTranslate.Conflict = !CommonUtil.RegisterHotKey( + handle, + CrosswordTranslateId, + (byte)Hotkeys!.CrosswordTranslate.Modifiers, + (int)Hotkeys!.CrosswordTranslate.Key + ); + } + if (Hotkeys!.OpenMainWindow.Key != 0) + { + Hotkeys!.OpenMainWindow.Conflict = !CommonUtil.RegisterHotKey( + handle, + OpenMainWindowId, + (byte)Hotkeys!.OpenMainWindow.Modifiers, + (int)Hotkeys!.OpenMainWindow.Key + ); + } + if (Hotkeys!.MousehookTranslate.Key != 0) + { + Hotkeys!.MousehookTranslate.Conflict = !CommonUtil.RegisterHotKey( + handle, + MousehookTranslateId, + (byte)Hotkeys!.MousehookTranslate.Modifiers, + (int)Hotkeys!.MousehookTranslate.Key + ); + } + if (Hotkeys!.OCR.Key != 0) + { + Hotkeys!.OCR.Conflict = !CommonUtil.RegisterHotKey( + handle, + OCRId, + (byte)Hotkeys!.OCR.Modifiers, + (int)Hotkeys!.OCR.Key + ); + } + } + + /// + /// 重新注册快捷键 + /// + public static void ReRegisterHotKey() + { + if (Hotkeys!.InputTranslate.Key == 0) + { + CommonUtil.UnregisterHotKey(MainIntPtr, InputTranslateId); + } + else if ( + InputTranslateModifiers != Hotkeys!.InputTranslate.Modifiers + || InputTranslateKey != Hotkeys!.InputTranslate.Key + ) + { + { + CommonUtil.UnregisterHotKey(MainIntPtr, InputTranslateId); + Hotkeys!.InputTranslate.Conflict = !CommonUtil.RegisterHotKey( + MainIntPtr, + InputTranslateId, + (byte)Hotkeys!.InputTranslate.Modifiers, + (int)Hotkeys!.InputTranslate.Key + ); + } + } + InputTranslateModifiers = Hotkeys!.InputTranslate.Modifiers; + InputTranslateKey = Hotkeys!.InputTranslate.Key; + + if (Hotkeys!.CrosswordTranslate.Key == 0) + { + CommonUtil.UnregisterHotKey(MainIntPtr, CrosswordTranslateId); + } + else if ( + CrosswordTranslateModifiers != Hotkeys!.CrosswordTranslate.Modifiers + || CrosswordTranslateKey != Hotkeys!.CrosswordTranslate.Key + ) + { + { + CommonUtil.UnregisterHotKey(MainIntPtr, CrosswordTranslateId); + Hotkeys!.CrosswordTranslate.Conflict = !CommonUtil.RegisterHotKey( + MainIntPtr, + CrosswordTranslateId, + (byte)Hotkeys!.CrosswordTranslate.Modifiers, + (int)Hotkeys!.CrosswordTranslate.Key + ); + } + } + CrosswordTranslateModifiers = Hotkeys!.CrosswordTranslate.Modifiers; + CrosswordTranslateKey = Hotkeys!.CrosswordTranslate.Key; + + if (Hotkeys!.ScreenShotTranslate.Key == 0) + { + CommonUtil.UnregisterHotKey(MainIntPtr, ScreenShotTranslateId); + } + else if ( + ScreenShotTranslateModifiers != Hotkeys!.ScreenShotTranslate.Modifiers + || ScreenShotTranslateKey != Hotkeys!.ScreenShotTranslate.Key + ) + { + CommonUtil.UnregisterHotKey(MainIntPtr, ScreenShotTranslateId); + Hotkeys!.ScreenShotTranslate.Conflict = !CommonUtil.RegisterHotKey( + MainIntPtr, + ScreenShotTranslateId, + (byte)Hotkeys!.ScreenShotTranslate.Modifiers, + (int)Hotkeys!.ScreenShotTranslate.Key + ); + } + ScreenShotTranslateModifiers = Hotkeys!.ScreenShotTranslate.Modifiers; + ScreenShotTranslateKey = Hotkeys!.ScreenShotTranslate.Key; + + if (Hotkeys!.OpenMainWindow.Key == 0) + { + CommonUtil.UnregisterHotKey(MainIntPtr, OpenMainWindowId); + } + else if ( + OpenMainWindowModifiers != Hotkeys!.OpenMainWindow.Modifiers + || OpenMainWindowKey != Hotkeys!.OpenMainWindow.Key + ) + { + CommonUtil.UnregisterHotKey(MainIntPtr, OpenMainWindowId); + Hotkeys!.OpenMainWindow.Conflict = !CommonUtil.RegisterHotKey( + MainIntPtr, + OpenMainWindowId, + (byte)Hotkeys!.OpenMainWindow.Modifiers, + (int)Hotkeys!.OpenMainWindow.Key + ); + } + OpenMainWindowModifiers = Hotkeys!.OpenMainWindow.Modifiers; + OpenMainWindowKey = Hotkeys!.OpenMainWindow.Key; + + if (Hotkeys!.MousehookTranslate.Key == 0) + { + CommonUtil.UnregisterHotKey(MainIntPtr, MousehookTranslateId); + } + else if ( + MousehookTranslateModifiers != Hotkeys!.MousehookTranslate.Modifiers + || MousehookTranslateKey != Hotkeys!.MousehookTranslate.Key + ) + { + CommonUtil.UnregisterHotKey(MainIntPtr, MousehookTranslateId); + Hotkeys!.MousehookTranslate.Conflict = !CommonUtil.RegisterHotKey( + MainIntPtr, + MousehookTranslateId, + (byte)Hotkeys!.MousehookTranslate.Modifiers, + (int)Hotkeys!.MousehookTranslate.Key + ); + } + MousehookTranslateModifiers = Hotkeys!.MousehookTranslate.Modifiers; + MousehookTranslateKey = Hotkeys!.MousehookTranslate.Key; + + if (Hotkeys!.OCR.Key == 0) + { + CommonUtil.UnregisterHotKey(MainIntPtr, OCRId); + } + else if ( + OCRModifiers != Hotkeys!.OCR.Modifiers + || OCRKey != Hotkeys!.OCR.Key + ) + { + CommonUtil.UnregisterHotKey(MainIntPtr, OCRId); + Hotkeys!.OCR.Conflict = !CommonUtil.RegisterHotKey( + MainIntPtr, + OCRId, + (byte)Hotkeys!.OCR.Modifiers, + (int)Hotkeys!.OCR.Key + ); + } + OCRModifiers = Hotkeys!.OCR.Modifiers; + OCRKey = Hotkeys!.OCR.Key; + } + + /// + /// 注销快捷键 + /// + public static void UnRegisterHotKey(Window window) + { + CommonUtil.UnregisterHotKey(MainIntPtr, InputTranslateId); + CommonUtil.UnregisterHotKey(MainIntPtr, CrosswordTranslateId); + CommonUtil.UnregisterHotKey(MainIntPtr, ScreenShotTranslateId); + CommonUtil.UnregisterHotKey(MainIntPtr, OpenMainWindowId); + CommonUtil.UnregisterHotKey(MainIntPtr, MousehookTranslateId); + CommonUtil.UnregisterHotKey(MainIntPtr, OCRId); + var hwnd = new WindowInteropHelper(window).Handle; + _hwndSource = HwndSource.FromHwnd(hwnd); + _hwndSource.RemoveHook(WndProc); + } + } +} \ No newline at end of file diff --git a/STranslate/Helper/HotkeysHelper.cs b/STranslate/Helper/HotkeysHelper.cs deleted file mode 100644 index 122cd2a0..00000000 --- a/STranslate/Helper/HotkeysHelper.cs +++ /dev/null @@ -1,190 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Windows; -using System.Windows.Interop; -using WpfScreenHelper; -using static STranslate.Helper.NativeMethodHelper; - -namespace STranslate.Helper -{ - - internal class HotkeysHelper - { - public static IntPtr mainFormHandle; - - public static int InputTranslateId = 854; - public static byte InputTranslateModifiers; - public static int InputTranslateKey; - public static int CrosswordTranslateId = 855; - public static byte CrosswordTranslateModifiers; - public static int CrosswordTranslateKey; -#if true - public static int ScreenShotTranslateId = 856; - public static byte ScreenShotTranslateModifiers; - public static int ScreenShotTranslateKey; -#endif - public static int OpenMainWindowId = 857; - public static byte OpenMainWindowModifiers; - public static int OpenMainWindowKey; - - public delegate void HotKeyCallBackHanlder(); - private static Dictionary keymap = new Dictionary(); - - public static void InitialHook(Window window) - { - var hwnd = new WindowInteropHelper(window).Handle; - RegisterHotKey(hwnd); - var _hwndSource = HwndSource.FromHwnd(hwnd); - _hwndSource.AddHook(WndProc); - } - /// - /// 注册快捷键 - /// https://git2.nas.zggsong.cn:5001/zggsong/STranslate/src/commit/2fe17e7f6596b47b33a40d8733a0527ba5d9b2fb/STranslate/Utils/HotKeysUtil.cs - /// - /// InputTranslateId、ScreenShotTranslateId、CrosswordTranslateId、OpenMainWindowId - /// - public static void Register(int id, HotKeyCallBackHanlder callBack) - { - keymap[id] = callBack; - } - - /// - /// 屏幕分辨率以及文本显示比例变更对应的消息标志 - /// - private const int WmDisplayChange = 0x007e; - /// - /// 文本显示比例变更消息标志 - /// - private const int WmTextChange = 0x02E0; - /// - /// window消息定义的 注册的热键消息标志 - /// - private const int WmHotkeys = 0x0312; - /// - /// 快捷键消息处理 - /// - private static IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) - { - switch (msg) - { - case WmHotkeys: - int id = wParam.ToInt32(); - if (keymap.TryGetValue(id, out var callback)) - { - callback(); - } - break; - } - return IntPtr.Zero; - } - - /// - /// 注册快捷键 - /// - /// - private static void RegisterHotKey(IntPtr mainFormHandle) - { - HotkeysHelper.mainFormHandle = mainFormHandle; - - InputTranslateModifiers = ViewModel.MainVM.Instance.NHotkeys.InputTranslate.Modifiers; - InputTranslateKey = ViewModel.MainVM.Instance.NHotkeys.InputTranslate.Key; - CrosswordTranslateModifiers = ViewModel.MainVM.Instance.NHotkeys.CrosswordTranslate.Modifiers; - CrosswordTranslateKey = ViewModel.MainVM.Instance.NHotkeys.CrosswordTranslate.Key; - -#if true - ScreenShotTranslateModifiers = ViewModel.MainVM.Instance.NHotkeys.ScreenShotTranslate.Modifiers; - ScreenShotTranslateKey = ViewModel.MainVM.Instance.NHotkeys.ScreenShotTranslate.Key; - if (ViewModel.MainVM.Instance.NHotkeys.ScreenShotTranslate.Key != 0) - { - ViewModel.MainVM.Instance.NHotkeys.ScreenShotTranslate.Conflict = !NativeMethodHelper.RegisterHotKey(mainFormHandle, ScreenShotTranslateId, ViewModel.MainVM.Instance.NHotkeys.ScreenShotTranslate.Modifiers, ViewModel.MainVM.Instance.NHotkeys.ScreenShotTranslate.Key); - } -#endif - - if (ViewModel.MainVM.Instance.NHotkeys.InputTranslate.Key != 0) - { - ViewModel.MainVM.Instance.NHotkeys.InputTranslate.Conflict = !NativeMethodHelper.RegisterHotKey(mainFormHandle, InputTranslateId, ViewModel.MainVM.Instance.NHotkeys.InputTranslate.Modifiers, ViewModel.MainVM.Instance.NHotkeys.InputTranslate.Key); - } - if (ViewModel.MainVM.Instance.NHotkeys.CrosswordTranslate.Key != 0) - { - ViewModel.MainVM.Instance.NHotkeys.CrosswordTranslate.Conflict = !NativeMethodHelper.RegisterHotKey(mainFormHandle, CrosswordTranslateId, ViewModel.MainVM.Instance.NHotkeys.CrosswordTranslate.Modifiers, ViewModel.MainVM.Instance.NHotkeys.CrosswordTranslate.Key); - } - if (ViewModel.MainVM.Instance.NHotkeys.OpenMainWindow.Key != 0) - { - ViewModel.MainVM.Instance.NHotkeys.OpenMainWindow.Conflict = !NativeMethodHelper.RegisterHotKey(mainFormHandle, OpenMainWindowId, ViewModel.MainVM.Instance.NHotkeys.OpenMainWindow.Modifiers, ViewModel.MainVM.Instance.NHotkeys.OpenMainWindow.Key); - } - } - - /// - /// 注销快捷键 - /// - public static void UnRegisterHotKey() - { - UnregisterHotKey(mainFormHandle, InputTranslateId); - UnregisterHotKey(mainFormHandle, CrosswordTranslateId); -#if true - UnregisterHotKey(mainFormHandle, ScreenShotTranslateId); -#endif - UnregisterHotKey(mainFormHandle, OpenMainWindowId); - } - - /// - /// 重新注册快捷键 - /// - public static void ReRegisterHotKey() - { - if (ViewModel.MainVM.Instance.NHotkeys.InputTranslate.Key == 0) - { - UnregisterHotKey(mainFormHandle, InputTranslateId); - } - else if (InputTranslateModifiers != ViewModel.MainVM.Instance.NHotkeys.InputTranslate.Modifiers || InputTranslateKey != ViewModel.MainVM.Instance.NHotkeys.InputTranslate.Key) - { - { - UnregisterHotKey(mainFormHandle, InputTranslateId); - ViewModel.MainVM.Instance.NHotkeys.InputTranslate.Conflict = !NativeMethodHelper.RegisterHotKey(mainFormHandle, InputTranslateId, ViewModel.MainVM.Instance.NHotkeys.InputTranslate.Modifiers, ViewModel.MainVM.Instance.NHotkeys.InputTranslate.Key); - } - } - InputTranslateModifiers = ViewModel.MainVM.Instance.NHotkeys.InputTranslate.Modifiers; - InputTranslateKey = ViewModel.MainVM.Instance.NHotkeys.InputTranslate.Key; - - if (ViewModel.MainVM.Instance.NHotkeys.CrosswordTranslate.Key == 0) - { - UnregisterHotKey(mainFormHandle, CrosswordTranslateId); - } - else if (CrosswordTranslateModifiers != ViewModel.MainVM.Instance.NHotkeys.CrosswordTranslate.Modifiers || CrosswordTranslateKey != ViewModel.MainVM.Instance.NHotkeys.CrosswordTranslate.Key) - { - { - UnregisterHotKey(mainFormHandle, CrosswordTranslateId); - ViewModel.MainVM.Instance.NHotkeys.CrosswordTranslate.Conflict = !NativeMethodHelper.RegisterHotKey(mainFormHandle, CrosswordTranslateId, ViewModel.MainVM.Instance.NHotkeys.CrosswordTranslate.Modifiers, ViewModel.MainVM.Instance.NHotkeys.CrosswordTranslate.Key); - } - } - CrosswordTranslateModifiers = ViewModel.MainVM.Instance.NHotkeys.CrosswordTranslate.Modifiers; - CrosswordTranslateKey = ViewModel.MainVM.Instance.NHotkeys.CrosswordTranslate.Key; - -#if true - if (ViewModel.MainVM.Instance.NHotkeys.ScreenShotTranslate.Key == 0) - { - UnregisterHotKey(mainFormHandle, ScreenShotTranslateId); - } - else if (ScreenShotTranslateModifiers != ViewModel.MainVM.Instance.NHotkeys.ScreenShotTranslate.Modifiers || ScreenShotTranslateKey != ViewModel.MainVM.Instance.NHotkeys.ScreenShotTranslate.Key) - { - UnregisterHotKey(mainFormHandle, ScreenShotTranslateId); - ViewModel.MainVM.Instance.NHotkeys.ScreenShotTranslate.Conflict = !NativeMethodHelper.RegisterHotKey(mainFormHandle, ScreenShotTranslateId, ViewModel.MainVM.Instance.NHotkeys.ScreenShotTranslate.Modifiers, ViewModel.MainVM.Instance.NHotkeys.ScreenShotTranslate.Key); - } - ScreenShotTranslateModifiers = ViewModel.MainVM.Instance.NHotkeys.ScreenShotTranslate.Modifiers; - ScreenShotTranslateKey = ViewModel.MainVM.Instance.NHotkeys.ScreenShotTranslate.Key; -#endif - - if (ViewModel.MainVM.Instance.NHotkeys.OpenMainWindow.Key == 0) - { - UnregisterHotKey(mainFormHandle, OpenMainWindowId); - } - else if (OpenMainWindowModifiers != ViewModel.MainVM.Instance.NHotkeys.OpenMainWindow.Modifiers || OpenMainWindowKey != ViewModel.MainVM.Instance.NHotkeys.OpenMainWindow.Key) - { - UnregisterHotKey(mainFormHandle, OpenMainWindowId); - ViewModel.MainVM.Instance.NHotkeys.OpenMainWindow.Conflict = !NativeMethodHelper.RegisterHotKey(mainFormHandle, OpenMainWindowId, ViewModel.MainVM.Instance.NHotkeys.OpenMainWindow.Modifiers, ViewModel.MainVM.Instance.NHotkeys.OpenMainWindow.Key); - } - OpenMainWindowModifiers = ViewModel.MainVM.Instance.NHotkeys.OpenMainWindow.Modifiers; - OpenMainWindowKey = ViewModel.MainVM.Instance.NHotkeys.OpenMainWindow.Key; - } - } -} \ No newline at end of file diff --git a/STranslate/Helper/MouseHookHelper.cs b/STranslate/Helper/MouseHookHelper.cs new file mode 100644 index 00000000..fc93a69c --- /dev/null +++ b/STranslate/Helper/MouseHookHelper.cs @@ -0,0 +1,73 @@ +using STranslate.Util; +using System; +using System.Windows.Forms; + +namespace STranslate.Helper +{ + public class MouseHookHelper + { + private readonly MouseHookUtil mouseHook; + + private bool isDown = false; + + private bool isMove = false; + + private bool isStart = false; + + public Action? OnGetwordsHandler; + + public MouseHookHelper() + { + mouseHook = new MouseHookUtil(); + } + + public void MouseHookStart() + { + mouseHook.MouseMove += mouseHook_MouseMove; + mouseHook.MouseDown += mouseHook_MouseDown; + mouseHook.MouseUp += mouseHook_MouseUp; + mouseHook.Start(); + isStart = true; + } + + public void MouseHookStop() + { + mouseHook.MouseMove -= mouseHook_MouseMove; + mouseHook.MouseDown -= mouseHook_MouseDown; + mouseHook.MouseUp -= mouseHook_MouseUp; + mouseHook.Stop(); + isStart = false; + } + + private void mouseHook_MouseDown(object? sender, MouseEventArgs e) + { + if (e.Button == MouseButtons.Left) + { + isDown = true; + } + } + + private void mouseHook_MouseMove(object? sender, MouseEventArgs e) + { + if (isDown && isStart) + { + isMove = true; + } + } + + private void mouseHook_MouseUp(object? sender, MouseEventArgs e) + { + if (e.Button != MouseButtons.Left) + { + return; + } + if (isDown && isMove) + { + var content = GetWordsUtil.MouseSlidGet(); + OnGetwordsHandler?.Invoke(content); + } + isDown = false; + isMove = false; + } + } +} \ No newline at end of file diff --git a/STranslate/Helper/MvvmHelper.cs b/STranslate/Helper/MvvmHelper.cs deleted file mode 100644 index 2400018d..00000000 --- a/STranslate/Helper/MvvmHelper.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System; -using System.Windows.Input; -using System.ComponentModel; -using System.Runtime.CompilerServices; - -namespace STranslate.Helper -{ - /// - /// Generate Singleton - /// - /// - public class Singleton where T : class - { - private static readonly Lazy _instance = new Lazy(() - => Activator.CreateInstance(typeof(T), true) as T, true); - - public static T Instance => _instance.Value; - } - - /// - /// 通知 - /// - public class BaseVM : INotifyPropertyChanged - { - public event PropertyChangedEventHandler PropertyChanged; - - protected void UpdateProperty(ref T properValue, T newValue, [CallerMemberName] string properName = "") - { - if (object.Equals(properValue, newValue)) - return; - properValue = newValue; - NotifyPropertyChanged(properName); - } - - public void NotifyPropertyChanged([CallerMemberName] string propertyName = null) - { - this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } - } - - /// - /// Command - /// - public class RelayCommand : ICommand - { - private readonly Predicate _canExecute; - private readonly Action _execute; - - public RelayCommand(Predicate canExecute, Action execute) - { - this._canExecute = canExecute; - this._execute = execute; - } - - public event EventHandler CanExecuteChanged - { - add { CommandManager.RequerySuggested += value; } - remove { CommandManager.RequerySuggested -= value; } - } - - public bool CanExecute(object parameter) - { - return _canExecute(parameter); - } - - public void Execute(object parameter) - { - _execute(parameter); - } - } -} diff --git a/STranslate/Helper/PaddleOCRHelper.cs b/STranslate/Helper/PaddleOCRHelper.cs new file mode 100644 index 00000000..10bd10ec --- /dev/null +++ b/STranslate/Helper/PaddleOCRHelper.cs @@ -0,0 +1,126 @@ +#if false +using OpenCvSharp; +using Sdcb.OpenVINO.PaddleOCR.Models.Online; +using Sdcb.OpenVINO.PaddleOCR.Models; +using Sdcb.OpenVINO.PaddleOCR; +using System; +using System.Threading.Tasks; + +namespace STranslate.Helper +{ + public class PaddleOCRHelper + { + private FullOcrModel model; + + public PaddleOCRHelper() + { + // 在构造函数中调用异步初始化方法 + model = InitializeAsync().GetAwaiter().GetResult(); + } + + // 异步初始化方法 + private async Task InitializeAsync() => await OnlineFullModels.ChineseServerV4.DownloadAsync(); + + /// + /// 执行 + /// + /// + /// + public string Excute(byte[] bytes) + { + try + { + string ret = string.Empty; + + using Mat src = Cv2.ImDecode(bytes, ImreadModes.Color); + + if (model == null) + throw new Exception("模型未下载完成"); + + using (PaddleOcrAll all = new(model) { AllowRotateDetection = true, Enable180Classification = true, }) + { + // Load local file by following code: + // using (Mat src2 = Cv2.ImRead(@"C:\test.jpg")) + //Stopwatch sw = Stopwatch.StartNew(); + PaddleOcrResult result = all.Run(src); + ret = result.Text; + //Console.WriteLine($"elapsed={sw.ElapsedMilliseconds} ms"); + //Console.WriteLine("Detected all texts: \n" + result.Text); + //foreach (PaddleOcrResultRegion region in result.Regions) + //{ + // Console.WriteLine($"Text: {region.Text}, Score: {region.Score}, RectCenter: {region.Rect.Center}, RectSize: {region.Rect.Size}, Angle: {region.Rect.Angle}"); + //} + } + return ret; + } + catch (Exception ex) + { + throw new Exception(string.Format("OCR出错: {0}", ex.Message)); + } + } + } +} + +#endif + +#if true +using PaddleOCRSharp; +using System; +using System.Text; + +namespace STranslate.Helper +{ + public class PaddleOCRHelper : IDisposable + { + private PaddleOCREngine paddleOCREngine; + + public PaddleOCRHelper() + { + //使用默认中英文V4模型 + OCRModelConfig? config = null; + + //使用默认参数 + OCRParameter oCRParameter = new OCRParameter(); + oCRParameter.cpu_math_library_num_threads = 10;//预测并发线程数 + oCRParameter.enable_mkldnn = true;//web部署该值建议设置为0,否则出错,内存如果使用很大,建议该值也设置为0. + oCRParameter.cls = false; //是否执行文字方向分类;默认false + oCRParameter.det = true;//是否开启方向检测,用于检测识别180旋转 + oCRParameter.use_angle_cls = false;//是否开启方向检测,用于检测识别180旋转 + oCRParameter.det_db_score_mode = true;//是否使用多段线,即文字区域是用多段线还是用矩形, + //识别结果对象 + OCRResult ocrResult = new OCRResult(); + //建议程序全局初始化一次即可,不必每次识别都初始化,容易报错。 + paddleOCREngine = new PaddleOCREngine(config, oCRParameter); + } + + public void Dispose() + { + paddleOCREngine.Dispose(); + } + + /// + /// 执行 + /// + /// + /// + public string Excute(byte[] bytes) + { + try + { + StringBuilder sb = new StringBuilder(); + + var ocrResult = paddleOCREngine.DetectText(bytes); + + ocrResult?.TextBlocks.ForEach(x => sb.AppendLine(x.Text)); + //ret = ocrResult?.Text ?? ""; + + return sb.ToString(); + } + catch (Exception ex) + { + throw new Exception(string.Format("OCR出错: {0}", ex.Message)); + } + } + } +} +#endif diff --git a/STranslate/Helper/Processhelper.cs b/STranslate/Helper/Processhelper.cs deleted file mode 100644 index c691c4ad..00000000 --- a/STranslate/Helper/Processhelper.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace STranslate.Helper -{ - public class ProcessHelper - { - public static bool Run(string filename, string[] args) - { - try - { - string arguments = ""; - foreach (string arg in args) - { - arguments += $"\"{arg}\" "; - } - arguments = arguments.Trim(); - Process process = new Process(); - ProcessStartInfo startInfo = new ProcessStartInfo(filename, arguments); - process.StartInfo = startInfo; - process.Start(); - return true; - } - catch (Exception) - { - //Logger.Error(ex.ToString()); - return false; - } - - } - } -} diff --git a/STranslate/Helper/SqlHelper.cs b/STranslate/Helper/SqlHelper.cs new file mode 100644 index 00000000..1407a163 --- /dev/null +++ b/STranslate/Helper/SqlHelper.cs @@ -0,0 +1,339 @@ +using Dapper; +using Dapper.Contrib.Extensions; +using Microsoft.Data.Sqlite; +using STranslate.Model; +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Threading.Tasks; + +namespace STranslate.Helper +{ + public class SqlHelper + { + #region Asynchronous method + + /// + /// 创建数据库 + /// + /// + public static async Task InitializeDBAsync() + { + using var connection = new SqliteConnection(ConnectionString); + connection.Open(); + + // 创建表的 SQL 语句 + string createTableSql = + @" + CREATE TABLE IF NOT EXISTS History ( + Id INTEGER PRIMARY KEY AUTOINCREMENT, + Time TEXT NOT NULL, + SourceLang TEXT, + TargetLang TEXT, + SourceText TEXT, + Data TEXT, + Favorite INTEGER, + Remark TEXT + ); + "; + await connection.ExecuteAsync(createTableSql); + } + + /// + /// 删除记录 + /// + /// + /// + public static async Task DeleteDataAsync(HistoryModel history) + { + using var connection = new SqliteConnection(ConnectionString); + connection.Open(); + + return await connection.DeleteAsync(history); + } + + /// + /// 删除所有记录 + /// + /// + public static async Task DeleteAllDataAsync() + { + using var connection = new SqliteConnection(ConnectionString); + connection.Open(); + + return await connection.DeleteAllAsync(); + } + + /// + /// 插入数据-异步 + /// + /// + /// + /// + /// + public static async Task InsertDataAsync(HistoryModel history, long count, bool forceWrite = false) + { + using var connection = new SqliteConnection(ConnectionString); + connection.Open(); + + var curCount = await connection.QueryFirstOrDefaultAsync("SELECT COUNT(*) FROM History"); + if (curCount > count) + { + var sql = @"DELETE FROM History WHERE Id IN (SELECT Id FROM History ORDER BY Id ASC LIMIT @Limit)"; + + await connection.ExecuteAsync(sql, new { Limit = curCount - count + 1 }); + } + + if (forceWrite) + { + // 使用 Dapper 的 FirstOrDefault 方法进行查询 + var existingHistory = await connection.QueryFirstOrDefaultAsync( + "SELECT * FROM History WHERE SourceText = @SourceText AND SourceLang = @SourceLang AND TargetLang = @TargetLang", + new + { + history.SourceText, + history.SourceLang, + history.TargetLang + } + ); + + if (existingHistory != null) + { + // 使用 Dapper.Contrib 的 Update 方法更新数据 + existingHistory.Time = history.Time; + existingHistory.Data = history.Data; + await connection.UpdateAsync(existingHistory); + return; + } + } + + // 使用 Dapper.Contrib 的 Insert 方法插入数据 + await connection.InsertAsync(history); + } + + /// + /// 查询数据 + /// + /// + /// + /// + /// + public static async Task GetDataAsync(string content, string source, string target) + { + using var connection = new SqliteConnection(ConnectionString); + connection.Open(); + + // 使用 Dapper 执行查询数据的 SQL 语句 + return await connection.QueryFirstOrDefaultAsync( + "SELECT * FROM History WHERE SourceText = @SourceText AND SourceLang = @SourceLang AND TargetLang = @TargetLang", + new + { + SourceText = content, + SourceLang = source, + TargetLang = target + } + ); + } + + /// + /// 查询所有数据 + /// + /// + public static async Task> GetDataAsync() + { + using var connection = new SqliteConnection(ConnectionString); + connection.Open(); + + // 使用 Dapper 执行查询数据的 SQL 语句 + return await connection.GetAllAsync(); + } + + /// + /// 分页查询 + /// + /// + /// + /// + public static async Task?> GetDataAsync(int pageNum, int pageSize) + { + using var connection = new SqliteConnection(ConnectionString); + connection.Open(); + + // 计算起始行号 + int startRow = (pageNum - 1) * pageSize + 1; + + // 使用 Dapper 进行分页查询 + string query = + @"SELECT * FROM (SELECT ROW_NUMBER() OVER (ORDER BY Time) AS RowNum, SourceText FROM History) AS p WHERE RowNum BETWEEN @StartRow AND @EndRow"; + + return await connection.QueryAsync(query, new { StartRow = startRow, EndRow = startRow + pageSize - 1 }); + } + + #endregion Asynchronous method + + #region Synchronous method + + public static void InitializeDB() + { + using var connection = new SqliteConnection(ConnectionString); + connection.Open(); + + // 创建表的 SQL 语句 + string createTableSql = + @" + CREATE TABLE IF NOT EXISTS History ( + Id INTEGER PRIMARY KEY AUTOINCREMENT, + Time TEXT NOT NULL, + SourceLang TEXT, + TargetLang TEXT, + SourceText TEXT, + Data TEXT, + Favorite INTEGER, + Remark TEXT + ); + "; + connection.Execute(createTableSql); + } + + /// + /// 删除记录 + /// + /// + /// + public static bool DeleteData(HistoryModel history) + { + using var connection = new SqliteConnection(ConnectionString); + connection.Open(); + + return connection.Delete(history); + } + + /// + /// 删除所有记录 + /// + /// + public static bool DeleteAllData() + { + using var connection = new SqliteConnection(ConnectionString); + connection.Open(); + + return connection.DeleteAll(); + } + + /// + /// 插入数据 + /// + /// + /// + /// + public static void InsertData(HistoryModel history, long count, bool forceWrite = false) + { + using var connection = new SqliteConnection(ConnectionString); + connection.Open(); + + var curCount = connection.QueryFirstOrDefault("SELECT COUNT(*) FROM History"); + if (curCount > count) + { + var sql = @"DELETE FROM History WHERE Id IN (SELECT Id FROM History ORDER BY Id ASC LIMIT @Limit)"; + + connection.Execute(sql, new { Limit = curCount - count + 1 }); + } + + if (forceWrite) + { + // 使用 Dapper 的 FirstOrDefault 方法进行查询 + var existingHistory = connection.QueryFirstOrDefault( + "SELECT * FROM History WHERE SourceText = @SourceText AND SourceLang = @SourceLang AND TargetLang = @TargetLang", + new + { + history.SourceText, + history.SourceLang, + history.TargetLang + } + ); + + if (existingHistory != null) + { + // 使用 Dapper.Contrib 的 Update 方法更新数据 + existingHistory.Time = history.Time; + existingHistory.Data = history.Data; + connection.Update(existingHistory); + return; + } + } + + // 使用 Dapper.Contrib 的 Insert 方法插入数据 + connection.Insert(history); + } + + /// + /// 查询所有数据 + /// + /// + public static IEnumerable GetData() + { + using var connection = new SqliteConnection(ConnectionString); + connection.Open(); + + // 使用 Dapper 执行查询数据的 SQL 语句 + return connection.GetAll(); + } + + /// + /// 查询数据 + /// + /// + /// + /// + /// + public static HistoryModel? GetData(string content, string source, string target) + { + using var connection = new SqliteConnection(ConnectionString); + connection.Open(); + + // 使用 Dapper 执行查询数据的 SQL 语句 + return connection.QueryFirstOrDefault( + "SELECT * FROM History WHERE SourceText = @SourceText AND SourceLang = @SourceLang AND TargetLang = @TargetLang", + new + { + SourceText = content, + SourceLang = source, + TargetLang = target + } + ); + } + + #endregion Synchronous method + + #region Properties + + /// + /// 连接字符串 + /// + private static string ConnectionString => $"Data Source={DbName}"; + + /// + /// 数据文件 + /// + private static string DbName => $"{ApplicationPath}\\{AppName.ToLower()}.db"; + +#if true + + /// + /// C:\Users\user\AppData\Local\STranslate + /// + private static string ApplicationPath => + $"{Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)}\\{AppName}"; + +#else + /// + /// 当前目录 + /// + private static string ApplicationPath => $"{AppDomain.CurrentDomain.BaseDirectory}"; +#endif + private static readonly string AppName = Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly()!.Location); + + #endregion Properties + } +} \ No newline at end of file diff --git a/STranslate/Helper/SqliteHelper.cs b/STranslate/Helper/SqliteHelper.cs deleted file mode 100644 index b4ae1fe0..00000000 --- a/STranslate/Helper/SqliteHelper.cs +++ /dev/null @@ -1,98 +0,0 @@ -using SQLite; -using STranslate.Model; -using STranslate.ViewModel; -using System; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Threading.Tasks; - -namespace STranslate.Helper -{ - //https://github.com/praeclarum/sqlite-net - public class SqliteHelper : IDisposable - { - public SqliteHelper() - { - _sqlDB = new SQLiteConnection(_dbName); - _sqlDB.CreateTable(); - } - - public void Insert(DateTime time, string source, string target, LanguageEnum sourceLang, LanguageEnum targetLang, string api) - { - try - { - var model = new SqliteModel - { - Time = time, - SourceText = source, - TargetText = target, - SourceLang = sourceLang.ToString(), - TargetLang = targetLang.ToString(), - Api = api, - }; - - //查询最大数量 - var count = _sqlDB.Table().Count(); - if (count >= SettingsVM.Instance.MaxHistoryCount) - { - _sqlDB.Execute("delete from histories where id in (select id from histories limit (?))" - , count + 1 - SettingsVM.Instance.MaxHistoryCount); - } - //手动切换目标语言强制插入替换 - _sqlDB.Table() - .Where(x => x.SourceText.Equals(model.SourceText)) - .ToList().ForEach(x => - { - _sqlDB.Delete(x); - }); - _sqlDB.Insert(model); - } - catch (Exception ex) - { - System.Windows.MessageBox.Show($"本地记录插入错误\n{ex.Message}"); - } - } - - /// - /// 查询记录 - /// - /// - /// - public string Query(string str) - { - var query = _sqlDB.Table().Where(x => x.SourceText.Equals(str)); - if (query.Count() <= 0) - { - return string.Empty; - } - else//如果超过一个删除多余的 - { - var tmp = query.ToList(); - for (int i = 1; i < tmp.Count; i++) - { - _sqlDB.Delete(tmp[i]); - } - return query.First().TargetText; - } - } - - public void Dispose() - { - _sqlDB.Close(); - } - - private readonly SQLiteConnection _sqlDB; - - /// - /// 数据文件 - /// - private static string _dbName => $"{_ApplicationData}\\{_AppName.ToLower()}.db"; - - /// - /// C:\Users\user\AppData\Local\STranslate - /// - private static string _ApplicationData => $"{Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)}\\{_AppName}"; - private static readonly string _AppName = Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly().Location); - } -} diff --git a/STranslate/Helper/ToastHelper.cs b/STranslate/Helper/ToastHelper.cs new file mode 100644 index 00000000..27d1bcb9 --- /dev/null +++ b/STranslate/Helper/ToastHelper.cs @@ -0,0 +1,138 @@ +using STranslate.Model; +using STranslate.Views; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Windows; +using System.Windows.Media.Animation; +using System.Windows.Threading; + +namespace STranslate.Helper +{ + public class ToastHelper + { + /// + /// 存储每个窗口类型对应的通知信息 + /// + private static readonly Dictionary toastDictionary = []; + + /// + /// 自动收回超时时间 + /// + private const int TIMEOUT = 2; + + /// + /// 显示通知 + /// + /// 通知消息 + /// 窗口类型 + public static void Show(string message, WindowType type = WindowType.Main) + { + EnsureInitialized(type); + + var toastInfo = toastDictionary[type]; + toastInfo.ToastControl.ToastText = message; + toastInfo.ToastControl.Visibility = Visibility.Visible; + + // 复制滑入动画并监听完成事件 + var slideInStoryboard = (toastInfo.ToastControl.Resources["SlideInStoryboard"] as Storyboard)!.Clone(); + slideInStoryboard.Completed += (sender, e) => SlideInStoryboard_Completed(type); + toastInfo.ToastControl.BeginStoryboard(slideInStoryboard); + + // 重置计时器 + toastInfo.Timer.Stop(); + toastInfo.Timer.Start(); + } + + /// + /// 确保窗口初始化 + /// + /// 窗口类型 + private static void EnsureInitialized(WindowType type) + { + DispatcherTimer t; + ToastView tv; + if (!toastDictionary.ContainsKey(type)) + { + t = new DispatcherTimer { Interval = TimeSpan.FromSeconds(TIMEOUT) }; + t.Tick += (sender, e) => Timer_Tick(type); + + // 根据窗口类型将通知控件添加到对应的窗口 + tv = GetNotifyControlForWindowType(type); + + // 将通知信息添加到字典中 + toastDictionary.Add(type, new ToastInfo(tv, t)); + } + else + { + t = toastDictionary[type].Timer; + // 根据窗口类型将通知控件添加到对应的窗口 + tv = GetNotifyControlForWindowType(type); + + toastDictionary[type] = new ToastInfo(tv, t); + } + } + + /// + /// 获取ToastView + /// + /// + /// + private static ToastView GetNotifyControlForWindowType(WindowType type) => type switch + { + WindowType.Preference => Application.Current.Windows.OfType().FirstOrDefault()!.notify, + WindowType.OCR => Application.Current.Windows.OfType().FirstOrDefault()!.notify, + _ => Application.Current.Windows.OfType().FirstOrDefault()!.notify + }; + + /// + /// 滑入动画完成事件处理 + /// + /// 窗口类型 + private static void SlideInStoryboard_Completed(WindowType type) + { + // 开始计时器 + toastDictionary[type].Timer.Start(); + } + + /// + /// 计时器触发事件处理 + /// + /// 窗口类型 + private static void Timer_Tick(WindowType type) + { + var toastInfo = toastDictionary[type]; + toastInfo.Timer.Stop(); + + // 复制滑出动画并监听完成事件 + var slideOutStoryboard = (toastInfo.ToastControl.Resources["SlideOutStoryboard"] as Storyboard)!.Clone(); + slideOutStoryboard.Completed += (sender, e) => SlideOutStoryboard_Completed(type); + toastInfo.ToastControl.BeginStoryboard(slideOutStoryboard); + } + + /// + /// 滑出动画完成事件处理 + /// + /// 窗口类型 + private static void SlideOutStoryboard_Completed(WindowType type) + { + // 隐藏通知控件 + toastDictionary[type].ToastControl.Visibility = Visibility.Collapsed; + } + + /// + /// 存储通知信息的内部类 + /// + private class ToastInfo + { + public ToastView ToastControl { get; } + public DispatcherTimer Timer { get; } + + public ToastInfo(ToastView toastControl, DispatcherTimer timer) + { + ToastControl = toastControl; + Timer = timer; + } + } + } +} \ No newline at end of file diff --git a/STranslate/Properties/AssemblyInfo.cs b/STranslate/Properties/AssemblyInfo.cs deleted file mode 100644 index 65495e5b..00000000 --- a/STranslate/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; -using System.Windows; - -// 有关程序集的一般信息由以下 -// 控制。更改这些特性值可修改 -// 与程序集关联的信息。 -[assembly: AssemblyTitle("STranslate")] -[assembly: AssemblyDescription("A ready to use and ready to go translation tool")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("ZGGSONG")] -[assembly: AssemblyProduct("STranslate")] -[assembly: AssemblyCopyright("Copyright © 2022")] -[assembly: AssemblyTrademark("ZGGSONG")] -[assembly: AssemblyCulture("")] - -// 将 ComVisible 设置为 false 会使此程序集中的类型 -//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型 -//请将此类型的 ComVisible 特性设置为 true。 -[assembly: ComVisible(false)] - -//若要开始生成可本地化的应用程序,请设置 -//.csproj 文件中的 CultureYouAreCodingWith -//例如,如果您在源文件中使用的是美国英语, -//使用的是美国英语,请将 设置为 en-US。 然后取消 -//对以下 NeutralResourceLanguage 特性的注释。 更新 -//以下行中的“en-US”以匹配项目文件中的 UICulture 设置。 - -//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] - -[assembly: ThemeInfo( - ResourceDictionaryLocation.None, //主题特定资源词典所处位置 - //(未在页面中找到资源时使用, - //或应用程序资源字典中找到时使用) - ResourceDictionaryLocation.SourceAssembly //常规资源词典所处位置 - //(未在页面中找到资源时使用, - //、应用程序或任何主题专用资源字典中找到时使用) -)] - -// 程序集的版本信息由下列四个值组成: -// -// 主版本 -// 次版本 -// 生成号 -// 修订号 -// -//可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值 -//通过使用 "*",如下所示: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("0.2.8.0")] -[assembly: AssemblyFileVersion("0.2.8.0")] -[assembly: Guid("CE252DD8-179F-4544-9989-453F5DEA378D")] \ No newline at end of file diff --git a/STranslate/Properties/PublishProfiles/FolderProfile.pubxml b/STranslate/Properties/PublishProfiles/FolderProfile.pubxml new file mode 100644 index 00000000..5215a1cc --- /dev/null +++ b/STranslate/Properties/PublishProfiles/FolderProfile.pubxml @@ -0,0 +1,18 @@ + + + + + Release + Any CPU + bin\Release\net8.0-windows\publish\win-x64\ + FileSystem + <_TargetId>Folder + net8.0-windows + win-x64 + false + false + false + + \ No newline at end of file diff --git a/STranslate/Properties/Resources.Designer.cs b/STranslate/Properties/Resources.Designer.cs deleted file mode 100644 index b649b702..00000000 --- a/STranslate/Properties/Resources.Designer.cs +++ /dev/null @@ -1,73 +0,0 @@ -//------------------------------------------------------------------------------ -// -// 此代码由工具生成。 -// 运行时版本:4.0.30319.42000 -// -// 对此文件的更改可能会导致不正确的行为,并且如果 -// 重新生成代码,这些更改将会丢失。 -// -//------------------------------------------------------------------------------ - -namespace STranslate.Properties { - using System; - - - /// - /// 一个强类型的资源类,用于查找本地化的字符串等。 - /// - // 此类是由 StronglyTypedResourceBuilder - // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 - // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen - // (以 /str 作为命令选项),或重新生成 VS 项目。 - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() { - } - - /// - /// 返回此类使用的缓存的 ResourceManager 实例。 - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("STranslate.Properties.Resources", typeof(Resources).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// 重写当前线程的 CurrentUICulture 属性,对 - /// 使用此强类型资源类的所有资源查找执行重写。 - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// 查找类似于 (图标) 的 System.Drawing.Icon 类型的本地化资源。 - /// - internal static System.Drawing.Icon translate { - get { - object obj = ResourceManager.GetObject("translate", resourceCulture); - return ((System.Drawing.Icon)(obj)); - } - } - } -} diff --git a/STranslate/Properties/Resources.resx b/STranslate/Properties/Resources.resx deleted file mode 100644 index ee247771..00000000 --- a/STranslate/Properties/Resources.resx +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - - ..\Resources\translate.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - \ No newline at end of file diff --git a/STranslate/Properties/Settings.Designer.cs b/STranslate/Properties/Settings.Designer.cs deleted file mode 100644 index 39fccf46..00000000 --- a/STranslate/Properties/Settings.Designer.cs +++ /dev/null @@ -1,26 +0,0 @@ -//------------------------------------------------------------------------------ -// -// 此代码由工具生成。 -// 运行时版本:4.0.30319.42000 -// -// 对此文件的更改可能会导致不正确的行为,并且如果 -// 重新生成代码,这些更改将会丢失。 -// -//------------------------------------------------------------------------------ - -namespace STranslate.Properties { - - - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.4.0.0")] - internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { - - private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); - - public static Settings Default { - get { - return defaultInstance; - } - } - } -} diff --git a/STranslate/Properties/Settings.settings b/STranslate/Properties/Settings.settings deleted file mode 100644 index 033d7a5e..00000000 --- a/STranslate/Properties/Settings.settings +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/STranslate/Resources/translate.ico b/STranslate/Resources/translate.ico deleted file mode 100644 index 37cd4bbc..00000000 Binary files a/STranslate/Resources/translate.ico and /dev/null differ diff --git a/STranslate/STranslate.csproj b/STranslate/STranslate.csproj index abbff7d8..4f0f24f2 100644 --- a/STranslate/STranslate.csproj +++ b/STranslate/STranslate.csproj @@ -1,242 +1,58 @@ - - - + + - Debug - AnyCPU - {2597B480-185C-4D6D-94EC-8641BB6777F5} WinExe - STranslate - STranslate - v4.8 - 512 - {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 4 - true - true - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - false - true - - - + net8.0-windows + enable + true + ..\STranslate.Style\Resources\favicon.ico + true + app.manifest + x64 + README.md + 1.0.0.0104 + 1.0.0.0104 - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - false - - -   -     -    .allowedextension -  AnyCPUnonetruebin\Release\TRACEprompt4falseAuto - - Resources\translate.ico - - - app.manifest + + + none + - - ..\packages\Microsoft.Xaml.Behaviors.Wpf.1.1.39\lib\net45\Microsoft.Xaml.Behaviors.dll - - - ..\packages\Newtonsoft.Json.13.0.2\lib\net45\Newtonsoft.Json.dll - - - ..\packages\sqlite-net-pcl.1.8.116\lib\netstandard2.0\SQLite-net.dll - - - ..\packages\SQLitePCLRaw.bundle_green.2.0.4\lib\net461\SQLitePCLRaw.batteries_v2.dll - - - ..\packages\SQLitePCLRaw.core.2.0.4\lib\netstandard2.0\SQLitePCLRaw.core.dll - - - ..\packages\SQLitePCLRaw.bundle_green.2.0.4\lib\net461\SQLitePCLRaw.nativelibrary.dll - - - ..\packages\SQLitePCLRaw.provider.dynamic_cdecl.2.0.4\lib\netstandard2.0\SQLitePCLRaw.provider.dynamic_cdecl.dll - - - - ..\packages\System.Buffers.4.4.0\lib\netstandard2.0\System.Buffers.dll - - - - - ..\packages\System.Memory.4.5.3\lib\netstandard2.0\System.Memory.dll - - - - ..\packages\System.Numerics.Vectors.4.4.0\lib\net46\System.Numerics.Vectors.dll - - - ..\packages\System.Runtime.CompilerServices.Unsafe.4.5.2\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll - - - - - - - - - - - - 4.0 - - - ..\packages\Tesseract.5.2.0\lib\net48\Tesseract.dll - - - - - - ..\packages\WpfScreenHelper.2.1.0\lib\net40\WpfScreenHelper.dll - + + + + + + + + + + + + - - MSBuild:Compile - Designer - - - - - - - - - - - True - True - Resources.resx - - - - - - - ScreenShotWindow.xaml - - - SettingsWindow.xaml - - - MSBuild:Compile - Designer - - - App.xaml + + + + + + + + + Code - - MainWindow.xaml + Code - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - - + Code - - True - Settings.settings - True + + Code - - PreserveNewest - - - ResXFileCodeGenerator - Resources.Designer.cs - - - PreserveNewest - - - - Always - - - - SettingsSingleFileGenerator - Settings.Designer.cs - - - - - - - - False - Microsoft .NET Framework 4.8 %28x86 和 x64%29 - true - - - False - .NET Framework 3.5 SP1 - false - - - - - {F935DC20-1CF0-11D0-ADB9-00C04FD58A0B} - 1 - 0 - 0 - tlbimp - False - True - - - - - {a65f547f-52e2-4ad5-8c8b-53710e1485fa} - STranslate.Model - - - {a5f60db2-ef06-4f36-a907-c92a64d0557e} - STranslate.Style - - - - - - 这台计算机上缺少此项目引用的 NuGet 程序包。使用“NuGet 程序包还原”可下载这些程序包。有关更多信息,请参见 http://go.microsoft.com/fwlink/?LinkID=322105。缺少的文件是 {0}。 - - - - - - \ No newline at end of file + + diff --git a/STranslate/Util/Util.cs b/STranslate/Util/Util.cs deleted file mode 100644 index ac8d9211..00000000 --- a/STranslate/Util/Util.cs +++ /dev/null @@ -1,354 +0,0 @@ -using IWshRuntimeLibrary; -using Newtonsoft.Json; -using STranslate.Model; -using System; -using System.Collections.Generic; -using System.Drawing; -using System.Linq; -using System.Net.Http; -using System.Reflection; -using System.Security.Cryptography; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading.Tasks; -using System.Web; -using System.Windows; -using System.Windows.Media; -using Tesseract; - -namespace STranslate -{ - public class Util - { - #region 翻译接口 - public static async Task TranslateDeepLAsync(string url, string text, LanguageEnum target, LanguageEnum source = LanguageEnum.AUTO) - { - var req = new DeeplReq() - { - Text = text, - SourceLang = source.ToString(), - TargetLang = target.ToString(), - }; - - if (source == LanguageEnum.AUTO) - { - req.SourceLang = LanguageEnum.AUTO.ToString().ToLower(); - } - if (target == LanguageEnum.AUTO) - { - req.TargetLang = LanguageEnum.AUTO.ToString().ToLower(); - } - var reqStr = JsonConvert.SerializeObject(req); - var respStr = await PostAsync(url, reqStr); - var resp = JsonConvert.DeserializeObject(respStr); - - if (resp == null || resp.Code != 200) - { - return string.Empty; - } - - return resp?.Data ?? string.Empty; - } - - /// - /// 百度翻译异步接口 - /// - /// 应用ID - /// 应用Secret - /// 需要翻译的文本 - /// 目标语言 - /// 当前语言 - /// - public static async Task TranslateBaiduAsync(string appID, string secretKey, string text, LanguageEnum target, LanguageEnum source = LanguageEnum.AUTO) - { - try - { - Random rd = new Random(); - string salt = rd.Next(100000).ToString(); - string sign = EncryptString(appID + text + salt + secretKey); - string url = "http://api.fanyi.baidu.com/api/trans/vip/translate?"; - url += "q=" + HttpUtility.UrlEncode(text); - url += "&from=" + source.ToString().ToLower(); - url += "&to=" + target.ToString().ToLower(); - url += "&appid=" + appID; - url += "&salt=" + salt; - url += "&sign=" + sign; -#if false - HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); - request.Method = "GET"; - request.ContentType = "text/html;charset=UTF-8"; - request.UserAgent = null; - request.Timeout = 6000; - HttpWebResponse response = (HttpWebResponse)request.GetResponse(); - Stream myResponseStream = response.GetResponseStream(); - StreamReader myStreamReader = new StreamReader(myResponseStream, Encoding.GetEncoding("utf-8")); - string retString = myStreamReader.ReadToEnd(); - myStreamReader.Close(); - myResponseStream.Close(); -#endif - - var retString = await GetAsync(url); - var resp = JsonConvert.DeserializeObject(retString); - if (resp.From != null) - { - return resp.TransResult[0]?.Dst; - } - return string.Empty; - } - catch (Exception ex) - { - throw ex; - } - } - - // 计算MD5值 - public static string EncryptString(string str) - { - MD5 md5 = MD5.Create(); - // 将字符串转换成字节数组 - byte[] byteOld = Encoding.UTF8.GetBytes(str); - // 调用加密方法 - byte[] byteNew = md5.ComputeHash(byteOld); - // 将加密结果转换为字符串 - StringBuilder sb = new StringBuilder(); - foreach (byte b in byteNew) - { - // 将字节转换成16进制表示的字符串, - sb.Append(b.ToString("x2")); - } - // 返回加密的字符串 - return sb.ToString(); - } - - /// - /// 枚举信息 - /// - /// - /// - public static Dictionary GetEnumList() where T : Enum - { - var dict = new Dictionary(); - List list = Enum.GetValues(typeof(T)).OfType().ToList(); - list.ForEach(x => - { - dict.Add(x.GetDescription(), x); - }); - return dict; - } - #endregion - - #region Http - /// - /// 异步Post请求 - /// - /// - /// - /// - public static async Task PostAsync(string url, string req) - { - using (var client = new HttpClient()) - { - var content = new StringContent(req, Encoding.UTF8, "application/json"); - - var respContent = await client.PostAsync(url, content); - - string respStr = await respContent.Content.ReadAsStringAsync(); - ; - return respStr; - } - } - - /// - /// 异步Get请求 - /// - /// - /// - public static async Task GetAsync(string urlpath) - { - using (var client = new HttpClient()) - { - try - { - var respContent = await client.GetAsync(urlpath); - - string respStr = await respContent.Content.ReadAsStringAsync(); - - return respStr; - } - catch (Exception ex) - { - throw ex; - } - } - } - #endregion - - #region GenString - /// - /// 构造蛇形结果 - /// - /// - /// - public static string GenSnakeString(List req) - { - var ret = string.Empty; - - req.ForEach(x => - { - ret += "_" + x.ToLower(); - }); - return ret.Substring(1); - } - - /// - /// 构造驼峰结果 - /// - /// - /// 是否为小驼峰 - /// - public static string GenHumpString(List req, bool isSmallHump) - { - try - { - string ret = string.Empty; - var array = req.ToArray(); - for (var j = 0; j < array.Length; j++) - { - char[] chars = array[j].ToCharArray(); - //判断chars是否为空 - if (chars.Length == 0) continue; - if (j == 0 && isSmallHump) chars[0] = char.ToLower(chars[0]); - else chars[0] = char.ToUpper(chars[0]); - for (int i = 1; i < chars.Length; i++) - { - chars[i] = char.ToLower(chars[i]); - } - ret += new string(chars); - } - return ret; - } - catch (Exception ex) - { - throw new Exception("构造驼峰出错, " + ex.Message); - } - - } - /// - /// 提取英文 - /// - /// - /// - public static string ExtractEngString(string str) - { - Regex regex = new Regex("[a-zA-Z]+"); - - MatchCollection mMactchCol = regex.Matches(str); - string strA_Z = string.Empty; - foreach (Match mMatch in mMactchCol) - { - strA_Z += mMatch.Value; - } - return strA_Z; - } - - /// - /// 划词文本预处理,例如PDF文字复制出来总含有很多多余的空格 - /// 使用正则表达式[\\s]+匹配连续的空白字符(包括空格、制表符、换行符等),并将其替换为单个空格字符 - /// - /// - /// - public static string PreProcessTexts(string text) - { - try - { - text = new Regex("[\\s]+").Replace(text, " "); - } - catch (Exception) - { - text = string.Empty; - } - return text.Trim(); - } - #endregion - - #region Screenshot - /// - /// Tesseract 库获取文本 - /// - /// - /// - public static string TesseractGetText(Bitmap bmp) - { - try - { - using (var engine = new TesseractEngine(@"./tessdata", "eng", EngineMode.Default)) - //using (var engine = new TesseractEngine(@"./tessdata", "chi_sim", EngineMode.Default)) - { - using (var pix = PixConverter.ToPix(bmp)) - { - using (var page = engine.Process(pix)) - { - return page.GetText(); - } - } - } - } - catch (Exception ex) - { - throw ex; - } - } - - public static ImageBrush BitmapToImageBrush(Bitmap bmp) - { - ImageBrush brush = new ImageBrush(); - IntPtr hBitmap = bmp.GetHbitmap(); - ImageSource wpfBitmap = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap( - hBitmap, - IntPtr.Zero, - Int32Rect.Empty, - System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions()); - brush.ImageSource = wpfBitmap; - FlushMemory(); - return brush; - } - /// - /// 清理内存 - /// - public static void FlushMemory() - { - GC.Collect(); - GC.WaitForPendingFinalizers(); - if (Environment.OSVersion.Platform == PlatformID.Win32NT) - { - Helper.NativeMethodHelper.SetProcessWorkingSetSize(System.Diagnostics.Process.GetCurrentProcess().Handle, -1, -1); - } - } - #endregion - - #region Shortcut - public static void CreateShortcut() - { - string deskTop = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Desktop); - string dirPath = System.Environment.CurrentDirectory; - string exePath = Assembly.GetExecutingAssembly().Location; - System.Diagnostics.FileVersionInfo exeInfo = System.Diagnostics.FileVersionInfo.GetVersionInfo(exePath); - if (System.IO.File.Exists(string.Format(@"{0}\STranslate.lnk", deskTop))) - { - System.IO.File.Delete(string.Format(@"{0}\STranslate.lnk", deskTop));//删除原来的桌面快捷键方式 - return; - } - WshShell shell = new WshShell(); - IWshShortcut shortcut = (IWshShortcut)shell.CreateShortcut(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) + "\\" + "STranslate.lnk"); - shortcut.TargetPath = @exePath; //目标文件 - shortcut.WorkingDirectory = dirPath; //目标文件夹 - shortcut.WindowStyle = 1; //目标应用程序的窗口状态分为普通、最大化、最小化【1,3,7】 - shortcut.Description = "自动更新程序"; //描述 - shortcut.IconLocation = string.Format(@"{0}\64.ico", dirPath); //快捷方式图标 - shortcut.Arguments = ""; - shortcut.Hotkey = "SHIFT+DELETE"; // 快捷键 - shortcut.Save(); - } - #endregion - } -} diff --git a/STranslate/View/MainWindow.xaml b/STranslate/View/MainWindow.xaml deleted file mode 100644 index 40a3d60c..00000000 --- a/STranslate/View/MainWindow.xaml +++ /dev/null @@ -1,256 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/STranslate/View/MainWindow.xaml.cs b/STranslate/View/MainWindow.xaml.cs deleted file mode 100644 index 4a80549c..00000000 --- a/STranslate/View/MainWindow.xaml.cs +++ /dev/null @@ -1,186 +0,0 @@ -using STranslate.Helper; -using STranslate.ViewModel; -using System; -using System.IO; -using System.Windows; -using System.Windows.Forms; -using System.Windows.Media.Animation; -using System.Windows.Media; -using Application = System.Windows.Application; -using MessageBox = System.Windows.MessageBox; - -namespace STranslate.View -{ - /// - /// Interaction logic for MainWindow.xaml - /// - public partial class MainWindow : Window - { - public MainWindow() - { - DataContext = vm; - vm.Mainwin = this; - - InitialTray(); - - Microsoft.Win32.SystemEvents.DisplaySettingsChanged += SystemEvents_DisplaySettingsChanged; - } - - private void SystemEvents_DisplaySettingsChanged(object sender, EventArgs e) - { - InitIcon(); - } - - /// - /// 监听全局快捷键 - /// - /// - protected override void OnSourceInitialized(EventArgs e) - { - base.OnSourceInitialized(e); - - this.Hide(); - - HotkeysHelper.InitialHook(this); - HotkeysHelper.Register(HotkeysHelper.InputTranslateId, () => - { - vm.InputTranslate(); - }); - - HotkeysHelper.Register(HotkeysHelper.CrosswordTranslateId, () => - { - vm.CrossWordTranslate(); - }); - - HotkeysHelper.Register(HotkeysHelper.ScreenShotTranslateId, () => - { - vm.ScreenShotTranslate(); - }); - - HotkeysHelper.Register(HotkeysHelper.OpenMainWindowId, () => - { - vm.OpenMainWin(); - }); - - if (ViewModel.MainVM.Instance.NHotkeys.InputTranslate.Conflict - || ViewModel.MainVM.Instance.NHotkeys.CrosswordTranslate.Conflict - || ViewModel.MainVM.Instance.NHotkeys.ScreenShotTranslate.Conflict - || ViewModel.MainVM.Instance.NHotkeys.OpenMainWindow.Conflict) - { - MessageBox.Show("全局快捷键有冲突,请前往软件首选项中修改..."); - } - } - - private void Copy_Click(object sender, RoutedEventArgs e) - { - var o = sender as System.Windows.Controls.Button; - toastTxt.Text = (o.ToolTip as System.Windows.Controls.ToolTip).Name; - //创建一个一个对象,对两个值在时间线上进行动画处理(移动距离,移动到的位置) - var da = new DoubleAnimation(); - //设定动画时间线 - da.Duration = new Duration(TimeSpan.FromSeconds(0.8)); - //设定移动动画的结束值,控件向下移动60个像素,向上移动则是-60 - da.To = 50; - da.From = 0; - da.AccelerationRatio = 0.2; - da.DecelerationRatio = 0.8; - da.AutoReverse = true; - //btnFlash要进行动画操作的控件名 - Toast.RenderTransform = new TranslateTransform(); - //开始进行动画处理 - Toast.RenderTransform.BeginAnimation(TranslateTransform.YProperty, da); - } - - private MainVM vm = MainVM.Instance; - - public readonly NotifyIcon NotifyIcon = new NotifyIcon(); - - #region Initial TrayIcon - private void InitIcon() - { - var stream = Application - .GetResourceStream( - new Uri("pack://application:,,,/STranslate.Style;component/Resources/translate.ico", - UriKind.RelativeOrAbsolute))?.Stream; - if (NotifyIcon.Icon != null) - { - NotifyIcon.Icon.Dispose(); - } - if (stream != null) - { - NotifyIcon.Icon = new System.Drawing.Icon(stream); - } - } - private void InitialTray() - { - var app = Path.GetFileNameWithoutExtension(System.Reflection.Assembly.GetEntryAssembly()?.Location); - NotifyIcon.Text = $@"{app} {SettingsVM.Instance.Version}"; - InitIcon(); - NotifyIcon.Visible = true; - NotifyIcon.BalloonTipText = $@"{app} already started..."; - NotifyIcon.ShowBalloonTip(500); - - NotifyIcon.MouseDoubleClick += (o, e) => vm.InputTranslate(); - - var menuItems = new[] - { - new MenuItem("输入翻译", (o, e) => vm.InputTranslate()), - new MenuItem("截图翻译", (o, e) => vm.ScreenShotTranslate()), - new MenuItem("划词翻译") { Enabled = false }, - new MenuItem("-"), - new MenuItem("显示主界面", (o, e) => vm.OpenMainWin()), - new MenuItem("首选项", (o, e) => Preference()), - new MenuItem("-"), - new MenuItem("退出", (o, e) => vm.ExitApp(0)), - }; - NotifyIcon.ContextMenu = new ContextMenu(menuItems); - } - - /// - /// 设置 - /// - private void Preference() - { - SettingsWindow window = null; - foreach (Window item in Application.Current.Windows) - { - if (item is SettingsWindow) - { - window = (SettingsWindow)item; - window.WindowState = WindowState.Normal; - window.Activate(); - break; - } - } - if (window == null) - { - window = new SettingsWindow(); - window.Show(); - window.Activate(); - } - } - - private void ScreenshotTranslateMenuItem_Click(object sender, EventArgs e) - { - vm.ScreenShotTranslate(); - } - - private void OpenMainWin_Click(object sender, EventArgs e) - { - vm.OpenMainWin(); - } - - private void Exit_Click(object sender, EventArgs e) - { - vm.ExitApp(0); - } - - private void InputTranslateMenuItem_Click(object sender, EventArgs e) - { - vm.InputTranslate(); - } - - #endregion - - } -} \ No newline at end of file diff --git a/STranslate/View/ScreenShotWindow.xaml b/STranslate/View/ScreenShotWindow.xaml deleted file mode 100644 index 2ac3e20f..00000000 --- a/STranslate/View/ScreenShotWindow.xaml +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/STranslate/View/SettingsWindow.xaml b/STranslate/View/SettingsWindow.xaml deleted file mode 100644 index 75d0b821..00000000 --- a/STranslate/View/SettingsWindow.xaml +++ /dev/null @@ -1,384 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/STranslate/View/SettingsWindow.xaml.cs b/STranslate/View/SettingsWindow.xaml.cs deleted file mode 100644 index 618cc84e..00000000 --- a/STranslate/View/SettingsWindow.xaml.cs +++ /dev/null @@ -1,187 +0,0 @@ -using STranslate.Helper; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Data; -using System.Windows.Documents; -using System.Windows.Input; -using System.Windows.Media; -using System.Windows.Media.Imaging; -using System.Windows.Shapes; - -namespace STranslate.View -{ - /// - /// SettingsWindow.xaml 的交互逻辑 - /// - public partial class SettingsWindow : Window - { - public SettingsWindow() - { - InitializeComponent(); - -#if DEBUG - this.window.Topmost = true; -#endif - - DataContext = ViewModel.SettingsVM.Instance; - } - - private void window_Loaded(object sender, RoutedEventArgs e) - { - this.InputTextBox.Text = ViewModel.MainVM.Instance.NHotkeys.InputTranslate.Text; - this.CrossWordTextBox.Text = ViewModel.MainVM.Instance.NHotkeys.CrosswordTranslate.Text; - this.ScreenshotTextBox.Text = ViewModel.MainVM.Instance.NHotkeys.ScreenShotTranslate.Text; - this.ShowMainwinTextBox.Text = ViewModel.MainVM.Instance.NHotkeys.OpenMainWindow.Text; - HotKeyConflictCheck(); - } - - private byte _hotkeysModifiers; - private int _hotkeysKey; - private string _hotkeysText = string.Empty; - - private void HotKeyTextBox_PreviewKeyDown(object sender, KeyEventArgs e) - { - e.Handled = true; - _hotkeysModifiers = 0; - _hotkeysKey = 0; - _hotkeysText = ""; - Key key = (e.Key == Key.System ? e.SystemKey : e.Key); - if (key == Key.LeftShift || key == Key.RightShift - || key == Key.LeftCtrl || key == Key.RightCtrl - || key == Key.LeftAlt || key == Key.RightAlt - || key == Key.LWin || key == Key.RWin) - { - return; - } - StringBuilder shortcutText = new StringBuilder(); - if ((Keyboard.Modifiers & ModifierKeys.Control) != 0) - { - _hotkeysModifiers += 2; - shortcutText.Append("Ctrl + "); - } - if ((Keyboard.Modifiers & ModifierKeys.Shift) != 0) - { - _hotkeysModifiers += 4; - shortcutText.Append("Shift + "); - } - if ((Keyboard.Modifiers & ModifierKeys.Alt) != 0) - { - _hotkeysModifiers += 1; - shortcutText.Append("Alt + "); - } - if (_hotkeysModifiers == 0 && (key < Key.F1 || key > Key.F12)) - { - _hotkeysKey = 0; - shortcutText.Clear(); - ((System.Windows.Controls.TextBox)sender).Text = _hotkeysText = ""; - return; - } - _hotkeysKey = KeyInterop.VirtualKeyFromKey(key); - shortcutText.Append(key.ToString()); - ((System.Windows.Controls.TextBox)sender).Text = _hotkeysText = shortcutText.ToString(); - } - private void CrossWord_KeyUp(object sender, KeyEventArgs e) - { - Key key = (e.Key == Key.System ? e.SystemKey : e.Key); - if (key == Key.LeftShift || key == Key.RightShift - || key == Key.LeftCtrl || key == Key.RightCtrl - || key == Key.LeftAlt || key == Key.RightAlt - || key == Key.LWin || key == Key.RWin) - { - return; - } - ViewModel.MainVM.Instance.NHotkeys.CrosswordTranslate.Modifiers = _hotkeysModifiers; - ViewModel.MainVM.Instance.NHotkeys.CrosswordTranslate.Key = _hotkeysKey; - ViewModel.MainVM.Instance.NHotkeys.CrosswordTranslate.Text = _hotkeysText.ToString(); - HotkeysHelper.ReRegisterHotKey(); - HotKeyConflictCheck(); - } - private void Input_KeyUp(object sender, KeyEventArgs e) - { - Key key = (e.Key == Key.System ? e.SystemKey : e.Key); - if (key == Key.LeftShift || key == Key.RightShift - || key == Key.LeftCtrl || key == Key.RightCtrl - || key == Key.LeftAlt || key == Key.RightAlt - || key == Key.LWin || key == Key.RWin) - { - return; - } - ViewModel.MainVM.Instance.NHotkeys.InputTranslate.Modifiers = _hotkeysModifiers; - ViewModel.MainVM.Instance.NHotkeys.InputTranslate.Key = _hotkeysKey; - ViewModel.MainVM.Instance.NHotkeys.InputTranslate.Text = _hotkeysText.ToString(); - HotkeysHelper.ReRegisterHotKey(); - HotKeyConflictCheck(); - } - private void Screenshot_KeyUp(object sender, KeyEventArgs e) - { - Key key = (e.Key == Key.System ? e.SystemKey : e.Key); - if (key == Key.LeftShift || key == Key.RightShift - || key == Key.LeftCtrl || key == Key.RightCtrl - || key == Key.LeftAlt || key == Key.RightAlt - || key == Key.LWin || key == Key.RWin) - { - return; - } - ViewModel.MainVM.Instance.NHotkeys.ScreenShotTranslate.Modifiers = _hotkeysModifiers; - ViewModel.MainVM.Instance.NHotkeys.ScreenShotTranslate.Key = _hotkeysKey; - ViewModel.MainVM.Instance.NHotkeys.ScreenShotTranslate.Text = _hotkeysText.ToString(); - HotkeysHelper.ReRegisterHotKey(); - HotKeyConflictCheck(); - } - private void ShowMainwin_KeyUp(object sender, KeyEventArgs e) - { - Key key = (e.Key == Key.System ? e.SystemKey : e.Key); - if (key == Key.LeftShift || key == Key.RightShift - || key == Key.LeftCtrl || key == Key.RightCtrl - || key == Key.LeftAlt || key == Key.RightAlt - || key == Key.LWin || key == Key.RWin) - { - return; - } - ViewModel.MainVM.Instance.NHotkeys.OpenMainWindow.Modifiers = _hotkeysModifiers; - ViewModel.MainVM.Instance.NHotkeys.OpenMainWindow.Key = _hotkeysKey; - ViewModel.MainVM.Instance.NHotkeys.OpenMainWindow.Text = _hotkeysText.ToString(); - HotkeysHelper.ReRegisterHotKey(); - HotKeyConflictCheck(); - } - - private void ResetHoskeys_Click(object sender, RoutedEventArgs e) - { - CrossWordTextBox.Text = "Alt + D"; - InputTextBox.Text = "Alt + A"; - ScreenshotTextBox.Text = "Alt + S"; - ShowMainwinTextBox.Text = "Alt + G"; - - ViewModel.MainVM.Instance.NHotkeys.CrosswordTranslate.Modifiers = 1; - ViewModel.MainVM.Instance.NHotkeys.CrosswordTranslate.Key = 68; - ViewModel.MainVM.Instance.NHotkeys.CrosswordTranslate.Text = "Alt + D"; - - ViewModel.MainVM.Instance.NHotkeys.InputTranslate.Modifiers = 1; - ViewModel.MainVM.Instance.NHotkeys.InputTranslate.Key = 65; - ViewModel.MainVM.Instance.NHotkeys.InputTranslate.Text = "Alt + A"; - - ViewModel.MainVM.Instance.NHotkeys.ScreenShotTranslate.Modifiers = 1; - ViewModel.MainVM.Instance.NHotkeys.ScreenShotTranslate.Key = 83; - ViewModel.MainVM.Instance.NHotkeys.ScreenShotTranslate.Text = "Alt + S"; - - ViewModel.MainVM.Instance.NHotkeys.OpenMainWindow.Modifiers = 1; - ViewModel.MainVM.Instance.NHotkeys.OpenMainWindow.Key = 71; - ViewModel.MainVM.Instance.NHotkeys.OpenMainWindow.Text = "Alt + G"; - - HotkeysHelper.ReRegisterHotKey(); - HotKeyConflictCheck(); - } - private void HotKeyConflictCheck() - { - this.CrossWordHotKeyConflictLabel.Visibility = ViewModel.MainVM.Instance.NHotkeys.CrosswordTranslate.Conflict ? Visibility.Visible : Visibility.Hidden; - this.ScreenshotHotKeyConflictLabel.Visibility = ViewModel.MainVM.Instance.NHotkeys.ScreenShotTranslate.Conflict ? Visibility.Visible : Visibility.Hidden; - this.InputHotKeyConflictLabel.Visibility = ViewModel.MainVM.Instance.NHotkeys.InputTranslate.Conflict ? Visibility.Visible : Visibility.Hidden; - this.ShowMainwinHotKeyConflictLabel.Visibility = ViewModel.MainVM.Instance.NHotkeys.OpenMainWindow.Conflict ? Visibility.Visible : Visibility.Hidden; - } - } -} diff --git a/STranslate/ViewModel/MainVM.cs b/STranslate/ViewModel/MainVM.cs deleted file mode 100644 index f8990397..00000000 --- a/STranslate/ViewModel/MainVM.cs +++ /dev/null @@ -1,553 +0,0 @@ -using System; -using System.Linq; -using System.Windows; -using System.Windows.Input; -using System.Threading.Tasks; -using System.Speech.Synthesis; -using System.Collections.Generic; -using System.Text.RegularExpressions; -using STranslate.View; -using STranslate.Model; -using STranslate.Helper; -using System.Windows.Controls; - -namespace STranslate.ViewModel -{ - public class MainVM : BaseVM - { - public MainVM() - { - #region Initial - if (!ReadConfig()) - { - ExitApp(-1); - } - - InputCombo = LanguageEnumDict.Keys.ToList(); - OutputCombo = LanguageEnumDict.Keys.ToList(); - _sqlHelper = new SqliteHelper(); - #endregion - - #region Common - //退出 - ExitCmd = new RelayCommand((_) => true, (_) => ExitApp(0)); - //置顶 - TopmostCmd = new RelayCommand((_) => true, (o) => - { - ((Button)o).SetResourceReference(Control.TemplateProperty, - IsTopmost ? UnTopmostTemplateName : TopmostTemplateName); - IsTopmost = !IsTopmost; - }); - - //ESC - EscCmd = new RelayCommand((_) => true, (o) => - { - //取消置顶 - if (IsTopmost) - { - ((Button)o).SetResourceReference(Control.TemplateProperty, UnTopmostTemplateName); - IsTopmost = !IsTopmost; - } - Mainwin.Hide(); - }); - - //切换语言 - SelectLangChangedCmd = new RelayCommand((_) => true, (_) => - { - if (string.IsNullOrEmpty(InputTxt)) return; - IdentifyLanguage = string.Empty; - _ = Translate(); - }); - //移动 - MouseLeftDownCmd = new RelayCommand((_) => true, (_) => - { - Mainwin.DragMove(); - }); - //失去焦点 - DeactivatedCmd = new RelayCommand((_) => true, (_) => - { - if (IsTopmost) return; - _speech.SpeakAsyncCancelAll(); - Mainwin.Hide(); - - Util.FlushMemory(); - }); - //source speak - SourceSpeakCmd = new RelayCommand((_) => true, (_) => - { - _speech.SpeakAsync(InputTxt); - }); - //target speak - TargetSpeakCmd = new RelayCommand((_) => true, (_) => - { - _speech.SpeakAsync(OutputTxt); - }); - //复制输入 - CopyInputCmd = new RelayCommand((_) => true, (_) => - { - Clipboard.SetDataObject(InputTxt); - }); - //复制翻译结果 - CopyResultCmd = new RelayCommand((_) => true, (_) => - { - Clipboard.SetDataObject(OutputTxt); - }); - //复制蛇形结果 - CopySnakeResultCmd = new RelayCommand((_) => true, (_) => - { - Clipboard.SetDataObject(SnakeRet); - }); - //复制小驼峰结果 - CopySmallHumpResultCmd = new RelayCommand((_) => true, (_) => - { - Clipboard.SetDataObject(SmallHumpRet); - }); - //复制大驼峰结果 - CopyLargeHumpResultCmd = new RelayCommand((_) => true, (_) => - { - Clipboard.SetDataObject(LargeHumpRet); - }); - - //主题切换 - ThemeConvertCmd = new RelayCommand((_) => true, (o) => - { - Application.Current.Resources.MergedDictionaries[0].Source = - Application.Current.Resources.MergedDictionaries[0].Source - .ToString() == ThemeDark ? new Uri(ThemeDefault) : new Uri(ThemeDark); - }); - - //翻译 - TranslateCmd = new RelayCommand((_) => !string.IsNullOrEmpty(InputTxt), async (o) => - { - var forceTranslate = o is null ? false : true; - await Translate(forceTranslate); - }); - #endregion - } - - #region handle - /// - /// 清空所有 - /// - private void ClearAll() - { - InputTxt = string.Empty; - OutputTxt = string.Empty; - SnakeRet = string.Empty; - SmallHumpRet = string.Empty; - LargeHumpRet = string.Empty; - IdentifyLanguage = string.Empty; - - Util.FlushMemory(); - } - /// - /// 打开主窗口 - /// - public void OpenMainWin() - { - Mainwin.Show(); - Mainwin.Activate(); - //TODO: need to deal with this - //TextBoxInput.Focus(); - ((TextBox)Mainwin.FindName("TextBoxInput"))?.Focus(); - } - /// - /// 输入翻译 - /// - public void InputTranslate() - { - ClearAll(); - OpenMainWin(); - } - /// - /// 划词翻译 - /// - public void CrossWordTranslate() - { - ClearAll(); - var sentence = GetWordsHelper.Get(); - OpenMainWin(); - sentence = Util.PreProcessTexts(sentence); - InputTxt = sentence; - _ = Translate(); - } - - /// - /// 截屏翻译 - /// - public void ScreenShotTranslate() - { - ScreenShotWindow window = null; - foreach (Window item in Application.Current.Windows) - { - if (item is ScreenShotWindow) - { - window = (ScreenShotWindow)item; - window.WindowState = WindowState.Normal; - window.Activate(); - break; - } - } - if (window == null) - { - window = new ScreenShotWindow(); - window.Show(); - window.Activate(); - } - } - /// - /// 截屏翻译Ex - /// - public void ScreenShotTranslateEx(string text) - { - InputTranslate(); - text = Util.PreProcessTexts(text); - InputTxt = text; - _ = Translate(); - } - - /// - /// 退出App - /// - public void ExitApp(int id) - { - Util.FlushMemory(); - Mainwin.NotifyIcon.Dispose(); - Mainwin.Close(); - //关闭数据库 - _sqlHelper.Dispose(); - //语音合成销毁 - _speech.Dispose(); - //注销快捷键 - HotkeysHelper.UnRegisterHotKey(); - if (id == 0) - { - //写入配置 - WriteConfig(); - } - Environment.Exit(id); - } - - /// - /// 初始化配置文件 - /// - /// - private bool ReadConfig() - { - try - { - _globalConfig = ConfigHelper.Instance.ReadConfig(); - - #region 读取热键 - NHotkeys = _globalConfig.Hotkeys ?? new Hotkeys - { - InputTranslate = new InputTranslate - { - Modifiers = 1, - Key = 65, - Text = "Alt + A", - Conflict = false, - }, - CrosswordTranslate = new CrosswordTranslate - { - Modifiers = 1, - Key = 68, - Text = "Alt + D", - Conflict = false, - }, - ScreenShotTranslate = new ScreenShotTranslate - { - Modifiers = 1, - Key = 83, - Text = "Alt + S", - Conflict = false, - }, - OpenMainWindow = new OpenMainWindow - { - Modifiers = 1, - Key = 71, - Text = "Alt + G", - Conflict = false, - }, - }; - #endregion - - //读取历史记录数量 - var count = _globalConfig.MaxHistoryCount; - SettingsVM.Instance.MaxHistoryCount = (count <= 0 || count >= 1000) ? 100 : count; - - //读取自动识别语种比例 - var scale = _globalConfig.AutoScale; - SettingsVM.Instance.AutoScale = (scale <= 0 || scale >= 1) ? 0.8 : scale; - - //读取间隔 - var tmp = _globalConfig.WordPickupInterval; - SettingsVM.Instance.WordPickupInterval = (tmp == 0 || tmp > 1000 || tmp < 100) ? 200 : tmp; - - //配置读取主题 - Application.Current.Resources.MergedDictionaries[0].Source = _globalConfig.IsBright ? new Uri(ThemeDefault) : new Uri(ThemeDark); - - //更新服务 - TranslationInterface = _globalConfig.Servers.ToList(); - - if (TranslationInterface.Count < 1) throw new Exception("尚未配置任何翻译接口服务"); - - try - { - //配置读取接口 - SelectedTranslationInterface = TranslationInterface[_globalConfig.SelectServer]; - } - catch (Exception ex) - { - throw new Exception($"配置文件选择服务索引出错, 请修改配置文件后重试", ex); - } - - //从配置读取source target - InputComboSelected = _globalConfig.SourceLanguage; - OutputComboSelected = _globalConfig.TargetLanguage; - - return true; - } - catch (Exception ex) - { - MessageBox.Show(ex.Message, "错误", MessageBoxButton.OK, MessageBoxImage.Error); - return false; - } - } - private void WriteConfig() - { - try - { - ConfigHelper.Instance.WriteConfig(new ConfigModel - { - MaxHistoryCount = SettingsVM.Instance.MaxHistoryCount, - AutoScale = SettingsVM.Instance.AutoScale, - WordPickupInterval = SettingsVM.Instance.WordPickupInterval, - IsBright = Application.Current.Resources.MergedDictionaries[0].Source.ToString() == ThemeDefault ? true : false, - SourceLanguage = InputComboSelected, - TargetLanguage = OutputComboSelected, - SelectServer = TranslationInterface.FindIndex(x => x == SelectedTranslationInterface), - Servers = _globalConfig.Servers, - Hotkeys = NHotkeys, - }); - } - catch (Exception ex) - { - MessageBox.Show(ex.Message, "错误", MessageBoxButton.OK, MessageBoxImage.Error); - } - } - - /// - /// 自动识别语种 - /// - /// 输入语言 - /// - /// Item1: SourceLang - /// Item2: TargetLang - /// - private Tuple AutomaticLanguageRecognition(string text) - { - //1. 首先去除所有数字、标点及特殊符号 - //https://www.techiedelight.com/zh/strip-punctuations-from-a-string-in-csharp/ - text = Regex.Replace(text, - "[1234567890!\"#$%&'()*+,-./:;<=>?@\\[\\]^_`{|}~,。、《》?;‘’:“”【】、{}|·!@#¥%……&*()——+~\\\\]", - string.Empty).Replace(Environment.NewLine, "").Replace(" ", ""); - - //2. 取出上一步中所有英文字符 - var engStr = Util.ExtractEngString(text); - - var ratio = (double)engStr.Length / text.Length; - - //3. 判断英文字符个数占第一步所有字符个数比例,若超过一定比例则判定原字符串为英文字符串,否则为中文字符串 - if (ratio > SettingsVM.Instance.AutoScale) - { - return new Tuple(LanguageEnum.EN.GetDescription(), LanguageEnum.ZH.GetDescription()); - } - else - { - return new Tuple(LanguageEnum.ZH.GetDescription(), LanguageEnum.EN.GetDescription()); - } - } - - /// - /// 翻译 - /// - /// - private async Task Translate(bool forceTranslate = false) - { - try - { - if (string.IsNullOrEmpty(InputTxt.Trim())) - throw new Exception("输入值为空!"); - var isEng = string.Empty; - IdentifyLanguage = string.Empty; - OutputTxt = "翻译中..."; - //清空多种复制 - SnakeRet = string.Empty; - SmallHumpRet = string.Empty; - LargeHumpRet = string.Empty; - - //自动选择目标语言 - if (OutputComboSelected == LanguageEnum.AUTO.GetDescription()) - { - //只有在自动的模式下读取 - var resp = _sqlHelper.Query(InputTxt); - if (!string.IsNullOrEmpty(resp) && !forceTranslate) - { - OutputTxt = resp; - return; - } - var autoRet = AutomaticLanguageRecognition(InputTxt); - IdentifyLanguage = autoRet.Item1; - isEng = autoRet.Item2; - _translateResp = await Util.TranslateDeepLAsync(SelectedTranslationInterface.Api, InputTxt, LanguageEnumDict[autoRet.Item2], LanguageEnumDict[InputComboSelected]); - } - else - { - _translateResp = await Util.TranslateDeepLAsync(SelectedTranslationInterface.Api, InputTxt, LanguageEnumDict[OutputComboSelected], LanguageEnumDict[InputComboSelected]); - } - - //百度 Api - //var translateResp = await TranslateUtil.TranslateBaiduAsync(config.baidu.appid, config.baidu.secretKey, InputTxt, LanguageEnumDict[OutputComboSelected], LanguageEnumDict[InputComboSelected]); - if (_translateResp == string.Empty) - { - OutputTxt = "翻译出错,请稍候再试..."; - return; - } - OutputTxt = _translateResp; - - await Task.Run(() => - { - _sqlHelper.Insert(DateTime.Now, - InputTxt, - OutputTxt, - LanguageEnumDict[string.IsNullOrEmpty(IdentifyLanguage) ? InputComboSelected : IdentifyLanguage], - LanguageEnumDict[string.IsNullOrEmpty(isEng) ? OutputComboSelected : isEng], - SelectedTranslationInterface.Api); - }); - - //如果目标语言不是英文则不进行转换 - //1. 自动判断语种:Tuple item2 不为 EN - //2. 非自动判断语种,目标语种不为 EN - if (!string.IsNullOrEmpty(isEng)) - { - if (isEng != LanguageEnum.EN.GetDescription()) return; - } - else - { - if (LanguageEnumDict[OutputComboSelected] != LanguageEnum.EN) return; - } - - var splitList = OutputTxt.Split(' ').ToList(); - if (splitList.Count > 1) - { - SnakeRet = Util.GenSnakeString(splitList); - SmallHumpRet = Util.GenHumpString(splitList, true); //小驼峰 - LargeHumpRet = Util.GenHumpString(splitList, false); //大驼峰 - } - //System.Diagnostics.Debug.Print(SnakeRet + "\n" + SmallHumpRet + "\n" + LargeHumpRet); - } - catch (Exception ex) - { - OutputTxt = ex.Message; - } - } - #endregion handle - - #region Params - private readonly SqliteHelper _sqlHelper; - private string _translateResp; - public ICommand MouseLeftDownCmd { get; set; } - public ICommand DeactivatedCmd { get; set; } - public ICommand SourceSpeakCmd { get; set; } - public ICommand TargetSpeakCmd { get; set; } - public ICommand TranslateCmd { get; set; } - public ICommand CopyInputCmd { get; set; } - public ICommand CopyResultCmd { get; set; } - public ICommand CopySnakeResultCmd { get; set; } - public ICommand CopySmallHumpResultCmd { get; set; } - public ICommand CopyLargeHumpResultCmd { get; set; } - public ICommand ThemeConvertCmd { get; set; } - public ICommand TopmostCmd { get; set; } - public ICommand EscCmd { get; set; } - public ICommand ExitCmd { get; set; } - public ICommand SelectLangChangedCmd { get; set; } - - /// - /// view传递至viewmodel - /// - public MainWindow Mainwin; - - private static Lazy _instance = new Lazy(() => new MainVM()); - public static MainVM Instance => _instance.Value; - - public Hotkeys NHotkeys; - private bool IsTopmost { get; set; } - private const string TopmostTemplateName = "ButtonTemplateTopmost"; - private const string UnTopmostTemplateName = "ButtonTemplateUnTopmost"; - - /// - /// 全局配置文件 - /// - private ConfigModel _globalConfig; - - /// - /// 识别语种 - /// - private string _identifyLanguage; - public string IdentifyLanguage { get => _identifyLanguage; set => UpdateProperty(ref _identifyLanguage, value); } - /// - /// 构造蛇形结果 - /// - private string _snakeRet; - public string SnakeRet { get => _snakeRet; set => UpdateProperty(ref _snakeRet, value); } - /// - /// 构造驼峰结果 - /// - private string _smallHumpRet; - public string SmallHumpRet { get => _smallHumpRet; set => UpdateProperty(ref _smallHumpRet, value); } - /// - /// 构造驼峰结果 - /// - private string _largeHumpRet; - public string LargeHumpRet { get => _largeHumpRet; set => UpdateProperty(ref _largeHumpRet, value); } - - private string _inputTxt; - public string InputTxt { get => _inputTxt; set => UpdateProperty(ref _inputTxt, value); } - - private string _outputTxt; - public string OutputTxt { get => _outputTxt; set => UpdateProperty(ref _outputTxt, value); } - - private List _inputCombo; - public List InputCombo { get => _inputCombo; set => UpdateProperty(ref _inputCombo, value); } - - private string _inputComboSelected; - public string InputComboSelected { get => _inputComboSelected; set => UpdateProperty(ref _inputComboSelected, value); } - - private List _outputCombo; - public List OutputCombo { get => _outputCombo; set => UpdateProperty(ref _outputCombo, value); } - - private string _outputComboSelected; - public string OutputComboSelected { get => _outputComboSelected; set => UpdateProperty(ref _outputComboSelected, value); } - - /// - /// 目标接口 - /// - private List _translationInterface; - public List TranslationInterface { get => _translationInterface; set => UpdateProperty(ref _translationInterface, value); } - - private Server _selectedTranslationInterface; - public Server SelectedTranslationInterface { get => _selectedTranslationInterface; set => UpdateProperty(ref _selectedTranslationInterface, value); } - private static Dictionary LanguageEnumDict { get => Util.GetEnumList(); } - - /// - /// 语音 - /// - private readonly SpeechSynthesizer _speech = new SpeechSynthesizer(); - - private const string ThemeDark = "pack://application:,,,/STranslate.Style;component/Styles/Dark.xaml"; - private const string ThemeDefault = "pack://application:,,,/STranslate.Style;component/Styles/Default.xaml"; - - #endregion Params - } -} \ No newline at end of file diff --git a/STranslate/ViewModel/ScreenShotVM.cs b/STranslate/ViewModel/ScreenShotVM.cs deleted file mode 100644 index f13ba123..00000000 --- a/STranslate/ViewModel/ScreenShotVM.cs +++ /dev/null @@ -1,167 +0,0 @@ -using STranslate.Helper; -using System; -using System.Collections.Generic; -using System.Drawing; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Input; - -namespace STranslate.ViewModel -{ - public class ScreenShotVM : BaseVM - { - /// - /// reference https://github.com/NPCDW/WpfTool - /// - /// - public ScreenShotVM(Window ui) - { - _ScreenShotWin = ui; - EscCmd = new RelayCommand((_) => true, (_) => - { - _ScreenShotWin.Close(); - }); - - //鼠标移动 - MouseMoveCmd = new RelayCommand((_) => true, (_) => - { - if (MouseDown) - { - System.Windows.Point CurrentPoint = Mouse.GetPosition(_ScreenShotWin); - Rectangle = new Rect(StartPoint, CurrentPoint); - - Canvas.SetLeft(_ScreenShotWin.FindName("LeftMask") as System.Windows.Shapes.Rectangle, 0); - Canvas.SetTop(_ScreenShotWin.FindName("LeftMask") as System.Windows.Shapes.Rectangle, 0); - (_ScreenShotWin.FindName("LeftMask") as System.Windows.Shapes.Rectangle).Width = Rectangle.X; - (_ScreenShotWin.FindName("LeftMask") as System.Windows.Shapes.Rectangle).Height = bitmap.Height; - - Canvas.SetLeft(_ScreenShotWin.FindName("RightMask") as System.Windows.Shapes.Rectangle, Rectangle.Left + Rectangle.Width); - Canvas.SetTop(_ScreenShotWin.FindName("RightMask") as System.Windows.Shapes.Rectangle, 0); - (_ScreenShotWin.FindName("RightMask") as System.Windows.Shapes.Rectangle).Width = bitmap.Width - Rectangle.Left - Rectangle.Width; - (_ScreenShotWin.FindName("RightMask") as System.Windows.Shapes.Rectangle).Height = bitmap.Height; - - Canvas.SetLeft(_ScreenShotWin.FindName("UpMask") as System.Windows.Shapes.Rectangle, Rectangle.Left); - Canvas.SetTop(_ScreenShotWin.FindName("UpMask") as System.Windows.Shapes.Rectangle, 0); - (_ScreenShotWin.FindName("UpMask") as System.Windows.Shapes.Rectangle).Width = Rectangle.Width; - (_ScreenShotWin.FindName("UpMask") as System.Windows.Shapes.Rectangle).Height = Rectangle.Y; - - Canvas.SetLeft(_ScreenShotWin.FindName("DownMask") as System.Windows.Shapes.Rectangle, Rectangle.Left); - Canvas.SetTop(_ScreenShotWin.FindName("DownMask") as System.Windows.Shapes.Rectangle, Rectangle.Y + Rectangle.Height); - (_ScreenShotWin.FindName("DownMask") as System.Windows.Shapes.Rectangle).Width = Rectangle.Width; - (_ScreenShotWin.FindName("DownMask") as System.Windows.Shapes.Rectangle).Height = bitmap.Height - Rectangle.Height - Rectangle.Y; - } - }); - - //左键Down - MouseLeftDownCmd = new RelayCommand((_) => true, (_) => - { - MouseDown = true; - StartPoint = Mouse.GetPosition(_ScreenShotWin); - }); - - //左键Up - MouseLeftUpCmd = new RelayCommand((_) => true, (_) => - { - MouseDown = false; - - int x = (int)(Rectangle.X * dpiScale); - int y = (int)(Rectangle.Y * dpiScale); - int width = (int)(Rectangle.Width * dpiScale); - int height = (int)(Rectangle.Height * dpiScale); - if (width <= 0 || height <= 0) - { - return; - } - Bitmap bmpOut = new Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format32bppArgb); - Graphics g = Graphics.FromImage(bmpOut); - g.DrawImage(bitmap, - new Rectangle(0, 0, width, height), - new Rectangle(x, y, width, height), - GraphicsUnit.Pixel); - - _ScreenShotWin.Close(); - - //TestSaveBmp(bmpOut); - - var getText = Util.TesseractGetText(bmpOut).Trim(); - - MainVM.Instance.ScreenShotTranslateEx(getText); - }); - - ClosedCmd = new RelayCommand((_) => true, (_) => - { - Util.FlushMemory(); - }); - } - - /// - /// 测试保存 - /// - /// - private void TestSaveBmp(Bitmap bmp) - { - bmp.Save("D:\\a.png", System.Drawing.Imaging.ImageFormat.Bmp); - } - - public Tuple InitView1() - { - // 获取鼠标所在屏幕 - System.Drawing.Point ms = System.Windows.Forms.Control.MousePosition; - Rect bounds = new Rect(); - int x = 0, y = 0, width = 0, height = 0; - foreach (WpfScreenHelper.Screen screen in WpfScreenHelper.Screen.AllScreens) - { - bounds = screen.WpfBounds; - dpiScale = screen.ScaleFactor; - x = (int)(bounds.X * dpiScale); - y = (int)(bounds.Y * dpiScale); - width = (int)(bounds.Width * dpiScale); - height = (int)(bounds.Height * dpiScale); - if (x <= ms.X && ms.X < x + width && y <= ms.Y && ms.Y < y + height) - { - break; - } - } - return new Tuple(bounds, x, y, width, height); - } - public void InitView2(Tuple tuple) - { - // 设置窗体位置、大小(实际宽高,单位unit) - _ScreenShotWin.Top = tuple.Item1.X; - _ScreenShotWin.Left = tuple.Item1.Y; - _ScreenShotWin.Width = tuple.Item1.Width; - _ScreenShotWin.Height = tuple.Item1.Height; - - // 设置遮罩 - Canvas.SetLeft(_ScreenShotWin, tuple.Item1.X); - Canvas.SetTop(_ScreenShotWin, tuple.Item1.Y); - (_ScreenShotWin.FindName("LeftMask") as System.Windows.Shapes.Rectangle).Width = tuple.Item1.Width; - (_ScreenShotWin.FindName("LeftMask") as System.Windows.Shapes.Rectangle).Height = tuple.Item1.Height; - - // 设置窗体背景(像素宽高,单位px) - bitmap = new Bitmap(tuple.Item4, tuple.Item5); - using (Graphics g = Graphics.FromImage(bitmap)) - { - g.CopyFromScreen(tuple.Item2, tuple.Item3, 0, 0, new System.Drawing.Size(tuple.Item4, tuple.Item5), CopyPixelOperation.SourceCopy); - } - _ScreenShotWin.Background = Util.BitmapToImageBrush(bitmap); - } - - public ICommand EscCmd { get; private set; } - public ICommand MouseMoveCmd { get; private set; } - public ICommand MouseLeftDownCmd { get; private set; } - public ICommand MouseLeftUpCmd { get; private set; } - public ICommand ClosedCmd { get; private set; } - - private Window _ScreenShotWin; //Window - private Rect Rectangle = new Rect(); //保存的矩形 - private System.Windows.Point StartPoint; //鼠标按下的点 - private bool MouseDown; //鼠标是否被按下 - private Bitmap bitmap; // 截屏图片 - private double dpiScale = 1; - - } -} diff --git a/STranslate/ViewModel/SettingsVM.cs b/STranslate/ViewModel/SettingsVM.cs deleted file mode 100644 index b3ae1317..00000000 --- a/STranslate/ViewModel/SettingsVM.cs +++ /dev/null @@ -1,147 +0,0 @@ -using STranslate.Helper; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Input; - -namespace STranslate.ViewModel -{ - public class SettingsVM : BaseVM - { - public SettingsVM() - { - IsStartup = ShortcutHelper.IsStartup(); - - Version = HandleVersion(System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString() ?? "1.0.0.0"); - - ClosedCmd = new RelayCommand((_) => true, (_) => - { - Util.FlushMemory(); - }); - - //更新 - UpdateCmd = new RelayCommand((_) => true, (_) => - { - try - { - var updaterExePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, - "Updater.exe"); - var updaterCacheExePath = Path.Combine( - AppDomain.CurrentDomain.BaseDirectory, - "Updater", - "Updater.exe"); - var updateDirPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Updater"); - if (!Directory.Exists(updateDirPath)) - { - Directory.CreateDirectory(updateDirPath); - } - - if (!File.Exists(updaterExePath)) - { - MessageBox.Show("升级程序似乎已被删除,请手动前往发布页查看新版本"); - return; - } - File.Copy(updaterExePath, updaterCacheExePath, true); - - File.Copy(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Newtonsoft.Json.dll"), Path.Combine( - AppDomain.CurrentDomain.BaseDirectory, - "Updater", - "Newtonsoft.Json.dll"), true); - - ProcessHelper.Run(updaterCacheExePath, new string[] { Version }); - } - catch (Exception ex) - { - - MessageBox.Show($"无法正确启动检查更新程序\n{ex.Message}"); - } - }); - - StartupCmd = new RelayCommand((_) => true, (_) => - { - if (ShortcutHelper.IsStartup()) ShortcutHelper.UnSetStartup(); - else ShortcutHelper.SetStartup(); - IsStartup = ShortcutHelper.IsStartup(); - }); - EscCmd = new RelayCommand((_) => true, (o) => - { - (o as Window)?.Close(); - }); - - OpenUrlCmd = new RelayCommand((_) => true, (o) => - { - try - { - System.Diagnostics.Process proc = new System.Diagnostics.Process(); - proc.StartInfo.FileName = o.ToString(); - proc.Start(); - } - catch (Exception ex) - { - MessageBox.Show($"未找到默认应用\n{ex.Message}"); - } - }); - } - - - /// - /// 同步Github版本命名 - /// - /// - /// - private static string HandleVersion(string version) - { - var ret = string.Empty; - ret = version.Substring(0, version.Length - 2); - var location = ret.LastIndexOf('.'); - ret = ret.Remove(location, 1); - return ret; - } - - - public ICommand ClosedCmd { get; private set; } - public ICommand OpenUrlCmd { get; private set; } - public ICommand UpdateCmd { get; private set; } - public ICommand StartupCmd { get; private set; } - public ICommand EscCmd { get; private set; } - - - private static Lazy _instance = new Lazy(() => new SettingsVM()); - public static SettingsVM Instance => _instance.Value; - - /// - /// 是否开机自启 - /// - private bool _isStartup; - public bool IsStartup { get => _isStartup; set => UpdateProperty(ref _isStartup, value); } - - /// - /// 版本 - /// - private string _version; - public string Version { get => _version; set => UpdateProperty(ref _version, value); } - - /// - /// 语种识别比例 - /// - private double _autoScale; - public double AutoScale { get => _autoScale; set => UpdateProperty(ref _autoScale, value); } - - /// - /// 取词间隔 - /// - private double _wordPickupInterval; - public double WordPickupInterval { get => _wordPickupInterval; set => UpdateProperty(ref _wordPickupInterval, value); } - - /// - /// 最大历史记录数量 - /// - private int _maxHistoryCount; - public int MaxHistoryCount { get => _maxHistoryCount; set => UpdateProperty(ref _maxHistoryCount, value); } - - } -} diff --git a/STranslate/ViewModels/Base/WindowVMBase.cs b/STranslate/ViewModels/Base/WindowVMBase.cs new file mode 100644 index 00000000..ffe77876 --- /dev/null +++ b/STranslate/ViewModels/Base/WindowVMBase.cs @@ -0,0 +1,42 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using STranslate.Model; +using System.Windows; + +namespace STranslate.ViewModels.Base +{ + public partial class WindowVMBase : ObservableObject + { + [RelayCommand] + private void Minimize(Window win) + { + win.WindowState = WindowState.Minimized; + } + + [RelayCommand] + private void Maximize(Window win) + { + win.WindowState = win.WindowState == WindowState.Normal ? WindowState.Maximized : WindowState.Normal; + } + + [RelayCommand] + public virtual void Close(Window win) + { + win.Close(); + } + + [RelayCommand] + private void WindowStateChange(Window win) + { + MaximizeContent = win.WindowState switch + { + WindowState.Normal => ConstStr.MAXIMIZECONTENT, + WindowState.Maximized => ConstStr.MAXIMIZEBACKCONTENT, + _ => MaximizeContent + }; + } + + [ObservableProperty] + private string _maximizeContent = ConstStr.MAXIMIZECONTENT; + } +} \ No newline at end of file diff --git a/STranslate/ViewModels/InputViewModel.cs b/STranslate/ViewModels/InputViewModel.cs new file mode 100644 index 00000000..1158398e --- /dev/null +++ b/STranslate/ViewModels/InputViewModel.cs @@ -0,0 +1,453 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Serialization; +using STranslate.Helper; +using STranslate.Log; +using STranslate.Model; +using STranslate.Util; +using STranslate.ViewModels.Preference; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; + +namespace STranslate.ViewModels +{ + public partial class InputViewModel : ObservableObject + { + #region 属性、字段 + + /// + /// 自动识别的语言 + /// + [ObservableProperty] + private string identifyLanguage = string.Empty; + + private static Dictionary LangDict + { + get => CommonUtil.GetEnumList(); + } + + /// + /// 输入内容 + /// + private string inputContent = string.Empty; + + public string InputContent + { + get => inputContent; + set + { + if (inputContent != value) + { + OnPropertyChanging(nameof(InputContent)); + inputContent = value; + OnPropertyChanged(nameof(InputContent)); + + //清空识别语种 + if (!string.IsNullOrEmpty(IdentifyLanguage)) + IdentifyLanguage = string.Empty; + + TranslateCommand.NotifyCanExecuteChanged(); + } + } + } + + private bool CanTranslate => !string.IsNullOrEmpty(InputContent); + + #endregion 属性、字段 + + #region 命令 + + #region Translatehandle + + [RelayCommand(CanExecute = nameof(CanTranslate), IncludeCancelCommand = true)] + private async Task TranslateAsync(object obj, CancellationToken token) + { + //翻译前清空旧数据 + Singleton.Instance.Clear(); + string source = Singleton.Instance.SelectedSourceLanguage ?? LanguageEnum.AUTO.GetDescription(); + string target = Singleton.Instance.SelectedTargetLanguage ?? LanguageEnum.AUTO.GetDescription(); + long size = Singleton.Instance.CurrentConfig?.HistorySize ?? 100; + string dbTarget = target; + HistoryModel? history = null; + + try + { + history = await TranslateServiceAsync(obj, source, dbTarget, target, size, token); + } + catch (Exception ex) + { + LogService.Logger.Error("[TranslateAsync]", ex); + } + finally + { + await HandleHistoryAsync(obj, history, source, dbTarget, size); + } + } + + /// + /// 插入数据库 + /// + /// + /// + /// + /// + /// + /// + private async Task HandleHistoryAsync(object obj, HistoryModel? history, string source, string dbTarget, long size) + { + if (history is null && size > 0) + { + var enableServices = Singleton.Instance.CurTransServiceList.Where(x => x.IsEnabled); + var jsonSerializerSettings = new JsonSerializerSettings { ContractResolver = new CustomizeContractResolver() }; + + var data = new HistoryModel + { + Time = DateTime.Now, + SourceLang = source, + TargetLang = dbTarget, + SourceText = InputContent, + Data = JsonConvert.SerializeObject(enableServices, Formatting.None, jsonSerializerSettings) + }; + var isForceWrite = obj != null; + //翻译结果插入数据库 + await SqlHelper.InsertDataAsync(data, size, isForceWrite); + } + } + + private async Task TranslateServiceAsync( + object obj, + string source, + string dbTarget, + string target, + long size, + CancellationToken token + ) + { + var services = Singleton.Instance.CurTransServiceList; + HistoryModel? history = null; + List? translatorList = null; + + //如果数据库限制大小为0则跳过检查数据库 + if (size == 0) + goto excute; + + //是否强制翻译 + bool isCheckCacheFirst = obj == null; + if (isCheckCacheFirst) + { + history = await SqlHelper.GetDataAsync(InputContent, source, dbTarget); + + if (history != null) + { + var settings = new JsonSerializerSettings { Converters = { new CurrentTranslatorConverter() } }; + + translatorList = JsonConvert.DeserializeObject>(history.Data, settings); + } + } + + excute: + await Parallel.ForEachAsync( + services, + token, + async (service, token) => + { + ///检查是否启用 + if (!service.IsEnabled) + return; + + try + { + Task response; + + if (translatorList != null) + { + IdentifyLanguage = "缓存"; + service.Data = translatorList?.FirstOrDefault(x => x.Identify == service.Identify)?.Data ?? "该服务未获取到缓存Ctrl+Enter更新"; + return; + } + + //如果是自动则获取自动识别后的目标语种 + if (target == LanguageEnum.AUTO.GetDescription()) + { + var autoRet = StringUtil.AutomaticLanguageRecognition(InputContent); + IdentifyLanguage = autoRet.Item1; + target = autoRet.Item2; + } + + //根据不同服务类型区分 + if (service.Type == ServiceType.ApiService) + { + response = + (Task) + await service.TranslateAsync( + new RequestApi() + { + Text = InputContent, + SourceLang = LangDict[source].ToString(), + TargetLang = LangDict[target].ToString() + }, + token + ); + service.Data = (response.Result as ResponseApi)!.Data; + } + else if (service.Type == ServiceType.CloudService) + { + Random rd = new Random(); + string salt = rd.Next(100000).ToString(); + string sign = StringUtil.EncryptString(service.AppID + InputContent + salt + service.AppKey); + response = + (Task) + await service.TranslateAsync( + new RequestBaidu() + { + Text = InputContent, + From = LangDict[source].ToString(), + TO = LangDict[target].ToString(), + AppId = service.AppID, + Salt = salt, + Sign = sign + }, + token + ); + var ret = (response.Result as ResponseBaidu)?.TransResult ?? []; + if (ret.Length != 0) + { + var nonEmptyDstValues = ret.Where(trans => !string.IsNullOrEmpty(trans.Dst)).Select(trans => trans.Dst); + + service.Data = string.Join(Environment.NewLine, nonEmptyDstValues); + } + else + { + service.Data = ""; + } + } + } + catch (TaskCanceledException ex) + { + HandleTranslationException(service, "请求取消", ex, token); + } + catch (HttpRequestException ex) + { + HandleTranslationException(service, "请求出错", ex, token); + } + catch (Exception ex) + { + HandleTranslationException(service, "翻译出错", ex, token); + } + } + ); + + return history; + } + + private void HandleTranslationException(ITranslator service, string errorMessage, Exception exception, CancellationToken token) + { + bool isDebug = false; + if (exception is TaskCanceledException) + { + errorMessage = token.IsCancellationRequested ? "请求取消..." : "请求超时..."; + isDebug = token.IsCancellationRequested; + } + else if (exception is HttpRequestException) + { + errorMessage = "请求出错..."; + } + + service.Data = errorMessage; + + if (isDebug) + LogService.Logger.Debug($"[{service.Name}({service.Identify})] {errorMessage}, 请求API: {service.Url}, 异常信息: {exception?.Message}"); + else + LogService.Logger.Error($"[{service.Name}({service.Identify})] {errorMessage}, 请求API: {service.Url}, 异常信息: {exception?.Message}"); + } + + #endregion Translatehandle + + public void Clear() + + { + InputContent = string.Empty; + } + + [RelayCommand] + private void CopyContent(string content) + { + if (!string.IsNullOrEmpty(content)) + { + Clipboard.SetDataObject(content); + ToastHelper.Show("复制成功"); + } + } + + [RelayCommand] + private void RemoveLineBreaks() + { + var tmp = InputContent; + InputContent = StringUtil.RemoveLineBreaks(InputContent); + if (string.Equals(tmp, InputContent)) + return; + + ToastHelper.Show("移除换行"); + if (!Singleton.Instance.CurrentConfig?.IsAdjustContentTranslate ?? false) + return; + + TranslateCancelCommand.Execute(null); + TranslateCommand.Execute(null); + } + + [RelayCommand] + private void RemoveSpace() + { + var tmp = InputContent; + InputContent = StringUtil.RemoveSpace(InputContent); + if (string.Equals(tmp, InputContent)) + return; + + ToastHelper.Show("移除空格"); + + if (!Singleton.Instance.CurrentConfig?.IsAdjustContentTranslate ?? false) + return; + + TranslateCancelCommand.Execute(null); + TranslateCommand.Execute(null); + } + + #region ContextMenu + + [RelayCommand] + private void TBSelectAll(object obj) + { + if (obj is TextBox tb) + { + tb.SelectAll(); + } + } + + [RelayCommand] + private void TBCopy(object obj) + { + if (obj is TextBox tb) + { + var text = tb.SelectedText; + if (!string.IsNullOrEmpty(text)) + Clipboard.SetDataObject(text); + } + } + + [RelayCommand] + private void TBPaste(object obj) + { + if (obj is TextBox tb) + { + var getText = Clipboard.GetText(); + + //剪贴板内容为空或者为非字符串则不处理 + if (string.IsNullOrEmpty(getText)) + return; + var index = tb.SelectionStart; + //处理选中字符串 + var selectLength = tb.SelectionLength; + //删除选中文本再粘贴 + var preHandleStr = tb.Text.Remove(index, selectLength); + + var newText = preHandleStr.Insert(index, getText); + tb.Text = newText; + + // 重新定位光标索引 + tb.SelectionStart = index + getText.Length; + + // 聚焦光标 + tb.Focus(); + } + } + + [RelayCommand] + private void TBClear(object obj) + { + if (obj is TextBox tb) + { + tb.Clear(); + } + } + + #endregion ContextMenu + + #endregion 命令 + } + + #region JsonConvert + + /// + /// 自定义属性构造器 + /// 1、可以通过构造方法,传入bool动态控制,主要用于外面有统一封装的时候 + /// 2、可以通过构造方法,传入需要显示的属性名称,然后基于list做linq过滤 + /// + public class CustomizeContractResolver : DefaultContractResolver + { + protected override IList CreateProperties(Type type, MemberSerialization memberSerialization) + { + var list = base.CreateProperties(type, memberSerialization); + + list! + .ToList() + ?.ForEach(x => + { + if (x.Ignored && x.PropertyName == "Data") + x.Ignored = false; //不忽略 + }); + return list!; + } + } + + /// + /// 获取当前翻译服务 + /// + public class CurrentTranslatorConverter : JsonConverter + { + public override ITranslator ReadJson( + JsonReader reader, + Type objectType, + ITranslator? existingValue, + bool hasExistingValue, + JsonSerializer serializer + ) + { + // 从 JSON 数据中加载一个 JObject + JObject jsonObject = JObject.Load(reader); + + // 获取当前可用的翻译服务列表 + var translators = Singleton.Instance.CurTransServiceList; + + // 从 JSON 中提取 Identify 字段的值,用于确定具体实现类 + var identify = jsonObject["Identify"]!.Value(); + ITranslator translator; + + // 根据 Identify 查找匹配的翻译服务 + translator = + translators.FirstOrDefault(x => x.Identify.ToString() == identify) + ?? throw new NotSupportedException($"Unsupported Service: {identify}"); + + // 从 JSON 中提取 Data 字段的值,设置到 translator 的 Data 属性中 + translator.Data = jsonObject["Data"]!.Value()!; + + // 返回构建好的 translator 对象 + return translator; + } + + public override void WriteJson(JsonWriter writer, ITranslator? value, JsonSerializer serializer) + { + // WriteJson 方法在此处未实现,因为当前转换器主要用于反序列化 + throw new NotImplementedException(); + } + } + + #endregion JsonConvert +} \ No newline at end of file diff --git a/STranslate/ViewModels/MainViewModel.cs b/STranslate/ViewModels/MainViewModel.cs new file mode 100644 index 00000000..2748564e --- /dev/null +++ b/STranslate/ViewModels/MainViewModel.cs @@ -0,0 +1,282 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using STranslate.Helper; +using STranslate.Log; +using STranslate.Model; +using STranslate.Style.Controls; +using STranslate.Util; +using STranslate.ViewModels.Preference; +using System; +using System.Collections.Generic; +using System.Windows; + +namespace STranslate.ViewModels +{ + public partial class MainViewModel : ObservableObject + { + public InputViewModel InputVM { get; } = Singleton.Instance; + public OutputViewModel OutputVM { get; set; } = Singleton.Instance; + public NotifyIconViewModel NotifyIconVM { get; } = Singleton.Instance; + + /// + /// 语言字典 + /// + private static Dictionary Languages => CommonUtil.GetEnumList(); + + [ObservableProperty] + private List? _sourceLanguageList = [.. Languages.Keys]; + + [ObservableProperty] + private List? _targetLanguageList = [.. Languages.Keys]; + + [ObservableProperty] + private string? _selectedSourceLanguage = LanguageEnum.AUTO.GetDescription(); + + [ObservableProperty] + private string? _selectedTargetLanguage = LanguageEnum.AUTO.GetDescription(); + + [ObservableProperty] + private string _isTopMost = ConstStr.TAGFALSE; + + [ObservableProperty] + private string _isEnableMosehook = ConstStr.TAGFALSE; + + [ObservableProperty] + private string _topMostContent = ConstStr.UNTOPMOSTCONTENT; + + public MainViewModel() + { + SqlHelper.InitializeDB(); + // 加载语言选择 + SelectedSourceLanguage = Singleton.Instance.CurrentConfig?.SourceLanguage ?? LanguageEnum.AUTO.GetDescription(); + SelectedTargetLanguage = Singleton.Instance.CurrentConfig?.TargetLanguage ?? LanguageEnum.AUTO.GetDescription(); + } + + [RelayCommand] + private void Loaded(Window view) + { + try + { + HotkeyHelper.Hotkeys = Singleton.Instance.CurrentConfig?.Hotkeys ?? + throw new Exception("快捷键配置出错,请检查配置后重启..."); + + NotifyIconVM.OnMousehook += MouseHook; + NotifyIconVM.OnForbiddenShortcuts += OnForbiddenShortcutsChanged; + Register(view); + } + catch (Exception ex) + { + LogService.Logger.Error($"[Hotkeys] {ex.Message}"); + } + } + + [RelayCommand] + private void Closing(Window view) + { + NotifyIconVM.OnMousehook -= MouseHook; + NotifyIconVM.OnForbiddenShortcuts -= OnForbiddenShortcutsChanged; + UnRegister(view); + } + + /// + /// 禁用/启用快捷键 + /// + /// + /// + private void OnForbiddenShortcutsChanged(Window view, bool forbidden) + { + if (forbidden) + UnRegister(view); + else + Register(view); + } + + private void Register(Window view) + { + try + { + HotkeyHelper.InitialHook(view); + HotkeyHelper.Register(HotkeyHelper.InputTranslateId, () => + { + NotifyIconVM.InputTranslateCommand.Execute(view); + }); + + HotkeyHelper.Register(HotkeyHelper.CrosswordTranslateId, () => + { + NotifyIconVM.CrossWordTranslateCommand.Execute(view); + }); + + HotkeyHelper.Register(HotkeyHelper.ScreenShotTranslateId, () => + { + NotifyIconVM.ScreenShotTranslateCommand.Execute(null); + }); + + HotkeyHelper.Register(HotkeyHelper.OpenMainWindowId, () => + { + NotifyIconVM.OpenMainWindowCommand.Execute(view); + }); + + HotkeyHelper.Register(HotkeyHelper.MousehookTranslateId, () => + { + NotifyIconVM.MousehookTranslateCommand.Execute(view); + }); + + HotkeyHelper.Register(HotkeyHelper.OCRId, () => + { + NotifyIconVM.OCRCommand.Execute(null); + }); + + if (HotkeyHelper.Hotkeys!.InputTranslate.Conflict + || HotkeyHelper.Hotkeys!.CrosswordTranslate.Conflict + || HotkeyHelper.Hotkeys!.ScreenShotTranslate.Conflict + || HotkeyHelper.Hotkeys!.OpenMainWindow.Conflict + || HotkeyHelper.Hotkeys!.MousehookTranslate.Conflict + || HotkeyHelper.Hotkeys!.OCR.Conflict) + { + MessageBox_S.Show("全局快捷键有冲突,请前往软件首选项中修改..."); + } + var msg = ""; + if (!HotkeyHelper.Hotkeys!.InputTranslate.Conflict) + msg += $"输入: {HotkeyHelper.Hotkeys.InputTranslate.Text}\n"; + if (!HotkeyHelper.Hotkeys!.CrosswordTranslate.Conflict) + msg += $"划词: {HotkeyHelper.Hotkeys.CrosswordTranslate.Text}\n"; + if (!HotkeyHelper.Hotkeys!.MousehookTranslate.Conflict) + msg += $"鼠标: {HotkeyHelper.Hotkeys.MousehookTranslate.Text}\n"; + if (!HotkeyHelper.Hotkeys!.ScreenShotTranslate.Conflict) + msg += $"截图: {HotkeyHelper.Hotkeys.ScreenShotTranslate.Text}\n"; + if (!HotkeyHelper.Hotkeys!.OpenMainWindow.Conflict) + msg += $"显示: {HotkeyHelper.Hotkeys.OpenMainWindow.Text}\n"; + if (!HotkeyHelper.Hotkeys!.OCR.Conflict) + msg += $"识字: {HotkeyHelper.Hotkeys.OCR.Text}\n"; + NotifyIconVM.UpdateToolTip(msg.TrimEnd('\n')); + } + catch (Exception) + { + throw; + } + } + + private void UnRegister(Window view) + { + HotkeyHelper.UnRegisterHotKey(view); + + NotifyIconVM.UpdateToolTip($"快捷键禁用"); + } + + [RelayCommand] + private void LangChanged() + { + //清空识别缓存 + InputVM.IdentifyLanguage = string.Empty; + } + + [RelayCommand] + private void ExchangeSourceTarget() + { + if (SelectedSourceLanguage != SelectedTargetLanguage && !string.IsNullOrEmpty(InputVM.InputContent)) + { + (SelectedSourceLanguage, SelectedTargetLanguage) = (SelectedTargetLanguage, SelectedSourceLanguage); + + InputVM.TranslateCancelCommand.Execute(null); + InputVM.TranslateCommand.Execute(null); + } + } + + private bool isMouseHook = false; + + [RelayCommand] + private void MouseHook(Window view) + { + isMouseHook = !isMouseHook; + IsEnableMosehook = isMouseHook ? ConstStr.TAGTRUE : ConstStr.TAGFALSE; + + if (isMouseHook) + { + view.Topmost = true; + IsTopMost = ConstStr.TAGTRUE; + TopMostContent = ConstStr.TOPMOSTCONTENT; + Singleton.Instance.MouseHookStart(); + Singleton.Instance.OnGetwordsHandler += OnGetwordsHandlerChanged; + ToastHelper.Show("启用鼠标划词"); + } + else + { + view.Topmost = false; + IsTopMost = ConstStr.TAGFALSE; + TopMostContent = ConstStr.UNTOPMOSTCONTENT; + Singleton.Instance.MouseHookStop(); + Singleton.Instance.OnGetwordsHandler -= OnGetwordsHandlerChanged; + ToastHelper.Show("关闭鼠标划词"); + } + } + + private void OnGetwordsHandlerChanged(string content) + { + if (string.IsNullOrEmpty(content)) + return; + InputVM.InputContent = content; + + //如果重复执行先取消上一步操作 + InputVM.TranslateCancelCommand.Execute(null); + + InputVM.TranslateCommand.Execute(null); + } + + /// + /// 点击置顶按钮 + /// + /// + [RelayCommand] + private void Sticky(Window win) + { + if (isMouseHook) + { + MessageBox_S.Show("当前监听鼠标划词中,请先解除监听..."); + return; + } + var tmp = !win.Topmost; + IsTopMost = tmp ? ConstStr.TAGTRUE : ConstStr.TAGFALSE; + TopMostContent = tmp ? ConstStr.TOPMOSTCONTENT : ConstStr.UNTOPMOSTCONTENT; + win.Topmost = tmp; + + if (tmp) + { + ToastHelper.Show("启用置顶"); + } + else + { + ToastHelper.Show("关闭置顶"); + } + } + + /// + /// 隐藏窗口 + /// + /// + [RelayCommand] + private void Esc(Window win) + { + if (isMouseHook) + { + MessageBox_S.Show("当前监听鼠标划词中,请先解除监听..."); + return; + } + + win.Topmost = false; + IsTopMost = ConstStr.TAGFALSE; + TopMostContent = ConstStr.UNTOPMOSTCONTENT; + win.Hide(); + InputVM.TranslateCancelCommand.Execute(null); + } + + /// + /// 切换主题 + /// + /// + [RelayCommand] + private void SwitchTheme() + { + Singleton.Instance.IsBright = !Singleton.Instance.IsBright; + } + } +} \ No newline at end of file diff --git a/STranslate/ViewModels/NotifyIconViewModel.cs b/STranslate/ViewModels/NotifyIconViewModel.cs new file mode 100644 index 00000000..4273cabc --- /dev/null +++ b/STranslate/ViewModels/NotifyIconViewModel.cs @@ -0,0 +1,346 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using STranslate.Helper; +using STranslate.Log; +using STranslate.Model; +using STranslate.Util; +using STranslate.Views; +using System; +using System.Linq; +using System.Threading; +using System.Windows; + +namespace STranslate.ViewModels +{ + public partial class NotifyIconViewModel : ObservableObject + { + public NotifyIconModel NIModel { get; } = new(); + + public Action? OnForbiddenShortcuts; + + public Action? OnMousehook; + + [ObservableProperty] + private bool _isForbiddenShortcuts = false; + + public NotifyIconViewModel() + { + UpdateToolTip(); + Microsoft.Win32.SystemEvents.DisplaySettingsChanged += DisplaySettingsChanged; + } + + public void UpdateToolTip(string msg = "") + { + NIModel.ToolTip = string.Format("STranslate {0} #\r\n{1}", Application.ResourceAssembly.GetName().Version!.ToString(), msg); + } + + [RelayCommand] + private void DoubleClick(Window view) + { + switch (Singleton.Instance.CurrentConfig?.DoubleTapTrayFunc ?? DoubleTapFuncEnum.InputFunc) + { + case DoubleTapFuncEnum.InputFunc: + InputTranslateCommand.Execute(view); + break; + + case DoubleTapFuncEnum.ScreenFunc: + ScreenShotTranslateCommand.Execute(null); + break; + + case DoubleTapFuncEnum.MouseHookFunc: + MousehookTranslateCommand.Execute(view); + break; + + case DoubleTapFuncEnum.OCRFunc: + OCRCommand.Execute(null); + break; + + case DoubleTapFuncEnum.ShowViewFunc: + OpenMainWindowCommand.Execute(view); + break; + + case DoubleTapFuncEnum.PreferenceFunc: + OpenPreferenceCommand.Execute(null); + break; + + case DoubleTapFuncEnum.ForbidShortcutFunc: + ForbiddenShortcutsCommand.Execute(view); + break; + + case DoubleTapFuncEnum.ExitFunc: + ExitCommand.Execute(null); + break; + + default: + break; + } + } + + [RelayCommand] + private void InputTranslate(Window view) + { + Clear(); + ShowAndActive(view); + } + + [RelayCommand] + private void CrossWordTranslate(Window view) + { + var content = GetWordsUtil.Get(); + if (string.IsNullOrWhiteSpace(content)) + return; + + //取词前移除换行 + if (Singleton.Instance.CurrentConfig?.IsRemoveLineBreakGettingWords ?? false) + content = StringUtil.RemoveLineBreaks(content); + + //如果重复执行先取消上一步操作 + Singleton.Instance.TranslateCancelCommand.Execute(null); + Clear(); + + Singleton.Instance.InputContent = content; + ShowAndActive(view); + + Singleton.Instance.TranslateCommand.Execute(null); + } + + [RelayCommand] + private void MousehookTranslate(Window view) + { + ShowAndActive(view); + OnMousehook?.Invoke(view); + } + + [RelayCommand] + private void QRCode() + { + System.Threading.Tasks.Task + .Delay(200) + .ContinueWith(_ => + { + CommonUtil.InvokeOnUIThread(() => + { + ScreenshotView view = new(); + ShowAndActive(view, false); + + view.BitmapCallback += ( + bitmap => + { + //显示OCR窗口 + OCRView? view = Application.Current.Windows.OfType().FirstOrDefault(); + view ??= new OCRView(); + ShowAndActive(view, false); + + //显示截图 + var bs = BitmapUtil.ConvertBitmap2BitmapSource(bitmap); + + Singleton.Instance.GetImg = bs; + + Singleton.Instance.QRCodeCommand.Execute(bs); + } + ); + }); + }); + } + + [RelayCommand] + private void OCR(object obj) + { + if (obj == null) + { + OCRHandler(); + return; + } + System.Threading.Tasks.Task + .Delay(200) + .ContinueWith(_ => + { + CommonUtil.InvokeOnUIThread(() => + { + OCRHandler(); + }); + }); + } + + private void OCRHandler() + { + ScreenshotView view = new(); + ShowAndActive(view, false); + + view.BitmapCallback += ( + bitmap => + { + //显示OCR窗口 + OCRView? view = Application.Current.Windows.OfType().FirstOrDefault(); + view ??= new OCRView(); + ShowAndActive(view, false); + + //显示截图 + var bs = BitmapUtil.ConvertBitmap2BitmapSource(bitmap); + + Singleton.Instance.GetImg = bs; + + Singleton.Instance.RecertificationCommand.Execute(bs); + } + ); + } + + [RelayCommand] + private void ScreenShotTranslate(object obj) + { + if (obj == null) + { + ScreenShotHandler(); + return; + } + System.Threading.Tasks.Task + .Delay(200) + .ContinueWith(_ => + { + CommonUtil.InvokeOnUIThread(() => + { + ScreenShotHandler(); + }); + }); + } + + private void ScreenShotHandler() + { + ScreenshotView view = new(); + ShowAndActive(view, false); + + view.BitmapCallback += ( + bitmap => + { + //var getText = TesseractHelper.TesseractOCR(bitmap, OcrType).Trim(); + + //如果重复执行先取消上一步操作 + Singleton.Instance.TranslateCancelCommand.Execute(null); + + Clear(); + + MainView view = Application.Current.Windows.OfType().FirstOrDefault()!; + ShowAndActive(view); + + var bytes = BitmapUtil.ConvertBitmap2Bytes(bitmap); + + Singleton.Instance.InputContent = "识别中..."; + Thread thread = new Thread(() => + { + string getText = ""; + try + { + getText = Singleton.Instance.Excute(bytes).Trim(); + + //取词前移除换行 + if ( + Singleton.Instance.CurrentConfig?.IsRemoveLineBreakGettingWords + ?? false && !string.IsNullOrEmpty(getText) + ) + getText = StringUtil.RemoveLineBreaks(getText); + + //OCR后自动复制 + if ( + Singleton.Instance.CurrentConfig?.IsOcrAutoCopyText ?? false && !string.IsNullOrEmpty(getText) + ) + Clipboard.SetDataObject(getText, true); + + CommonUtil.InvokeOnUIThread(() => + { + Singleton.Instance.InputContent = getText; + Singleton.Instance.TranslateCommand.Execute(null); + }); + } + catch (Exception ex) + { + CommonUtil.InvokeOnUIThread(() => Singleton.Instance.InputContent = ex.Message); + } + }); + thread.IsBackground = true; + thread.SetApartmentState(ApartmentState.STA); + thread.Start(); + } + ); + } + + [RelayCommand] + private void OpenMainWindow(Window view) + { + ShowAndActive(view); + } + + private void Clear() + { + //清空输入相关 + Singleton.Instance.Clear(); + //清空输出相关 + Singleton.Instance.Clear(); + } + + private void ShowAndActive(Window view, bool canFollowMouse = true) + { + if ((Singleton.Instance.CurrentConfig?.IsFollowMouse ?? false) && canFollowMouse) + { + Point mouseLocation = CommonUtil.GetMousePositionWindowsForms(); + view.Left = mouseLocation.X; + view.Top = mouseLocation.Y; + } + //显示主界面 + view.Show(); + view.Activate(); + } + + [RelayCommand] + private void OpenPreference() + { + PreferenceView? window = Application.Current.Windows.OfType().FirstOrDefault(); + window ??= new PreferenceView(); + + ShowAndActive(window, false); + } + + [RelayCommand] + private void ForbiddenShortcuts(Window view) + { + IsForbiddenShortcuts = !IsForbiddenShortcuts; + if (IsForbiddenShortcuts) + NIModel.IconSource = ConstStr.ICONFORBIDDEN; + else + NIModel.IconSource = ConstStr.ICON; + + OnForbiddenShortcuts?.Invoke(view, IsForbiddenShortcuts); + } + + private void SaveSelectedLang() + { + //写入配置 + var vm = Singleton.Instance; + var source = vm.SelectedSourceLanguage ?? "自动选择"; + var target = vm.SelectedTargetLanguage ?? "自动选择"; + if (!Singleton.Instance.WriteConfig(source, target)) + { + LogService.Logger.Debug($"保存源语言({source})、目标语言({target})配置失败..."); + } + } + + /// + /// 系统显示变化 + /// + /// + /// + private void DisplaySettingsChanged(object? sender, EventArgs e) + { + NIModel.IconSource = ConstStr.ICON; + } + + [RelayCommand] + private void Exit() + { + Microsoft.Win32.SystemEvents.DisplaySettingsChanged -= DisplaySettingsChanged; + + SaveSelectedLang(); + + Application.Current.Shutdown(); + } + } +} diff --git a/STranslate/ViewModels/OCRViewModel.cs b/STranslate/ViewModels/OCRViewModel.cs new file mode 100644 index 00000000..1e404874 --- /dev/null +++ b/STranslate/ViewModels/OCRViewModel.cs @@ -0,0 +1,413 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using Microsoft.Win32; +using STranslate.Helper; +using STranslate.Log; +using STranslate.Model; +using STranslate.Style.Controls; +using STranslate.Util; +using STranslate.ViewModels.Base; +using STranslate.Views; +using System; +using System.ComponentModel; +using System.IO; +using System.Linq; +using System.Reflection.Metadata; +using System.Threading; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media.Imaging; +using ZXing; + +namespace STranslate.ViewModels +{ + public partial class OCRViewModel : WindowVMBase + { + [ObservableProperty] + private BitmapSource? _getImg; + + [ObservableProperty] + private string _getContent = ""; + + [ObservableProperty] + private BindingList _ocrTypes = new(); + + [ObservableProperty] + private OCRType _ocrType = OCRType.Chinese; + + [ObservableProperty] + private string _isTopMost = ConstStr.TAGFALSE; + + [ObservableProperty] + private string _topMostContent = ConstStr.UNTOPMOSTCONTENT; + + /// + /// 点击置顶按钮 + /// + /// + [RelayCommand] + private void Sticky(Window win) + { + var tmp = !win.Topmost; + IsTopMost = tmp ? ConstStr.TAGTRUE : ConstStr.TAGFALSE; + TopMostContent = tmp ? ConstStr.TOPMOSTCONTENT : ConstStr.UNTOPMOSTCONTENT; + win.Topmost = tmp; + + if (tmp) + { + ToastHelper.Show("启用置顶", WindowType.OCR); + } + else + { + ToastHelper.Show("关闭置顶", WindowType.OCR); + } + } + + public override void Close(Window win) + { + win.Topmost = false; + IsTopMost = ConstStr.TAGFALSE; + TopMostContent = ConstStr.UNTOPMOSTCONTENT; + base.Close(win); + } + + [RelayCommand] + private void CopyImg(BitmapSource? source) + { + if (source is not null) + { + Clipboard.SetImage(source); + + ToastHelper.Show("复制图片", WindowType.OCR); + } + } + + [RelayCommand] + private void SaveImg(BitmapSource? source) + { + if (source is not null) + { + // 创建一个 SaveFileDialog + SaveFileDialog saveFileDialog = + new() + { + Title = "Save Image", + Filter = "PNG Files (*.png)|*.png|JPEG Files (*.jpg;*.jpeg)|*.jpg;*.jpeg|All Files (*.*)|*.*", + FileName = $"{DateTime.Now:yyyyMMddHHmmssfff}", + DefaultDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Desktop), + AddToRecent = true, + }; + // 打开 SaveFileDialog,并获取用户选择的文件路径 + if (saveFileDialog.ShowDialog() == true) + { + var fileName = saveFileDialog.FileName; + // 根据文件扩展名选择图像格式 + BitmapEncoder encoder; + if (fileName.EndsWith(".png", StringComparison.OrdinalIgnoreCase)) + { + encoder = new PngBitmapEncoder(); + } + else + { + encoder = new JpegBitmapEncoder(); + } + + // 将 BitmapSource 添加到 BitmapEncoder + encoder.Frames.Add(BitmapFrame.Create(source)); + + // 使用 FileStream 保存到文件 + using FileStream fs = new(fileName, FileMode.Create); + encoder.Save(fs); + + ToastHelper.Show("保存图片成功", WindowType.OCR); + } + else + { + ToastHelper.Show("取消保存图片", WindowType.OCR); + } + } + } + + [RelayCommand] + private void Copy(string? content) + { + if (!string.IsNullOrEmpty(content)) + { + Clipboard.SetDataObject(content); + + ToastHelper.Show("复制成功", WindowType.OCR); + } + } + + [RelayCommand] + private void RemoveLineBreaks() + { + var tmp = GetContent; + GetContent = StringUtil.RemoveLineBreaks(GetContent); + if (string.Equals(tmp, GetContent)) + return; + + ToastHelper.Show("移除换行", WindowType.OCR); + } + + [RelayCommand] + private void RemoveSpace() + { + var tmp = GetContent; + GetContent = StringUtil.RemoveSpace(GetContent); + if (string.Equals(tmp, GetContent)) + return; + + ToastHelper.Show("移除空格", WindowType.OCR); + } + + [RelayCommand] + private void Drop(DragEventArgs e) + { + if (e.Data.GetDataPresent(DataFormats.FileDrop)) + { + string[] files = (string[])e.Data.GetData(DataFormats.FileDrop); + + // 取第一个文件 + string filePath = files[0]; + + // 检查文件类型,确保是图片文件 + if (BitmapUtil.IsImageFile(filePath)) + { + ImgFileHandler(filePath); + } + else + { + //MessageBox_S.Show("请选择图片(*.png|*.jpg|*.jpeg|*.bmp)"); + ToastHelper.Show("请选择图片", WindowType.OCR); + } + } + } + + [RelayCommand] + private void Openfile() + { + var openfileDialog = new OpenFileDialog() + { + InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Desktop), + Filter = "Images|*.bmp;*.png;*.jpg;*.jpeg", + RestoreDirectory = true, + }; + if (openfileDialog.ShowDialog() == true) + { + ImgFileHandler(openfileDialog.FileName); + } + } + + [RelayCommand] + private void Screenshot(Window view) + { + if (IsTopMost == "False") + { + view.Hide(); + view.Close(); + Thread.Sleep(200); + } + Singleton.Instance.OCRCommand.Execute(null); + } + + [RelayCommand] + private void ClipboardImg() + { + var img = Clipboard.GetImage(); + if (img != null) + { + var bytes = BitmapUtil.ConvertBitmapSource2Bytes(img); + + //TODO: 很奇怪的现象,获取出来直接赋值给前台绑定的Img不显示,转成Byte再转回来就可以显示了 + GetImg = BitmapUtil.ConvertBytes2BitmapSource(bytes); + + OCRHandler(bytes); + + return; + } + + ToastHelper.Show("剪贴板最近无图片", WindowType.OCR); + } + + /// + /// 重新识别 + /// + [RelayCommand] + private void Recertification(BitmapSource bs) + { + var bytes = BitmapUtil.ConvertBitmapSource2Bytes(bs); + + OCRHandler(bytes); + } + + private void ImgFileHandler(string file) + { + using var fs = new FileStream(file, FileMode.Open, FileAccess.Read); + var bytes = new byte[fs.Length]; + fs.Read(bytes, 0, bytes.Length); + + GetImg = BitmapUtil.ConvertBytes2BitmapSource(bytes); + + OCRHandler(bytes); + } + + private void OCRHandler(byte[] bytes) + { + ToastHelper.Show("识别中...", WindowType.OCR); + string getText = ""; + + Thread thread = new Thread(() => + { + CommonUtil.InvokeOnUIThread(() => GetContent = "识别中..."); + try + { + getText = Singleton.Instance.Excute(bytes).Trim(); + + //取词前移除换行 + getText = + Singleton.Instance.CurrentConfig?.IsRemoveLineBreakGettingWords + ?? false && !string.IsNullOrEmpty(getText) + ? StringUtil.RemoveLineBreaks(getText) + : getText; + + //OCR后自动复制 + if (Singleton.Instance.CurrentConfig?.IsOcrAutoCopyText ?? false && !string.IsNullOrEmpty(getText)) + Clipboard.SetDataObject(getText, true); + } + catch (Exception ex) + { + getText = ex.Message; + LogService.Logger.Error("OCR失败", ex); + } + CommonUtil.InvokeOnUIThread(() => + { + GetContent = getText; + + ToastHelper.Show("识别成功", WindowType.OCR); + }); + }); + thread.IsBackground = true; + thread.SetApartmentState(ApartmentState.STA); + thread.Start(); + } + + /// + /// 识别二维码 + /// + [RelayCommand] + private void QRCode(BitmapSource bs) + { + var reader = new ZXing.ZKWeb.BarcodeReader(); + reader.Options.CharacterSet = "UTF-8"; + using var stream = new MemoryStream(); + var encoder = new BmpBitmapEncoder(); + encoder.Frames.Add(BitmapFrame.Create(bs)); + encoder.Save(stream); + var map = new System.DrawingCore.Bitmap(stream); + var readerResult = reader.Decode(map); + if (readerResult != null) + { + GetContent = readerResult.Text; + ToastHelper.Show("二维码识别成功", WindowType.OCR); + } + else + { + ToastHelper.Show("未识别到二维码", WindowType.OCR); + } + } + + /// + /// 翻译 + /// + [RelayCommand] + private void Translate(System.Collections.Generic.List obj) + { + string content = (obj.FirstOrDefault() as string) ?? ""; + Window? ocrView = (obj.LastOrDefault() as Window); + + //OCR结果翻译关闭界面 + if (Singleton.Instance.CurrentConfig?.CloseUIOcrRetTranslate ?? false) + ocrView?.Close(); + + //如果重复执行先取消上一步操作 + Singleton.Instance.TranslateCancelCommand.Execute(null); + //清空输入相关 + Singleton.Instance.Clear(); + //清空输出相关 + Singleton.Instance.Clear(); + + //获取主窗口 + MainView window = Application.Current.Windows.OfType().FirstOrDefault()!; + window.Show(); + window.Activate(); + + //获取文本 + Singleton + .Instance + .InputContent = content; + //执行翻译 + Singleton.Instance.TranslateCommand.Execute(null); + } + + #region ContextMenu + + [RelayCommand] + private void TBSelectAll(object obj) + { + if (obj is TextBox tb) + { + tb.SelectAll(); + } + } + + [RelayCommand] + private void TBCopy(object obj) + { + if (obj is TextBox tb) + { + var text = tb.SelectedText; + if (!string.IsNullOrEmpty(text)) + Clipboard.SetDataObject(text); + } + } + + [RelayCommand] + private void TBPaste(object obj) + { + if (obj is TextBox tb) + { + var getText = Clipboard.GetText(); + + //剪贴板内容为空或者为非字符串则不处理 + if (string.IsNullOrEmpty(getText)) + return; + var index = tb.SelectionStart; + //处理选中字符串 + var selectLength = tb.SelectionLength; + //删除选中文本再粘贴 + var preHandleStr = tb.Text.Remove(index, selectLength); + + var newText = preHandleStr.Insert(index, getText); + tb.Text = newText; + + // 重新定位光标索引 + tb.SelectionStart = index + getText.Length; + + // 聚焦光标 + tb.Focus(); + } + } + + [RelayCommand] + private void TBClear(object obj) + { + if (obj is TextBox tb) + { + tb.Clear(); + } + } + + #endregion ContextMenu + } +} diff --git a/STranslate/ViewModels/OutputViewModel.cs b/STranslate/ViewModels/OutputViewModel.cs new file mode 100644 index 00000000..c8730de9 --- /dev/null +++ b/STranslate/ViewModels/OutputViewModel.cs @@ -0,0 +1,73 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using STranslate.Helper; +using STranslate.Model; +using STranslate.Util; +using STranslate.ViewModels.Preference; +using System.ComponentModel; +using System.Linq; +using System.Windows; + +namespace STranslate.ViewModels +{ + public partial class OutputViewModel : ObservableObject + { + [ObservableProperty] + private BindingList _translators = Singleton.Instance.CurTransServiceList; + + [RelayCommand] + private void CopyResult(object obj) + { + if (obj is string str && !string.IsNullOrEmpty(str)) + { + Clipboard.SetDataObject(str); + + ToastHelper.Show("复制成功"); + } + } + + [RelayCommand] + private void CopySnakeResult(object obj) + { + if (obj is string str && !string.IsNullOrEmpty(str)) + { + var snakeRet = StringUtil.GenSnakeString(str.Split(' ').ToList()); + Clipboard.SetDataObject(snakeRet); + + ToastHelper.Show("蛇形复制成功"); + } + } + + [RelayCommand] + private void CopySmallHumpResult(object obj) + { + if (obj is string str && !string.IsNullOrEmpty(str)) + { + var snakeRet = StringUtil.GenHumpString(str.Split(' ').ToList(), true); + Clipboard.SetDataObject(snakeRet); + + ToastHelper.Show("小驼峰复制成功"); + } + } + + [RelayCommand] + private void CopyLargeHumpResult(object obj) + { + if (obj is string str && !string.IsNullOrEmpty(str)) + { + var snakeRet = StringUtil.GenHumpString(str.Split(' ').ToList(), false); + Clipboard.SetDataObject(snakeRet); + + ToastHelper.Show("大驼峰复制成功"); + } + } + + public void Clear() + { + foreach (var item in Translators) + { + item.Data = string.Empty; + } + } + } +} \ No newline at end of file diff --git a/STranslate/ViewModels/Preference/AboutViewModel.cs b/STranslate/ViewModels/Preference/AboutViewModel.cs new file mode 100644 index 00000000..85ba7c41 --- /dev/null +++ b/STranslate/ViewModels/Preference/AboutViewModel.cs @@ -0,0 +1,86 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using STranslate.Log; +using STranslate.Style.Controls; +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; + +namespace STranslate.ViewModels.Preference +{ + public partial class AboutViewModel : ObservableObject + { + [ObservableProperty] + private string version = ""; + + public AboutViewModel() + { + Version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? "1.0.0.0101"; + } + + [RelayCommand] + private void OpenLink(string url) => Process.Start(new ProcessStartInfo { FileName = url, UseShellExecute = true }); + + [RelayCommand] + private void CheckUpdate() + { + try + { + const string updateFolder = "Update"; + + string GetPath(string fileName) => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName); + + string GetCachePath(string fileName) => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, updateFolder, fileName); + + string[] requiredFiles = + { + "Updater.exe", + "Updater.dll", + "Newtonsoft.Json.dll", + "Updater.deps.json", + "Updater.runtimeconfig.json" + }; + + if (!Directory.Exists(GetPath(updateFolder))) + { + Directory.CreateDirectory(GetPath(updateFolder)); + } + + if (requiredFiles.All(file => File.Exists(GetPath(file)))) + { + foreach (var file in requiredFiles) + { + File.Copy(GetPath(file), GetCachePath(file), true); + } + + Util.CommonUtil.ExecuteProgram(GetCachePath("Updater.exe"), [Version]); + } + else + { + MessageBox_S.Show("升级程序似乎遭到破坏,请手动前往发布页查看新版本"); + LogService.Logger.Warn("升级程序似乎遭到破坏,请手动前往发布页查看新版本"); + } + } + catch (Exception ex) + { + MessageBox_S.Show($"更新程序已打开或无法正确启动检查更新程序"); + LogService.Logger.Warn($"更新程序已打开或无法正确启动检查更新程序, {ex.Message}"); + } + } + + /// + /// 同步Github版本命名 + /// + /// + /// + [Obsolete] + private static string HandleVersion(string version) + { + string? ret = version[..^2]; + var location = ret.LastIndexOf('.'); + ret = ret.Remove(location, 1); + return ret; + } + } +} \ No newline at end of file diff --git a/STranslate/ViewModels/Preference/CommonViewModel.cs b/STranslate/ViewModels/Preference/CommonViewModel.cs new file mode 100644 index 00000000..b4a1aac5 --- /dev/null +++ b/STranslate/ViewModels/Preference/CommonViewModel.cs @@ -0,0 +1,154 @@ +using System.Collections.Generic; +using System.Linq; +using System.Windows; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using Newtonsoft.Json; +using STranslate.Helper; +using STranslate.Log; +using STranslate.Model; +using STranslate.Util; + +namespace STranslate.ViewModels.Preference +{ + public partial class CommonViewModel : ObservableObject + { + [RelayCommand] + private void Save() + { + if (Singleton.Instance.WriteConfig(this)) + { + ToastHelper.Show("保存常规配置成功", WindowType.Preference); + + if (IsStartup) + { + if (!ShortcutUtil.IsStartup()) + ShortcutUtil.SetStartup(); + } + else + { + ShortcutUtil.UnSetStartup(); + } + } + else + { + LogService.Logger.Debug($"保存常规配置失败,{JsonConvert.SerializeObject(this)}"); + ToastHelper.Show("保存常规配置失败", WindowType.Preference); + } + } + + [RelayCommand] + private void Reset() + { + IsStartup = Singleton.Instance.CurrentConfig?.IsStartup ?? false; + NeedAdmin = Singleton.Instance.CurrentConfig?.NeedAdministrator ?? false; + HistorySize = Singleton.Instance.CurrentConfig?.HistorySize ?? 100; + AutoScale = Singleton.Instance.CurrentConfig?.AutoScale ?? 0.8; + IsBright = Singleton.Instance.CurrentConfig?.IsBright ?? false; + IsFollowMouse = Singleton.Instance.CurrentConfig?.IsFollowMouse ?? false; + CloseUIOcrRetTranslate = Singleton.Instance.CurrentConfig?.CloseUIOcrRetTranslate ?? false; + UnconventionalScreen = Singleton.Instance.CurrentConfig?.UnconventionalScreen ?? false; + IsOcrAutoCopyText = Singleton.Instance.CurrentConfig?.IsOcrAutoCopyText ?? false; + IsAdjustContentTranslate = Singleton.Instance.CurrentConfig?.IsAdjustContentTranslate ?? false; + IsRemoveLineBreakGettingWords = Singleton.Instance.CurrentConfig?.IsRemoveLineBreakGettingWords ?? false; + DoubleTapTrayFunc = Singleton.Instance.CurrentConfig?.DoubleTapTrayFunc ?? DoubleTapFuncEnum.InputFunc; + + ToastHelper.Show("重置配置", WindowType.Preference); + if (IsStartup) + { + if (!ShortcutUtil.IsStartup()) + ShortcutUtil.SetStartup(); + } + else + { + ShortcutUtil.UnSetStartup(); + } + } + + [ObservableProperty] + private bool isStartup = Singleton.Instance.CurrentConfig?.IsStartup ?? false; + + [ObservableProperty] + private bool needAdmin = Singleton.Instance.CurrentConfig?.NeedAdministrator ?? false; + + private long historySizeType = 1; + + public long HistorySizeType + { + get => historySizeType; + set + { + if (historySizeType != value) + { + OnPropertyChanging(nameof(HistorySizeType)); + historySizeType = value; + + HistorySize = value switch + { + 0 => 50, + 1 => 100, + 2 => 200, + 3 => 500, + 4 => 1000, + 5 => long.MaxValue, + _ => 0 + }; + + OnPropertyChanged(nameof(HistorySizeType)); + } + } + } + + public long HistorySize = Singleton.Instance.CurrentConfig?.HistorySize ?? 100; + + [ObservableProperty] + private double autoScale = Singleton.Instance.CurrentConfig?.AutoScale ?? 0.8; + + private bool isBright = Singleton.Instance.CurrentConfig?.IsBright ?? false; + + public bool IsBright + { + get => isBright; + set + { + if (isBright != value) + { + OnPropertyChanging(nameof(IsBright)); + isBright = value; + + // 切换主题 + Application.Current.Resources.MergedDictionaries.First().Source = value ? ConstStr.LIGHTURI : ConstStr.DARKURI; + + OnPropertyChanged(nameof(IsBright)); + } + } + } + + [ObservableProperty] + private bool isFollowMouse = Singleton.Instance.CurrentConfig?.IsFollowMouse ?? false; + + [ObservableProperty] + private bool closeUIOcrRetTranslate = Singleton.Instance.CurrentConfig?.CloseUIOcrRetTranslate ?? false; + + [ObservableProperty] + private bool unconventionalScreen = Singleton.Instance.CurrentConfig?.UnconventionalScreen ?? false; + + [ObservableProperty] + private bool isOcrAutoCopyText = Singleton.Instance.CurrentConfig?.IsOcrAutoCopyText ?? false; + + [ObservableProperty] + private bool isAdjustContentTranslate = Singleton.Instance.CurrentConfig?.IsAdjustContentTranslate ?? false; + + [ObservableProperty] + private bool isRemoveLineBreakGettingWords = Singleton.Instance.CurrentConfig?.IsRemoveLineBreakGettingWords ?? false; + + public Dictionary FuncDict + { + get => CommonUtil.GetEnumList(); + } + + [ObservableProperty] + private DoubleTapFuncEnum doubleTapTrayFunc = + Singleton.Instance.CurrentConfig?.DoubleTapTrayFunc ?? DoubleTapFuncEnum.InputFunc; + } +} diff --git a/STranslate/ViewModels/Preference/FavoriteViewModel.cs b/STranslate/ViewModels/Preference/FavoriteViewModel.cs new file mode 100644 index 00000000..10cbc46c --- /dev/null +++ b/STranslate/ViewModels/Preference/FavoriteViewModel.cs @@ -0,0 +1,13 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace STranslate.ViewModels.Preference +{ + public partial class FavoriteViewModel : ObservableObject + { + } +} diff --git a/STranslate/ViewModels/Preference/History/HistoryContentViewModel.cs b/STranslate/ViewModels/Preference/History/HistoryContentViewModel.cs new file mode 100644 index 00000000..37d9f555 --- /dev/null +++ b/STranslate/ViewModels/Preference/History/HistoryContentViewModel.cs @@ -0,0 +1,108 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using STranslate.Helper; +using STranslate.Model; +using STranslate.Util; +using STranslate.ViewModels.Preference.Services; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Windows; + +namespace STranslate.ViewModels.Preference.History +{ + public partial class HistoryContentViewModel : ObservableObject + { + public HistoryContentViewModel(HistoryModel? history) + { + if (history == null) + { + return; + } + + var settings = new JsonSerializerSettings { Converters = { new HistoryTranslatorConverter() } }; + + var outputs = JsonConvert.DeserializeObject>(history.Data, settings); + + InputContent = history.SourceText; + Time = history.Time; + SourceLang = history.SourceLang; + TargetLang = history.TargetLang; + + outputContents = outputs?.Select(x => new Tuple(x.Name, x.Icon, x.Data?.ToString() ?? ""))?.ToList(); + } + + [RelayCommand] + private void Delete() + { + Singleton.Instance.DeleteHistoryCommand.Execute(null); + } + + [RelayCommand] + private void CopyResult(object obj) + { + if (obj is string str && !string.IsNullOrEmpty(str)) + { + Clipboard.SetDataObject(str); + } + } + + [ObservableProperty] + private string inputContent = ""; + + [ObservableProperty] + private DateTime time; + + [ObservableProperty] + private string sourceLang = ""; + + [ObservableProperty] + private string targetLang = ""; + + [ObservableProperty] + private List>? outputContents; + } + + public class HistoryTranslatorConverter : JsonConverter + { + public override ITranslator ReadJson( + JsonReader reader, + Type objectType, + ITranslator? existingValue, + bool hasExistingValue, + JsonSerializer serializer + ) + { + // 从 JSON 数据中加载一个 JObject + JObject jsonObject = JObject.Load(reader); + + // 根据Type字段的值来决定具体实现类 + var type = jsonObject["Type"]!.Value(); + ITranslator translator; + + translator = type switch + { + (int)ServiceType.ApiService => new TranslatorApi(), + (int)ServiceType.CloudService => new TranslatorBaidu(), + //TODO: 更多其他服务在这里添加 + _ => throw new NotSupportedException($"Unsupported ServiceType: {type}") + }; + + serializer.Populate(jsonObject.CreateReader(), translator); + + // 从 JSON 中提取 Data 字段的值,设置到 translator 的 Data 属性中 + translator.Data = jsonObject["Data"]!.Value()!; + + // 返回构建好的 translator 对象 + return translator; + } + + public override void WriteJson(JsonWriter writer, ITranslator? value, JsonSerializer serializer) + { + // WriteJson 方法在此处未实现,因为当前转换器主要用于反序列化 + throw new NotImplementedException(); + } + } +} diff --git a/STranslate/ViewModels/Preference/HistoryViewModel.cs b/STranslate/ViewModels/Preference/HistoryViewModel.cs new file mode 100644 index 00000000..4dfa1c81 --- /dev/null +++ b/STranslate/ViewModels/Preference/HistoryViewModel.cs @@ -0,0 +1,166 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using STranslate.Helper; +using STranslate.Log; +using STranslate.Model; +using STranslate.ViewModels.Preference.History; +using STranslate.Views.Preference.History; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; + +namespace STranslate.ViewModels.Preference +{ + public partial class HistoryViewModel : ObservableObject + { + public HistoryViewModel() + { + RefreshCommand.Execute(null); + } + + [RelayCommand] + private async Task RefreshAsync(ScrollViewer? scroll) + { + var historyModels = await SqlHelper.GetDataAsync(); + HistoryList = new BindingList(historyModels.Reverse().ToList()); + + Count = HistoryList.Count; + + if (Count > 0) + { + SelectedIndex = 0; + HistoryDetailContent = new HistoryContentPage(new HistoryContentViewModel(HistoryList.FirstOrDefault())); + } + else + { + HistoryDetailContent = null; + } + + scroll?.ScrollToTop(); + + ToastHelper.Show("刷新历史记录", WindowType.Preference); + } + + [RelayCommand] + private void Popup(Popup control) => control.IsOpen = true; + + /// + /// 删除某条记录 + /// + [RelayCommand] + private async Task DeleteHistoryAsync() + { + if (HistoryList == null || HistoryList.Count < 1) + { + return; + } + + var tmpIndex = SelectedIndex; + var history = HistoryList.ElementAtOrDefault(SelectedIndex); + if (history == null || !await SqlHelper.DeleteDataAsync(history)) + { + LogService.Logger.Warn($"删除失败,{history}"); + + ToastHelper.Show("删除失败", WindowType.Preference); + return; + } + HistoryList.RemoveAt(SelectedIndex); + SelectedIndex = tmpIndex < HistoryList.Count ? tmpIndex : tmpIndex - 1; + Count--; + + UpdateHistoryDetailContent(); + + ToastHelper.Show("删除成功", WindowType.Preference); + } + + /// + /// 删除所有记录 + /// + [RelayCommand] + private async Task DeleteAllHistoryAsync(Popup control) + { + if (await SqlHelper.DeleteAllDataAsync()) + { + HistoryList?.Clear(); + Count = 0; + SelectedIndex = -1; + HistoryDetailContent = null; + + ToastHelper.Show("删除全部成功", WindowType.Preference); + } + + control.IsOpen = false; + } + + /// + /// 更新历史详情UI + /// + private void UpdateHistoryDetailContent() + { + if (SelectedIndex != -1) + { + HistoryDetailContent = new HistoryContentPage(new HistoryContentViewModel(HistoryList![SelectedIndex])); + } + else + { + HistoryDetailContent = null; + } + } + + /// + /// 导航页面 + /// + /// + [RelayCommand] + private void TogglePage(HistoryModel? model) + { + if (model == null) + return; + + // 防止重入 + if (!_isSelectionChanging) + { + _isSelectionChanging = true; + + try + { + var method = typeof(HistoryContentPage).GetMethod("UpdateVM"); + method?.Invoke(HistoryDetailContent, new[] { new HistoryContentViewModel(model) }); + } + catch (Exception ex) + { + LogService.Logger.Error("历史记录导航出错", ex); + } + + _isSelectionChanging = false; + } + } + + /// + /// SelectedChanged 可能会先触发 "SelectionChanged" 事件 + /// 然后再触发 "LostFocus" 事件,导致 Command 被调用两次 + /// + private bool _isSelectionChanging = false; + + + [ObservableProperty] + private List eventList = ["清空全部"]; + + [ObservableProperty] + private int _selectedIndex; + + [ObservableProperty] + private int _count = 0; + + [ObservableProperty] + private UIElement? _historyDetailContent; + + [ObservableProperty] + private BindingList? _historyList; + } +} diff --git a/STranslate/ViewModels/Preference/HotkeyViewModel.cs b/STranslate/ViewModels/Preference/HotkeyViewModel.cs new file mode 100644 index 00000000..31174101 --- /dev/null +++ b/STranslate/ViewModels/Preference/HotkeyViewModel.cs @@ -0,0 +1,17 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using STranslate.Model; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Controls; +using System.Windows.Input; + +namespace STranslate.ViewModels.Preference +{ + public partial class HotkeyViewModel : ObservableObject + { + } +} diff --git a/STranslate/ViewModels/Preference/ServiceViewModel.cs b/STranslate/ViewModels/Preference/ServiceViewModel.cs new file mode 100644 index 00000000..c289c21f --- /dev/null +++ b/STranslate/ViewModels/Preference/ServiceViewModel.cs @@ -0,0 +1,218 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using STranslate.Log; +using STranslate.Model; +using STranslate.ViewModels.Preference.Services; +using STranslate.Util; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using STranslate.Style.Controls; +using Newtonsoft.Json; +using STranslate.Helper; + +namespace STranslate.ViewModels.Preference +{ + public partial class ServiceViewModel : ObservableObject + { + public ServiceViewModel() + { + TransServices.Add(new TranslatorApi()); + TransServices.Add(new TranslatorBaidu()); + + ResetView(); + } + + /// + /// 重置选中项 + /// + /// + private void ResetView(ActionType type = ActionType.Initialize) + { + ServiceCounter = CurTransServiceList.Count; + + if (ServiceCounter < 1) + return; + + switch (type) + { + case ActionType.Delete: + { + //不允许小于0 + SelectedIndex = Math.Max(tmpIndex - 1, 0); + TogglePageCommand.Execute(CurTransServiceList[SelectedIndex]); + break; + } + case ActionType.Add: + { + //选中最后一项 + SelectedIndex = ServiceCounter - 1; + TogglePageCommand.Execute(CurTransServiceList[SelectedIndex]); + break; + } + default: + { + //初始化默认执行选中第一条 + SelectedIndex = 0; + TogglePageCommand.Execute(CurTransServiceList.First()); + break; + } + } + + + } + + private int tmpIndex; + + [RelayCommand] + private void TogglePage(ITranslator service) + { + if (service != null) + { + if (SelectedIndex != -1) + tmpIndex = SelectedIndex; + + string head = "STranslate.Views.Preference.Service."; + var name = service.Type switch + { + ServiceType.ApiService => string.Format("{0}TextApiServicePage", head), + ServiceType.CloudService => string.Format("{0}TextCloudServicesPage", head), + _ => string.Format("{0}TextApiServicePage", head) + }; + + NavigationPage(name, service); + } + } + + [RelayCommand] + private void Popup(Popup control) + { + control.IsOpen = true; + } + + [RelayCommand] + private void Add(List list) + { + if (list?.Count == 2) + { + var service = list.First(); + if (service is TranslatorApi ta) + { + CurTransServiceList.Add(ta.DeepClone()); + } + else if (service is TranslatorBaidu tb) + { + CurTransServiceList.Add(tb.DeepClone()); + } + + (list.Last() as Popup)!.IsOpen = false; + + ResetView(ActionType.Add); + } + } + + private bool CanDelete => ServiceCounter > 1; + + [RelayCommand(CanExecute = nameof(CanDelete))] + private void Delete(ITranslator service) + { + if (service != null) + { + CurTransServiceList.Remove(service); + + ResetView(ActionType.Delete); + + ToastHelper.Show("删除成功", WindowType.Preference); + } + } + + [RelayCommand] + private void Save() + { + if (!Singleton.Instance.WriteConfig(CurTransServiceList)) + { + LogService.Logger.Debug($"保存服务失败,{JsonConvert.SerializeObject(CurTransServiceList)}"); + + ToastHelper.Show("保存失败", WindowType.Preference); + } + ToastHelper.Show("保存成功", WindowType.Preference); + } + + [RelayCommand] + private void Reset() + { + CurTransServiceList = Singleton.Instance.ResetConfig.Services ?? []; + ResetView(ActionType.Initialize); + ToastHelper.Show("重置配置", WindowType.Preference); + } + + /// + /// 导航 UI 缓存 + /// + private readonly Dictionary ContentCache = []; + + /// + /// 导航页面 + /// + /// + /// + public void NavigationPage(string name, ITranslator translator) + { + UIElement? content = null; + + try + { + if (string.IsNullOrWhiteSpace(name)) + throw new ArgumentException("param name is null or empty", nameof(name)); + + if (translator == null) + throw new ArgumentNullException(nameof(translator)); + + Type? type = Type.GetType(name) ?? throw new Exception($"{nameof(NavigationPage)} get {name} exception"); + + //读取缓存是否存在,存在则从缓存中获取View实例并通过UpdateVM刷新ViewModel + if (ContentCache.ContainsKey(type)) + { + content = ContentCache[type]; + if (content is UserControl uc) + { + var method = type.GetMethod("UpdateVM"); + method?.Invoke(uc, new[] { translator }); + } + } + else//不存在则创建并通过构造函数传递ViewModel + { + content = (UIElement?)Activator.CreateInstance(type, translator); + ContentCache.Add(type, content); + } + + ServiceContent = content; + } + catch (Exception ex) + { + LogService.Logger.Error("服务导航出错", ex); + } + } + + [ObservableProperty] + private int _selectedIndex = 0; + + [ObservableProperty] + private UIElement? _serviceContent; + + [ObservableProperty] + private BindingList _transServices = new(); + + [ObservableProperty] + [NotifyCanExecuteChangedFor(nameof(DeleteCommand))] + private int _serviceCounter; + + [ObservableProperty] + private BindingList _curTransServiceList = + Singleton.Instance.CurrentConfig?.Services ?? new BindingList(); + } +} diff --git a/STranslate/ViewModels/Preference/Services/TranslatorApi.cs b/STranslate/ViewModels/Preference/Services/TranslatorApi.cs new file mode 100644 index 00000000..70dbf306 --- /dev/null +++ b/STranslate/ViewModels/Preference/Services/TranslatorApi.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using CommunityToolkit.Mvvm.ComponentModel; +using Newtonsoft.Json; +using STranslate.Model; +using STranslate.Util; + +namespace STranslate.ViewModels.Preference.Services +{ + public partial class TranslatorApi : ObservableObject, ITranslator + { + public TranslatorApi() + : this(Guid.NewGuid(), "https://deeplx.deno.dev/translate", "DeepL") { } + + public TranslatorApi( + Guid guid, + string url, + string name = "", + IconType icon = IconType.DeepL, + string appID = "", + string appKey = "", + bool isEnabled = true, + ServiceType type = ServiceType.ApiService, + RequestMode mode = RequestMode.POST + ) + { + Identify = guid; + Url = url; + Name = name; + Icon = icon; + AppID = appID; + AppKey = appKey; + IsEnabled = isEnabled; + Type = type; + HttpMode = mode; + } + + [ObservableProperty] + private Guid _identify = Guid.Empty; + + [JsonIgnore] + [ObservableProperty] + private ServiceType _type = 0; + + [JsonIgnore] + [ObservableProperty] + public bool _isEnabled = true; + + [JsonIgnore] + [ObservableProperty] + private string _name = string.Empty; + + [JsonIgnore] + [ObservableProperty] + private IconType _icon = IconType.DeepL; + + [JsonIgnore] + [ObservableProperty] + public string _url = string.Empty; + + [JsonIgnore] + [ObservableProperty] + public RequestMode _httpMode = RequestMode.POST; + + [JsonIgnore] + [ObservableProperty] + public string _appID = string.Empty; + + [JsonIgnore] + [ObservableProperty] + public string _appKey = string.Empty; + + [JsonIgnore] + public object _data = string.Empty; + + [JsonIgnore] + public object Data + { + get => _data; + set + { + if (_data != value) + { + OnPropertyChanging(nameof(Data)); + _data = value; + OnPropertyChanged(nameof(Data)); + } + } + } + + [JsonIgnore] + public List Icons { get; private set; } = Enum.GetValues(typeof(IconType)).OfType().ToList(); + + public async Task TranslateAsync(object request, CancellationToken token) + { + if (request is RequestApi) + { + var req = JsonConvert.SerializeObject(request); + + string resp = await HttpUtil.PostAsync(Url, req, token); + if (string.IsNullOrEmpty(resp)) + throw new Exception($"请求结果为空"); + + var ret = JsonConvert.DeserializeObject(resp ?? ""); + + if (ret is null || string.IsNullOrEmpty(ret.Data.ToString())) + { + ret = new ResponseApi { Data = resp! }; + } + + return Task.FromResult(ret); + } + return Task.FromResult("请求数据出错"); + } + } +} diff --git a/STranslate/ViewModels/Preference/Services/TranslatorBaidu.cs b/STranslate/ViewModels/Preference/Services/TranslatorBaidu.cs new file mode 100644 index 00000000..ae24f698 --- /dev/null +++ b/STranslate/ViewModels/Preference/Services/TranslatorBaidu.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using CommunityToolkit.Mvvm.ComponentModel; +using Newtonsoft.Json; +using STranslate.Model; +using STranslate.Util; + +namespace STranslate.ViewModels.Preference.Services +{ + public partial class TranslatorBaidu : ObservableObject, ITranslator + { + public TranslatorBaidu() + : this(Guid.NewGuid(), "https://fanyi-api.baidu.com/api/trans/vip/translate", "Baidu") { } + + public TranslatorBaidu( + Guid guid, + string url, + string name = "", + IconType icon = IconType.Baidu, + string appID = "", + string appKey = "", + bool isEnabled = true, + ServiceType type = ServiceType.CloudService, + RequestMode mode = RequestMode.GET + ) + { + Identify = guid; + Url = url; + Name = name; + Icon = icon; + AppID = appID; + AppKey = appKey; + IsEnabled = isEnabled; + Type = type; + HttpMode = mode; + } + + [ObservableProperty] + private Guid _identify = Guid.Empty; + + [JsonIgnore] + [ObservableProperty] + private ServiceType _type = 0; + + [JsonIgnore] + [ObservableProperty] + public bool _isEnabled = true; + + [JsonIgnore] + [ObservableProperty] + private string _name = string.Empty; + + [JsonIgnore] + [ObservableProperty] + private IconType _icon = IconType.Baidu; + + [JsonIgnore] + [ObservableProperty] + public string _url = string.Empty; + + [JsonIgnore] + [ObservableProperty] + public RequestMode _httpMode = RequestMode.POST; + + [JsonIgnore] + [ObservableProperty] + public string _appID = string.Empty; + + [JsonIgnore] + [ObservableProperty] + public string _appKey = string.Empty; + + [JsonIgnore] + public object _data = string.Empty; + + [JsonIgnore] + public object Data + { + get => _data; + set + { + if (_data != value) + { + OnPropertyChanging(nameof(Data)); + _data = value; + OnPropertyChanged(nameof(Data)); + } + } + } + + [JsonIgnore] + public List Icons { get; private set; } = Enum.GetValues(typeof(IconType)).OfType().ToList(); + + public async Task TranslateAsync(object request, CancellationToken token) + { + if (request is RequestBaidu rb) + { + var req = new Dictionary + { + { "q", rb.Text }, + { "from", rb.From.ToLower() }, + { "to", rb.TO.ToLower() }, + { "appid", rb.AppId }, + { "salt", rb.Salt }, + { "sign", rb.Sign } + }; + + string resp = await HttpUtil.GetAsync(Url, req, token); + if (string.IsNullOrEmpty(resp)) + throw new Exception("请求结果为空"); + + var ret = JsonConvert.DeserializeObject(resp ?? ""); + + if (ret is null || string.IsNullOrEmpty(ret.TransResult?.FirstOrDefault()?.Dst)) + { + ret = new ResponseBaidu { TransResult = [new TransResult { Dst = resp! }] }; + } + return Task.FromResult(ret); + } + return Task.FromResult(new ResponseBaidu { TransResult = [new TransResult { Dst = "请求数据出错..." }] }); + } + } +} diff --git a/STranslate/ViewModels/PreferenceViewModel.cs b/STranslate/ViewModels/PreferenceViewModel.cs new file mode 100644 index 00000000..5ca3d4aa --- /dev/null +++ b/STranslate/ViewModels/PreferenceViewModel.cs @@ -0,0 +1,38 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using STranslate.Util; +using STranslate.ViewModels.Base; +using STranslate.ViewModels.Preference; + +namespace STranslate.ViewModels +{ + public partial class PreferenceViewModel : WindowVMBase + { + public PreferenceViewModel() + { + // 初始化Page + CommonPage(); + } + + [RelayCommand] + private void CommonPage() => CurrentView = Singleton.Instance; + + [RelayCommand] + private void HotkeyPage() => CurrentView = Singleton.Instance; + + [RelayCommand] + private void ServicePage() => CurrentView = Singleton.Instance; + + [RelayCommand] + private void FavoritePage() => CurrentView = Singleton.Instance; + + [RelayCommand] + private void HistoryPage() => CurrentView = Singleton.Instance; + + [RelayCommand] + private void AboutPage() => CurrentView = Singleton.Instance; + + [ObservableProperty] + private object? _currentView; + } +} \ No newline at end of file diff --git a/STranslate/Views/HeaderView.xaml b/STranslate/Views/HeaderView.xaml new file mode 100644 index 00000000..37fe6e10 --- /dev/null +++ b/STranslate/Views/HeaderView.xaml @@ -0,0 +1,43 @@ + + + + + + + + + \ No newline at end of file diff --git a/STranslate/Views/OCRView.xaml.cs b/STranslate/Views/OCRView.xaml.cs new file mode 100644 index 00000000..6fcfdeff --- /dev/null +++ b/STranslate/Views/OCRView.xaml.cs @@ -0,0 +1,141 @@ +using STranslate.Util; +using STranslate.ViewModels; +using System; +using System.Runtime.InteropServices; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Interop; + +namespace STranslate.Views +{ + /// + /// OCRView.xaml 的交互逻辑 + /// + public partial class OCRView : Window + { + [DllImport("user32.dll")] + public static extern bool ReleaseCapture(); + + [DllImport("user32.dll")] + public static extern bool SendMessage(IntPtr hwnd, int wMsg, int wParam, int lParam); + + [DllImport("user32.dll", SetLastError = true)] + public static extern int SetParent(IntPtr hWndChild, IntPtr hWndNewParent); + + [DllImport("user32.dll", SetLastError = true)] + public static extern bool MoveWindow(IntPtr hwnd, int x, int y, int cx, int cy, bool repaint); + + //Windows消息: https://www.cnblogs.com/cncc/articles/8004771.html + /// + /// 执行系统命令,如移动、最小化、最大化 + /// + private const int WM_SYSCOMMAND = 0x0112; + + /// + /// 移动窗口的系统命令 + /// + private const int SC_MOVE = 0xF010; + + /// + /// 一个窗口被销毁 + /// + private const int WM_DESTROY = 0x0002; + + /// + /// 空消息 + /// + private const int WM_NULL = 0x0000; + + public OCRView() + { + InitializeComponent(); + + DataContext = Singleton.Instance; +#if false + Topmost = true; +#endif + } + + /// + /// 左键按住拖动 + /// + /// + /// + private void Header_MouseDown(object sender, MouseButtonEventArgs e) + { + WindowInteropHelper wndHelper = new WindowInteropHelper(this); + ReleaseCapture(); + SendMessage(wndHelper.Handle, WM_SYSCOMMAND, SC_MOVE + WM_DESTROY, WM_NULL); + } + + /// + /// 双击最大/恢复 + /// + /// + /// + private void Header_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) + { + if (e.ClickCount == 2) + { + if (this.WindowState == WindowState.Maximized) + this.WindowState = WindowState.Normal; + else + this.WindowState = WindowState.Maximized; + } + } + + private void InputTB_PreviewMouseWheel(object sender, MouseWheelEventArgs e) + { + TextBox textBox = (TextBox)sender; + + // 检查是否按住 Ctrl 键,如果按住则进行缩放等特殊操作,否则进行滚动 + if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)) + { + double currentFontSize = (double)Application.Current.Resources["FontSize_TextBox"]; + + // 根据滚轮事件更改字体大小 + if (e.Delta > 0) + { + if (currentFontSize < 40) + { + currentFontSize += 2.0; // 根据需要调整放大的幅度 + } + } + else + { + if (currentFontSize > 10) + { + currentFontSize -= 2.0; // 根据需要调整放大的幅度 + } + } + + // 设置新的字体大小 + Application.Current.Resources["FontSize_TextBox"] = currentFontSize; + } + else//修复普通滚动 + { + // 普通滚动 + if (e.Delta > 0) + { + // 向上滚动 + if (textBox.VerticalOffset > 0) + { + textBox.ScrollToVerticalOffset(textBox.VerticalOffset - 30); + } + } + else + { + // 向下滚动 + if (textBox.VerticalOffset < textBox.ExtentHeight - textBox.ViewportHeight) + { + textBox.ScrollToVerticalOffset(textBox.VerticalOffset + 30); + } + } + } + + // 防止事件继续传播 + e.Handled = true; + } + } +} \ No newline at end of file diff --git a/STranslate/Views/OutputView.xaml b/STranslate/Views/OutputView.xaml new file mode 100644 index 00000000..973c1fb3 --- /dev/null +++ b/STranslate/Views/OutputView.xaml @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + +