Compare commits

..

No commits in common. "master" and "1.2.2" have entirely different histories.

175 changed files with 4861 additions and 52144 deletions

View File

@ -1,16 +0,0 @@
---
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
...

View File

@ -1,37 +0,0 @@
---
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.

View File

@ -1,20 +0,0 @@
---
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.

View File

@ -1,100 +0,0 @@
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

5
.gitignore vendored
View File

@ -5,7 +5,4 @@
*.user
*.exe
distr/content/
*.tar.gz
__pycache__
distr/content/

276
GlobalActionHelper.cpp Normal file
View File

@ -0,0 +1,276 @@
#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);
}

29
GlobalActionHelper.h Normal file
View File

@ -0,0 +1,29 @@
#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

91
GoogleWebTranslator.cpp Normal file
View File

@ -0,0 +1,91 @@
#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);
}

43
GoogleWebTranslator.h Normal file
View File

@ -0,0 +1,43 @@
#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

151
ImageProcessing.cpp Normal file
View File

@ -0,0 +1,151 @@
#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);
}

18
ImageProcessing.h Normal file
View File

@ -0,0 +1,18 @@
#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

View File

@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2017 Gres (gres@gres.biz)
Copyright (c) 2014 Gres (gres@gres.biz)
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

244
LanguageHelper.cpp Normal file
View File

@ -0,0 +1,244 @@
#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");
}

39
LanguageHelper.h Normal file
View File

@ -0,0 +1,39 @@
#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 Normal file
View File

@ -0,0 +1,223 @@
#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 Normal file
View File

@ -0,0 +1,57 @@
#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

11
ProcessingItem.cpp Normal file
View File

@ -0,0 +1,11 @@
#include "ProcessingItem.h"
bool ProcessingItem::isValid() const
{
bool valid = true;
valid &= (!screenPos.isNull ());
valid &= (!source.isNull ());
valid &= (!recognized.isEmpty ());
valid &= (!translated.isEmpty ());
return valid;
}

20
ProcessingItem.h Normal file
View File

@ -0,0 +1,20 @@
#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
View File

@ -1,92 +1,38 @@
# Screen Translator
**The project is almost abandoned. I don't have time for it and I can only fix minor issues**
## Introduction
Screen Translator
=================
Introduction
------------
This software allows you to translate any text on screen.
Basically it is a combination of screen capture, OCR and translation tools.
Translation is currently done via online services.
## Installation
Usage
-----
1. Press capture hotkey.
2. Select region on screen.
3. Get translation of recognized text.
**Windows**: download archive from [github releases](https://github.com/OneMoreGres/ScreenTranslator/releases) page, extract it and run `.exe` file.
Features
--------
* 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.
**Linux**: download `.AppImage` file from [github releases](https://github.com/OneMoreGres/ScreenTranslator/releases), make executable (`chmod +x <file>`) and run it.
Limitations
-----------
* Works only on primary screen
* Can not capture some dynamic web-pages
* Not very precise OCR (need better preprocessing steps)
**OS X**: currently not supported.
Used software
-------------
* 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/)

99
Recognizer.cpp Normal file
View File

@ -0,0 +1,99 @@
#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 Normal file
View File

@ -0,0 +1,40 @@
#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

7
Recources.qrc Normal file
View File

@ -0,0 +1,7 @@
<RCC>
<qresource prefix="/">
<file>translations/translation_en.qm</file>
<file>translations/translation_ru.qm</file>
<file>images/icon.png</file>
</qresource>
</RCC>

68
ResultDialog.cpp Normal file
View File

@ -0,0 +1,68 @@
#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);
}
}

31
ResultDialog.h Normal file
View File

@ -0,0 +1,31 @@
#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

98
ResultDialog.ui Normal file
View File

@ -0,0 +1,98 @@
<?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>

65
ScreenTranslator.pro Normal file
View File

@ -0,0 +1,65 @@
#-------------------------------------------------
#
# 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

165
SelectionDialog.cpp Normal file
View File

@ -0,0 +1,165 @@
#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 ();
}

41
SelectionDialog.h Normal file
View File

@ -0,0 +1,41 @@
#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

43
SelectionDialog.ui Normal file
View File

@ -0,0 +1,43 @@
<?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 Normal file
View File

@ -0,0 +1,50 @@
#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

133
SettingsEditor.cpp Normal file
View File

@ -0,0 +1,133 @@
#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));
}

43
SettingsEditor.h Normal file
View File

