Compare commits
477 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
6efc473859 | ||
![]() |
e1ec86f298 | ||
![]() |
29ee5dda90 | ||
![]() |
b8cd2dff54 | ||
![]() |
fb3f32f050 | ||
![]() |
41f1f56fe5 | ||
![]() |
5be5def820 | ||
![]() |
12997389ff | ||
![]() |
ab15cc8c79 | ||
![]() |
6cb9199a6c | ||
![]() |
57b1cf8865 | ||
![]() |
d328026356 | ||
![]() |
8d4bcb8605 | ||
![]() |
5a2c52e4c5 | ||
![]() |
8c918e14a4 | ||
![]() |
936aaa90ff | ||
![]() |
efa9fab49f | ||
![]() |
cad1e83d44 | ||
![]() |
7be070744b | ||
![]() |
cb203b912e | ||
![]() |
5f53aaec23 | ||
![]() |
b71c67dd1b | ||
![]() |
5df1f52a68 | ||
![]() |
59d7f1d4f5 | ||
![]() |
bf5d4efc6b | ||
![]() |
6cb853d804 | ||
![]() |
ac24d909d1 | ||
![]() |
9b6657318b | ||
![]() |
ccfbf843dc | ||
![]() |
00e3f90430 | ||
![]() |
60f115acd1 | ||
![]() |
1f6fff0050 | ||
![]() |
030c99d0fe | ||
![]() |
a4a3f44806 | ||
![]() |
ce9c47c3ea | ||
![]() |
07b520d10b | ||
![]() |
260a10bea3 | ||
![]() |
4f195f5629 | ||
![]() |
7d84bd2f7b | ||
![]() |
a4e09d88c6 | ||
![]() |
ad39d98858 | ||
![]() |
2369015824 | ||
![]() |
156cd7c926 | ||
![]() |
ff041facd8 | ||
![]() |
5581f385ae | ||
![]() |
958b86044e | ||
![]() |
032a14380f | ||
![]() |
c03a3d2fc2 | ||
![]() |
d5eb9f1855 | ||
![]() |
7a8e8e510c | ||
![]() |
45a36f3222 | ||
![]() |
155c3577c3 | ||
![]() |
0e72af3770 | ||
![]() |
fdfa43c093 | ||
![]() |
af15301bf1 | ||
![]() |
26c034e75a | ||
![]() |
75bfd798c8 | ||
![]() |
59a10af57e | ||
![]() |
2ce8e0edc3 | ||
![]() |
1fa62d8370 | ||
![]() |
70f75ae63d | ||
![]() |
39d1d6e237 | ||
![]() |
3f71c10964 | ||
![]() |
e4db31de10 | ||
![]() |
d5dfa5786b | ||
![]() |
1a153ff5c3 | ||
![]() |
e8f4f01d9c | ||
![]() |
718bb7314b | ||
![]() |
3f2dcbbeb0 | ||
![]() |
413cae80c4 | ||
![]() |
9706ab1c12 | ||
![]() |
d9bff84acd | ||
![]() |
c715ed136a | ||
![]() |
5b591fa476 | ||
![]() |
efee1088f7 | ||
![]() |
3aa76f6074 | ||
![]() |
bf3c3ad893 | ||
![]() |
30894d7689 | ||
![]() |
65b61dec56 | ||
![]() |
8273b245ef | ||
![]() |
49983a772a | ||
![]() |
a76ee0f25a | ||
![]() |
abdf15aaaf | ||
![]() |
75a04beffa | ||
![]() |
d46c72fafb | ||
![]() |
6936914976 | ||
![]() |
eba1b84184 | ||
![]() |
99fbdf7d90 | ||
![]() |
53d9470ccd | ||
![]() |
9c383badc9 | ||
![]() |
64869f6128 | ||
![]() |
74e2be5b8d | ||
![]() |
aba0a8b316 | ||
![]() |
ac1c598953 | ||
![]() |
0920ed1f40 | ||
![]() |
bd99d04416 | ||
![]() |
6c62b5506e | ||
![]() |
d8db4684fa | ||
![]() |
b67935ef57 | ||
![]() |
63876a3215 | ||
![]() |
698c9e4e70 | ||
![]() |
092c5e65ca | ||
![]() |
19f814261c | ||
![]() |
94776f06b3 | ||
![]() |
614e1e5a02 | ||
![]() |
bbc635e500 | ||
![]() |
644278890b | ||
![]() |
a84013cb5c | ||
![]() |
82be88bdf7 | ||
![]() |
30ac4433f3 | ||
![]() |
d7953024ff | ||
![]() |
0a8ec0086b | ||
![]() |
ed81d9c406 | ||
![]() |
56e502c3c9 | ||
![]() |
79b3092411 | ||
![]() |
36700db5d1 | ||
![]() |
3a4ee296af | ||
![]() |
b3bf0b6b69 | ||
![]() |
ddbb180a0f | ||
![]() |
4fb3af6cbf | ||
![]() |
c5b57d1d7a | ||
![]() |
5c7e0260a9 | ||
![]() |
1d185c9402 | ||
![]() |
283624a564 | ||
![]() |
d5480e6f0a | ||
![]() |
f71e886ccc | ||
![]() |
51356f2624 | ||
![]() |
55c97694fd | ||
![]() |
5ee59598d3 | ||
![]() |
65d17394c5 | ||
![]() |
aa801fecb4 | ||
![]() |
1497538a9a | ||
![]() |
b1655775bd | ||
![]() |
63a008d067 | ||
![]() |
1ce2f86b3d | ||
![]() |
409ad6d3c2 | ||
![]() |
5afd8e7e7f | ||
![]() |
e1ec939862 | ||
![]() |
96c678bf33 | ||
![]() |
16904f2531 | ||
![]() |
05b1c57d7d | ||
![]() |
94afaf9497 | ||
![]() |
16ffc3307b | ||
![]() |
b73cbde790 | ||
![]() |
5556d7aae2 | ||
![]() |
3e1ab494d9 | ||
![]() |
6c70b0f21a | ||
![]() |
3d50c17b81 | ||
![]() |
74d4a6eca4 | ||
![]() |
1764cf80e4 | ||
![]() |
38b9086cac | ||
![]() |
9de8db88a9 | ||
![]() |
5d4ad56b1e | ||
![]() |
a1c4d8bbd9 | ||
![]() |
c26d9a0924 | ||
![]() |
7b1d7b9eeb | ||
![]() |
9b2009ed6c | ||
![]() |
351847a3ef | ||
![]() |
68efe4c8cb | ||
![]() |
a27f97f2cb | ||
![]() |
1a44e17739 | ||
![]() |
351ac398b4 | ||
![]() |
f51521f8e2 | ||
![]() |
461f1f433d | ||
![]() |
055ba92667 | ||
![]() |
250feb9bc5 | ||
![]() |
0b0b0cb09c | ||
![]() |
962234418b | ||
![]() |
c63abf4809 | ||
![]() |
04de7388b1 | ||
![]() |
2f796d33c9 | ||
![]() |
858649e57b | ||
![]() |
633b9c6561 | ||
![]() |
dce3c798a2 | ||
![]() |
da51f79708 | ||
![]() |
bcf83acf4e | ||
![]() |
48a01a3992 | ||
![]() |
23cdc3e57c | ||
![]() |
3c39faaaad | ||
![]() |
8957f2407d | ||
![]() |
7227fc9844 | ||
![]() |
7c87371fe5 | ||
![]() |
8bd89db060 | ||
![]() |
a865381b65 | ||
![]() |
0c3893ddf8 | ||
![]() |
7bc5030ed5 | ||
![]() |
5be05c46d8 | ||
![]() |
eff7552d07 | ||
![]() |
1ba4f6be4d | ||
![]() |
b2e0694dfa | ||
![]() |
69819dee7f | ||
![]() |
576d9d5662 | ||
![]() |
d931015d6b | ||
![]() |
5397521c79 | ||
![]() |
b9ff1eaca3 | ||
![]() |
ae8801d837 | ||
![]() |
071eadc088 | ||
![]() |
56bacbfc7e | ||
![]() |
ee899c1b23 | ||
![]() |
0ef82187d6 | ||
![]() |
7e73804348 | ||
![]() |
b2f5cc93f2 | ||
![]() |
77a1f4a00b | ||
![]() |
8d2e726715 | ||
![]() |
5f4ef955e1 | ||
![]() |
35cbdb17e8 | ||
![]() |
0df0f2ef20 | ||
![]() |
bbe7d0a729 | ||
![]() |
013370e51e | ||
![]() |
58f6283fe3 | ||
![]() |
ca99f570f7 | ||
![]() |
45e12265c1 | ||
![]() |
8aa10754e2 | ||
![]() |
9144ea262d | ||
![]() |
040d0ff540 | ||
![]() |
a85dafa18d | ||
![]() |
6f7270f229 | ||
![]() |
2f1779ee9b | ||
![]() |
4dd2d2ddb6 | ||
![]() |
9ea8974108 | ||
![]() |
ff19f87b44 | ||
![]() |
e36fbbf3b0 | ||
![]() |
15d4bcb36e | ||
![]() |
0738a88eb7 | ||
![]() |
aac286df9d | ||
![]() |
43597f05f2 | ||
![]() |
e70f5c21a5 | ||
![]() |
ac1267c88d | ||
![]() |
1ff3fb1f1b | ||
![]() |
c9b2677bec | ||
![]() |
9e72e67ee7 | ||
![]() |
258073c785 | ||
![]() |
87987f4a71 | ||
![]() |
c2a1dab1c7 | ||
![]() |
4ca17dca30 | ||
![]() |
2da5363448 | ||
![]() |
d55ccd82b9 | ||
![]() |
a92a5a1cc6 | ||
![]() |
9d9f082b57 | ||
![]() |
7ed6742f51 | ||
![]() |
897db5b6c4 | ||
![]() |
d760ea3f13 | ||
![]() |
872b047d70 | ||
![]() |
4b5fb132f7 | ||
![]() |
e1c2defd71 | ||
![]() |
cc815f4245 | ||
![]() |
aee380fcd5 | ||
![]() |
6336d3545a | ||
![]() |
295353332f | ||
![]() |
10f5e5e387 | ||
![]() |
12b8d332c8 | ||
![]() |
ce83cfff54 | ||
![]() |
4edb231c4e | ||
![]() |
2874177bc4 | ||
![]() |
178c954124 | ||
![]() |
668c8f1183 | ||
![]() |
71b74bb286 | ||
![]() |
d7cb1e3f56 | ||
![]() |
1757b2b89f | ||
![]() |
705bae636d | ||
![]() |
0da289e16f | ||
![]() |
2bf5515b0b | ||
![]() |
04abe4b0d4 | ||
![]() |
0fcdff9be2 | ||
![]() |
1f478bc48a | ||
![]() |
69f7d10c3a | ||
![]() |
124d32857f | ||
![]() |
8b84fef929 | ||
![]() |
b81bdc69eb | ||
![]() |
cc00f0a54d | ||
![]() |
fa75537487 | ||
![]() |
1582bdf294 | ||
![]() |
71bb96fe2f | ||
![]() |
148bf249f8 | ||
![]() |
ced4fdd834 | ||
![]() |
afe2cc1a2c | ||
![]() |
3847c8d61b | ||
![]() |
493262876f | ||
![]() |
83232bfc76 | ||
![]() |
032895830c | ||
![]() |
d7b671a73d | ||
![]() |
bff2598285 | ||
![]() |
40824e60de | ||
![]() |
c5889e1374 | ||
![]() |
bfe84af681 | ||
![]() |
9da8c516b6 | ||
![]() |
835714d76d | ||
![]() |
e7fe13c5f0 | ||
![]() |
cc4b0573d8 | ||
![]() |
deb1bee2c7 | ||
![]() |
4005f94766 | ||
![]() |
d90fd735e6 | ||
![]() |
4316d1607a | ||
![]() |
a4df962ca8 | ||
![]() |
d945081dab | ||
![]() |
ab23b6ba6c | ||
![]() |
8a65cddc6f | ||
![]() |
a197edd62b | ||
![]() |
6d820ad229 | ||
![]() |
7cba01ef20 | ||
![]() |
cdce150db5 | ||
![]() |
546f4782d1 | ||
![]() |
aee289eaef | ||
![]() |
86d5ee3aa6 | ||
![]() |
fd45105dc7 | ||
![]() |
4d6f5857c0 | ||
![]() |
ad107834eb | ||
![]() |
fbbfdb9921 | ||
![]() |
d3b2b1f328 | ||
![]() |
0b6511434a | ||
![]() |
8804be6436 | ||
![]() |
cc93067587 | ||
![]() |
8b4f6742c6 | ||
![]() |
630a4e8ec9 | ||
![]() |
863fe2fe92 | ||
![]() |
a371b5bed4 | ||
![]() |
40f37f648d | ||
![]() |
a2a24f6648 | ||
![]() |
8387662e83 | ||
![]() |
f2b7ac1821 | ||
![]() |
81f370f5c7 | ||
![]() |
4c526f65df | ||
![]() |
623a4b6086 | ||
![]() |
5a980b4365 | ||
![]() |
9356f4cb1f | ||
![]() |
7a2d512a81 | ||
![]() |
d198c88625 | ||
![]() |
12cb3bf4ab | ||
![]() |
f15fb29ac1 | ||
![]() |
2ae8b4e4a7 | ||
![]() |
eb796471cf | ||
![]() |
393cec503d | ||
![]() |
fa89460466 | ||
![]() |
d44c8ef060 | ||
![]() |
0b3396e1f4 | ||
![]() |
1e3411bd58 | ||
![]() |
49c5659334 | ||
![]() |
a69213c956 | ||
![]() |
4109968fed | ||
![]() |
bab4d633db | ||
![]() |
6a315c75c5 | ||
![]() |
778fe16481 | ||
![]() |
1db0e85e2d | ||
![]() |
013f697461 | ||
![]() |
98c73947d4 | ||
![]() |
dcbcb070a8 | ||
![]() |
f641197552 | ||
![]() |
1841539543 | ||
![]() |
b21cd123bb | ||
![]() |
670fc64d9a | ||
![]() |
4499e94c44 | ||
![]() |
fe16cbe669 | ||
![]() |
ce8567afca | ||
![]() |
b022dd18f4 | ||
![]() |
1d609339f6 | ||
![]() |
399824afb6 | ||
![]() |
9a5738c4fd | ||
![]() |
1bc25c174e | ||
![]() |
9ecf4c27fb | ||
![]() |
5800c23989 | ||
![]() |
280175e9c2 | ||
![]() |
e6a3a5d158 | ||
![]() |
407ed8a8a2 | ||
![]() |
a6be52ea0d | ||
![]() |
a489912731 | ||
![]() |
c6dc304627 | ||
![]() |
b11f3e9368 | ||
![]() |
e5a25b1bc3 | ||
![]() |
f3732c96e9 | ||
![]() |
0bab1d6f60 | ||
![]() |
7a424953cc | ||
![]() |
c382cdf8cd | ||
![]() |
180dc2c35e | ||
![]() |
34081855cd | ||
![]() |
8dbc32a07f | ||
![]() |
a84e2fa72a | ||
![]() |
d1f930ff57 | ||
![]() |
ab1af32fa8 | ||
![]() |
a4f82b3d75 | ||
![]() |
fd1a97d119 | ||
![]() |
382db2f810 | ||
![]() |
41d028951f | ||
![]() |
e0177de97e | ||
![]() |
eca5d1ed39 | ||
![]() |
8c15289f8a | ||
![]() |
777bb9ff25 | ||
![]() |
aa26e6f9de | ||
![]() |
8c6e6854e1 | ||
![]() |
02e4624514 | ||
![]() |
a0a1e4afd4 | ||
![]() |
6ce69c9db1 | ||
![]() |
9ce964945c | ||
![]() |
83ed896b93 | ||
![]() |
4f8958b9c6 | ||
![]() |
4a14a0af7f | ||
![]() |
810564f75f | ||
![]() |
f9b47872d7 | ||
![]() |
baa0717884 | ||
![]() |
e00b244050 | ||
![]() |
45d7f76890 | ||
![]() |
7db8cd2d19 | ||
![]() |
692ccc133b | ||
![]() |
b0c745b61e | ||
![]() |
af91756ca3 | ||
![]() |
ff7ed3de66 | ||
![]() |
c53dcdae6d | ||
![]() |
25d6ccd81a | ||
![]() |
01b5d1c919 | ||
![]() |
9892a9f29b | ||
![]() |
37f2293156 | ||
![]() |
e18f9b2431 | ||
![]() |
d90c104fd2 | ||
![]() |
c79f40cf6d | ||
![]() |
e5e9950cfc | ||
![]() |
a4d3dd90fd | ||
![]() |
1d211d1baf | ||
![]() |
027c7b46a0 | ||
![]() |
a57e0b1af8 | ||
![]() |
c1dab33e7c | ||
![]() |
1c922db961 | ||
![]() |
a6c813e07a | ||
![]() |
45978ec013 | ||
![]() |
811b232c81 | ||
![]() |
b6461fa3a9 | ||
![]() |
0fd694787a | ||
![]() |
fd78dde837 | ||
![]() |
a7977a1f0e | ||
![]() |
350fa4cd72 | ||
![]() |
9083a7ca6c | ||
![]() |
2a1ee13b46 | ||
![]() |
468a6fd039 | ||
![]() |
90cce0a370 | ||
![]() |
a7ffec8827 | ||
![]() |
3a564e85a8 | ||
![]() |
6bacb14c56 | ||
![]() |
67a4c8e0ea | ||
![]() |
186395f692 | ||
![]() |
56a7469260 | ||
![]() |
e38449b928 | ||
![]() |
01d969968e | ||
![]() |
f3c4f1c5b5 | ||
![]() |
381df69650 | ||
![]() |
77757ff3c6 | ||
![]() |
3e499b86c5 | ||
![]() |
f33c0431b9 | ||
![]() |
6358629269 | ||
![]() |
54f809707c | ||
![]() |
283b596ef1 | ||
![]() |
1f5d6c73bd | ||
![]() |
5b166cba4c | ||
![]() |
fb1d25f914 | ||
![]() |
4c51e264b5 | ||
![]() |
3109444805 | ||
![]() |
9e85af0eaf | ||
![]() |
dd3d92edb6 | ||
![]() |
c380d96815 | ||
![]() |
6d57198471 | ||
![]() |
bf6eea7f8e | ||
![]() |
097405038b | ||
![]() |
0fa1a6a1fc | ||
![]() |
4887441aeb | ||
![]() |
aa705b0ef5 | ||
![]() |
bd6b417cd8 | ||
![]() |
256e812401 | ||
![]() |
d46eb9c5bd | ||
![]() |
5c7220707e | ||
![]() |
26ddd92584 | ||
![]() |
670fda2ac5 | ||
![]() |
ced4b1c5c8 | ||
![]() |
bf2d19b6f0 | ||
![]() |
9007379242 | ||
![]() |
f97082d1ed | ||
![]() |
9eb07a41a0 | ||
![]() |
39e7bdbd41 | ||
![]() |
1bbfd1b11d | ||
![]() |
b5246c9694 | ||
![]() |
2b15c64ebf |
16
.clang-format
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
BasedOnStyle: Google
|
||||||
|
AccessModifierOffset: '-2'
|
||||||
|
AllowShortCaseLabelsOnASingleLine: 'true'
|
||||||
|
AllowShortFunctionsOnASingleLine: InlineOnly
|
||||||
|
AllowShortIfStatementsOnASingleLine: 'false'
|
||||||
|
BreakBeforeBraces: Linux
|
||||||
|
BreakConstructorInitializers: BeforeComma
|
||||||
|
ConstructorInitializerAllOnOneLineOrOnePerLine: 'false'
|
||||||
|
ConstructorInitializerIndentWidth: '2'
|
||||||
|
Standard: Cpp11
|
||||||
|
TabWidth: '2'
|
||||||
|
UseTab: Never
|
||||||
|
IncludeBlocks: Preserve
|
||||||
|
|
||||||
|
...
|
37
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help us improve
|
||||||
|
title: ''
|
||||||
|
labels: 'bug'
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Describe the bug**
|
||||||
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
**To Reproduce**
|
||||||
|
Steps to reproduce the behavior:
|
||||||
|
1. Go to '...'
|
||||||
|
2. Click on '....'
|
||||||
|
3. Scroll down to '....'
|
||||||
|
4. See error
|
||||||
|
|
||||||
|
**Expected behavior**
|
||||||
|
A clear and concise description of what you expected to happen.
|
||||||
|
|
||||||
|
**Logs**
|
||||||
|
Application log file recorded during an error.
|
||||||
|
To start recording a log file, open the settings window,
|
||||||
|
enable the 'write trace file' option and apply the settings.
|
||||||
|
|
||||||
|
**Screenshots**
|
||||||
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
|
||||||
|
**Desktop (please complete the following information):**
|
||||||
|
- OS: [e.g. windows 10, ubuntu 18.04]
|
||||||
|
- Desktop environment (linux only): [e.g. KDE, Gnome]
|
||||||
|
- App version (release archive name): [e.g. 3.0.0-win32, 3.0.0-compatible-win64]
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context about the problem here.
|
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
title: ''
|
||||||
|
labels: 'enhancement'
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Is your feature request related to a problem? Please describe.**
|
||||||
|
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||||
|
|
||||||
|
**Describe the solution you'd like**
|
||||||
|
A clear and concise description of what you want to happen.
|
||||||
|
|
||||||
|
**Describe alternatives you've considered**
|
||||||
|
A clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context or screenshots about the feature request here.
|
100
.github/workflows/build.yml
vendored
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
name: App build
|
||||||
|
|
||||||
|
on: [push]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
name: Create release
|
||||||
|
if: contains(github.ref, '/tags/')
|
||||||
|
runs-on: ubuntu-18.04
|
||||||
|
steps:
|
||||||
|
- name: Create release
|
||||||
|
id: create_release
|
||||||
|
uses: actions/create-release@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
tag_name: ${{ github.ref }}
|
||||||
|
release_name: ${{ github.ref }}
|
||||||
|
draft: false
|
||||||
|
prerelease: false
|
||||||
|
|
||||||
|
- name: Store release url
|
||||||
|
run: echo "${{ steps.create_release.outputs.upload_url }}" > ./release_upload_url
|
||||||
|
|
||||||
|
- name: Upload release url
|
||||||
|
uses: actions/upload-artifact@v1
|
||||||
|
with:
|
||||||
|
path: ./release_upload_url
|
||||||
|
name: release_upload_url
|
||||||
|
|
||||||
|
build:
|
||||||
|
name: Build ${{ matrix.config.name }}${{ matrix.config.tag }}
|
||||||
|
runs-on: ${{ matrix.config.os }}
|
||||||
|
env:
|
||||||
|
OS: ${{ matrix.config.name }}
|
||||||
|
MSVC_VERSION: C:/Program Files/Microsoft Visual Studio/2022/Enterprise
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
config:
|
||||||
|
- { name: "win64", os: windows-latest }
|
||||||
|
- { name: "win32", os: windows-latest }
|
||||||
|
- { name: "linux", os: ubuntu-18.04 }
|
||||||
|
# - { name: "macos", os: macos-latest }
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
fetch-depth: 10
|
||||||
|
|
||||||
|
- name: Setup python
|
||||||
|
uses: actions/setup-python@v1
|
||||||
|
with:
|
||||||
|
python-version: "3.x"
|
||||||
|
|
||||||
|
- name: Install system libs
|
||||||
|
if: runner.os == 'Linux'
|
||||||
|
run: |
|
||||||
|
sudo apt-get install libgl1-mesa-dev libxkbcommon-x11-0 libxcb-*
|
||||||
|
echo "QMAKE_FLAGS=QMAKE_CXX=g++-10 QMAKE_CC=gcc-10 QMAKE_LINK=g++-10" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Cache dependencies
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: deps
|
||||||
|
key: ${{ env.OS }}-${{ hashFiles('./share/ci/*.py') }}
|
||||||
|
|
||||||
|
- name: Make a release
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
python ./share/ci/release.py
|
||||||
|
echo "artifact=`python ./share/ci/release.py artifact_name`" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Upload build artifact
|
||||||
|
if: env.artifact != ''
|
||||||
|
uses: actions/upload-artifact@v1
|
||||||
|
with:
|
||||||
|
name: ${{ env.artifact }}
|
||||||
|
path: ./${{ env.artifact }}
|
||||||
|
|
||||||
|
- name: Download release url
|
||||||
|
if: contains(github.ref, '/tags/')
|
||||||
|
uses: actions/download-artifact@v4.1.7
|
||||||
|
with:
|
||||||
|
name: release_upload_url
|
||||||
|
path: ./
|
||||||
|
|
||||||
|
- name: Set release env
|
||||||
|
if: contains(github.ref, '/tags/')
|
||||||
|
shell: bash
|
||||||
|
run: echo "upload_url=`cat ./release_upload_url`" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Upload release artifacts
|
||||||
|
if: contains(github.ref, '/tags/')
|
||||||
|
uses: actions/upload-release-asset@v1.0.1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ env.upload_url }}
|
||||||
|
asset_path: ./${{ env.artifact }}
|
||||||
|
asset_name: ${{ env.artifact }}
|
||||||
|
asset_content_type: application/zip
|
3
.gitignore
vendored
@ -6,3 +6,6 @@
|
|||||||
|
|
||||||
*.exe
|
*.exe
|
||||||
distr/content/
|
distr/content/
|
||||||
|
|
||||||
|
*.tar.gz
|
||||||
|
__pycache__
|
||||||
|
@ -1,276 +0,0 @@
|
|||||||
#include "GlobalActionHelper.h"
|
|
||||||
|
|
||||||
#include <qt_windows.h>
|
|
||||||
|
|
||||||
#include <QDebug>
|
|
||||||
#include <QApplication>
|
|
||||||
|
|
||||||
QHash<QPair<quint32, quint32>, QAction*> GlobalActionHelper::actions_;
|
|
||||||
|
|
||||||
bool GlobalActionHelper::nativeEventFilter(const QByteArray& eventType,
|
|
||||||
void* message, long* result)
|
|
||||||
{
|
|
||||||
Q_UNUSED (eventType);
|
|
||||||
Q_UNUSED (result);
|
|
||||||
MSG* msg = static_cast<MSG*>(message);
|
|
||||||
if (msg->message == WM_HOTKEY)
|
|
||||||
{
|
|
||||||
const quint32 keycode = HIWORD(msg->lParam);
|
|
||||||
const quint32 modifiers = LOWORD(msg->lParam);
|
|
||||||
QAction* action = actions_.value (qMakePair (keycode, modifiers));
|
|
||||||
Q_CHECK_PTR (action);
|
|
||||||
action->activate (QAction::Trigger);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GlobalActionHelper::init()
|
|
||||||
{
|
|
||||||
qApp->installNativeEventFilter (new GlobalActionHelper);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GlobalActionHelper::makeGlobal(QAction* action)
|
|
||||||
{
|
|
||||||
QKeySequence hotKey = action->shortcut ();
|
|
||||||
if (hotKey.isEmpty ())
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
Qt::KeyboardModifiers allMods = Qt::ShiftModifier | Qt::ControlModifier |
|
|
||||||
Qt::AltModifier | Qt::MetaModifier;
|
|
||||||
Qt::Key key = hotKey.isEmpty() ?
|
|
||||||
Qt::Key(0) :
|
|
||||||
Qt::Key((hotKey[0] ^ allMods) & hotKey[0]);
|
|
||||||
Qt::KeyboardModifiers mods = hotKey.isEmpty() ?
|
|
||||||
Qt::KeyboardModifiers(0) :
|
|
||||||
Qt::KeyboardModifiers(hotKey[0] & allMods);
|
|
||||||
const quint32 nativeKey = nativeKeycode(key);
|
|
||||||
const quint32 nativeMods = nativeModifiers(mods);
|
|
||||||
const bool res = registerHotKey(nativeKey, nativeMods);
|
|
||||||
if (res)
|
|
||||||
actions_.insert (qMakePair(nativeKey, nativeMods), action);
|
|
||||||
else
|
|
||||||
qWarning() << "Failed to register global hotkey:" << hotKey.toString();
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GlobalActionHelper::removeGlobal(QAction *action)
|
|
||||||
{
|
|
||||||
QKeySequence hotKey = action->shortcut ();
|
|
||||||
if (hotKey.isEmpty ())
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
Qt::KeyboardModifiers allMods = Qt::ShiftModifier | Qt::ControlModifier |
|
|
||||||
Qt::AltModifier | Qt::MetaModifier;
|
|
||||||
Qt::Key key = hotKey.isEmpty() ?
|
|
||||||
Qt::Key(0) :
|
|
||||||
Qt::Key((hotKey[0] ^ allMods) & hotKey[0]);
|
|
||||||
Qt::KeyboardModifiers mods = hotKey.isEmpty() ?
|
|
||||||
Qt::KeyboardModifiers(0) :
|
|
||||||
Qt::KeyboardModifiers(hotKey[0] & allMods);
|
|
||||||
const quint32 nativeKey = nativeKeycode(key);
|
|
||||||
const quint32 nativeMods = nativeModifiers(mods);
|
|
||||||
if (!actions_.contains (qMakePair(nativeKey, nativeMods)))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const bool res = unregisterHotKey(nativeKey, nativeMods);
|
|
||||||
if (res)
|
|
||||||
actions_.remove (qMakePair(nativeKey, nativeMods));
|
|
||||||
else
|
|
||||||
qWarning() << "Failed to unregister global hotkey:" << hotKey.toString();
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
quint32 GlobalActionHelper::nativeKeycode(Qt::Key key)
|
|
||||||
{
|
|
||||||
switch (key)
|
|
||||||
{
|
|
||||||
case Qt::Key_Escape:
|
|
||||||
return VK_ESCAPE;
|
|
||||||
case Qt::Key_Tab:
|
|
||||||
case Qt::Key_Backtab:
|
|
||||||
return VK_TAB;
|
|
||||||
case Qt::Key_Backspace:
|
|
||||||
return VK_BACK;
|
|
||||||
case Qt::Key_Return:
|
|
||||||
case Qt::Key_Enter:
|
|
||||||
return VK_RETURN;
|
|
||||||
case Qt::Key_Insert:
|
|
||||||
return VK_INSERT;
|
|
||||||
case Qt::Key_Delete:
|
|
||||||
return VK_DELETE;
|
|
||||||
case Qt::Key_Pause:
|
|
||||||
return VK_PAUSE;
|
|
||||||
case Qt::Key_Print:
|
|
||||||
return VK_PRINT;
|
|
||||||
case Qt::Key_Clear:
|
|
||||||
return VK_CLEAR;
|
|
||||||
case Qt::Key_Home:
|
|
||||||
return VK_HOME;
|
|
||||||
case Qt::Key_End:
|
|
||||||
return VK_END;
|
|
||||||
case Qt::Key_Left:
|
|
||||||
return VK_LEFT;
|
|
||||||
case Qt::Key_Up:
|
|
||||||
return VK_UP;
|
|
||||||
case Qt::Key_Right:
|
|
||||||
return VK_RIGHT;
|
|
||||||
case Qt::Key_Down:
|
|
||||||
return VK_DOWN;
|
|
||||||
case Qt::Key_PageUp:
|
|
||||||
return VK_PRIOR;
|
|
||||||
case Qt::Key_PageDown:
|
|
||||||
return VK_NEXT;
|
|
||||||
case Qt::Key_F1:
|
|
||||||
return VK_F1;
|
|
||||||
case Qt::Key_F2:
|
|
||||||
return VK_F2;
|
|
||||||
case Qt::Key_F3:
|
|
||||||
return VK_F3;
|
|
||||||
case Qt::Key_F4:
|
|
||||||
return VK_F4;
|
|
||||||
case Qt::Key_F5:
|
|
||||||
return VK_F5;
|
|
||||||
case Qt::Key_F6:
|
|
||||||
return VK_F6;
|
|
||||||
case Qt::Key_F7:
|
|
||||||
return VK_F7;
|
|
||||||
case Qt::Key_F8:
|
|
||||||
return VK_F8;
|
|
||||||
case Qt::Key_F9:
|
|
||||||
return VK_F9;
|
|
||||||
case Qt::Key_F10:
|
|
||||||
return VK_F10;
|
|
||||||
case Qt::Key_F11:
|
|
||||||
return VK_F11;
|
|
||||||
case Qt::Key_F12:
|
|
||||||
return VK_F12;
|
|
||||||
case Qt::Key_F13:
|
|
||||||
return VK_F13;
|
|
||||||
case Qt::Key_F14:
|
|
||||||
return VK_F14;
|
|
||||||
case Qt::Key_F15:
|
|
||||||
return VK_F15;
|
|
||||||
case Qt::Key_F16:
|
|
||||||
return VK_F16;
|
|
||||||
case Qt::Key_F17:
|
|
||||||
return VK_F17;
|
|
||||||
case Qt::Key_F18:
|
|
||||||
return VK_F18;
|
|
||||||
case Qt::Key_F19:
|
|
||||||
return VK_F19;
|
|
||||||
case Qt::Key_F20:
|
|
||||||
return VK_F20;
|
|
||||||
case Qt::Key_F21:
|
|
||||||
return VK_F21;
|
|
||||||
case Qt::Key_F22:
|
|
||||||
return VK_F22;
|
|
||||||
case Qt::Key_F23:
|
|
||||||
return VK_F23;
|
|
||||||
case Qt::Key_F24:
|
|
||||||
return VK_F24;
|
|
||||||
case Qt::Key_Space:
|
|
||||||
return VK_SPACE;
|
|
||||||
case Qt::Key_Asterisk:
|
|
||||||
return VK_MULTIPLY;
|
|
||||||
case Qt::Key_Plus:
|
|
||||||
return VK_ADD;
|
|
||||||
case Qt::Key_Comma:
|
|
||||||
return VK_SEPARATOR;
|
|
||||||
case Qt::Key_Minus:
|
|
||||||
return VK_SUBTRACT;
|
|
||||||
case Qt::Key_Slash:
|
|
||||||
return VK_DIVIDE;
|
|
||||||
case Qt::Key_MediaNext:
|
|
||||||
return VK_MEDIA_NEXT_TRACK;
|
|
||||||
case Qt::Key_MediaPrevious:
|
|
||||||
return VK_MEDIA_PREV_TRACK;
|
|
||||||
case Qt::Key_MediaPlay:
|
|
||||||
return VK_MEDIA_PLAY_PAUSE;
|
|
||||||
case Qt::Key_MediaStop:
|
|
||||||
return VK_MEDIA_STOP;
|
|
||||||
// couldn't find those in VK_*
|
|
||||||
//case Qt::Key_MediaLast:
|
|
||||||
//case Qt::Key_MediaRecord:
|
|
||||||
case Qt::Key_VolumeDown:
|
|
||||||
return VK_VOLUME_DOWN;
|
|
||||||
case Qt::Key_VolumeUp:
|
|
||||||
return VK_VOLUME_UP;
|
|
||||||
case Qt::Key_VolumeMute:
|
|
||||||
return VK_VOLUME_MUTE;
|
|
||||||
|
|
||||||
// numbers
|
|
||||||
case Qt::Key_0:
|
|
||||||
case Qt::Key_1:
|
|
||||||
case Qt::Key_2:
|
|
||||||
case Qt::Key_3:
|
|
||||||
case Qt::Key_4:
|
|
||||||
case Qt::Key_5:
|
|
||||||
case Qt::Key_6:
|
|
||||||
case Qt::Key_7:
|
|
||||||
case Qt::Key_8:
|
|
||||||
case Qt::Key_9:
|
|
||||||
return key;
|
|
||||||
|
|
||||||
// letters
|
|
||||||
case Qt::Key_A:
|
|
||||||
case Qt::Key_B:
|
|
||||||
case Qt::Key_C:
|
|
||||||
case Qt::Key_D:
|
|
||||||
case Qt::Key_E:
|
|
||||||
case Qt::Key_F:
|
|
||||||
case Qt::Key_G:
|
|
||||||
case Qt::Key_H:
|
|
||||||
case Qt::Key_I:
|
|
||||||
case Qt::Key_J:
|
|
||||||
case Qt::Key_K:
|
|
||||||
case Qt::Key_L:
|
|
||||||
case Qt::Key_M:
|
|
||||||
case Qt::Key_N:
|
|
||||||
case Qt::Key_O:
|
|
||||||
case Qt::Key_P:
|
|
||||||
case Qt::Key_Q:
|
|
||||||
case Qt::Key_R:
|
|
||||||
case Qt::Key_S:
|
|
||||||
case Qt::Key_T:
|
|
||||||
case Qt::Key_U:
|
|
||||||
case Qt::Key_V:
|
|
||||||
case Qt::Key_W:
|
|
||||||
case Qt::Key_X:
|
|
||||||
case Qt::Key_Y:
|
|
||||||
case Qt::Key_Z:
|
|
||||||
return key;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
quint32 GlobalActionHelper::nativeModifiers(Qt::KeyboardModifiers modifiers)
|
|
||||||
{
|
|
||||||
// MOD_ALT, MOD_CONTROL, (MOD_KEYUP), MOD_SHIFT, MOD_WIN
|
|
||||||
quint32 native = 0;
|
|
||||||
if (modifiers & Qt::ShiftModifier)
|
|
||||||
native |= MOD_SHIFT;
|
|
||||||
if (modifiers & Qt::ControlModifier)
|
|
||||||
native |= MOD_CONTROL;
|
|
||||||
if (modifiers & Qt::AltModifier)
|
|
||||||
native |= MOD_ALT;
|
|
||||||
if (modifiers & Qt::MetaModifier)
|
|
||||||
native |= MOD_WIN;
|
|
||||||
//if (modifiers & Qt::KeypadModifier)
|
|
||||||
//if (modifiers & Qt::GroupSwitchModifier)
|
|
||||||
return native;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GlobalActionHelper::registerHotKey(quint32 nativeKey, quint32 nativeMods)
|
|
||||||
{
|
|
||||||
return RegisterHotKey(0, nativeMods ^ nativeKey, nativeMods, nativeKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GlobalActionHelper::unregisterHotKey(quint32 nativeKey, quint32 nativeMods)
|
|
||||||
{
|
|
||||||
return UnregisterHotKey(0, nativeMods ^ nativeKey);
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
#ifndef GLOBALACTIONHELPER_H
|
|
||||||
#define GLOBALACTIONHELPER_H
|
|
||||||
|
|
||||||
// Some functions copied from QXT lib
|
|
||||||
|
|
||||||
#include <QAbstractNativeEventFilter>
|
|
||||||
#include <QAction>
|
|
||||||
|
|
||||||
class GlobalActionHelper : public QAbstractNativeEventFilter
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
bool nativeEventFilter (const QByteArray &eventType, void *message,
|
|
||||||
long *result);
|
|
||||||
|
|
||||||
static void init ();
|
|
||||||
static bool makeGlobal (QAction* action);
|
|
||||||
static bool removeGlobal (QAction* action);
|
|
||||||
|
|
||||||
private:
|
|
||||||
static QHash<QPair<quint32, quint32>, QAction*> actions_;
|
|
||||||
|
|
||||||
static quint32 nativeKeycode (Qt::Key key);
|
|
||||||
static quint32 nativeModifiers (Qt::KeyboardModifiers modifiers);
|
|
||||||
static bool registerHotKey (quint32 nativeKey, quint32 nativeMods);
|
|
||||||
static bool unregisterHotKey (quint32 nativeKey, quint32 nativeMods);
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // GLOBALACTIONHELPER_H
|
|
@ -1,91 +0,0 @@
|
|||||||
#include <QWebView>
|
|
||||||
#include <QWebFrame>
|
|
||||||
#include <QWebElement>
|
|
||||||
#include <QSettings>
|
|
||||||
#include <QNetworkReply>
|
|
||||||
#include <QTimer>
|
|
||||||
|
|
||||||
#include "GoogleWebTranslator.h"
|
|
||||||
#include "Settings.h"
|
|
||||||
|
|
||||||
GoogleWebTranslator::GoogleWebTranslator()
|
|
||||||
: QObject (), view_ (new QWebView),
|
|
||||||
isLoadFinished_ (true), isTranslationFinished_ (false) {
|
|
||||||
|
|
||||||
view_->settings()->setAttribute(QWebSettings::AutoLoadImages, false);
|
|
||||||
connect (view_, SIGNAL (loadStarted()), this, SLOT (loadStarted()));
|
|
||||||
connect (view_, SIGNAL (loadFinished(bool)), this, SLOT (loadFinished(bool)));
|
|
||||||
|
|
||||||
connect (view_->page()->networkAccessManager(), SIGNAL (finished(QNetworkReply*)),
|
|
||||||
this, SLOT(replyFinished(QNetworkReply*)));
|
|
||||||
applySettings ();
|
|
||||||
}
|
|
||||||
|
|
||||||
GoogleWebTranslator::~GoogleWebTranslator() {
|
|
||||||
delete view_;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GoogleWebTranslator::translate(ProcessingItem item) {
|
|
||||||
queue_.push_back (item);
|
|
||||||
if (isLoadFinished_) {
|
|
||||||
load (item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GoogleWebTranslator::applySettings(){
|
|
||||||
QSettings settings;
|
|
||||||
settings.beginGroup (settings_names::translationGroup);
|
|
||||||
translationLanguage_ = settings.value (settings_names::translationLanguage,
|
|
||||||
settings_values::translationLanguage).toString ();
|
|
||||||
}
|
|
||||||
|
|
||||||
void GoogleWebTranslator::loadStarted() {
|
|
||||||
isLoadFinished_ = false;
|
|
||||||
isTranslationFinished_ = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GoogleWebTranslator::loadFinished(bool ok) {
|
|
||||||
isLoadFinished_ = true;
|
|
||||||
if (ok && !isTranslationFinished_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Q_ASSERT (!queue_.isEmpty());
|
|
||||||
ProcessingItem item = queue_.front();
|
|
||||||
queue_.pop_front();
|
|
||||||
if (ok) {
|
|
||||||
QWebElementCollection result = view_->page()->mainFrame()->findAllElements("#result_box > span");
|
|
||||||
item.translated = "";
|
|
||||||
foreach (const QWebElement& element, result) {
|
|
||||||
item.translated += element.toInnerXml() + " ";
|
|
||||||
}
|
|
||||||
emit translated(item, !item.translated.isEmpty());
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
emit translated (item, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!queue_.isEmpty()) {
|
|
||||||
load (queue_.front());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GoogleWebTranslator::replyFinished(QNetworkReply *reply)
|
|
||||||
{
|
|
||||||
if (reply->url().toString().contains ("/translate_a/single")) {
|
|
||||||
isTranslationFinished_ = true;
|
|
||||||
if (isLoadFinished_) {
|
|
||||||
QTimer::singleShot(2000, this, SLOT(loadFinished()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GoogleWebTranslator::load(const ProcessingItem &item) {
|
|
||||||
Q_ASSERT (!item.recognized.isEmpty ());
|
|
||||||
if (translationLanguage_.isEmpty ()) {
|
|
||||||
emit error (tr ("Неверные парметры для перевода."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
QUrl url (QString ("https://translate.google.com/#auto/%1/%2").arg(translationLanguage_, item.recognized));
|
|
||||||
view_->setUrl(url);
|
|
||||||
}
|
|
@ -1,43 +0,0 @@
|
|||||||
#ifndef GOOGLEWEBTRANSLATOR_H
|
|
||||||
#define GOOGLEWEBTRANSLATOR_H
|
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
|
|
||||||
#include "ProcessingItem.h"
|
|
||||||
|
|
||||||
class QWebView;
|
|
||||||
class QUrl;
|
|
||||||
class QNetworkReply;
|
|
||||||
|
|
||||||
class GoogleWebTranslator : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
GoogleWebTranslator();
|
|
||||||
~GoogleWebTranslator();
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void translated (ProcessingItem item, bool success);
|
|
||||||
void error (QString text);
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void translate (ProcessingItem item);
|
|
||||||
void applySettings ();
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void loadStarted ();
|
|
||||||
void loadFinished(bool ok=true);
|
|
||||||
void replyFinished(QNetworkReply * reply);
|
|
||||||
|
|
||||||
private:
|
|
||||||
void load (const ProcessingItem& item);
|
|
||||||
|
|
||||||
private:
|
|
||||||
QVector<ProcessingItem> queue_;
|
|
||||||
QString translationLanguage_;
|
|
||||||
QWebView *view_;
|
|
||||||
bool isLoadFinished_;
|
|
||||||
bool isTranslationFinished_;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // GOOGLEWEBTRANSLATOR_H
|
|
@ -1,151 +0,0 @@
|
|||||||
#include <QDebug>
|
|
||||||
|
|
||||||
#include <leptonica/allheaders.h>
|
|
||||||
|
|
||||||
#include <tesseract/host.h>
|
|
||||||
|
|
||||||
#include "ImageProcessing.h"
|
|
||||||
|
|
||||||
#ifdef WIN32
|
|
||||||
#include <windows.h>
|
|
||||||
qint64 getFreeMemory ()
|
|
||||||
{
|
|
||||||
MEMORYSTATUSEX statex;
|
|
||||||
statex.dwLength = sizeof (statex);
|
|
||||||
if (GlobalMemoryStatusEx (&statex))
|
|
||||||
{
|
|
||||||
return statex.ullAvailPhys;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
Pix *convertImage(const QImage& image)
|
|
||||||
{
|
|
||||||
PIX *pix;
|
|
||||||
|
|
||||||
QImage swapped = image.rgbSwapped();
|
|
||||||
int width = swapped.width();
|
|
||||||
int height = swapped.height();
|
|
||||||
int depth = swapped.depth();
|
|
||||||
int wpl = swapped.bytesPerLine() / 4;
|
|
||||||
|
|
||||||
pix = pixCreate(width, height, depth);
|
|
||||||
pixSetWpl(pix, wpl);
|
|
||||||
pixSetColormap(pix, NULL);
|
|
||||||
l_uint32 *outData = pix->data;
|
|
||||||
|
|
||||||
for (int y = 0; y < height; y++)
|
|
||||||
{
|
|
||||||
l_uint32 *lines = outData + y * wpl;
|
|
||||||
QByteArray a((const char*)swapped.scanLine(y), swapped.bytesPerLine());
|
|
||||||
for (int j = 0; j < a.size(); j++)
|
|
||||||
{
|
|
||||||
*((l_uint8 *)lines + j) = a[j];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const qreal toDPM = 1.0 / 0.0254;
|
|
||||||
int resolutionX = swapped.dotsPerMeterX() / toDPM;
|
|
||||||
int resolutionY = swapped.dotsPerMeterY() / toDPM;
|
|
||||||
|
|
||||||
if (resolutionX < 300) resolutionX = 300;
|
|
||||||
if (resolutionY < 300) resolutionY = 300;
|
|
||||||
pixSetResolution(pix, resolutionX, resolutionY);
|
|
||||||
|
|
||||||
return pixEndianByteSwapNew(pix);
|
|
||||||
}
|
|
||||||
|
|
||||||
QImage convertImage(Pix &image)
|
|
||||||
{
|
|
||||||
int width = pixGetWidth(&image);
|
|
||||||
int height = pixGetHeight(&image);
|
|
||||||
int depth = pixGetDepth(&image);
|
|
||||||
int bytesPerLine = pixGetWpl(&image) * 4;
|
|
||||||
l_uint32 * datas = pixGetData(pixEndianByteSwapNew(&image));
|
|
||||||
|
|
||||||
QImage::Format format;
|
|
||||||
if (depth == 1)
|
|
||||||
format = QImage::Format_Mono;
|
|
||||||
else if (depth == 8)
|
|
||||||
format = QImage::Format_Indexed8;
|
|
||||||
else
|
|
||||||
format = QImage::Format_RGB32;
|
|
||||||
|
|
||||||
QImage result((uchar*)datas, width, height, bytesPerLine, format);
|
|
||||||
|
|
||||||
// Set resolution
|
|
||||||
l_int32 xres, yres;
|
|
||||||
pixGetResolution(&image, &xres, &yres);
|
|
||||||
const qreal toDPM = 1.0 / 0.0254;
|
|
||||||
result.setDotsPerMeterX(xres * toDPM);
|
|
||||||
result.setDotsPerMeterY(yres * toDPM);
|
|
||||||
|
|
||||||
// Handle pallete
|
|
||||||
QVector<QRgb> _bwCT;
|
|
||||||
_bwCT.append(qRgb(255,255,255));
|
|
||||||
_bwCT.append(qRgb(0,0,0));
|
|
||||||
|
|
||||||
QVector<QRgb> _grayscaleCT(256);
|
|
||||||
for (int i = 0; i < 256; i++) {
|
|
||||||
_grayscaleCT.append(qRgb(i, i, i));
|
|
||||||
}
|
|
||||||
switch (depth) {
|
|
||||||
case 1:
|
|
||||||
result.setColorTable(_bwCT);
|
|
||||||
break;
|
|
||||||
case 8:
|
|
||||||
result.setColorTable(_grayscaleCT);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
result.setColorTable(_grayscaleCT);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.isNull()) {
|
|
||||||
static QImage none(0,0,QImage::Format_Invalid);
|
|
||||||
qDebug("Invalid format!!!\n");
|
|
||||||
return none;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.rgbSwapped();
|
|
||||||
}
|
|
||||||
|
|
||||||
Pix *prepareImage(const QImage &image, int preferredScale)
|
|
||||||
{
|
|
||||||
Pix* pix = convertImage (image);
|
|
||||||
Q_ASSERT (pix != NULL);
|
|
||||||
|
|
||||||
Pix* gray = pixConvertRGBToGray (pix, 0.0, 0.0, 0.0);
|
|
||||||
Q_ASSERT (gray != NULL);
|
|
||||||
pixDestroy (&pix);
|
|
||||||
|
|
||||||
Pix* scaled = gray;
|
|
||||||
if (preferredScale > 0)
|
|
||||||
{
|
|
||||||
float maxScaleX = MAX_INT16 / double (gray->w);
|
|
||||||
float scaleX = std::min (float (preferredScale), maxScaleX);
|
|
||||||
float maxScaleY = MAX_INT16 / double (gray->h);
|
|
||||||
float scaleY = std::min (float (preferredScale), maxScaleY);
|
|
||||||
float scale = std::min (scaleX, scaleY);
|
|
||||||
|
|
||||||
#ifdef WIN32
|
|
||||||
qint64 availableMemory = getFreeMemory () * 0.95;
|
|
||||||
qint32 actualSize = gray->w * gray->h * gray->d / 8;
|
|
||||||
float maxScaleMemory = float (availableMemory) / actualSize;
|
|
||||||
scale = std::min (scale, maxScaleMemory);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
scaled = pixScale (gray, scale, scale);
|
|
||||||
}
|
|
||||||
Q_ASSERT (scaled != NULL);
|
|
||||||
if (scaled != gray)
|
|
||||||
{
|
|
||||||
pixDestroy (&gray);
|
|
||||||
}
|
|
||||||
return scaled;
|
|
||||||
}
|
|
||||||
|
|
||||||
void cleanupImage(Pix **image)
|
|
||||||
{
|
|
||||||
pixDestroy (image);
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
#ifndef IMAGEPROCESSING_H
|
|
||||||
#define IMAGEPROCESSING_H
|
|
||||||
|
|
||||||
#include <QImage>
|
|
||||||
|
|
||||||
class Pix;
|
|
||||||
|
|
||||||
//! Convert QImage to Leptonica's PIX.
|
|
||||||
Pix* convertImage(const QImage& image);
|
|
||||||
//! Convert Leptonica's PIX to QImage.
|
|
||||||
QImage convertImage(Pix &image);
|
|
||||||
|
|
||||||
//! Propare image for OCR.
|
|
||||||
Pix* prepareImage (const QImage& image, int preferredScale);
|
|
||||||
//! Free allocated resources for image.
|
|
||||||
void cleanupImage (Pix** image);
|
|
||||||
|
|
||||||
#endif // IMAGEPROCESSING_H
|
|
@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2014 Gres (gres@gres.biz)
|
Copyright (c) 2017 Gres (gres@gres.biz)
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
this software and associated documentation files (the "Software"), to deal in
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
@ -1,244 +0,0 @@
|
|||||||
#include <QDir>
|
|
||||||
#include <QSettings>
|
|
||||||
|
|
||||||
#include "LanguageHelper.h"
|
|
||||||
#include "Settings.h"
|
|
||||||
|
|
||||||
LanguageHelper::LanguageHelper()
|
|
||||||
{
|
|
||||||
init ();
|
|
||||||
}
|
|
||||||
|
|
||||||
QStringList LanguageHelper::availableOcrLanguagesUi() const
|
|
||||||
{
|
|
||||||
QStringList uiItems;
|
|
||||||
foreach (const QString& item, availableOcrLanguages_)
|
|
||||||
{
|
|
||||||
uiItems << ocrCodeToUi (item);
|
|
||||||
}
|
|
||||||
uiItems.sort ();
|
|
||||||
return uiItems;
|
|
||||||
}
|
|
||||||
|
|
||||||
const QStringList &LanguageHelper::availableOcrLanguages() const
|
|
||||||
{
|
|
||||||
return availableOcrLanguages_;
|
|
||||||
}
|
|
||||||
|
|
||||||
QStringList LanguageHelper::availableOcrLanguages(const QString &path) const
|
|
||||||
{
|
|
||||||
QDir dir (path + "/tessdata/");
|
|
||||||
if (!dir.exists ())
|
|
||||||
{
|
|
||||||
return QStringList ();
|
|
||||||
}
|
|
||||||
QStringList items;
|
|
||||||
QStringList files = dir.entryList (QStringList () << "*.traineddata", QDir::Files);
|
|
||||||
foreach (const QString& file, files)
|
|
||||||
{
|
|
||||||
QString lang = file.left (file.indexOf ("."));
|
|
||||||
items << lang;
|
|
||||||
}
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
|
|
||||||
QStringList LanguageHelper::availableOcrLanguagesUi(const QString &path) const
|
|
||||||
{
|
|
||||||
QStringList uiItems, items;
|
|
||||||
items = availableOcrLanguages (path);
|
|
||||||
foreach (const QString& item, items)
|
|
||||||
{
|
|
||||||
uiItems << ocrCodeToUi (item);
|
|
||||||
}
|
|
||||||
uiItems.sort ();
|
|
||||||
return uiItems;
|
|
||||||
}
|
|
||||||
|
|
||||||
QStringList LanguageHelper::translateLanguagesUi() const
|
|
||||||
{
|
|
||||||
QStringList uiItems = translateLanguages_.keys ();
|
|
||||||
uiItems.sort ();
|
|
||||||
return uiItems;
|
|
||||||
}
|
|
||||||
|
|
||||||
QStringList LanguageHelper::translateLanguages() const
|
|
||||||
{
|
|
||||||
return translateLanguages_.values ();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString LanguageHelper::translateCodeToUi(const QString &text) const
|
|
||||||
{
|
|
||||||
return translateLanguages_.key (text, text);
|
|
||||||
}
|
|
||||||
|
|
||||||
QString LanguageHelper::translateUiToCode(const QString &text) const
|
|
||||||
{
|
|
||||||
return translateLanguages_.value (text, text);
|
|
||||||
}
|
|
||||||
|
|
||||||
QString LanguageHelper::ocrCodeToUi(const QString &text) const
|
|
||||||
{
|
|
||||||
return ocrLanguages_.key (text, text);
|
|
||||||
}
|
|
||||||
|
|
||||||
QString LanguageHelper::ocrUiToCode(const QString &text) const
|
|
||||||
{
|
|
||||||
return ocrLanguages_.value (text, text);
|
|
||||||
}
|
|
||||||
|
|
||||||
QString LanguageHelper::translateForOcrCode(const QString &text) const
|
|
||||||
{
|
|
||||||
QString ocrUi = ocrUiToCode (text);
|
|
||||||
QString translate = translateCodeToUi (ocrUi);
|
|
||||||
if (translate == ocrUi)
|
|
||||||
{
|
|
||||||
translate = "auto";
|
|
||||||
}
|
|
||||||
return translate;
|
|
||||||
}
|
|
||||||
|
|
||||||
void LanguageHelper::init()
|
|
||||||
{
|
|
||||||
initOcrLanguages ();
|
|
||||||
initTranslateLanguages ();
|
|
||||||
updateAvailableOcrLanguages ();
|
|
||||||
}
|
|
||||||
|
|
||||||
void LanguageHelper::updateAvailableOcrLanguages()
|
|
||||||
{
|
|
||||||
availableOcrLanguages_.clear ();
|
|
||||||
QSettings settings;
|
|
||||||
settings.beginGroup (settings_names::recogntionGroup);
|
|
||||||
QString tessDataPlace = settings.value (settings_names::tessDataPlace,
|
|
||||||
settings_values::tessDataPlace).toString ();
|
|
||||||
availableOcrLanguages_ = availableOcrLanguages (tessDataPlace);
|
|
||||||
}
|
|
||||||
|
|
||||||
void LanguageHelper::initTranslateLanguages()
|
|
||||||
{
|
|
||||||
translateLanguages_.insert(QObject::tr("Afrikaans"),"af");
|
|
||||||
translateLanguages_.insert(QObject::tr("Albanian"),"sq");
|
|
||||||
translateLanguages_.insert(QObject::tr("Arabic"),"ar");
|
|
||||||
translateLanguages_.insert(QObject::tr("Armenian"),"hy");
|
|
||||||
translateLanguages_.insert(QObject::tr("Azerbaijani"),"az");
|
|
||||||
translateLanguages_.insert(QObject::tr("Basque"),"eu");
|
|
||||||
translateLanguages_.insert(QObject::tr("Belarusian"),"be");
|
|
||||||
translateLanguages_.insert(QObject::tr("Bulgarian"),"bg");
|
|
||||||
translateLanguages_.insert(QObject::tr("Catalan"),"ca");
|
|
||||||
translateLanguages_.insert(QObject::tr("Chinese (Simplified)"),"zh-CN");
|
|
||||||
translateLanguages_.insert(QObject::tr("Chinese (Traditional)"),"zh-TW");
|
|
||||||
translateLanguages_.insert(QObject::tr("Croatian"),"hr");
|
|
||||||
translateLanguages_.insert(QObject::tr("Czech"),"cs");
|
|
||||||
translateLanguages_.insert(QObject::tr("Danish"),"da");
|
|
||||||
translateLanguages_.insert(QObject::tr("Dutch"),"nl");
|
|
||||||
translateLanguages_.insert(QObject::tr("English"),"en");
|
|
||||||
translateLanguages_.insert(QObject::tr("Estonian"),"et");
|
|
||||||
translateLanguages_.insert(QObject::tr("Filipino"),"tl");
|
|
||||||
translateLanguages_.insert(QObject::tr("Finnish"),"fi");
|
|
||||||
translateLanguages_.insert(QObject::tr("French"),"fr");
|
|
||||||
translateLanguages_.insert(QObject::tr("Galician"),"gl");
|
|
||||||
translateLanguages_.insert(QObject::tr("Georgian"),"ka");
|
|
||||||
translateLanguages_.insert(QObject::tr("German"),"de");
|
|
||||||
translateLanguages_.insert(QObject::tr("Greek"),"el");
|
|
||||||
translateLanguages_.insert(QObject::tr("Haitian Creole"),"ht");
|
|
||||||
translateLanguages_.insert(QObject::tr("Hebrew"),"iw");
|
|
||||||
translateLanguages_.insert(QObject::tr("Hindi"),"hi");
|
|
||||||
translateLanguages_.insert(QObject::tr("Hungarian"),"hu");
|
|
||||||
translateLanguages_.insert(QObject::tr("Icelandic"),"is");
|
|
||||||
translateLanguages_.insert(QObject::tr("Indonesian"),"id");
|
|
||||||
translateLanguages_.insert(QObject::tr("Irish"),"ga");
|
|
||||||
translateLanguages_.insert(QObject::tr("Italian"),"it");
|
|
||||||
translateLanguages_.insert(QObject::tr("Japanese"),"ja");
|
|
||||||
translateLanguages_.insert(QObject::tr("Korean"),"ko");
|
|
||||||
translateLanguages_.insert(QObject::tr("Latvian"),"lv");
|
|
||||||
translateLanguages_.insert(QObject::tr("Lithuanian"),"lt");
|
|
||||||
translateLanguages_.insert(QObject::tr("Macedonian"),"mk");
|
|
||||||
translateLanguages_.insert(QObject::tr("Malay"),"ms");
|
|
||||||
translateLanguages_.insert(QObject::tr("Maltese"),"mt");
|
|
||||||
translateLanguages_.insert(QObject::tr("Norwegian"),"no");
|
|
||||||
translateLanguages_.insert(QObject::tr("Persian"),"fa");
|
|
||||||
translateLanguages_.insert(QObject::tr("Polish"),"pl");
|
|
||||||
translateLanguages_.insert(QObject::tr("Portuguese"),"pt");
|
|
||||||
translateLanguages_.insert(QObject::tr("Romanian"),"ro");
|
|
||||||
translateLanguages_.insert(QObject::tr("Russian"),"ru");
|
|
||||||
translateLanguages_.insert(QObject::tr("Serbian"),"sr");
|
|
||||||
translateLanguages_.insert(QObject::tr("Slovak"),"sk");
|
|
||||||
translateLanguages_.insert(QObject::tr("Slovenian"),"sl");
|
|
||||||
translateLanguages_.insert(QObject::tr("Spanish"),"es");
|
|
||||||
translateLanguages_.insert(QObject::tr("Swahili"),"sw");
|
|
||||||
translateLanguages_.insert(QObject::tr("Swedish"),"sv");
|
|
||||||
translateLanguages_.insert(QObject::tr("Thai"),"th");
|
|
||||||
translateLanguages_.insert(QObject::tr("Turkish"),"tr");
|
|
||||||
translateLanguages_.insert(QObject::tr("Ukrainian"),"uk");
|
|
||||||
translateLanguages_.insert(QObject::tr("Urdu"),"ur");
|
|
||||||
translateLanguages_.insert(QObject::tr("Vietnamese"),"vi");
|
|
||||||
translateLanguages_.insert(QObject::tr("Welsh"),"cy");
|
|
||||||
translateLanguages_.insert(QObject::tr("Yiddish"),"yi");
|
|
||||||
}
|
|
||||||
|
|
||||||
void LanguageHelper::initOcrLanguages()
|
|
||||||
{
|
|
||||||
ocrLanguages_.insert(QObject::tr("Ancient Greek"),"grc");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Esperanto alternative"),"epo_alt");
|
|
||||||
ocrLanguages_.insert(QObject::tr("English"),"eng");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Ukrainian"),"ukr");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Turkish"),"tur");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Thai"),"tha");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Tagalog"),"tgl");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Telugu"),"tel");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Tamil"),"tam");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Swedish"),"swe");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Swahili"),"swa");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Serbian"),"srp");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Albanian"),"sqi");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Spanish"),"spa");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Slovenian"),"slv");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Slovakian"),"slk");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Romanian"),"ron");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Portuguese"),"por");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Polish"),"pol");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Norwegian"),"nor");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Dutch"),"nld");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Malay"),"msa");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Maltese"),"mlt");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Macedonian"),"mkd");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Malayalam"),"mal");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Lithuanian"),"lit");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Latvian"),"lav");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Korean"),"kor");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Kannada"),"kan");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Italian"),"ita");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Icelandic"),"isl");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Indonesian"),"ind");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Cherokee"),"chr");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Hungarian"),"hun");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Croatian"),"hrv");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Hindi"),"hin");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Hebrew"),"heb");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Galician"),"glg");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Middle French (ca. 1400-1600)"),"frm");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Frankish"),"frk");
|
|
||||||
ocrLanguages_.insert(QObject::tr("French"),"fra");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Finnish"),"fin");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Basque"),"eus");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Estonian"),"est");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Math / equation"),"equ");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Esperanto"),"epo");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Middle English (1100-1500)"),"enm");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Greek"),"ell");
|
|
||||||
ocrLanguages_.insert(QObject::tr("German"),"deu");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Danish"),"dan");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Czech"),"ces");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Catalan"),"cat");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Bulgarian"),"bul");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Bengali"),"ben");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Belarusian"),"bel");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Azerbaijani"),"aze");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Arabic"),"ara");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Afrikaans"),"afr");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Japanese"),"jpn");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Chinese (Simplified)"),"chi_sim");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Chinese (Traditional)"),"chi_tra");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Russian"),"rus");
|
|
||||||
ocrLanguages_.insert(QObject::tr("Vietnamese"),"vie");
|
|
||||||
}
|
|
@ -1,39 +0,0 @@
|
|||||||
#ifndef LANGUAGEHELPER_H
|
|
||||||
#define LANGUAGEHELPER_H
|
|
||||||
|
|
||||||
#include <QMap>
|
|
||||||
#include <QStringList>
|
|
||||||
|
|
||||||
class LanguageHelper
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
LanguageHelper ();
|
|
||||||
|
|
||||||
QStringList availableOcrLanguagesUi () const;
|
|
||||||
const QStringList& availableOcrLanguages () const;
|
|
||||||
QStringList availableOcrLanguagesUi (const QString& path) const;
|
|
||||||
QStringList translateLanguagesUi () const;
|
|
||||||
QStringList translateLanguages () const;
|
|
||||||
|
|
||||||
QString translateCodeToUi (const QString& text) const;
|
|
||||||
QString translateUiToCode (const QString& text) const;
|
|
||||||
QString ocrCodeToUi (const QString& text) const;
|
|
||||||
QString ocrUiToCode (const QString& text) const;
|
|
||||||
QString translateForOcrCode (const QString& text) const;
|
|
||||||
|
|
||||||
void updateAvailableOcrLanguages ();
|
|
||||||
|
|
||||||
private:
|
|
||||||
QStringList availableOcrLanguages (const QString& path) const;
|
|
||||||
void init ();
|
|
||||||
void initTranslateLanguages ();
|
|
||||||
void initOcrLanguages ();
|
|
||||||
|
|
||||||
private:
|
|
||||||
QStringList availableOcrLanguages_;
|
|
||||||
QMap<QString, QString> translateLanguages_;
|
|
||||||
QMap<QString, QString> ocrLanguages_;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
#endif // LANGUAGEHELPER_H
|
|
223
Manager.cpp
@ -1,223 +0,0 @@
|
|||||||
#include "Manager.h"
|
|
||||||
|
|
||||||
#include <QDebug>
|
|
||||||
#include <QMenu>
|
|
||||||
#include <QApplication>
|
|
||||||
#include <QDesktopWidget>
|
|
||||||
#include <QScreen>
|
|
||||||
#include <QThread>
|
|
||||||
#include <QSettings>
|
|
||||||
#include <QClipboard>
|
|
||||||
#include <QMessageBox>
|
|
||||||
|
|
||||||
#include "Settings.h"
|
|
||||||
#include "SettingsEditor.h"
|
|
||||||
#include "SelectionDialog.h"
|
|
||||||
#include "GlobalActionHelper.h"
|
|
||||||
#include "Recognizer.h"
|
|
||||||
#include "Translator.h"
|
|
||||||
#include "ResultDialog.h"
|
|
||||||
#include "LanguageHelper.h"
|
|
||||||
|
|
||||||
Manager::Manager(QObject *parent) :
|
|
||||||
QObject(parent),
|
|
||||||
trayIcon_ (new QSystemTrayIcon (QIcon (":/images/icon.png"), this)),
|
|
||||||
dictionary_ (new LanguageHelper),
|
|
||||||
selection_ (new SelectionDialog (*dictionary_)),
|
|
||||||
resultDialog_ (new ResultDialog),
|
|
||||||
captureAction_ (NULL), repeatAction_ (NULL), clipboardAction_ (NULL),
|
|
||||||
useResultDialog_ (true)
|
|
||||||
{
|
|
||||||
GlobalActionHelper::init ();
|
|
||||||
qRegisterMetaType<ProcessingItem>();
|
|
||||||
|
|
||||||
// Recognizer
|
|
||||||
Recognizer* recognizer = new Recognizer;
|
|
||||||
connect (selection_, SIGNAL (selected (ProcessingItem)),
|
|
||||||
recognizer, SLOT (recognize (ProcessingItem)));
|
|
||||||
connect (recognizer, SIGNAL (error (QString)),
|
|
||||||
SLOT (showError (QString)));
|
|
||||||
connect (this, SIGNAL (settingsEdited ()),
|
|
||||||
recognizer, SLOT (applySettings ()));
|
|
||||||
QThread* recognizerThread = new QThread (this);
|
|
||||||
recognizer->moveToThread (recognizerThread);
|
|
||||||
recognizerThread->start ();
|
|
||||||
connect (qApp, SIGNAL (aboutToQuit ()), recognizerThread, SLOT (quit ()));
|
|
||||||
|
|
||||||
|
|
||||||
// Translator
|
|
||||||
Translator* translator = new Translator;
|
|
||||||
connect (recognizer, SIGNAL (recognized (ProcessingItem)),
|
|
||||||
translator, SLOT (translate (ProcessingItem)));
|
|
||||||
connect (translator, SIGNAL (error (QString)),
|
|
||||||
SLOT (showError (QString)));
|
|
||||||
connect (this, SIGNAL (settingsEdited ()),
|
|
||||||
translator, SLOT (applySettings ()));
|
|
||||||
QThread* translatorThread = new QThread (this);
|
|
||||||
translator->moveToThread (translatorThread);
|
|
||||||
translatorThread->start ();
|
|
||||||
connect (qApp, SIGNAL (aboutToQuit ()), translatorThread, SLOT (quit ()));
|
|
||||||
|
|
||||||
connect (translator, SIGNAL (translated (ProcessingItem)),
|
|
||||||
SLOT (showResult (ProcessingItem)));
|
|
||||||
|
|
||||||
connect (this, SIGNAL (showPixmap (QPixmap)),
|
|
||||||
selection_, SLOT (setPixmap (QPixmap)));
|
|
||||||
|
|
||||||
connect (this, SIGNAL (settingsEdited ()), selection_, SLOT (updateMenu ()));
|
|
||||||
connect (this, SIGNAL (settingsEdited ()), this, SLOT (applySettings ()));
|
|
||||||
selection_->setWindowIcon (trayIcon_->icon ());
|
|
||||||
resultDialog_->setWindowIcon (trayIcon_->icon ());
|
|
||||||
|
|
||||||
|
|
||||||
connect (trayIcon_, SIGNAL (activated (QSystemTrayIcon::ActivationReason)),
|
|
||||||
SLOT (processTrayAction (QSystemTrayIcon::ActivationReason)));
|
|
||||||
|
|
||||||
trayIcon_->setContextMenu (trayContextMenu ());
|
|
||||||
trayIcon_->show ();
|
|
||||||
|
|
||||||
applySettings ();
|
|
||||||
}
|
|
||||||
|
|
||||||
QMenu*Manager::trayContextMenu()
|
|
||||||
{
|
|
||||||
QMenu* menu = new QMenu ();
|
|
||||||
captureAction_ = menu->addAction (tr ("Захват"), this, SLOT (capture ()));
|
|
||||||
QMenu* translateMenu = menu->addMenu (tr ("Перевод"));
|
|
||||||
repeatAction_ = translateMenu->addAction (tr ("Повторить"), this,
|
|
||||||
SLOT (showLast ()));
|
|
||||||
clipboardAction_ = translateMenu->addAction (tr ("Скопировать"), this,
|
|
||||||
SLOT (copyLastToClipboard ()));
|
|
||||||
menu->addAction (tr ("Настройки"), this, SLOT (settings ()));
|
|
||||||
menu->addAction (tr ("О программе"), this, SLOT (about ()));
|
|
||||||
menu->addAction (tr ("Выход"), this, SLOT (close ()));
|
|
||||||
return menu;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Manager::applySettings()
|
|
||||||
{
|
|
||||||
QSettings settings;
|
|
||||||
settings.beginGroup (settings_names::guiGroup);
|
|
||||||
QString captureHotkey = settings.value (settings_names::captureHotkey,
|
|
||||||
settings_values::captureHotkey).toString ();
|
|
||||||
Q_CHECK_PTR (captureAction_);
|
|
||||||
GlobalActionHelper::removeGlobal (captureAction_);
|
|
||||||
captureAction_->setShortcut (captureHotkey);
|
|
||||||
GlobalActionHelper::makeGlobal (captureAction_);
|
|
||||||
|
|
||||||
QString repeatHotkey = settings.value (settings_names::repeatHotkey,
|
|
||||||
settings_values::repeatHotkey).toString ();
|
|
||||||
Q_CHECK_PTR (repeatAction_);
|
|
||||||
GlobalActionHelper::removeGlobal (repeatAction_);
|
|
||||||
repeatAction_->setShortcut (repeatHotkey);
|
|
||||||
GlobalActionHelper::makeGlobal (repeatAction_);
|
|
||||||
|
|
||||||
QString clipboardHotkey = settings.value (settings_names::clipboardHotkey,
|
|
||||||
settings_values::clipboardHotkey).toString ();
|
|
||||||
Q_CHECK_PTR (clipboardAction_);
|
|
||||||
GlobalActionHelper::removeGlobal (clipboardAction_);
|
|
||||||
clipboardAction_->setShortcut (clipboardHotkey);
|
|
||||||
GlobalActionHelper::makeGlobal (clipboardAction_);
|
|
||||||
|
|
||||||
// Depends on SettingsEditor button indexes. 1==dialog
|
|
||||||
useResultDialog_ = settings.value (settings_names::resultShowType,
|
|
||||||
settings_values::resultShowType).toBool ();
|
|
||||||
|
|
||||||
Q_CHECK_PTR (dictionary_);
|
|
||||||
dictionary_->updateAvailableOcrLanguages ();
|
|
||||||
}
|
|
||||||
|
|
||||||
Manager::~Manager()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void Manager::capture()
|
|
||||||
{
|
|
||||||
QList<QScreen*> screens = QApplication::screens ();
|
|
||||||
Q_ASSERT (!screens.isEmpty ());
|
|
||||||
QScreen* screen = screens.first ();
|
|
||||||
Q_CHECK_PTR (screen);
|
|
||||||
WId desktopId = QApplication::desktop ()->winId ();
|
|
||||||
QPixmap pixmap = screen->grabWindow (desktopId);
|
|
||||||
Q_ASSERT (!pixmap.isNull ());
|
|
||||||
emit showPixmap (pixmap);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Manager::settings()
|
|
||||||
{
|
|
||||||
SettingsEditor editor (*dictionary_);
|
|
||||||
editor.setWindowIcon (trayIcon_->icon ());
|
|
||||||
connect (&editor, SIGNAL (settingsEdited ()), SIGNAL (settingsEdited ()));
|
|
||||||
editor.exec ();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Manager::close()
|
|
||||||
{
|
|
||||||
QApplication::quit ();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Manager::about()
|
|
||||||
{
|
|
||||||
QString text = tr ("Программа для распознавания текста на экране.\n"\
|
|
||||||
"Создана с использованием Qt, tesseract-ocr, Google Translate.\n"
|
|
||||||
"Автор: Gres (translator@gres.biz)");
|
|
||||||
|
|
||||||
QMessageBox message (QMessageBox::Information, tr ("О программе"), text,
|
|
||||||
QMessageBox::Ok);
|
|
||||||
message.setIconPixmap (trayIcon_->icon ().pixmap (QSize (64, 64)));
|
|
||||||
message.exec ();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Manager::processTrayAction(QSystemTrayIcon::ActivationReason reason)
|
|
||||||
{
|
|
||||||
if (reason == QSystemTrayIcon::Trigger)
|
|
||||||
{
|
|
||||||
showLast ();
|
|
||||||
}
|
|
||||||
else if (reason == QSystemTrayIcon::MiddleClick)
|
|
||||||
{
|
|
||||||
copyLastToClipboard ();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Manager::showLast()
|
|
||||||
{
|
|
||||||
if (lastItem_.isValid ())
|
|
||||||
{
|
|
||||||
showResult (lastItem_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Manager::copyLastToClipboard()
|
|
||||||
{
|
|
||||||
if (lastItem_.isValid ())
|
|
||||||
{
|
|
||||||
QClipboard* clipboard = QApplication::clipboard ();
|
|
||||||
QString message = lastItem_.recognized + " - " + lastItem_.translated;
|
|
||||||
clipboard->setText (message);
|
|
||||||
trayIcon_->showMessage (tr ("Перевод"),
|
|
||||||
tr ("Последний перевод был скопирован в буфер обмена."),
|
|
||||||
QSystemTrayIcon::Information);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Manager::showResult(ProcessingItem item)
|
|
||||||
{
|
|
||||||
Q_ASSERT (item.isValid ());
|
|
||||||
lastItem_ = item;
|
|
||||||
if (useResultDialog_)
|
|
||||||
{
|
|
||||||
resultDialog_->showResult (item);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
QString message = item.recognized + " - " + item.translated;
|
|
||||||
trayIcon_->showMessage (tr ("Перевод"), message, QSystemTrayIcon::Information);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Manager::showError(QString text)
|
|
||||||
{
|
|
||||||
qCritical () << text;
|
|
||||||
trayIcon_->showMessage (tr ("Ошибка"), text, QSystemTrayIcon::Critical);
|
|
||||||
}
|
|
57
Manager.h
@ -1,57 +0,0 @@
|
|||||||
#ifndef MANAGER_H
|
|
||||||
#define MANAGER_H
|
|
||||||
|
|
||||||
#include <QPixmap>
|
|
||||||
#include <QSystemTrayIcon>
|
|
||||||
|
|
||||||
#include "ProcessingItem.h"
|
|
||||||
|
|
||||||
class QAction;
|
|
||||||
class QMenu;
|
|
||||||
|
|
||||||
class SelectionDialog;
|
|
||||||
class ResultDialog;
|
|
||||||
class LanguageHelper;
|
|
||||||
|
|
||||||
class Manager : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
explicit Manager(QObject *parent = 0);
|
|
||||||
~Manager ();
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void showPixmap (QPixmap pixmap);
|
|
||||||
void settingsEdited ();
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void capture ();
|
|
||||||
void settings ();
|
|
||||||
void close ();
|
|
||||||
void about ();
|
|
||||||
void showLast ();
|
|
||||||
void copyLastToClipboard ();
|
|
||||||
|
|
||||||
void applySettings ();
|
|
||||||
|
|
||||||
void processTrayAction (QSystemTrayIcon::ActivationReason reason);
|
|
||||||
|
|
||||||
void showResult (ProcessingItem item);
|
|
||||||
void showError (QString text);
|
|
||||||
|
|
||||||
private:
|
|
||||||
QMenu* trayContextMenu ();
|
|
||||||
|
|
||||||
private:
|
|
||||||
QSystemTrayIcon* trayIcon_;
|
|
||||||
LanguageHelper* dictionary_;
|
|
||||||
SelectionDialog* selection_;
|
|
||||||
ResultDialog* resultDialog_;
|
|
||||||
QAction* captureAction_;
|
|
||||||
QAction* repeatAction_;
|
|
||||||
QAction* clipboardAction_;
|
|
||||||
ProcessingItem lastItem_;
|
|
||||||
bool useResultDialog_;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // MANAGER_H
|
|
@ -1,11 +0,0 @@
|
|||||||
#include "ProcessingItem.h"
|
|
||||||
|
|
||||||
bool ProcessingItem::isValid() const
|
|
||||||
{
|
|
||||||
bool valid = true;
|
|
||||||
valid &= (!screenPos.isNull ());
|
|
||||||
valid &= (!source.isNull ());
|
|
||||||
valid &= (!recognized.isEmpty ());
|
|
||||||
valid &= (!translated.isEmpty ());
|
|
||||||
return valid;
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
#ifndef PROCESSINGITEM_H
|
|
||||||
#define PROCESSINGITEM_H
|
|
||||||
|
|
||||||
#include <QPixmap>
|
|
||||||
|
|
||||||
struct ProcessingItem
|
|
||||||
{
|
|
||||||
QPoint screenPos;
|
|
||||||
QPixmap source;
|
|
||||||
QString recognized;
|
|
||||||
QString translated;
|
|
||||||
|
|
||||||
QString ocrLanguage;
|
|
||||||
QString sourceLanguage;
|
|
||||||
|
|
||||||
bool isValid () const;
|
|
||||||
};
|
|
||||||
Q_DECLARE_METATYPE(ProcessingItem)
|
|
||||||
|
|
||||||
#endif // PROCESSINGITEM_H
|
|
112
README.md
@ -1,38 +1,92 @@
|
|||||||
Screen Translator
|
# Screen Translator
|
||||||
=================
|
|
||||||
|
**The project is almost abandoned. I don't have time for it and I can only fix minor issues**
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
Introduction
|
|
||||||
------------
|
|
||||||
This software allows you to translate any text on screen.
|
This software allows you to translate any text on screen.
|
||||||
Basically it is a combination of screen capture, OCR and translation tools.
|
Basically it is a combination of screen capture, OCR and translation tools.
|
||||||
|
Translation is currently done via online services.
|
||||||
|
|
||||||
Usage
|
## Installation
|
||||||
-----
|
|
||||||
1. Press capture hotkey.
|
|
||||||
2. Select region on screen.
|
|
||||||
3. Get translation of recognized text.
|
|
||||||
|
|
||||||
Features
|
**Windows**: download archive from [github releases](https://github.com/OneMoreGres/ScreenTranslator/releases) page, extract it and run `.exe` file.
|
||||||
--------
|
|
||||||
* Many OCR languages (can be modified dynamicly)
|
|
||||||
* Global hotkeys for main actions
|
|
||||||
* Copy last translation to clipboard
|
|
||||||
* Repeat last translation
|
|
||||||
* Show result in 2 ways (widget or tray baloon)
|
|
||||||
* Preprocess (scale) recognizeable image
|
|
||||||
* Interface languages (ru, eng)
|
|
||||||
|
|
||||||
|
If the app fails to start complaining about missing dlls or there are any update errors related to SSL/TLS then install or repair `vs_redist*.exe` from the release archive.
|
||||||
|
|
||||||
Limitations
|
**Linux**: download `.AppImage` file from [github releases](https://github.com/OneMoreGres/ScreenTranslator/releases), make executable (`chmod +x <file>`) and run it.
|
||||||
-----------
|
|
||||||
* Works only on primary screen
|
|
||||||
* Can not capture some dynamic web-pages
|
|
||||||
* Not very precise OCR (need better preprocessing steps)
|
|
||||||
|
|
||||||
Used software
|
**OS X**: currently not supported.
|
||||||
-------------
|
|
||||||
* see [Qt 5](http://qt-project.org/)
|
|
||||||
* see [Tesseract](https://code.google.com/p/tesseract-ocr/)
|
|
||||||
* see [Leptonica](http://leptonica.com/) (Tesseract dependency)
|
|
||||||
* Google Translate
|
|
||||||
|
|
||||||
|
### App translation
|
||||||
|
|
||||||
|
To install Hebrew translation of the app (thanks to [Y-PLONI](https://github.com/Y-PLONI)),
|
||||||
|
download [this](https://github.com/OneMoreGres/ScreenTranslator/releases/download/3.3.0/screentranslator_he.qm)
|
||||||
|
file and place it into the `translations` folder next to `screen-translator.exe`.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
The app doesn't have a main window.
|
||||||
|
After start it shows only the tray icon.
|
||||||
|
|
||||||
|
If the app detects invalid settings, it will show the error message via system tray.
|
||||||
|
It will also highlight the section name in red on the left panel of the settings window.
|
||||||
|
Clicking on that section name will show a more detailed error message in the right panel (also in red).
|
||||||
|
|
||||||
|
The packages downloaded from this site do not include resources, such as recognition language packs or scripts to interact with online translation services.
|
||||||
|
|
||||||
|
To download them, open the settings window and go to the `Update` section.
|
||||||
|
In the right panel, expand the `recognizers` and `translators` sections.
|
||||||
|
Select preferred items, then right click and choose `Install/Update`.
|
||||||
|
After the progress bar reaches `100%`, the resource's state will change to `Up to Date`.
|
||||||
|
|
||||||
|
You must download at least one `recognizers` resource and one `translators` resource.
|
||||||
|
|
||||||
|
After finishing downloads, go to the `Recognition` section and update the default recognition language setting (the source to be translated).
|
||||||
|
Then go to the `Translation` section, update the default translation language setting (the language to be translated into) and enable some or all translation sevices (you may also change their order by dragging).
|
||||||
|
|
||||||
|
After that all sections in the left panel should be black.
|
||||||
|
Then click `Ok` to close settings.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
1. Run program (note that it doesn't have main window).
|
||||||
|
2. Press capture hotkey.
|
||||||
|
3. Select region on screen. Customize it if needed.
|
||||||
|
4. Get translation of recognized text.
|
||||||
|
5. Check for updates if something is not working.
|
||||||
|
|
||||||
|
## FAQ
|
||||||
|
|
||||||
|
By default resources are downloaded to the one of the user's folders.
|
||||||
|
If `Portable` setting in `General` section is checked, then resources will be downloaded to the app's folder.
|
||||||
|
|
||||||
|
Set `QTWEBENGINE_DISABLE_SANDBOX=1` environment variable when fail to start due to crash.
|
||||||
|
|
||||||
|
Answers to some frequently asked questions can be found in issues or
|
||||||
|
[wiki](https://github.com/OneMoreGres/ScreenTranslator/wiki/FAQ)
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
* Can not capture some dynamic web-pages/full screen applications
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
* see [Qt 5](https://qt-project.org/)
|
||||||
|
* see [Tesseract](https://github.com/tesseract-ocr/tesseract/)
|
||||||
|
* see [Leptonica](https://leptonica.com/)
|
||||||
|
* several online translation services
|
||||||
|
|
||||||
|
## Build from source
|
||||||
|
|
||||||
|
Look at the scripts (python3) in the `share/ci` folder.
|
||||||
|
Normally, you should only edit the `config.py` file.
|
||||||
|
|
||||||
|
Build dependencies at first, then build the app.
|
||||||
|
|
||||||
|
## Attributions
|
||||||
|
|
||||||
|
* icons made by
|
||||||
|
[Smashicons](https://www.flaticon.com/authors/smashicons),
|
||||||
|
[Freepik](https://www.flaticon.com/authors/freepik),
|
||||||
|
from [Flaticon](https://www.flaticon.com/)
|
||||||
|
@ -1,99 +0,0 @@
|
|||||||
#include "Recognizer.h"
|
|
||||||
|
|
||||||
#include <tesseract/baseapi.h>
|
|
||||||
|
|
||||||
#include <QDebug>
|
|
||||||
#include <QSettings>
|
|
||||||
|
|
||||||
#include "Settings.h"
|
|
||||||
#include "ImageProcessing.h"
|
|
||||||
|
|
||||||
Recognizer::Recognizer(QObject *parent) :
|
|
||||||
QObject(parent),
|
|
||||||
engine_ (NULL), imageScale_ (0)
|
|
||||||
{
|
|
||||||
applySettings ();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Recognizer::applySettings()
|
|
||||||
{
|
|
||||||
QSettings settings;
|
|
||||||
settings.beginGroup (settings_names::recogntionGroup);
|
|
||||||
|
|
||||||
tessDataDir_ = settings.value (settings_names::tessDataPlace,
|
|
||||||
settings_values::tessDataPlace).toString ();
|
|
||||||
if (tessDataDir_.right (1) != "/")
|
|
||||||
{
|
|
||||||
tessDataDir_ += "/";
|
|
||||||
}
|
|
||||||
ocrLanguage_ = settings.value (settings_names::ocrLanguage,
|
|
||||||
settings_values::ocrLanguage).toString ();
|
|
||||||
imageScale_ = settings.value (settings_names::imageScale,
|
|
||||||
settings_values::imageScale).toInt ();
|
|
||||||
|
|
||||||
initEngine (engine_, ocrLanguage_);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Recognizer::initEngine(tesseract::TessBaseAPI *&engine, const QString& language)
|
|
||||||
{
|
|
||||||
if (tessDataDir_.isEmpty () || language.isEmpty ())
|
|
||||||
{
|
|
||||||
emit error (tr ("Неверные параметры для OCR"));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (engine != NULL)
|
|
||||||
{
|
|
||||||
delete engine;
|
|
||||||
}
|
|
||||||
engine = new tesseract::TessBaseAPI();
|
|
||||||
int result = engine->Init(qPrintable (tessDataDir_), qPrintable (language),
|
|
||||||
tesseract::OEM_DEFAULT);
|
|
||||||
if (result != 0)
|
|
||||||
{
|
|
||||||
emit error (tr ("Ошибка инициализации OCR: %1").arg (result));
|
|
||||||
delete engine;
|
|
||||||
engine = NULL;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Recognizer::recognize(ProcessingItem item)
|
|
||||||
{
|
|
||||||
Q_ASSERT (!item.source.isNull ());
|
|
||||||
bool isCustomLanguage = (!item.ocrLanguage.isEmpty () &&
|
|
||||||
item.ocrLanguage != ocrLanguage_);
|
|
||||||
tesseract::TessBaseAPI* engine = (isCustomLanguage) ? NULL : engine_;
|
|
||||||
if (engine == NULL)
|
|
||||||
{
|
|
||||||
QString language = (isCustomLanguage) ? item.ocrLanguage : ocrLanguage_;
|
|
||||||
if (!initEngine (engine, language))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Pix* image = prepareImage (item.source.toImage (), imageScale_);
|
|
||||||
Q_ASSERT (image != NULL);
|
|
||||||
engine->SetImage (image);
|
|
||||||
char* outText = engine->GetUTF8Text();
|
|
||||||
engine->Clear();
|
|
||||||
cleanupImage (&image);
|
|
||||||
|
|
||||||
QString result = QString (outText).trimmed ();
|
|
||||||
delete [] outText;
|
|
||||||
if (isCustomLanguage)
|
|
||||||
{
|
|
||||||
delete engine;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!result.isEmpty ())
|
|
||||||
{
|
|
||||||
item.recognized = result;
|
|
||||||
emit recognized (item);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
emit error (tr ("Текст не распознан."));
|
|
||||||
}
|
|
||||||
}
|
|
40
Recognizer.h
@ -1,40 +0,0 @@
|
|||||||
#ifndef RECOGNIZER_H
|
|
||||||
#define RECOGNIZER_H
|
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
#include "QPixmap"
|
|
||||||
|
|
||||||
#include "ProcessingItem.h"
|
|
||||||
|
|
||||||
namespace tesseract
|
|
||||||
{
|
|
||||||
class TessBaseAPI;
|
|
||||||
}
|
|
||||||
|
|
||||||
class Recognizer : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
explicit Recognizer(QObject *parent = 0);
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void recognized (ProcessingItem item);
|
|
||||||
void error (QString text);
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void recognize (ProcessingItem item);
|
|
||||||
void applySettings ();
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool initEngine (tesseract::TessBaseAPI*&engine, const QString &language);
|
|
||||||
|
|
||||||
private:
|
|
||||||
tesseract::TessBaseAPI* engine_;
|
|
||||||
|
|
||||||
QString tessDataDir_;
|
|
||||||
QString ocrLanguage_;
|
|
||||||
int imageScale_;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // RECOGNIZER_H
|
|
@ -1,7 +0,0 @@
|
|||||||
<RCC>
|
|
||||||
<qresource prefix="/">
|
|
||||||
<file>translations/translation_en.qm</file>
|
|
||||||
<file>translations/translation_ru.qm</file>
|
|
||||||
<file>images/icon.png</file>
|
|
||||||
</qresource>
|
|
||||||
</RCC>
|
|
@ -1,68 +0,0 @@
|
|||||||
#include "ResultDialog.h"
|
|
||||||
#include "ui_ResultDialog.h"
|
|
||||||
|
|
||||||
#include <QDesktopWidget>
|
|
||||||
|
|
||||||
ResultDialog::ResultDialog(QWidget *parent) :
|
|
||||||
QDialog(parent),
|
|
||||||
ui(new Ui::ResultDialog),
|
|
||||||
isShowAtCapturePos_ (true)
|
|
||||||
{
|
|
||||||
ui->setupUi(this);
|
|
||||||
setWindowFlags (Qt::FramelessWindowHint | Qt::NoDropShadowWindowHint |
|
|
||||||
Qt::WindowStaysOnTopHint);
|
|
||||||
|
|
||||||
installEventFilter (this);
|
|
||||||
}
|
|
||||||
|
|
||||||
ResultDialog::~ResultDialog()
|
|
||||||
{
|
|
||||||
delete ui;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ResultDialog::eventFilter(QObject* object, QEvent* event)
|
|
||||||
{
|
|
||||||
Q_UNUSED (object);
|
|
||||||
if (event->type () == QEvent::MouseButtonRelease)
|
|
||||||
{
|
|
||||||
hide ();
|
|
||||||
}
|
|
||||||
return QDialog::eventFilter (object, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ResultDialog::showResult(ProcessingItem item)
|
|
||||||
{
|
|
||||||
Q_ASSERT (!item.source.isNull ());
|
|
||||||
Q_ASSERT (!item.recognized.isEmpty ());
|
|
||||||
Q_ASSERT (!item.translated.isEmpty ());
|
|
||||||
Q_ASSERT (!item.screenPos.isNull ());
|
|
||||||
|
|
||||||
ui->sourceLabel->setPixmap (item.source);
|
|
||||||
ui->recognizeLabel->setText (item.recognized);
|
|
||||||
ui->translateLabel->setText (item.translated);
|
|
||||||
|
|
||||||
show ();
|
|
||||||
adjustSize ();
|
|
||||||
|
|
||||||
QDesktopWidget* desktop = QApplication::desktop ();
|
|
||||||
Q_CHECK_PTR (desktop);
|
|
||||||
if (isShowAtCapturePos_)
|
|
||||||
{
|
|
||||||
QPoint correction = QPoint (ui->frame->lineWidth (), ui->frame->lineWidth ());
|
|
||||||
move (item.screenPos - correction);
|
|
||||||
QRect screenRect = desktop->screenGeometry (this);
|
|
||||||
int minY = screenRect.bottom () - height ();
|
|
||||||
if (y () > minY)
|
|
||||||
{
|
|
||||||
move (x (), minY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
|
|
||||||
QRect screenRect = desktop->availableGeometry (this);
|
|
||||||
Q_ASSERT (screenRect.isValid ());
|
|
||||||
QPoint newPos (screenRect.width () - width (), screenRect.height () - height ());
|
|
||||||
move (newPos);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
#ifndef RESULTDIALOG_H
|
|
||||||
#define RESULTDIALOG_H
|
|
||||||
|
|
||||||
#include <QDialog>
|
|
||||||
|
|
||||||
#include "ProcessingItem.h"
|
|
||||||
|
|
||||||
namespace Ui {
|
|
||||||
class ResultDialog;
|
|
||||||
}
|
|
||||||
|
|
||||||
class ResultDialog : public QDialog
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit ResultDialog(QWidget *parent = 0);
|
|
||||||
~ResultDialog();
|
|
||||||
|
|
||||||
public:
|
|
||||||
bool eventFilter (QObject *object, QEvent *event);
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void showResult (ProcessingItem item);
|
|
||||||
|
|
||||||
private:
|
|
||||||
Ui::ResultDialog *ui;
|
|
||||||
bool isShowAtCapturePos_;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // RESULTDIALOG_H
|
|
@ -1,98 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<ui version="4.0">
|
|
||||||
<class>ResultDialog</class>
|
|
||||||
<widget class="QDialog" name="ResultDialog">
|
|
||||||
<property name="geometry">
|
|
||||||
<rect>
|
|
||||||
<x>0</x>
|
|
||||||
<y>0</y>
|
|
||||||
<width>260</width>
|
|
||||||
<height>222</height>
|
|
||||||
</rect>
|
|
||||||
</property>
|
|
||||||
<property name="windowTitle">
|
|
||||||
<string>Результат</string>
|
|
||||||
</property>
|
|
||||||
<layout class="QGridLayout" name="gridLayout">
|
|
||||||
<property name="margin">
|
|
||||||
<number>0</number>
|
|
||||||
</property>
|
|
||||||
<property name="spacing">
|
|
||||||
<number>0</number>
|
|
||||||
</property>
|
|
||||||
<item row="0" column="0">
|
|
||||||
<widget class="QFrame" name="frame">
|
|
||||||
<property name="frameShape">
|
|
||||||
<enum>QFrame::StyledPanel</enum>
|
|
||||||
</property>
|
|
||||||
<property name="frameShadow">
|
|
||||||
<enum>QFrame::Plain</enum>
|
|
||||||
</property>
|
|
||||||
<layout class="QGridLayout" name="gridLayout_2">
|
|
||||||
<property name="margin">
|
|
||||||
<number>0</number>
|
|
||||||
</property>
|
|
||||||
<property name="spacing">
|
|
||||||
<number>0</number>
|
|
||||||
</property>
|
|
||||||
<item row="0" column="0">
|
|
||||||
<widget class="QLabel" name="sourceLabel">
|
|
||||||
<property name="text">
|
|
||||||
<string/>
|
|
||||||
</property>
|
|
||||||
<property name="alignment">
|
|
||||||
<set>Qt::AlignCenter</set>
|
|
||||||
</property>
|
|
||||||
<property name="wordWrap">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="0">
|
|
||||||
<widget class="Line" name="line_2">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="2" column="0">
|
|
||||||
<widget class="QLabel" name="recognizeLabel">
|
|
||||||
<property name="text">
|
|
||||||
<string/>
|
|
||||||
</property>
|
|
||||||
<property name="alignment">
|
|
||||||
<set>Qt::AlignCenter</set>
|
|
||||||
</property>
|
|
||||||
<property name="wordWrap">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="3" column="0">
|
|
||||||
<widget class="Line" name="line">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="4" column="0">
|
|
||||||
<widget class="QLabel" name="translateLabel">
|
|
||||||
<property name="text">
|
|
||||||
<string/>
|
|
||||||
</property>
|
|
||||||
<property name="alignment">
|
|
||||||
<set>Qt::AlignCenter</set>
|
|
||||||
</property>
|
|
||||||
<property name="wordWrap">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
<resources/>
|
|
||||||
<connections/>
|
|
||||||
</ui>
|
|
@ -1,65 +0,0 @@
|
|||||||
#-------------------------------------------------
|
|
||||||
#
|
|
||||||
# Project created by QtCreator 2013-11-22T12:00:23
|
|
||||||
#
|
|
||||||
#-------------------------------------------------
|
|
||||||
|
|
||||||
QT += core gui network webkitwidgets
|
|
||||||
|
|
||||||
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
|
|
||||||
|
|
||||||
TARGET = ScreenTranslator
|
|
||||||
TEMPLATE = app
|
|
||||||
|
|
||||||
INCLUDEPATH += ../include
|
|
||||||
|
|
||||||
LIBS += -L../bin -ltesseract -llept -ltiff -lgif -ljpeg -lz
|
|
||||||
LIBS += -lWs2_32
|
|
||||||
|
|
||||||
SOURCES += main.cpp\
|
|
||||||
Manager.cpp \
|
|
||||||
SettingsEditor.cpp \
|
|
||||||
SelectionDialog.cpp \
|
|
||||||
GlobalActionHelper.cpp \
|
|
||||||
Recognizer.cpp \
|
|
||||||
Translator.cpp \
|
|
||||||
ResultDialog.cpp \
|
|
||||||
ProcessingItem.cpp \
|
|
||||||
ImageProcessing.cpp \
|
|
||||||
LanguageHelper.cpp \
|
|
||||||
GoogleWebTranslator.cpp
|
|
||||||
|
|
||||||
HEADERS += \
|
|
||||||
Manager.h \
|
|
||||||
SettingsEditor.h \
|
|
||||||
SelectionDialog.h \
|
|
||||||
GlobalActionHelper.h \
|
|
||||||
Recognizer.h \
|
|
||||||
Translator.h \
|
|
||||||
Settings.h \
|
|
||||||
ProcessingItem.h \
|
|
||||||
ResultDialog.h \
|
|
||||||
ImageProcessing.h \
|
|
||||||
LanguageHelper.h \
|
|
||||||
GoogleWebTranslator.h
|
|
||||||
|
|
||||||
FORMS += \
|
|
||||||
SettingsEditor.ui \
|
|
||||||
SelectionDialog.ui \
|
|
||||||
ResultDialog.ui
|
|
||||||
|
|
||||||
RESOURCES += \
|
|
||||||
Recources.qrc
|
|
||||||
|
|
||||||
TRANSLATIONS += \
|
|
||||||
translations/translation_en.ts \
|
|
||||||
translations/translation_ru.ts
|
|
||||||
|
|
||||||
win32{
|
|
||||||
RC_FILE = app.rc
|
|
||||||
}
|
|
||||||
|
|
||||||
OTHER_FILES += \
|
|
||||||
app.rc \
|
|
||||||
images/icon.ico \
|
|
||||||
README.md
|
|
@ -1,165 +0,0 @@
|
|||||||
#include "SelectionDialog.h"
|
|
||||||
#include "ui_SelectionDialog.h"
|
|
||||||
#include "LanguageHelper.h"
|
|
||||||
|
|
||||||
#include <QMouseEvent>
|
|
||||||
#include <QPainter>
|
|
||||||
#include <QDebug>
|
|
||||||
#include <QMenu>
|
|
||||||
|
|
||||||
SelectionDialog::SelectionDialog(const LanguageHelper &dictionary, QWidget *parent) :
|
|
||||||
QDialog(parent),
|
|
||||||
ui(new Ui::SelectionDialog), dictionary_ (dictionary),
|
|
||||||
languageMenu_ (new QMenu)
|
|
||||||
{
|
|
||||||
ui->setupUi(this);
|
|
||||||
setWindowFlags (Qt::FramelessWindowHint | Qt::NoDropShadowWindowHint |
|
|
||||||
Qt::WindowStaysOnTopHint);
|
|
||||||
|
|
||||||
ui->label->setAutoFillBackground(false);
|
|
||||||
ui->label->installEventFilter (this);
|
|
||||||
|
|
||||||
updateMenu ();
|
|
||||||
}
|
|
||||||
|
|
||||||
SelectionDialog::~SelectionDialog()
|
|
||||||
{
|
|
||||||
delete ui;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SelectionDialog::updateMenu()
|
|
||||||
{
|
|
||||||
Q_CHECK_PTR (languageMenu_);
|
|
||||||
languageMenu_->clear ();
|
|
||||||
QStringList languages = dictionary_.availableOcrLanguagesUi ();
|
|
||||||
if (languages.isEmpty ())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const int max = 10;
|
|
||||||
|
|
||||||
if (languages.size () <= max)
|
|
||||||
{
|
|
||||||
foreach (const QString& language, languages)
|
|
||||||
{
|
|
||||||
languageMenu_->addAction (language);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
int subIndex = max;
|
|
||||||
QMenu* subMenu = NULL;
|
|
||||||
QString prevLetter;
|
|
||||||
foreach (const QString& language, languages)
|
|
||||||
{
|
|
||||||
QString curLetter = language.left (1);
|
|
||||||
if (++subIndex >= max && prevLetter != curLetter)
|
|
||||||
{
|
|
||||||
if (subMenu != NULL)
|
|
||||||
{
|
|
||||||
subMenu->setTitle (subMenu->title () + " - " + prevLetter);
|
|
||||||
}
|
|
||||||
subMenu = languageMenu_->addMenu (curLetter);
|
|
||||||
subIndex = 0;
|
|
||||||
}
|
|
||||||
prevLetter = curLetter;
|
|
||||||
subMenu->addAction (language);
|
|
||||||
}
|
|
||||||
subMenu->setTitle (subMenu->title () + " - " + prevLetter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SelectionDialog::eventFilter(QObject* object, QEvent* event)
|
|
||||||
{
|
|
||||||
if (object != ui->label)
|
|
||||||
{
|
|
||||||
return QDialog::eventFilter (object, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event->type () == QEvent::Show)
|
|
||||||
{
|
|
||||||
startSelectPos_ = currentSelectPos_ = QPoint ();
|
|
||||||
}
|
|
||||||
else if (event->type () == QEvent::MouseButtonPress)
|
|
||||||
{
|
|
||||||
QMouseEvent* mouseEvent = static_cast <QMouseEvent*> (event);
|
|
||||||
if ((mouseEvent->button () == Qt::LeftButton ||
|
|
||||||
mouseEvent->button () == Qt::RightButton) && startSelectPos_.isNull ())
|
|
||||||
{
|
|
||||||
startSelectPos_ = mouseEvent->pos ();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (event->type () == QEvent::MouseMove)
|
|
||||||
{
|
|
||||||
QMouseEvent* mouseEvent = static_cast <QMouseEvent*> (event);
|
|
||||||
if ((mouseEvent->buttons () & Qt::LeftButton ||
|
|
||||||
mouseEvent->buttons () & Qt::RightButton) && !startSelectPos_.isNull ())
|
|
||||||
{
|
|
||||||
currentSelectPos_ = mouseEvent->pos ();
|
|
||||||
ui->label->repaint ();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (event->type () == QEvent::Paint)
|
|
||||||
{
|
|
||||||
QRect selection = QRect (startSelectPos_, currentSelectPos_).normalized ();
|
|
||||||
if (selection.isValid ())
|
|
||||||
{
|
|
||||||
QPainter painter (ui->label);
|
|
||||||
painter.setPen (Qt::red);
|
|
||||||
painter.drawRect (selection);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (event->type () == QEvent::MouseButtonRelease)
|
|
||||||
{
|
|
||||||
QMouseEvent* mouseEvent = static_cast <QMouseEvent*> (event);
|
|
||||||
if (mouseEvent->button () == Qt::LeftButton ||
|
|
||||||
mouseEvent->button () == Qt::RightButton)
|
|
||||||
{
|
|
||||||
if (startSelectPos_.isNull () || currentPixmap_.isNull ())
|
|
||||||
{
|
|
||||||
return QDialog::eventFilter (object, event);
|
|
||||||
}
|
|
||||||
QPoint endPos = mouseEvent->pos ();
|
|
||||||
QRect selection = QRect (startSelectPos_, endPos).normalized ();
|
|
||||||
QPixmap selectedPixmap = currentPixmap_.copy (selection);
|
|
||||||
if (!selectedPixmap.isNull ())
|
|
||||||
{
|
|
||||||
ProcessingItem item;
|
|
||||||
item.source = selectedPixmap;
|
|
||||||
item.screenPos = selection.topLeft ();
|
|
||||||
|
|
||||||
if (mouseEvent->button () == Qt::RightButton &&
|
|
||||||
!languageMenu_->children ().isEmpty ())
|
|
||||||
{
|
|
||||||
QAction* action = languageMenu_->exec (QCursor::pos ());
|
|
||||||
if (action == NULL)
|
|
||||||
{
|
|
||||||
reject ();
|
|
||||||
return QDialog::eventFilter (object, event);
|
|
||||||
}
|
|
||||||
item.ocrLanguage = dictionary_.ocrUiToCode (action->text ());
|
|
||||||
Q_ASSERT (!item.ocrLanguage.isEmpty ());
|
|
||||||
item.sourceLanguage = dictionary_.translateForOcrCode (item.ocrLanguage);
|
|
||||||
Q_ASSERT (!item.sourceLanguage.isEmpty ());
|
|
||||||
}
|
|
||||||
emit selected (item);
|
|
||||||
accept ();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return QDialog::eventFilter (object, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SelectionDialog::setPixmap(QPixmap pixmap)
|
|
||||||
{
|
|
||||||
Q_ASSERT (!pixmap.isNull ());
|
|
||||||
currentPixmap_ = pixmap;
|
|
||||||
QPalette palette = this->palette ();
|
|
||||||
palette.setBrush (this->backgroundRole (), pixmap);
|
|
||||||
this->setPalette (palette);
|
|
||||||
this->resize (pixmap.size ());
|
|
||||||
|
|
||||||
show ();
|
|
||||||
setFocus ();
|
|
||||||
}
|
|
@ -1,41 +0,0 @@
|
|||||||
#ifndef SELECTIONDIALOG_H
|
|
||||||
#define SELECTIONDIALOG_H
|
|
||||||
|
|
||||||
#include <QDialog>
|
|
||||||
#include <QPixmap>
|
|
||||||
#include <QMenu>
|
|
||||||
|
|
||||||
#include "ProcessingItem.h"
|
|
||||||
|
|
||||||
namespace Ui {
|
|
||||||
class SelectionDialog;
|
|
||||||
}
|
|
||||||
class LanguageHelper;
|
|
||||||
|
|
||||||
class SelectionDialog : public QDialog
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit SelectionDialog(const LanguageHelper& dictionary, QWidget *parent = 0);
|
|
||||||
~SelectionDialog();
|
|
||||||
|
|
||||||
bool eventFilter (QObject *object, QEvent *event);
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void selected (ProcessingItem pixmap);
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void setPixmap (QPixmap pixmap);
|
|
||||||
void updateMenu ();
|
|
||||||
|
|
||||||
private:
|
|
||||||
Ui::SelectionDialog *ui;
|
|
||||||
const LanguageHelper& dictionary_;
|
|
||||||
QPoint startSelectPos_;
|
|
||||||
QPoint currentSelectPos_;
|
|
||||||
QPixmap currentPixmap_;
|
|
||||||
QMenu* languageMenu_;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // SELECTIONDIALOG_H
|
|
@ -1,43 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<ui version="4.0">
|
|
||||||
<class>SelectionDialog</class>
|
|
||||||
<widget class="QDialog" name="SelectionDialog">
|
|
||||||
<property name="geometry">
|
|
||||||
<rect>
|
|
||||||
<x>0</x>
|
|
||||||
<y>0</y>
|
|
||||||
<width>400</width>
|
|
||||||
<height>300</height>
|
|
||||||
</rect>
|
|
||||||
</property>
|
|
||||||
<property name="windowTitle">
|
|
||||||
<string>Dialog</string>
|
|
||||||
</property>
|
|
||||||
<layout class="QGridLayout" name="gridLayout">
|
|
||||||
<property name="leftMargin">
|
|
||||||
<number>0</number>
|
|
||||||
</property>
|
|
||||||
<property name="topMargin">
|
|
||||||
<number>0</number>
|
|
||||||
</property>
|
|
||||||
<property name="rightMargin">
|
|
||||||
<number>0</number>
|
|
||||||
</property>
|
|
||||||
<property name="bottomMargin">
|
|
||||||
<number>0</number>
|
|
||||||
</property>
|
|
||||||
<item row="0" column="0">
|
|
||||||
<widget class="QLabel" name="label">
|
|
||||||
<property name="cursor">
|
|
||||||
<cursorShape>CrossCursor</cursorShape>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string/>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
<resources/>
|
|
||||||
<connections/>
|
|
||||||
</ui>
|
|
50
Settings.h
@ -1,50 +0,0 @@
|
|||||||
#ifndef SETTINGS_H
|
|
||||||
#define SETTINGS_H
|
|
||||||
|
|
||||||
#include <QString>
|
|
||||||
|
|
||||||
namespace settings_names
|
|
||||||
{
|
|
||||||
//! UI
|
|
||||||
const QString guiGroup = "GUI";
|
|
||||||
const QString geometry = "geometry";
|
|
||||||
const QString captureHotkey = "captureHotkey";
|
|
||||||
const QString repeatHotkey = "repeatHotkey";
|
|
||||||
const QString clipboardHotkey = "clipboardHotkey";
|
|
||||||
const QString resultShowType = "resultShowType";
|
|
||||||
|
|
||||||
//! Recognition
|
|
||||||
const QString recogntionGroup = "Recognition";
|
|
||||||
const QString tessDataPlace = "tessdata_dir";
|
|
||||||
const QString ocrLanguage = "language";
|
|
||||||
const QString imageScale = "image_scale";
|
|
||||||
|
|
||||||
//! Translation
|
|
||||||
const QString translationGroup = "Translation";
|
|
||||||
const QString sourceLanguage = "source_language";
|
|
||||||
const QString translationLanguage = "translation_language";
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace settings_values
|
|
||||||
{
|
|
||||||
const QString appName = "ScreenTranslator";
|
|
||||||
const QString companyName = "Gres";
|
|
||||||
|
|
||||||
//! UI
|
|
||||||
const QString captureHotkey = "Ctrl+Alt+Z";
|
|
||||||
const QString repeatHotkey = "Ctrl+Alt+X";
|
|
||||||
const QString clipboardHotkey = "Ctrl+Alt+C";
|
|
||||||
const QString resultShowType = "1";//dialog
|
|
||||||
|
|
||||||
//! Recognition
|
|
||||||
const QString tessDataPlace = "./";
|
|
||||||
const QString ocrLanguage = "eng";
|
|
||||||
const int imageScale = 5;
|
|
||||||
|
|
||||||
//! Translation
|
|
||||||
const QString sourceLanguage = "auto";
|
|
||||||
const QString translationLanguage = "ru";
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // SETTINGS_H
|
|
@ -1,133 +0,0 @@
|
|||||||
#include "SettingsEditor.h"
|
|
||||||
#include "ui_SettingsEditor.h"
|
|
||||||
#include "LanguageHelper.h"
|
|
||||||
|
|
||||||
#include <QSettings>
|
|
||||||
#include <QFileDialog>
|
|
||||||
|
|
||||||
#include "Settings.h"
|
|
||||||
|
|
||||||
SettingsEditor::SettingsEditor(const LanguageHelper &dictionary, QWidget *parent) :
|
|
||||||
QDialog(parent),
|
|
||||||
ui(new Ui::SettingsEditor), dictionary_ (dictionary),
|
|
||||||
buttonGroup_ (new QButtonGroup (this))
|
|
||||||
{
|
|
||||||
ui->setupUi(this);
|
|
||||||
|
|
||||||
buttonGroup_->addButton (ui->trayRadio, 0);
|
|
||||||
buttonGroup_->addButton (ui->dialogRadio, 1);
|
|
||||||
|
|
||||||
connect (ui->tessdataButton, SIGNAL (clicked ()), SLOT (openTessdataDialog ()));
|
|
||||||
connect (ui->tessdataEdit, SIGNAL (textChanged (const QString&)),
|
|
||||||
SLOT (initOcrLangCombo (const QString&)));
|
|
||||||
|
|
||||||
ui->translateLangCombo->addItems (dictionary_.translateLanguagesUi ());
|
|
||||||
loadSettings ();
|
|
||||||
loadState ();
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingsEditor::~SettingsEditor()
|
|
||||||
{
|
|
||||||
saveState ();
|
|
||||||
delete ui;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SettingsEditor::done(int result)
|
|
||||||
{
|
|
||||||
if (result == QDialog::Accepted)
|
|
||||||
{
|
|
||||||
saveSettings ();
|
|
||||||
emit settingsEdited ();
|
|
||||||
}
|
|
||||||
QDialog::done (result);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SettingsEditor::saveSettings() const
|
|
||||||
{
|
|
||||||
QSettings settings;
|
|
||||||
settings.beginGroup (settings_names::guiGroup);
|
|
||||||
settings.setValue (settings_names::captureHotkey, ui->captureEdit->text ());
|
|
||||||
settings.setValue (settings_names::repeatHotkey, ui->repeatEdit->text ());
|
|
||||||
settings.setValue (settings_names::clipboardHotkey, ui->clipboardEdit->text ());
|
|
||||||
settings.setValue (settings_names::resultShowType, buttonGroup_->checkedId ());
|
|
||||||
settings.endGroup ();
|
|
||||||
|
|
||||||
|
|
||||||
settings.beginGroup (settings_names::recogntionGroup);
|
|
||||||
settings.setValue (settings_names::tessDataPlace, ui->tessdataEdit->text ());
|
|
||||||
QString ocrLanguage = dictionary_.ocrUiToCode (ui->ocrLangCombo->currentText ());
|
|
||||||
settings.setValue (settings_names::ocrLanguage, ocrLanguage);
|
|
||||||
settings.setValue (settings_names::imageScale, ui->imageScaleSpin->value ());
|
|
||||||
settings.endGroup ();
|
|
||||||
|
|
||||||
|
|
||||||
settings.beginGroup (settings_names::translationGroup);
|
|
||||||
QString trLanguage = dictionary_.translateUiToCode (ui->translateLangCombo->currentText ());
|
|
||||||
settings.setValue (settings_names::translationLanguage, trLanguage);
|
|
||||||
QString sourceLanguage = dictionary_.translateForOcrCode (ocrLanguage);
|
|
||||||
settings.setValue (settings_names::sourceLanguage, sourceLanguage);
|
|
||||||
settings.endGroup ();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SettingsEditor::openTessdataDialog()
|
|
||||||
{
|
|
||||||
QString path = QFileDialog::getExistingDirectory (this, tr ("Путь к tessdata"));
|
|
||||||
if (path.isEmpty ())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
QDir dir (path);
|
|
||||||
if (dir.dirName () == QString ("tessdata"))
|
|
||||||
{
|
|
||||||
dir.cdUp ();
|
|
||||||
}
|
|
||||||
ui->tessdataEdit->setText (dir.path ());
|
|
||||||
}
|
|
||||||
|
|
||||||
void SettingsEditor::loadSettings()
|
|
||||||
{
|
|
||||||
#define GET(FIELD) settings.value (settings_names::FIELD, settings_values::FIELD)
|
|
||||||
QSettings settings;
|
|
||||||
|
|
||||||
settings.beginGroup (settings_names::guiGroup);
|
|
||||||
ui->captureEdit->setText (GET(captureHotkey).toString ());
|
|
||||||
ui->repeatEdit->setText (GET(repeatHotkey).toString ());
|
|
||||||
ui->clipboardEdit->setText (GET(clipboardHotkey).toString ());
|
|
||||||
QAbstractButton* button = buttonGroup_->button (GET(resultShowType).toInt ());
|
|
||||||
Q_CHECK_PTR (button);
|
|
||||||
button->setChecked (true);
|
|
||||||
settings.endGroup ();
|
|
||||||
|
|
||||||
settings.beginGroup (settings_names::recogntionGroup);
|
|
||||||
ui->tessdataEdit->setText (GET(tessDataPlace).toString ());
|
|
||||||
QString ocrLanguage = dictionary_.ocrCodeToUi (GET(ocrLanguage).toString ());
|
|
||||||
ui->ocrLangCombo->setCurrentText (ocrLanguage);
|
|
||||||
ui->imageScaleSpin->setValue (GET(imageScale).toInt ());
|
|
||||||
settings.endGroup ();
|
|
||||||
|
|
||||||
settings.beginGroup (settings_names::translationGroup);
|
|
||||||
QString trLanguage = dictionary_.translateCodeToUi (GET(translationLanguage).toString ());
|
|
||||||
ui->translateLangCombo->setCurrentText (trLanguage);
|
|
||||||
settings.endGroup ();
|
|
||||||
#undef GET
|
|
||||||
}
|
|
||||||
|
|
||||||
void SettingsEditor::saveState() const
|
|
||||||
{
|
|
||||||
QSettings settings;
|
|
||||||
settings.beginGroup (settings_names::guiGroup);
|
|
||||||
settings.setValue (objectName () + "_" + settings_names::geometry, saveGeometry ());
|
|
||||||
}
|
|
||||||
|
|
||||||
void SettingsEditor::loadState()
|
|
||||||
{
|
|
||||||
QSettings settings;
|
|
||||||
settings.beginGroup (settings_names::guiGroup);
|
|
||||||
restoreGeometry (settings.value (objectName () + "_" + settings_names::geometry).toByteArray ());
|
|
||||||
}
|
|
||||||
|
|
||||||
void SettingsEditor::initOcrLangCombo(const QString &path)
|
|
||||||
{
|
|
||||||
ui->ocrLangCombo->clear ();
|
|
||||||
ui->ocrLangCombo->addItems (dictionary_.availableOcrLanguagesUi (path));
|
|
||||||
}
|
|
@ -1,43 +0,0 @@
|
|||||||
#ifndef SETTINGSEDITOR_H
|
|
||||||
#define SETTINGSEDITOR_H
|
|
||||||
|
|
||||||
#include <QDialog>
|
|
||||||
#include <QButtonGroup>
|
|
||||||
#include <QMap>
|
|
||||||
|
|
||||||
namespace Ui {
|
|
||||||
class SettingsEditor;
|
|
||||||
}
|
|
||||||
class LanguageHelper;
|
|
||||||
|
|
||||||
class SettingsEditor : public QDialog
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit SettingsEditor(const LanguageHelper& dictionary, QWidget *parent = 0);
|
|
||||||
~SettingsEditor();
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void settingsEdited ();
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void done (int result);
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void saveSettings () const;
|
|
||||||
void openTessdataDialog ();
|
|
||||||
void initOcrLangCombo (const QString& path);
|
|
||||||
|
|
||||||
private:
|
|
||||||
void loadSettings ();
|
|
||||||
void saveState () const;
|
|
||||||
void loadState ();
|
|
||||||
|
|
||||||
private:
|
|
||||||
Ui::SettingsEditor *ui;
|
|
||||||
const LanguageHelper& dictionary_;
|
|
||||||
QButtonGroup* buttonGroup_;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // SETTINGSEDITOR_H
|
|
@ -1,271 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<ui version="4.0">
|
|
||||||
<class>SettingsEditor</class>
|
|
||||||
<widget class="QDialog" name="SettingsEditor">
|
|
||||||
<property name="geometry">
|
|
||||||
<rect>
|
|
||||||
<x>0</x>
|
|
||||||
<y>0</y>
|
|
||||||
<width>435</width>
|
|
||||||
<height>221</height>
|
|
||||||
</rect>
|
|
||||||
</property>
|
|
||||||
<property name="windowTitle">
|
|
||||||
<string>Настройки</string>
|
|
||||||
</property>
|
|
||||||
<layout class="QGridLayout" name="gridLayout_5">
|
|
||||||
<item row="0" column="0">
|
|
||||||
<widget class="QGroupBox" name="groupBox">
|
|
||||||
<property name="title">
|
|
||||||
<string>Горячие клавиши</string>
|
|
||||||
</property>
|
|
||||||
<layout class="QGridLayout" name="gridLayout">
|
|
||||||
<item row="0" column="0">
|
|
||||||
<widget class="QLabel" name="label">
|
|
||||||
<property name="toolTip">
|
|
||||||
<string><html><head/><body><p>Сочетание клавиш для перехода в режим захвата.</p></body></html></string>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Захватить</string>
|
|
||||||
</property>
|
|
||||||
<property name="buddy">
|
|
||||||
<cstring>captureEdit</cstring>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="0" column="1" colspan="2">
|
|
||||||
<widget class="QLineEdit" name="captureEdit"/>
|
|
||||||
</item>
|
|
||||||
<item row="2" column="0">
|
|
||||||
<widget class="QLabel" name="label_7">
|
|
||||||
<property name="toolTip">
|
|
||||||
<string><html><head/><body><p>Сочетание клавиш для перехода в режим захвата.</p></body></html></string>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Скопировать</string>
|
|
||||||
</property>
|
|
||||||
<property name="buddy">
|
|
||||||
<cstring>captureEdit</cstring>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="2" column="1" colspan="2">
|
|
||||||
<widget class="QLineEdit" name="clipboardEdit"/>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="0">
|
|
||||||
<widget class="QLabel" name="label_3">
|
|
||||||
<property name="toolTip">
|
|
||||||
<string><html><head/><body><p>Сочетание клавиш для перехода в режим захвата.</p></body></html></string>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Повторить</string>
|
|
||||||
</property>
|
|
||||||
<property name="buddy">
|
|
||||||
<cstring>captureEdit</cstring>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="1" colspan="2">
|
|
||||||
<widget class="QLineEdit" name="repeatEdit"/>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="0" column="1">
|
|
||||||
<widget class="QGroupBox" name="groupBox_2">
|
|
||||||
<property name="title">
|
|
||||||
<string>Распознавание</string>
|
|
||||||
</property>
|
|
||||||
<layout class="QGridLayout" name="gridLayout_3">
|
|
||||||
<item row="0" column="0">
|
|
||||||
<widget class="QLabel" name="label_2">
|
|
||||||
<property name="toolTip">
|
|
||||||
<string><html><head/><body><p>Необходимо для распознавания.</p><p>Скачивается отсюда: <a href="https://code.google.com/p/tesseract-ocr/downloads/list"><span style=" text-decoration: underline; color:#0000ff;">https://code.google.com/p/tesseract-ocr/downloads/list</span></a></p></body></html></string>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Путь к tessdata</string>
|
|
||||||
</property>
|
|
||||||
<property name="buddy">
|
|
||||||
<cstring>tessdataEdit</cstring>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="0" column="1">
|
|
||||||
<widget class="QLineEdit" name="tessdataEdit"/>
|
|
||||||
</item>
|
|
||||||
<item row="0" column="2">
|
|
||||||
<widget class="QToolButton" name="tessdataButton">
|
|
||||||
<property name="text">
|
|
||||||
<string>...</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="0">
|
|
||||||
<widget class="QLabel" name="label_4">
|
|
||||||
<property name="toolTip">
|
|
||||||
<string><html><head/><body><p>Заполняется на основании содержания tessdata</p></body></html></string>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Язык распознавания</string>
|
|
||||||
</property>
|
|
||||||
<property name="buddy">
|
|
||||||
<cstring>ocrLangCombo</cstring>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="2" column="0">
|
|
||||||
<widget class="QLabel" name="label_5">
|
|
||||||
<property name="toolTip">
|
|
||||||
<string><html><head/><body><p>Масштабирование изображения для улучшения распознания. Больше - лучше (до определенных пределов), но медленнее.</p></body></html></string>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Увеличение масштаба</string>
|
|
||||||
</property>
|
|
||||||
<property name="buddy">
|
|
||||||
<cstring>imageScaleSpin</cstring>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="1" colspan="2">
|
|
||||||
<widget class="QComboBox" name="ocrLangCombo"/>
|
|
||||||
</item>
|
|
||||||
<item row="2" column="1" colspan="2">
|
|
||||||
<widget class="QSpinBox" name="imageScaleSpin"/>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="0" column="2">
|
|
||||||
<spacer name="horizontalSpacer">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeHint" stdset="0">
|
|
||||||
<size>
|
|
||||||
<width>0</width>
|
|
||||||
<height>20</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</spacer>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="0">
|
|
||||||
<widget class="QGroupBox" name="resultGroup">
|
|
||||||
<property name="title">
|
|
||||||
<string>Вывод результата</string>
|
|
||||||
</property>
|
|
||||||
<layout class="QGridLayout" name="gridLayout_4">
|
|
||||||
<item row="0" column="1">
|
|
||||||
<widget class="QRadioButton" name="trayRadio">
|
|
||||||
<property name="text">
|
|
||||||
<string>Трей</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="0" column="2">
|
|
||||||
<widget class="QRadioButton" name="dialogRadio">
|
|
||||||
<property name="text">
|
|
||||||
<string>Окно</string>
|
|
||||||
</property>
|
|
||||||
<property name="checked">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="1">
|
|
||||||
<widget class="QGroupBox" name="groupBox_3">
|
|
||||||
<property name="title">
|
|
||||||
<string>Перевод</string>
|
|
||||||
</property>
|
|
||||||
<layout class="QGridLayout" name="gridLayout_2">
|
|
||||||
<item row="0" column="0">
|
|
||||||
<widget class="QLabel" name="label_6">
|
|
||||||
<property name="toolTip">
|
|
||||||
<string><html><head/><body><p>Язык, на который осуществляется перевод.</p></body></html></string>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Язык результата</string>
|
|
||||||
</property>
|
|
||||||
<property name="buddy">
|
|
||||||
<cstring>translateLangCombo</cstring>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="0" column="1">
|
|
||||||
<widget class="QComboBox" name="translateLangCombo"/>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="2" column="0">
|
|
||||||
<spacer name="verticalSpacer">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Vertical</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeHint" stdset="0">
|
|
||||||
<size>
|
|
||||||
<width>20</width>
|
|
||||||
<height>1</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</spacer>
|
|
||||||
</item>
|
|
||||||
<item row="3" column="1">
|
|
||||||
<widget class="QDialogButtonBox" name="buttonBox">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
<property name="standardButtons">
|
|
||||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
<tabstops>
|
|
||||||
<tabstop>captureEdit</tabstop>
|
|
||||||
<tabstop>translateLangCombo</tabstop>
|
|
||||||
<tabstop>tessdataEdit</tabstop>
|
|
||||||
<tabstop>tessdataButton</tabstop>
|
|
||||||
<tabstop>ocrLangCombo</tabstop>
|
|
||||||
<tabstop>imageScaleSpin</tabstop>
|
|
||||||
<tabstop>buttonBox</tabstop>
|
|
||||||
</tabstops>
|
|
||||||
<resources/>
|
|
||||||
<connections>
|
|
||||||
<connection>
|
|
||||||
<sender>buttonBox</sender>
|
|
||||||
<signal>accepted()</signal>
|
|
||||||
<receiver>SettingsEditor</receiver>
|
|
||||||
<slot>accept()</slot>
|
|
||||||
<hints>
|
|
||||||
<hint type="sourcelabel">
|
|
||||||
<x>248</x>
|
|
||||||
<y>254</y>
|
|
||||||
</hint>
|
|
||||||
<hint type="destinationlabel">
|
|
||||||
<x>157</x>
|
|
||||||
<y>274</y>
|
|
||||||
</hint>
|
|
||||||
</hints>
|
|
||||||
</connection>
|
|
||||||
<connection>
|
|
||||||
<sender>buttonBox</sender>
|
|
||||||
<signal>rejected()</signal>
|
|
||||||
<receiver>SettingsEditor</receiver>
|
|
||||||
<slot>reject()</slot>
|
|
||||||
<hints>
|
|
||||||
<hint type="sourcelabel">
|
|
||||||
<x>316</x>
|
|
||||||
<y>260</y>
|
|
||||||
</hint>
|
|
||||||
<hint type="destinationlabel">
|
|
||||||
<x>286</x>
|
|
||||||
<y>274</y>
|
|
||||||
</hint>
|
|
||||||
</hints>
|
|
||||||
</connection>
|
|
||||||
</connections>
|
|
||||||
</ui>
|
|
120
Translator.cpp
@ -1,120 +0,0 @@
|
|||||||
#include "Translator.h"
|
|
||||||
|
|
||||||
#include <QDebug>
|
|
||||||
#include <QNetworkRequest>
|
|
||||||
#include <QNetworkReply>
|
|
||||||
#include <QJsonDocument>
|
|
||||||
#include <QJsonArray>
|
|
||||||
#include <QJsonParseError>
|
|
||||||
#include <QSettings>
|
|
||||||
|
|
||||||
#include "Settings.h"
|
|
||||||
#include "GoogleWebTranslator.h"
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
const QString translateBaseUrl = "http://translate.google.com/translate_a/"
|
|
||||||
"t?client=t&text=%1&sl=%2&tl=%3";
|
|
||||||
}
|
|
||||||
|
|
||||||
Translator::Translator(QObject *parent) :
|
|
||||||
QObject(parent),
|
|
||||||
network_ (this),
|
|
||||||
useAlternativeTranslation_ (false)
|
|
||||||
{
|
|
||||||
connect (&network_, SIGNAL (finished (QNetworkReply*)),
|
|
||||||
SLOT (replyFinished (QNetworkReply*)));
|
|
||||||
|
|
||||||
GoogleWebTranslator* googleWeb = new GoogleWebTranslator;
|
|
||||||
connect (this, SIGNAL (translateAlternative (ProcessingItem)),
|
|
||||||
googleWeb, SLOT (translate (ProcessingItem)));
|
|
||||||
connect (googleWeb, SIGNAL (translated (ProcessingItem, bool)),
|
|
||||||
this, SLOT (translatedAlternative(ProcessingItem, bool)));
|
|
||||||
connect (googleWeb, SIGNAL (error (QString)), this, SIGNAL (error (QString)));
|
|
||||||
|
|
||||||
applySettings ();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Translator::applySettings()
|
|
||||||
{
|
|
||||||
QSettings settings;
|
|
||||||
settings.beginGroup (settings_names::translationGroup);
|
|
||||||
translationLanguage_ = settings.value (settings_names::translationLanguage,
|
|
||||||
settings_values::translationLanguage).toString ();
|
|
||||||
sourceLanguage_ = settings.value (settings_names::sourceLanguage,
|
|
||||||
settings_values::sourceLanguage).toString ();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Translator::translate(ProcessingItem item)
|
|
||||||
{
|
|
||||||
if (useAlternativeTranslation_) {
|
|
||||||
emit translateAlternative(item);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Q_ASSERT (!item.recognized.isEmpty ());
|
|
||||||
QString sourceLanguage = item.sourceLanguage.isEmpty () ? sourceLanguage_ :
|
|
||||||
item.sourceLanguage;
|
|
||||||
if (translationLanguage_.isEmpty () || sourceLanguage.isEmpty ())
|
|
||||||
{
|
|
||||||
emit error (tr ("Неверные парметры для перевода."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
QUrl url (translateBaseUrl.arg (item.recognized, sourceLanguage, translationLanguage_));
|
|
||||||
QNetworkReply* reply = network_.get (QNetworkRequest (url));
|
|
||||||
items_.insert (reply, item);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Translator::translatedAlternative(ProcessingItem item, bool success)
|
|
||||||
{
|
|
||||||
if (success)
|
|
||||||
{
|
|
||||||
emit translated(item);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
emit error (tr ("Ошибка альтернативного перевода текста: %1").arg (item.recognized));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Translator::replyFinished(QNetworkReply* reply)
|
|
||||||
{
|
|
||||||
Q_ASSERT (items_.contains (reply));
|
|
||||||
ProcessingItem item = items_.take (reply);
|
|
||||||
Q_ASSERT (reply->isFinished ());
|
|
||||||
if (reply->error () != QNetworkReply::NoError)
|
|
||||||
{
|
|
||||||
useAlternativeTranslation_ = true;
|
|
||||||
emit translateAlternative(item);
|
|
||||||
reply->deleteLater ();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
QByteArray data = reply->readAll ();
|
|
||||||
reply->deleteLater ();
|
|
||||||
|
|
||||||
while (data.indexOf (",,") != -1)//make json valid
|
|
||||||
{
|
|
||||||
data.replace (",,", ",");
|
|
||||||
}
|
|
||||||
QJsonParseError parseError;
|
|
||||||
QJsonDocument document = QJsonDocument::fromJson (data, &parseError);
|
|
||||||
if (document.isEmpty ())
|
|
||||||
{
|
|
||||||
useAlternativeTranslation_ = true;
|
|
||||||
emit translateAlternative(item);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
QJsonArray answerArray = document.array ();
|
|
||||||
QJsonArray fullTranslation = answerArray.first ().toArray ();
|
|
||||||
QString translation = "";
|
|
||||||
foreach (QJsonValue part, fullTranslation)
|
|
||||||
{
|
|
||||||
QJsonArray partTranslation = part.toArray ();
|
|
||||||
if (partTranslation.isEmpty ())
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
translation += partTranslation.at (0).toString ();
|
|
||||||
}
|
|
||||||
item.translated = translation;
|
|
||||||
emit translated (item);
|
|
||||||
}
|
|
36
Translator.h
@ -1,36 +0,0 @@
|
|||||||
#ifndef TRANSLATOR_H
|
|
||||||
#define TRANSLATOR_H
|
|
||||||
|
|
||||||
#include <QNetworkAccessManager>
|
|
||||||
|
|
||||||
#include "ProcessingItem.h"
|
|
||||||
|
|
||||||
class Translator : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
explicit Translator(QObject *parent = 0);
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void translated (ProcessingItem item);
|
|
||||||
void translateAlternative (ProcessingItem item);
|
|
||||||
void error (QString text);
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void translate (ProcessingItem item);
|
|
||||||
void translatedAlternative (ProcessingItem item, bool success);
|
|
||||||
void applySettings ();
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void replyFinished (QNetworkReply* reply);
|
|
||||||
|
|
||||||
private:
|
|
||||||
QNetworkAccessManager network_;
|
|
||||||
QString translationLanguage_;
|
|
||||||
QString sourceLanguage_;
|
|
||||||
QHash<QNetworkReply*, ProcessingItem> items_;
|
|
||||||
bool useAlternativeTranslation_;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // TRANSLATOR_H
|
|
BIN
distr/Files.xlsx
@ -1,269 +0,0 @@
|
|||||||
; Script generated by the Inno Setup Script Wizard.
|
|
||||||
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
|
|
||||||
|
|
||||||
#define MyAppName "Screen Translator"
|
|
||||||
#define MyAppVersion "1.2.2"
|
|
||||||
#define MyAppPublisher "Gres"
|
|
||||||
#define MyAppURL "http://gres.biz/screen-translator/"
|
|
||||||
#define MyAppExeName "ScreenTranslator.exe"
|
|
||||||
#define MyAppDescription "Screen capture and translation tool"
|
|
||||||
|
|
||||||
[Setup]
|
|
||||||
; NOTE: The value of AppId uniquely identifies this application.
|
|
||||||
; Do not use the same AppId value in installers for other applications.
|
|
||||||
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
|
|
||||||
AppId={{016F399E-0EED-476C-AB00-8AD0FF5BFD77}
|
|
||||||
AppName={#MyAppName}
|
|
||||||
AppVersion={#MyAppVersion}
|
|
||||||
;AppVerName={#MyAppName} {#MyAppVersion}
|
|
||||||
AppPublisher={#MyAppPublisher}
|
|
||||||
AppPublisherURL={#MyAppURL}
|
|
||||||
AppSupportURL={#MyAppURL}
|
|
||||||
AppUpdatesURL={#MyAppURL}
|
|
||||||
DefaultDirName={pf}\{#MyAppName}
|
|
||||||
DefaultGroupName={#MyAppName}
|
|
||||||
AllowNoIcons=yes
|
|
||||||
PrivilegesRequired=admin
|
|
||||||
CloseApplications=yes
|
|
||||||
OutputDir=.
|
|
||||||
OutputBaseFilename=ScreenTranslator-{#MyAppVersion}
|
|
||||||
SetupIconFile=..\images\icon.ico
|
|
||||||
SolidCompression=yes
|
|
||||||
RestartIfNeededByRun=False
|
|
||||||
ShowLanguageDialog=auto
|
|
||||||
VersionInfoCompany={#MyAppPublisher}
|
|
||||||
VersionInfoDescription={#MyAppDescription}
|
|
||||||
VersionInfoProductName={#MyAppName}
|
|
||||||
VersionInfoProductVersion={#MyAppVersion}
|
|
||||||
VersionInfoVersion={#MyAppVersion}
|
|
||||||
Compression=lzma2/ultra64
|
|
||||||
InternalCompressLevel=max
|
|
||||||
|
|
||||||
[Languages]
|
|
||||||
Name: "english"; MessagesFile: "compiler:Default.isl"; InfoBeforeFile: "eng\Changelog.txt"
|
|
||||||
Name: "russian"; MessagesFile: "compiler:Languages\Russian.isl,ru\Russian.isl"; InfoBeforeFile: "ru\Changelog.txt"
|
|
||||||
|
|
||||||
[Tasks]
|
|
||||||
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
|
|
||||||
Name: "startupicon"; Description: "{cm:CreateStartupIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
|
|
||||||
Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked; OnlyBelowVersion: 0,6.1
|
|
||||||
|
|
||||||
[Icons]
|
|
||||||
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
|
|
||||||
Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}"
|
|
||||||
Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
|
|
||||||
Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: quicklaunchicon
|
|
||||||
Name: "{commonstartup}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: startupicon
|
|
||||||
|
|
||||||
[Run]
|
|
||||||
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
|
|
||||||
|
|
||||||
[Files]
|
|
||||||
Source: "content\ScreenTranslator.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: Executable
|
|
||||||
|
|
||||||
Source: "content\*.dll"; DestDir: "{app}"; Flags: ignoreversion; Components: Libraries
|
|
||||||
Source: "content\platforms\*"; DestDir: "{app}\platforms"; Flags: ignoreversion; Components: Libraries
|
|
||||||
|
|
||||||
Source: "content\tessdata\afr.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\Afrikaans
|
|
||||||
Source: "content\tessdata\sqi.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\Albanian
|
|
||||||
Source: "content\tessdata\grc.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\AncientGreek
|
|
||||||
Source: "content\tessdata\ara.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\Arabic
|
|
||||||
Source: "content\tessdata\aze.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\Azerbaijani
|
|
||||||
Source: "content\tessdata\eus.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\Basque
|
|
||||||
Source: "content\tessdata\bel.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\Belarusian
|
|
||||||
Source: "content\tessdata\ben.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\Bengali
|
|
||||||
Source: "content\tessdata\bul.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\Bulgarian
|
|
||||||
Source: "content\tessdata\cat.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\Catalan
|
|
||||||
Source: "content\tessdata\chr.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\Cherokee
|
|
||||||
Source: "content\tessdata\chi_sim.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\ChineseSimplified
|
|
||||||
Source: "content\tessdata\chi_tra.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\ChineseTraditional
|
|
||||||
Source: "content\tessdata\hrv.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\Croatian
|
|
||||||
Source: "content\tessdata\ces.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\Czech
|
|
||||||
Source: "content\tessdata\dan.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\Danish
|
|
||||||
Source: "content\tessdata\nld.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\Dutch
|
|
||||||
Source: "content\tessdata\eng.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\English
|
|
||||||
Source: "content\tessdata\epo.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\Esperanto
|
|
||||||
Source: "content\tessdata\epo_alt.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\Esperantoalternative
|
|
||||||
Source: "content\tessdata\est.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\Estonian
|
|
||||||
Source: "content\tessdata\fin.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\Finnish
|
|
||||||
Source: "content\tessdata\frk.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\Frankish
|
|
||||||
Source: "content\tessdata\fra.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\French
|
|
||||||
Source: "content\tessdata\glg.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\Galician
|
|
||||||
Source: "content\tessdata\deu.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\German
|
|
||||||
Source: "content\tessdata\ell.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\Greek
|
|
||||||
Source: "content\tessdata\heb.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\Hebrew
|
|
||||||
Source: "content\tessdata\hin.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\Hindi
|
|
||||||
Source: "content\tessdata\hun.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\Hungarian
|
|
||||||
Source: "content\tessdata\isl.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\Icelandic
|
|
||||||
Source: "content\tessdata\ind.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\Indonesian
|
|
||||||
Source: "content\tessdata\ita.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\Italian
|
|
||||||
Source: "content\tessdata\jpn.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\Japanese
|
|
||||||
Source: "content\tessdata\kan.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\Kannada
|
|
||||||
Source: "content\tessdata\kor.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\Korean
|
|
||||||
Source: "content\tessdata\lav.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\Latvian
|
|
||||||
Source: "content\tessdata\lit.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\Lithuanian
|
|
||||||
Source: "content\tessdata\mkd.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\Macedonian
|
|
||||||
Source: "content\tessdata\msa.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\Malay
|
|
||||||
Source: "content\tessdata\mal.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\Malayalam
|
|
||||||
Source: "content\tessdata\mlt.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\Maltese
|
|
||||||
Source: "content\tessdata\equ.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\MathEquation
|
|
||||||
Source: "content\tessdata\enm.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\MiddleEnglish
|
|
||||||
Source: "content\tessdata\frm.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\MiddleFrench
|
|
||||||
Source: "content\tessdata\nor.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\Norwegian
|
|
||||||
Source: "content\tessdata\pol.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\Polish
|
|
||||||
Source: "content\tessdata\por.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\Portuguese
|
|
||||||
Source: "content\tessdata\ron.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\Romanian
|
|
||||||
Source: "content\tessdata\rus.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\Russian
|
|
||||||
Source: "content\tessdata\srp.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\Serbian
|
|
||||||
Source: "content\tessdata\slk.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\Slovakian
|
|
||||||
Source: "content\tessdata\slv.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\Slovenian
|
|
||||||
Source: "content\tessdata\spa.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\Spanish
|
|
||||||
Source: "content\tessdata\swa.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\Swahili
|
|
||||||
Source: "content\tessdata\swe.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\Swedish
|
|
||||||
Source: "content\tessdata\tgl.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\Tagalog
|
|
||||||
Source: "content\tessdata\tam.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\Tamil
|
|
||||||
Source: "content\tessdata\tel.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\Telugu
|
|
||||||
Source: "content\tessdata\tha.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\Thai
|
|
||||||
Source: "content\tessdata\tur.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\Turkish
|
|
||||||
Source: "content\tessdata\ukr.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\Ukrainian
|
|
||||||
Source: "content\tessdata\vie.*"; DestDir: "{app}\tessdata"; Flags: ignoreversion; Components: Languages\Vietnamese
|
|
||||||
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
|
|
||||||
|
|
||||||
[CustomMessages]
|
|
||||||
english.CreateStartupIcon=Create autostart icon
|
|
||||||
english.Executables=Executables
|
|
||||||
english.Libraries=Libraries
|
|
||||||
english.Languages=OCR Languages
|
|
||||||
|
|
||||||
english.Afrikaans=Afrikaans
|
|
||||||
english.Albanian=Albanian
|
|
||||||
english.AncientGreek=AncientGreek
|
|
||||||
english.Arabic=Arabic
|
|
||||||
english.Azerbaijani=Azerbaijani
|
|
||||||
english.Basque=Basque
|
|
||||||
english.Belarusian=Belarusian
|
|
||||||
english.Bengali=Bengali
|
|
||||||
english.Bulgarian=Bulgarian
|
|
||||||
english.Catalan=Catalan
|
|
||||||
english.Cherokee=Cherokee
|
|
||||||
english.ChineseSimplified=ChineseSimplified
|
|
||||||
english.ChineseTraditional=ChineseTraditional
|
|
||||||
english.Croatian=Croatian
|
|
||||||
english.Czech=Czech
|
|
||||||
english.Danish=Danish
|
|
||||||
english.Dutch=Dutch
|
|
||||||
english.English=English
|
|
||||||
english.Esperanto=Esperanto
|
|
||||||
english.Esperantoalternative=Esperantoalternative
|
|
||||||
english.Estonian=Estonian
|
|
||||||
english.Finnish=Finnish
|
|
||||||
english.Frankish=Frankish
|
|
||||||
english.French=French
|
|
||||||
english.Galician=Galician
|
|
||||||
english.German=German
|
|
||||||
english.Greek=Greek
|
|
||||||
english.Hebrew=Hebrew
|
|
||||||
english.Hindi=Hindi
|
|
||||||
english.Hungarian=Hungarian
|
|
||||||
english.Icelandic=Icelandic
|
|
||||||
english.Indonesian=Indonesian
|
|
||||||
english.Italian=Italian
|
|
||||||
english.Japanese=Japanese
|
|
||||||
english.Kannada=Kannada
|
|
||||||
english.Korean=Korean
|
|
||||||
english.Latvian=Latvian
|
|
||||||
english.Lithuanian=Lithuanian
|
|
||||||
english.Macedonian=Macedonian
|
|
||||||
english.Malay=Malay
|
|
||||||
english.Malayalam=Malayalam
|
|
||||||
english.Maltese=Maltese
|
|
||||||
english.MathEquation=MathEquation
|
|
||||||
english.MiddleEnglish=MiddleEnglish
|
|
||||||
english.MiddleFrench=MiddleFrench
|
|
||||||
english.Norwegian=Norwegian
|
|
||||||
english.Polish=Polish
|
|
||||||
english.Portuguese=Portuguese
|
|
||||||
english.Romanian=Romanian
|
|
||||||
english.Russian=Russian
|
|
||||||
english.Serbian=Serbian
|
|
||||||
english.Slovakian=Slovakian
|
|
||||||
english.Slovenian=Slovenian
|
|
||||||
english.Spanish=Spanish
|
|
||||||
english.Swahili=Swahili
|
|
||||||
english.Swedish=Swedish
|
|
||||||
english.Tagalog=Tagalog
|
|
||||||
english.Tamil=Tamil
|
|
||||||
english.Telugu=Telugu
|
|
||||||
english.Thai=Thai
|
|
||||||
english.Turkish=Turkish
|
|
||||||
english.Ukrainian=Ukrainian
|
|
||||||
english.Vietnamese=Vietnamese
|
|
||||||
|
|
||||||
[Components]
|
|
||||||
Name: "Executable"; Description: "{cm:Executables}"; Types: compact custom full; Flags: fixed;
|
|
||||||
Name: "Libraries"; Description: "{cm:Libraries}"; Types: compact custom full; Flags: fixed;
|
|
||||||
Name: "Languages"; Description: "{cm:Languages}"; Types: custom full
|
|
||||||
|
|
||||||
Name: "Languages\Afrikaans"; Description: "{cm:Afrikaans}"; Types: full
|
|
||||||
Name: "Languages\Albanian"; Description: "{cm:Albanian}"; Types: full
|
|
||||||
Name: "Languages\AncientGreek"; Description: "{cm:AncientGreek}"; Types: full
|
|
||||||
Name: "Languages\Arabic"; Description: "{cm:Arabic}"; Types: full
|
|
||||||
Name: "Languages\Azerbaijani"; Description: "{cm:Azerbaijani}"; Types: full
|
|
||||||
Name: "Languages\Basque"; Description: "{cm:Basque}"; Types: full
|
|
||||||
Name: "Languages\Belarusian"; Description: "{cm:Belarusian}"; Types: full
|
|
||||||
Name: "Languages\Bengali"; Description: "{cm:Bengali}"; Types: full
|
|
||||||
Name: "Languages\Bulgarian"; Description: "{cm:Bulgarian}"; Types: full
|
|
||||||
Name: "Languages\Catalan"; Description: "{cm:Catalan}"; Types: full
|
|
||||||
Name: "Languages\Cherokee"; Description: "{cm:Cherokee}"; Types: full
|
|
||||||
Name: "Languages\ChineseSimplified"; Description: "{cm:ChineseSimplified}"; Types: full
|
|
||||||
Name: "Languages\ChineseTraditional"; Description: "{cm:ChineseTraditional}"; Types: compact custom full
|
|
||||||
Name: "Languages\Croatian"; Description: "{cm:Croatian}"; Types: full
|
|
||||||
Name: "Languages\Czech"; Description: "{cm:Czech}"; Types: full
|
|
||||||
Name: "Languages\Danish"; Description: "{cm:Danish}"; Types: full
|
|
||||||
Name: "Languages\Dutch"; Description: "{cm:Dutch}"; Types: full
|
|
||||||
Name: "Languages\English"; Description: "{cm:English}"; Types: compact custom full
|
|
||||||
Name: "Languages\Esperanto"; Description: "{cm:Esperanto}"; Types: full
|
|
||||||
Name: "Languages\Esperantoalternative"; Description: "{cm:Esperantoalternative}"; Types: full
|
|
||||||
Name: "Languages\Estonian"; Description: "{cm:Estonian}"; Types: full
|
|
||||||
Name: "Languages\Finnish"; Description: "{cm:Finnish}"; Types: full
|
|
||||||
Name: "Languages\Frankish"; Description: "{cm:Frankish}"; Types: full
|
|
||||||
Name: "Languages\French"; Description: "{cm:French}"; Types: compact custom full
|
|
||||||
Name: "Languages\Galician"; Description: "{cm:Galician}"; Types: full
|
|
||||||
Name: "Languages\German"; Description: "{cm:German}"; Types: compact custom full
|
|
||||||
Name: "Languages\Greek"; Description: "{cm:Greek}"; Types: full
|
|
||||||
Name: "Languages\Hebrew"; Description: "{cm:Hebrew}"; Types: full
|
|
||||||
Name: "Languages\Hindi"; Description: "{cm:Hindi}"; Types: full
|
|
||||||
Name: "Languages\Hungarian"; Description: "{cm:Hungarian}"; Types: full
|
|
||||||
Name: "Languages\Icelandic"; Description: "{cm:Icelandic}"; Types: full
|
|
||||||
Name: "Languages\Indonesian"; Description: "{cm:Indonesian}"; Types: full
|
|
||||||
Name: "Languages\Italian"; Description: "{cm:Italian}"; Types: full
|
|
||||||
Name: "Languages\Japanese"; Description: "{cm:Japanese}"; Types: compact custom full
|
|
||||||
Name: "Languages\Kannada"; Description: "{cm:Kannada}"; Types: full
|
|
||||||
Name: "Languages\Korean"; Description: "{cm:Korean}"; Types: compact custom full
|
|
||||||
Name: "Languages\Latvian"; Description: "{cm:Latvian}"; Types: full
|
|
||||||
Name: "Languages\Lithuanian"; Description: "{cm:Lithuanian}"; Types: full
|
|
||||||
Name: "Languages\Macedonian"; Description: "{cm:Macedonian}"; Types: full
|
|
||||||
Name: "Languages\Malay"; Description: "{cm:Malay}"; Types: full
|
|
||||||
Name: "Languages\Malayalam"; Description: "{cm:Malayalam}"; Types: full
|
|
||||||
Name: "Languages\Maltese"; Description: "{cm:Maltese}"; Types: full
|
|
||||||
Name: "Languages\MathEquation"; Description: "{cm:MathEquation}"; Types: compact custom full
|
|
||||||
Name: "Languages\MiddleEnglish"; Description: "{cm:MiddleEnglish}"; Types: full
|
|
||||||
Name: "Languages\MiddleFrench"; Description: "{cm:MiddleFrench}"; Types: full
|
|
||||||
Name: "Languages\Norwegian"; Description: "{cm:Norwegian}"; Types: full
|
|
||||||
Name: "Languages\Polish"; Description: "{cm:Polish}"; Types: full
|
|
||||||
Name: "Languages\Portuguese"; Description: "{cm:Portuguese}"; Types: full
|
|
||||||
Name: "Languages\Romanian"; Description: "{cm:Romanian}"; Types: full
|
|
||||||
Name: "Languages\Russian"; Description: "{cm:Russian}"; Types: compact custom full
|
|
||||||
Name: "Languages\Serbian"; Description: "{cm:Serbian}"; Types: full
|
|
||||||
Name: "Languages\Slovakian"; Description: "{cm:Slovakian}"; Types: full
|
|
||||||
Name: "Languages\Slovenian"; Description: "{cm:Slovenian}"; Types: full
|
|
||||||
Name: "Languages\Spanish"; Description: "{cm:Spanish}"; Types: compact custom full
|
|
||||||
Name: "Languages\Swahili"; Description: "{cm:Swahili}"; Types: full
|
|
||||||
Name: "Languages\Swedish"; Description: "{cm:Swedish}"; Types: full
|
|
||||||
Name: "Languages\Tagalog"; Description: "{cm:Tagalog}"; Types: full
|
|
||||||
Name: "Languages\Tamil"; Description: "{cm:Tamil}"; Types: full
|
|
||||||
Name: "Languages\Telugu"; Description: "{cm:Telugu}"; Types: full
|
|
||||||
Name: "Languages\Thai"; Description: "{cm:Thai}"; Types: full
|
|
||||||
Name: "Languages\Turkish"; Description: "{cm:Turkish}"; Types: full
|
|
||||||
Name: "Languages\Ukrainian"; Description: "{cm:Ukrainian}"; Types: full
|
|
||||||
Name: "Languages\Vietnamese"; Description: "{cm:Vietnamese}"; Types: full
|
|
@ -1,23 +0,0 @@
|
|||||||
Changes.
|
|
||||||
1.2.2:
|
|
||||||
* Added alternative translation source.
|
|
||||||
1.2.1:
|
|
||||||
* Fixed the bug with the lack of translation.
|
|
||||||
* Fixed the bug with the use of language recognition by default when you select another one in OCR region selection mode.
|
|
||||||
1.2.0:
|
|
||||||
+ Changed installer.
|
|
||||||
+ Added all available languages for recognition.
|
|
||||||
+ Added ability to specify language when selecting the field of recognition using right click.
|
|
||||||
+ Human readable language names.
|
|
||||||
* Reduced memory usage.
|
|
||||||
* Updated libraries.
|
|
||||||
1.1.3:
|
|
||||||
* Added library libgcc_s_dw2-1.dll.
|
|
||||||
* Updated libraries.
|
|
||||||
1.1.2:
|
|
||||||
* If you specify in the settings the path to tessdata characters "\" or "/" at the end of the path are no longer required.
|
|
||||||
1.1.1:
|
|
||||||
* Fixed an issue with incorrect window size when display results.
|
|
||||||
1.1.0:
|
|
||||||
+ Displays the result in the window, along with the picture.
|
|
||||||
+ Context menu expanded. Added buttons display the last result and copy it to the clipboard.
|
|
@ -1,23 +0,0 @@
|
|||||||
Изменения.
|
|
||||||
1.2.2:
|
|
||||||
* Добавлен альтернативный источник перевода.
|
|
||||||
1.2.1:
|
|
||||||
* Устранена ошибка отсутствия перевода.
|
|
||||||
* Устранена ошибка использования языка распознавания по умолчанию при выборе другого в окне выделения области распознавания.
|
|
||||||
1.2.0:
|
|
||||||
+ Изменен установщик.
|
|
||||||
+ В установщик добавлены все доступные языки для распознавания.
|
|
||||||
+ Добавлена возможность указания языка при выборе области распознавания при помощи выделения с правым кликом.
|
|
||||||
+ Человекочитаемые названия языков.
|
|
||||||
* Уменьшено потребление памяти.
|
|
||||||
* Обновлены библиотеки.
|
|
||||||
1.1.3:
|
|
||||||
- В установщик добавлена библиотека libgcc_s_dw2-1.dll.
|
|
||||||
- Обновлены библиотеки.
|
|
||||||
1.1.2:
|
|
||||||
- При задании в настройках пути к tessdata символы «\» или «/» в конце пути теперь не обязательны.
|
|
||||||
1.1.1:
|
|
||||||
- Пофиксен баг с неверным размером окна отображения результатов.
|
|
||||||
1.1.0:
|
|
||||||
- Отображение результата в окошке, вместе с картинкой.
|
|
||||||
- Контекстное меню расширено. Добавлены кнопки отображения последнего результата и копирования его в буфер обмена.
|
|
@ -1,78 +0,0 @@
|
|||||||
[LangOptions]
|
|
||||||
LanguageName=<0420><0443><0441><0441><043A><0438><0439>
|
|
||||||
LanguageID=$0419
|
|
||||||
LanguageCodePage=1251
|
|
||||||
|
|
||||||
[CustomMessages]
|
|
||||||
|
|
||||||
; *** Components
|
|
||||||
CreateStartupIcon=Äîáàâèòü â àâòîçàïóñê
|
|
||||||
Executables=Èñïîëíÿåìûå ôàéëû
|
|
||||||
Libraries=Áèáëèîòåêè
|
|
||||||
Languages=ßçûêè
|
|
||||||
|
|
||||||
AncientGreek=Äðåâíåãðå÷åñêèé
|
|
||||||
Esperantoalternative=Ýñïåðàíòî àëüòåðíàòèâíûé
|
|
||||||
English=Àíãëèéñêèé
|
|
||||||
Ukrainian=Óêðàèíñêèé
|
|
||||||
Turkish=Òóðåöêèé
|
|
||||||
Thai=Òàéñêèé
|
|
||||||
Tagalog=Òàãàëüñêèé
|
|
||||||
Telugu=Òåëóãó
|
|
||||||
Tamil=Òàìèëüñêèé
|
|
||||||
Swedish=Øâåäñêèé
|
|
||||||
Swahili=Ñóàõèëè
|
|
||||||
Serbian=Ñåðáñêèé
|
|
||||||
Albanian=Àëáàíñêèé
|
|
||||||
Spanish=Èñïàíñêèé
|
|
||||||
Slovenian=Ñëîâåíñêèé
|
|
||||||
Slovakian=Ñëîâàöêèé
|
|
||||||
Romanian=Ðóìûíñêèé
|
|
||||||
Portuguese=Ïîðòóãàëüñêèé
|
|
||||||
Polish=Ïîëüñêèé
|
|
||||||
Norwegian=Íîðâåæñêèé
|
|
||||||
Dutch=Ãîëëàíäñêèé
|
|
||||||
Malay=Ìàëàéñêèé
|
|
||||||
Maltese=Ìàëüòèéñêèé
|
|
||||||
Macedonian=Ìàêåäîíñêèé
|
|
||||||
Malayalam=Ìàëàÿëàì
|
|
||||||
Lithuanian=Ëèòîâñêèé
|
|
||||||
Latvian=Ëàòûøñêèé
|
|
||||||
Korean=Êîðåéñêèé
|
|
||||||
Kannada=Êàííàäà
|
|
||||||
Italian=Èòàëüÿíñêèé
|
|
||||||
Icelandic=Èñëàíäñêèé
|
|
||||||
Indonesian=Èíäîíåçèéñêèé
|
|
||||||
Cherokee=×åðîêè
|
|
||||||
Hungarian=Âåíãåðñêèé
|
|
||||||
Croatian=Õîðâàòñêèé
|
|
||||||
Hindi=Õèíäè
|
|
||||||
Hebrew=Èâðèò
|
|
||||||
Galician=Ãàëèöêèé
|
|
||||||
MiddleFrench=Ñðåäíåâåêîâûé Ôðàíöóçñêèé
|
|
||||||
Frankish=Ôðàíêñêèé
|
|
||||||
French=Ôðàíöóçñêèé
|
|
||||||
Finnish=Ôèíñêèé
|
|
||||||
Basque=Áàñêñêèé
|
|
||||||
Estonian=Ýñòîíñêèé
|
|
||||||
MathEquation=Ìàòåìàòèêà / óðàâíåíèå
|
|
||||||
Esperanto=Ýñïåðàíòî
|
|
||||||
MiddleEnglish=Ñðåäíåâåêîâûé Àíãëèéñêèé
|
|
||||||
Greek=Ãðå÷åñêèé
|
|
||||||
German=Íåìåöêèé
|
|
||||||
Danish=Äàòñêèé
|
|
||||||
Czech=×åøñêèé
|
|
||||||
Catalan=Êàòàëîíñêèé
|
|
||||||
Bulgarian=Áîëãàðñêèé
|
|
||||||
Bengali=Áåíãàëüñêèé
|
|
||||||
Belarusian=Áåëîðóññêèé
|
|
||||||
Azerbaijani=Àçåðáàéäæàíñêèé
|
|
||||||
Arabic=Àðàáñêèé
|
|
||||||
Afrikaans=Àôðèêààíñ
|
|
||||||
Japanese=ßïîíñêèé
|
|
||||||
ChineseSimplified=Êèòàéñêèé (óïðîùåííûé)
|
|
||||||
ChineseTraditional=Êèòàéñêèé (òðàäèöèîííûé)
|
|
||||||
Russian=Ðóññêèé
|
|
||||||
Vietnamese=Âüåòíàìñêèé
|
|
||||||
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
|||||||
#Changes.
|
|
||||||
## 1.2.2:
|
|
||||||
* Added alternative translation source.
|
|
||||||
## 1.2.1:
|
|
||||||
* Fixed the bug with the lack of translation.
|
|
||||||
* Fixed the bug with the use of language recognition by default when you select another one in OCR region selection mode.
|
|
||||||
## 1.2.0:
|
|
||||||
* Changed installer.
|
|
||||||
* Added all available languages for recognition.
|
|
||||||
* Added ability to specify language when selecting the field of recognition using right click.
|
|
||||||
* Human readable language names.
|
|
||||||
* Reduced memory usage.
|
|
||||||
* Updated libraries.
|
|
||||||
## 1.1.3:
|
|
||||||
* Added library libgcc_s_dw2-1.dll.
|
|
||||||
* Updated libraries.
|
|
||||||
## 1.1.2:
|
|
||||||
* If you specify in the settings the path to tessdata characters "\" or "/" at the end of the path are no longer required.
|
|
||||||
## 1.1.1:
|
|
||||||
* Fixed an issue with incorrect window size when display results.
|
|
||||||
## 1.1.0:
|
|
||||||
* Displays the result in the window, along with the picture.
|
|
||||||
* Context menu expanded. Added buttons display the last result and copy it to the clipboard.
|
|
28
external/gtest/LICENSE
vendored
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
Copyright 2008, Google Inc.
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
11824
external/gtest/gtest-all.cc
vendored
Normal file
14813
external/gtest/gtest.h
vendored
Normal file
22
external/miniz/LICENSE
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
Copyright 2013-2014 RAD Game Tools and Valve Software
|
||||||
|
Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC
|
||||||
|
|
||||||
|
All Rights Reserved.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
7657
external/miniz/miniz.c
vendored
Normal file
1338
external/miniz/miniz.h
vendored
Normal file
BIN
images/icon.ico
Before Width: | Height: | Size: 31 KiB |
BIN
images/icon.png
Before Width: | Height: | Size: 6.5 KiB |
25
main.cpp
@ -1,25 +0,0 @@
|
|||||||
#include <QApplication>
|
|
||||||
#include <QTranslator>
|
|
||||||
|
|
||||||
#include <Manager.h>
|
|
||||||
#include <Settings.h>
|
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
|
||||||
{
|
|
||||||
QApplication a(argc, argv);
|
|
||||||
a.setQuitOnLastWindowClosed (false);
|
|
||||||
a.setApplicationName (settings_values::appName);
|
|
||||||
a.setOrganizationName (settings_values::companyName);
|
|
||||||
|
|
||||||
QTranslator translator;
|
|
||||||
// Set default to english.
|
|
||||||
if (translator.load (QLocale::system (), "translation", "_", ":/translations") ||
|
|
||||||
translator.load (":/translations/translation_en"))
|
|
||||||
{
|
|
||||||
a.installTranslator(&translator);
|
|
||||||
}
|
|
||||||
|
|
||||||
Manager manager;
|
|
||||||
|
|
||||||
return a.exec();
|
|
||||||
}
|
|
16
recources.qrc
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<RCC>
|
||||||
|
<qresource prefix="/icons">
|
||||||
|
<file alias="app.png">share/images/STIconBlue.png</file>
|
||||||
|
<file alias="st_success.png">share/images/STIconGreen.png</file>
|
||||||
|
<file alias="st_busy.png">share/images/STIconOrange.png</file>
|
||||||
|
<file alias="st_error.png">share/images/STIconRed.png</file>
|
||||||
|
<file alias="loadImages.png">share/images/loadImages.png</file>
|
||||||
|
<file alias="loadImages@2x.png">share/images/loadImages@2x.png</file>
|
||||||
|
<file alias="debug.png">share/images/debug.png</file>
|
||||||
|
<file alias="debug@2x.png">share/images/debug@2x.png</file>
|
||||||
|
</qresource>
|
||||||
|
<qresource prefix="/translations">
|
||||||
|
<file alias="screentranslator_ru.qm">share/translations/screentranslator_ru.qm</file>
|
||||||
|
<file alias="screentranslator_he.qm">share/translations/screentranslator_he.qm</file>
|
||||||
|
</qresource>
|
||||||
|
</RCC>
|
142
screen-translator.pro
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
QT = core gui widgets network webenginewidgets
|
||||||
|
|
||||||
|
TARGET = screen-translator
|
||||||
|
TEMPLATE = app
|
||||||
|
CONFIG += c++17
|
||||||
|
|
||||||
|
DEPS_DIR=$$(ST_DEPS_DIR)
|
||||||
|
isEmpty(DEPS_DIR):DEPS_DIR=$$PWD/../deps
|
||||||
|
INCLUDEPATH += $$DEPS_DIR/include
|
||||||
|
LIBS += -L$$DEPS_DIR/lib
|
||||||
|
LIBS += -lhunspell -lleptonica -ltesseract
|
||||||
|
|
||||||
|
win32{
|
||||||
|
LIBS += -lUser32
|
||||||
|
}
|
||||||
|
linux{
|
||||||
|
QT += x11extras
|
||||||
|
LIBS += -lX11
|
||||||
|
}
|
||||||
|
|
||||||
|
SOURCES += $$PWD/external/miniz/miniz.c
|
||||||
|
INCLUDEPATH += $$PWD/external
|
||||||
|
|
||||||
|
VER=3.3.0
|
||||||
|
DEFINES += VERSION="$$VER"
|
||||||
|
VERSION = $$VER.0
|
||||||
|
QMAKE_TARGET_COMPANY = Gres
|
||||||
|
QMAKE_TARGET_PRODUCT = Screen Translator
|
||||||
|
QMAKE_TARGET_COPYRIGHT = Copyright (c) Gres
|
||||||
|
RC_ICONS = $$PWD/share/images/icon.ico
|
||||||
|
|
||||||
|
INCLUDEPATH += src src/service src/capture src/ocr \
|
||||||
|
src/represent src/translate src/correct
|
||||||
|
|
||||||
|
HEADERS += \
|
||||||
|
src/capture/capturearea.h \
|
||||||
|
src/capture/captureareaeditor.h \
|
||||||
|
src/capture/captureareaselector.h \
|
||||||
|
src/capture/capturer.h \
|
||||||
|
src/commonmodels.h \
|
||||||
|
src/correct/corrector.h \
|
||||||
|
src/correct/correctorworker.h \
|
||||||
|
src/correct/hunspellcorrector.h \
|
||||||
|
src/languagecodes.h \
|
||||||
|
src/manager.h \
|
||||||
|
src/ocr/recognizer.h \
|
||||||
|
src/ocr/recognizerworker.h \
|
||||||
|
src/ocr/tesseract.h \
|
||||||
|
src/represent/representer.h \
|
||||||
|
src/represent/resulteditor.h \
|
||||||
|
src/represent/resultwidget.h \
|
||||||
|
src/service/apptranslator.h \
|
||||||
|
src/service/debug.h \
|
||||||
|
src/service/geometryutils.h \
|
||||||
|
src/service/globalaction.h \
|
||||||
|
src/service/keysequenceedit.h \
|
||||||
|
src/service/runatsystemstart.h \
|
||||||
|
src/service/singleapplication.h \
|
||||||
|
src/service/updates.h \
|
||||||
|
src/service/widgetstate.h \
|
||||||
|
src/settings.h \
|
||||||
|
src/settingseditor.h \
|
||||||
|
src/settingsvalidator.h \
|
||||||
|
src/stfwd.h \
|
||||||
|
src/substitutionstable.h \
|
||||||
|
src/task.h \
|
||||||
|
src/translate/translator.h \
|
||||||
|
src/translate/webpage.h \
|
||||||
|
src/translate/webpageproxy.h \
|
||||||
|
src/trayicon.h
|
||||||
|
|
||||||
|
SOURCES += \
|
||||||
|
src/capture/capturearea.cpp \
|
||||||
|
src/capture/captureareaeditor.cpp \
|
||||||
|
src/capture/captureareaselector.cpp \
|
||||||
|
src/capture/capturer.cpp \
|
||||||
|
src/commonmodels.cpp \
|
||||||
|
src/correct/corrector.cpp \
|
||||||
|
src/correct/correctorworker.cpp \
|
||||||
|
src/correct/hunspellcorrector.cpp \
|
||||||
|
src/languagecodes.cpp \
|
||||||
|
src/main.cpp \
|
||||||
|
src/manager.cpp \
|
||||||
|
src/ocr/recognizer.cpp \
|
||||||
|
src/ocr/recognizerworker.cpp \
|
||||||
|
src/ocr/tesseract.cpp \
|
||||||
|
src/represent/representer.cpp \
|
||||||
|
src/represent/resulteditor.cpp \
|
||||||
|
src/represent/resultwidget.cpp \
|
||||||
|
src/service/apptranslator.cpp \
|
||||||
|
src/service/debug.cpp \
|
||||||
|
src/service/geometryutils.cpp \
|
||||||
|
src/service/globalaction.cpp \
|
||||||
|
src/service/keysequenceedit.cpp \
|
||||||
|
src/service/runatsystemstart.cpp \
|
||||||
|
src/service/singleapplication.cpp \
|
||||||
|
src/service/updates.cpp \
|
||||||
|
src/service/widgetstate.cpp \
|
||||||
|
src/settings.cpp \
|
||||||
|
src/settingseditor.cpp \
|
||||||
|
src/settingsvalidator.cpp \
|
||||||
|
src/substitutionstable.cpp \
|
||||||
|
src/translate/translator.cpp \
|
||||||
|
src/translate/webpage.cpp \
|
||||||
|
src/translate/webpageproxy.cpp \
|
||||||
|
src/trayicon.cpp
|
||||||
|
|
||||||
|
RESOURCES += \
|
||||||
|
recources.qrc
|
||||||
|
|
||||||
|
FORMS += \
|
||||||
|
src/settingseditor.ui
|
||||||
|
|
||||||
|
OTHER_FILES += \
|
||||||
|
translators/*.js \
|
||||||
|
version.json \
|
||||||
|
updates.json
|
||||||
|
|
||||||
|
TRANSLATIONS += \
|
||||||
|
share/translations/screentranslator_ru.ts \
|
||||||
|
share/translations/screentranslator_he.ts
|
||||||
|
|
||||||
|
linux {
|
||||||
|
PREFIX = /usr
|
||||||
|
|
||||||
|
target.path = $$PREFIX/bin
|
||||||
|
|
||||||
|
shortcuts.files = $$PWD/share/screentranslator.desktop
|
||||||
|
shortcuts.path = $$PREFIX/share/applications/
|
||||||
|
pixmaps.files += $$PWD/share/images/screentranslator.png
|
||||||
|
pixmaps.path = $$PREFIX/share/icons/hicolor/128x128/apps/
|
||||||
|
|
||||||
|
INSTALLS += target shortcuts pixmaps
|
||||||
|
}
|
||||||
|
win32 {
|
||||||
|
RC_ICONS = $$PWD/share/images/icon.ico
|
||||||
|
target.path = /
|
||||||
|
INSTALLS += target
|
||||||
|
}
|
||||||
|
mac {
|
||||||
|
ICON = $$PWD/share/images/icon.icns
|
||||||
|
}
|
125
share/Changelog_en.md
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
# Changes
|
||||||
|
|
||||||
|
## 3.3.0
|
||||||
|
|
||||||
|
* Use single tesseract library (not optimized and compatible versions)
|
||||||
|
* Improved recognition
|
||||||
|
|
||||||
|
## 3.2.3
|
||||||
|
|
||||||
|
* Fixed translators order persistance
|
||||||
|
* Fixed multi-monitor support
|
||||||
|
* Improves result representation near monitor borders
|
||||||
|
* Updated recognition library
|
||||||
|
|
||||||
|
## 3.2.2
|
||||||
|
|
||||||
|
* Disabled hotkeys with several consecutive combinations
|
||||||
|
* Added the ability to use some service buttons for hotkeys
|
||||||
|
* Fixed multiple monitors support if the main one is not at the top left
|
||||||
|
* Automatic selection of the supported version of tesseract
|
||||||
|
|
||||||
|
## 3.2.1
|
||||||
|
|
||||||
|
* Fixed incorrect update install
|
||||||
|
|
||||||
|
## 3.2.0
|
||||||
|
|
||||||
|
* Improved vertical text recognition
|
||||||
|
* Improved incorrect settings notification
|
||||||
|
* Improved update process
|
||||||
|
|
||||||
|
## 3.1.2
|
||||||
|
|
||||||
|
* Fixed manually corrected text translation
|
||||||
|
|
||||||
|
## 3.1.1
|
||||||
|
|
||||||
|
* Fixed portable mode detection
|
||||||
|
|
||||||
|
## 3.1.0
|
||||||
|
|
||||||
|
* Added ability to choose a recognition version from the program
|
||||||
|
* Added some error messages about misconfiguration
|
||||||
|
* Fixed some errors
|
||||||
|
|
||||||
|
## 3.0.1
|
||||||
|
|
||||||
|
* Fixed some errors
|
||||||
|
* Added `compatible` version (fixes crash during recognition)
|
||||||
|
|
||||||
|
## 3.0.0
|
||||||
|
|
||||||
|
* Changed distribution model: a zip archive instead of an installer
|
||||||
|
* Required resources can now be downloaded from the program
|
||||||
|
* Added ability to capture images in saved areas (reuse selection)
|
||||||
|
* Many interface changes
|
||||||
|
* Updated libraries versions
|
||||||
|
|
||||||
|
## 2.0.2
|
||||||
|
|
||||||
|
* Added force translator rotation option.
|
||||||
|
|
||||||
|
## 2.0.1
|
||||||
|
|
||||||
|
* Fixed installer.
|
||||||
|
|
||||||
|
## 2.0.0
|
||||||
|
|
||||||
|
* Added a version for linux.
|
||||||
|
* Added support for multiple monitors.
|
||||||
|
* Added ability of recognition without translation.
|
||||||
|
* Added ability to recapture from old image.
|
||||||
|
* Added ability to recapture without closing capture window.
|
||||||
|
* Added ability to re-recognize other language.
|
||||||
|
* Added ability to display intermediate result when error occured.
|
||||||
|
* Added support for different translation services.
|
||||||
|
* Added ability to copy image to clipboard.
|
||||||
|
* Added ability to edit recognized text.
|
||||||
|
* Added ability to automatically correct common recognition mistakes.
|
||||||
|
* Added ability to use a proxy.
|
||||||
|
* Added ability to swap translation and recognition languages.
|
||||||
|
* Updated icons.
|
||||||
|
* Show progress on icon.
|
||||||
|
* Added ability to automatically update.
|
||||||
|
|
||||||
|
## 1.2.3
|
||||||
|
|
||||||
|
* Fixed possible crash.
|
||||||
|
* Added version information and some error messages.
|
||||||
|
|
||||||
|
## 1.2.2
|
||||||
|
|
||||||
|
* Added alternative translation source.
|
||||||
|
|
||||||
|
## 1.2.1
|
||||||
|
|
||||||
|
* Fixed the bug with the lack of translation.
|
||||||
|
* Fixed the bug with the use of language recognition by default when you select another one in OCR region selection mode.
|
||||||
|
|
||||||
|
## 1.2.0
|
||||||
|
|
||||||
|
* Changed installer.
|
||||||
|
* Added all available languages for recognition.
|
||||||
|
* Added ability to specify language when selecting the field of recognition using right click.
|
||||||
|
* Human readable language names.
|
||||||
|
* Reduced memory usage.
|
||||||
|
* Updated libraries.
|
||||||
|
|
||||||
|
## 1.1.3
|
||||||
|
|
||||||
|
* Added library libgcc_s_dw2-1.dll.
|
||||||
|
* Updated libraries.
|
||||||
|
|
||||||
|
## 1.1.2
|
||||||
|
|
||||||
|
* If you specify in the settings the path to tessdata characters "\" or "/" at the end of the path are no longer required.
|
||||||
|
|
||||||
|
## 1.1.1
|
||||||
|
|
||||||
|
* Fixed an issue with incorrect window size when display results.
|
||||||
|
|
||||||
|
## 1.1.0
|
||||||
|
|
||||||
|
* Displays the result in the window, along with the picture.
|
||||||
|
* Context menu expanded. Added buttons display the last result and copy it to the clipboard.
|
125
share/Changelog_ru.md
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
# Изменения
|
||||||
|
|
||||||
|
## 3.3.0
|
||||||
|
|
||||||
|
* Использование единой библиотеки распознавания (без оптимизированной и совместимой версий)
|
||||||
|
* Улучшено распознавание
|
||||||
|
|
||||||
|
## 3.2.3
|
||||||
|
|
||||||
|
* Исправлено сохранение порядка переводчиков в настройках
|
||||||
|
* Исправлена работа с несколькими мониторами
|
||||||
|
* Улучшено отображение результата на границе монитора
|
||||||
|
* Обновлена версия библиотеки распознавания
|
||||||
|
|
||||||
|
## 3.2.2
|
||||||
|
|
||||||
|
* Исключено задание горячих клавиш из нескольких последовательных комбинаций
|
||||||
|
* Добавлена возможность использования некоторых служебных кнопок для горячих клавиш
|
||||||
|
* Исправлена работа с несколькими мониторами, если главный находится не слева-вверху
|
||||||
|
* Автоматический выбор поддерживаемой версии tesseract
|
||||||
|
|
||||||
|
## 3.2.1
|
||||||
|
|
||||||
|
* Исправлена некорректная установка обновления
|
||||||
|
|
||||||
|
## 3.2.0
|
||||||
|
|
||||||
|
* Улучшено распознавание вертикального текста
|
||||||
|
* Улучшено информирование о некорректных настройках
|
||||||
|
* Упрощена работа с обновлениями
|
||||||
|
|
||||||
|
## 3.1.2
|
||||||
|
|
||||||
|
* Исправлен перевод исправленного вручную текста
|
||||||
|
|
||||||
|
## 3.1.1
|
||||||
|
|
||||||
|
* Исправлено определение работы в Portable режиме
|
||||||
|
|
||||||
|
## 3.1.0
|
||||||
|
|
||||||
|
* Добавлена возможность выбора версии библиотеки распознавания из программы
|
||||||
|
* Добавлены сообщения об ошибках при неправильной настройке
|
||||||
|
* Исправлены некоторые ошибки
|
||||||
|
|
||||||
|
## 3.0.1
|
||||||
|
|
||||||
|
* Исправлены некоторые ошибки
|
||||||
|
* Добавлена `совместимая` версия (исправляет падение при распознавании)
|
||||||
|
|
||||||
|
## 3.0.0
|
||||||
|
|
||||||
|
* Изменен порядок распространения: удалены установщики. Для установки достаточно распаковать папку нужного дистрибутива в желаемое место и запустить программу
|
||||||
|
* Необходимые ресурсы скачиваются из программы, а не вручную или через установщик
|
||||||
|
* Добавлена возможность захвата изображений в заранее подготовленных областях
|
||||||
|
* Много мелких изменений в интерфейсе
|
||||||
|
* Обновлены версии библиотек
|
||||||
|
|
||||||
|
## 2.0.2
|
||||||
|
|
||||||
|
* Добавлена настройка принудительной смены переводчиков.
|
||||||
|
|
||||||
|
## 2.0.1
|
||||||
|
|
||||||
|
* Исправлен установщик.
|
||||||
|
|
||||||
|
## 2.0.0
|
||||||
|
|
||||||
|
* Добавлена версия под linux.
|
||||||
|
* Добавлена поддержка нескольких мониторов.
|
||||||
|
* Добавлена возможность распознание без перевода.
|
||||||
|
* Добавлена возможность вызова старого рисунка для выделения.
|
||||||
|
* Добавлена возможность повторного выделения без закрытия окна захвата.
|
||||||
|
* Добавлена возможность повторного распознания на другом языке.
|
||||||
|
* Добавлена возможность отображения промежуточного результата при ошибке перевода.
|
||||||
|
* Добавлена поддержка разных сервисов перевода.
|
||||||
|
* Добавлена возможность копирования изображения в буфер.
|
||||||
|
* Добавлена возможность редакции распознанного текста.
|
||||||
|
* Добавлена возможность автоматической коррекции частых ошибок распознавания.
|
||||||
|
* Добавлена возможность использования прокси.
|
||||||
|
* Добавлена возможность разовой смена языка перевода и распознавания.
|
||||||
|
* Обновлены иконки.
|
||||||
|
* Добавлено отображение статуса работы на иконке.
|
||||||
|
* Добавлена возможность автоматического обновления.
|
||||||
|
|
||||||
|
## 1.2.3
|
||||||
|
|
||||||
|
* Устранена возможная причина падения.
|
||||||
|
* Добавлена информация о версии и некоторые сообщения об ошибках.
|
||||||
|
|
||||||
|
## 1.2.2
|
||||||
|
|
||||||
|
* Добавлен альтернативный источник перевода.
|
||||||
|
|
||||||
|
## 1.2.1
|
||||||
|
|
||||||
|
* Устранена ошибка отсутствия перевода.
|
||||||
|
* Устранена ошибка использования языка распознавания по умолчанию при выборе другого в окне выделения области распознавания.
|
||||||
|
|
||||||
|
## 1.2.0
|
||||||
|
|
||||||
|
* Изменен установщик.
|
||||||
|
* В установщик добавлены все доступные языки для распознавания.
|
||||||
|
* Добавлена возможность указания языка при выборе области распознавания при помощи выделения с правым кликом.
|
||||||
|
* Человекочитаемые названия языков.
|
||||||
|
* Уменьшено потребление памяти.
|
||||||
|
* Обновлены библиотеки.
|
||||||
|
|
||||||
|
## 1.1.3
|
||||||
|
|
||||||
|
* В установщик добавлена библиотека libgcc_s_dw2-1.dll.
|
||||||
|
* Обновлены библиотеки.
|
||||||
|
|
||||||
|
## 1.1.2
|
||||||
|
|
||||||
|
* При задании в настройках пути к tessdata символы «\» или «/» в конце пути теперь не обязательны.
|
||||||
|
|
||||||
|
## 1.1.1
|
||||||
|
|
||||||
|
* Пофиксен баг с неверным размером окна отображения результатов.
|
||||||
|
|
||||||
|
## 1.1.0
|
||||||
|
|
||||||
|
* Отображение результата в окошке, вместе с картинкой.
|
||||||
|
* Контекстное меню расширено. Добавлены кнопки отображения последнего результата и копирования его в буфер обмена.
|
65
share/ci/appimage.py
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import common as c
|
||||||
|
from config import *
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import subprocess as sub
|
||||||
|
import shutil
|
||||||
|
from glob import glob
|
||||||
|
|
||||||
|
if len(sys.argv) > 1 and sys.argv[1] == 'glibc_version': # subcommand
|
||||||
|
sub.run('ldd --version | head -n 1 | grep -Po "\\d\\.\\d\\d"', shell=True)
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
tag = os.environ.get('TAG', '')
|
||||||
|
artifact_name = '{}-{}{}.AppImage'.format(app_name, app_version, tag)
|
||||||
|
if len(sys.argv) > 1 and sys.argv[1] == 'artifact_name': # subcommand
|
||||||
|
c.print(artifact_name)
|
||||||
|
exit(0)
|
||||||
|
artifact_path = os.path.abspath(artifact_name)
|
||||||
|
|
||||||
|
c.print('>> Making appimage')
|
||||||
|
|
||||||
|
base_url = 'https://github.com/probonopd/linuxdeployqt/releases/download'
|
||||||
|
continuous_url = base_url + '/continuous/linuxdeployqt-continuous-x86_64.AppImage'
|
||||||
|
tagged_url = base_url + '/6/linuxdeployqt-6-x86_64.AppImage'
|
||||||
|
linuxdeployqt_url = continuous_url
|
||||||
|
linuxdeployqt_original = os.path.basename(linuxdeployqt_url)
|
||||||
|
|
||||||
|
c.download(linuxdeployqt_url, linuxdeployqt_original)
|
||||||
|
c.run('chmod a+x {}'.format(linuxdeployqt_original))
|
||||||
|
|
||||||
|
linuxdeployqt_bin = os.path.abspath('linuxdeployqt')
|
||||||
|
c.symlink(linuxdeployqt_original, linuxdeployqt_bin)
|
||||||
|
|
||||||
|
os.chdir(build_dir)
|
||||||
|
|
||||||
|
install_dir = os.path.abspath('appdir')
|
||||||
|
c.recreate_dir(install_dir)
|
||||||
|
c.run('make INSTALL_ROOT={0} DESTDIR={0} install'.format(install_dir))
|
||||||
|
|
||||||
|
if c.is_inside_docker():
|
||||||
|
c.run('{} --appimage-extract'.format(linuxdeployqt_bin))
|
||||||
|
linuxdeployqt_bin = os.path.abspath('squashfs-root/AppRun')
|
||||||
|
|
||||||
|
os.environ['LD_LIBRARY_PATH'] = dependencies_dir + '/lib'
|
||||||
|
os.environ['VERSION'] = app_version
|
||||||
|
# debug flags: -unsupported-bundle-everything -unsupported-allow-new-glibc
|
||||||
|
flags = '' if os.getenv("DEBUG") is None else '-unsupported-allow-new-glibc'
|
||||||
|
|
||||||
|
additional_files = glob(ssl_dir + '/lib/lib*.so.*') + \
|
||||||
|
glob('/usr/lib/x86_64-linux-gnu/nss/*')
|
||||||
|
out_lib_dir = install_dir + '/usr/lib'
|
||||||
|
os.makedirs(out_lib_dir, exist_ok=True)
|
||||||
|
for f in additional_files:
|
||||||
|
c.print('>> Copying {} to {}'.format(f, out_lib_dir))
|
||||||
|
shutil.copy(f, out_lib_dir)
|
||||||
|
|
||||||
|
c.ensure_got_path('{}/usr/share/doc/libc6/copyright'.format(install_dir))
|
||||||
|
|
||||||
|
c.run('{} {}/usr/share/applications/*.desktop {} -appimage -qmake={}/bin/qmake'.format(
|
||||||
|
linuxdeployqt_bin, install_dir, flags, qt_dir))
|
||||||
|
|
||||||
|
c.run('mv {}-{}*.AppImage "{}"'.format(app_name, app_version, artifact_path))
|
||||||
|
|
||||||
|
bin_path = install_dir + '/usr/bin/' + bin_name
|
||||||
|
c.print('>> Md5 {} {}'.format(bin_path, c.md5sum(bin_path)))
|
26
share/ci/build.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import common as c
|
||||||
|
from config import *
|
||||||
|
import os
|
||||||
|
import platform
|
||||||
|
|
||||||
|
c.print('>> Building {} on {}'.format(app_name, os_name))
|
||||||
|
|
||||||
|
c.add_to_path(os.path.abspath(qt_dir + '/bin'))
|
||||||
|
os.environ['ST_DEPS_DIR'] = dependencies_dir
|
||||||
|
|
||||||
|
if platform.system() == "Windows":
|
||||||
|
env_cmd = c.get_msvc_env_cmd(bitness=bitness, msvc_version=msvc_version)
|
||||||
|
c.apply_cmd_env(env_cmd)
|
||||||
|
|
||||||
|
c.recreate_dir(build_dir)
|
||||||
|
os.chdir(build_dir)
|
||||||
|
|
||||||
|
c.run('lupdate "{}"'.format(pro_file))
|
||||||
|
c.run('lrelease "{}"'.format(pro_file))
|
||||||
|
|
||||||
|
c.set_make_threaded()
|
||||||
|
build_type_flag = 'debug' if build_type == 'debug' else 'release'
|
||||||
|
qmake_flags = os.environ.get('QMAKE_FLAGS','') + ' CONFIG+=' + build_type_flag
|
||||||
|
c.run('qmake {} "{}"'.format(qmake_flags, pro_file))
|
||||||
|
make_cmd = c.get_make_cmd()
|
||||||
|
c.run(make_cmd)
|
213
share/ci/common.py
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
import os
|
||||||
|
import subprocess as sub
|
||||||
|
import urllib.request
|
||||||
|
from shutil import which
|
||||||
|
import zipfile
|
||||||
|
import tarfile
|
||||||
|
import functools
|
||||||
|
import shutil
|
||||||
|
import multiprocessing
|
||||||
|
import platform
|
||||||
|
import re
|
||||||
|
import ast
|
||||||
|
import hashlib
|
||||||
|
|
||||||
|
|
||||||
|
print = functools.partial(print, flush=True)
|
||||||
|
|
||||||
|
|
||||||
|
def run(cmd, capture_output=False, silent=False):
|
||||||
|
print('>> Running', cmd)
|
||||||
|
if capture_output:
|
||||||
|
result = sub.run(cmd, check=True, shell=True, universal_newlines=True,
|
||||||
|
stdout=sub.PIPE, stderr=sub.STDOUT)
|
||||||
|
if not silent:
|
||||||
|
print(result.stdout)
|
||||||
|
else:
|
||||||
|
if not silent:
|
||||||
|
result = sub.run(cmd, check=True, shell=True)
|
||||||
|
else:
|
||||||
|
result = sub.run(cmd, check=True, shell=True,
|
||||||
|
stdout=sub.DEVNULL, stderr=sub.DEVNULL)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def download(url, out, force=False):
|
||||||
|
print('>> Downloading', url, 'as', out)
|
||||||
|
if not force and os.path.exists(out):
|
||||||
|
print('>>', out, 'already exists')
|
||||||
|
return
|
||||||
|
out_path = os.path.dirname(out)
|
||||||
|
if len(out_path) > 0:
|
||||||
|
os.makedirs(out_path, exist_ok=True)
|
||||||
|
urllib.request.urlretrieve(url, out)
|
||||||
|
|
||||||
|
|
||||||
|
def extract(src, dest):
|
||||||
|
abs_path = os.path.abspath(src)
|
||||||
|
print('>> Extracting', abs_path, 'to', dest)
|
||||||
|
if len(dest) > 0:
|
||||||
|
os.makedirs(dest, exist_ok=True)
|
||||||
|
|
||||||
|
if which('cmake'):
|
||||||
|
out = run('cmake -E tar t "{}"'.format(abs_path),
|
||||||
|
capture_output=True, silent=True)
|
||||||
|
files = out.stdout.split('\n')
|
||||||
|
already_exist = True
|
||||||
|
for file in files:
|
||||||
|
if not os.path.exists(os.path.join(dest, file)):
|
||||||
|
already_exist = False
|
||||||
|
break
|
||||||
|
if already_exist:
|
||||||
|
print('>> All files already exist')
|
||||||
|
return
|
||||||
|
sub.run('cmake -E tar xvf "{}"'.format(abs_path),
|
||||||
|
check=True, shell=True, cwd=dest)
|
||||||
|
return
|
||||||
|
|
||||||
|
is_tar_smth = src.endswith('.tar', 0, src.rfind('.'))
|
||||||
|
if which('7z'):
|
||||||
|
sub.run('7z x "{}" -o"{}"'.format(abs_path, dest),
|
||||||
|
check=True, shell=True, input=b'S\n')
|
||||||
|
|
||||||
|
if is_tar_smth:
|
||||||
|
inner_name = abs_path[:abs_path.rfind('.')]
|
||||||
|
sub.run('7z x "{}" -o"{}"'.format(inner_name, dest),
|
||||||
|
check=True, shell=True, input=b'S\n')
|
||||||
|
return
|
||||||
|
|
||||||
|
if src.endswith('.tar') or is_tar_smth:
|
||||||
|
path = abs_path if platform.system() != "Windows" else os.path.relpath(abs_path)
|
||||||
|
if which('tar'):
|
||||||
|
sub.run('tar xf "{}" --keep-newer-files -C "{}"'.format(path, dest),
|
||||||
|
check=True, shell=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
raise RuntimeError('No archiver to extract {} file'.format(src))
|
||||||
|
|
||||||
|
|
||||||
|
def get_folder_files(path):
|
||||||
|
result = []
|
||||||
|
for root, _, files in os.walk(path):
|
||||||
|
for file in files:
|
||||||
|
result.append(os.path.join(root, file))
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def get_archive_top_dir(path):
|
||||||
|
"""Return first top level folder name in given archive or raises RuntimeError"""
|
||||||
|
with tarfile.open(path) as tar:
|
||||||
|
first = tar.next()
|
||||||
|
if not first is None:
|
||||||
|
result = os.path.dirname(first.path)
|
||||||
|
if len(result) == 0:
|
||||||
|
result = first.path
|
||||||
|
return result
|
||||||
|
raise RuntimeError('Failed to open file or empty archive ' + path)
|
||||||
|
|
||||||
|
|
||||||
|
def archive(files, out):
|
||||||
|
print('>> Archiving', files, 'into', out)
|
||||||
|
if out.endswith('.zip'):
|
||||||
|
arc = zipfile.ZipFile(out, 'w', zipfile.ZIP_DEFLATED)
|
||||||
|
for f in files:
|
||||||
|
arc.write(f)
|
||||||
|
arc.close()
|
||||||
|
return
|
||||||
|
|
||||||
|
if out.endswith('.tar.gz'):
|
||||||
|
arc = tarfile.open(out, 'w|gz')
|
||||||
|
for f in files:
|
||||||
|
arc.add(f)
|
||||||
|
arc.close()
|
||||||
|
return
|
||||||
|
|
||||||
|
raise RuntimeError('No archiver to create {} file'.format(out))
|
||||||
|
|
||||||
|
|
||||||
|
def symlink(src, dest):
|
||||||
|
print('>> Creating symlink', src, '=>', dest)
|
||||||
|
norm_src = os.path.normcase(src)
|
||||||
|
norm_dest = os.path.normcase(dest)
|
||||||
|
if os.path.lexists(norm_dest):
|
||||||
|
os.remove(norm_dest)
|
||||||
|
os.symlink(norm_src, norm_dest,
|
||||||
|
target_is_directory=os.path.isdir(norm_src))
|
||||||
|
|
||||||
|
|
||||||
|
def recreate_dir(path):
|
||||||
|
shutil.rmtree(path, ignore_errors=True)
|
||||||
|
os.mkdir(path)
|
||||||
|
|
||||||
|
|
||||||
|
def add_to_path(entry, prepend=True):
|
||||||
|
path_separator = ';' if platform.system() == "Windows" else ':'
|
||||||
|
os.environ['PATH'] = entry + path_separator + os.environ['PATH']
|
||||||
|
|
||||||
|
|
||||||
|
def get_msvc_env_cmd(bitness='64', msvc_version=''):
|
||||||
|
"""Return environment setup command for running msvc compiler for current platform"""
|
||||||
|
if platform.system() != "Windows":
|
||||||
|
return None
|
||||||
|
|
||||||
|
env_script = msvc_version + '/VC/Auxiliary/Build/vcvars{}.bat'.format(bitness)
|
||||||
|
return '"' + env_script + '"'
|
||||||
|
|
||||||
|
|
||||||
|
def get_cmake_arch_args(bitness='64'):
|
||||||
|
if platform.system() != "Windows":
|
||||||
|
return ''
|
||||||
|
return '-A {}'.format('Win32' if bitness == '32' else 'x64')
|
||||||
|
|
||||||
|
|
||||||
|
def get_make_cmd():
|
||||||
|
"""Return `make` command for current platform"""
|
||||||
|
return 'nmake' if platform.system() == "Windows" else 'make'
|
||||||
|
|
||||||
|
|
||||||
|
def set_make_threaded():
|
||||||
|
"""Adjust environment to run threaded make command"""
|
||||||
|
if platform.system() == "Windows":
|
||||||
|
os.environ['CL'] = '/MP'
|
||||||
|
else:
|
||||||
|
os.environ['MAKEFLAGS'] = '-j{}'.format(multiprocessing.cpu_count())
|
||||||
|
|
||||||
|
|
||||||
|
def is_inside_docker():
|
||||||
|
""" Return True if running in a Docker container """
|
||||||
|
with open('/proc/1/cgroup', 'rt') as f:
|
||||||
|
return 'docker' in f.read()
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_got_path(path):
|
||||||
|
os.makedirs(path, exist_ok=True)
|
||||||
|
|
||||||
|
|
||||||
|
def apply_cmd_env(cmd):
|
||||||
|
"""Run cmd and apply its modified environment"""
|
||||||
|
print('>> Applying env after', cmd)
|
||||||
|
separator = 'env follows'
|
||||||
|
script = 'import os,sys;sys.stdout.buffer.write(str(dict(os.environ)).encode(\\\"utf-8\\\"))'
|
||||||
|
env = sub.run('{} && echo "{}" && python -c "{}"'.format(cmd, separator, script),
|
||||||
|
shell=True, stdout=sub.PIPE, encoding='utf-8')
|
||||||
|
|
||||||
|
stringed = env.stdout[env.stdout.index(separator) + len(separator) + 1:]
|
||||||
|
parsed = ast.literal_eval(stringed)
|
||||||
|
|
||||||
|
for key, value in parsed.items():
|
||||||
|
if key in os.environ and os.environ[key] == value:
|
||||||
|
continue
|
||||||
|
if key in os.environ:
|
||||||
|
print('>>> Changing env', key, '\nfrom\n',
|
||||||
|
os.environ[key], '\nto\n', value)
|
||||||
|
os.environ[key] = value
|
||||||
|
|
||||||
|
|
||||||
|
def md5sum(path):
|
||||||
|
if not os.path.exists(path):
|
||||||
|
return ''
|
||||||
|
md5 = hashlib.md5()
|
||||||
|
with open(path, 'rb') as f:
|
||||||
|
md5.update(f.read())
|
||||||
|
return md5.hexdigest()
|
||||||
|
return ''
|
35
share/ci/config.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
from os import getenv, path
|
||||||
|
import re
|
||||||
|
|
||||||
|
app_name = 'ScreenTranslator'
|
||||||
|
|
||||||
|
target_name = app_name
|
||||||
|
qt_version = '5.15.2'
|
||||||
|
qt_modules = ['qtbase', 'qttools', 'icu',
|
||||||
|
'qttranslations', 'qtx11extras', 'qtwebengine', 'qtwebchannel',
|
||||||
|
'qtdeclarative', 'qtlocation', 'opengl32sw', 'd3dcompiler_47',
|
||||||
|
'qtserialport']
|
||||||
|
qt_dir = path.abspath('qt')
|
||||||
|
ssl_dir = path.abspath('ssl')
|
||||||
|
|
||||||
|
build_dir = path.abspath('build')
|
||||||
|
dependencies_dir = path.abspath('deps')
|
||||||
|
pro_file = path.abspath(path.dirname(__file__) +
|
||||||
|
'/../../screen-translator.pro')
|
||||||
|
test_pro_file = path.abspath(path.dirname(__file__) +
|
||||||
|
'/../../tests/tests.pro')
|
||||||
|
bin_name = 'screen-translator'
|
||||||
|
app_version = 'testing'
|
||||||
|
with open(pro_file, 'r') as f:
|
||||||
|
match = re.search(r'VER=(.*)', f.read())
|
||||||
|
if match:
|
||||||
|
app_version = match.group(1)
|
||||||
|
ts_files_dir = path.abspath(path.dirname(__file__) + '/../../translations')
|
||||||
|
|
||||||
|
os_name = getenv('OS', 'linux')
|
||||||
|
app_version += {'linux': '', 'macos': '-experimental',
|
||||||
|
'win32': '', 'win64': ''}[os_name]
|
||||||
|
bitness = '32' if os_name == 'win32' else '64'
|
||||||
|
msvc_version = getenv('MSVC_VERSION', 'C:/Program Files (x86)/Microsoft Visual Studio/2019/Community')
|
||||||
|
|
||||||
|
build_type = 'release' # 'debug'
|
131
share/ci/get_hunspell.py
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
import common as c
|
||||||
|
from config import bitness, msvc_version, build_dir, dependencies_dir, build_type
|
||||||
|
import os
|
||||||
|
import platform
|
||||||
|
|
||||||
|
c.print('>> Installing hunspell')
|
||||||
|
|
||||||
|
install_dir = dependencies_dir
|
||||||
|
url = 'https://github.com/hunspell/hunspell/files/2573619/hunspell-1.7.0.tar.gz'
|
||||||
|
required_version = '1.7.0'
|
||||||
|
|
||||||
|
|
||||||
|
build_type_flag = 'Debug' if build_type == 'debug' else 'Release'
|
||||||
|
|
||||||
|
cache_file = install_dir + '/hunspell.cache'
|
||||||
|
cache_file_data = required_version + build_type_flag
|
||||||
|
|
||||||
|
|
||||||
|
def check_existing():
|
||||||
|
if not os.path.exists(cache_file):
|
||||||
|
return False
|
||||||
|
with open(cache_file, 'r') as f:
|
||||||
|
cached = f.read()
|
||||||
|
if cached != cache_file_data:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if platform.system() == "Windows":
|
||||||
|
dll = install_dir + '/bin/hunspell.dll'
|
||||||
|
lib = install_dir + '/lib/hunspell.lib'
|
||||||
|
if not os.path.exists(dll) or not os.path.exists(lib):
|
||||||
|
return False
|
||||||
|
elif platform.system() == "Darwin":
|
||||||
|
lib = install_dir + '/lib/libhunspell.dylib'
|
||||||
|
if not os.path.exists(lib):
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
lib = install_dir + '/lib/libhunspell.so'
|
||||||
|
if not os.path.exists(lib):
|
||||||
|
return False
|
||||||
|
|
||||||
|
includes_path = install_dir + '/include/hunspell'
|
||||||
|
if len(c.get_folder_files(includes_path)) == 0:
|
||||||
|
return False
|
||||||
|
|
||||||
|
version_file = install_dir + '/lib/pkgconfig/hunspell.pc'
|
||||||
|
if not os.path.exists(version_file):
|
||||||
|
return False
|
||||||
|
|
||||||
|
with open(version_file, 'rt') as f:
|
||||||
|
lines = f.readlines()
|
||||||
|
for l in lines:
|
||||||
|
if not l.startswith('Version'):
|
||||||
|
continue
|
||||||
|
existing_version = l[9:14] # Version: 1.7.0
|
||||||
|
if existing_version != required_version:
|
||||||
|
return False
|
||||||
|
break
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
if check_existing():
|
||||||
|
c.print('>> Using cached')
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
archive = os.path.basename(url)
|
||||||
|
c.download(url, archive)
|
||||||
|
|
||||||
|
src_dir = os.path.abspath('hunspell_src')
|
||||||
|
c.extract(archive, '.')
|
||||||
|
c.symlink(c.get_archive_top_dir(archive), src_dir)
|
||||||
|
|
||||||
|
c.ensure_got_path(install_dir)
|
||||||
|
|
||||||
|
c.recreate_dir(build_dir)
|
||||||
|
os.chdir(build_dir)
|
||||||
|
|
||||||
|
c.set_make_threaded()
|
||||||
|
|
||||||
|
lib_src = os.path.join(src_dir, 'src', 'hunspell')
|
||||||
|
sources = []
|
||||||
|
with os.scandir(lib_src) as it:
|
||||||
|
for f in it:
|
||||||
|
if not f.is_file() or not f.name.endswith('.cxx'):
|
||||||
|
continue
|
||||||
|
sources.append('${SRC_DIR}/' + f.name)
|
||||||
|
headers = ['${SRC_DIR}/atypes.hxx', '${SRC_DIR}/hunspell.h', '${SRC_DIR}/hunspell.hxx',
|
||||||
|
'${SRC_DIR}/hunvisapi.h', '${SRC_DIR}/w_char.hxx']
|
||||||
|
cmake_file = os.path.join(build_dir, 'CMakeLists.txt')
|
||||||
|
with open(cmake_file, 'w') as f:
|
||||||
|
f.write('project(hunspell)\n')
|
||||||
|
f.write('cmake_minimum_required(VERSION 3.11)\n')
|
||||||
|
f.write('set(SRC_DIR "{}")\n'.format(lib_src).replace('\\', '/'))
|
||||||
|
f.write('\n')
|
||||||
|
f.write('add_library(hunspell SHARED {})\n'.format(' '.join(sources)))
|
||||||
|
f.write('\n')
|
||||||
|
f.write('add_compile_definitions(HAVE_CONFIG_H BUILDING_LIBHUNSPELL)\n')
|
||||||
|
if platform.system() == "Windows":
|
||||||
|
f.write('add_compile_definitions(_WIN32)\n')
|
||||||
|
f.write('\n')
|
||||||
|
f.write('install(FILES {} \
|
||||||
|
DESTINATION include/hunspell)\n'.format(' '.join(headers)))
|
||||||
|
f.write('\n')
|
||||||
|
f.write('install(TARGETS hunspell \
|
||||||
|
RUNTIME DESTINATION bin LIBRARY DESTINATION lib ARCHIVE DESTINATION lib)\n')
|
||||||
|
f.write('\n')
|
||||||
|
f.write('set(prefix "${CMAKE_INSTALL_PREFIX}")\n')
|
||||||
|
f.write('set(VERSION "{}")\n'.format(required_version))
|
||||||
|
f.write('configure_file({}/hunspell.pc.in \
|
||||||
|
${{CMAKE_CURRENT_BINARY_DIR}}/hunspell.pc @ONLY)\n'.format(src_dir.replace('\\', '/')))
|
||||||
|
f.write('install(FILES ${CMAKE_CURRENT_BINARY_DIR}/hunspell.pc \
|
||||||
|
DESTINATION lib/pkgconfig)\n')
|
||||||
|
|
||||||
|
cmake_args = '"{}" -DCMAKE_INSTALL_PREFIX="{}" {}'.format(
|
||||||
|
build_dir, install_dir, c.get_cmake_arch_args(bitness=bitness))
|
||||||
|
|
||||||
|
if platform.system() == "Windows":
|
||||||
|
env_cmd = c.get_msvc_env_cmd(bitness=bitness, msvc_version=msvc_version)
|
||||||
|
c.apply_cmd_env(env_cmd)
|
||||||
|
|
||||||
|
c.set_make_threaded()
|
||||||
|
c.run('cmake {}'.format(cmake_args))
|
||||||
|
build_type_flag = 'Debug' if build_type == 'debug' else 'Release'
|
||||||
|
c.run('cmake --build . --config {}'.format(build_type_flag))
|
||||||
|
c.run('cmake --build . --target install --config {}'.format(build_type_flag))
|
||||||
|
|
||||||
|
with open(cache_file, 'w') as f:
|
||||||
|
f.write(cache_file_data)
|
||||||
|
|
||||||
|
if not check_existing(): # create links
|
||||||
|
c.print('>> Build failed')
|
||||||
|
exit(1)
|
100
share/ci/get_leptonica.py
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
import common as c
|
||||||
|
from config import bitness, msvc_version, build_dir, dependencies_dir, build_type
|
||||||
|
import os
|
||||||
|
import platform
|
||||||
|
|
||||||
|
c.print('>> Installing leptonica')
|
||||||
|
|
||||||
|
install_dir = dependencies_dir
|
||||||
|
url = 'https://github.com/DanBloomberg/leptonica/releases/download/1.82.0/leptonica-1.82.0.tar.gz'
|
||||||
|
required_version = '1.82.0'
|
||||||
|
|
||||||
|
|
||||||
|
build_type_flag = 'Debug' if build_type == 'debug' else 'Release'
|
||||||
|
|
||||||
|
cache_file = install_dir + '/leptonica.cache'
|
||||||
|
cache_file_data = required_version + build_type_flag
|
||||||
|
|
||||||
|
|
||||||
|
def check_existing():
|
||||||
|
if not os.path.exists(cache_file):
|
||||||
|
return False
|
||||||
|
with open(cache_file, 'r') as f:
|
||||||
|
cached = f.read()
|
||||||
|
if cached != cache_file_data:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if platform.system() == "Windows":
|
||||||
|
dll = install_dir + '/bin/leptonica-1.82.0.dll'
|
||||||
|
lib = install_dir + '/lib/leptonica-1.82.0.lib'
|
||||||
|
if not os.path.exists(dll) or not os.path.exists(lib):
|
||||||
|
return False
|
||||||
|
c.symlink(dll, install_dir + '/bin/leptonica.dll')
|
||||||
|
c.symlink(lib, install_dir + '/lib/leptonica.lib')
|
||||||
|
elif platform.system() == "Darwin":
|
||||||
|
lib = install_dir + '/lib/libleptonica.1.82.0.dylib'
|
||||||
|
if not os.path.exists(lib):
|
||||||
|
return False
|
||||||
|
c.symlink(lib, install_dir + '/lib/libleptonica.dylib')
|
||||||
|
else:
|
||||||
|
if not os.path.exists(install_dir + '/lib/libleptonica.so'):
|
||||||
|
return False
|
||||||
|
|
||||||
|
includes_path = install_dir + '/include/leptonica'
|
||||||
|
if len(c.get_folder_files(includes_path)) == 0:
|
||||||
|
return False
|
||||||
|
|
||||||
|
version_file = install_dir + '/lib/cmake/leptonica/LeptonicaConfig-version.cmake'
|
||||||
|
if not os.path.exists(version_file):
|
||||||
|
return False
|
||||||
|
|
||||||
|
with open(version_file, 'rt') as f:
|
||||||
|
existing_version = f.readline()[22:28] # set(Leptonica_VERSION 1.82.0)
|
||||||
|
if existing_version != required_version:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
if check_existing():
|
||||||
|
c.print('>> Using cached')
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
archive = os.path.basename(url)
|
||||||
|
c.download(url, archive)
|
||||||
|
|
||||||
|
src_dir = os.path.abspath('leptonica_src')
|
||||||
|
c.extract(archive, '.')
|
||||||
|
c.symlink(c.get_archive_top_dir(archive), src_dir)
|
||||||
|
|
||||||
|
with open('{}/CMakeLists.txt'.format(src_dir), 'r+') as f:
|
||||||
|
data = f.read()
|
||||||
|
data = data.replace('pkg_check_modules(WEBP', '#pkg_check_modules(WEBP')
|
||||||
|
data = data.replace('if(NOT WEBP', 'if(FALSE')
|
||||||
|
f.seek(0, os.SEEK_SET)
|
||||||
|
f.write(data)
|
||||||
|
|
||||||
|
c.ensure_got_path(install_dir)
|
||||||
|
|
||||||
|
c.recreate_dir(build_dir)
|
||||||
|
os.chdir(build_dir)
|
||||||
|
|
||||||
|
cmake_args = '"{}" -DCMAKE_INSTALL_PREFIX="{}" -DBUILD_SHARED_LIBS=ON \
|
||||||
|
-DSW_BUILD=OFF'.format(src_dir, install_dir,)
|
||||||
|
|
||||||
|
if platform.system() == "Windows":
|
||||||
|
env_cmd = c.get_msvc_env_cmd(bitness=bitness, msvc_version=msvc_version)
|
||||||
|
c.apply_cmd_env(env_cmd)
|
||||||
|
cmake_args += ' ' + c.get_cmake_arch_args(bitness=bitness)
|
||||||
|
|
||||||
|
c.set_make_threaded()
|
||||||
|
c.run('cmake {}'.format(cmake_args))
|
||||||
|
build_type_flag = 'Debug' if build_type == 'debug' else 'Release'
|
||||||
|
c.run('cmake --build . --config {}'.format(build_type_flag))
|
||||||
|
c.run('cmake --build . --target install --config {}'.format(build_type_flag))
|
||||||
|
|
||||||
|
with open(cache_file, 'w') as f:
|
||||||
|
f.write(cache_file_data)
|
||||||
|
|
||||||
|
if not check_existing(): # create links
|
||||||
|
c.print('>> Build failed')
|
||||||
|
exit(1)
|
82
share/ci/get_qt.py
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import common as c
|
||||||
|
from config import qt_modules, qt_version, qt_dir, os_name
|
||||||
|
import sys
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
|
c.print('>> Downloading Qt {} ({}) for {}'.format(
|
||||||
|
qt_version, qt_modules, os_name))
|
||||||
|
|
||||||
|
if os_name == 'linux':
|
||||||
|
os_url = 'linux_x64'
|
||||||
|
kit_arch = 'gcc_64'
|
||||||
|
qt_dir_prefix = '{}/gcc_64'.format(qt_version)
|
||||||
|
elif os_name == 'win32':
|
||||||
|
os_url = 'windows_x86'
|
||||||
|
kit_arch = 'win32_msvc2019'
|
||||||
|
qt_dir_prefix = '{}/msvc2019'.format(qt_version)
|
||||||
|
elif os_name == 'win64':
|
||||||
|
os_url = 'windows_x86'
|
||||||
|
kit_arch = 'win64_msvc2019_64'
|
||||||
|
qt_dir_prefix = '{}/msvc2019_64'.format(qt_version)
|
||||||
|
elif os_name == 'macos':
|
||||||
|
os_url = 'mac_x64'
|
||||||
|
kit_arch = 'clang_64'
|
||||||
|
qt_dir_prefix = '{}/clang_64'.format(qt_version)
|
||||||
|
|
||||||
|
qt_version_dotless = qt_version.replace('.', '')
|
||||||
|
base_url = 'https://download.qt.io/online/qtsdkrepository/{}/desktop/qt5_{}' \
|
||||||
|
.format(os_url, qt_version_dotless)
|
||||||
|
updates_file = 'Updates-{}-{}.xml'.format(qt_version, os_name)
|
||||||
|
c.download(base_url + '/Updates.xml', updates_file)
|
||||||
|
|
||||||
|
updates = ET.parse(updates_file)
|
||||||
|
updates_root = updates.getroot()
|
||||||
|
all_modules = {}
|
||||||
|
for i in updates_root.iter('PackageUpdate'):
|
||||||
|
name = i.find('Name').text
|
||||||
|
if 'debug' in name or not kit_arch in name:
|
||||||
|
continue
|
||||||
|
|
||||||
|
archives = i.find('DownloadableArchives')
|
||||||
|
if archives.text is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
archives_parts = archives.text.split(',')
|
||||||
|
version = i.find('Version').text
|
||||||
|
for archive in archives_parts:
|
||||||
|
archive = archive.strip()
|
||||||
|
parts = archive.split('-')
|
||||||
|
module_name = parts[0]
|
||||||
|
all_modules[module_name] = {'package': name, 'file': version + archive}
|
||||||
|
|
||||||
|
if len(sys.argv) > 1: # handle subcommand
|
||||||
|
if sys.argv[1] == 'list':
|
||||||
|
c.print('Available modules:')
|
||||||
|
for k in iter(sorted(all_modules.keys())):
|
||||||
|
c.print(k, '---', all_modules[k]['file'])
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
for module in qt_modules:
|
||||||
|
if module not in all_modules:
|
||||||
|
c.print('>> Required module {} not available'.format(module))
|
||||||
|
continue
|
||||||
|
file_name = all_modules[module]['file']
|
||||||
|
package = all_modules[module]['package']
|
||||||
|
c.download(base_url + '/' + package + '/' + file_name, file_name)
|
||||||
|
c.extract(file_name, '.')
|
||||||
|
|
||||||
|
c.symlink(qt_dir_prefix, qt_dir)
|
||||||
|
|
||||||
|
c.print('>> Updating license')
|
||||||
|
config_name = qt_dir + '/mkspecs/qconfig.pri'
|
||||||
|
config = ''
|
||||||
|
with open(config_name, 'r') as f:
|
||||||
|
config = f.read()
|
||||||
|
|
||||||
|
config = config.replace('Enterprise', 'OpenSource')
|
||||||
|
config = config.replace('licheck.exe', '')
|
||||||
|
config = config.replace('licheck64', '')
|
||||||
|
config = config.replace('licheck_mac', '')
|
||||||
|
|
||||||
|
with open(config_name, 'w') as f:
|
||||||
|
f.write(config)
|
52
share/ci/get_qt_ssl.py
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import common as c
|
||||||
|
from config import ssl_dir, os_name
|
||||||
|
import sys
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
|
c.print('>> Downloading ssl for Qt for {}'.format(os_name))
|
||||||
|
|
||||||
|
if os_name == 'linux':
|
||||||
|
os_url = 'linux_x64'
|
||||||
|
tool_name = 'tools_openssl_x64'
|
||||||
|
root_path = 'Tools/OpenSSL/binary'
|
||||||
|
elif os_name == 'win32':
|
||||||
|
os_url = 'windows_x86'
|
||||||
|
tool_name = 'tools_openssl_x86'
|
||||||
|
root_path = 'Tools/OpenSSL/Win_x86'
|
||||||
|
elif os_name == 'win64':
|
||||||
|
os_url = 'windows_x86'
|
||||||
|
tool_name = 'tools_openssl_x64'
|
||||||
|
root_path = 'Tools/OpenSSL/Win_x64'
|
||||||
|
elif os_name == 'macos':
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
base_url = 'https://download.qt.io/online/qtsdkrepository/{}/desktop/{}' \
|
||||||
|
.format(os_url, tool_name)
|
||||||
|
updates_file = 'Updates-{}-{}.xml'.format(tool_name, os_name)
|
||||||
|
c.download(base_url + '/Updates.xml', updates_file)
|
||||||
|
|
||||||
|
updates = ET.parse(updates_file)
|
||||||
|
updates_root = updates.getroot()
|
||||||
|
url = ''
|
||||||
|
file_name = ''
|
||||||
|
for i in updates_root.iter('PackageUpdate'):
|
||||||
|
name = i.find('Name').text
|
||||||
|
if not 'qt.tools.openssl' in name:
|
||||||
|
continue
|
||||||
|
|
||||||
|
archives = i.find('DownloadableArchives')
|
||||||
|
if archives.text is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
version = i.find('Version').text
|
||||||
|
url = base_url + '/' + name + '/' + version + archives.text
|
||||||
|
file_name = archives.text
|
||||||
|
|
||||||
|
if len(url) == 0:
|
||||||
|
c.print('>> No ssl url found')
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
c.download(url, file_name)
|
||||||
|
c.extract(file_name, '.')
|
||||||
|
|
||||||
|
c.symlink(root_path, ssl_dir)
|
98
share/ci/get_tesseract.py
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
import common as c
|
||||||
|
from config import bitness, msvc_version, build_dir, dependencies_dir, build_type
|
||||||
|
import os
|
||||||
|
import platform
|
||||||
|
|
||||||
|
c.print('>> Installing tesseract')
|
||||||
|
|
||||||
|
install_dir = dependencies_dir
|
||||||
|
required_version = '5.2.0'
|
||||||
|
url = 'https://github.com/tesseract-ocr/tesseract/archive/{}.tar.gz'.format(required_version)
|
||||||
|
|
||||||
|
build_type_flag = 'Debug' if build_type == 'debug' else 'Release'
|
||||||
|
|
||||||
|
cache_file = install_dir + '/tesseract.cache'
|
||||||
|
cache_file_data = required_version + build_type_flag
|
||||||
|
|
||||||
|
def check_existing():
|
||||||
|
if not os.path.exists(cache_file):
|
||||||
|
return False
|
||||||
|
with open(cache_file, 'r') as f:
|
||||||
|
cached = f.read()
|
||||||
|
if cached != cache_file_data:
|
||||||
|
return False
|
||||||
|
|
||||||
|
includes_path = install_dir + '/include/tesseract'
|
||||||
|
if len(c.get_folder_files(includes_path)) == 0:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if platform.system() == "Windows":
|
||||||
|
file_name_ver = required_version[0] + required_version[2]
|
||||||
|
dll = install_dir + '/bin/tesseract{}.dll'.format(file_name_ver)
|
||||||
|
lib = install_dir + '/lib/tesseract{}.lib'.format(file_name_ver)
|
||||||
|
if not os.path.exists(dll) or not os.path.exists(lib):
|
||||||
|
return False
|
||||||
|
c.symlink(dll, install_dir + '/bin/tesseract.dll')
|
||||||
|
c.symlink(lib, install_dir + '/lib/tesseract.lib')
|
||||||
|
elif platform.system() == "Darwin":
|
||||||
|
lib = install_dir + '/lib/libtesseract.{}.dylib'.format(required_version)
|
||||||
|
if not os.path.exists(lib):
|
||||||
|
return False
|
||||||
|
c.symlink(lib, install_dir + '/lib/libtesseract.dylib')
|
||||||
|
else:
|
||||||
|
lib = install_dir + '/lib/libtesseract.so.{}'.format(required_version)
|
||||||
|
if not os.path.exists(lib):
|
||||||
|
return False
|
||||||
|
c.symlink(lib, install_dir + '/lib/libtesseract.so')
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
if check_existing() and not 'FORCE' in os.environ:
|
||||||
|
c.print('>> Using cached')
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
archive = 'tesseract-' + os.path.basename(url)
|
||||||
|
c.download(url, archive)
|
||||||
|
|
||||||
|
src_dir = os.path.abspath('tesseract_src')
|
||||||
|
c.extract(archive, '.')
|
||||||
|
c.symlink(c.get_archive_top_dir(archive), src_dir)
|
||||||
|
|
||||||
|
c.ensure_got_path(install_dir)
|
||||||
|
|
||||||
|
c.recreate_dir(build_dir)
|
||||||
|
os.chdir(build_dir)
|
||||||
|
|
||||||
|
cmake_args = '"{0}" \
|
||||||
|
-DCMAKE_INSTALL_PREFIX="{1}" \
|
||||||
|
-DLeptonica_DIR="{1}/cmake" \
|
||||||
|
-DSW_BUILD=OFF \
|
||||||
|
-DBUILD_TRAINING_TOOLS=OFF \
|
||||||
|
-DBUILD_TESTS=OFF \
|
||||||
|
-DBUILD_SHARED_LIBS=ON \
|
||||||
|
-DDISABLE_CURL=ON \
|
||||||
|
-DDISABLE_ARCHIVE=ON \
|
||||||
|
-DUSE_SYSTEM_ICU=ON \
|
||||||
|
-DENABLE_LTO=ON \
|
||||||
|
-DGRAPHICS_DISABLED=ON \
|
||||||
|
-DDISABLED_LEGACY_ENGINE=ON \
|
||||||
|
'.format(src_dir, install_dir)
|
||||||
|
|
||||||
|
if platform.system() == "Windows":
|
||||||
|
env_cmd = c.get_msvc_env_cmd(bitness=bitness, msvc_version=msvc_version)
|
||||||
|
c.apply_cmd_env(env_cmd)
|
||||||
|
cmake_args += ' ' + c.get_cmake_arch_args(bitness=bitness)
|
||||||
|
|
||||||
|
c.set_make_threaded()
|
||||||
|
c.run('cmake {}'.format(cmake_args))
|
||||||
|
|
||||||
|
c.run('cmake --build . --config {}'.format(build_type_flag))
|
||||||
|
c.run('cmake --build . --target install --config {}'.format(build_type_flag))
|
||||||
|
|
||||||
|
with open(cache_file, 'w') as f:
|
||||||
|
f.write(cache_file_data)
|
||||||
|
|
||||||
|
if not check_existing(): # add suffix
|
||||||
|
c.print('>> Build failed')
|
||||||
|
exit(1)
|
19
share/ci/macdeploy.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import common as c
|
||||||
|
from config import *
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
tag = os.environ.get('TAG', '')
|
||||||
|
artifact_name = '{}-{}{}.dmg'.format(app_name, app_version, tag)
|
||||||
|
if len(sys.argv) > 1 and sys.argv[1] == 'artifact_name': # subcommand
|
||||||
|
c.print(artifact_name)
|
||||||
|
exit(0)
|
||||||
|
artifact_path = os.path.abspath(artifact_name)
|
||||||
|
|
||||||
|
c.print('>> Making mac deploy')
|
||||||
|
|
||||||
|
os.chdir(build_dir)
|
||||||
|
build_target = build_dir + '/' + target_name + '.app'
|
||||||
|
built_dmg = build_dir + '/' + target_name + '.dmg'
|
||||||
|
c.run('{}/bin/macdeployqt "{}" -dmg'.format(qt_dir, build_target))
|
||||||
|
os.rename(built_dmg, artifact_path)
|
43
share/ci/release.py
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import os
|
||||||
|
import platform
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
here = os.path.dirname(__file__)
|
||||||
|
|
||||||
|
|
||||||
|
def r_out(script, args):
|
||||||
|
return subprocess.run([sys.executable, os.path.join(here, script)] + args, check=True, stdout=subprocess.PIPE).stdout.decode('utf-8').strip()
|
||||||
|
|
||||||
|
|
||||||
|
if len(sys.argv) > 1 and sys.argv[1] == 'artifact_name':
|
||||||
|
artifact_name = ''
|
||||||
|
if platform.system() == "Linux":
|
||||||
|
artifact_name = r_out('appimage.py', ['artifact_name'])
|
||||||
|
if platform.system() == "Windows":
|
||||||
|
artifact_name = r_out('windeploy.py', ['artifact_name'])
|
||||||
|
if platform.system() == "Darwin":
|
||||||
|
artifact_name = r_out('macdeploy.py', ['artifact_name'])
|
||||||
|
print(artifact_name)
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
def r(script):
|
||||||
|
return subprocess.run([sys.executable, os.path.join(here, script)], check=True)
|
||||||
|
|
||||||
|
|
||||||
|
r('get_qt.py')
|
||||||
|
r('get_qt_ssl.py')
|
||||||
|
r('get_leptonica.py')
|
||||||
|
r('get_tesseract.py')
|
||||||
|
|
||||||
|
r('get_hunspell.py')
|
||||||
|
r('test.py')
|
||||||
|
r('build.py')
|
||||||
|
|
||||||
|
if platform.system() == "Linux":
|
||||||
|
r('appimage.py')
|
||||||
|
if platform.system() == "Windows":
|
||||||
|
r('windeploy.py')
|
||||||
|
if platform.system() == "Darwin":
|
||||||
|
r('macdeploy.py')
|
84
share/ci/sourceforge.py
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import common as c
|
||||||
|
from config import app_version
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import io
|
||||||
|
import urllib
|
||||||
|
import platform
|
||||||
|
from paramiko import SSHClient, WarningPolicy, RSAKey, SSHException
|
||||||
|
|
||||||
|
files = sys.argv[1:]
|
||||||
|
c.print('>> Uploading artifacts to sourceforge {}'.format(files))
|
||||||
|
|
||||||
|
for f in files:
|
||||||
|
if not os.path.exists(f):
|
||||||
|
c.print('>> File "{}" not exists. Exiting'.format(f))
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
pkey_name = 'SF_PKEY'
|
||||||
|
if not pkey_name in os.environ:
|
||||||
|
c.print('>> No sf pkey set. Exiting')
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
api_name = 'SF_API'
|
||||||
|
if not api_name in os.environ:
|
||||||
|
c.print('>> No sf api set. Exiting')
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
pkey_data = io.StringIO(os.environ[pkey_name])
|
||||||
|
pkey = None
|
||||||
|
try:
|
||||||
|
pkey = RSAKey.from_private_key(pkey_data)
|
||||||
|
except SSHException as e:
|
||||||
|
c.print('>> Sf pkey error "{}". Exiting'.format(e))
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
ssh = SSHClient()
|
||||||
|
ssh.set_missing_host_key_policy(WarningPolicy())
|
||||||
|
ssh.connect('frs.sourceforge.net', username='onemoregres', pkey=pkey)
|
||||||
|
sftp = ssh.open_sftp()
|
||||||
|
target_path = 'bin/v' + app_version
|
||||||
|
try:
|
||||||
|
remote_path = '/home/frs/project/screen-translator/'
|
||||||
|
for part in target_path.split('/'):
|
||||||
|
existing = sftp.listdir(remote_path)
|
||||||
|
remote_path = remote_path + part + '/'
|
||||||
|
if not part in existing:
|
||||||
|
sftp.mkdir(remote_path)
|
||||||
|
existing = sftp.listdir(remote_path)
|
||||||
|
for f in files:
|
||||||
|
file_name = os.path.basename(f)
|
||||||
|
if file_name in existing:
|
||||||
|
c.print('>> File "{}" already exists. Removing'.format(file_name))
|
||||||
|
sftp.remove(remote_path + file_name)
|
||||||
|
sftp.put(f, remote_path + file_name)
|
||||||
|
except IOError as err:
|
||||||
|
c.print('>> SFTP error "{}". Exiting'.format(err))
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
sftp.close()
|
||||||
|
ssh.close()
|
||||||
|
|
||||||
|
api_key = os.environ[api_name]
|
||||||
|
base_url = 'https://sourceforge.net/projects/screen-translator/files/' + target_path
|
||||||
|
for f in files:
|
||||||
|
file_name = os.path.basename(f)
|
||||||
|
url = base_url + '/' + file_name
|
||||||
|
|
||||||
|
data = {'api_key': api_key}
|
||||||
|
if platform.system() == "Windows":
|
||||||
|
data['default'] = 'windows'
|
||||||
|
elif platform.system() == "Darwin":
|
||||||
|
data['default'] = 'mac'
|
||||||
|
else:
|
||||||
|
data['default'] = 'linux'
|
||||||
|
|
||||||
|
raw_data = urllib.parse.urlencode(data).encode('utf-8')
|
||||||
|
try:
|
||||||
|
request = urllib.request.Request(
|
||||||
|
url, method='PUT', headers={"Accept": "application/json"}, data=raw_data)
|
||||||
|
with urllib.request.urlopen(request) as r:
|
||||||
|
pass
|
||||||
|
c.print('>> Updated info for "{}"'.format(url), r.status, r.reason)
|
||||||
|
except Exception as e:
|
||||||
|
c.print('>> Update info for "{}" failed {}'.format(url, e))
|
25
share/ci/test.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import common as c
|
||||||
|
from config import *
|
||||||
|
import os
|
||||||
|
import platform
|
||||||
|
import glob
|
||||||
|
|
||||||
|
c.print('>> Testing {} on {}'.format(app_name, os_name))
|
||||||
|
|
||||||
|
c.add_to_path(os.path.abspath(qt_dir + '/bin'))
|
||||||
|
|
||||||
|
if platform.system() == "Windows":
|
||||||
|
env_cmd = c.get_msvc_env_cmd(bitness=bitness, msvc_version=msvc_version)
|
||||||
|
c.apply_cmd_env(env_cmd)
|
||||||
|
|
||||||
|
c.recreate_dir(build_dir)
|
||||||
|
os.chdir(build_dir)
|
||||||
|
|
||||||
|
c.set_make_threaded()
|
||||||
|
c.run('qmake {} "{}"'.format(os.environ.get('QMAKE_FLAGS', ''), test_pro_file))
|
||||||
|
make_cmd = c.get_make_cmd()
|
||||||
|
c.run(make_cmd)
|
||||||
|
|
||||||
|
for file in glob.glob('./**/tests*', recursive=True):
|
||||||
|
print(file)
|
||||||
|
c.run(file, silent=False)
|
57
share/ci/windeploy.py
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import common as c
|
||||||
|
from config import *
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import shutil
|
||||||
|
from glob import glob
|
||||||
|
|
||||||
|
tag = os.environ.get('TAG', '')
|
||||||
|
artifact_name = '{}-{}{}-{}.zip'.format(app_name, app_version, tag, os_name)
|
||||||
|
if len(sys.argv) > 1 and sys.argv[1] == 'artifact_name': # subcommand
|
||||||
|
c.print(artifact_name)
|
||||||
|
exit(0)
|
||||||
|
artifact_path = os.path.abspath(artifact_name)
|
||||||
|
|
||||||
|
c.print('>> Making win deploy')
|
||||||
|
|
||||||
|
if os_name.startswith('win'):
|
||||||
|
env_cmd = c.get_msvc_env_cmd(bitness=bitness, msvc_version=msvc_version)
|
||||||
|
c.apply_cmd_env(env_cmd)
|
||||||
|
|
||||||
|
pwd = os.getcwd()
|
||||||
|
os.chdir(build_dir)
|
||||||
|
|
||||||
|
install_dir = os.path.abspath(app_name)
|
||||||
|
c.recreate_dir(install_dir)
|
||||||
|
|
||||||
|
c.run('nmake INSTALL_ROOT="{0}" DESTDIR="{0}" install'.format(install_dir))
|
||||||
|
c.run('{}/bin/windeployqt.exe "{}"'.format(qt_dir, install_dir))
|
||||||
|
|
||||||
|
vcredist_for_ssl_url = ''
|
||||||
|
vcredist_for_ssl_file = ''
|
||||||
|
if bitness == '32':
|
||||||
|
vcredist_for_ssl_url = 'https://download.microsoft.com/download/C/6/D/C6D0FD4E-9E53-4897-9B91-836EBA2AACD3/vcredist_x86.exe'
|
||||||
|
vcredist_for_ssl_file = 'vc_redist.x86.2010.exe'
|
||||||
|
else:
|
||||||
|
vcredist_for_ssl_url = 'https://download.microsoft.com/download/A/8/0/A80747C3-41BD-45DF-B505-E9710D2744E0/vcredist_x64.exe'
|
||||||
|
vcredist_for_ssl_file = 'vc_redist.x64.2010.exe'
|
||||||
|
|
||||||
|
c.download(vcredist_for_ssl_url, os.path.join(install_dir, vcredist_for_ssl_file))
|
||||||
|
|
||||||
|
libs_dir = os.path.join(dependencies_dir, 'bin')
|
||||||
|
for file in os.scandir(libs_dir):
|
||||||
|
if file.is_file(follow_symlinks=False) and file.name.endswith('.dll'):
|
||||||
|
full_name = os.path.join(libs_dir, file.name)
|
||||||
|
c.print('>> Copying {} to {}'.format(full_name, install_dir))
|
||||||
|
shutil.copy(full_name, install_dir)
|
||||||
|
|
||||||
|
for f in glob(ssl_dir + '/bin/*.dll'):
|
||||||
|
c.print('>> Copying {} to {}'.format(f, install_dir))
|
||||||
|
shutil.copy(f, install_dir)
|
||||||
|
|
||||||
|
open(os.path.join(install_dir, 'qt.conf'), 'a').close() # fix for non-latin paths
|
||||||
|
|
||||||
|
c.archive(c.get_folder_files(os.path.relpath(install_dir)), artifact_path)
|
||||||
|
|
||||||
|
bin_path = install_dir + '\\' + bin_name + '.exe'
|
||||||
|
c.print('>> Md5 {} {}'.format(bin_path, c.md5sum(bin_path)))
|
BIN
share/images/STIcon.xcf
Normal file
BIN
share/images/STIconBlue.png
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
share/images/STIconGreen.png
Normal file
After Width: | Height: | Size: 4.1 KiB |
BIN
share/images/STIconOrange.png
Normal file
After Width: | Height: | Size: 3.7 KiB |
BIN
share/images/STIconRed.png
Normal file
After Width: | Height: | Size: 4.0 KiB |
BIN
share/images/debug.png
Normal file
After Width: | Height: | Size: 641 B |
BIN
share/images/debug@2x.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
share/images/icon.icns
Normal file
BIN
share/images/icon.ico
Normal file
After Width: | Height: | Size: 97 KiB |
BIN
share/images/loadImages.png
Normal file
After Width: | Height: | Size: 894 B |
BIN
share/images/loadImages@2x.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
share/images/screentranslator.png
Normal file
After Width: | Height: | Size: 3.9 KiB |
13
share/mirror/Dockerfile
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
FROM alpine:latest
|
||||||
|
|
||||||
|
ADD entrypoint.sh /entrypoint.sh
|
||||||
|
|
||||||
|
RUN \
|
||||||
|
addgroup -g 1200 -S app && \
|
||||||
|
adduser -G app -u 1200 -S app && \
|
||||||
|
apk add --upgrade --no-cache git zip && \
|
||||||
|
chmod +x /entrypoint.sh
|
||||||
|
|
||||||
|
USER app
|
||||||
|
VOLUME [ "/git", "/packed" ]
|
||||||
|
ENTRYPOINT ["/entrypoint.sh"]
|
15
share/mirror/docker-compose.yml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
version: '3'
|
||||||
|
services:
|
||||||
|
mirror:
|
||||||
|
build: .
|
||||||
|
image: gres/st_mirror
|
||||||
|
restart: always
|
||||||
|
container_name: st_mirror
|
||||||
|
logging:
|
||||||
|
driver: json-file
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "5"
|
||||||
|
volumes:
|
||||||
|
- ./git:/git
|
||||||
|
- ./packed:/packed
|
51
share/mirror/entrypoint.sh
Executable file
@ -0,0 +1,51 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
pack() {
|
||||||
|
mkdir -p "$2"
|
||||||
|
for f in $(ls $1); do
|
||||||
|
source="$1/$f"
|
||||||
|
target="$2/$f"
|
||||||
|
if [ -d "$source" ]; then
|
||||||
|
pack "$source" "$target"
|
||||||
|
elif [ -f "$source" ]; then
|
||||||
|
if [ "$target.zip" -nt "$source" ]; then
|
||||||
|
echo "$source is up to date"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
tmp=/tmp/archive.zip
|
||||||
|
echo "packing $source -> $tmp"
|
||||||
|
ls -l "$source"
|
||||||
|
zip -9 -j "$tmp" "$source"
|
||||||
|
|
||||||
|
echo "moving $tmp -> $target.zip"
|
||||||
|
mv "$tmp" "$target.zip"
|
||||||
|
chmod 444 "$target.zip"
|
||||||
|
ls -l "$target.zip"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
mirror() {
|
||||||
|
cur="$(pwd)"
|
||||||
|
url="$1"
|
||||||
|
dir="$2"
|
||||||
|
git_dir="/git/$dir"
|
||||||
|
pack_dir="/packed/$dir"
|
||||||
|
echo $url $git_dir $pack_dir
|
||||||
|
if [ -d $git_dir ]; then
|
||||||
|
echo "fetching"
|
||||||
|
cd $git_dir && git fetch --depth=1 origin master
|
||||||
|
else
|
||||||
|
echo "cloning"
|
||||||
|
git clone --depth=1 --single-branch "$url" $git_dir
|
||||||
|
fi
|
||||||
|
echo "packing"
|
||||||
|
pack "$git_dir" "$pack_dir"
|
||||||
|
}
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
mirror 'git://anongit.freedesktop.org/libreoffice/dictionaries' 'dictionaries'
|
||||||
|
mirror 'https://github.com/tesseract-ocr/tessdata_best.git' 'tessdata_best'
|
||||||
|
echo "sleeping"
|
||||||
|
sleep 6h
|
||||||
|
done
|
9
share/screentranslator.desktop
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Comment=OCR and translation tool
|
||||||
|
Exec=screen-translator
|
||||||
|
GenericName=Screen Translator
|
||||||
|
Name=ScreenTranslator
|
||||||
|
Icon=screentranslator
|
||||||
|
Terminal=false
|
||||||
|
Type=Application
|
||||||
|
Categories=Utility;Core;Qt;
|
1248
share/translations/screentranslator_he.ts
Normal file
1544
share/translations/screentranslator_ru.ts
Normal file
1578
share/uncrustify.cfg
Normal file
106
share/updates/hunspell.py
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
def parse_language_names():
|
||||||
|
root = os.path.abspath(os.path.basename(__file__) + '/../../..')
|
||||||
|
lines = []
|
||||||
|
with open(root + '/src/languagecodes.cpp', 'r') as d:
|
||||||
|
lines = d.readlines()
|
||||||
|
result = {}
|
||||||
|
for line in lines:
|
||||||
|
if line.startswith('//'):
|
||||||
|
continue
|
||||||
|
all = re.findall(r'"(.*?)"', line)
|
||||||
|
if len(all) != 6:
|
||||||
|
continue
|
||||||
|
result[all[2]] = all[5]
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
print("Usage:", sys.argv[0], "<dict_dir> [<download_url>]")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
dict_dir = sys.argv[1]
|
||||||
|
|
||||||
|
download_url = "https://cgit.freedesktop.org/libreoffice/dictionaries/plain"
|
||||||
|
if len(sys.argv) > 2:
|
||||||
|
download_url = sys.argv[2]
|
||||||
|
|
||||||
|
mirror_url = "https://translator.gres.biz/resources/dictionaries"
|
||||||
|
|
||||||
|
language_names = parse_language_names()
|
||||||
|
|
||||||
|
preferred = ['sr.aff', 'sv_FI.aff',
|
||||||
|
'en_US.aff', 'de_DE_frami.aff', 'nb_NO.aff']
|
||||||
|
|
||||||
|
files = {}
|
||||||
|
it = os.scandir(dict_dir)
|
||||||
|
for d in it:
|
||||||
|
if not d.is_dir():
|
||||||
|
continue
|
||||||
|
|
||||||
|
lang = d.name
|
||||||
|
if '_' in lang:
|
||||||
|
lang = lang[0:lang.index('_')]
|
||||||
|
|
||||||
|
affs = []
|
||||||
|
fit = os.scandir(os.path.join(dict_dir, d.name))
|
||||||
|
for f in fit:
|
||||||
|
if not f.is_file or not f.name.endswith('.aff'):
|
||||||
|
continue
|
||||||
|
affs.append(f.name)
|
||||||
|
|
||||||
|
aff = ''
|
||||||
|
if len(affs) == 0:
|
||||||
|
continue
|
||||||
|
if len(affs) == 1:
|
||||||
|
aff = affs[0]
|
||||||
|
else:
|
||||||
|
for p in preferred:
|
||||||
|
if p in affs:
|
||||||
|
aff = p
|
||||||
|
break
|
||||||
|
|
||||||
|
if len(aff) == 0:
|
||||||
|
print('no aff for', lang, affs)
|
||||||
|
continue
|
||||||
|
|
||||||
|
aff = os.path.join(d.name, aff)
|
||||||
|
dic = aff[:aff.rindex('.')] + '.dic'
|
||||||
|
if not os.path.exists(os.path.join(dict_dir, dic)):
|
||||||
|
print('no dic exists', dic)
|
||||||
|
|
||||||
|
files[lang] = [aff, dic]
|
||||||
|
|
||||||
|
|
||||||
|
print(',"correction": {')
|
||||||
|
comma = ''
|
||||||
|
unknown_names = []
|
||||||
|
for lang in sorted(files.keys()):
|
||||||
|
file_names = files[lang]
|
||||||
|
if not lang in language_names:
|
||||||
|
unknown_names.append(lang)
|
||||||
|
continue
|
||||||
|
lang_name = language_names[lang]
|
||||||
|
print(' {}"{}":{{"files":['.format(comma, lang_name))
|
||||||
|
comma = ', '
|
||||||
|
lang_comma = ''
|
||||||
|
for file_name in file_names:
|
||||||
|
git_cmd = ['git', 'log', '-1', '--pretty=format:%cI', file_name]
|
||||||
|
date = subprocess.run(git_cmd, cwd=dict_dir, universal_newlines=True,
|
||||||
|
stdout=subprocess.PIPE, check=True).stdout
|
||||||
|
size = os.path.getsize(os.path.join(dict_dir, file_name))
|
||||||
|
installed = lang + file_name[file_name.index('/'):]
|
||||||
|
mirror = ',"' + mirror_url + '/' + file_name + \
|
||||||
|
'.zip"' if len(mirror_url) > 0 else ''
|
||||||
|
print(' {}{{"url":["{}/{}"{}], "path":"$hunspell$/{}", "date":"{}", "size":{}}}'.format(
|
||||||
|
lang_comma, download_url, file_name, mirror, installed, date, size))
|
||||||
|
lang_comma = ','
|
||||||
|
print(' ]}')
|
||||||
|
print('}')
|
||||||
|
|
||||||
|
print('unknown names', unknown_names)
|
68
share/updates/tessdata.py
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
def parse_language_names():
|
||||||
|
root = os.path.abspath(os.path.basename(__file__) + '/../../..')
|
||||||
|
lines = []
|
||||||
|
with open(root + '/src/languagecodes.cpp', 'r') as f:
|
||||||
|
lines = f.readlines()
|
||||||
|
result = {}
|
||||||
|
for line in lines:
|
||||||
|
all = re.findall(r'"(.*?)"', line)
|
||||||
|
if len(all) != 6:
|
||||||
|
continue
|
||||||
|
result[all[3]] = all[5]
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
print("Usage:", sys.argv[0], "<tessdata_dir> [<download_url>]")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
tessdata_dir = sys.argv[1]
|
||||||
|
|
||||||
|
download_url = "https://github.com/tesseract-ocr/tessdata_best/raw/master"
|
||||||
|
if len(sys.argv) > 2:
|
||||||
|
download_url = sys.argv[2]
|
||||||
|
|
||||||
|
mirror_url = "https://translator.gres.biz/resources/tessdata_best"
|
||||||
|
|
||||||
|
language_names = parse_language_names()
|
||||||
|
|
||||||
|
files = {}
|
||||||
|
it = os.scandir(tessdata_dir)
|
||||||
|
for f in it:
|
||||||
|
if not f.is_file() or f.name in ["LICENSE", "README.md"]:
|
||||||
|
continue
|
||||||
|
name = f.name[:f.name.index('.')]
|
||||||
|
if len(name) == 0:
|
||||||
|
continue
|
||||||
|
files.setdefault(name, []).append(f.name)
|
||||||
|
|
||||||
|
print(',"recognizers": {')
|
||||||
|
comma = ''
|
||||||
|
unknown_names = []
|
||||||
|
for name in sorted(files.keys()):
|
||||||
|
file_names = files[name]
|
||||||
|
if not name in language_names:
|
||||||
|
unknown_names.append(name)
|
||||||
|
else:
|
||||||
|
name = language_names[name]
|
||||||
|
print(' {}"{}":{{"files":['.format(comma, name))
|
||||||
|
comma = ', '
|
||||||
|
for file_name in file_names:
|
||||||
|
git_cmd = ['git', 'log', '-1', '--pretty=format:%cI', file_name]
|
||||||
|
date = subprocess.run(git_cmd, cwd=tessdata_dir, universal_newlines=True,
|
||||||
|
stdout=subprocess.PIPE, check=True).stdout
|
||||||
|
size = os.path.getsize(os.path.join(tessdata_dir, file_name))
|
||||||
|
mirror = ',"' + mirror_url + '/' + file_name + \
|
||||||
|
'.zip"' if len(mirror_url) > 0 else ''
|
||||||
|
print(' {{"url":["{}/{}"{}], "path":"$tessdata$/{}", "date":"{}", "size":{}}}'.format(
|
||||||
|
download_url, file_name, mirror, file_name, date, size))
|
||||||
|
print(' ]}')
|
||||||
|
print('}')
|
||||||
|
|
||||||
|
print('unknown names', unknown_names)
|
38
share/updates/translators.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import hashlib
|
||||||
|
|
||||||
|
download_url = "https://raw.githubusercontent.com/OneMoreGres/ScreenTranslator/master"
|
||||||
|
|
||||||
|
if len(sys.argv) > 1:
|
||||||
|
download_url = sys.argv[1]
|
||||||
|
|
||||||
|
subdir = 'translators'
|
||||||
|
root = os.path.abspath(os.path.basename(__file__) + '/../../..')
|
||||||
|
translators_dir = root + '/' + subdir
|
||||||
|
|
||||||
|
files = {}
|
||||||
|
it = os.scandir(translators_dir)
|
||||||
|
for f in it:
|
||||||
|
if not f.is_file() or not f.name.endswith('.js'):
|
||||||
|
continue
|
||||||
|
name = f.name[:f.name.index('.')]
|
||||||
|
files[name] = f.name
|
||||||
|
|
||||||
|
print(',"translators":{')
|
||||||
|
comma = ''
|
||||||
|
for name in sorted(files.keys()):
|
||||||
|
file_name = files[name]
|
||||||
|
print(' {}"{}": {{"files":['.format(comma, name))
|
||||||
|
comma = ','
|
||||||
|
md5 = hashlib.md5()
|
||||||
|
size = 0
|
||||||
|
with open(os.path.join(translators_dir, file_name), 'rb') as f:
|
||||||
|
data = f.read()
|
||||||
|
size = len(data)
|
||||||
|
md5.update(data)
|
||||||
|
print(' {{"url":"{}/{}", "path":"$translators$/{}", "md5":"{}", "size":{}}}'.format(
|
||||||
|
download_url, subdir + '/' + file_name, file_name,
|
||||||
|
md5.hexdigest(), size))
|
||||||
|
print(' ]}')
|
||||||
|
print('}')
|
71
src/capture/capturearea.cpp
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
#include "capturearea.h"
|
||||||
|
#include "settings.h"
|
||||||
|
#include "task.h"
|
||||||
|
|
||||||
|
CaptureArea::CaptureArea(const QRect &rect, const Settings &settings)
|
||||||
|
: rect_(rect)
|
||||||
|
, doTranslation_(settings.doTranslation)
|
||||||
|
, useHunspell_(settings.useHunspell)
|
||||||
|
, sourceLanguage_(settings.sourceLanguage)
|
||||||
|
, targetLanguage_(settings.targetLanguage)
|
||||||
|
, translators_(settings.translators)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
TaskPtr CaptureArea::task(const QPixmap &pixmap,
|
||||||
|
const QPoint &pixmapOffset) const
|
||||||
|
{
|
||||||
|
if (pixmap.isNull() || !isValid())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
auto task = std::make_shared<Task>();
|
||||||
|
task->generation = generation_;
|
||||||
|
task->useHunspell = useHunspell_;
|
||||||
|
task->captured = pixmap.copy(rect_);
|
||||||
|
task->capturePoint = pixmapOffset + rect_.topLeft();
|
||||||
|
task->sourceLanguage = sourceLanguage_;
|
||||||
|
if (task->sourceLanguage.isEmpty())
|
||||||
|
task->error += QObject::tr("No source language set");
|
||||||
|
|
||||||
|
if (doTranslation_ && !translators_.isEmpty()) {
|
||||||
|
task->targetLanguage = targetLanguage_;
|
||||||
|
task->translators = translators_;
|
||||||
|
if (task->targetLanguage.isEmpty()) {
|
||||||
|
task->error += (task->error.isEmpty() ? "" : ", ") +
|
||||||
|
QObject::tr("No target language set");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CaptureArea::setGeneration(uint generation)
|
||||||
|
{
|
||||||
|
generation_ = generation;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CaptureArea::isValid() const
|
||||||
|
{
|
||||||
|
return !(rect_.width() < 3 || rect_.height() < 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
const QRect &CaptureArea::rect() const
|
||||||
|
{
|
||||||
|
return rect_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CaptureArea::setRect(const QRect &rect)
|
||||||
|
{
|
||||||
|
rect_ = rect;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString CaptureArea::toolTip() const
|
||||||
|
{
|
||||||
|
return doTranslation_ ? sourceLanguage_ + "->" + targetLanguage_
|
||||||
|
: sourceLanguage_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CaptureArea::isLocked() const
|
||||||
|
{
|
||||||
|
return isLocked_;
|
||||||
|
}
|
35
src/capture/capturearea.h
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "stfwd.h"
|
||||||
|
|
||||||
|
#include <QRect>
|
||||||
|
#include <QStringList>
|
||||||
|
|
||||||
|
class QPixmap;
|
||||||
|
|
||||||
|
class CaptureArea
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CaptureArea(const QRect& rect, const Settings& settings);
|
||||||
|
TaskPtr task(const QPixmap& pixmap, const QPoint& pixmapOffset) const;
|
||||||
|
|
||||||
|
void setGeneration(uint generation);
|
||||||
|
bool isValid() const;
|
||||||
|
bool isLocked() const;
|
||||||
|
const QRect& rect() const;
|
||||||
|
void setRect(const QRect& rect);
|
||||||
|
|
||||||
|
QString toolTip() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class CaptureAreaEditor;
|
||||||
|
|
||||||
|
Generation generation_{};
|
||||||
|
QRect rect_;
|
||||||
|
bool doTranslation_;
|
||||||
|
bool isLocked_{false};
|
||||||
|
bool useHunspell_{false};
|
||||||
|
LanguageId sourceLanguage_;
|
||||||
|
LanguageId targetLanguage_;
|
||||||
|
QStringList translators_;
|
||||||
|
};
|
86
src/capture/captureareaeditor.cpp
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
#include "captureareaeditor.h"
|
||||||
|
#include "capturearea.h"
|
||||||
|
#include "captureareaselector.h"
|
||||||
|
#include "commonmodels.h"
|
||||||
|
#include "languagecodes.h"
|
||||||
|
|
||||||
|
#include <QCheckBox>
|
||||||
|
#include <QComboBox>
|
||||||
|
#include <QGridLayout>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QPushButton>
|
||||||
|
|
||||||
|
CaptureAreaEditor::CaptureAreaEditor(const CommonModels &models,
|
||||||
|
QWidget *parent)
|
||||||
|
: QWidget(parent)
|
||||||
|
, doTranslation_(new QCheckBox(tr("Translate:"), this))
|
||||||
|
, isLocked_(new QCheckBox(tr("Save (can capture via hotkey)"), this))
|
||||||
|
, useHunspell_(new QCheckBox(tr("Use auto corrections"), this))
|
||||||
|
, sourceLanguage_(new QComboBox(this))
|
||||||
|
, targetLanguage_(new QComboBox(this))
|
||||||
|
{
|
||||||
|
setCursor(Qt::CursorShape::ArrowCursor);
|
||||||
|
|
||||||
|
auto layout = new QGridLayout(this);
|
||||||
|
auto row = 0;
|
||||||
|
layout->addWidget(new QLabel(tr("Recognize:")), row, 0);
|
||||||
|
layout->addWidget(sourceLanguage_, row, 1);
|
||||||
|
auto swapLanguages = new QPushButton(tr("⇵"));
|
||||||
|
layout->addWidget(swapLanguages, row, 2, 2, 1);
|
||||||
|
|
||||||
|
++row;
|
||||||
|
layout->addWidget(doTranslation_, row, 0);
|
||||||
|
layout->addWidget(targetLanguage_, row, 1);
|
||||||
|
|
||||||
|
++row;
|
||||||
|
layout->addWidget(useHunspell_, row, 0, 1, 2);
|
||||||
|
|
||||||
|
++row;
|
||||||
|
layout->addWidget(isLocked_, row, 0, 1, 2);
|
||||||
|
|
||||||
|
sourceLanguage_->setModel(models.sourceLanguageModel());
|
||||||
|
targetLanguage_->setModel(models.targetLanguageModel());
|
||||||
|
targetLanguage_->setEnabled(doTranslation_->isChecked());
|
||||||
|
|
||||||
|
swapLanguages->setFlat(true);
|
||||||
|
{
|
||||||
|
auto font = swapLanguages->font();
|
||||||
|
font.setPointSize(std::max(font.pointSize() * 2, 16));
|
||||||
|
swapLanguages->setFont(font);
|
||||||
|
}
|
||||||
|
swapLanguages->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||||
|
|
||||||
|
connect(doTranslation_, &QCheckBox::toggled, //
|
||||||
|
targetLanguage_, &QComboBox::setEnabled);
|
||||||
|
connect(swapLanguages, &QPushButton::clicked, //
|
||||||
|
this, &CaptureAreaEditor::swapLanguages);
|
||||||
|
}
|
||||||
|
|
||||||
|
CaptureAreaEditor::~CaptureAreaEditor() = default;
|
||||||
|
|
||||||
|
void CaptureAreaEditor::swapLanguages()
|
||||||
|
{
|
||||||
|
const auto target = targetLanguage_->currentText();
|
||||||
|
targetLanguage_->setCurrentText(sourceLanguage_->currentText());
|
||||||
|
sourceLanguage_->setCurrentText(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CaptureAreaEditor::set(const CaptureArea &area)
|
||||||
|
{
|
||||||
|
isLocked_->setChecked(area.isLocked());
|
||||||
|
useHunspell_->setChecked(area.useHunspell_);
|
||||||
|
doTranslation_->setChecked(area.doTranslation_);
|
||||||
|
sourceLanguage_->setCurrentText(LanguageCodes::name(area.sourceLanguage_));
|
||||||
|
targetLanguage_->setCurrentText(LanguageCodes::name(area.targetLanguage_));
|
||||||
|
}
|
||||||
|
|
||||||
|
void CaptureAreaEditor::apply(CaptureArea &area) const
|
||||||
|
{
|
||||||
|
area.isLocked_ = isLocked_->isChecked();
|
||||||
|
area.useHunspell_ = useHunspell_->isChecked();
|
||||||
|
area.doTranslation_ = doTranslation_->isChecked();
|
||||||
|
area.sourceLanguage_ =
|
||||||
|
LanguageCodes::idForName(sourceLanguage_->currentText());
|
||||||
|
area.targetLanguage_ =
|
||||||
|
LanguageCodes::idForName(targetLanguage_->currentText());
|
||||||
|
}
|
29
src/capture/captureareaeditor.h
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "stfwd.h"
|
||||||
|
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
class QCheckBox;
|
||||||
|
class QComboBox;
|
||||||
|
|
||||||
|
class CaptureAreaEditor : public QWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit CaptureAreaEditor(const CommonModels& models,
|
||||||
|
QWidget* parent = nullptr);
|
||||||
|
~CaptureAreaEditor();
|
||||||
|
|
||||||
|
void set(const CaptureArea& area);
|
||||||
|
void apply(CaptureArea& area) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void swapLanguages();
|
||||||
|
|
||||||
|
QCheckBox* doTranslation_;
|
||||||
|
QCheckBox* isLocked_;
|
||||||
|
QCheckBox* useHunspell_;
|
||||||
|
QComboBox* sourceLanguage_;
|
||||||
|
QComboBox* targetLanguage_;
|
||||||
|
};
|
355
src/capture/captureareaselector.cpp
Normal file
@ -0,0 +1,355 @@
|
|||||||
|
#include "captureareaselector.h"
|
||||||
|
#include "capturearea.h"
|
||||||
|
#include "captureareaeditor.h"
|
||||||
|
#include "capturer.h"
|
||||||
|
#include "debug.h"
|
||||||
|
#include "geometryutils.h"
|
||||||
|
#include "settings.h"
|
||||||
|
|
||||||
|
#include <QMenu>
|
||||||
|
#include <QMouseEvent>
|
||||||
|
#include <QPainter>
|
||||||
|
|
||||||
|
static bool locked(const std::shared_ptr<CaptureArea> &area)
|
||||||
|
{
|
||||||
|
return area->isLocked();
|
||||||
|
}
|
||||||
|
static bool notLocked(const std::shared_ptr<CaptureArea> &area)
|
||||||
|
{
|
||||||
|
return !area->isLocked();
|
||||||
|
}
|
||||||
|
|
||||||
|
CaptureAreaSelector::CaptureAreaSelector(Capturer &capturer,
|
||||||
|
const Settings &settings,
|
||||||
|
const CommonModels &models,
|
||||||
|
const QPixmap &pixmap,
|
||||||
|
const QPoint &pixmapOffset)
|
||||||
|
: capturer_(capturer)
|
||||||
|
, settings_(settings)
|
||||||
|
, pixmap_(pixmap)
|
||||||
|
, pixmapOffset_(pixmapOffset)
|
||||||
|
, editor_(std::make_unique<CaptureAreaEditor>(models, this))
|
||||||
|
, contextMenu_(new QMenu(this))
|
||||||
|
{
|
||||||
|
setWindowFlags(Qt::FramelessWindowHint | Qt::NoDropShadowWindowHint |
|
||||||
|
Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint);
|
||||||
|
setMouseTracking(true);
|
||||||
|
setAttribute(Qt::WA_OpaquePaintEvent);
|
||||||
|
|
||||||
|
help_ = tr(R"(Right click on selection - customize
|
||||||
|
Left click on selection - process
|
||||||
|
Enter - process all selections
|
||||||
|
Esc - cancel
|
||||||
|
Ctrl - keep selecting)");
|
||||||
|
|
||||||
|
{
|
||||||
|
auto action = contextMenu_->addAction(tr("Capture all"));
|
||||||
|
connect(action, &QAction::triggered, //
|
||||||
|
this, &CaptureAreaSelector::captureAll);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
auto action = contextMenu_->addAction(tr("Cancel"));
|
||||||
|
connect(action, &QAction::triggered, //
|
||||||
|
this, &CaptureAreaSelector::cancel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CaptureAreaSelector::~CaptureAreaSelector() = default;
|
||||||
|
|
||||||
|
void CaptureAreaSelector::activate()
|
||||||
|
{
|
||||||
|
setGeometry(QRect(pixmapOffset_, pixmap_.size()));
|
||||||
|
show();
|
||||||
|
activateWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CaptureAreaSelector::hasLocked() const
|
||||||
|
{
|
||||||
|
const auto it = std::find_if(areas_.cbegin(), areas_.cend(), locked);
|
||||||
|
return it != areas_.cend();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CaptureAreaSelector::captureLocked()
|
||||||
|
{
|
||||||
|
SOFT_ASSERT(hasLocked(), return );
|
||||||
|
++generation_;
|
||||||
|
for (auto &area : areas_) {
|
||||||
|
if (area->isLocked())
|
||||||
|
capture(*area, generation_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CaptureAreaSelector::capture(CaptureArea &area, uint generation)
|
||||||
|
{
|
||||||
|
area.setGeneration(generation);
|
||||||
|
capturer_.selected(area);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CaptureAreaSelector::captureAll()
|
||||||
|
{
|
||||||
|
SOFT_ASSERT(!areas_.empty(), return );
|
||||||
|
++generation_;
|
||||||
|
for (auto &area : areas_) capture(*area, generation_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CaptureAreaSelector::cancel()
|
||||||
|
{
|
||||||
|
capturer_.canceled();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CaptureAreaSelector::updateCursorShape(const QPoint &pos)
|
||||||
|
{
|
||||||
|
const auto set = [this](Qt::CursorShape shape) {
|
||||||
|
const auto current = cursor().shape();
|
||||||
|
if (current != shape)
|
||||||
|
setCursor(shape);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (areas_.empty()) {
|
||||||
|
set(Qt::CrossCursor);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto &area : areas_) {
|
||||||
|
if (area->rect().contains(pos)) {
|
||||||
|
set(Qt::CursorShape::PointingHandCursor);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set(Qt::CrossCursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CaptureAreaSelector::setScreenRects(const std::vector<QRect> &screens)
|
||||||
|
{
|
||||||
|
auto helpRect = fontMetrics().boundingRect({}, 0, help_);
|
||||||
|
helpRect.setSize(helpRect.size() * 1.4);
|
||||||
|
|
||||||
|
helpRects_.clear();
|
||||||
|
helpRects_.reserve(screens.size());
|
||||||
|
for (const auto &screen : screens) {
|
||||||
|
auto possible = std::vector<QRect>(2, helpRect);
|
||||||
|
possible[0].moveTopLeft(screen.topLeft());
|
||||||
|
possible[1].moveTopRight(screen.topRight());
|
||||||
|
helpRects_.push_back({possible[0], possible});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CaptureAreaSelector::updateSettings()
|
||||||
|
{
|
||||||
|
areas_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CaptureAreaSelector::paintEvent(QPaintEvent * /*event*/)
|
||||||
|
{
|
||||||
|
QPainter painter(this);
|
||||||
|
painter.drawPixmap(rect(), pixmap_);
|
||||||
|
|
||||||
|
for (const auto &rect : helpRects_) drawHelpRects(painter, rect);
|
||||||
|
|
||||||
|
if (!areas_.empty()) {
|
||||||
|
for (const auto &area : areas_) drawCaptureArea(painter, *area);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (editor_->isVisible()) {
|
||||||
|
painter.setBrush(QBrush(QColor(200, 200, 200, 200)));
|
||||||
|
painter.setPen(Qt::NoPen);
|
||||||
|
painter.drawRect(editor_->geometry());
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto area = CaptureArea(
|
||||||
|
QRect(startSelectPos_, currentSelectPos_).normalized(), settings_);
|
||||||
|
if (!area.isValid())
|
||||||
|
return;
|
||||||
|
drawCaptureArea(painter, area);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CaptureAreaSelector::updateCurrentHelpRects()
|
||||||
|
{
|
||||||
|
const auto cursor = mapFromGlobal(QCursor::pos());
|
||||||
|
auto changed = false;
|
||||||
|
|
||||||
|
for (auto &screenHelp : helpRects_) {
|
||||||
|
if (!screenHelp.current.contains(cursor))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for (const auto &screenPossible : screenHelp.possible) {
|
||||||
|
if (screenPossible.contains(cursor))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
screenHelp.current = screenPossible;
|
||||||
|
changed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CaptureAreaSelector::drawHelpRects(QPainter &painter,
|
||||||
|
const HelpRect &rect) const
|
||||||
|
{
|
||||||
|
painter.setBrush(QBrush(QColor(200, 200, 200, 200)));
|
||||||
|
painter.setPen(Qt::NoPen);
|
||||||
|
painter.drawRect(rect.current);
|
||||||
|
|
||||||
|
painter.setBrush({});
|
||||||
|
painter.setPen(Qt::black);
|
||||||
|
painter.drawText(rect.current, Qt::AlignCenter, help_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CaptureAreaSelector::drawCaptureArea(QPainter &painter,
|
||||||
|
const CaptureArea &area) const
|
||||||
|
{
|
||||||
|
const auto areaRect = area.rect();
|
||||||
|
const auto toolTip = area.toolTip();
|
||||||
|
auto toolTipRect = painter.boundingRect(QRect(), 0, toolTip);
|
||||||
|
toolTipRect.moveTopLeft(areaRect.topLeft() - QPoint(0, toolTipRect.height()));
|
||||||
|
|
||||||
|
painter.setBrush(QBrush(QColor(200, 200, 200, 50)));
|
||||||
|
painter.setPen(Qt::NoPen);
|
||||||
|
painter.drawRect(areaRect);
|
||||||
|
|
||||||
|
painter.setBrush(QBrush(QColor(200, 200, 200, 200)));
|
||||||
|
painter.drawRect(toolTipRect);
|
||||||
|
|
||||||
|
painter.setBrush({});
|
||||||
|
painter.setPen(Qt::red);
|
||||||
|
painter.drawRect(areaRect);
|
||||||
|
|
||||||
|
painter.setPen(Qt::black);
|
||||||
|
painter.drawText(toolTipRect, 0, toolTip);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CaptureAreaSelector::showEvent(QShowEvent * /*event*/)
|
||||||
|
{
|
||||||
|
editor_->hide();
|
||||||
|
startSelectPos_ = currentSelectPos_ = QPoint();
|
||||||
|
areas_.erase(std::remove_if(areas_.begin(), areas_.end(), notLocked),
|
||||||
|
areas_.end());
|
||||||
|
updateCursorShape(QCursor::pos());
|
||||||
|
}
|
||||||
|
|
||||||
|
void CaptureAreaSelector::hideEvent(QHideEvent * /*event*/)
|
||||||
|
{
|
||||||
|
editor_->hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CaptureAreaSelector::keyPressEvent(QKeyEvent *event)
|
||||||
|
{
|
||||||
|
if (event->key() == Qt::Key_Escape) {
|
||||||
|
if (editor_ && editor_->isVisible())
|
||||||
|
applyEditor();
|
||||||
|
cancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event->key() == Qt::Key_Return) {
|
||||||
|
if (editor_ && editor_->isVisible())
|
||||||
|
applyEditor();
|
||||||
|
|
||||||
|
if (!areas_.empty()) {
|
||||||
|
captureAll();
|
||||||
|
} else {
|
||||||
|
cancel();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CaptureAreaSelector::mousePressEvent(QMouseEvent *event)
|
||||||
|
{
|
||||||
|
SOFT_ASSERT(editor_, return );
|
||||||
|
if (editor_->isVisible()) {
|
||||||
|
if (editor_->geometry().contains(event->pos()))
|
||||||
|
return;
|
||||||
|
applyEditor();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!areas_.empty()) {
|
||||||
|
for (auto &area : areas_) {
|
||||||
|
if (!area->rect().contains(event->pos()))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (event->button() == Qt::LeftButton) {
|
||||||
|
capture(*area, ++generation_);
|
||||||
|
} else if (event->button() == Qt::RightButton) {
|
||||||
|
customize(area);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startSelectPos_.isNull())
|
||||||
|
startSelectPos_ = currentSelectPos_ = event->pos();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CaptureAreaSelector::mouseMoveEvent(QMouseEvent *event)
|
||||||
|
{
|
||||||
|
updateCursorShape(QCursor::pos());
|
||||||
|
|
||||||
|
if (startSelectPos_.isNull()) {
|
||||||
|
if (updateCurrentHelpRects())
|
||||||
|
update();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentSelectPos_ = event->pos();
|
||||||
|
updateCurrentHelpRects();
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CaptureAreaSelector::mouseReleaseEvent(QMouseEvent *event)
|
||||||
|
{
|
||||||
|
if (startSelectPos_.isNull())
|
||||||
|
return;
|
||||||
|
|
||||||
|
const auto endPos = event->pos();
|
||||||
|
const auto selection = QRect(startSelectPos_, endPos).normalized();
|
||||||
|
|
||||||
|
startSelectPos_ = currentSelectPos_ = {};
|
||||||
|
|
||||||
|
auto area = CaptureArea(selection, settings_);
|
||||||
|
if (!area.isValid()) { // just a click
|
||||||
|
if (areas_.empty()) {
|
||||||
|
cancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (event->button() == Qt::RightButton) {
|
||||||
|
contextMenu_->popup(QCursor::pos());
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
areas_.emplace_back(std::make_unique<CaptureArea>(area));
|
||||||
|
if (event->button() == Qt::RightButton) {
|
||||||
|
customize(areas_.back());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(event->modifiers() & Qt::ControlModifier))
|
||||||
|
captureAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CaptureAreaSelector::customize(const std::shared_ptr<CaptureArea> &area)
|
||||||
|
{
|
||||||
|
SOFT_ASSERT(editor_, return );
|
||||||
|
SOFT_ASSERT(area, return );
|
||||||
|
editor_->set(*area);
|
||||||
|
edited_ = area;
|
||||||
|
editor_->show();
|
||||||
|
const auto topLeft = service::geometry::cornerAtPoint(
|
||||||
|
area->rect().center(), editor_->size(), QRect({}, size()));
|
||||||
|
editor_->move(topLeft);
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CaptureAreaSelector::applyEditor()
|
||||||
|
{
|
||||||
|
SOFT_ASSERT(editor_, return );
|
||||||
|
if (!editor_->isVisible() || edited_.expired())
|
||||||
|
return;
|
||||||
|
editor_->apply(*edited_.lock());
|
||||||
|
editor_->hide();
|
||||||
|
update();
|
||||||
|
}
|
64
src/capture/captureareaselector.h
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "stfwd.h"
|
||||||
|
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
class QMenu;
|
||||||
|
|
||||||
|
class CaptureAreaSelector : public QWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
CaptureAreaSelector(Capturer &capturer, const Settings &settings,
|
||||||
|
const CommonModels &models, const QPixmap &pixmap,
|
||||||
|
const QPoint &pixmapOffset);
|
||||||
|
~CaptureAreaSelector();
|
||||||
|
|
||||||
|
void activate();
|
||||||
|
bool hasLocked() const;
|
||||||
|
void captureLocked();
|
||||||
|
void setScreenRects(const std::vector<QRect> &screens);
|
||||||
|
void updateSettings();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void showEvent(QShowEvent *event) override;
|
||||||
|
void hideEvent(QHideEvent *event) override;
|
||||||
|
void keyPressEvent(QKeyEvent *event) override;
|
||||||
|
void mousePressEvent(QMouseEvent *event) override;
|
||||||
|
void mouseMoveEvent(QMouseEvent *event) override;
|
||||||
|
void mouseReleaseEvent(QMouseEvent *event) override;
|
||||||
|
void paintEvent(QPaintEvent *event) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct HelpRect {
|
||||||
|
QRect current;
|
||||||
|
std::vector<QRect> possible;
|
||||||
|
};
|
||||||
|
void capture(CaptureArea &area, uint generation);
|
||||||
|
void captureAll();
|
||||||
|
void cancel();
|
||||||
|
void updateCursorShape(const QPoint &pos);
|
||||||
|
|
||||||
|
bool updateCurrentHelpRects();
|
||||||
|
void drawHelpRects(QPainter &painter, const HelpRect &rect) const;
|
||||||
|
|
||||||
|
void customize(const std::shared_ptr<CaptureArea> &area);
|
||||||
|
void applyEditor();
|
||||||
|
void drawCaptureArea(QPainter &painter, const CaptureArea &area) const;
|
||||||
|
|
||||||
|
Capturer &capturer_;
|
||||||
|
const Settings &settings_;
|
||||||
|
const QPixmap &pixmap_;
|
||||||
|
const QPoint &pixmapOffset_;
|
||||||
|
Generation generation_{};
|
||||||
|
QPoint startSelectPos_;
|
||||||
|
QPoint currentSelectPos_;
|
||||||
|
QString help_;
|
||||||
|
std::vector<HelpRect> helpRects_;
|
||||||
|
std::vector<std::shared_ptr<CaptureArea>> areas_;
|
||||||
|
std::weak_ptr<CaptureArea> edited_;
|
||||||
|
std::unique_ptr<CaptureAreaEditor> editor_;
|
||||||
|
QMenu *contextMenu_;
|
||||||
|
};
|
106
src/capture/capturer.cpp
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
#include "capturer.h"
|
||||||
|
#include "capturearea.h"
|
||||||
|
#include "captureareaselector.h"
|
||||||
|
#include "debug.h"
|
||||||
|
#include "manager.h"
|
||||||
|
#include "settings.h"
|
||||||
|
#include "task.h"
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QPainter>
|
||||||
|
#include <QScreen>
|
||||||
|
|
||||||
|
Capturer::Capturer(Manager &manager, const Settings &settings,
|
||||||
|
const CommonModels &models)
|
||||||
|
: manager_(manager)
|
||||||
|
, settings_(settings)
|
||||||
|
, selector_(std::make_unique<CaptureAreaSelector>(*this, settings_, models,
|
||||||
|
pixmap_, pixmapOffset_))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Capturer::~Capturer() = default;
|
||||||
|
|
||||||
|
void Capturer::capture()
|
||||||
|
{
|
||||||
|
updatePixmap();
|
||||||
|
SOFT_ASSERT(selector_, return );
|
||||||
|
selector_->activate();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Capturer::canCaptureLocked()
|
||||||
|
{
|
||||||
|
SOFT_ASSERT(selector_, return false);
|
||||||
|
return selector_->hasLocked();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Capturer::captureLocked()
|
||||||
|
{
|
||||||
|
updatePixmap();
|
||||||
|
SOFT_ASSERT(selector_, return );
|
||||||
|
selector_->captureLocked();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Capturer::updatePixmap()
|
||||||
|
{
|
||||||
|
const auto screens = QApplication::screens();
|
||||||
|
std::vector<QRect> screenRects;
|
||||||
|
screenRects.reserve(screens.size());
|
||||||
|
QRect rect;
|
||||||
|
|
||||||
|
for (const QScreen *screen : screens) {
|
||||||
|
const auto geometry = screen->geometry();
|
||||||
|
screenRects.push_back(geometry);
|
||||||
|
rect |= geometry;
|
||||||
|
}
|
||||||
|
|
||||||
|
QPixmap combined(rect.size());
|
||||||
|
QPainter p(&combined);
|
||||||
|
p.translate(-rect.topLeft());
|
||||||
|
|
||||||
|
for (const auto screen : screens) {
|
||||||
|
const auto geometry = screen->geometry();
|
||||||
|
const auto pixmap =
|
||||||
|
screen->grabWindow(0, 0, 0, geometry.width(), geometry.height());
|
||||||
|
p.drawPixmap(geometry, pixmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
SOFT_ASSERT(selector_, return );
|
||||||
|
pixmap_ = combined;
|
||||||
|
pixmapOffset_ = rect.topLeft();
|
||||||
|
|
||||||
|
for (auto &r : screenRects) r.translate(-rect.topLeft());
|
||||||
|
selector_->setScreenRects(screenRects);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Capturer::repeatCapture()
|
||||||
|
{
|
||||||
|
SOFT_ASSERT(selector_, return );
|
||||||
|
selector_->activate();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Capturer::updateSettings()
|
||||||
|
{
|
||||||
|
SOFT_ASSERT(selector_, return );
|
||||||
|
selector_->updateSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Capturer::selected(const CaptureArea &area)
|
||||||
|
{
|
||||||
|
SOFT_ASSERT(selector_, return manager_.captureCanceled())
|
||||||
|
selector_->hide();
|
||||||
|
|
||||||
|
SOFT_ASSERT(!pixmap_.isNull(), return manager_.captureCanceled())
|
||||||
|
auto task = area.task(pixmap_, pixmapOffset_);
|
||||||
|
if (task)
|
||||||
|
manager_.captured(task);
|
||||||
|
else
|
||||||
|
manager_.captureCanceled();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Capturer::canceled()
|
||||||
|
{
|
||||||
|
SOFT_ASSERT(selector_, return );
|
||||||
|
selector_->hide();
|
||||||
|
manager_.captureCanceled();
|
||||||
|
}
|
31
src/capture/capturer.h
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "stfwd.h"
|
||||||
|
|
||||||
|
#include <QPixmap>
|
||||||
|
|
||||||
|
class Capturer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Capturer(Manager &manager, const Settings &settings,
|
||||||
|
const CommonModels &models);
|
||||||
|
~Capturer();
|
||||||
|
|
||||||
|
void capture();
|
||||||
|
bool canCaptureLocked();
|
||||||
|
void captureLocked();
|
||||||
|
void repeatCapture();
|
||||||
|
void updateSettings();
|
||||||
|
|
||||||
|
void selected(const CaptureArea &area);
|
||||||
|
void canceled();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void updatePixmap();
|
||||||
|
|
||||||
|
Manager &manager_;
|
||||||
|
const Settings &settings_;
|
||||||
|
QPixmap pixmap_;
|
||||||
|
QPoint pixmapOffset_;
|
||||||
|
std::unique_ptr<CaptureAreaSelector> selector_;
|
||||||
|
};
|
51
src/commonmodels.cpp
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
#include "commonmodels.h"
|
||||||
|
#include "settings.h"
|
||||||
|
#include "tesseract.h"
|
||||||
|
#include "translator.h"
|
||||||
|
|
||||||
|
CommonModels::CommonModels()
|
||||||
|
: sourceLanguageModel_(std::make_unique<QStringListModel>())
|
||||||
|
, targetLanguageModel_(std::make_unique<QStringListModel>())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
CommonModels::~CommonModels() = default;
|
||||||
|
|
||||||
|
void CommonModels::update(const QString &tessdataPath,
|
||||||
|
const QString &translatorPath)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
auto names = Tesseract::availableLanguageNames(tessdataPath);
|
||||||
|
std::sort(names.begin(), names.end());
|
||||||
|
sourceLanguageModel_->setStringList(names);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
translators_ = Translator::availableTranslators(translatorPath);
|
||||||
|
std::sort(translators_.begin(), translators_.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetLanguageModel_->rowCount() > 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
{
|
||||||
|
auto names = Translator::availableLanguageNames();
|
||||||
|
std::sort(names.begin(), names.end());
|
||||||
|
targetLanguageModel_->setStringList(names);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringListModel *CommonModels::sourceLanguageModel() const
|
||||||
|
{
|
||||||
|
return sourceLanguageModel_.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringListModel *CommonModels::targetLanguageModel() const
|
||||||
|
{
|
||||||
|
return targetLanguageModel_.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
const QStringList &CommonModels::translators() const
|
||||||
|
{
|
||||||
|
return translators_;
|
||||||
|
}
|