@ -0,0 +1,43 @@
#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

271
SettingsEditor.ui Normal file
View File

@ -0,0 +1,271 @@
<?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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Сочетание клавиш для перехода в режим захвата.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Сочетание клавиш для перехода в режим захвата.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Сочетание клавиш для перехода в режим захвата.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Необходимо для распознавания.&lt;/p&gt;&lt;p&gt;Скачивается отсюда: &lt;a href=&quot;https://code.google.com/p/tesseract-ocr/downloads/list&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;https://code.google.com/p/tesseract-ocr/downloads/list&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Заполняется на основании содержания tessdata&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Масштабирование изображения для улучшения распознания. Больше - лучше (до определенных пределов), но медленнее.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Язык, на который осуществляется перевод.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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 Normal file
View File

@ -0,0 +1,120 @@
#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 Normal file
View File

@ -0,0 +1,36 @@
#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

1
app.rc Normal file
View File

@ -0,0 +1 @@
IDI_ICON1 ICON DISCARDABLE "images/icon.ico"

BIN
distr/Files.xlsx Normal file

Binary file not shown.

269
distr/InnoSetup.iss Normal file
View File

@ -0,0 +1,269 @@
; 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

23
distr/eng/Changelog.txt Normal file
View File

@ -0,0 +1,23 @@
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.

23
distr/ru/Changelog.txt Normal file
View File

@ -0,0 +1,23 @@
Изменения.
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:
- Отображение результата в окошке, вместе с картинкой.
- Контекстное меню расширено. Добавлены кнопки отображения последнего результата и копирования его в буфер обмена.

78
distr/ru/Russian.isl Normal file
View File

@ -0,0 +1,78 @@
[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=Âüåòíàìñêèé

23
distr/sf/readme.md Normal file
View File

@ -0,0 +1,23 @@
#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.

View File

@ -1,28 +0,0 @@
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.

File diff suppressed because it is too large Load Diff

14813
external/gtest/gtest.h vendored

File diff suppressed because it is too large Load Diff

View File

@ -1,22 +0,0 @@
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

File diff suppressed because it is too large Load Diff

1338
external/miniz/miniz.h vendored

File diff suppressed because it is too large Load Diff

BIN
images/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
images/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

25
main.cpp Normal file
View File

@ -0,0 +1,25 @@
#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();
}

View File

@ -1,16 +0,0 @@
<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>

View File

@ -1,142 +0,0 @@
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
}

View File

@ -1,125 +0,0 @@
# 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.

View File

@ -1,125 +0,0 @@
# Изменения
## 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
* Отображение результата в окошке, вместе с картинкой.
* Контекстное меню расширено. Добавлены кнопки отображения последнего результата и копирования его в буфер обмена.

View File

@ -1,65 +0,0 @@
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)))

View File

@ -1,26 +0,0 @@
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)

View File

@ -1,213 +0,0 @@
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 ''

View File

@ -1,35 +0,0 @@
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'

View File

@ -1,131 +0,0 @@
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)

View File

@ -1,100 +0,0 @@
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)

View File

@ -1,82 +0,0 @@
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)

View File

@ -1,52 +0,0 @@
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)

View File

@ -1,98 +0,0 @@
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)

View File

@ -1,19 +0,0 @@
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)

View File

@ -1,43 +0,0 @@
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')

View File

@ -1,84 +0,0 @@
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))

View File

@ -1,25 +0,0 @@
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)

View File

@ -1,57 +0,0 @@
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)))

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 641 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 894 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -1,13 +0,0 @@
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"]

View File

@ -1,15 +0,0 @@
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

View File

@ -1,51 +0,0 @@
#!/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

View File

@ -1,9 +0,0 @@
[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;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,106 +0,0 @@
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)

View File

@ -1,68 +0,0 @@
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)

View File

@ -1,38 +0,0 @@
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('}')

View File

@ -1,71 +0,0 @@
#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_;
}

View File

@ -1,35 +0,0 @@
#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_;
};

View File

@ -1,86 +0,0 @@
#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());
}

View File

@ -1,29 +0,0 @@
#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_;
};

View File

@ -1,355 +0,0 @@
#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();
}

View File

@ -1,64 +0,0 @@
#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_;
};

View File

@ -1,106 +0,0 @@
#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();
}

View File

@ -1,31 +0,0 @@
#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_;
};

View File

@ -1,51 +0,0 @@
#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_;
}

Some files were not shown because too many files have changed in this diff Show More