Refactor. WIP

This commit is contained in:
Gres 2020-02-20 20:45:53 +03:00
parent 623a4b6086
commit 4c526f65df
100 changed files with 3990 additions and 5369 deletions

16
.clang-format Normal file
View File

@ -0,0 +1,16 @@
---
BasedOnStyle: Google
AccessModifierOffset: '-2'
AllowShortCaseLabelsOnASingleLine: 'true'
AllowShortFunctionsOnASingleLine: InlineOnly
AllowShortIfStatementsOnASingleLine: 'false'
BreakBeforeBraces: Linux
BreakConstructorInitializers: BeforeComma
ConstructorInitializerAllOnOneLineOrOnePerLine: 'false'
ConstructorInitializerIndentWidth: '2'
Standard: Cpp11
TabWidth: '2'
UseTab: Never
IncludeBlocks: Preserve
...

View File

@ -1,20 +0,0 @@
language: cpp
compiler:
- gcc
- clang
sudo: required
dist: trusty
install: ./scripts/install_deps.sh
script: cd scripts && ./make_all.sh
branches:
only:
- master
- develop
- /release.*/
- /.*travis/
os:
- linux
notifications:
email:
on_success: change
on_failure: change

View File

@ -1,204 +0,0 @@
/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Solutions component.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "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 Digia Plc and its Subsidiary(-ies) 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."
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qtlocalpeer.h"
#include <QCoreApplication>
#include <QDataStream>
#include <QTime>
#if defined(Q_OS_WIN)
#include <QLibrary>
#include <qt_windows.h>
typedef BOOL(WINAPI*PProcessIdToSessionId)(DWORD,DWORD*);
static PProcessIdToSessionId pProcessIdToSessionId = 0;
#endif
#if defined(Q_OS_UNIX)
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#endif
namespace QtLP_Private {
#include "qtlockedfile.cpp"
#if defined(Q_OS_WIN)
#include "qtlockedfile_win.cpp"
#else
#include "qtlockedfile_unix.cpp"
#endif
}
const char* QtLocalPeer::ack = "ack";
QtLocalPeer::QtLocalPeer(QObject* parent, const QString &appId)
: QObject(parent), id(appId)
{
QString prefix = id;
if (id.isEmpty()) {
id = QCoreApplication::applicationFilePath();
#if defined(Q_OS_WIN)
id = id.toLower();
#endif
prefix = id.section(QLatin1Char('/'), -1);
}
prefix.remove(QRegExp("[^a-zA-Z]"));
prefix.truncate(6);
QByteArray idc = id.toUtf8();
quint16 idNum = qChecksum(idc.constData(), idc.size());
socketName = QLatin1String("qtsingleapp-") + prefix
+ QLatin1Char('-') + QString::number(idNum, 16);
#if defined(Q_OS_WIN)
if (!pProcessIdToSessionId) {
QLibrary lib("kernel32");
pProcessIdToSessionId = (PProcessIdToSessionId)lib.resolve("ProcessIdToSessionId");
}
if (pProcessIdToSessionId) {
DWORD sessionId = 0;
pProcessIdToSessionId(GetCurrentProcessId(), &sessionId);
socketName += QLatin1Char('-') + QString::number(sessionId, 16);
}
#else
socketName += QLatin1Char('-') + QString::number(::getuid(), 16);
#endif
server = new QLocalServer(this);
QString lockName = QDir(QDir::tempPath()).absolutePath()
+ QLatin1Char('/') + socketName
+ QLatin1String("-lockfile");
lockFile.setFileName(lockName);
lockFile.open(QIODevice::ReadWrite);
}
bool QtLocalPeer::isClient()
{
if (lockFile.isLocked())
return false;
if (!lockFile.lock(QtLP_Private::QtLockedFile::WriteLock, false))
return true;
bool res = server->listen(socketName);
#if defined(Q_OS_UNIX) && (QT_VERSION >= QT_VERSION_CHECK(4,5,0))
// ### Workaround
if (!res && server->serverError() == QAbstractSocket::AddressInUseError) {
QFile::remove(QDir::cleanPath(QDir::tempPath())+QLatin1Char('/')+socketName);
res = server->listen(socketName);
}
#endif
if (!res)
qWarning("QtSingleCoreApplication: listen on local socket failed, %s", qPrintable(server->errorString()));
QObject::connect(server, SIGNAL(newConnection()), SLOT(receiveConnection()));
return false;
}
bool QtLocalPeer::sendMessage(const QString &message, int timeout)
{
if (!isClient())
return false;
QLocalSocket socket;
bool connOk = false;
for(int i = 0; i < 2; i++) {
// Try twice, in case the other instance is just starting up
socket.connectToServer(socketName);
connOk = socket.waitForConnected(timeout/2);
if (connOk || i)
break;
int ms = 250;
#if defined(Q_OS_WIN)
Sleep(DWORD(ms));
#else
struct timespec ts = { ms / 1000, (ms % 1000) * 1000 * 1000 };
nanosleep(&ts, NULL);
#endif
}
if (!connOk)
return false;
QByteArray uMsg(message.toUtf8());
QDataStream ds(&socket);
ds.writeBytes(uMsg.constData(), uMsg.size());
bool res = socket.waitForBytesWritten(timeout);
if (res) {
res &= socket.waitForReadyRead(timeout); // wait for ack
if (res)
res &= (socket.read(qstrlen(ack)) == ack);
}
return res;
}
void QtLocalPeer::receiveConnection()
{
QLocalSocket* socket = server->nextPendingConnection();
if (!socket)
return;
while (socket->bytesAvailable() < (int)sizeof(quint32))
socket->waitForReadyRead();
QDataStream ds(socket);
QByteArray uMsg;
quint32 remaining;
ds >> remaining;
uMsg.resize(remaining);
int got = 0;
char* uMsgBuf = uMsg.data();
do {
got = ds.readRawData(uMsgBuf, remaining);
remaining -= got;
uMsgBuf += got;
} while (remaining && got >= 0 && socket->waitForReadyRead(2000));
if (got < 0) {
qWarning("QtLocalPeer: Message reception failed %s", socket->errorString().toLatin1().constData());
delete socket;
return;
}
QString message(QString::fromUtf8(uMsg));
socket->write(ack, qstrlen(ack));
socket->waitForBytesWritten(1000);
socket->waitForDisconnected(1000); // make sure client reads ack
delete socket;
emit messageReceived(message); //### (might take a long time to return)
}

View File

@ -1,77 +0,0 @@
/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Solutions component.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "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 Digia Plc and its Subsidiary(-ies) 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."
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QTLOCALPEER_H
#define QTLOCALPEER_H
#include <QLocalServer>
#include <QLocalSocket>
#include <QDir>
#include "qtlockedfile.h"
class QtLocalPeer : public QObject
{
Q_OBJECT
public:
QtLocalPeer(QObject *parent = 0, const QString &appId = QString());
bool isClient();
bool sendMessage(const QString &message, int timeout);
QString applicationId() const
{ return id; }
Q_SIGNALS:
void messageReceived(const QString &message);
protected Q_SLOTS:
void receiveConnection();
protected:
QString id;
QString socketName;
QLocalServer* server;
QtLP_Private::QtLockedFile lockFile;
private:
static const char* ack;
};
#endif // QTLOCALPEER_H

View File

@ -1,193 +0,0 @@
/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Solutions component.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "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 Digia Plc and its Subsidiary(-ies) 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."
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qtlockedfile.h"
/*!
\class QtLockedFile
\brief The QtLockedFile class extends QFile with advisory locking
functions.
A file may be locked in read or write mode. Multiple instances of
\e QtLockedFile, created in multiple processes running on the same
machine, may have a file locked in read mode. Exactly one instance
may have it locked in write mode. A read and a write lock cannot
exist simultaneously on the same file.
The file locks are advisory. This means that nothing prevents
another process from manipulating a locked file using QFile or
file system functions offered by the OS. Serialization is only
guaranteed if all processes that access the file use
QLockedFile. Also, while holding a lock on a file, a process
must not open the same file again (through any API), or locks
can be unexpectedly lost.
The lock provided by an instance of \e QtLockedFile is released
whenever the program terminates. This is true even when the
program crashes and no destructors are called.
*/
/*! \enum QtLockedFile::LockMode
This enum describes the available lock modes.
\value ReadLock A read lock.
\value WriteLock A write lock.
\value NoLock Neither a read lock nor a write lock.
*/
/*!
Constructs an unlocked \e QtLockedFile object. This constructor
behaves in the same way as \e QFile::QFile().
\sa QFile::QFile()
*/
QtLockedFile::QtLockedFile()
: QFile()
{
#ifdef Q_OS_WIN
wmutex = 0;
rmutex = 0;
#endif
m_lock_mode = NoLock;
}
/*!
Constructs an unlocked QtLockedFile object with file \a name. This
constructor behaves in the same way as \e QFile::QFile(const
QString&).
\sa QFile::QFile()
*/
QtLockedFile::QtLockedFile(const QString &name)
: QFile(name)
{
#ifdef Q_OS_WIN
wmutex = 0;
rmutex = 0;
#endif
m_lock_mode = NoLock;
}
/*!
Opens the file in OpenMode \a mode.
This is identical to QFile::open(), with the one exception that the
Truncate mode flag is disallowed. Truncation would conflict with the
advisory file locking, since the file would be modified before the
write lock is obtained. If truncation is required, use resize(0)
after obtaining the write lock.
Returns true if successful; otherwise false.
\sa QFile::open(), QFile::resize()
*/
bool QtLockedFile::open(OpenMode mode)
{
if (mode & QIODevice::Truncate) {
qWarning("QtLockedFile::open(): Truncate mode not allowed.");
return false;
}
return QFile::open(mode);
}
/*!
Returns \e true if this object has a in read or write lock;
otherwise returns \e false.
\sa lockMode()
*/
bool QtLockedFile::isLocked() const
{
return m_lock_mode != NoLock;
}
/*!
Returns the type of lock currently held by this object, or \e
QtLockedFile::NoLock.
\sa isLocked()
*/
QtLockedFile::LockMode QtLockedFile::lockMode() const
{
return m_lock_mode;
}
/*!
\fn bool QtLockedFile::lock(LockMode mode, bool block = true)
Obtains a lock of type \a mode. The file must be opened before it
can be locked.
If \a block is true, this function will block until the lock is
aquired. If \a block is false, this function returns \e false
immediately if the lock cannot be aquired.
If this object already has a lock of type \a mode, this function
returns \e true immediately. If this object has a lock of a
different type than \a mode, the lock is first released and then a
new lock is obtained.
This function returns \e true if, after it executes, the file is
locked by this object, and \e false otherwise.
\sa unlock(), isLocked(), lockMode()
*/
/*!
\fn bool QtLockedFile::unlock()
Releases a lock.
If the object has no lock, this function returns immediately.
This function returns \e true if, after it executes, the file is
not locked by this object, and \e false otherwise.
\sa lock(), isLocked(), lockMode()
*/
/*!
\fn QtLockedFile::~QtLockedFile()
Destroys the \e QtLockedFile object. If any locks were held, they
are released.
*/

View File

@ -1,97 +0,0 @@
/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Solutions component.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "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 Digia Plc and its Subsidiary(-ies) 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."
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QTLOCKEDFILE_H
#define QTLOCKEDFILE_H
#include <QFile>
#ifdef Q_OS_WIN
#include <QVector>
#endif
#if defined(Q_OS_WIN)
# if !defined(QT_QTLOCKEDFILE_EXPORT) && !defined(QT_QTLOCKEDFILE_IMPORT)
# define QT_QTLOCKEDFILE_EXPORT
# elif defined(QT_QTLOCKEDFILE_IMPORT)
# if defined(QT_QTLOCKEDFILE_EXPORT)
# undef QT_QTLOCKEDFILE_EXPORT
# endif
# define QT_QTLOCKEDFILE_EXPORT __declspec(dllimport)
# elif defined(QT_QTLOCKEDFILE_EXPORT)
# undef QT_QTLOCKEDFILE_EXPORT
# define QT_QTLOCKEDFILE_EXPORT __declspec(dllexport)
# endif
#else
# define QT_QTLOCKEDFILE_EXPORT
#endif
namespace QtLP_Private {
class QT_QTLOCKEDFILE_EXPORT QtLockedFile : public QFile
{
public:
enum LockMode { NoLock = 0, ReadLock, WriteLock };
QtLockedFile();
QtLockedFile(const QString &name);
~QtLockedFile();
bool open(OpenMode mode);
bool lock(LockMode mode, bool block = true);
bool unlock();
bool isLocked() const;
LockMode lockMode() const;
private:
#ifdef Q_OS_WIN
Qt::HANDLE wmutex;
Qt::HANDLE rmutex;
QVector<Qt::HANDLE> rmutexes;
QString mutexname;
Qt::HANDLE getMutexHandle(int idx, bool doCreate);
bool waitMutex(Qt::HANDLE mutex, bool doBlock);
#endif
LockMode m_lock_mode;
};
}
#endif

View File

@ -1,115 +0,0 @@
/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Solutions component.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "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 Digia Plc and its Subsidiary(-ies) 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."
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include "qtlockedfile.h"
bool QtLockedFile::lock(LockMode mode, bool block)
{
if (!isOpen()) {
qWarning("QtLockedFile::lock(): file is not opened");
return false;
}
if (mode == NoLock)
return unlock();
if (mode == m_lock_mode)
return true;
if (m_lock_mode != NoLock)
unlock();
struct flock fl;
fl.l_whence = SEEK_SET;
fl.l_start = 0;
fl.l_len = 0;
fl.l_type = (mode == ReadLock) ? F_RDLCK : F_WRLCK;
int cmd = block ? F_SETLKW : F_SETLK;
int ret = fcntl(handle(), cmd, &fl);
if (ret == -1) {
if (errno != EINTR && errno != EAGAIN)
qWarning("QtLockedFile::lock(): fcntl: %s", strerror(errno));
return false;
}
m_lock_mode = mode;
return true;
}
bool QtLockedFile::unlock()
{
if (!isOpen()) {
qWarning("QtLockedFile::unlock(): file is not opened");
return false;
}
if (!isLocked())
return true;
struct flock fl;
fl.l_whence = SEEK_SET;
fl.l_start = 0;
fl.l_len = 0;
fl.l_type = F_UNLCK;
int ret = fcntl(handle(), F_SETLKW, &fl);
if (ret == -1) {
qWarning("QtLockedFile::lock(): fcntl: %s", strerror(errno));
return false;
}
m_lock_mode = NoLock;
return true;
}
QtLockedFile::~QtLockedFile()
{
if (isOpen())
unlock();
}

View File

@ -1,211 +0,0 @@
/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Solutions component.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "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 Digia Plc and its Subsidiary(-ies) 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."
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qtlockedfile.h"
#include <qt_windows.h>
#include <QFileInfo>
#define MUTEX_PREFIX "QtLockedFile mutex "
// Maximum number of concurrent read locks. Must not be greater than MAXIMUM_WAIT_OBJECTS
#define MAX_READERS MAXIMUM_WAIT_OBJECTS
#if QT_VERSION >= 0x050000
#define QT_WA(unicode, ansi) unicode
#endif
Qt::HANDLE QtLockedFile::getMutexHandle(int idx, bool doCreate)
{
if (mutexname.isEmpty()) {
QFileInfo fi(*this);
mutexname = QString::fromLatin1(MUTEX_PREFIX)
+ fi.absoluteFilePath().toLower();
}
QString mname(mutexname);
if (idx >= 0)
mname += QString::number(idx);
Qt::HANDLE mutex;
if (doCreate) {
QT_WA( { mutex = CreateMutexW(NULL, FALSE, (TCHAR*)mname.utf16()); },
{ mutex = CreateMutexA(NULL, FALSE, mname.toLocal8Bit().constData()); } );
if (!mutex) {
qErrnoWarning("QtLockedFile::lock(): CreateMutex failed");
return 0;
}
}
else {
QT_WA( { mutex = OpenMutexW(SYNCHRONIZE | MUTEX_MODIFY_STATE, FALSE, (TCHAR*)mname.utf16()); },
{ mutex = OpenMutexA(SYNCHRONIZE | MUTEX_MODIFY_STATE, FALSE, mname.toLocal8Bit().constData()); } );
if (!mutex) {
if (GetLastError() != ERROR_FILE_NOT_FOUND)
qErrnoWarning("QtLockedFile::lock(): OpenMutex failed");
return 0;
}
}
return mutex;
}
bool QtLockedFile::waitMutex(Qt::HANDLE mutex, bool doBlock)
{
Q_ASSERT(mutex);
DWORD res = WaitForSingleObject(mutex, doBlock ? INFINITE : 0);
switch (res) {
case WAIT_OBJECT_0:
case WAIT_ABANDONED:
return true;
break;
case WAIT_TIMEOUT:
break;
default:
qErrnoWarning("QtLockedFile::lock(): WaitForSingleObject failed");
}
return false;
}
bool QtLockedFile::lock(LockMode mode, bool block)
{
if (!isOpen()) {
qWarning("QtLockedFile::lock(): file is not opened");
return false;
}
if (mode == NoLock)
return unlock();
if (mode == m_lock_mode)
return true;
if (m_lock_mode != NoLock)
unlock();
if (!wmutex && !(wmutex = getMutexHandle(-1, true)))
return false;
if (!waitMutex(wmutex, block))
return false;
if (mode == ReadLock) {
int idx = 0;
for (; idx < MAX_READERS; idx++) {
rmutex = getMutexHandle(idx, false);
if (!rmutex || waitMutex(rmutex, false))
break;
CloseHandle(rmutex);
}
bool ok = true;
if (idx >= MAX_READERS) {
qWarning("QtLockedFile::lock(): too many readers");
rmutex = 0;
ok = false;
}
else if (!rmutex) {
rmutex = getMutexHandle(idx, true);
if (!rmutex || !waitMutex(rmutex, false))
ok = false;
}
if (!ok && rmutex) {
CloseHandle(rmutex);
rmutex = 0;
}
ReleaseMutex(wmutex);
if (!ok)
return false;
}
else {
Q_ASSERT(rmutexes.isEmpty());
for (int i = 0; i < MAX_READERS; i++) {
Qt::HANDLE mutex = getMutexHandle(i, false);
if (mutex)
rmutexes.append(mutex);
}
if (rmutexes.size()) {
DWORD res = WaitForMultipleObjects(rmutexes.size(), rmutexes.constData(),
TRUE, block ? INFINITE : 0);
if (res != WAIT_OBJECT_0 && res != WAIT_ABANDONED) {
if (res != WAIT_TIMEOUT)
qErrnoWarning("QtLockedFile::lock(): WaitForMultipleObjects failed");
m_lock_mode = WriteLock; // trick unlock() to clean up - semiyucky
unlock();
return false;
}
}
}
m_lock_mode = mode;
return true;
}
bool QtLockedFile::unlock()
{
if (!isOpen()) {
qWarning("QtLockedFile::unlock(): file is not opened");
return false;
}
if (!isLocked())
return true;
if (m_lock_mode == ReadLock) {
ReleaseMutex(rmutex);
CloseHandle(rmutex);
rmutex = 0;
}
else {
foreach(Qt::HANDLE mutex, rmutexes) {
ReleaseMutex(mutex);
CloseHandle(mutex);
}
rmutexes.clear();
ReleaseMutex(wmutex);
}
m_lock_mode = QtLockedFile::NoLock;
return true;
}
QtLockedFile::~QtLockedFile()
{
if (isOpen())
unlock();
if (wmutex)
CloseHandle(wmutex);
}

View File

@ -1,347 +0,0 @@
/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Solutions component.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "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 Digia Plc and its Subsidiary(-ies) 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."
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qtsingleapplication.h"
#include "qtlocalpeer.h"
#include <QWidget>
/*!
\class QtSingleApplication qtsingleapplication.h
\brief The QtSingleApplication class provides an API to detect and
communicate with running instances of an application.
This class allows you to create applications where only one
instance should be running at a time. I.e., if the user tries to
launch another instance, the already running instance will be
activated instead. Another usecase is a client-server system,
where the first started instance will assume the role of server,
and the later instances will act as clients of that server.
By default, the full path of the executable file is used to
determine whether two processes are instances of the same
application. You can also provide an explicit identifier string
that will be compared instead.
The application should create the QtSingleApplication object early
in the startup phase, and call isRunning() to find out if another
instance of this application is already running. If isRunning()
returns false, it means that no other instance is running, and
this instance has assumed the role as the running instance. In
this case, the application should continue with the initialization
of the application user interface before entering the event loop
with exec(), as normal.
The messageReceived() signal will be emitted when the running
application receives messages from another instance of the same
application. When a message is received it might be helpful to the
user to raise the application so that it becomes visible. To
facilitate this, QtSingleApplication provides the
setActivationWindow() function and the activateWindow() slot.
If isRunning() returns true, another instance is already
running. It may be alerted to the fact that another instance has
started by using the sendMessage() function. Also data such as
startup parameters (e.g. the name of the file the user wanted this
new instance to open) can be passed to the running instance with
this function. Then, the application should terminate (or enter
client mode).
If isRunning() returns true, but sendMessage() fails, that is an
indication that the running instance is frozen.
Here's an example that shows how to convert an existing
application to use QtSingleApplication. It is very simple and does
not make use of all QtSingleApplication's functionality (see the
examples for that).
\code
// Original
int main(int argc, char **argv)
{
QApplication app(argc, argv);
MyMainWidget mmw;
mmw.show();
return app.exec();
}
// Single instance
int main(int argc, char **argv)
{
QtSingleApplication app(argc, argv);
if (app.isRunning())
return !app.sendMessage(someDataString);
MyMainWidget mmw;
app.setActivationWindow(&mmw);
mmw.show();
return app.exec();
}
\endcode
Once this QtSingleApplication instance is destroyed (normally when
the process exits or crashes), when the user next attempts to run the
application this instance will not, of course, be encountered. The
next instance to call isRunning() or sendMessage() will assume the
role as the new running instance.
For console (non-GUI) applications, QtSingleCoreApplication may be
used instead of this class, to avoid the dependency on the QtGui
library.
\sa QtSingleCoreApplication
*/
void QtSingleApplication::sysInit(const QString &appId)
{
actWin = 0;
peer = new QtLocalPeer(this, appId);
connect(peer, SIGNAL(messageReceived(const QString&)), SIGNAL(messageReceived(const QString&)));
}
/*!
Creates a QtSingleApplication object. The application identifier
will be QCoreApplication::applicationFilePath(). \a argc, \a
argv, and \a GUIenabled are passed on to the QAppliation constructor.
If you are creating a console application (i.e. setting \a
GUIenabled to false), you may consider using
QtSingleCoreApplication instead.
*/
QtSingleApplication::QtSingleApplication(int &argc, char **argv, bool GUIenabled)
: QApplication(argc, argv, GUIenabled)
{
sysInit();
}
/*!
Creates a QtSingleApplication object with the application
identifier \a appId. \a argc and \a argv are passed on to the
QAppliation constructor.
*/
QtSingleApplication::QtSingleApplication(const QString &appId, int &argc, char **argv)
: QApplication(argc, argv)
{
sysInit(appId);
}
#if QT_VERSION < 0x050000
/*!
Creates a QtSingleApplication object. The application identifier
will be QCoreApplication::applicationFilePath(). \a argc, \a
argv, and \a type are passed on to the QAppliation constructor.
*/
QtSingleApplication::QtSingleApplication(int &argc, char **argv, Type type)
: QApplication(argc, argv, type)
{
sysInit();
}
# if defined(Q_WS_X11)
/*!
Special constructor for X11, ref. the documentation of
QApplication's corresponding constructor. The application identifier
will be QCoreApplication::applicationFilePath(). \a dpy, \a visual,
and \a cmap are passed on to the QApplication constructor.
*/
QtSingleApplication::QtSingleApplication(Display* dpy, Qt::HANDLE visual, Qt::HANDLE cmap)
: QApplication(dpy, visual, cmap)
{
sysInit();
}
/*!
Special constructor for X11, ref. the documentation of
QApplication's corresponding constructor. The application identifier
will be QCoreApplication::applicationFilePath(). \a dpy, \a argc, \a
argv, \a visual, and \a cmap are passed on to the QApplication
constructor.
*/
QtSingleApplication::QtSingleApplication(Display *dpy, int &argc, char **argv, Qt::HANDLE visual, Qt::HANDLE cmap)
: QApplication(dpy, argc, argv, visual, cmap)
{
sysInit();
}
/*!
Special constructor for X11, ref. the documentation of
QApplication's corresponding constructor. The application identifier
will be \a appId. \a dpy, \a argc, \a
argv, \a visual, and \a cmap are passed on to the QApplication
constructor.
*/
QtSingleApplication::QtSingleApplication(Display* dpy, const QString &appId, int argc, char **argv, Qt::HANDLE visual, Qt::HANDLE cmap)
: QApplication(dpy, argc, argv, visual, cmap)
{
sysInit(appId);
}
# endif // Q_WS_X11
#endif // QT_VERSION < 0x050000
/*!
Returns true if another instance of this application is running;
otherwise false.
This function does not find instances of this application that are
being run by a different user (on Windows: that are running in
another session).
\sa sendMessage()
*/
bool QtSingleApplication::isRunning()
{
return peer->isClient();
}
/*!
Tries to send the text \a message to the currently running
instance. The QtSingleApplication object in the running instance
will emit the messageReceived() signal when it receives the
message.
This function returns true if the message has been sent to, and
processed by, the current instance. If there is no instance
currently running, or if the running instance fails to process the
message within \a timeout milliseconds, this function return false.
\sa isRunning(), messageReceived()
*/
bool QtSingleApplication::sendMessage(const QString &message, int timeout)
{
return peer->sendMessage(message, timeout);
}
/*!
Returns the application identifier. Two processes with the same
identifier will be regarded as instances of the same application.
*/
QString QtSingleApplication::id() const
{
return peer->applicationId();
}
/*!
Sets the activation window of this application to \a aw. The
activation window is the widget that will be activated by
activateWindow(). This is typically the application's main window.
If \a activateOnMessage is true (the default), the window will be
activated automatically every time a message is received, just prior
to the messageReceived() signal being emitted.
\sa activateWindow(), messageReceived()
*/
void QtSingleApplication::setActivationWindow(QWidget* aw, bool activateOnMessage)
{
actWin = aw;
if (activateOnMessage)
connect(peer, SIGNAL(messageReceived(const QString&)), this, SLOT(activateWindow()));
else
disconnect(peer, SIGNAL(messageReceived(const QString&)), this, SLOT(activateWindow()));
}
/*!
Returns the applications activation window if one has been set by
calling setActivationWindow(), otherwise returns 0.
\sa setActivationWindow()
*/
QWidget* QtSingleApplication::activationWindow() const
{
return actWin;
}
/*!
De-minimizes, raises, and activates this application's activation window.
This function does nothing if no activation window has been set.
This is a convenience function to show the user that this
application instance has been activated when he has tried to start
another instance.
This function should typically be called in response to the
messageReceived() signal. By default, that will happen
automatically, if an activation window has been set.
\sa setActivationWindow(), messageReceived(), initialize()
*/
void QtSingleApplication::activateWindow()
{
if (actWin) {
actWin->setWindowState(actWin->windowState() & ~Qt::WindowMinimized);
actWin->raise();
actWin->activateWindow();
}
}
/*!
\fn void QtSingleApplication::messageReceived(const QString& message)
This signal is emitted when the current instance receives a \a
message from another instance of this application.
\sa sendMessage(), setActivationWindow(), activateWindow()
*/
/*!
\fn void QtSingleApplication::initialize(bool dummy = true)
\obsolete
*/

View File

@ -1,105 +0,0 @@
/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Solutions component.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "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 Digia Plc and its Subsidiary(-ies) 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."
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QTSINGLEAPPLICATION_H
#define QTSINGLEAPPLICATION_H
#include <QApplication>
class QtLocalPeer;
#if defined(Q_OS_WIN)
# if !defined(QT_QTSINGLEAPPLICATION_EXPORT) && !defined(QT_QTSINGLEAPPLICATION_IMPORT)
# define QT_QTSINGLEAPPLICATION_EXPORT
# elif defined(QT_QTSINGLEAPPLICATION_IMPORT)
# if defined(QT_QTSINGLEAPPLICATION_EXPORT)
# undef QT_QTSINGLEAPPLICATION_EXPORT
# endif
# define QT_QTSINGLEAPPLICATION_EXPORT __declspec(dllimport)
# elif defined(QT_QTSINGLEAPPLICATION_EXPORT)
# undef QT_QTSINGLEAPPLICATION_EXPORT
# define QT_QTSINGLEAPPLICATION_EXPORT __declspec(dllexport)
# endif
#else
# define QT_QTSINGLEAPPLICATION_EXPORT
#endif
class QT_QTSINGLEAPPLICATION_EXPORT QtSingleApplication : public QApplication
{
Q_OBJECT
public:
QtSingleApplication(int &argc, char **argv, bool GUIenabled = true);
QtSingleApplication(const QString &id, int &argc, char **argv);
#if QT_VERSION < 0x050000
QtSingleApplication(int &argc, char **argv, Type type);
# if defined(Q_WS_X11)
QtSingleApplication(Display* dpy, Qt::HANDLE visual = 0, Qt::HANDLE colormap = 0);
QtSingleApplication(Display *dpy, int &argc, char **argv, Qt::HANDLE visual = 0, Qt::HANDLE cmap= 0);
QtSingleApplication(Display* dpy, const QString &appId, int argc, char **argv, Qt::HANDLE visual = 0, Qt::HANDLE colormap = 0);
# endif // Q_WS_X11
#endif // QT_VERSION < 0x050000
bool isRunning();
QString id() const;
void setActivationWindow(QWidget* aw, bool activateOnMessage = true);
QWidget* activationWindow() const;
// Obsolete:
void initialize(bool dummy = true)
{ isRunning(); Q_UNUSED(dummy) }
public Q_SLOTS:
bool sendMessage(const QString &message, int timeout = 5000);
void activateWindow();
Q_SIGNALS:
void messageReceived(const QString &message);
private:
void sysInit(const QString &appId = QString());
QtLocalPeer *peer;
QWidget *actWin;
};
#endif // QTSINGLEAPPLICATION_H

View File

@ -1,17 +0,0 @@
#include(../common.pri)
INCLUDEPATH += $$PWD
DEPENDPATH += $$PWD
QT *= network
greaterThan(QT_MAJOR_VERSION, 4): QT *= widgets
qtsingleapplication-uselib:!qtsingleapplication-buildlib {
LIBS += -L$$QTSINGLEAPPLICATION_LIBDIR -l$$QTSINGLEAPPLICATION_LIBNAME
} else {
SOURCES += $$PWD/qtsingleapplication.cpp $$PWD/qtlocalpeer.cpp
HEADERS += $$PWD/qtsingleapplication.h $$PWD/qtlocalpeer.h
}
win32 {
contains(TEMPLATE, lib):contains(CONFIG, shared):DEFINES += QT_QTSINGLEAPPLICATION_EXPORT
else:qtsingleapplication-uselib:DEFINES += QT_QTSINGLEAPPLICATION_IMPORT
}

View File

@ -1,149 +0,0 @@
/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Solutions component.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "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 Digia Plc and its Subsidiary(-ies) 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."
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qtsinglecoreapplication.h"
#include "qtlocalpeer.h"
/*!
\class QtSingleCoreApplication qtsinglecoreapplication.h
\brief A variant of the QtSingleApplication class for non-GUI applications.
This class is a variant of QtSingleApplication suited for use in
console (non-GUI) applications. It is an extension of
QCoreApplication (instead of QApplication). It does not require
the QtGui library.
The API and usage is identical to QtSingleApplication, except that
functions relating to the "activation window" are not present, for
obvious reasons. Please refer to the QtSingleApplication
documentation for explanation of the usage.
A QtSingleCoreApplication instance can communicate to a
QtSingleApplication instance if they share the same application
id. Hence, this class can be used to create a light-weight
command-line tool that sends commands to a GUI application.
\sa QtSingleApplication
*/
/*!
Creates a QtSingleCoreApplication object. The application identifier
will be QCoreApplication::applicationFilePath(). \a argc and \a
argv are passed on to the QCoreAppliation constructor.
*/
QtSingleCoreApplication::QtSingleCoreApplication(int &argc, char **argv)
: QCoreApplication(argc, argv)
{
peer = new QtLocalPeer(this);
connect(peer, SIGNAL(messageReceived(const QString&)), SIGNAL(messageReceived(const QString&)));
}
/*!
Creates a QtSingleCoreApplication object with the application
identifier \a appId. \a argc and \a argv are passed on to the
QCoreAppliation constructor.
*/
QtSingleCoreApplication::QtSingleCoreApplication(const QString &appId, int &argc, char **argv)
: QCoreApplication(argc, argv)
{
peer = new QtLocalPeer(this, appId);
connect(peer, SIGNAL(messageReceived(const QString&)), SIGNAL(messageReceived(const QString&)));
}
/*!
Returns true if another instance of this application is running;
otherwise false.
This function does not find instances of this application that are
being run by a different user (on Windows: that are running in
another session).
\sa sendMessage()
*/
bool QtSingleCoreApplication::isRunning()
{
return peer->isClient();
}
/*!
Tries to send the text \a message to the currently running
instance. The QtSingleCoreApplication object in the running instance
will emit the messageReceived() signal when it receives the
message.
This function returns true if the message has been sent to, and
processed by, the current instance. If there is no instance
currently running, or if the running instance fails to process the
message within \a timeout milliseconds, this function return false.
\sa isRunning(), messageReceived()
*/
bool QtSingleCoreApplication::sendMessage(const QString &message, int timeout)
{
return peer->sendMessage(message, timeout);
}
/*!
Returns the application identifier. Two processes with the same
identifier will be regarded as instances of the same application.
*/
QString QtSingleCoreApplication::id() const
{
return peer->applicationId();
}
/*!
\fn void QtSingleCoreApplication::messageReceived(const QString& message)
This signal is emitted when the current instance receives a \a
message from another instance of this application.
\sa sendMessage()
*/

View File

@ -1,71 +0,0 @@
/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Solutions component.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "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 Digia Plc and its Subsidiary(-ies) 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."
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QTSINGLECOREAPPLICATION_H
#define QTSINGLECOREAPPLICATION_H
#include <QCoreApplication>
class QtLocalPeer;
class QtSingleCoreApplication : public QCoreApplication
{
Q_OBJECT
public:
QtSingleCoreApplication(int &argc, char **argv);
QtSingleCoreApplication(const QString &id, int &argc, char **argv);
bool isRunning();
QString id() const;
public Q_SLOTS:
bool sendMessage(const QString &message, int timeout = 5000);
Q_SIGNALS:
void messageReceived(const QString &message);
private:
QtLocalPeer* peer;
};
#endif // QTSINGLECOREAPPLICATION_H

View File

@ -1,10 +0,0 @@
INCLUDEPATH += $$PWD
DEPENDPATH += $$PWD
HEADERS += $$PWD/qtsinglecoreapplication.h $$PWD/qtlocalpeer.h
SOURCES += $$PWD/qtsinglecoreapplication.cpp $$PWD/qtlocalpeer.cpp
QT *= network
win32:contains(TEMPLATE, lib):contains(CONFIG, shared) {
DEFINES += QT_QTSINGLECOREAPPLICATION_EXPORT=__declspec(dllexport)
}

View File

@ -1,11 +1,11 @@
<RCC>
<qresource prefix="/">
<file>translations/translation_en.qm</file>
<file>translations/translation_ru.qm</file>
<file>images/STIconBlue.png</file>
<file>images/STIconGreen.png</file>
<file>images/STIconOrange.png</file>
<file>images/STIconRed.png</file>
<file>version.json</file>
</qresource>
<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>
</qresource>
</RCC>

92
screen-translator.pro Normal file
View File

@ -0,0 +1,92 @@
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 += -ltesseract -lleptonica
win32{
LIBS += -lUser32
}
linux{
QT += x11extras
LIBS += -lX11
}
DEFINES += VERSION="3.0.0"
VERSION = 3.0.0.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/captureoverlay.h \
src/capture/capturer.h \
src/correct/corrector.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/resultwidget.h \
src/service/apptranslator.h \
src/service/debug.h \
src/service/globalaction.h \
src/service/singleapplication.h \
src/service/widgetstate.h \
src/settings.h \
src/settingseditor.h \
src/stfwd.h \
src/task.h \
src/translate/translator.h \
src/translate/webpage.h \
src/translate/webpageproxy.h \
src/trayicon.h
SOURCES += \
src/capture/captureoverlay.cpp \
src/capture/capturer.cpp \
src/correct/corrector.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/resultwidget.cpp \
src/service/apptranslator.cpp \
src/service/debug.cpp \
src/service/globalaction.cpp \
src/service/singleapplication.cpp \
src/service/widgetstate.cpp \
src/settings.cpp \
src/settingseditor.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
#TRANSLATIONS += \
# translations/translation_en.ts \
# translations/translation_ru.ts

View File

@ -1,96 +0,0 @@
#-------------------------------------------------
#
# Project created by QtCreator 2013-11-22T12:00:23
#
#-------------------------------------------------
QT += core gui network webkitwidgets
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = ScreenTranslator
TEMPLATE = app
CONFIG += c++11
INCLUDEPATH += $$(DEPS_DIR)/include
LIBS += -L$$(DEPS_DIR)/lib
win32{
LIBS += --lUser32 -lws2_32
}
linux{
QT += x11extras
LIBS += -lX11 -Wl,-rpath,.
}
LIBS += -ltesseract -lleptonica
include(3rd-party/qtsingleapplication/qtsingleapplication.pri)
INCLUDEPATH += src
SOURCES += \
src/main.cpp \
src/manager.cpp \
src/settingseditor.cpp \
src/selectiondialog.cpp \
src/globalactionhelper.cpp \
src/recognizer.cpp \
src/resultdialog.cpp \
src/processingitem.cpp \
src/imageprocessing.cpp \
src/languagehelper.cpp \
src/webtranslator.cpp \
src/webtranslatorproxy.cpp \
src/translatorhelper.cpp \
src/recognizerhelper.cpp \
src/utils.cpp \
src/updater.cpp
HEADERS += \
src/manager.h \
src/settingseditor.h \
src/selectiondialog.h \
src/globalactionhelper.h \
src/recognizer.h \
src/settings.h \
src/processingitem.h \
src/resultdialog.h \
src/imageprocessing.h \
src/languagehelper.h \
src/webtranslator.h \
src/webtranslatorproxy.h \
src/stassert.h \
src/translatorhelper.h \
src/recognizerhelper.h \
src/utils.h \
src/updater.h
FORMS += \
src/settingseditor.ui \
src/selectiondialog.ui \
src/resultdialog.ui
RESOURCES += \
recources.qrc
TRANSLATIONS += \
translations/translation_en.ts \
translations/translation_ru.ts
OTHER_FILES += \
images/* \
translators/* \
scripts/* \
distr/* \
version.json \
README.md \
share/uncrustify.cfg \
.travis.yml \
TODO.md
QMAKE_TARGET_COMPANY = Gres
QMAKE_TARGET_PRODUCT = Screen Translator
QMAKE_TARGET_COPYRIGHT = Copyright (c) Gres
VERSION = 2.0.0.0
RC_ICONS = images/icon.ico

View File

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 97 KiB

View File

@ -0,0 +1,93 @@
#include "captureoverlay.h"
#include "capturer.h"
#include "task.h"
#include <QMouseEvent>
#include <QPainter>
#include <QScreen>
CaptureOverlay::CaptureOverlay(Capturer &capturer)
: capturer_(capturer)
{
setWindowFlags(Qt::FramelessWindowHint | Qt::NoDropShadowWindowHint |
Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint);
setCursor(Qt::CrossCursor);
}
void CaptureOverlay::setScreen(QScreen &screen)
{
const auto geometry = screen.availableGeometry();
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
const auto pixmap =
screen.grabWindow(0, 0, 0, geometry.width(), geometry.height());
#else
const auto pixmap = screen.grabWindow(0, geometry.x(), geometry.y(),
geometry.width(), geometry.height());
#endif
pixmap_ = pixmap;
auto palette = this->palette();
palette.setBrush(backgroundRole(), pixmap);
setPalette(palette);
setGeometry(geometry);
}
void CaptureOverlay::showEvent(QShowEvent * /*event*/)
{
startSelectPos_ = currentSelectPos_ = QPoint();
}
void CaptureOverlay::keyPressEvent(QKeyEvent *event)
{
if (event->key() == Qt::Key_Escape)
capturer_.canceled();
}
void CaptureOverlay::paintEvent(QPaintEvent * /*event*/)
{
auto selection = QRect(startSelectPos_, currentSelectPos_).normalized();
if (!selection.isValid())
return;
QPainter painter(this);
painter.setPen(Qt::red);
painter.drawRect(selection);
}
void CaptureOverlay::mousePressEvent(QMouseEvent *event)
{
if (startSelectPos_.isNull())
startSelectPos_ = event->pos();
}
void CaptureOverlay::mouseMoveEvent(QMouseEvent *event)
{
if (startSelectPos_.isNull())
return;
currentSelectPos_ = event->pos();
repaint();
}
void CaptureOverlay::mouseReleaseEvent(QMouseEvent *event)
{
if (startSelectPos_.isNull() || pixmap_.isNull())
return;
const auto endPos = event->pos();
const auto selection = QRect(startSelectPos_, endPos).normalized();
const auto selectedPixmap = pixmap_.copy(selection);
startSelectPos_ = currentSelectPos_ = QPoint();
if (selectedPixmap.width() < 3 || selectedPixmap.height() < 3) {
capturer_.canceled();
return;
}
auto task = std::make_shared<Task>();
task->captured = selectedPixmap;
task->capturePoint = pos() + selection.topLeft();
// TODO add customization menus
capturer_.captured(task);
}

View File

@ -0,0 +1,30 @@
#pragma once
#include <QWidget>
class Capturer;
class QScreen;
class CaptureOverlay : public QWidget
{
Q_OBJECT
public:
explicit CaptureOverlay(Capturer &capturer);
void setScreen(QScreen &screen);
protected:
void showEvent(QShowEvent *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:
Capturer &capturer_;
QPixmap pixmap_;
QPoint startSelectPos_;
QPoint currentSelectPos_;
};

80
src/capture/capturer.cpp Normal file
View File

@ -0,0 +1,80 @@
#include "capturer.h"
#include "captureoverlay.h"
#include "manager.h"
#include "settings.h"
#include "task.h"
#include <QApplication>
Capturer::Capturer(Manager &manager)
: manager_(manager)
{
}
void Capturer::capture()
{
showOverlays(true);
}
void Capturer::repeatCapture()
{
showOverlays(false);
}
void Capturer::updateSettings(const Settings &settings)
{
sourceLanguage_ = settings.sourceLanguage;
targetLanguage_ = settings.targetLanguage;
translators_ = settings.translators;
}
void Capturer::captured(const TaskPtr &task)
{
hideOverlays();
// TODO respect more overlay's options
// TODO process modifiers
task->translators = translators_;
task->sourceLanguage = sourceLanguage_;
task->targetLanguage = targetLanguage_;
manager_.captured(task);
}
void Capturer::canceled()
{
hideOverlays();
manager_.captureCanceled();
}
void Capturer::showOverlays(bool capturePixmap)
{
const auto screens = QApplication::screens();
const auto screensSize = screens.size();
int overlaysSize = overlays_.size();
if (screensSize > overlaysSize)
overlays_.reserve(screensSize);
for (auto i = 0, end = screensSize; i < end; ++i) {
if (i == overlaysSize) {
overlays_.push_back(new CaptureOverlay(*this));
++overlaysSize;
}
const auto screen = screens[i];
auto &overlay = overlays_[i];
overlay->hide();
if (capturePixmap)
overlay->setScreen(*screen);
overlay->show();
overlay->activateWindow();
}
if (screensSize < overlaysSize) {
for (auto i = overlaysSize - 1; i >= screensSize; --i)
overlays_[i]->deleteLater();
}
}
void Capturer::hideOverlays()
{
for (const auto &overlay : overlays_) overlay->hide();
}

33
src/capture/capturer.h Normal file
View File

@ -0,0 +1,33 @@
#pragma once
#include "stfwd.h"
#include <QStringList>
#include <vector>
class CaptureOverlay;
class Capturer
{
public:
explicit Capturer(Manager &manager);
void capture();
void repeatCapture();
void updateSettings(const Settings &settings);
void captured(const TaskPtr &task);
void canceled();
private:
void showOverlays(bool capturePixmap);
void hideOverlays();
Manager &manager_;
std::vector<CaptureOverlay *> overlays_;
LanguageId sourceLanguage_;
LanguageId targetLanguage_;
QStringList translators_;
};

56
src/correct/corrector.cpp Normal file
View File

@ -0,0 +1,56 @@
#include "corrector.h"
#include "debug.h"
#include "manager.h"
#include "task.h"
Corrector::Corrector(Manager &manager)
: manager_(manager)
{
}
void Corrector::correct(const TaskPtr &task)
{
SOFT_ASSERT(task, return );
SOFT_ASSERT(task->isValid(), return );
if (!userSubstitutions_.empty())
task->recognized = substituteUser(task->recognized, task->sourceLanguage);
manager_.corrected(task);
}
void Corrector::updateSettings(const Settings &settings)
{
userSubstitutions_ = settings.userSubstitutions;
}
QString Corrector::substituteUser(const QString &source,
const LanguageId &language) const
{
auto result = source;
const auto range = userSubstitutions_.equal_range(language);
if (range.first == userSubstitutions_.cend())
return result;
while (true) {
auto bestMatch = range.first;
auto bestMatchLen = 0;
for (auto it = range.first; it != range.second; ++it) {
const auto &sub = it->second;
const auto len = sub.source.length();
if (len > bestMatchLen) {
bestMatchLen = len;
bestMatch = it;
}
}
if (bestMatchLen < 1)
break;
result.replace(bestMatch->second.source, bestMatch->second.target);
}
return result;
}

20
src/correct/corrector.h Normal file
View File

@ -0,0 +1,20 @@
#pragma once
#include "settings.h"
#include "stfwd.h"
class Corrector
{
public:
explicit Corrector(Manager &manager);
void correct(const TaskPtr &task);
void updateSettings(const Settings &settings);
private:
QString substituteUser(const QString &source,
const LanguageId &language) const;
Manager &manager_;
Substitutions userSubstitutions_;
};

View File

@ -1,367 +0,0 @@
#include "globalactionhelper.h"
#include <QDebug>
#include <QApplication>
QHash<QPair<quint32, quint32>, QAction *> GlobalActionHelper::actions_;
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;
}
void GlobalActionHelper::triggerHotKey (quint32 nativeKey, quint32 nativeMods) {
QAction *action = actions_.value (qMakePair (nativeKey, nativeMods));
if (action && action->isEnabled ()) {
action->activate (QAction::Trigger);
}
}
#if defined(Q_OS_LINUX)
# include <QX11Info>
# include <X11/Xlib.h>
# include <xcb/xcb_event.h>
static bool error = false;
static int customHandler (Display *display, XErrorEvent *event) {
Q_UNUSED (display);
switch (event->error_code) {
case BadAccess:
case BadValue:
case BadWindow:
if (event->request_code == 33 /* X_GrabKey */ ||
event->request_code == 34 /* X_UngrabKey */) {
error = true;
}
default:
return 0;
}
}
bool GlobalActionHelper::registerHotKey (quint32 nativeKey, quint32 nativeMods) {
Display *display = QX11Info::display ();
Window window = QX11Info::appRootWindow ();
Bool owner = True;
int pointer = GrabModeAsync;
int keyboard = GrabModeAsync;
error = false;
int (*handler)(Display *display, XErrorEvent *event) = XSetErrorHandler (customHandler);
XGrabKey (display, nativeKey, nativeMods, window, owner, pointer, keyboard);
// allow numlock
XGrabKey (display, nativeKey, nativeMods | Mod2Mask, window, owner, pointer, keyboard);
XSync (display, False);
XSetErrorHandler (handler);
return !error;
}
bool GlobalActionHelper::unregisterHotKey (quint32 nativeKey, quint32 nativeMods) {
Display *display = QX11Info::display ();
Window window = QX11Info::appRootWindow ();
error = false;
int (*handler)(Display *display, XErrorEvent *event) = XSetErrorHandler (customHandler);
XUngrabKey (display, nativeKey, nativeMods, window);
// allow numlock
XUngrabKey (display, nativeKey, nativeMods | Mod2Mask, window);
XSync (display, False);
XSetErrorHandler (handler);
return !error;
}
bool GlobalActionHelper::nativeEventFilter (const QByteArray &eventType,
void *message, long *result) {
Q_UNUSED (eventType);
Q_UNUSED (result);
xcb_generic_event_t *event = static_cast<xcb_generic_event_t *>(message);
if (event->response_type == XCB_KEY_PRESS) {
xcb_key_press_event_t *keyEvent = static_cast<xcb_key_press_event_t *>(message);
const quint32 keycode = keyEvent->detail;
const quint32 modifiers = keyEvent->state & ~XCB_MOD_MASK_2;
triggerHotKey (keycode, modifiers);
}
return false;
}
quint32 GlobalActionHelper::nativeKeycode (Qt::Key key) {
Display *display = QX11Info::display ();
KeySym keySym = XStringToKeysym (qPrintable (QKeySequence (key).toString ()));
return XKeysymToKeycode (display, keySym);
}
quint32 GlobalActionHelper::nativeModifiers (Qt::KeyboardModifiers modifiers) {
quint32 native = 0;
if (modifiers & Qt::ShiftModifier) {
native |= ShiftMask;
}
if (modifiers & Qt::ControlModifier) {
native |= ControlMask;
}
if (modifiers & Qt::AltModifier) {
native |= Mod1Mask;
}
if (modifiers & Qt::MetaModifier) {
native |= Mod4Mask;
}
return native;
}
#elif defined(Q_OS_WIN)
# include <qt_windows.h>
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);
}
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);
triggerHotKey (keycode, modifiers);
}
return false;
}
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;
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;
}
#endif

View File

@ -1,29 +0,0 @@
#ifndef GLOBALACTIONHELPER_H
#define GLOBALACTIONHELPER_H
// Some functions copied from QXT lib
#include <QAbstractNativeEventFilter>
#include <QAction>
class GlobalActionHelper : public QAbstractNativeEventFilter {
public:
bool nativeEventFilter (const QByteArray &eventType, void *message,
long *result);
static void init ();
static bool makeGlobal (QAction *action);
static bool removeGlobal (QAction *action);
private:
static QHash<QPair<quint32, quint32>, QAction *> actions_;
static quint32 nativeKeycode (Qt::Key key);
static quint32 nativeModifiers (Qt::KeyboardModifiers modifiers);
static bool registerHotKey (quint32 nativeKey, quint32 nativeMods);
static bool unregisterHotKey (quint32 nativeKey, quint32 nativeMods);
static void triggerHotKey (quint32 nativeKey, quint32 nativeMods);
};
#endif // GLOBALACTIONHELPER_H

View File

@ -1,155 +0,0 @@
#include <QDebug>
#include <leptonica/allheaders.h>
#include "imageprocessing.h"
#include "stassert.h"
#if defined(Q_OS_LINUX)
# include <fstream>
# include <limits>
qint64 getFreeMemory () {
std::string token;
std::ifstream file ("/proc/meminfo");
qint64 freeMem = 0;
while (file >> token) {
if (token == "MemFree:" || token == "Buffers:" || token == "Cached:") {
unsigned long mem = 0;
freeMem += (file >> mem) ? mem : 0;
}
}
return freeMem * 1024;
}
#elif defined(Q_OS_WIN)
# 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;
int width = image.width ();
int height = image.height ();
int depth = image.depth ();
int bytesPerLine = image.bytesPerLine ();
int wpl = bytesPerLine / 4;
pix = pixCreate (width, height, depth);
pixSetWpl (pix, wpl);
pixSetColormap (pix, NULL);
memmove (pix->data, image.bits (), bytesPerLine * height);
const qreal toDPM = 1.0 / 0.0254;
int resolutionX = image.dotsPerMeterX () / toDPM;
int resolutionY = image.dotsPerMeterY () / toDPM;
if (resolutionX < 300) {
resolutionX = 300;
}
if (resolutionY < 300) {
resolutionY = 300;
}
pixSetResolution (pix, resolutionX, resolutionY);
return 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 (&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;
}
Pix * prepareImage (const QImage &image, int preferredScale) {
Pix *pix = convertImage (image);
ST_ASSERT (pix != NULL);
Pix *gray = pixConvertRGBToGray (pix, 0.0, 0.0, 0.0);
ST_ASSERT (gray != NULL);
pixDestroy (&pix);
Pix *scaled = gray;
if (preferredScale > 0) {
const auto MAX_INT16 = 0x7fff;
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);
qint64 availableMemory = getFreeMemory () * 0.95;
if (availableMemory > 0) {
qint32 actualSize = gray->w * gray->h * gray->d / 8;
float maxScaleMemory = float (availableMemory) / actualSize;
scale = std::min (scale, maxScaleMemory);
}
scaled = pixScale (gray, scale, scale);
if (scaled == NULL) {
scaled = gray;
}
}
if (scaled != gray) {
pixDestroy (&gray);
}
return scaled;
}
void cleanupImage (Pix **image) {
pixDestroy (image);
}

View File

@ -1,18 +0,0 @@
#ifndef IMAGEPROCESSING_H
#define IMAGEPROCESSING_H
#include <QImage>
class Pix;
//! Convert QImage to Leptonica's PIX.
Pix * convertImage (const QImage &image);
//! Convert Leptonica's PIX to QImage.
QImage convertImage (Pix &image);
//! Propare image for OCR.
Pix * prepareImage (const QImage &image, int preferredScale);
//! Free allocated resources for image.
void cleanupImage (Pix **image);
#endif // IMAGEPROCESSING_H

240
src/languagecodes.cpp Normal file
View File

@ -0,0 +1,240 @@
#include "languagecodes.h"
#include <QObject>
#define S(XXX) QStringLiteral(XXX)
#define I(XXX) LanguageId(QStringLiteral(XXX))
#define TR(XXX) QT_TR_NOOP(XXX)
const std::unordered_map<LanguageId, LanguageCodes::Bundle>
LanguageCodes::codes_{
// clang-format off
// {I("abk"), {I("abk"), S("ab"), S("abk"), TR("Abkhazian")}},
// {I("aar"), {I("aar"), S("aa"), S("aar"), TR("Afar")}},
{I("afr"), {I("afr"), S("af"), S("afr"), TR("Afrikaans")}},
// {I("aka"), {I("aka"), S("ak"), S("aka"), TR("Akan")}},
{I("alb"), {I("alb"), S("sq"), S("alb"), TR("Albanian")}},
// {I("amh"), {I("amh"), S("am"), S("amh"), TR("Amharic")}},
{I("ara"), {I("ara"), S("ar"), S("ara"), TR("Arabic")}},
// {I("arg"), {I("arg"), S("an"), S("arg"), TR("Aragonese")}},
{I("arm"), {I("arm"), S("hy"), S("arm"), TR("Armenian")}},
// {I("asm"), {I("asm"), S("as"), S("asm"), TR("Assamese")}},
// {I("ava"), {I("ava"), S("av"), S("ava"), TR("Avaric")}},
// {I("ave"), {I("ave"), S("ae"), S("ave"), TR("Avestan")}},
// {I("aym"), {I("aym"), S("ay"), S("aym"), TR("Aymara")}},
{I("aze"), {I("aze"), S("az"), S("aze"), TR("Azerbaijani")}},
// {I("bam"), {I("bam"), S("bm"), S("bam"), TR("Bambara")}},
// {I("bak"), {I("bak"), S("ba"), S("bak"), TR("Bashkir")}},
{I("baq"), {I("baq"), S("eu"), S("baq"), TR("Basque")}},
{I("bel"), {I("bel"), S("be"), S("bel"), TR("Belarusian")}},
// {I("ben"), {I("ben"), S("bn"), S("ben"), TR("Bengali")}},
// {I("bih"), {I("bih"), S("bh"), S("bih"), TR("Bihari languages")}},
// {I("bis"), {I("bis"), S("bi"), S("bis"), TR("Bislama")}},
// {I("bos"), {I("bos"), S("bs"), S("bos"), TR("Bosnian")}},
// {I("bre"), {I("bre"), S("br"), S("bre"), TR("Breton")}},
{I("bul"), {I("bul"), S("bg"), S("bul"), TR("Bulgarian")}},
// {I("bur"), {I("bur"), S("my"), S("bur"), TR("Burmese")}},
{I("cat"), {I("cat"), S("ca"), S("cat"), TR("Catalan, Valencian")}},
// {I("cha"), {I("cha"), S("ch"), S("cha"), TR("Chamorro")}},
// {I("che"), {I("che"), S("ce"), S("che"), TR("Chechen")}},
// {I("nya"), {I("nya"), S("ny"), S("nya"), TR("Chichewa, Chewa, Nyanja")}},
// {I("chi"), {I("chi"), S("zh"), S("chi"), TR("Chinese")}},
// {I("chv"), {I("chv"), S("cv"), S("chv"), TR("Chuvash")}},
// {I("cor"), {I("cor"), S("kw"), S("cor"), TR("Cornish")}},
// {I("cos"), {I("cos"), S("co"), S("cos"), TR("Corsican")}},
// {I("cre"), {I("cre"), S("cr"), S("cre"), TR("Cree")}},
{I("hrv"), {I("hrv"), S("hr"), S("hrv"), TR("Croatian")}},
{I("cze"), {I("cze"), S("cs"), S("cze"), TR("Czech")}},
{I("dan"), {I("dan"), S("da"), S("dan"), TR("Danish")}},
// {I("div"), {I("div"), S("dv"), S("div"), TR("Divehi, Dhivehi, Maldivian")}},
// {I("dut"), {I("dut"), S("nl"), S("dut"), TR("Dutch, Flemish")}},
// {I("dzo"), {I("dzo"), S("dz"), S("dzo"), TR("Dzongkha")}},
{I("eng"), {I("eng"), S("en"), S("eng"), TR("English")}},
// {I("epo"), {I("epo"), S("eo"), S("epo"), TR("Esperanto")}},
{I("est"), {I("est"), S("et"), S("est"), TR("Estonian")}},
// {I("ewe"), {I("ewe"), S("ee"), S("ewe"), TR("Ewe")}},
// {I("fao"), {I("fao"), S("fo"), S("fao"), TR("Faroese")}},
// {I("fij"), {I("fij"), S("fj"), S("fij"), TR("Fijian")}},
{I("fin"), {I("fin"), S("fi"), S("fin"), TR("Finnish")}},
{I("fre"), {I("fre"), S("fr"), S("fre"), TR("French")}},
// {I("ful"), {I("ful"), S("ff"), S("ful"), TR("Fulah")}},
{I("glg"), {I("glg"), S("gl"), S("glg"), TR("Galician")}},
{I("geo"), {I("geo"), S("ka"), S("geo"), TR("Georgian")}},
{I("ger"), {I("ger"), S("de"), S("ger"), TR("German")}},
{I("gre"), {I("gre"), S("el"), S("gre"), TR("Greek")}},
// {I("grn"), {I("grn"), S("gn"), S("grn"), TR("Guarani")}},
// {I("guj"), {I("guj"), S("gu"), S("guj"), TR("Gujarati")}},
{I("hat"), {I("hat"), S("ht"), S("hat"), TR("Haitian, Haitian Creole")}},
// {I("hau"), {I("hau"), S("ha"), S("hau"), TR("Hausa")}},
{I("heb"), {I("heb"), S("he"), S("heb"), TR("Hebrew")}},
// {I("her"), {I("her"), S("hz"), S("her"), TR("Herero")}},
{I("hin"), {I("hin"), S("hi"), S("hin"), TR("Hindi")}},
// {I("hmo"), {I("hmo"), S("ho"), S("hmo"), TR("Hiri Motu")}},
{I("hun"), {I("hun"), S("hu"), S("hun"), TR("Hungarian")}},
// {I("ina"), {I("ina"), S("ia"), S("ina"), TR("Interlingua (International Auxiliary Language Association)")}},
{I("ind"), {I("ind"), S("id"), S("ind"), TR("Indonesian")}},
// {I("ile"), {I("ile"), S("ie"), S("ile"), TR("Interlingue, Occidental")}},
{I("gle"), {I("gle"), S("ga"), S("gle"), TR("Irish")}},
// {I("ibo"), {I("ibo"), S("ig"), S("ibo"), TR("Igbo")}},
// {I("ipk"), {I("ipk"), S("ik"), S("ipk"), TR("Inupiaq")}},
// {I("ido"), {I("ido"), S("io"), S("ido"), TR("Ido")}},
{I("ice"), {I("ice"), S("is"), S("ice"), TR("Icelandic")}},
{I("ita"), {I("ita"), S("it"), S("ita"), TR("Italian")}},
// {I("iku"), {I("iku"), S("iu"), S("iku"), TR("Inuktitut")}},
{I("jpn"), {I("jpn"), S("ja"), S("jpn"), TR("Japanese")}},
// {I("jav"), {I("jav"), S("jv"), S("jav"), TR("Javanese")}},
// {I("kal"), {I("kal"), S("kl"), S("kal"), TR("Kalaallisut, Greenlandic")}},
// {I("kan"), {I("kan"), S("kn"), S("kan"), TR("Kannada")}},
// {I("kau"), {I("kau"), S("kr"), S("kau"), TR("Kanuri")}},
// {I("kas"), {I("kas"), S("ks"), S("kas"), TR("Kashmiri")}},
// {I("kaz"), {I("kaz"), S("kk"), S("kaz"), TR("Kazakh")}},
// {I("khm"), {I("khm"), S("km"), S("khm"), TR("Central Khmer")}},
// {I("kik"), {I("kik"), S("ki"), S("kik"), TR("Kikuyu, Gikuyu")}},
// {I("kin"), {I("kin"), S("rw"), S("kin"), TR("Kinyarwanda")}},
// {I("kir"), {I("kir"), S("ky"), S("kir"), TR("Kirghiz, Kyrgyz")}},
// {I("kom"), {I("kom"), S("kv"), S("kom"), TR("Komi")}},
// {I("kon"), {I("kon"), S("kg"), S("kon"), TR("Kongo")}},
{I("kor"), {I("kor"), S("ko"), S("kor"), TR("Korean")}},
// {I("kur"), {I("kur"), S("ku"), S("kur"), TR("Kurdish")}},
// {I("kua"), {I("kua"), S("kj"), S("kua"), TR("Kuanyama, Kwanyama")}},
// {I("lat"), {I("lat"), S("la"), S("lat"), TR("Latin")}},
// {I("ltz"), {I("ltz"), S("lb"), S("ltz"), TR("Luxembourgish, Letzeburgesch")}},
// {I("lug"), {I("lug"), S("lg"), S("lug"), TR("Ganda")}},
// {I("lim"), {I("lim"), S("li"), S("lim"), TR("Limburgan, Limburger, Limburgish")}},
// {I("lin"), {I("lin"), S("ln"), S("lin"), TR("Lingala")}},
// {I("lao"), {I("lao"), S("lo"), S("lao"), TR("Lao")}},
{I("lit"), {I("lit"), S("lt"), S("lit"), TR("Lithuanian")}},
// {I("lub"), {I("lub"), S("lu"), S("lub"), TR("Luba-Katanga")}},
{I("lav"), {I("lav"), S("lv"), S("lav"), TR("Latvian")}},
// {I("glv"), {I("glv"), S("gv"), S("glv"), TR("Manx")}},
{I("mac"), {I("mac"), S("mk"), S("mac"), TR("Macedonian")}},
// {I("mlg"), {I("mlg"), S("mg"), S("mlg"), TR("Malagasy")}},
{I("may"), {I("may"), S("ms"), S("may"), TR("Malay")}},
// {I("mal"), {I("mal"), S("ml"), S("mal"), TR("Malayalam")}},
{I("mlt"), {I("mlt"), S("mt"), S("mlt"), TR("Maltese")}},
// {I("mao"), {I("mao"), S("mi"), S("mao"), TR("Maori")}},
// {I("mar"), {I("mar"), S("mr"), S("mar"), TR("Marathi")}},
// {I("mah"), {I("mah"), S("mh"), S("mah"), TR("Marshallese")}},
// {I("mon"), {I("mon"), S("mn"), S("mon"), TR("Mongolian")}},
// {I("nau"), {I("nau"), S("na"), S("nau"), TR("Nauru")}},
// {I("nav"), {I("nav"), S("nv"), S("nav"), TR("Navajo, Navaho")}},
// {I("nde"), {I("nde"), S("nd"), S("nde"), TR("North Ndebele")}},
// {I("nep"), {I("nep"), S("ne"), S("nep"), TR("Nepali")}},
// {I("ndo"), {I("ndo"), S("ng"), S("ndo"), TR("Ndonga")}},
// {I("nob"), {I("nob"), S("nb"), S("nob"), TR("Norwegian Bokmål")}},
// {I("nno"), {I("nno"), S("nn"), S("nno"), TR("Norwegian Nynorsk")}},
{I("nor"), {I("nor"), S("no"), S("nor"), TR("Norwegian")}},
// {I("iii"), {I("iii"), S("ii"), S("iii"), TR("Sichuan Yi, Nuosu")}},
// {I("nbl"), {I("nbl"), S("nr"), S("nbl"), TR("South Ndebele")}},
// {I("oci"), {I("oci"), S("oc"), S("oci"), TR("Occitan")}},
// {I("oji"), {I("oji"), S("oj"), S("oji"), TR("Ojibwa")}},
// {I("chu"), {I("chu"), S("cu"), S("chu"), TR("Church Slavic, Old Slavonic, Church Slavonic, Old Bulgarian, Old Church Slavonic")}},
// {I("orm"), {I("orm"), S("om"), S("orm"), TR("Oromo")}},
// {I("ori"), {I("ori"), S("or"), S("ori"), TR("Oriya")}},
// {I("oss"), {I("oss"), S("os"), S("oss"), TR("Ossetian, Ossetic")}},
// {I("pan"), {I("pan"), S("pa"), S("pan"), TR("Punjabi, Panjabi")}},
// {I("pli"), {I("pli"), S("pi"), S("pli"), TR("Pali")}},
{I("per"), {I("per"), S("fa"), S("per"), TR("Persian")}},
{I("pol"), {I("pol"), S("pl"), S("pol"), TR("Polish")}},
// {I("pus"), {I("pus"), S("ps"), S("pus"), TR("Pashto, Pushto")}},
{I("por"), {I("por"), S("pt"), S("por"), TR("Portuguese")}},
// {I("que"), {I("que"), S("qu"), S("que"), TR("Quechua")}},
// {I("roh"), {I("roh"), S("rm"), S("roh"), TR("Romansh")}},
// {I("run"), {I("run"), S("rn"), S("run"), TR("Rundi")}},
{I("rum"), {I("rum"), S("ro"), S("rum"), TR("Romanian, Moldavian, Moldovan")}},
{I("rus"), {I("rus"), S("ru"), S("rus"), TR("Russian")}},
// {I("san"), {I("san"), S("sa"), S("san"), TR("Sanskrit")}},
// {I("srd"), {I("srd"), S("sc"), S("srd"), TR("Sardinian")}},
// {I("snd"), {I("snd"), S("sd"), S("snd"), TR("Sindhi")}},
// {I("sme"), {I("sme"), S("se"), S("sme"), TR("Northern Sami")}},
// {I("smo"), {I("smo"), S("sm"), S("smo"), TR("Samoan")}},
// {I("sag"), {I("sag"), S("sg"), S("sag"), TR("Sango")}},
{I("srp"), {I("srp"), S("sr"), S("srp"), TR("Serbian")}},
// {I("gla"), {I("gla"), S("gd"), S("gla"), TR("Gaelic, Scottish Gaelic")}},
// {I("sna"), {I("sna"), S("sn"), S("sna"), TR("Shona")}},
// {I("sin"), {I("sin"), S("si"), S("sin"), TR("Sinhala, Sinhalese")}},
{I("slo"), {I("slo"), S("sk"), S("slo"), TR("Slovak")}},
{I("slv"), {I("slv"), S("sl"), S("slv"), TR("Slovenian")}},
// {I("som"), {I("som"), S("so"), S("som"), TR("Somali")}},
// {I("sot"), {I("sot"), S("st"), S("sot"), TR("Southern Sotho")}},
{I("spa"), {I("spa"), S("es"), S("spa"), TR("Spanish, Castilian")}},
// {I("sun"), {I("sun"), S("su"), S("sun"), TR("Sundanese")}},
{I("swa"), {I("swa"), S("sw"), S("swa"), TR("Swahili")}},
// {I("ssw"), {I("ssw"), S("ss"), S("ssw"), TR("Swati")}},
{I("swe"), {I("swe"), S("sv"), S("swe"), TR("Swedish")}},
// {I("tam"), {I("tam"), S("ta"), S("tam"), TR("Tamil")}},
// {I("tel"), {I("tel"), S("te"), S("tel"), TR("Telugu")}},
// {I("tgk"), {I("tgk"), S("tg"), S("tgk"), TR("Tajik")}},
{I("tha"), {I("tha"), S("th"), S("tha"), TR("Thai")}},
// {I("tir"), {I("tir"), S("ti"), S("tir"), TR("Tigrinya")}},
// {I("tib"), {I("tib"), S("bo"), S("tib"), TR("Tibetan")}},
// {I("tuk"), {I("tuk"), S("tk"), S("tuk"), TR("Turkmen")}},
// {I("tgl"), {I("tgl"), S("tl"), S("tgl"), TR("Tagalog")}},
// {I("tsn"), {I("tsn"), S("tn"), S("tsn"), TR("Tswana")}},
// {I("ton"), {I("ton"), S("to"), S("ton"), TR("Tonga (Tonga Islands)")}},
{I("tur"), {I("tur"), S("tr"), S("tur"), TR("Turkish")}},
// {I("tso"), {I("tso"), S("ts"), S("tso"), TR("Tsonga")}},
// {I("tat"), {I("tat"), S("tt"), S("tat"), TR("Tatar")}},
// {I("twi"), {I("twi"), S("tw"), S("twi"), TR("Twi")}},
// {I("tah"), {I("tah"), S("ty"), S("tah"), TR("Tahitian")}},
// {I("uig"), {I("uig"), S("ug"), S("uig"), TR("Uighur, Uyghur")}},
{I("ukr"), {I("ukr"), S("uk"), S("ukr"), TR("Ukrainian")}},
{I("urd"), {I("urd"), S("ur"), S("urd"), TR("Urdu")}},
// {I("uzb"), {I("uzb"), S("uz"), S("uzb"), TR("Uzbek")}},
// {I("ven"), {I("ven"), S("ve"), S("ven"), TR("Venda")}},
{I("vie"), {I("vie"), S("vi"), S("vie"), TR("Vietnamese")}},
// {I("vol"), {I("vol"), S("vo"), S("vol"), TR("Volapük")}},
// {I("wln"), {I("wln"), S("wa"), S("wln"), TR("Walloon")}},
{I("wel"), {I("wel"), S("cy"), S("wel"), TR("Welsh")}},
// {I("wol"), {I("wol"), S("wo"), S("wol"), TR("Wolof")}},
// {I("fry"), {I("fry"), S("fy"), S("fry"), TR("Western Frisian")}},
// {I("xho"), {I("xho"), S("xh"), S("xho"), TR("Xhosa")}},
{I("yid"), {I("yid"), S("yi"), S("yid"), TR("Yiddish")}},
// {I("yor"), {I("yor"), S("yo"), S("yor"), TR("Yoruba")}},
// {I("zha"), {I("zha"), S("za"), S("zha"), TR("Zhuang, Chuang")}},
// {I("zul"), {I("zul"), S("zu"), S("zul"), TR("Zulu")}},
// custom
{I("chi_sim"), {I("chi_sim"), S("zh-CN"), S("chi_sim"), TR("Chinese (Simplified)")}},
{I("chi_tra"), {I("chi_tra"), S("zh-TW"), S("chi_tra"), TR("Chinese (Traditional)")}},
// clang-format on
};
#undef I
#undef S
std::optional<LanguageCodes::Bundle> LanguageCodes::findById(
const LanguageId &id) const
{
auto it = codes_.find(id);
if (it != codes_.cend())
return it->second;
return {};
}
std::optional<LanguageCodes::Bundle> LanguageCodes::findByName(
const QString &name) const
{
auto it = std::find_if(codes_.cbegin(), codes_.cend(),
[name](const std::pair<LanguageId, Bundle> &i) {
return name == i.second.name;
});
if (it != codes_.cend())
return it->second;
return {};
}
std::optional<LanguageCodes::Bundle> LanguageCodes::findByTesseract(
const QString &name) const
{
auto it = std::find_if(codes_.cbegin(), codes_.cend(),
[name](const std::pair<LanguageId, Bundle> &i) {
return name == i.second.tesseract;
});
if (it != codes_.cend())
return it->second;
return {};
}
const std::unordered_map<LanguageId, LanguageCodes::Bundle>
&LanguageCodes::all() const
{
return codes_;
}

27
src/languagecodes.h Normal file
View File

@ -0,0 +1,27 @@
#pragma once
#include <QString>
#include <optional>
#include <unordered_map>
using LanguageId = QString;
class LanguageCodes
{
public:
struct Bundle {
LanguageId id;
QString iso639_1;
QString tesseract;
QString name;
};
std::optional<Bundle> findById(const LanguageId& id) const;
std::optional<Bundle> findByName(const QString& name) const;
std::optional<Bundle> findByTesseract(const QString& name) const;
const std::unordered_map<LanguageId, Bundle>& all() const;
private:
const static std::unordered_map<LanguageId, Bundle> codes_;
};

View File

@ -1,301 +0,0 @@
#include <QDir>
#include <QSettings>
#include "languagehelper.h"
#include "settings.h"
#include "stassert.h"
LanguageHelper::LanguageHelper () {
init ();
}
QStringList LanguageHelper::availableOcrLanguagesUi () const {
QStringList uiItems;
for (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);
if (!dir.exists ()) {
return QStringList ();
}
QStringList items;
QStringList files = dir.entryList ({"*.traineddata"}, QDir::Files);
for (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);
for (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::ocrToTranslateCodes (const QString &text) const {
QString ocrUi = ocrCodeToUi (text);
QString translateCode = translateUiToCode (ocrUi);
if (translateCode == ocrUi) {
translateCode = "auto";
}
return translateCode;
}
QString LanguageHelper::translateToOcrCodes (const QString &text) const {
QString translateUi = translateCodeToUi (text);
QString ocrCode = ocrUiToCode (translateUi);
if (translateUi == ocrCode) {
return QString ();
}
return ocrCode;
}
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::updateMenu (QMenu *menu, const QStringList &languages, int groupSize) const {
ST_ASSERT (menu != NULL);
menu->clear ();
if (languages.isEmpty ()) {
return;
}
if (languages.size () <= groupSize) {
for (const QString &language: languages) {
menu->addAction (language);
}
}
else {
int subIndex = groupSize;
QMenu *subMenu = NULL;
QString prevLetter;
for (const QString &language: languages) {
QString curLetter = language.left (1);
if (++subIndex >= groupSize && prevLetter != curLetter) {
if (subMenu != NULL) {
subMenu->setTitle (subMenu->title () + " - " + prevLetter);
}
subMenu = menu->addMenu (curLetter);
subIndex = 0;
}
prevLetter = curLetter;
subMenu->addAction (language);
}
subMenu->setTitle (subMenu->title () + " - " + prevLetter);
}
}
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");
ocrLanguages_.insert (QObject::tr ("Amharic"),"amh");
ocrLanguages_.insert (QObject::tr ("Assamese"),"asm");
ocrLanguages_.insert (QObject::tr ("Tibetan"),"bod");
ocrLanguages_.insert (QObject::tr ("Bosnian"),"bos");
ocrLanguages_.insert (QObject::tr ("Cebuano"),"ceb");
ocrLanguages_.insert (QObject::tr ("Welsh"),"cym");
ocrLanguages_.insert (QObject::tr ("Dzongkha"),"dzo");
ocrLanguages_.insert (QObject::tr ("Persian"),"fas");
ocrLanguages_.insert (QObject::tr ("Irish"),"gle");
ocrLanguages_.insert (QObject::tr ("Gujarati"),"guj");
ocrLanguages_.insert (QObject::tr ("Haitian"),"hat");
ocrLanguages_.insert (QObject::tr ("Inuktitut"),"iku");
ocrLanguages_.insert (QObject::tr ("Javanese"),"jav");
ocrLanguages_.insert (QObject::tr ("Georgian"),"kat");
ocrLanguages_.insert (QObject::tr ("Kazakh"),"kaz");
ocrLanguages_.insert (QObject::tr ("Khmer"),"khm");
ocrLanguages_.insert (QObject::tr ("Kirghiz"),"kir");
ocrLanguages_.insert (QObject::tr ("Kurdish"),"kur");
ocrLanguages_.insert (QObject::tr ("Lao"),"lao");
ocrLanguages_.insert (QObject::tr ("Latin"),"lat");
ocrLanguages_.insert (QObject::tr ("Marathi"),"mar");
ocrLanguages_.insert (QObject::tr ("Burmese"),"mya");
ocrLanguages_.insert (QObject::tr ("Nepali"),"nep");
ocrLanguages_.insert (QObject::tr ("Oriya"),"ori");
ocrLanguages_.insert (QObject::tr ("Panjabi"),"pan");
ocrLanguages_.insert (QObject::tr ("Pushto"),"pus");
ocrLanguages_.insert (QObject::tr ("Sanskrit"),"san");
ocrLanguages_.insert (QObject::tr ("Sinhala"),"sin");
ocrLanguages_.insert (QObject::tr ("Syriac"),"syr");
ocrLanguages_.insert (QObject::tr ("Tajik"),"tgk");
ocrLanguages_.insert (QObject::tr ("Tigrinya"),"tir");
ocrLanguages_.insert (QObject::tr ("Uighur"),"uig");
ocrLanguages_.insert (QObject::tr ("Urdu"),"urd");
ocrLanguages_.insert (QObject::tr ("Uzbek"),"uzb");
ocrLanguages_.insert (QObject::tr ("Yiddish"),"yid");
}

View File

@ -1,43 +0,0 @@
#ifndef LANGUAGEHELPER_H
#define LANGUAGEHELPER_H
#include <QMap>
#include <QStringList>
#include <QMenu>
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 ocrToTranslateCodes (const QString &text) const;
QString translateToOcrCodes (const QString &text) const;
void updateAvailableOcrLanguages ();
//! Update languages menu. Group items into submenus if needed.
void updateMenu (QMenu *menu, const QStringList &languages, int groupSize = 10) const;
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

View File

@ -2,33 +2,46 @@
#include <locale.h>
#endif
#include "apptranslator.h"
#include "manager.h"
#include "singleapplication.h"
#include <QApplication>
#include <QTranslator>
#include <QCommandLineParser>
#include <qtsingleapplication.h>
#define STR2(XXX) #XXX
#define STR(XXX) STR2(XXX)
#include <manager.h>
#include <settings.h>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
a.setApplicationName("ScreenTranslator");
a.setOrganizationName("Gres");
a.setApplicationVersion(STR(VERSION));
int main (int argc, char *argv[]) {
QtSingleApplication a (argc, argv);
if (a.sendMessage (QString ())) {
return 0;
}
#ifdef Q_OS_LINUX
setlocale (LC_NUMERIC, "C");
#endif
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);
{
AppTranslator appTranslator({"screentranslator"});
appTranslator.retranslate();
}
{
QCommandLineParser parser;
parser.setApplicationDescription(QObject::tr("OCR and translation tool"));
parser.addHelpOption();
parser.addVersionOption();
parser.process(a);
}
SingleApplication guard;
if (!guard.isValid())
return 1;
// tesseract recomments
setlocale(LC_NUMERIC, "C");
Manager manager;
return a.exec();

View File

@ -1,463 +1,209 @@
#include "manager.h"
#include "capturer.h"
#include "corrector.h"
#include "debug.h"
#include "recognizer.h"
#include "representer.h"
#include "settingseditor.h"
#include "task.h"
#include "translator.h"
#include "trayicon.h"
#include <QDebug>
#include <QMenu>
#include <QApplication>
#include <QDesktopWidget>
#include <QScreen>
#include <QDesktopWidget>
#include <QThread>
#include <QSettings>
#include <QClipboard>
#include <QMessageBox>
#include <QInputDialog>
#include <QNetworkProxy>
#include "settings.h"
#include "settingseditor.h"
#include "selectiondialog.h"
#include "globalactionhelper.h"
#include "recognizer.h"
#include "webtranslator.h"
#include "resultdialog.h"
#include "languagehelper.h"
#include "stassert.h"
#include "utils.h"
#include "updater.h"
Manager::Manager()
{
tray_ = std::make_unique<TrayIcon>(*this);
capturer_ = std::make_unique<Capturer>(*this);
recognizer_ = std::make_unique<Recognizer>(*this);
translator_ = std::make_unique<Translator>(*this);
corrector_ = std::make_unique<Corrector>(*this);
representer_ = std::make_unique<Representer>(*this, *tray_);
Manager::Manager (QObject *parent) :
QObject (parent),
trayIcon_ (new QSystemTrayIcon (this)),
dictionary_ (new LanguageHelper),
resultDialog_ (new ResultDialog (*dictionary_)),
updater_ (new Updater (this)), updateTimer_ (new QTimer (this)),
captureAction_ (NULL), repeatCaptureAction_ (NULL),
repeatAction_ (NULL), clipboardAction_ (NULL),
useResultDialog_ (true), doTranslation_ (true), itemProcessingCount_ (0) {
updateNormalIcon ();
GlobalActionHelper::init ();
qRegisterMetaType<ProcessingItem>();
qRegisterMetaType<TaskPtr>();
// Recognizer
Recognizer *recognizer = new Recognizer;
connect (this, SIGNAL (requestRecognize (ProcessingItem)),
recognizer, SLOT (recognize (ProcessingItem)));
connect (recognizer, SIGNAL (recognized (ProcessingItem)),
this, SIGNAL (requestTranslate (ProcessingItem)));
connect (recognizer, SIGNAL (error (QString)),
SLOT (showError (QString)));
connect (this, SIGNAL (settingsEdited ()),
recognizer, SLOT (applySettings ()));
QThread *recognizerThread = new QThread (this);
threads_ << recognizerThread;
recognizer->moveToThread (recognizerThread);
recognizerThread->start ();
connect (qApp, SIGNAL (aboutToQuit ()), recognizerThread, SLOT (quit ()));
// Translator
WebTranslator *translator = new WebTranslator;
connect (this, SIGNAL (requestTranslate (ProcessingItem)),
translator, SLOT (translate (ProcessingItem)));
connect (translator, SIGNAL (translated (ProcessingItem)),
SLOT (showResult (ProcessingItem)));
connect (translator, SIGNAL (error (QString)),
SLOT (showError (QString)));
connect (this, SIGNAL (settingsEdited ()),
translator, SLOT (applySettings ()));
connect (this, SIGNAL (settingsEdited ()), this, SLOT (applySettings ()));
connect (updater_, SIGNAL (updated ()), SIGNAL (settingsEdited ()));
connect (updater_, SIGNAL (error (QString)), SLOT (showError (QString)));
updateTimer_->setSingleShot (true);
connect (updateTimer_, SIGNAL (timeout ()), SLOT (checkForUpdates ()));
resultDialog_->setWindowIcon (trayIcon_->icon ());
connect (this, SIGNAL (settingsEdited ()), resultDialog_, SLOT (applySettings ()));
connect (resultDialog_, SIGNAL (requestRecognize (ProcessingItem)),
this, SIGNAL (requestRecognize (ProcessingItem)));
connect (resultDialog_, SIGNAL (requestTranslate (ProcessingItem)),
this, SIGNAL (requestTranslate (ProcessingItem)));
connect (resultDialog_, SIGNAL (requestClipboard ()), SLOT (copyLastToClipboard ()));
connect (resultDialog_, SIGNAL (requestImageClipboard ()),
SLOT (copyLastImageToClipboard ()));
connect (resultDialog_, SIGNAL (requestEdition (ProcessingItem)),
this, SLOT (editRecognized (ProcessingItem)));
connect (trayIcon_, SIGNAL (activated (QSystemTrayIcon::ActivationReason)),
SLOT (processTrayAction (QSystemTrayIcon::ActivationReason)));
trayIcon_->setContextMenu (trayContextMenu ());
updateActionsState ();
trayIcon_->show ();
applySettings ();
Settings settings;
settings.load();
updateSettings(settings);
}
QMenu * Manager::trayContextMenu () {
QMenu *menu = new QMenu ();
captureAction_ = menu->addAction (tr ("Захват"), this, SLOT (capture ()));
repeatCaptureAction_ = menu->addAction (tr ("Повторить захват"),
this, SLOT (repeatCapture ()));
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;
Manager::~Manager() = default;
void Manager::updateSettings(const Settings &settings)
{
LTRACE() << "updateSettings";
tray_->updateSettings(settings);
capturer_->updateSettings(settings);
recognizer_->updateSettings(settings);
translator_->updateSettings(settings);
corrector_->updateSettings(settings);
representer_->updateSettings(settings);
}
void Manager::updateActionsState (bool isEnabled) {
#ifdef Q_OS_LINUX
// Avoid unneeded tray blinking (required to update context menu).
QList<QAction *> actions;
actions << captureAction_ << repeatCaptureAction_ << repeatAction_ << clipboardAction_;
QList<bool> states;
for (const QAction *action: actions) {
states << action->isEnabled ();
}
#endif
captureAction_->setEnabled (isEnabled);
repeatCaptureAction_->setEnabled (isEnabled && !selections_.isEmpty ());
const ProcessingItem &lastItem = resultDialog_->item ();
repeatAction_->setEnabled (isEnabled && lastItem.isValid ());
clipboardAction_->setEnabled (isEnabled && lastItem.isValid ());
#ifdef Q_OS_LINUX
for (int i = 0, end = actions.size (); i < end; ++i) {
if (states.at (i) != actions.at (i)->isEnabled ()) {
trayIcon_->hide ();
trayIcon_->show ();
break;
}
}
#endif
}
void Manager::finishTask(const TaskPtr &task)
{
SOFT_ASSERT(task, return );
LTRACE() << "finishTask" << task->captured << task->error;
void Manager::applySettings () {
#define GET(NAME) settings.value (settings_names::NAME, settings_values::NAME)
QSettings settings;
settings.beginGroup (settings_names::guiGroup);
--activeTaskCount_;
tray_->setActiveTaskCount(activeTaskCount_);
QStringList globalActionsFailed;
Q_CHECK_PTR (captureAction_);
GlobalActionHelper::removeGlobal (captureAction_);
captureAction_->setShortcut (GET (captureHotkey).toString ());
if (!GlobalActionHelper::makeGlobal (captureAction_)) {
globalActionsFailed << captureAction_->shortcut ().toString ();
}
last_ = task;
tray_->setTaskActionsEnabled(last_->isValid());
Q_CHECK_PTR (repeatCaptureAction_);
GlobalActionHelper::removeGlobal (repeatCaptureAction_);
repeatCaptureAction_->setShortcut (GET (repeatCaptureHotkey).toString ());
if (!GlobalActionHelper::makeGlobal (repeatCaptureAction_)) {
globalActionsFailed << repeatCaptureAction_->shortcut ().toString ();
}
Q_CHECK_PTR (repeatAction_);
GlobalActionHelper::removeGlobal (repeatAction_);
repeatAction_->setShortcut (GET (repeatHotkey).toString ());
if (!GlobalActionHelper::makeGlobal (repeatAction_)) {
globalActionsFailed << repeatAction_->shortcut ().toString ();
}
Q_CHECK_PTR (clipboardAction_);
GlobalActionHelper::removeGlobal (clipboardAction_);
clipboardAction_->setShortcut (GET (clipboardHotkey).toString ());
if (!GlobalActionHelper::makeGlobal (clipboardAction_)) {
globalActionsFailed << clipboardAction_->shortcut ().toString ();
}
if (!globalActionsFailed.isEmpty ()) {
showError (tr ("Failed to register global shortcuts:\n%1")
.arg (globalActionsFailed.join ("\n")));
}
// Depends on SettingsEditor button indexes. 1==dialog
useResultDialog_ = GET (resultShowType).toBool ();
QNetworkProxy proxy = QNetworkProxy::applicationProxy ();
QList<int> proxyTypes = proxyTypeOrder ();
int proxyTypeIndex = std::min (GET (proxyType).toInt (), proxyTypes.size ());
proxy.setType (QNetworkProxy::ProxyType (proxyTypes.at (std::max (proxyTypeIndex, 0))));
proxy.setHostName (GET (proxyHostName).toString ());
proxy.setPort (GET (proxyPort).toInt ());
proxy.setUser (GET (proxyUser).toString ());
if (GET (proxySavePassword).toBool ()) {
proxy.setPassword (encode (GET (proxyPassword).toString ()));
}
QNetworkProxy::setApplicationProxy (proxy);
scheduleUpdate ();
settings.endGroup ();
settings.beginGroup (settings_names::recogntionGroup);
defaultOrcLanguage_ = GET (ocrLanguage).toString ();
settings.endGroup ();
settings.beginGroup (settings_names::translationGroup);
defaultTranslationLanguage_ = GET (translationLanguage).toString ();
doTranslation_ = GET (doTranslation).toBool ();
settings.endGroup ();
Q_CHECK_PTR (dictionary_);
dictionary_->updateAvailableOcrLanguages ();
#undef GET
}
void Manager::scheduleUpdate (bool justChecked) {
#define GET(NAME) settings.value (settings_names::NAME, settings_values::NAME)
QSettings settings;
settings.beginGroup (settings_names::guiGroup);
updateTimer_->stop ();
if (justChecked) {
settings.setValue (settings_names::lastUpdateCheck, QDateTime::currentDateTime ());
}
QDateTime nextUpdateCheck = updater_->nextCheckTime (GET (lastUpdateCheck).toDateTime (),
GET (autoUpdateType).toInt ());
if (nextUpdateCheck.isValid ()) {
updateTimer_->start (QDateTime::currentDateTime ().msecsTo (nextUpdateCheck));
}
#undef GET
}
void Manager::checkForUpdates () {
updater_->checkForUpdates ();
scheduleUpdate (true);
}
Manager::~Manager () {
for (SelectionDialog *selection: selections_.values ()) {
selection->hide ();
delete selection;
}
trayIcon_->hide ();
delete trayIcon_->contextMenu ();
for (QThread *thread: threads_) {
thread->quit ();
thread->wait (1000000);
}
}
void Manager::capture () {
QList<QScreen *> screens = QApplication::screens ();
for (QScreen *screen: screens) {
QRect geometry = screen->availableGeometry ();
#if QT_VERSION >= QT_VERSION_CHECK (5,10,0)
QPixmap pixmap = screen->grabWindow (0, 0, 0,
geometry.width (), geometry.height ());
#else
QPixmap pixmap = screen->grabWindow (0, geometry.x (), geometry.y (),
geometry.width (), geometry.height ());
#endif
QString name = screen->name ();
if (!selections_.contains (name)) {
SelectionDialog *selection = new SelectionDialog (*dictionary_);
selection->setWindowIcon (trayIcon_->icon ());
connect (this, SIGNAL (closeSelections ()), selection, SLOT (close ()));
connect (this, SIGNAL (settingsEdited ()), selection, SLOT (applySettings ()));
connect (selection, SIGNAL (selected (ProcessingItem)),
SLOT (handleSelection (ProcessingItem)));
connect (selection, SIGNAL (rejected ()), SIGNAL (closeSelections ()));
selections_[name] = selection;
}
SelectionDialog *selection = selections_[name];
selection->setPixmap (pixmap, geometry);
}
updateActionsState ();
}
void Manager::handleSelection (ProcessingItem item) {
bool altMod = item.modifiers & Qt::AltModifier;
bool doTranslation = (doTranslation_ && !altMod) || (!doTranslation_ && altMod);
if (doTranslation) {
item.translateLanguage = defaultTranslationLanguage_;
}
if (item.ocrLanguage.isEmpty ()) {
item.ocrLanguage = defaultOrcLanguage_;
}
if (item.swapLanguages_) {
QString translate = (item.translateLanguage.isEmpty ())
? defaultTranslationLanguage_ : item.translateLanguage;
if (doTranslation) {
item.translateLanguage = dictionary_->ocrToTranslateCodes (item.ocrLanguage);
}
item.sourceLanguage.clear ();
item.ocrLanguage = dictionary_->translateToOcrCodes (translate);
if (item.ocrLanguage.isEmpty ()) {
showError (tr ("Не найден подходящий язык распознавания."));
if (!task->isValid()) {
tray_->showError(task->error);
return;
}
}
if (item.sourceLanguage.isEmpty ()) {
item.sourceLanguage = dictionary_->ocrToTranslateCodes (item.ocrLanguage);
}
emit requestRecognize (item);
++itemProcessingCount_;
updateNormalIcon ();
if (!(item.modifiers & Qt::ControlModifier)) {
emit closeSelections ();
}
tray_->showSuccess();
}
void Manager::repeatCapture () {
if (selections_.isEmpty ()) {
void Manager::captured(const TaskPtr &task)
{
tray_->blockActions(false);
SOFT_ASSERT(task, return );
LTRACE() << "captured" << task->captured << task->error;
++activeTaskCount_;
tray_->setActiveTaskCount(activeTaskCount_);
if (!task->isValid()) {
finishTask(task);
return;
}
QList<QScreen *> screens = QApplication::screens ();
for (QScreen *screen: screens) {
QString name = screen->name ();
if (!selections_.contains (name)) {
continue;
}
SelectionDialog *selection = selections_[name];
selection->show ();
selection->activateWindow ();
}
recognizer_->recognize(task);
}
void Manager::settings () {
SettingsEditor editor (*dictionary_);
editor.setWindowIcon (trayIcon_->icon ());
connect (&editor, SIGNAL (settingsEdited ()), SIGNAL (settingsEdited ()));
connect (&editor, SIGNAL (updateCheckRequested ()), SLOT (checkForUpdates ()));
updateActionsState (false);
editor.exec ();
updateActionsState (true);
void Manager::captureCanceled()
{
tray_->blockActions(false);
}
void Manager::close () {
QApplication::quit ();
void Manager::recognized(const TaskPtr &task)
{
SOFT_ASSERT(task, return );
LTRACE() << "recognized" << task->recognized << task->error;
if (!task->isValid()) {
finishTask(task);
return;
}
void Manager::about () {
QString text = tr ("Программа для распознавания текста на экране.\n" \
"Создана с использованием Qt, tesseract-ocr, Google Translate.\n"
"Автор: Gres (translator@gres.biz)\n"
"Версия: %1 от %2 %3").arg (updater_->currentAppVersion ())
.arg (__DATE__).arg (__TIME__);
QString tips = tr ("\n\nПодсказки.\n"
"Клик по иконке в трее:\n"
"* левой кнопкой - отобразить последний результат\n"
"* средней кнопкой - скопировать последний результат в буфер обмена\n"
#ifdef Q_OS_WIN
"* двойной клик - повторный захват последнего экрана\n"
#endif
"\n"
"Захвата изображения при зажатых кнопках:\n"
"* Ctrl - не выходить из режима захвата\n"
"* Alt - выполнить перевод, если в настройках он выключен "
"(и наоборот, не выполнять, если включен)\n"
"");
corrector_->correct(task);
}
QMessageBox message (QMessageBox::Information, tr ("О программе"), text + tips,
void Manager::corrected(const TaskPtr &task)
{
SOFT_ASSERT(task, return );
LTRACE() << "corrected" << task->recognized << task->error;
if (!task->isValid()) {
finishTask(task);
return;
}
if (!task->targetLanguage.isEmpty())
translator_->translate(task);
else
translated(task);
}
void Manager::translated(const TaskPtr &task)
{
SOFT_ASSERT(task, return );
LTRACE() << "translated" << task->recognized << task->error;
finishTask(task);
if (!task->isValid())
return;
representer_->represent(task);
}
void Manager::fatalError(const QString &text)
{
tray_->blockActions(false);
tray_->showFatalError(text);
}
void Manager::capture()
{
SOFT_ASSERT(capturer_, return );
tray_->blockActions(true);
capturer_->capture();
tray_->setRepeatCaptureEnabled(true);
}
void Manager::repeatCapture()
{
SOFT_ASSERT(capturer_, return );
tray_->blockActions(true);
capturer_->repeatCapture();
}
void Manager::showLast()
{
if (!last_ || !last_->isValid())
return;
SOFT_ASSERT(representer_, return );
representer_->represent(last_);
}
void Manager::settings()
{
SettingsEditor editor;
Settings settings;
settings.load();
editor.setSettings(settings);
tray_->blockActions(true);
auto result = editor.exec();
tray_->blockActions(false);
if (result != QDialog::Accepted)
return;
tray_->resetFatalError();
settings = editor.settings();
settings.save();
updateSettings(settings);
}
void Manager::copyLastToClipboard()
{
if (!last_ || !last_->isValid())
return;
QClipboard *clipboard = QApplication::clipboard();
clipboard->setText(last_->recognized + QLatin1String(" - ") +
last_->translated);
tray_->showInformation(
QObject::tr("The last result was copied to the clipboard."));
}
void Manager::about()
{
auto text =
QObject::tr(R"(Optical character recognition (OCR) and translation tool
Author: Gres (translator@gres.biz)
Version: %1)")
.arg(QApplication::applicationVersion());
QMessageBox message(QMessageBox::Information, QObject::tr("About"), text,
QMessageBox::Ok);
message.setIconPixmap (trayIcon_->icon ().pixmap (QSize (64, 64)));
message.setIconPixmap(QIcon(":/icons/app.png").pixmap(QSize(64, 64)));
message.exec();
}
void Manager::processTrayAction (QSystemTrayIcon::ActivationReason reason) {
if (reason == QSystemTrayIcon::Trigger && repeatAction_->isEnabled ()) {
showLast ();
}
else if (reason == QSystemTrayIcon::MiddleClick && clipboardAction_->isEnabled ()) {
copyLastToClipboard ();
trayIcon_->showMessage (tr ("Результат"),
tr ("Последний результат был скопирован в буфер обмена."),
QSystemTrayIcon::Information);
}
else if (reason == QSystemTrayIcon::DoubleClick && repeatCaptureAction_->isEnabled ()) {
repeatCapture ();
}
}
void Manager::editRecognized (ProcessingItem item) {
QString fixed = QInputDialog::getMultiLineText (
NULL, tr ("Правка"), tr ("Исправьте распознанный текст"), item.recognized);
if (!fixed.isEmpty ()) {
item.recognized = fixed;
++itemProcessingCount_;
updateNormalIcon ();
emit requestTranslate (item);
}
}
void Manager::showLast () {
const ProcessingItem &item = resultDialog_->item ();
if (item.isValid ()) {
++itemProcessingCount_;
showResult (item);
}
}
void Manager::copyLastToClipboard () {
const ProcessingItem &item = resultDialog_->item ();
if (item.isValid ()) {
QClipboard *clipboard = QApplication::clipboard ();
QString message = item.recognized;
if (!item.translated.isEmpty ()) {
message += " - " + item.translated;
}
clipboard->setText (message);
}
}
void Manager::copyLastImageToClipboard () {
const ProcessingItem &item = resultDialog_->item ();
if (item.isValid ()) {
QClipboard *clipboard = QApplication::clipboard ();
clipboard->setPixmap (item.source);
}
}
void Manager::showResult (ProcessingItem item) {
--itemProcessingCount_;
if (!item.isValid ()) {
// delay because it can show error
QTimer::singleShot (3000, this, SLOT (updateNormalIcon ()));
return;
}
changeIcon (IconTypeSuccess);
if (useResultDialog_) {
resultDialog_->showResult (item);
}
else {
QString message = item.recognized + " - " + item.translated;
trayIcon_->showMessage (tr ("Результат"), message, QSystemTrayIcon::Information);
}
updateActionsState ();
}
void Manager::showError (QString text) {
qCritical () << text;
changeIcon (IconTypeError);
trayIcon_->showMessage (tr ("Ошибка"), text, QSystemTrayIcon::Critical);
}
void Manager::changeIcon (int iconType, int timeoutMsec) {
QString fileName;
switch (iconType) {
case IconTypeSuccess:
fileName = ":/images/STIconGreen.png";
break;
case IconTypeError:
fileName = ":/images/STIconRed.png";
break;
default:
return;
}
trayIcon_->setIcon (QIcon (fileName));
if (timeoutMsec > 0) {
QTimer::singleShot (timeoutMsec, this, SLOT (updateNormalIcon ()));
}
}
void Manager::updateNormalIcon () {
QString fileName = itemProcessingCount_ > 0
? ":/images/STIconOrange.png" : ":/images/STIconBlue.png";
trayIcon_->setIcon (QIcon (fileName));
void Manager::quit()
{
QApplication::quit();
}

View File

@ -1,84 +1,40 @@
#ifndef MANAGER_H
#define MANAGER_H
#pragma once
#include <QPixmap>
#include <QSystemTrayIcon>
#include <QMap>
#include "stfwd.h"
#include "processingitem.h"
class QAction;
class QMenu;
class SelectionDialog;
class ResultDialog;
class LanguageHelper;
class Updater;
class Manager : public QObject {
Q_OBJECT
enum IconType {
IconTypeNormal, IconTypeWorking, IconTypeError, IconTypeSuccess
};
class QString;
class Manager
{
public:
explicit Manager (QObject *parent = 0);
Manager();
~Manager();
signals:
void requestRecognize (ProcessingItem item);
void requestTranslate (ProcessingItem item);
void closeSelections ();
void settingsEdited ();
void captured(const TaskPtr &task);
void captureCanceled();
void recognized(const TaskPtr &task);
void corrected(const TaskPtr &task);
void translated(const TaskPtr &task);
private slots:
void fatalError(const QString &text);
void capture();
void repeatCapture();
void settings ();
void close ();
void about ();
void showLast();
void settings();
void copyLastToClipboard();
void copyLastImageToClipboard ();
void applySettings ();
void checkForUpdates ();
void processTrayAction (QSystemTrayIcon::ActivationReason reason);
void editRecognized (ProcessingItem item);
void handleSelection (ProcessingItem item);
void showResult (ProcessingItem item);
void showError (QString text);
void updateNormalIcon ();
void about();
void quit();
private:
QMenu * trayContextMenu ();
void updateActionsState (bool isEnabled = true);
void changeIcon (int iconType, int timeoutMsec = 3000);
void scheduleUpdate (bool justChecked = false);
void updateSettings(const Settings &settings);
void finishTask(const TaskPtr &task);
private:
QSystemTrayIcon *trayIcon_;
LanguageHelper *dictionary_;
//! Selection dialogs for each screen. Key - screen name.
QMap<QString, SelectionDialog *> selections_;
ResultDialog *resultDialog_;
Updater *updater_;
QTimer *updateTimer_;
QAction *captureAction_;
QAction *repeatCaptureAction_;
QAction *repeatAction_;
QAction *clipboardAction_;
bool useResultDialog_;
//! Used threads. For proper termination.
QList<QThread *> threads_;
QString defaultTranslationLanguage_;
QString defaultOrcLanguage_;
bool doTranslation_;
int itemProcessingCount_;
std::unique_ptr<TrayIcon> tray_;
std::unique_ptr<Capturer> capturer_;
std::unique_ptr<Recognizer> recognizer_;
std::unique_ptr<Corrector> corrector_;
std::unique_ptr<Translator> translator_;
std::unique_ptr<Representer> representer_;
TaskPtr last_;
int activeTaskCount_{0};
};
#endif // MANAGER_H

48
src/ocr/recognizer.cpp Normal file
View File

@ -0,0 +1,48 @@
#include "recognizer.h"
#include "manager.h"
#include "recognizerworker.h"
#include "settings.h"
#include "tesseract.h"
#include <QThread>
Recognizer::Recognizer(Manager &manager)
: manager_(manager)
, workerThread_(new QThread(this))
{
auto worker = new RecognizeWorker;
connect(this, &Recognizer::reset, //
worker, &RecognizeWorker::reset);
connect(this, &Recognizer::recognize, //
worker, &RecognizeWorker::handle);
connect(worker, &RecognizeWorker::finished, //
this, &Recognizer::recognized);
connect(workerThread_, &QThread::finished, //
worker, &QObject::deleteLater);
workerThread_->start();
worker->moveToThread(workerThread_);
}
void Recognizer::recognized(const TaskPtr &task)
{
manager_.recognized(task);
}
Recognizer::~Recognizer()
{
workerThread_->quit();
const auto timeoutMs = 2000;
if (!workerThread_->wait(timeoutMs))
workerThread_->terminate();
}
void Recognizer::updateSettings(const Settings &settings)
{
if (settings.tessdataPath.isEmpty()) {
manager_.fatalError(tr("Tessdata path is empty"));
return;
}
emit reset(settings.tessdataPath);
}

25
src/ocr/recognizer.h Normal file
View File

@ -0,0 +1,25 @@
#pragma once
#include "stfwd.h"
#include <QObject>
class Recognizer : public QObject
{
Q_OBJECT
public:
explicit Recognizer(Manager &manager);
~Recognizer();
void updateSettings(const Settings &settings);
signals:
void recognize(const TaskPtr &task);
void reset(const QString &tessdataPath);
private:
void recognized(const TaskPtr &task);
Manager &manager_;
QThread *workerThread_;
};

View File

@ -0,0 +1,46 @@
#include "recognizerworker.h"
#include "debug.h"
#include "task.h"
#include "tesseract.h"
RecognizeWorker::~RecognizeWorker() = default;
void RecognizeWorker::handle(const TaskPtr &task)
{
SOFT_ASSERT(task, return );
SOFT_ASSERT(task->isValid(), return );
SOFT_ASSERT(!tessdataPath_.isEmpty(), return );
auto result = task;
if (!engines_.count(task->sourceLanguage)) {
auto engine =
std::make_unique<Tesseract>(task->sourceLanguage, tessdataPath_);
if (!engine->isValid()) {
result->error = tr("Failed to init OCR engine: %1").arg(engine->error());
emit finished(result);
return;
}
engines_.emplace(task->sourceLanguage, std::move(engine));
}
auto &engine = engines_[task->sourceLanguage];
SOFT_ASSERT(engine->isValid(), return );
result->recognized = engine->recognize(task->captured);
if (result->recognized.isEmpty())
result->error = engine->error();
emit finished(result);
}
void RecognizeWorker::reset(const QString &tessdataPath)
{
if (tessdataPath_ == tessdataPath)
return;
tessdataPath_ = tessdataPath;
engines_.clear();
}

View File

@ -0,0 +1,24 @@
#pragma once
#include "stfwd.h"
#include <QObject>
class Tesseract;
class RecognizeWorker : public QObject
{
Q_OBJECT
public:
~RecognizeWorker();
void handle(const TaskPtr &task);
void reset(const QString &tessdataPath);
signals:
void finished(const TaskPtr &task);
private:
std::map<QString, std::unique_ptr<Tesseract>> engines_;
QString tessdataPath_;
};

220
src/ocr/tesseract.cpp Normal file
View File

@ -0,0 +1,220 @@
#include "tesseract.h"
#include "debug.h"
#include "languagecodes.h"
#include "task.h"
#include <leptonica/allheaders.h>
#include <tesseract/baseapi.h>
#if defined(Q_OS_LINUX)
#include <fstream>
static qint64 getFreeMemory()
{
std::string token;
std::ifstream file("/proc/meminfo");
qint64 freeMem = 0;
while (file >> token) {
if (token == "MemFree:" || token == "Buffers:" || token == "Cached:") {
unsigned long mem = 0;
freeMem += (file >> mem) ? mem : 0;
}
}
return freeMem * 1024;
}
#elif defined(Q_OS_WIN)
#include <windows.h>
#undef min
#undef max
static qint64 getFreeMemory()
{
MEMORYSTATUSEX statex;
statex.dwLength = sizeof(statex);
if (GlobalMemoryStatusEx(&statex)) {
return statex.ullAvailPhys;
}
return -1;
}
#endif
static Pix *convertImage(const QImage &image)
{
PIX *pix;
int width = image.width();
int height = image.height();
int depth = image.depth();
int bytesPerLine = image.bytesPerLine();
int wpl = bytesPerLine / 4;
pix = pixCreate(width, height, depth);
pixSetWpl(pix, wpl);
pixSetColormap(pix, nullptr);
memmove(pix->data, image.bits(), bytesPerLine * height);
const qreal toDPM = 1.0 / 0.0254;
int resolutionX = image.dotsPerMeterX() / toDPM;
int resolutionY = image.dotsPerMeterY() / toDPM;
pixSetResolution(pix, resolutionX, resolutionY);
return pix;
}
static 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(&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 palette
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;
}
static Pix *prepareImage(const QImage &image)
{
Pix *pix = convertImage(image);
SOFT_ASSERT(pix != NULL, return nullptr);
Pix *gray = pixConvertRGBToGray(pix, 0.0, 0.0, 0.0);
SOFT_ASSERT(gray != NULL, return nullptr);
pixDestroy(&pix);
Pix *scaled = gray;
const auto xRes = pixGetXRes(gray);
const auto yRes = pixGetYRes(gray);
if (xRes * yRes != 0) {
const auto preferredScale = std::max(300.0 / std::min(xRes, yRes), 1.0);
if (preferredScale > 1.0) {
const auto MAX_INT16 = 0x7fff;
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);
qint64 availableMemory = getFreeMemory() * 0.95;
if (availableMemory > 0) {
qint32 actualSize = gray->w * gray->h * gray->d / 8;
float maxScaleMemory = float(availableMemory) / actualSize;
scale = std::min(scale, maxScaleMemory);
}
scaled = pixScale(gray, scale, scale);
if (scaled == NULL) {
scaled = gray;
}
}
}
if (scaled != gray) {
pixDestroy(&gray);
}
return scaled;
}
static void cleanupImage(Pix **image)
{
pixDestroy(image);
}
Tesseract::Tesseract(const LanguageId &language, const QString &tessdataPath)
{
SOFT_ASSERT(!tessdataPath.isEmpty(), return );
SOFT_ASSERT(!language.isEmpty(), return );
init(language, tessdataPath);
}
Tesseract::~Tesseract() = default;
void Tesseract::init(const LanguageId &language, const QString &tessdataPath)
{
SOFT_ASSERT(!engine_, return );
LanguageCodes languages;
auto langCodes = languages.findById(language);
if (!langCodes) {
error_ = QObject::tr("unknown recognition language: %1").arg(language);
return;
}
engine_ = std::make_unique<tesseract::TessBaseAPI>();
auto result =
engine_->Init(qPrintable(tessdataPath), qPrintable(langCodes->tesseract),
tesseract::OEM_DEFAULT);
if (result == 0)
return;
error_ = QObject::tr("troubles with tessdata");
engine_.reset();
return;
}
const QString &Tesseract::error() const
{
return error_;
}
QString Tesseract::recognize(const QPixmap &source)
{
SOFT_ASSERT(engine_, return {});
SOFT_ASSERT(!source.isNull(), return {});
error_.clear();
Pix *image = prepareImage(source.toImage());
SOFT_ASSERT(image != NULL, return {});
engine_->SetImage(image);
char *outText = engine_->GetUTF8Text();
engine_->Clear();
cleanupImage(&image);
QString result = QString(outText).trimmed();
delete[] outText;
if (result.isEmpty())
error_ = QObject::tr("Failed to recognize text");
return result;
}
bool Tesseract::isValid() const
{
return engine_.get();
}

31
src/ocr/tesseract.h Normal file
View File

@ -0,0 +1,31 @@
#pragma once
#include "stfwd.h"
#include <QString>
#include <memory>
class QPixmap;
namespace tesseract
{
class TessBaseAPI;
}
class Task;
class Tesseract
{
public:
Tesseract(const LanguageId& language, const QString& tessdataPath);
~Tesseract();
QString recognize(const QPixmap& source);
bool isValid() const;
const QString& error() const;
private:
void init(const LanguageId& language, const QString& tessdataPath);
std::unique_ptr<tesseract::TessBaseAPI> engine_;
QString error_;
};

View File

@ -1,17 +0,0 @@
#include "processingitem.h"
ProcessingItem::ProcessingItem ()
: swapLanguages_ (false) {
}
bool ProcessingItem::isValid (bool checkOnlyInput) const {
bool valid = true;
valid &= (!screenPos.isNull ());
valid &= (!source.isNull ());
valid &= (!ocrLanguage.isEmpty ());
if (!checkOnlyInput) {
valid &= (!recognized.isEmpty ());
}
return valid;
}

View File

@ -1,24 +0,0 @@
#ifndef PROCESSINGITEM_H
#define PROCESSINGITEM_H
#include <QPixmap>
struct ProcessingItem {
ProcessingItem ();
QPoint screenPos;
QPixmap source;
QString recognized;
QString translated;
QString ocrLanguage;
QString sourceLanguage;
QString translateLanguage;
Qt::KeyboardModifiers modifiers;
bool swapLanguages_;
bool isValid (bool checkOnlyInput = false) const;
};
Q_DECLARE_METATYPE (ProcessingItem)
#endif // PROCESSINGITEM_H

View File

@ -1,90 +0,0 @@
#include "recognizer.h"
#include <tesseract/baseapi.h>
#include <QDebug>
#include <QSettings>
#include "settings.h"
#include "imageprocessing.h"
#include "stassert.h"
#include "recognizerhelper.h"
Recognizer::Recognizer (QObject *parent) :
QObject (parent),
engine_ (NULL), recognizerHelper_ (new RecognizerHelper), imageScale_ (0) {
applySettings ();
}
void Recognizer::applySettings () {
QSettings settings;
settings.beginGroup (settings_names::recogntionGroup);
recognizerHelper_->load ();
tessDataDir_ = settings.value (settings_names::tessDataPlace,
settings_values::tessDataPlace).toString ();
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) {
if (!item.isValid (true)) {
emit recognized (item);
return;
}
bool isCustomLanguage = (item.ocrLanguage != ocrLanguage_);
tesseract::TessBaseAPI *engine = (isCustomLanguage) ? NULL : engine_;
QString language = (isCustomLanguage) ? item.ocrLanguage : ocrLanguage_;
if (engine == NULL) {
if (!initEngine (engine, language)) {
emit recognized (item);
return;
}
}
Pix *image = prepareImage (item.source.toImage (), imageScale_);
ST_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 = recognizerHelper_->substitute (result, language);
}
else {
emit error (tr ("Текст не распознан."));
}
emit recognized (item);
}

View File

@ -1,41 +0,0 @@
#ifndef RECOGNIZER_H
#define RECOGNIZER_H
#include <QObject>
#include "QPixmap"
#include "processingitem.h"
namespace tesseract {
class TessBaseAPI;
}
class RecognizerHelper;
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_;
RecognizerHelper *recognizerHelper_;
QString tessDataDir_;
QString ocrLanguage_;
int imageScale_;
};
#endif // RECOGNIZER_H

View File

@ -1,88 +0,0 @@
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QSettings>
#include <QApplication>
#include "recognizerhelper.h"
RecognizerHelper::RecognizerHelper ()
: fileName_ ("st_subs.csv") {
#ifdef Q_OS_LINUX
QDir settingDir = QFileInfo (QSettings ().fileName ()).dir ();
fileName_ = settingDir.absoluteFilePath (fileName_);
#else
fileName_ = QApplication::applicationDirPath () + QDir::separator () + fileName_;
#endif
}
void RecognizerHelper::load () {
subs_.clear ();
QFile f (fileName_);
if (!f.open (QFile::ReadOnly)) {
return;
}
QByteArray data = f.readAll ();
f.close ();
QStringList lines = QString::fromUtf8 (data).split ('\n', QString::SkipEmptyParts);
for (const QString &line: lines) {
QStringList parts = line.mid (1, line.size () - 2).split ("\",\""); // remove "
if (parts.size () < 3) {
continue;
}
subs_.append (Sub (parts[0], parts[1], parts[2]));
}
}
void RecognizerHelper::save () {
QFile f (fileName_);
if (!f.open (QFile::WriteOnly)) {
return;
}
for (const Sub &sub: subs_) {
QStringList parts = QStringList () << sub.language << sub.source << sub.target;
QString line = "\"" + parts.join ("\",\"") + "\"\n";
f.write (line.toUtf8 ());
}
f.close ();
}
QString RecognizerHelper::substitute (const QString &source, const QString &language) const {
QString result = source;
while (true) {
int bestMatchIndex = -1;
int bestMatchLen = 0;
int index = -1;
for (const Sub &sub: subs_) {
++index;
if (sub.language != language || !result.contains (sub.source)) {
continue;
}
int len = sub.source.length ();
if (len > bestMatchLen) {
bestMatchLen = len;
bestMatchIndex = index;
}
}
if (bestMatchIndex > -1) {
const Sub &sub = subs_.at (bestMatchIndex);
result.replace (sub.source, sub.target);
continue;
}
break;
}
return result;
}
const RecognizerHelper::Subs &RecognizerHelper::subs () const {
return subs_;
}
void RecognizerHelper::setSubs (const Subs &subs) {
subs_ = subs;
}
RecognizerHelper::Sub::Sub (const QString &language, const QString &source, const QString &target)
: language (language), source (source), target (target) {
}

View File

@ -1,33 +0,0 @@
#ifndef RECOGNIZERHELPER_H
#define RECOGNIZERHELPER_H
#include <QString>
class RecognizerHelper {
public:
struct Sub {
Sub (const QString &language = QString (), const QString &source = QString (),
const QString &target = QString ());
QString language;
QString source;
QString target;
};
typedef QList<Sub> Subs;
public:
RecognizerHelper ();
void load ();
void save ();
QString substitute (const QString &source, const QString& language) const;
const Subs &subs () const;
void setSubs (const Subs &subs);
private:
QString fileName_;
Subs subs_;
};
#endif // RECOGNIZERHELPER_H

View File

@ -0,0 +1,42 @@
#include "representer.h"
#include "manager.h"
#include "resultwidget.h"
#include "settings.h"
#include "task.h"
#include "trayicon.h"
Representer::Representer(Manager &manager, TrayIcon &tray)
: manager_(manager)
, tray_(tray)
, mode_{ResultMode::Widget}
{
}
Representer::~Representer() = default;
void Representer::represent(const TaskPtr &task)
{
if (mode_ == ResultMode::Tooltip)
showTooltip(task);
else
showWidget(task);
}
void Representer::updateSettings(const Settings &settings)
{
mode_ = settings.resultShowType;
}
void Representer::showTooltip(const TaskPtr &task)
{
auto message = task->recognized + " - " + task->translated;
tray_.showInformation(message);
}
void Representer::showWidget(const TaskPtr &task)
{
if (!widget_)
widget_ = std::make_unique<ResultWidget>();
widget_->show(task);
}

View File

@ -0,0 +1,25 @@
#pragma once
#include "stfwd.h"
enum class ResultMode;
class ResultWidget;
class Representer
{
public:
Representer(Manager &manager, TrayIcon &tray);
~Representer();
void represent(const TaskPtr &task);
void updateSettings(const Settings &settings);
private:
void showTooltip(const TaskPtr &task);
void showWidget(const TaskPtr &task);
Manager &manager_;
TrayIcon &tray_;
std::unique_ptr<ResultWidget> widget_;
ResultMode mode_;
};

View File

@ -0,0 +1,116 @@
#include "resultwidget.h"
#include "debug.h"
#include "task.h"
#include <QApplication>
#include <QBoxLayout>
#include <QDesktopWidget>
#include <QLabel>
#include <QMouseEvent>
ResultWidget::ResultWidget(QWidget *parent)
: QFrame(parent)
, image_(new QLabel(this))
, recognized_(new QLabel(this))
, translated_(new QLabel(this))
{
setWindowFlags(Qt::FramelessWindowHint | Qt::NoDropShadowWindowHint |
Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint);
setLineWidth(1);
setFrameShape(QFrame::StyledPanel);
setFrameShadow(QFrame::Plain);
image_->setAlignment(Qt::AlignCenter);
recognized_->setObjectName("recognizeLabel");
recognized_->setAlignment(Qt::AlignCenter);
recognized_->setWordWrap(true);
translated_->setObjectName("translateLabel");
translated_->setAlignment(Qt::AlignCenter);
translated_->setWordWrap(true);
const auto styleSheet =
"#recognizeLabel, #translateLabel {"
"color: black;"
"background: qlineargradient(x1:0, y1:0, x2:1, y2:1,"
"stop:0 darkGray, stop: 0.5 lightGray, stop:1 darkGray);"
"}";
setStyleSheet(styleSheet);
installEventFilter(this);
auto layout = new QVBoxLayout(this);
layout->addWidget(image_);
layout->addWidget(recognized_);
layout->addWidget(translated_);
layout->setMargin(0);
layout->setSpacing(1);
}
void ResultWidget::show(const TaskPtr &task)
{
SOFT_ASSERT(task->isValid(), return );
image_->setPixmap(task->captured);
recognized_->setText(task->recognized);
translated_->setText(task->translated);
const auto gotTranslation = !task->translated.isEmpty();
translated_->setVisible(gotTranslation);
show();
adjustSize();
QDesktopWidget *desktop = QApplication::desktop();
Q_CHECK_PTR(desktop);
auto correction = QPoint((width() - task->captured.width()) / 2, lineWidth());
move(task->capturePoint - correction);
auto screenRect = desktop->screenGeometry(this);
auto minY = screenRect.bottom() - height();
if (y() > minY) {
move(x(), minY);
}
activateWindow();
}
bool ResultWidget::eventFilter(QObject *watched, QEvent *event)
{
if (event->type() == QEvent::MouseButtonPress) {
const auto button = static_cast<QMouseEvent *>(event)->button();
if (button == Qt::LeftButton) {
hide();
}
// else if (button == Qt::RightButton) {
// QAction *action = contextMenu_->exec(QCursor::pos());
// if (recognizeSubMenu_->findChildren<QAction *>().contains(action)) {
// ProcessingItem item = item_;
// task->translated = task->recognized = QString();
// task->ocrLanguage = dictionary_.ocrUiToCode(action->text());
// emit requestRecognize(item);
// } else if (translateSubMenu_->findChildren<QAction *>().contains(
// action)) {
// ProcessingItem item = item_;
// task->translated.clear();
// task->translateLanguage =
// dictionary_.translateUiToCode(action->text()); emit
// requestTranslate(item);
// } else if (action == clipboardAction_) {
// emit requestClipboard();
// } else if (action == imageClipboardAction_) {
// emit requestImageClipboard();
// } else if (action == correctAction_) {
// emit requestEdition(item_);
// // Return because Manager calls showResult() before hide()
// otherwise.return QWidget::eventFilter(watched, event);
// }
// }
// hide();
} else if (event->type() == QEvent::WindowDeactivate) {
hide();
}
return QWidget::eventFilter(watched, event);
}

View File

@ -0,0 +1,24 @@
#pragma once
#include "stfwd.h"
#include <QFrame>
class QLabel;
class ResultWidget : public QFrame
{
Q_OBJECT
public:
ResultWidget(QWidget* parent = nullptr);
void show(const TaskPtr& task);
using QWidget::show;
bool eventFilter(QObject* watched, QEvent* event) override;
private:
QLabel* image_;
QLabel* recognized_;
QLabel* translated_;
};

View File

@ -1,121 +0,0 @@
#include "resultdialog.h"
#include "ui_resultdialog.h"
#include "stassert.h"
#include "languagehelper.h"
#include <QDesktopWidget>
#include <QMouseEvent>
#include <QMenu>
ResultDialog::ResultDialog (const LanguageHelper &dictionary, QWidget *parent) :
QDialog (parent),
ui (new Ui::ResultDialog),
dictionary_ (dictionary),
contextMenu_ (NULL), recognizeSubMenu_ (NULL), translateSubMenu_ (NULL),
clipboardAction_ (NULL), imageClipboardAction_ (NULL), correctAction_ (NULL) {
ui->setupUi (this);
setWindowFlags (Qt::FramelessWindowHint | Qt::NoDropShadowWindowHint |
Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint);
QString styleSheet =
"#recognizeLabel, #translateLabel {"
"color: black;"
"background: qlineargradient(x1:0, y1:0, x2:1, y2:1,"
"stop:0 darkGray, stop: 0.5 lightGray, stop:1 darkGray);"
"}";
setStyleSheet (styleSheet);
installEventFilter (this);
createContextMenu ();
applySettings ();
}
ResultDialog::~ResultDialog () {
delete contextMenu_;
delete ui;
}
const ProcessingItem &ResultDialog::item () const {
return item_;
}
void ResultDialog::applySettings () {
dictionary_.updateMenu (recognizeSubMenu_, dictionary_.availableOcrLanguagesUi ());
dictionary_.updateMenu (translateSubMenu_, dictionary_.translateLanguagesUi ());
}
void ResultDialog::createContextMenu () {
contextMenu_ = new QMenu ();
recognizeSubMenu_ = contextMenu_->addMenu (tr ("Распознать другой язык"));
translateSubMenu_ = contextMenu_->addMenu (tr ("Перевести на другой язык"));
clipboardAction_ = contextMenu_->addAction (tr ("Скопировать в буфер"));
imageClipboardAction_ = contextMenu_->addAction (tr ("Скопировать рисунок в буфер"));
correctAction_ = contextMenu_->addAction (tr ("Исправить распознанный текст"));
}
bool ResultDialog::eventFilter (QObject *object, QEvent *event) {
Q_UNUSED (object);
if (event->type () == QEvent::MouseButtonPress) {
Qt::MouseButton button = static_cast<QMouseEvent *>(event)->button ();
if (button == Qt::RightButton) {
QAction *action = contextMenu_->exec (QCursor::pos ());
if (recognizeSubMenu_->findChildren<QAction *> ().contains (action)) {
ProcessingItem item = item_;
item.translated = item.recognized = QString ();
item.ocrLanguage = dictionary_.ocrUiToCode (action->text ());
emit requestRecognize (item);
}
else if (translateSubMenu_->findChildren<QAction *> ().contains (action)) {
ProcessingItem item = item_;
item.translated.clear ();
item.translateLanguage = dictionary_.translateUiToCode (action->text ());
emit requestTranslate (item);
}
else if (action == clipboardAction_) {
emit requestClipboard ();
}
else if (action == imageClipboardAction_) {
emit requestImageClipboard ();
}
else if (action == correctAction_) {
emit requestEdition (item_);
// Return because Manager calls showResult() before hide() otherwise.
return QDialog::eventFilter (object, event);
}
}
hide ();
}
else if (event->type () == QEvent::WindowDeactivate) {
hide ();
}
return QDialog::eventFilter (object, event);
}
void ResultDialog::showResult (ProcessingItem item) {
ST_ASSERT (item.isValid ());
item_ = item;
ui->sourceLabel->setPixmap (item.source);
ui->recognizeLabel->setText (item.recognized);
ui->translateLabel->setText (item.translated);
bool gotTranslation = !item.translated.isEmpty ();
ui->translateLabel->setVisible (gotTranslation);
ui->translateLine->setVisible (gotTranslation);
show ();
adjustSize ();
#ifdef Q_OS_LINUX
hide (); // buggy otherwise (on some systems)
show ();
#endif
QDesktopWidget *desktop = QApplication::desktop ();
Q_CHECK_PTR (desktop);
QPoint correction = QPoint ((width () - item.source.width ()) / 2, ui->frame->lineWidth ());
move (item.screenPos - correction);
QRect screenRect = desktop->screenGeometry (this);
int minY = screenRect.bottom () - height ();
if (y () > minY) {
move (x (), minY);
}
activateWindow ();
}

View File

@ -1,51 +0,0 @@
#ifndef RESULTDIALOG_H
#define RESULTDIALOG_H
#include <QDialog>
#include <QMenu>
#include "processingitem.h"
namespace Ui {
class ResultDialog;
}
class LanguageHelper;
class ResultDialog : public QDialog {
Q_OBJECT
public:
explicit ResultDialog (const LanguageHelper &dictionary, QWidget *parent = 0);
~ResultDialog ();
signals:
void requestRecognize (ProcessingItem item);
void requestTranslate (ProcessingItem item);
void requestClipboard (); // Assume that slot will be called immediately.
void requestImageClipboard (); // Assume that slot will be called immediately.
void requestEdition (ProcessingItem item);
public:
const ProcessingItem &item () const;
bool eventFilter (QObject *object, QEvent *event);
public slots:
void showResult (ProcessingItem item);
void applySettings ();
private:
void createContextMenu ();
private:
Ui::ResultDialog *ui;
const LanguageHelper &dictionary_;
QMenu *contextMenu_;
QMenu *recognizeSubMenu_;
QMenu *translateSubMenu_;
QAction *clipboardAction_;
QAction *imageClipboardAction_;
QAction *correctAction_;
ProcessingItem item_;
};
#endif // RESULTDIALOG_H

View File

@ -1,116 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ResultDialog</class>
<widget class="QDialog" name="ResultDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>260</width>
<height>222</height>
</rect>
</property>
<property name="windowTitle">
<string>Результат</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<property name="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>
<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="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>
<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="recognizeLine">
<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="translateLine">
<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>

View File

@ -1,122 +0,0 @@
#include "selectiondialog.h"
#include "ui_selectiondialog.h"
#include "languagehelper.h"
#include "stassert.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), swapLanguagesAction_ (NULL) {
ui->setupUi (this);
setWindowFlags (Qt::FramelessWindowHint | Qt::NoDropShadowWindowHint |
Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint);
ui->label->setAutoFillBackground (false);
ui->label->installEventFilter (this);
applySettings ();
}
SelectionDialog::~SelectionDialog () {
delete languageMenu_;
delete ui;
}
void SelectionDialog::applySettings () {
dictionary_.updateMenu (languageMenu_, dictionary_.availableOcrLanguagesUi ());
if (!languageMenu_->isEmpty ()) {
swapLanguagesAction_ = languageMenu_->addAction (tr ("Поменять язык текста и перевода"));
}
}
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 ();
startSelectPos_ = currentSelectPos_ = QPoint ();
QPixmap selectedPixmap = currentPixmap_.copy (selection);
if (selectedPixmap.width () < 3 || selectedPixmap.height () < 3) {
reject ();
return QDialog::eventFilter (object, event);
}
ProcessingItem item;
item.source = selectedPixmap;
item.screenPos = pos () + selection.topLeft ();
item.modifiers = mouseEvent->modifiers ();
if (mouseEvent->button () == Qt::RightButton &&
!languageMenu_->children ().isEmpty ()) {
QAction *action = languageMenu_->exec (QCursor::pos ());
if (action == NULL) {
reject ();
return QDialog::eventFilter (object, event);
}
if (action == swapLanguagesAction_) {
item.swapLanguages_ = true;
}
else {
item.ocrLanguage = dictionary_.ocrUiToCode (action->text ());
ST_ASSERT (!item.ocrLanguage.isEmpty ());
item.sourceLanguage = dictionary_.ocrToTranslateCodes (item.ocrLanguage);
ST_ASSERT (!item.sourceLanguage.isEmpty ());
}
}
emit selected (item);
}
}
return QDialog::eventFilter (object, event);
}
void SelectionDialog::setPixmap (QPixmap pixmap, const QRect &showGeometry) {
ST_ASSERT (!pixmap.isNull ());
ST_ASSERT (!showGeometry.isEmpty ());
currentPixmap_ = pixmap;
QPalette palette = this->palette ();
palette.setBrush (this->backgroundRole (), pixmap);
this->setPalette (palette);
this->setGeometry (showGeometry);
show ();
activateWindow ();
}

View File

@ -1,43 +0,0 @@
#ifndef SELECTIONDIALOG_H
#define SELECTIONDIALOG_H
#include <QDialog>
#include <QPixmap>
#include <QMenu>
#include "processingitem.h"
namespace Ui {
class SelectionDialog;
}
class LanguageHelper;
class SelectionDialog : public QDialog {
Q_OBJECT
public:
explicit SelectionDialog (const LanguageHelper &dictionary, QWidget *parent = 0);
~SelectionDialog ();
bool eventFilter (QObject *object, QEvent *event);
signals:
void selected (ProcessingItem pixmap);
void nothingSelected ();
public slots:
//! Show pixmap with given geometry.
void setPixmap (QPixmap pixmap, const QRect &showGeometry);
void applySettings ();
private:
Ui::SelectionDialog *ui;
const LanguageHelper &dictionary_;
QPoint startSelectPos_;
QPoint currentSelectPos_;
QPixmap currentPixmap_;
QMenu *languageMenu_;
QAction *swapLanguagesAction_;
};
#endif // SELECTIONDIALOG_H

View File

@ -1,43 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SelectionDialog</class>
<widget class="QDialog" name="SelectionDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="cursor">
<cursorShape>CrossCursor</cursorShape>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,131 @@
#include "apptranslator.h"
#include "debug.h"
#include <QApplication>
#include <QDir>
#include <QLibraryInfo>
#include <QTranslator>
namespace
{
// const auto builtin = QLocale::English;
} // namespace
AppTranslator::AppTranslator(const QStringList &translationFiles)
: translationFiles_(translationFiles)
{
}
void AppTranslator::retranslate()
{
auto app = QCoreApplication::instance();
const auto oldTranslators = app->findChildren<QTranslator *>();
for (const auto &old : oldTranslators) {
old->deleteLater();
}
// QLocale locale(translation());
QLocale locale;
const auto files =
QStringList{QStringLiteral("qt"), QStringLiteral("qtbase")} +
translationFiles_;
const auto paths = searchPaths();
auto last = new QTranslator(app);
for (const auto &name : files) {
for (const auto &path : paths) {
if (!last->load(locale, name, QLatin1String("_"), path))
continue;
app->installTranslator(last);
last = new QTranslator(app);
break;
}
}
last->deleteLater();
}
// QStringList TranslationLoader::availableLanguages()
//{
// QStringList result{QLocale(builtin).nativeLanguageName()};
// auto checker = new QTranslator(QApplication::instance());
// for (const auto &dir : searchPaths()) {
// for (const auto &file :
// QDir(dir).entryInfoList({appTranslation + '*'}, QDir::Files)) {
// if (checker->load(file.absoluteFilePath())) {
// const auto name = file.baseName();
// const auto suffixIndex = name.indexOf(QLatin1Char('_'));
// if (suffixIndex < 0) {
// continue;
// }
// const auto suffix = name.mid(suffixIndex + 1);
// const auto locale = QLocale(suffix);
// const auto language = locale.nativeLanguageName();
// if (!result.contains(language)) {
// result.append(language);
// }
// }
// }
// }
// return result;
//}
// QString TranslationLoader::language()
//{
// return toLanguage(translation());
//}
// void TranslationLoader::setLanguage(const QString &language)
//{
// setTranslation(toTranslation(language));
//}
// QString TranslationLoader::translation()
//{
// SettingsManager settings;
// auto name = settings.get(SettingsManager::Translation).toString();
// if (name.isEmpty()) {
// const QLocale locale;
// if (locale.language() == QLocale::Language::C) {
// name = QLocale(builtin).name();
// } else {
// name = locale.name();
// }
// }
// return name;
//}
// void TranslationLoader::setTranslation(const QString &translation)
//{
// SettingsManager settings;
// settings.set(SettingsManager::Translation, translation);
//}
QStringList AppTranslator::searchPaths() const
{
return QStringList{
QLibraryInfo::location(QLibraryInfo::TranslationsPath),
#ifdef Q_OS_LINUX
qgetenv("APPDIR") +
QLibraryInfo::location(QLibraryInfo::TranslationsPath), // appimage
#endif // ifdef Q_OS_LINUX
{},
QLatin1String("translations"),
};
}
// QString TranslationLoader::toLanguage(const QString &translation)
//{
// return QLocale(translation).nativeLanguageName();
//}
// QString TranslationLoader::toTranslation(const QString &language)
//{
// for (auto i = 0; i < QLocale::Language::LastLanguage; ++i) {
// const auto locale = QLocale(QLocale::Language(i));
// if (locale.nativeLanguageName() == language) {
// return locale.name();
// }
// }
// return QLocale().name();
//}

View File

@ -0,0 +1,25 @@
#pragma once
#include <QStringList>
class AppTranslator
{
public:
explicit AppTranslator(const QStringList &translationFiles);
void retranslate();
// static QStringList availableLanguages();
// static QString language();
// static void setLanguage(const QString &language);
private:
// static QString translation();
// static void setTranslation(const QString &translation);
QStringList searchPaths() const;
// static QString toTranslation(const QString &language);
// static QString toLanguage(const QString &translation);
QStringList translationFiles_;
};

6
src/service/debug.cpp Normal file
View File

@ -0,0 +1,6 @@
#include "debug.h"
namespace debug
{
std::atomic_bool isTrace;
}

71
src/service/debug.h Normal file
View File

@ -0,0 +1,71 @@
#pragma once
#include <QDebug>
#define SOFT_ASSERT(XXX, WORKAROUND) \
if (!(XXX)) { \
qCritical() << "Soft assertion failed at" << __FILE__ << __LINE__ << ":" \
<< #XXX; \
WORKAROUND; \
}
#define ASSERT(XXX) \
if (!(XXX)) { \
qCritical() << "Assertion failed at" << __FILE__ << __LINE__ << ":" \
<< #XXX; \
Q_ASSERT(XXX); \
}
#define ASSERT_X(XXX, CONTEXT) \
if (!(XXX)) { \
qCritical() << "Assertion failed at" << __FILE__ << __LINE__ << ":" \
<< #XXX << "Context (" << #CONTEXT << ")" << CONTEXT; \
Q_ASSERT(XXX); \
}
namespace debug
{
extern std::atomic_bool isTrace;
};
#define LTRACE() \
if (debug::isTrace) \
qDebug()
#define LTRACE_IF(XXX) \
if (debug::isTrace && XXX) \
qDebug()
#define LDEBUG() qDebug()
#define LDEBUG_IF(XXX) \
if (XXX) \
qDebug()
#define LWARNING() qWarning()
#define LWARNING_IF(XXX) \
if (XXX) \
qWarning()
#define LERROR() qCritical()
#define LERROR_IF(XXX) \
if (XXX) \
qCritical()
#define LINFO() qInfo()
#define LINFO_IF(XXX) \
if (XXX) \
qInfo()
#define LFATAL() qFatal
#define LFATAL_IF(XXX) \
if (XXX) \
qFatal
#define LARG(XXX) #XXX "=" << XXX
#define LARG_N(NAME, XXX) NAME << '=' << XXX

View File

@ -0,0 +1,481 @@
#include "globalaction.h"
#include "debug.h"
#include <QApplication>
QHash<QPair<quint32, quint32>, QAction *> GlobalAction::actions_;
void GlobalAction::init()
{
qApp->installNativeEventFilter(new GlobalAction);
}
bool GlobalAction::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
LERROR() << "Failed to register global hotkey:" << LARG(hotKey.toString());
return res;
}
bool GlobalAction::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
LERROR() << "Failed to unregister global hotkey:" << (hotKey.toString());
return res;
}
bool GlobalAction::update(QAction *action, const QKeySequence &newShortcut)
{
if (!action->shortcut().isEmpty())
removeGlobal(action);
action->setShortcut(newShortcut);
return newShortcut.isEmpty() ? true : makeGlobal(action);
}
void GlobalAction::triggerHotKey(quint32 nativeKey, quint32 nativeMods)
{
QAction *action = actions_.value(qMakePair(nativeKey, nativeMods));
if (action && action->isEnabled())
action->activate(QAction::Trigger);
}
#ifdef Q_OS_LINUX
#include <X11/Xlib.h>
#include <xcb/xcb_event.h>
#include <QX11Info>
static bool error = false;
static int customHandler(Display *display, XErrorEvent *event)
{
Q_UNUSED(display);
switch (event->error_code) {
case BadAccess:
case BadValue:
case BadWindow:
if (event->request_code == 33 /* X_GrabKey */ ||
event->request_code == 34 /* X_UngrabKey */) {
error = true;
}
default: return 0;
}
}
bool GlobalAction::registerHotKey(quint32 nativeKey, quint32 nativeMods)
{
Display *display = QX11Info::display();
Window window = QX11Info::appRootWindow();
Bool owner = True;
int pointer = GrabModeAsync;
int keyboard = GrabModeAsync;
error = false;
int (*handler)(Display * display, XErrorEvent * event) =
XSetErrorHandler(customHandler);
XGrabKey(display, nativeKey, nativeMods, window, owner, pointer, keyboard);
// allow numlock
XGrabKey(display, nativeKey, nativeMods | Mod2Mask, window, owner, pointer,
keyboard);
XSync(display, False);
XSetErrorHandler(handler);
return !error;
}
bool GlobalAction::unregisterHotKey(quint32 nativeKey, quint32 nativeMods)
{
Display *display = QX11Info::display();
Window window = QX11Info::appRootWindow();
error = false;
int (*handler)(Display * display, XErrorEvent * event) =
XSetErrorHandler(customHandler);
XUngrabKey(display, nativeKey, nativeMods, window);
// allow numlock
XUngrabKey(display, nativeKey, nativeMods | Mod2Mask, window);
XSync(display, False);
XSetErrorHandler(handler);
return !error;
}
bool GlobalAction::nativeEventFilter(const QByteArray &eventType, void *message,
long *result)
{
Q_UNUSED(eventType);
Q_UNUSED(result);
xcb_generic_event_t *event = static_cast<xcb_generic_event_t *>(message);
if (event->response_type == XCB_KEY_PRESS) {
xcb_key_press_event_t *keyEvent =
static_cast<xcb_key_press_event_t *>(message);
const quint32 keycode = keyEvent->detail;
const quint32 modifiers = keyEvent->state & ~XCB_MOD_MASK_2;
triggerHotKey(keycode, modifiers);
}
return false;
}
quint32 GlobalAction::nativeKeycode(Qt::Key key)
{
Display *display = QX11Info::display();
KeySym keySym = XStringToKeysym(qPrintable(QKeySequence(key).toString()));
return XKeysymToKeycode(display, keySym);
}
quint32 GlobalAction::nativeModifiers(Qt::KeyboardModifiers modifiers)
{
quint32 native = 0;
if (modifiers & Qt::ShiftModifier)
native |= ShiftMask;
if (modifiers & Qt::ControlModifier)
native |= ControlMask;
if (modifiers & Qt::AltModifier)
native |= Mod1Mask;
if (modifiers & Qt::MetaModifier)
native |= Mod4Mask;
return native;
}
#endif // ifdef Q_OS_LINUX
#ifdef Q_OS_WIN
#include <qt_windows.h>
bool GlobalAction::registerHotKey(quint32 nativeKey, quint32 nativeMods)
{
return RegisterHotKey(0, nativeMods ^ nativeKey, nativeMods, nativeKey);
}
bool GlobalAction::unregisterHotKey(quint32 nativeKey, quint32 nativeMods)
{
return UnregisterHotKey(0, nativeMods ^ nativeKey);
}
bool GlobalAction::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);
triggerHotKey(keycode, modifiers);
}
return false;
}
quint32 GlobalAction::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;
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 GlobalAction::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;
}
#endif // ifdef Q_OS_WIN
#ifdef Q_OS_MAC
#include <Carbon/Carbon.h>
static bool isInited = false;
static QHash<QPair<quint32, quint32>, EventHotKeyRef> hotkeyRefs;
struct ActionAdapter {
static OSStatus macHandler(EventHandlerCallRef /*nextHandler*/,
EventRef event, void * /*userData*/)
{
EventHotKeyID id;
GetEventParameter(event, kEventParamDirectObject, typeEventHotKeyID, NULL,
sizeof(id), NULL, &id);
GlobalAction::triggerHotKey(quint32(id.signature), quint32(id.id));
return noErr;
}
};
bool GlobalAction::registerHotKey(quint32 nativeKey, quint32 nativeMods)
{
if (!isInited) {
EventTypeSpec spec;
spec.eventClass = kEventClassKeyboard;
spec.eventKind = kEventHotKeyPressed;
InstallApplicationEventHandler(&ActionAdapter::macHandler, 1, &spec, NULL,
NULL);
isInited = true;
}
EventHotKeyID id;
id.signature = nativeKey;
id.id = nativeMods;
EventHotKeyRef ref = NULL;
OSStatus status = RegisterEventHotKey(nativeKey, nativeMods, id,
GetApplicationEventTarget(), 0, &ref);
if (status != noErr) {
LERROR() << "RegisterEventHotKey error:" << LARG(status);
return false;
} else {
hotkeyRefs.insert(qMakePair(nativeKey, nativeMods), ref);
return true;
}
}
bool GlobalAction::unregisterHotKey(quint32 nativeKey, quint32 nativeMods)
{
EventHotKeyRef ref = hotkeyRefs.value(qMakePair(nativeKey, nativeMods));
ASSERT(ref);
OSStatus status = UnregisterEventHotKey(ref);
if (status != noErr) {
LERROR() << "UnregisterEventHotKey error:" << LARG(status);
return false;
} else {
hotkeyRefs.remove(qMakePair(nativeKey, nativeMods));
return true;
}
}
bool GlobalAction::nativeEventFilter(const QByteArray & /*eventType*/,
void * /*message*/, long * /*result*/)
{
return false;
}
quint32 GlobalAction::nativeKeycode(Qt::Key key)
{
switch (key) {
case Qt::Key_A: return kVK_ANSI_A;
case Qt::Key_B: return kVK_ANSI_B;
case Qt::Key_C: return kVK_ANSI_C;
case Qt::Key_D: return kVK_ANSI_D;
case Qt::Key_E: return kVK_ANSI_E;
case Qt::Key_F: return kVK_ANSI_F;
case Qt::Key_G: return kVK_ANSI_G;
case Qt::Key_H: return kVK_ANSI_H;
case Qt::Key_I: return kVK_ANSI_I;
case Qt::Key_J: return kVK_ANSI_J;
case Qt::Key_K: return kVK_ANSI_K;
case Qt::Key_L: return kVK_ANSI_L;
case Qt::Key_M: return kVK_ANSI_M;
case Qt::Key_N: return kVK_ANSI_N;
case Qt::Key_O: return kVK_ANSI_O;
case Qt::Key_P: return kVK_ANSI_P;
case Qt::Key_Q: return kVK_ANSI_Q;
case Qt::Key_R: return kVK_ANSI_R;
case Qt::Key_S: return kVK_ANSI_S;
case Qt::Key_T: return kVK_ANSI_T;
case Qt::Key_U: return kVK_ANSI_U;
case Qt::Key_V: return kVK_ANSI_V;
case Qt::Key_W: return kVK_ANSI_W;
case Qt::Key_X: return kVK_ANSI_X;
case Qt::Key_Y: return kVK_ANSI_Y;
case Qt::Key_Z: return kVK_ANSI_Z;
case Qt::Key_0: return kVK_ANSI_0;
case Qt::Key_1: return kVK_ANSI_1;
case Qt::Key_2: return kVK_ANSI_2;
case Qt::Key_3: return kVK_ANSI_3;
case Qt::Key_4: return kVK_ANSI_4;
case Qt::Key_5: return kVK_ANSI_5;
case Qt::Key_6: return kVK_ANSI_6;
case Qt::Key_7: return kVK_ANSI_7;
case Qt::Key_8: return kVK_ANSI_8;
case Qt::Key_9: return kVK_ANSI_9;
case Qt::Key_F1: return kVK_F1;
case Qt::Key_F2: return kVK_F2;
case Qt::Key_F3: return kVK_F3;
case Qt::Key_F4: return kVK_F4;
case Qt::Key_F5: return kVK_F5;
case Qt::Key_F6: return kVK_F6;
case Qt::Key_F7: return kVK_F7;
case Qt::Key_F8: return kVK_F8;
case Qt::Key_F9: return kVK_F9;
case Qt::Key_F10: return kVK_F10;
case Qt::Key_F11: return kVK_F11;
case Qt::Key_F12: return kVK_F12;
case Qt::Key_F13: return kVK_F13;
case Qt::Key_F14: return kVK_F14;
case Qt::Key_F15: return kVK_F15;
case Qt::Key_F16: return kVK_F16;
case Qt::Key_F17: return kVK_F17;
case Qt::Key_F18: return kVK_F18;
case Qt::Key_F19: return kVK_F19;
case Qt::Key_F20: return kVK_F10;
case Qt::Key_Return: return kVK_Return;
case Qt::Key_Enter: return kVK_ANSI_KeypadEnter;
case Qt::Key_Tab: return kVK_Tab;
case Qt::Key_Space: return kVK_Space;
case Qt::Key_Backspace: return kVK_Delete;
case Qt::Key_Escape: return kVK_Escape;
case Qt::Key_CapsLock: return kVK_CapsLock;
case Qt::Key_Option: return kVK_Option;
case Qt::Key_VolumeUp: return kVK_VolumeUp;
case Qt::Key_VolumeDown: return kVK_VolumeDown;
case Qt::Key_Help: return kVK_Help;
case Qt::Key_Home: return kVK_Home;
case Qt::Key_PageUp: return kVK_PageUp;
case Qt::Key_Delete: return kVK_ForwardDelete;
case Qt::Key_End: return kVK_End;
case Qt::Key_PageDown: return kVK_PageDown;
case Qt::Key_Left: return kVK_LeftArrow;
case Qt::Key_Right: return kVK_RightArrow;
case Qt::Key_Down: return kVK_DownArrow;
case Qt::Key_Up: return kVK_UpArrow;
default: return 0;
}
}
quint32 GlobalAction::nativeModifiers(Qt::KeyboardModifiers modifiers)
{
quint32 native = 0;
if (modifiers & Qt::ShiftModifier)
native |= shiftKey;
if (modifiers & Qt::ControlModifier)
native |= cmdKey;
if (modifiers & Qt::AltModifier)
native |= optionKey;
if (modifiers & Qt::MetaModifier)
native |= controlKey;
return native;
}
#endif // ifdef Q_OS_MAC

View File

@ -0,0 +1,29 @@
#pragma once
// Some functions copied from QXT lib
#include <QAbstractNativeEventFilter>
#include <QAction>
class GlobalAction : 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);
static bool update(QAction *action, const QKeySequence &newShortcut);
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);
static void triggerHotKey(quint32 nativeKey, quint32 nativeMods);
friend struct ActionAdapter;
};

View File

@ -0,0 +1,31 @@
#include "singleapplication.h"
#include "debug.h"
#include <QCoreApplication>
#include <QDir>
#include <QStandardPaths>
static QString fileName(const QString &baseName)
{
const auto name = !baseName.isEmpty()
? baseName
: QCoreApplication::applicationName().toLower();
SOFT_ASSERT(!name.isEmpty(), return QStringLiteral("./dummy.lock"));
return QStandardPaths::writableLocation(QStandardPaths::TempLocation) +
QDir::separator() + name + QLatin1String(".lock");
}
SingleApplication::SingleApplication(const QString &baseName)
: lockFile_(fileName(baseName))
{
if (!lockFile_.tryLock()) {
const auto lockName = fileName(baseName);
LERROR() << QObject::tr("Another instance is running. Lock file is busy.")
<< LARG(lockName);
}
}
bool SingleApplication::isValid() const
{
return lockFile_.isLocked();
}

View File

@ -0,0 +1,14 @@
#pragma once
#include <QLockFile>
class SingleApplication
{
public:
explicit SingleApplication(const QString &baseName = {});
bool isValid() const;
private:
QLockFile lockFile_;
};

118
src/service/widgetstate.cpp Normal file
View File

@ -0,0 +1,118 @@
#include "widgetstate.h"
#include "debug.h"
#include <QCoreApplication>
#include <QHeaderView>
#include <QMainWindow>
#include <QSettings>
#include <QSplitter>
#include <QTableView>
namespace
{
enum class Action { Save, Restore };
void handleGeometry(QSettings *settings, QWidget *widget, Action action)
{
SOFT_ASSERT(widget, return );
if (widget->parent())
return;
const auto name = widget->objectName() + QLatin1String("_geometry");
if (action == Action::Save) {
settings->setValue(name, widget->geometry());
} else {
if (settings->contains(name))
widget->setGeometry(settings->value(name).toRect());
}
}
template <class T>
void handleState(QSettings *settings, QWidget *widget, Action action)
{
auto instance = qobject_cast<T *>(widget);
if (!instance)
return;
const auto name = widget->objectName() + QLatin1Char('_') + typeid(T).name();
if (action == Action::Save)
settings->setValue(name, instance->saveState());
else
instance->restoreState(settings->value(name).toByteArray());
}
void handleWidget(QSettings *settings, QWidget *widget, Action action)
{
SOFT_ASSERT(widget, return );
if (!widget->objectName().isEmpty()) {
handleGeometry(settings, widget, action);
handleState<QSplitter>(settings, widget, action);
handleState<QHeaderView>(settings, widget, action);
handleState<QMainWindow>(settings, widget, action);
}
settings->beginGroup(widget->objectName());
const auto children =
widget->findChildren<QWidget *>(QString(), Qt::FindDirectChildrenOnly);
for (auto *child : children) {
handleWidget(settings, child, action);
}
settings->endGroup();
}
void apply(QWidget *widget, Action action)
{
QSettings settings;
settings.beginGroup(QStringLiteral("GUI"));
handleWidget(&settings, widget, action);
settings.endGroup();
}
} // namespace
WidgetState::WidgetState(QWidget *parent)
: QObject(parent)
{
add(parent);
}
void WidgetState::add(QWidget *watched)
{
if (!watched)
return;
watched->installEventFilter(this);
}
bool WidgetState::eventFilter(QObject *watched, QEvent *event)
{
if (event->type() != QEvent::Show && event->type() != QEvent::Hide)
return QObject::eventFilter(watched, event);
auto widget = qobject_cast<QWidget *>(watched);
if (!widget)
return QObject::eventFilter(watched, event);
if (event->type() == QEvent::Show)
restore(widget);
else if (event->type() == QEvent::Hide)
save(widget);
return QObject::eventFilter(watched, event);
}
void WidgetState::save(QWidget *widget)
{
SOFT_ASSERT(widget, return );
SOFT_ASSERT(!widget->objectName().isEmpty(), return );
apply(widget, Action::Save);
}
void WidgetState::restore(QWidget *widget)
{
if (QCoreApplication::arguments().contains(QLatin1String("--reset-gui")))
return;
SOFT_ASSERT(widget, return );
SOFT_ASSERT(!widget->objectName().isEmpty(), return );
apply(widget, Action::Restore);
}

14
src/service/widgetstate.h Normal file
View File

@ -0,0 +1,14 @@
#pragma once
#include <QObject>
class WidgetState : public QObject
{
public:
WidgetState(QWidget *parent = nullptr);
void add(QWidget *watched);
bool eventFilter(QObject *watched, QEvent *event) override;
static void save(QWidget *widget);
static void restore(QWidget *widget);
};

152
src/settings.cpp Normal file
View File

@ -0,0 +1,152 @@
#include "settings.h"
#include <QSettings>
namespace
{
const QString qs_guiGroup = "GUI";
const QString qs_captureHotkey = "captureHotkey";
const QString qs_repeatCaptureHotkey = "repeatCaptureHotkey";
const QString qs_repeatHotkey = "repeatHotkey";
const QString qs_clipboardHotkey = "clipboardHotkey";
const QString qs_resultShowType = "resultShowType";
const QString qs_proxyType = "proxyType";
const QString qs_proxyHostName = "proxyHostName";
const QString qs_proxyPort = "proxyPort";
const QString qs_proxyUser = "proxyUser";
const QString qs_proxyPassword = "proxyPassword";
const QString qs_proxySavePassword = "proxySavePassword";
const QString qs_autoUpdateType = "autoUpdateType";
const QString qs_lastUpdateCheck = "lastUpdateCheck";
const QString qs_recogntionGroup = "Recognition";
const QString qs_tessDataPlace = "tessdata_dir";
const QString qs_ocrLanguage = "language";
const QString qs_imageScale = "image_scale";
const QString qs_translationGroup = "Translation";
const QString qs_doTranslation = "doTranslation";
const QString qs_ignoreSslErrors = "ignoreSslErrors";
const QString qs_forceRotateTranslators = "forceRotateTranslators";
const QString qs_sourceLanguage = "source_language";
const QString qs_translationLanguage = "translation_language";
const QString qs_translationTimeout = "translation_timeout";
const QString qs_debugMode = "translation_debug";
const QString qs_translators = "translators";
QString shuffle(const QString &source)
{
if (source.isEmpty()) {
return source;
}
char encKeys[] = {14, 26, 99, 43};
std::string result = source.toStdString();
for (size_t i = 0, end = result.size(); i < end; ++i) {
result[i] = result[i] ^ encKeys[i % sizeof(encKeys)];
}
return QString::fromUtf8(result.data());
}
} // namespace
void Settings::save()
{
QSettings settings;
settings.beginGroup(qs_guiGroup);
settings.setValue(qs_captureHotkey, captureHotkey);
settings.setValue(qs_repeatCaptureHotkey, repeatCaptureHotkey);
settings.setValue(qs_repeatHotkey, showLastHotkey);
settings.setValue(qs_clipboardHotkey, clipboardHotkey);
settings.setValue(qs_resultShowType, int(resultShowType));
settings.setValue(qs_proxyType, proxyType);
settings.setValue(qs_proxyHostName, proxyHostName);
settings.setValue(qs_proxyPort, proxyPort);
settings.setValue(qs_proxyUser, proxyUser);
settings.setValue(qs_proxySavePassword, proxySavePassword);
if (proxySavePassword) {
settings.setValue(qs_proxyPassword, shuffle(proxyPassword));
} else {
settings.remove(qs_proxyPassword);
}
settings.setValue(qs_autoUpdateType, autoUpdateType);
settings.endGroup();
settings.beginGroup(qs_recogntionGroup);
settings.setValue(qs_tessDataPlace, tessdataPath);
settings.setValue(qs_ocrLanguage, sourceLanguage);
// TODO corrector
settings.endGroup();
settings.beginGroup(qs_translationGroup);
settings.setValue(qs_doTranslation, doTranslation);
settings.setValue(qs_ignoreSslErrors, ignoreSslErrors);
settings.setValue(qs_debugMode, debugMode);
settings.setValue(qs_translationLanguage, targetLanguage);
settings.setValue(qs_translationTimeout, int(translationTimeout.count()));
settings.setValue(qs_translators, translators);
settings.endGroup();
}
void Settings::load()
{
QSettings settings;
settings.beginGroup(qs_guiGroup);
captureHotkey = settings.value(qs_captureHotkey, captureHotkey).toString();
repeatCaptureHotkey =
settings.value(qs_repeatCaptureHotkey, repeatCaptureHotkey).toString();
showLastHotkey = settings.value(qs_repeatHotkey, showLastHotkey).toString();
clipboardHotkey =
settings.value(qs_clipboardHotkey, clipboardHotkey).toString();
resultShowType = ResultMode(std::clamp(
settings.value(qs_resultShowType, int(resultShowType)).toInt(), 0, 1));
proxyType = settings.value(qs_proxyType, proxyType).toInt();
proxyHostName = settings.value(qs_proxyHostName, proxyHostName).toString();
proxyPort = settings.value(qs_proxyPort, proxyPort).toInt();
proxyUser = settings.value(qs_proxyUser, proxyUser).toString();
proxySavePassword =
settings.value(qs_proxySavePassword, proxySavePassword).toBool();
proxyPassword = shuffle(settings.value(qs_proxyPassword).toString());
autoUpdateType = settings.value(qs_autoUpdateType, autoUpdateType).toInt();
settings.endGroup();
settings.beginGroup(qs_recogntionGroup);
tessdataPath = settings.value(qs_tessDataPlace, tessdataPath).toString();
sourceLanguage = settings.value(qs_ocrLanguage, sourceLanguage).toString();
settings.endGroup();
settings.beginGroup(qs_translationGroup);
doTranslation = settings.value(qs_doTranslation, doTranslation).toBool();
ignoreSslErrors =
settings.value(qs_ignoreSslErrors, ignoreSslErrors).toBool();
debugMode = settings.value(qs_debugMode, debugMode).toBool();
targetLanguage =
settings.value(qs_translationLanguage, targetLanguage).toString();
translationTimeout = std::chrono::seconds(
settings.value(qs_translationTimeout, int(translationTimeout.count()))
.toInt());
translators = settings.value(qs_translators, translators).toStringList();
if (translators.size() == 1 && translators.first().contains('|')) // legacy
translators = translators.first().split('|');
settings.endGroup();
}

View File

@ -1,81 +1,54 @@
#ifndef SETTINGS_H
#define SETTINGS_H
#pragma once
#include <QString>
#include "stfwd.h"
namespace settings_names {
//! UI
const QString guiGroup = "GUI";
const QString geometry = "geometry";
const QString captureHotkey = "captureHotkey";
const QString repeatCaptureHotkey = "repeatCaptureHotkey";
const QString repeatHotkey = "repeatHotkey";
const QString clipboardHotkey = "clipboardHotkey";
const QString resultShowType = "resultShowType";
const QString proxyType = "proxyType";
const QString proxyHostName = "proxyHostName";
const QString proxyPort = "proxyPort";
const QString proxyUser = "proxyUser";
const QString proxyPassword = "proxyPassword";
const QString proxySavePassword = "proxySavePassword";
const QString autoUpdateType = "autoUpdateType";
const QString lastUpdateCheck = "lastUpdateCheck";
#include <QStringList>
//! Recognition
const QString recogntionGroup = "Recognition";
const QString tessDataPlace = "tessdata_dir";
const QString ocrLanguage = "language";
const QString imageScale = "image_scale";
#include <chrono>
//! Translation
const QString translationGroup = "Translation";
const QString doTranslation = "doTranslation";
const QString ignoreSslErrors = "ignoreSslErrors";
const QString forceRotateTranslators = "forceRotateTranslators";
const QString sourceLanguage = "source_language";
const QString translationLanguage = "translation_language";
const QString translationTimeout = "translation_timeout";
const QString translationDebugMode = "translation_debug";
const QString translators = "translators";
}
enum class ResultMode { Widget, Tooltip };
namespace settings_values {
const QString appName = "ScreenTranslator";
const QString companyName = "Gres";
struct Substitution {
QString source;
QString target;
};
using Substitutions = std::unordered_multimap<LanguageId, Substitution>;
//! UI
const QString captureHotkey = "Ctrl+Alt+Z";
const QString repeatCaptureHotkey = "Ctrl+Alt+S";
const QString repeatHotkey = "Ctrl+Alt+X";
const QString clipboardHotkey = "Ctrl+Alt+C";
const QString resultShowType = "1";//dialog
const int proxyType = 0;
const QString proxyHostName = "";
const int proxyPort = 8080;
const QString proxyUser = "";
const QString proxyPassword = "";
const bool proxySavePassword = false;
const int autoUpdateType = 0; //Never
const QString lastUpdateCheck = "";
class Settings
{
public:
void save();
void load();
//! Recognition
#if defined(Q_OS_LINUX)
const QString tessDataPlace = "/usr/share/tesseract-ocr/";
#else
const QString tessDataPlace = "./";
#endif
const QString ocrLanguage = "eng";
const int imageScale = 5;
QString captureHotkey{"Ctrl+Alt+Z"};
QString repeatCaptureHotkey{"Ctrl+Alt+S"};
QString showLastHotkey{"Ctrl+Alt+X"};
QString clipboardHotkey{"Ctrl+Alt+C"};
//! Translation
const bool doTranslation = true;
const bool ignoreSslErrors = false;
const bool forceRotateTranslators = false;
const QString sourceLanguage = "auto";
const QString translationLanguage = "ru";
const int translationTimeout = 15; // secs
const bool translationDebugMode = false;
const QString translators = "";
}
int proxyType{0};
QString proxyHostName{""};
int proxyPort{8080};
QString proxyUser{""};
QString proxyPassword{""};
bool proxySavePassword{false};
#endif // SETTINGS_H
int autoUpdateType{0}; // Never
QString lastUpdateCheck{""};
Substitutions userSubstitutions;
bool debugMode{false};
QString tessdataPath{"tessdata"};
QString sourceLanguage{"eng"};
LanguageIds availableOcrLanguages_;
bool doTranslation{true};
bool ignoreSslErrors{false};
bool forceRotateTranslators{false};
LanguageId targetLanguage{"rus"};
std::chrono::seconds translationTimeout{15};
QString translatorsDir{"translators"};
QStringList translators{"google.js"};
ResultMode resultShowType{ResultMode::Widget}; // dialog
};

View File

@ -1,288 +1,204 @@
#include "settingseditor.h"
#include "ui_settingseditor.h"
#include "languagehelper.h"
#include "translatorhelper.h"
#include "recognizerhelper.h"
#include "stassert.h"
#include "utils.h"
#include <QSettings>
#include <QFileDialog>
#include <QDir>
#include <QRegExpValidator>
#include <QNetworkProxy>
#include "languagecodes.h"
#include "settings.h"
#include "ui_settingseditor.h"
#include "widgetstate.h"
SettingsEditor::SettingsEditor (const LanguageHelper &dictionary, QWidget *parent) :
QDialog (parent),
ui (new Ui::SettingsEditor), translatorHelper_ (new TranslatorHelper),
recognizerHelper_ (new RecognizerHelper), dictionary_ (dictionary),
buttonGroup_ (new QButtonGroup (this)) {
#include <QDir>
#include <QFileDialog>
#include <QNetworkProxy>
#include <QRegExpValidator>
#include <QSettings>
#include <QStringListModel>
SettingsEditor::SettingsEditor()
: ui(new Ui::SettingsEditor)
{
ui->setupUi(this);
buttonGroup_->addButton (ui->trayRadio, 0);
buttonGroup_->addButton (ui->dialogRadio, 1);
connect (ui->updateButton, SIGNAL (clicked (bool)), SIGNAL (updateCheckRequested ()));
QStringList updateTypes = QStringList () << tr ("Никогда") << tr ("Ежедневно")
<< tr ("Еженедельно") << tr ("Ежемесячно");
ui->updateCombo->addItems (updateTypes);
connect (ui->tessdataButton, SIGNAL (clicked ()), SLOT (openTessdataDialog ()));
connect (ui->tessdataEdit, SIGNAL (textChanged (const QString&)),
SLOT (initOcrLangCombo (const QString&)));
connect (ui->recognizerFixTable, SIGNAL (itemChanged (QTableWidgetItem*)),
SLOT (recognizerFixTableItemChanged (QTableWidgetItem*)));
ui->translateLangCombo->addItems (dictionary_.translateLanguagesUi ());
typedef QNetworkProxy::ProxyType ProxyType;
QMap<ProxyType, QString> proxyTypeNames;
proxyTypeNames.insert (QNetworkProxy::NoProxy, tr ("Нет"));
proxyTypeNames.insert (QNetworkProxy::Socks5Proxy, tr ("SOCKS 5"));
proxyTypeNames.insert (QNetworkProxy::HttpProxy, tr ("HTTP"));
QList<int> proxyOrder = proxyTypeOrder ();
for (int type: proxyOrder) {
ui->proxyTypeCombo->addItem (proxyTypeNames.value (QNetworkProxy::ProxyType (type)));
{
auto model = new QStringListModel(this);
model->setStringList({tr("General"), tr("Recognition"), tr("Correction"),
tr("Translation"), tr("Representation"),
tr("Update")});
ui->pagesList->setModel(model);
auto selection = ui->pagesList->selectionModel();
connect(selection, &QItemSelectionModel::currentRowChanged, //
this, &SettingsEditor::updateCurrentPage);
}
QRegExp urlRegexp (R"(^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$)");
ui->proxyHostEdit->setValidator (new QRegExpValidator (urlRegexp, ui->proxyHostEdit));
ui->proxyPassEdit->setEchoMode (QLineEdit::PasswordEchoOnEdit);
// general
// QMap<QNetworkProxy::ProxyType, QString> proxyTypeNames;
// proxyTypeNames.insert(QNetworkProxy::NoProxy, tr("No"));
// proxyTypeNames.insert(QNetworkProxy::DefaultProxy, tr("System"));
// proxyTypeNames.insert(QNetworkProxy::Socks5Proxy, tr("SOCKS 5"));
// proxyTypeNames.insert(QNetworkProxy::HttpProxy, tr("HTTP"));
// QList<int> proxyOrder = proxyTypeOrder();
// for (int type : proxyOrder) {
// ui->proxyTypeCombo->addItem(
// proxyTypeNames.value(QNetworkProxy::ProxyType(type)));
// }
loadSettings ();
loadState ();
// QRegExp urlRegexp(
// R"(^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$)");
// ui->proxyHostEdit->setValidator(
// new QRegExpValidator(urlRegexp, ui->proxyHostEdit));
// ui->proxyPassEdit->setEchoMode(QLineEdit::PasswordEchoOnEdit);
// recognition
connect(ui->tessdataButton, &QPushButton::clicked, //
this, &SettingsEditor::openTessdataDialog);
connect(ui->tessdataEdit, &QLineEdit::textChanged, //
this, &SettingsEditor::updateTesseractLanguages);
// connect(ui->recognizerFixTable, SIGNAL(itemChanged(QTableWidgetItem *)),
// SLOT(recognizerFixTableItemChanged(QTableWidgetItem *)));
// // ui->translateLangCombo->addItems(dictionary_.translateLanguagesUi());
// translation
updateTranslationLanguages();
// updates
ui->updateCombo->addItems(
{tr("Never"), tr("Daily"), tr("Weekly"), tr("Monthly")});
new WidgetState(this);
}
SettingsEditor::~SettingsEditor () {
saveState ();
delete recognizerHelper_;
delete translatorHelper_;
SettingsEditor::~SettingsEditor()
{
delete ui;
}
void SettingsEditor::done (int result) {
if (result == QDialog::Accepted) {
saveSettings ();
emit settingsEdited ();
}
QDialog::done (result);
Settings SettingsEditor::settings() const
{
Settings settings;
settings.captureHotkey = ui->captureEdit->keySequence().toString();
settings.repeatCaptureHotkey =
ui->repeatCaptureEdit->keySequence().toString();
settings.showLastHotkey = ui->repeatEdit->keySequence().toString();
settings.clipboardHotkey = ui->clipboardEdit->keySequence().toString();
settings.tessdataPath = ui->tessdataEdit->text();
settings.doTranslation = ui->doTranslationCheck->isChecked();
settings.ignoreSslErrors = ui->ignoreSslCheck->isChecked();
settings.debugMode = ui->translatorDebugCheck->isChecked();
settings.translationTimeout =
std::chrono::seconds(ui->translateTimeoutSpin->value());
settings.translators.clear();
for (auto i = 0, end = ui->translatorList->count(); i < end; ++i) {
auto item = ui->translatorList->item(i);
if (item->checkState() == Qt::Checked)
settings.translators.append(item->text());
}
void SettingsEditor::saveSettings () const {
using namespace settings_names;
QSettings settings;
settings.beginGroup (guiGroup);
settings.setValue (captureHotkey, ui->captureEdit->keySequence ().toString ());
settings.setValue (repeatCaptureHotkey, ui->repeatCaptureEdit->keySequence ().toString ());
settings.setValue (repeatHotkey, ui->repeatEdit->keySequence ().toString ());
settings.setValue (clipboardHotkey, ui->clipboardEdit->keySequence ().toString ());
settings.setValue (resultShowType, buttonGroup_->checkedId ());
settings.setValue (proxyType, ui->proxyTypeCombo->currentIndex ());
settings.setValue (proxyHostName, ui->proxyHostEdit->text ());
settings.setValue (proxyPort, ui->proxyPortSpin->value ());
settings.setValue (proxyUser, ui->proxyUserEdit->text ());
if (ui->proxySaveCheck->isChecked ()) {
settings.setValue (proxyPassword, encode (ui->proxyPassEdit->text ()));
}
else {
settings.remove (proxyPassword);
QNetworkProxy proxy = QNetworkProxy::applicationProxy ();
proxy.setPassword (ui->proxyPassEdit->text ());
QNetworkProxy::setApplicationProxy (proxy);
}
settings.setValue (proxySavePassword, ui->proxySaveCheck->isChecked ());
settings.setValue (autoUpdateType, ui->updateCombo->currentIndex ());
settings.endGroup ();
settings.beginGroup (recogntionGroup);
settings.setValue (tessDataPlace, ui->tessdataEdit->text ());
QString ocrLanguageVal = dictionary_.ocrUiToCode (ui->ocrLangCombo->currentText ());
settings.setValue (ocrLanguage, ocrLanguageVal);
settings.setValue (imageScale, ui->imageScaleSpin->value ());
{ //Recognizer substitutions
RecognizerHelper::Subs subs;
QTableWidget *t = ui->recognizerFixTable; // Shortcut
for (int i = 0, end = t->rowCount () - 1; i < end; ++i) {
QComboBox *combo = static_cast<QComboBox *>(t->cellWidget (i, SubsColLanguage));
QString langUi = combo->currentText ();
RecognizerHelper::Sub sub;
sub.language = dictionary_.ocrUiToCode (langUi);
#define GET(COL) (t->item (i, COL) ? t->item (i, COL)->text () : QString ())
sub.source = GET (SubsColSource);
sub.target = GET (SubsColTarget);
#undef GET
if (langUi.isEmpty () || sub.language == langUi || sub.source.isEmpty ()) {
continue;
}
subs.append (sub);
}
recognizerHelper_->setSubs (subs);
recognizerHelper_->save ();
settings.resultShowType =
ui->trayRadio->isChecked() ? ResultMode::Tooltip : ResultMode::Widget;
return settings;
}
settings.endGroup ();
void SettingsEditor::setSettings(const Settings &settings)
{
ui->captureEdit->setKeySequence(settings.captureHotkey);
ui->repeatCaptureEdit->setKeySequence(settings.repeatCaptureHotkey);
ui->repeatEdit->setKeySequence(settings.showLastHotkey);
ui->clipboardEdit->setKeySequence(settings.clipboardHotkey);
settings.beginGroup (translationGroup);
settings.setValue (doTranslation, ui->doTranslationCheck->isChecked ());
settings.setValue (ignoreSslErrors, ui->ignoreSslCheck->isChecked ());
settings.setValue (forceRotateTranslators, ui->forceRotateCheck->isChecked ());
settings.setValue (translationDebugMode, ui->translatorDebugCheck->isChecked ());
QString trLanguage = dictionary_.translateUiToCode (ui->translateLangCombo->currentText ());
settings.setValue (translationLanguage, trLanguage);
QString sourceLanguageVal = dictionary_.ocrToTranslateCodes (ocrLanguage);
settings.setValue (sourceLanguage, sourceLanguageVal);
settings.setValue (translationTimeout, ui->translateTimeoutSpin->value ());
ui->tessdataEdit->setText(settings.tessdataPath);
updateTesseractLanguages();
{//Translators
QStringList enabled;
for (int i = 0, end = ui->translatorList->count (); i < end; ++i) {
QListWidgetItem *item = ui->translatorList->item (i);
if (item->checkState () == Qt::Checked) {
enabled << item->text ();
}
}
translatorHelper_->setEnabledTranslators (enabled);
ui->doTranslationCheck->setChecked(settings.doTranslation);
ui->ignoreSslCheck->setChecked(settings.ignoreSslErrors);
ui->translatorDebugCheck->setChecked(settings.debugMode);
ui->translateTimeoutSpin->setValue(settings.translationTimeout.count());
translatorsDir_ = settings.translatorsDir;
updateTranslators(settings.translators);
ui->trayRadio->setChecked(settings.resultShowType == ResultMode::Tooltip);
ui->dialogRadio->setChecked(settings.resultShowType == ResultMode::Widget);
}
settings.endGroup ();
void SettingsEditor::updateCurrentPage()
{
ui->pagesView->setCurrentIndex(ui->pagesList->currentIndex().row());
}
void SettingsEditor::openTessdataDialog () {
QString path = QFileDialog::getExistingDirectory (this, tr ("Path to tessdata"));
if (path.isEmpty ()) {
void SettingsEditor::openTessdataDialog()
{
const auto path =
QFileDialog::getExistingDirectory(this, tr("Path to tessdata"));
if (path.isEmpty())
return;
}
ui->tessdataEdit->setText(path);
}
void SettingsEditor::loadSettings () {
#define GET(FIELD) settings.value (settings_names::FIELD, settings_values::FIELD)
QSettings settings;
void SettingsEditor::updateTesseractLanguages()
{
ui->tesseractLangCombo->clear();
ui->correctLangCombo->clear();
settings.beginGroup (settings_names::guiGroup);
ui->captureEdit->setKeySequence (QKeySequence (GET (captureHotkey).toString ()));
ui->repeatCaptureEdit->setKeySequence (QKeySequence (GET (repeatCaptureHotkey).toString ()));
ui->repeatEdit->setKeySequence (QKeySequence (GET (repeatHotkey).toString ()));
ui->clipboardEdit->setKeySequence (QKeySequence (GET (clipboardHotkey).toString ()));
QAbstractButton *button = buttonGroup_->button (GET (resultShowType).toInt ());
Q_CHECK_PTR (button);
button->setChecked (true);
ui->proxyTypeCombo->setCurrentIndex (GET (proxyType).toInt ());
ui->proxyHostEdit->setText (GET (proxyHostName).toString ());
ui->proxyPortSpin->setValue (GET (proxyPort).toInt ());
ui->proxyUserEdit->setText (GET (proxyUser).toString ());
ui->proxySaveCheck->setChecked (GET (proxySavePassword).toBool ());
if (ui->proxySaveCheck->isChecked ()) {
ui->proxyPassEdit->setText (encode (GET (proxyPassword).toString ()));
}
else {
ui->proxyPassEdit->setText (QNetworkProxy::applicationProxy ().password ());
}
ui->updateCombo->setCurrentIndex (GET (autoUpdateType).toInt ());
settings.endGroup ();
QDir dir(ui->tessdataEdit->text());
if (!dir.exists())
return;
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 ());
LanguageIds names;
LanguageCodes languages;
{//Recognizer substitutions
recognizerHelper_->load ();
RecognizerHelper::Subs subs = recognizerHelper_->subs ();
ui->recognizerFixTable->setRowCount (subs.size ());
int row = 0;
for (const RecognizerHelper::Sub &sub: subs) {
if (!initSubsTableRow (row, sub.language)) {
continue;
}
ui->recognizerFixTable->setItem (row, SubsColSource, new QTableWidgetItem (sub.source));
ui->recognizerFixTable->setItem (row, SubsColTarget, new QTableWidgetItem (sub.target));
++row;
}
ui->recognizerFixTable->setRowCount (row + 1);
initSubsTableRow (row);
ui->recognizerFixTable->resizeColumnsToContents ();
const auto files = dir.entryList({"*.traineddata"}, QDir::Files);
for (const auto &file : files) {
const auto lang = file.left(file.indexOf("."));
if (const auto bundle = languages.findByTesseract(lang))
names.append(bundle->name);
}
settings.endGroup ();
if (names.isEmpty())
return;
std::sort(names.begin(), names.end());
ui->tesseractLangCombo->addItems(names);
ui->correctLangCombo->addItems(names);
}
settings.beginGroup (settings_names::translationGroup);
ui->doTranslationCheck->setChecked (GET (doTranslation).toBool ());
ui->ignoreSslCheck->setChecked (GET (ignoreSslErrors).toBool ());
ui->forceRotateCheck->setChecked (GET (forceRotateTranslators).toBool ());
ui->translatorDebugCheck->setChecked (GET (translationDebugMode).toBool ());
QString trLanguage = dictionary_.translateCodeToUi (GET (translationLanguage).toString ());
ui->translateLangCombo->setCurrentText (trLanguage);
ui->translateTimeoutSpin->setValue (GET (translationTimeout).toInt ());
void SettingsEditor::updateCorrectionsTable()
{
}
{// Translators
QStringList enabled;
ui->translatorList->addItems (translatorHelper_->possibleTranslators (enabled));
for (int i = 0, end = ui->translatorList->count (); i < end; ++i) {
QListWidgetItem *item = ui->translatorList->item (i);
Qt::CheckState state = enabled.contains (item->text ()) ? Qt::Checked : Qt::Unchecked;
item->setCheckState (state);
void SettingsEditor::updateTranslators(const QStringList &enabled)
{
ui->translatorList->clear();
QDir dir(translatorsDir_);
if (!dir.exists())
return;
auto files = dir.entryList({"*.js"}, QDir::Files);
std::sort(files.begin(), files.end());
ui->translatorList->addItems(files);
for (auto i = 0, end = ui->translatorList->count(); i < end; ++i) {
auto item = ui->translatorList->item(i);
item->setCheckState(enabled.contains(item->text()) ? Qt::Checked
: Qt::Unchecked);
}
}
settings.endGroup ();
#undef GET
void SettingsEditor::updateTranslationLanguages()
{
LanguageIds names;
LanguageCodes languages;
for (const auto &bundle : languages.all()) {
if (!bundle.second.iso639_1.isEmpty())
names.append(bundle.second.name);
}
bool SettingsEditor::initSubsTableRow (int row, const QString &languageCode) {
QString lang = dictionary_.ocrCodeToUi (languageCode);
if (!languageCode.isEmpty () && lang == languageCode) {
return false;
}
QComboBox *langCombo = new QComboBox (ui->recognizerFixTable);
langCombo->setModel (ui->ocrLangCombo->model ());
if (!languageCode.isEmpty ()) {
langCombo->setCurrentText (lang);
}
else {
langCombo->setCurrentIndex (ui->ocrLangCombo->currentIndex ());
}
ui->recognizerFixTable->setCellWidget (row, SubsColLanguage, langCombo);
return true;
}
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));
}
void SettingsEditor::recognizerFixTableItemChanged (QTableWidgetItem *item) {
ST_ASSERT (item->column () < 3);
int row = item->row ();
QTableWidget *t = ui->recognizerFixTable;
#define CHECK(COL) (!t->item (row, COL) || t->item (row, COL)->text ().isEmpty ())
bool isRowEmpty = CHECK (SubsColSource) && CHECK (SubsColTarget);
#undef CHECK
int lastRow = ui->recognizerFixTable->rowCount () - 1;
if (isRowEmpty && row != lastRow) {
ui->recognizerFixTable->removeRow (row);
}
else if (!isRowEmpty && row == lastRow) {
int newRow = lastRow + 1;
ui->recognizerFixTable->insertRow (newRow);
initSubsTableRow (newRow);
}
ui->translateLangCombo->clear();
std::sort(names.begin(), names.end());
ui->translateLangCombo->addItems(names);
}

View File

@ -1,54 +1,33 @@
#ifndef SETTINGSEDITOR_H
#define SETTINGSEDITOR_H
#pragma once
#include <QDialog>
#include <QButtonGroup>
#include <QMap>
class QTableWidgetItem;
namespace Ui {
#include "settings.h"
namespace Ui
{
class SettingsEditor;
}
class LanguageHelper;
class TranslatorHelper;
class RecognizerHelper;
class SettingsEditor : public QDialog {
class SettingsEditor : public QDialog
{
Q_OBJECT
enum SubsCol {
SubsColLanguage = 0, SubsColSource, SubsColTarget
};
public:
explicit SettingsEditor (const LanguageHelper &dictionary, QWidget *parent = 0);
explicit SettingsEditor();
~SettingsEditor();
signals:
void settingsEdited ();
void updateCheckRequested ();
Settings settings() const;
void setSettings(const Settings &settings);
public slots:
void done (int result);
private slots:
void saveSettings () const;
private:
void updateCurrentPage();
void openTessdataDialog();
void initOcrLangCombo (const QString &path);
void recognizerFixTableItemChanged (QTableWidgetItem *item);
void updateTesseractLanguages();
void updateCorrectionsTable();
void updateTranslators(const QStringList &enabled);
void updateTranslationLanguages();
private:
void loadSettings ();
void saveState () const;
void loadState ();
bool initSubsTableRow (int row, const QString &languageCode = QString ());
private:
Ui::SettingsEditor *ui;
TranslatorHelper *translatorHelper_;
RecognizerHelper *recognizerHelper_;
const LanguageHelper &dictionary_;
QButtonGroup *buttonGroup_;
QString translatorsDir_;
};
#endif // SETTINGSEDITOR_H

View File

@ -6,15 +6,25 @@
<rect>
<x>0</x>
<y>0</y>
<width>553</width>
<height>483</height>
<width>889</width>
<height>549</height>
</rect>
</property>
<property name="windowTitle">
<string>Настройки</string>
</property>
<layout class="QGridLayout" name="gridLayout_6">
<item row="1" column="0" colspan="2">
<item row="0" column="0">
<widget class="QListView" name="pagesList">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="1" column="0" colspan="4">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
@ -24,20 +34,23 @@
</property>
</widget>
</item>
<item row="0" column="0" colspan="2">
<widget class="QTabWidget" name="tabWidget">
<item row="0" column="1" colspan="3">
<widget class="QStackedWidget" name="pagesView">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="commonTab">
<attribute name="title">
<string>Общее</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_8">
<item row="0" column="0">
<widget class="QWidget" name="pageGeneral">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Горячие клавиши</string>
<string>Shortcuts</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
@ -46,7 +59,7 @@
<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>
<string>Capture</string>
</property>
</widget>
</item>
@ -59,7 +72,7 @@
<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>
<string>Repeat capture</string>
</property>
</widget>
</item>
@ -72,7 +85,7 @@
<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>
<string>Show last result</string>
</property>
</widget>
</item>
@ -85,7 +98,7 @@
<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>
<string>Copy result to clipboard</string>
</property>
</widget>
</item>
@ -95,16 +108,16 @@
</layout>
</widget>
</item>
<item row="1" column="0">
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Прокси</string>
<string>Proxy</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<widget class="QLabel" name="label_12">
<property name="text">
<string>Тип:</string>
<string>Type:</string>
</property>
</widget>
</item>
@ -114,7 +127,7 @@
<item row="0" column="2">
<widget class="QLabel" name="label_15">
<property name="text">
<string>Пользователь:</string>
<string>User:</string>
</property>
</widget>
</item>
@ -124,7 +137,7 @@
<item row="1" column="0">
<widget class="QLabel" name="label_13">
<property name="text">
<string>Адрес:</string>
<string>Address:</string>
</property>
</widget>
</item>
@ -134,7 +147,7 @@
<item row="1" column="2">
<widget class="QLabel" name="label_16">
<property name="text">
<string>Пароль:</string>
<string>Password:</string>
</property>
</widget>
</item>
@ -144,7 +157,7 @@
<item row="2" column="0">
<widget class="QLabel" name="label_14">
<property name="text">
<string>Порт:</string>
<string>Port:</string>
</property>
</widget>
</item>
@ -161,53 +174,7 @@
<item row="2" column="2" colspan="2">
<widget class="QCheckBox" name="proxySaveCheck">
<property name="text">
<string>Сохранять пароль (небезопасно)</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="2" column="0">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QGroupBox" name="resultGroup">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Вывод результата</string>
</property>
<layout class="QGridLayout" name="gridLayout_4">
<item row="0" column="0">
<widget class="QRadioButton" name="trayRadio">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Трей</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QRadioButton" name="dialogRadio">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Окно</string>
</property>
<property name="checked">
<bool>true</bool>
<string>save password (unsafe)</string>
</property>
</widget>
</item>
@ -215,59 +182,55 @@
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_3">
<property name="title">
<string>Обновление</string>
</property>
<layout class="QGridLayout" name="gridLayout_5">
<item row="0" column="0">
<widget class="QLabel" name="label_17">
<property name="text">
<string>Проверять обновления:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="updateCombo"/>
</item>
<item row="0" column="2">
<widget class="QPushButton" name="updateButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Проверить</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item row="3" column="0">
<spacer name="verticalSpacer">
<spacer name="verticalSpacer_4">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>270</height>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="recognizeTab">
<attribute name="title">
<string>Распознавание</string>
</attribute>
<widget class="QWidget" name="pageRecognize">
<layout class="QGridLayout" name="gridLayout_2">
<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>Language</string>
</property>
<property name="buddy">
<cstring>tesseractLangCombo</cstring>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QToolButton" name="tessdataButton">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="2" column="1">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
@ -278,66 +241,50 @@
</property>
</widget>
</item>
<item row="0" column="1">
<item row="0" column="1" colspan="2">
<widget class="QLineEdit" name="tessdataEdit">
<property name="placeholderText">
<string>Enter path</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QToolButton" name="tessdataButton">
<property name="text">
<string>...</string>
</property>
</widget>
<item row="1" column="1" colspan="3">
<widget class="QComboBox" name="tesseractLangCombo"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_4">
</layout>
</widget>
<widget class="QWidget" name="pageCorrect">
<layout class="QGridLayout" name="gridLayout_10">
<item row="0" column="0">
<widget class="QLabel" name="label_18">
<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>
<string>Language:</string>
</property>
<property name="buddy">
<cstring>ocrLangCombo</cstring>
<cstring>tesseractLangCombo</cstring>
</property>
</widget>
</item>
<item row="1" column="1" colspan="2">
<widget class="QComboBox" name="ocrLangCombo"/>
<item row="0" column="1">
<widget class="QComboBox" name="correctLangCombo"/>
</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;p&gt;Рекомендуемые значения от 5 до 10.&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="2" column="1" colspan="2">
<widget class="QSpinBox" name="imageScaleSpin"/>
</item>
<item row="3" column="0" colspan="3">
<item row="1" column="0" colspan="2">
<widget class="QLabel" name="label_11">
<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>
<string>Corrections</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="4" column="0" colspan="3">
<item row="2" column="0" colspan="2">
<widget class="QTableWidget" name="recognizerFixTable">
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
@ -345,59 +292,45 @@
<property name="sortingEnabled">
<bool>true</bool>
</property>
<column>
<property name="text">
<string>Язык</string>
</property>
</column>
<column>
<property name="text">
<string>Исходный текст</string>
</property>
</column>
<column>
<property name="text">
<string>Исправление</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="translateTab">
<attribute name="title">
<string>Перевод</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_7">
<widget class="QWidget" name="pageTranslate">
<layout class="QGridLayout" name="gridLayout_9">
<item row="0" column="0">
<widget class="QCheckBox" name="doTranslationCheck">
<widget class="QCheckBox" name="translatorDebugCheck">
<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>
<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>
<string>Debug mode</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_9">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Максимальное время, которое может быть затрачено на перевод, чтобы он не считался &amp;quot;зависшим&amp;quot;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Максимальное время перевода:</string>
</property>
</widget>
</item>
<item row="3" column="1" colspan="2">
<item row="3" column="2">
<widget class="QSpinBox" name="translateTimeoutSpin">
<property name="suffix">
<string> сек.</string>
<string> secs</string>
</property>
</widget>
</item>
<item row="4" column="1" colspan="2">
<widget class="QComboBox" name="translateLangCombo"/>
<item row="2" column="0" colspan="2">
<widget class="QCheckBox" name="ignoreSslCheck">
<property name="text">
<string>Ignore SSL errors</string>
</property>
</widget>
</item>
<item row="5" column="0" colspan="3">
<widget class="QLabel" name="label_10">
<property name="text">
<string>Translators</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_6">
@ -405,23 +338,13 @@
<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>
<string>Language:</string>
</property>
<property name="buddy">
<cstring>translateLangCombo</cstring>
</property>
</widget>
</item>
<item row="5" column="0" colspan="3">
<widget class="QLabel" name="label_10">
<property name="text">
<string>Переводчики:</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="6" column="0" colspan="3">
<widget class="QListWidget" name="translatorList">
<property name="toolTip">
@ -438,60 +361,166 @@
</property>
</widget>
</item>
<item row="0" column="1" colspan="2">
<widget class="QCheckBox" name="translatorDebugCheck">
<item row="1" column="0" colspan="2">
<widget class="QCheckBox" name="doTranslationCheck">
<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>
<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>
<string>Translate text</string>
</property>
</widget>
</item>
<item row="4" column="1" colspan="2">
<widget class="QComboBox" name="translateLangCombo"/>
</item>
<item row="3" column="0" colspan="2">
<widget class="QLabel" name="label_9">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Максимальное время, которое может быть затрачено на перевод, чтобы он не считался &amp;quot;зависшим&amp;quot;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Single translator timeout:</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="pageRepresent">
<layout class="QGridLayout" name="gridLayout_7">
<item row="0" column="0">
<widget class="QGroupBox" name="resultGroup">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Result type</string>
</property>
<layout class="QGridLayout" name="gridLayout_4">
<item row="0" column="0">
<widget class="QRadioButton" name="trayRadio">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Tray</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QRadioButton" name="dialogRadio">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Window</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="ignoreSslCheck">
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="1">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="pageUpdate">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QWidget" name="widget_3" native="true">
<layout class="QGridLayout" name="gridLayout_5">
<item row="0" column="0">
<widget class="QLabel" name="label_17">
<property name="text">
<string>Игнорировать ошибки SSL</string>
<string>Check for updates:</string>
</property>
</widget>
</item>
<item row="1" column="1" colspan="2">
<widget class="QCheckBox" name="forceRotateCheck">
<item row="0" column="1">
<widget class="QComboBox" name="updateCombo"/>
</item>
<item row="0" column="2">
<widget class="QPushButton" name="updateButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Принудительно менять переводчики</string>
<string>Check now</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>389</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<tabstops>
<tabstop>tabWidget</tabstop>
<tabstop>captureEdit</tabstop>
<tabstop>repeatCaptureEdit</tabstop>
<tabstop>repeatEdit</tabstop>
<tabstop>clipboardEdit</tabstop>
<tabstop>trayRadio</tabstop>
<tabstop>dialogRadio</tabstop>
<tabstop>proxyTypeCombo</tabstop>
<tabstop>proxyHostEdit</tabstop>
<tabstop>proxyPortSpin</tabstop>
<tabstop>proxyUserEdit</tabstop>
<tabstop>proxyPassEdit</tabstop>
<tabstop>proxySaveCheck</tabstop>
<tabstop>tessdataEdit</tabstop>
<tabstop>tessdataButton</tabstop>
<tabstop>ocrLangCombo</tabstop>
<tabstop>imageScaleSpin</tabstop>
<tabstop>recognizerFixTable</tabstop>
<tabstop>doTranslationCheck</tabstop>
<tabstop>translatorDebugCheck</tabstop>
<tabstop>translateTimeoutSpin</tabstop>
<tabstop>translateLangCombo</tabstop>
<tabstop>translatorList</tabstop>
</tabstops>
<resources/>
<connections>

View File

@ -1,15 +0,0 @@
#ifndef ST_ASSERT_H
#define ST_ASSERT_H
#include <assert.h>
#if !defined(ST_ASSERT)
# if defined(ST_NO_ASSERT)
# define ST_ASSERT(CONDITION)
# else
# define ST_ASSERT(CONDITION) assert (CONDITION)
# endif
#endif
#endif // ST_ASSERT_H

21
src/stfwd.h Normal file
View File

@ -0,0 +1,21 @@
#pragma once
#include <memory>
class QString;
class QStringList;
class Manager;
class Settings;
class Task;
class Translator;
class TrayIcon;
class Capturer;
class Representer;
class Translator;
class Corrector;
class Recognizer;
using TaskPtr = std::shared_ptr<Task>;
using LanguageId = QString;
using LanguageIds = QStringList;

32
src/task.h Normal file
View File

@ -0,0 +1,32 @@
#pragma once
#include "stfwd.h"
#include <QPixmap>
class Task
{
public:
bool isNull() const { return captured.isNull() && !sourceLanguage.isEmpty(); }
bool isValid() const { return error.isEmpty(); }
// void trace(const QString& message);
QPoint capturePoint;
QPixmap captured;
QString recognized;
QString translated{"sample"};
LanguageId sourceLanguage{"eng"};
LanguageId targetLanguage; //{"ru"};
QStringList translators{"google.js"};
QString error;
// QStringList traceLog;
// bool swapLanguages;
};
using TaskPtr = std::shared_ptr<Task>;
Q_DECLARE_METATYPE(TaskPtr);

View File

@ -0,0 +1,210 @@
#include "translator.h"
#include "debug.h"
#include "manager.h"
#include "settings.h"
#include "task.h"
#include "webpage.h"
#include "webpageproxy.h"
#include "widgetstate.h"
#include <QBoxLayout>
#include <QCloseEvent>
#include <QFile>
#include <QSplitter>
#include <QTabWidget>
#include <QTcpSocket>
#include <QTextEdit>
#include <QWebChannel>
#include <QWebEngineView>
#include <unordered_set>
static std::map<QString, QString> loadScripts(const QString &dir,
const QStringList &scriptNames)
{
std::map<QString, QString> result;
for (const auto &name : scriptNames) {
QFile f(dir + QLatin1Char('/') + name);
if (!f.open(QFile::ReadOnly))
continue;
const auto script = QString::fromUtf8(f.readAll());
if (!script.isEmpty())
result.emplace(name, script);
}
return result;
}
Translator::Translator(Manager &manager)
: manager_(manager)
, view_(nullptr)
, tabs_(new QTabWidget(this))
{
#ifdef DEVELOP
{
QTcpSocket socket;
if (socket.bind()) {
quint16 port = socket.localPort();
LTRACE() << "debug port" << port;
qputenv("QTWEBENGINE_REMOTE_DEBUGGING", QString::number(port).toUtf8());
socket.close();
}
}
#endif
setObjectName("Translator");
view_ = new QWebEngineView(this);
auto splitter = new QSplitter(Qt::Vertical, this);
splitter->addWidget(view_);
splitter->addWidget(tabs_);
auto layout = new QVBoxLayout(this);
layout->addWidget(splitter);
startTimer(1000);
connect(tabs_, &QTabWidget::currentChanged, //
this, &Translator::changeCurrentPage);
view_->setMinimumSize(200, 200);
new WidgetState(this);
}
Translator::~Translator() = default;
void Translator::translate(const TaskPtr &task)
{
SOFT_ASSERT(task, return );
queue_.push_back(task);
processQueue();
}
void Translator::updateSettings(const Settings &settings)
{
view_->setPage(nullptr);
pages_.clear();
tabs_->blockSignals(true);
for (auto i = 0, end = tabs_->count(); i < end; ++i) {
auto tab = tabs_->widget(0);
tabs_->removeTab(0);
tab->deleteLater();
}
tabs_->blockSignals(false);
const auto loaded =
loadScripts(settings.translatorsDir, settings.translators);
if (loaded.empty()) {
manager_.fatalError(
tr("No translators loaded from %1 (named %2)")
.arg(settings.translatorsDir, settings.translators.join(", ")));
return;
}
for (const auto &script : loaded) {
const auto &scriptName = script.first;
const auto &scriptText = script.second;
const auto pageIt = pages_.emplace(
scriptName, std::make_unique<WebPage>(*this, scriptText, scriptName));
SOFT_ASSERT(pageIt.second, continue);
const auto &page = pageIt.first->second;
page->setIgnoreSslErrors(settings.ignoreSslErrors);
page->setTimeout(settings.translationTimeout);
auto log = new QTextEdit(tabs_);
tabs_->addTab(log, scriptName);
connect(page.get(), &WebPage::log, //
log, &QTextEdit::append);
SOFT_ASSERT(log->document(), continue)
log->document()->setMaximumBlockCount(1000);
}
if (settings.debugMode) {
show();
} else {
hide();
}
}
void Translator::changeCurrentPage(int tabIndex)
{
const auto name = tabs_->tabText(tabIndex);
SOFT_ASSERT(pages_.count(name), return );
view_->setPage(pages_[name].get());
}
void Translator::processQueue()
{
if (queue_.empty())
return;
std::unordered_set<QString> idlePages;
std::unordered_set<Task *> busyTasks;
for (auto &i : pages_) {
if (i.second->isBusy())
busyTasks.insert(i.second->task().get());
else
idlePages.insert(i.first);
}
if (idlePages.empty())
return;
std::vector<TaskPtr> finishedTasks;
for (const auto &task : queue_) {
if (idlePages.empty())
break;
if (busyTasks.count(task.get()))
continue;
if (task->translators.isEmpty()) {
task->error = tr("All translators failed");
finishedTasks.push_back(task);
continue;
}
for (auto &translator : task->translators) {
if (!idlePages.count(translator))
continue;
SOFT_ASSERT(pages_.count(translator), continue);
pages_[translator]->start(task);
task->translators.removeOne(translator);
idlePages.erase(translator);
break;
}
}
if (!finishedTasks.empty()) {
for (const auto &task : finishedTasks) markTranslated(task);
}
}
void Translator::markTranslated(const TaskPtr &task)
{
manager_.translated(task);
queue_.erase(std::remove(queue_.begin(), queue_.end(), task), queue_.end());
}
void Translator::finish(const TaskPtr &task)
{
markTranslated(task);
processQueue();
}
void Translator::timerEvent(QTimerEvent * /*event*/)
{
processQueue();
}
void Translator::closeEvent(QCloseEvent *event)
{
event->ignore();
}

View File

@ -0,0 +1,37 @@
#pragma once
#include "stfwd.h"
#include <QWidget>
class QWebEngineView;
class QTabWidget;
class WebPage;
class Translator : public QWidget
{
Q_OBJECT
public:
explicit Translator(Manager &manager);
~Translator();
void translate(const TaskPtr &task);
void updateSettings(const Settings &settings);
void finish(const TaskPtr &task);
protected:
void timerEvent(QTimerEvent *event) override;
void closeEvent(QCloseEvent *event) override;
private:
void changeCurrentPage(int tabIndex);
void processQueue();
void markTranslated(const TaskPtr &task);
Manager &manager_;
QWebEngineView *view_;
QTabWidget *tabs_;
std::vector<TaskPtr> queue_;
std::map<QString, std::unique_ptr<WebPage>> pages_;
};

166
src/translate/webpage.cpp Normal file
View File

@ -0,0 +1,166 @@
#include "webpage.h"
#include "debug.h"
#include "languagecodes.h"
#include "task.h"
#include "translator.h"
#include "webpageproxy.h"
#include <QWebEngineProfile>
#include <QWebEngineScriptCollection>
#include <QWebEngineSettings>
#include <QtWebChannel>
WebPage::WebPage(Translator &translator, const QString &script,
const QString &scriptName)
: QWebEnginePage(new QWebEngineProfile)
, translator_(translator)
, proxy_(new WebPageProxy(*this))
{
profile()->setParent(this);
connect(this, &WebPage::proxyAuthenticationRequired, this,
&WebPage::authenticateProxy);
scheduleWebchannelInitScript();
scheduleTranslatorScript(script, scriptName);
settings()->setAttribute(QWebEngineSettings::AutoLoadImages, false);
// settings()->setAttribute(QWebEngineSettings::LocalStorageEnabled, true);
auto channel = new QWebChannel(this);
channel->registerObject("proxy", proxy_.get());
setWebChannel(channel, QWebEngineScript::ScriptWorldId::UserWorld);
// to load scripts
setUrl(QUrl::fromUserInput("about:blank"));
}
WebPage::~WebPage() = default;
void WebPage::scheduleWebchannelInitScript()
{
QFile f(":/qtwebchannel/qwebchannel.js");
if (!f.open(QFile::ReadOnly)) {
LERROR() << "Failed to open bundled file" << f.fileName();
return;
}
const auto data =
QString::fromUtf8(f.readAll()) +
R"(new QWebChannel(qt.webChannelTransport, function(channel){
window.proxy = channel.objects.proxy;
if (typeof init === "function") init ();
});)";
QWebEngineScript js;
js.setSourceCode(data);
js.setName("qwebchannel.js");
js.setWorldId(QWebEngineScript::UserWorld);
js.setInjectionPoint(QWebEngineScript::Deferred);
js.setRunsOnSubFrames(false);
SOFT_ASSERT(profile(), return );
profile()->scripts()->insert(js);
}
void WebPage::scheduleTranslatorScript(const QString &script,
const QString &scriptName)
{
QWebEngineScript js;
js.setSourceCode(script);
js.setName(scriptName);
js.setWorldId(QWebEngineScript::UserWorld);
js.setInjectionPoint(QWebEngineScript::Deferred);
js.setRunsOnSubFrames(false);
SOFT_ASSERT(profile(), return );
profile()->scripts()->insert(js);
}
void WebPage::setIgnoreSslErrors(bool ignoreSslErrors)
{
ignoreSslErrors_ = ignoreSslErrors;
}
void WebPage::setTimeout(std::chrono::seconds timeout)
{
timeout_ = timeout;
}
void WebPage::start(const TaskPtr &task)
{
LanguageCodes languages;
auto langCodes = languages.findById(task->targetLanguage);
if (!langCodes) {
task->error = QObject::tr("unknown translation language: %1")
.arg(task->targetLanguage);
translator_.finish(task);
return;
}
task_ = task;
isBusy_ = true;
nextIdleTime_ = QDateTime::currentDateTime().addSecs(timeout_.count());
proxy_->translate(task->recognized, task->sourceLanguage,
langCodes->iso639_1);
}
bool WebPage::isBusy() const
{
return task_ && isBusy_ && QDateTime::currentDateTime() < nextIdleTime_;
}
void WebPage::setTranslated(const QString &text)
{
if (!isBusy())
return;
isBusy_ = false;
SOFT_ASSERT(task_, return )
task_->translated = text;
translator_.finish(task_);
}
void WebPage::setFailed(const QString &error)
{
if (!isBusy())
return;
isBusy_ = false;
SOFT_ASSERT(task_, return )
// task_->error = error;
translator_.finish(task_);
}
TaskPtr WebPage::task() const
{
return task_;
}
void WebPage::javaScriptConsoleMessage(
QWebEnginePage::JavaScriptConsoleMessageLevel /*level*/,
const QString &message, int lineNumber, const QString &sourceID)
{
qDebug() << sourceID << ":" << lineNumber << message;
emit log(QString("%1: %2 %3").arg(sourceID).arg(lineNumber).arg(message));
}
bool WebPage::certificateError(const QWebEngineCertificateError &error)
{
qDebug() << "certificateError" << error.url() << error.error()
<< error.errorDescription();
return ignoreSslErrors_;
}
void WebPage::authenticateProxy(const QUrl & /*requestUrl*/,
QAuthenticator *authenticator,
const QString & /*proxyHost*/)
{
const auto proxy = QNetworkProxy::applicationProxy();
authenticator->setUser(proxy.user());
authenticator->setPassword(proxy.password());
}

50
src/translate/webpage.h Normal file
View File

@ -0,0 +1,50 @@
#pragma once
#include "stfwd.h"
#include <QWebEngineCertificateError>
#include <QWebEngineView>
class WebPageProxy;
class WebPage : public QWebEnginePage
{
Q_OBJECT
public:
WebPage(Translator &translator, const QString &script,
const QString &scriptName);
~WebPage();
void setIgnoreSslErrors(bool ignoreSslErrors);
void setTimeout(std::chrono::seconds timeout);
void start(const TaskPtr &task);
void setTranslated(const QString &text);
void setFailed(const QString &error);
bool isBusy() const;
TaskPtr task() const;
signals:
void log(const QString &text);
protected:
void javaScriptConsoleMessage(JavaScriptConsoleMessageLevel level,
const QString &message, int lineNumber,
const QString &sourceID) override;
bool certificateError(const QWebEngineCertificateError &error) override;
private:
void authenticateProxy(const QUrl &requestUrl, QAuthenticator *authenticator,
const QString &proxyHost);
void scheduleWebchannelInitScript();
void scheduleTranslatorScript(const QString &script,
const QString &scriptName);
Translator &translator_;
std::unique_ptr<WebPageProxy> proxy_;
TaskPtr task_;
bool ignoreSslErrors_{false};
bool isBusy_{false};
QDateTime nextIdleTime_;
std::chrono::seconds timeout_{15};
};

View File

@ -0,0 +1,17 @@
#include "webpageproxy.h"
#include "webpage.h"
WebPageProxy::WebPageProxy(WebPage &page)
: page_(page)
{
}
void WebPageProxy::setTranslated(const QString &result)
{
page_.setTranslated(result);
}
void WebPageProxy::setFailed(const QString &error)
{
page_.setFailed(error);
}

View File

@ -0,0 +1,23 @@
#pragma once
#include <QObject>
class WebPage;
class WebPageProxy : public QObject
{
Q_OBJECT
public:
explicit WebPageProxy(WebPage& page);
signals:
void terminated();
void translate(const QString& text, const QString& from, const QString& to);
public slots:
void setTranslated(const QString& result);
void setFailed(const QString& error);
private:
WebPage& page_;
};

View File

@ -1,88 +0,0 @@
#include <QSettings>
#include <QDir>
#include <QFile>
#include <QApplication>
#include "translatorhelper.h"
#include "settings.h"
TranslatorHelper::TranslatorHelper ()
: translatorsDir_ ("translators"), currentIndex_ (0), triesLeft_ (0) {
translatorsDir_ = QApplication::applicationDirPath () + QDir::separator () + translatorsDir_;
}
void TranslatorHelper::setEnabledTranslators (const QStringList &enabled) const {
QSettings settings;
settings.beginGroup (settings_names::translationGroup);
settings.setValue (settings_names::translators, enabled.join ("|"));
}
QStringList TranslatorHelper::possibleTranslators (QStringList &enabled) const {
#define GET(FIELD) settings.value (settings_names::FIELD, settings_values::FIELD)
QSettings settings;
settings.beginGroup (settings_names::translationGroup);
QStringList exist = QDir (translatorsDir_).entryList (QStringList () << "*.js", QDir::Files);
QStringList saved = GET (translators).toString ().split ("|", QString::SkipEmptyParts);
QStringList on, off;
std::copy_if (saved.begin (), saved.end (), std::back_inserter (on), [&](const QString &i) {
return exist.contains (i);
});
on = on.isEmpty () ? exist : on;
std::copy_if (exist.begin (), exist.end (), std::back_inserter (off), [&](const QString &i) {
return !on.contains (i);
});
enabled = on;
return (on + off);
#undef GET
}
QStringList TranslatorHelper::enabledTranslatorScripts () const {
QStringList enabled;
possibleTranslators (enabled);
QStringList scripts;
for (const QString &name: enabled) {
QFile f (translatorsDir_ + QDir::separator () + name);
if (f.open (QFile::ReadOnly)) {
QString script = QString::fromUtf8 (f.readAll ());
if (!script.isEmpty ()) {
scripts << script;
}
}
}
return scripts;
}
void TranslatorHelper::loadScripts () {
scripts_ = enabledTranslatorScripts ();
}
void TranslatorHelper::newItem (bool forceRotate) {
triesLeft_ = scripts_.size ();
currentIndex_ = forceRotate ? currentIndex_ + 1 : 0;
if (currentIndex_ >= scripts_.size ()) {
currentIndex_ = 0;
}
}
QString TranslatorHelper::nextScript () {
--triesLeft_;
if (++currentIndex_ >= scripts_.size ()) {
currentIndex_ = 0;
}
return currentScript ();
}
QString TranslatorHelper::currentScript () const {
if (triesLeft_ > 0 && currentIndex_ < scripts_.size ()) {
return scripts_.at (currentIndex_);
}
return QString ();
}
bool TranslatorHelper::gotScripts () const {
return !scripts_.isEmpty ();
}

View File

@ -1,28 +0,0 @@
#ifndef TRANSLATORHELPER_H
#define TRANSLATORHELPER_H
#include <QStringList>
class TranslatorHelper {
public:
TranslatorHelper ();
QStringList possibleTranslators (QStringList &enabled) const;
QStringList enabledTranslatorScripts () const;
void setEnabledTranslators (const QStringList &enabled) const;
void loadScripts ();
void newItem (bool forceRotate);
QString nextScript ();
QString currentScript () const;
bool gotScripts () const;
private:
QString translatorsDir_;
QStringList scripts_;
int currentIndex_;
int triesLeft_;
};
#endif // TRANSLATORHELPER_H

221
src/trayicon.cpp Normal file
View File

@ -0,0 +1,221 @@
#include "trayicon.h"
#include "debug.h"
#include "globalaction.h"
#include "manager.h"
#include "settings.h"
#include <QMenu>
#include <QTimer>
TrayIcon::TrayIcon(Manager &manager)
: manager_(manager)
, tray_(std::make_unique<QSystemTrayIcon>())
, iconUpdateTimer_(std::make_unique<QTimer>())
{
GlobalAction::init();
connect(tray_.get(), &QSystemTrayIcon::activated, //
this, &TrayIcon::handleIconClick);
iconUpdateTimer_->setSingleShot(true);
connect(iconUpdateTimer_.get(), &QTimer::timeout, //
this, &TrayIcon::updateIcon);
tray_->setContextMenu(contextMenu());
setIcon(Icon::Idle, Duration::Permanent);
updateActions();
tray_->show();
}
TrayIcon::~TrayIcon() = default;
void TrayIcon::updateSettings(const Settings &settings)
{
QStringList failedActions;
if (!GlobalAction::update(captureAction_, settings.captureHotkey))
failedActions << settings.captureHotkey;
if (!GlobalAction::update(repeatCaptureAction_, settings.repeatCaptureHotkey))
failedActions << settings.repeatCaptureHotkey;
if (!GlobalAction::update(showLastAction_, settings.showLastHotkey))
failedActions << settings.showLastHotkey;
if (!GlobalAction::update(clipboardAction_, settings.clipboardHotkey))
failedActions << settings.clipboardHotkey;
if (!failedActions.isEmpty()) {
showError(tr("Failed to register global shortcuts:\n%1")
.arg(failedActions.join('\n')));
}
}
void TrayIcon::blockActions(bool block)
{
isActionsBlocked_ = block;
updateActions();
}
void TrayIcon::setTaskActionsEnabled(bool isEnabled)
{
gotTask_ = isEnabled;
updateActions();
}
void TrayIcon::setRepeatCaptureEnabled(bool isEnabled)
{
canRepeatCapture_ = isEnabled;
updateActions();
}
void TrayIcon::updateActions()
{
if (isActionsBlocked_) {
QVector<QAction *> blockable{captureAction_, repeatCaptureAction_,
showLastAction_, settingsAction_};
for (auto &action : blockable) action->setEnabled(false);
return;
}
captureAction_->setEnabled(true);
settingsAction_->setEnabled(true);
QVector<QAction *> taskActions{showLastAction_, clipboardAction_};
for (auto &action : taskActions) action->setEnabled(gotTask_);
repeatCaptureAction_->setEnabled(canRepeatCapture_);
}
void TrayIcon::setIcon(TrayIcon::Icon icon, Duration duration)
{
QMap<Icon, QString> icons{
{Icon::Idle, QStringLiteral(":icons/app.png")},
{Icon::Success, QStringLiteral(":icons/st_success.png")},
{Icon::Busy, QStringLiteral(":icons/st_busy.png")},
{Icon::Error, QStringLiteral(":icons/st_error.png")},
};
tray_->setIcon(QIcon(icons.value(icon)));
if (duration == Duration::Permanent) {
permanentIcon_ = icon;
return;
}
const auto durationMsec = 3000;
iconUpdateTimer_->start(durationMsec);
}
void TrayIcon::setActiveTaskCount(int count)
{
activeTaskCount_ = count;
updateIcon();
}
void TrayIcon::resetFatalError()
{
isFatalError_ = false;
updateIcon();
}
void TrayIcon::updateIcon()
{
if (iconUpdateTimer_->isActive())
return;
if (isFatalError_) {
setIcon(Icon::Error, Duration::Permanent);
return;
}
setIcon(activeTaskCount_ > 0 ? Icon::Busy : Icon::Idle, Duration::Permanent);
}
void TrayIcon::showInformation(const QString &text)
{
tray_->showMessage({}, text, QSystemTrayIcon::Information);
}
void TrayIcon::showError(const QString &text)
{
LERROR() << text;
setIcon(Icon::Error, Duration::Temporal);
tray_->showMessage(tr("Error"), text, QSystemTrayIcon::Warning);
}
void TrayIcon::showFatalError(const QString &text)
{
LERROR() << text;
isFatalError_ = true;
tray_->showMessage(tr("Error"), text, QSystemTrayIcon::Critical);
updateIcon();
}
void TrayIcon::showSuccess()
{
setIcon(Icon::Success, Duration::Temporal);
}
void TrayIcon::handleIconClick(QSystemTrayIcon::ActivationReason reason)
{
if (reason == QSystemTrayIcon::Trigger && showLastAction_->isEnabled()) {
manager_.showLast();
return;
}
if (reason == QSystemTrayIcon::MiddleClick && clipboardAction_->isEnabled()) {
manager_.copyLastToClipboard();
return;
}
if (reason == QSystemTrayIcon::DoubleClick &&
repeatCaptureAction_->isEnabled()) {
manager_.repeatCapture();
}
}
QMenu *TrayIcon::contextMenu()
{
QMenu *menu = new QMenu();
{
captureAction_ = menu->addAction(tr("Capture"));
connect(captureAction_, &QAction::triggered, //
this, [this] { manager_.capture(); });
}
{
repeatCaptureAction_ = menu->addAction(tr("Repeat capture"));
connect(repeatCaptureAction_, &QAction::triggered, //
this, [this] { manager_.repeatCapture(); });
}
{
QMenu *translateMenu = menu->addMenu(tr("Result"));
{
showLastAction_ = translateMenu->addAction(tr("Show"));
connect(showLastAction_, &QAction::triggered, //
this, [this] { manager_.showLast(); });
}
{
clipboardAction_ = translateMenu->addAction(tr("To clipboard"));
connect(clipboardAction_, &QAction::triggered, //
this, [this] { manager_.copyLastToClipboard(); });
}
}
{
settingsAction_ = menu->addAction(tr("Settings"));
connect(settingsAction_, &QAction::triggered, //
this, [this] { manager_.settings(); });
}
{
auto action = menu->addAction(tr("About"));
connect(action, &QAction::triggered, //
this, [this] { manager_.about(); });
}
{
auto action = menu->addAction(tr("Quit"));
connect(action, &QAction::triggered, //
this, [this] { manager_.quit(); });
}
return menu;
}

55
src/trayicon.h Normal file
View File

@ -0,0 +1,55 @@
#pragma once
#include "stfwd.h"
#include <QSystemTrayIcon>
class QAction;
class TrayIcon : public QObject
{
Q_OBJECT
public:
explicit TrayIcon(Manager &manager);
~TrayIcon();
void updateSettings(const Settings &settings);
void blockActions(bool block);
void setTaskActionsEnabled(bool isEnabled);
void setRepeatCaptureEnabled(bool isEnabled);
void setActiveTaskCount(int count);
void resetFatalError();
void showInformation(const QString &text);
void showError(const QString &text);
void showFatalError(const QString &text);
void showSuccess();
private:
enum class Icon { Idle, Success, Busy, Error };
enum Duration { Permanent, Temporal };
void setIcon(TrayIcon::Icon icon, Duration duration);
void handleIconClick(QSystemTrayIcon::ActivationReason reason);
QMenu *contextMenu();
void updateIcon();
void updateActions();
Manager &manager_;
std::unique_ptr<QSystemTrayIcon> tray_;
QAction *captureAction_{nullptr};
QAction *repeatCaptureAction_{nullptr};
QAction *showLastAction_{nullptr};
QAction *clipboardAction_{nullptr};
QAction *settingsAction_{nullptr};
std::unique_ptr<QTimer> iconUpdateTimer_;
int activeTaskCount_{0};
bool isFatalError_{false};
Icon permanentIcon_{Icon::Idle};
bool gotTask_{false};
bool canRepeatCapture_{false};
bool isActionsBlocked_{false};
};

View File

@ -1,264 +0,0 @@
#include <QDir>
#include <QFile>
#include <QJsonDocument>
#include <QJsonArray>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QMessageBox>
#include <QApplication>
#include "updater.h"
#include "stassert.h"
namespace {
#define FIELD(NAME) const QString _ ## NAME = #NAME
FIELD (Application);
FIELD (name);
FIELD (version);
FIELD (compatibleVersion);
FIELD (built_in);
FIELD (versionString);
FIELD (permissions);
FIELD (url);
FIELD (path);
#undef FIELD
#if defined(Q_OS_WIN)
const QString _platform = "_win";
#elif defined(Q_OS_LINUX)
const QString _platform = "_linux";
#endif
QString versionField (const QJsonObject &component, const QString &field) {
return component.contains (field + _platform)
? component[field + _platform].toVariant ().toString ()
: component[field].toVariant ().toString ();
}
QFileInfo fileDir (const QString &fileName) {
return QFileInfo (fileName).absolutePath ();
}
}
Updater::Updater (QObject *parent)
: QObject (parent),
network_ (new QNetworkAccessManager (this)),
componentsUpdating_ (0) {
updatesFileName_ = QApplication::applicationDirPath () + QDir::separator () + "updates.json";
backupSuffix_ = "_backup";
connect (network_, SIGNAL (finished (QNetworkReply*)),
SLOT (replyFinished (QNetworkReply*)));
getCurrentVersion ();
updateCurrentVersion ();
}
QDateTime Updater::nextCheckTime (const QDateTime &lastCheckTime, int updateType) const {
QDateTime nextTime;
switch (updateType) {
case UpdateTypeDaily:
nextTime = lastCheckTime.addDays (1);
break;
case UpdateTypeWeekly:
nextTime = lastCheckTime.addDays (7);
break;
case UpdateTypeMonthly:
nextTime = lastCheckTime.addDays (30);
break;
case UpdateTypeNever:
default:
return QDateTime ();
}
if (nextTime < QDateTime::currentDateTime ()) {
return QDateTime::currentDateTime ().addSecs (5);
}
return nextTime;
}
void Updater::getCurrentVersion () {
QFile f (":/version.json");
if (f.open (QFile::ReadOnly)) {
currentVersion_ = QJsonDocument::fromJson (f.readAll ()).object ();
f.close ();
}
else {
emit error (tr ("Ошибка определения текущей версии. Обновление недоступно."));
}
}
void Updater::updateCurrentVersion () {
QFile f (updatesFileName_);
if (!f.open (QFile::ReadOnly)) {
return;
}
QJsonObject updated = QJsonDocument::fromJson (f.readAll ()).object ();
f.close ();
for (const QString &component: updated.keys ()) {
QJsonObject current = currentVersion_[component].toObject ();
int updatedVersion = updated[component].toInt ();
if (current[_built_in].toBool () || current[_version].toInt () >= updatedVersion) {
continue;
}
current[_version] = updatedVersion;
currentVersion_[component] = current;
}
}
QString Updater::currentAppVersion () const {
return currentVersion_[_Application].toObject ()[_versionString].toString ();
}
void Updater::checkForUpdates () {
getAvailableVersion ();
}
void Updater::getAvailableVersion () {
QNetworkRequest request (versionField (currentVersion_, _url));
request.setAttribute (QNetworkRequest::User, _version);
network_->get (request);
}
void Updater::replyFinished (QNetworkReply *reply) {
if (reply->error () != QNetworkReply::NoError) {
emit tr ("Ошибка загрузки информации для обновления.");
return;
}
QByteArray content = reply->readAll ();
QString component = reply->request ().attribute (QNetworkRequest::User).toString ();
if (component == _version) {
availableVersion_ = QJsonDocument::fromJson (content).object ();
parseAvailableVersion ();
}
else if (availableVersion_.contains (component) && !content.isEmpty ()) {
installComponent (component, content);
}
reply->deleteLater ();
}
void Updater::parseAvailableVersion () {
QStringList inaccessible, incompatible;
QStringList updateList;
QDir currentDir;
for (const QString &component: availableVersion_.keys ()) {
QJsonObject available = availableVersion_[component].toObject ();
QJsonObject current = currentVersion_[component].toObject ();
QString path = versionField (available, _path);
if (path.isEmpty ()) {
continue;
}
QFileInfo installDir = fileDir (path);
if (!installDir.exists ()) {
currentDir.mkpath (installDir.absoluteFilePath ());
}
if (!installDir.isWritable ()) { // check dir because install = remove + make new
inaccessible << installDir.absoluteFilePath ();
}
if (current[_version].toInt () < versionField (available, _compatibleVersion).toInt ()) {
incompatible << component;
}
if (!QFile::exists (path) || current[_version].toInt () < available[_version].toInt ()) {
updateList << component;
}
}
if (updateList.isEmpty ()) {
return;
}
QFileInfo updateFileDir = fileDir (updatesFileName_);
if (!updateFileDir.isWritable ()) {
inaccessible << updateFileDir.absoluteFilePath ();
}
inaccessible.removeDuplicates ();
QString message = tr ("Доступно обновлений: %1.\n").arg (updateList.size ());
QMessageBox::StandardButtons buttons = QMessageBox::Ok;
if (!incompatible.isEmpty ()) {
message += tr ("Несовместимых обновлений: %1.\nВыполните обновление вручную.")
.arg (incompatible.size ());
}
else if (!inaccessible.isEmpty ()) {
message += tr ("Недоступных для записи директорий: %1.\n%2\nИзмените права доступа и "
"повторите попытку или выполните обновление вручную.")
.arg (inaccessible.size ()).arg (inaccessible.join ("\n"));
}
else {
message += tr ("Обновить?");
buttons = QMessageBox::Yes | QMessageBox::No;
}
int result = QMessageBox::question (NULL, tr ("Обновление"), message, buttons);
if (result == QMessageBox::Yes) {
componentsUpdating_ = updateList.size ();
for (const QString &component: updateList) {
getComponent (component);
}
}
}
void Updater::getComponent (const QString &component) {
QJsonObject available = availableVersion_[component].toObject ();
QString path = versionField (available, _path);
if (path.isEmpty ()) {
--componentsUpdating_;
return;
}
QString url = versionField (available, _url);
if (url.isEmpty ()) { // just remove component
installComponent (component, QByteArray ());
}
else {
QNetworkRequest request (url);
request.setAttribute (QNetworkRequest::User, component);
network_->get (request);
}
}
void Updater::installComponent (const QString &component, const QByteArray &newContent) {
--componentsUpdating_;
ST_ASSERT (availableVersion_.contains (component));
QJsonObject available = availableVersion_[component].toObject ();
QString path = versionField (available, _path);
ST_ASSERT (!path.isEmpty ());
QString backup = path + backupSuffix_;
QFile::remove (backup);
QFile::rename (path, backup);
if (!newContent.isEmpty ()) {
QFile f (path);
if (!f.open (QFile::WriteOnly)) {
emit error (tr ("Ошибка обновления файла (%1).").arg (path));
return;
}
f.write (newContent);
f.close ();
bool ok;
QFileDevice::Permissions perm (available[_permissions].toString ().toUInt (&ok, 16));
if (ok) {
f.setPermissions (perm);
}
}
updateVersionInfo (component, available[_version].toInt ());
if (componentsUpdating_ == 0) {
emit updated ();
QString message = tr ("Обновление завершено. Для активации некоторых компонентов "
"может потребоваться перезапуск.");
QMessageBox::information (NULL, tr ("Обновление"), message, QMessageBox::Ok);
}
}
void Updater::updateVersionInfo (const QString &component, int version) {
QFile f (updatesFileName_);
if (!f.open (QFile::ReadWrite)) {
emit error (tr ("Ошибка обновления файла с текущей версией."));
return;
}
QJsonObject updated = QJsonDocument::fromJson (f.readAll ()).object ();
updated[component] = version;
f.seek (0);
f.write (QJsonDocument (updated).toJson ());
f.close ();
}

View File

@ -1,65 +0,0 @@
#ifndef UPDATER_H
#define UPDATER_H
#include <QObject>
#include <QJsonObject>
#include <QNetworkAccessManager>
/*!
* \brief The Updater class.
*
* Allows to download and copy files from remote source to local machine.
*/
class Updater : public QObject {
Q_OBJECT
public:
enum UpdateType {
UpdateTypeNever, UpdateTypeDaily, UpdateTypeWeekly, UpdateTypeMonthly
};
explicit Updater (QObject *parent = 0);
QString currentAppVersion () const;
//! Initiate updates check.
void checkForUpdates ();
//! Get nearest update check time based on given settings.
QDateTime nextCheckTime (const QDateTime &lastCheckTime, int updateType) const;
signals:
void error (const QString &message);
//! Emited after all components updated.
void updated ();
private slots:
//! Handle remote downloads finish.
void replyFinished (QNetworkReply *reply);
private:
//! Load current version info (built-in).
void getCurrentVersion ();
//! Update current version info with information about preformed updates.
void updateCurrentVersion ();
//! Load latest available version info from remote source.
void getAvailableVersion ();
//! Check is updates available, prompt user and start update.
void parseAvailableVersion ();
//! Start update of given component.
void getComponent (const QString &component);
//! Finalize update of given component with given new content.
void installComponent (const QString &component, const QByteArray &newContent);
//! Save information about component update on disk (for updateCurrentVersion()).
void updateVersionInfo (const QString &component, int version);
private:
QNetworkAccessManager *network_;
QJsonObject availableVersion_;
QJsonObject currentVersion_;
QString updatesFileName_;
int componentsUpdating_;
QString backupSuffix_;
};
#endif // UPDATER_H

View File

@ -1,21 +0,0 @@
#include <QNetworkProxy>
#include "utils.h"
QString encode (const QString &source) {
if (source.isEmpty ()) {
return source;
}
char encKeys[] = {14, 26, 99, 43};
std::string result = source.toStdString ();
for (int i = 0, end = result.size (); i < end; ++i) {
result [i] = result[i] ^ encKeys[ i % sizeof(encKeys)];
}
return QString::fromUtf8 (result.data ());
}
QList<int> proxyTypeOrder () {
QList<int> proxyOrder;
proxyOrder << QNetworkProxy::NoProxy << QNetworkProxy::Socks5Proxy << QNetworkProxy::HttpProxy;
return proxyOrder;
}

View File

@ -1,10 +0,0 @@
#ifndef UTILS_H
#define UTILS_H
#include <QString>
QString encode (const QString &source);
QList<int> proxyTypeOrder ();
#endif // UTILS_H

View File

@ -1,156 +0,0 @@
#include <QWebView>
#include <QWebFrame>
#include <QSettings>
#include <QNetworkReply>
#include <QFile>
#include "webtranslator.h"
#include "processingitem.h"
#include "settings.h"
#include "stassert.h"
#include "webtranslatorproxy.h"
#include "translatorhelper.h"
WebTranslator::WebTranslator ()
: QObject (),
proxy_ (new WebTranslatorProxy (this)), view_ (new QWebView),
translatorHelper_ (new TranslatorHelper), isReady_ (true),
ignoreSslErrors_ (settings_values::ignoreSslErrors),
forceRotateTranslators_ (settings_values::forceRotateTranslators) {
view_->settings ()->setAttribute (QWebSettings::AutoLoadImages, false);
view_->settings ()->setAttribute (QWebSettings::DeveloperExtrasEnabled, true);
view_->settings ()->setAttribute (QWebSettings::LocalStorageEnabled, true);
connect (view_, SIGNAL (loadFinished (bool)), SLOT (loadFinished (bool)));
connect (view_->page ()->mainFrame (), SIGNAL (javaScriptWindowObjectCleared ()),
this, SLOT (addProxyToView ()));
connect (view_->page ()->networkAccessManager (), SIGNAL (finished (QNetworkReply*)),
this, SLOT (replyFinished (QNetworkReply*)));
connect (view_->page ()->networkAccessManager (),
SIGNAL (sslErrors (QNetworkReply*,QList<QSslError>)),
this, SLOT (handleSslErrors (QNetworkReply*,QList<QSslError>)));
translationTimeout_.setSingleShot (true);
connect (&translationTimeout_, SIGNAL (timeout ()), SLOT (abortTranslation ()));
connect (proxy_, SIGNAL (translated (QString)), SLOT (proxyTranslated (QString)));
// Delay because it can emit signal that is not connected yet.
QTimer::singleShot (500, this, SLOT (applySettings ()));
}
WebTranslator::~WebTranslator () {
delete translatorHelper_;
delete view_;
}
void WebTranslator::addProxyToView () {
view_->page ()->mainFrame ()->addToJavaScriptWindowObject ("st_wtp", proxy_);
view_->page ()->mainFrame ()->evaluateJavaScript (translatorHelper_->currentScript ());
}
void WebTranslator::translate (ProcessingItem item) {
if (!item.isValid () || item.translateLanguage.isEmpty ()) {
emit translated (item);
return;
}
queue_.push_back (item);
translateQueued ();
}
void WebTranslator::translateQueued () {
if (isReady_ && !queue_.isEmpty ()) {
translatorHelper_->newItem (forceRotateTranslators_);
proxy_->setItem (queue_.first ());
if (!tryNextTranslator (true)) {
return;
}
}
}
void WebTranslator::proxyTranslated (const QString &text) {
if (!queue_.isEmpty () && queue_.first ().recognized == proxy_->sourceText ()) {
if (text.isEmpty () && tryNextTranslator ()) {
return;
}
ProcessingItem &item = queue_.first ();
item.translated = text;
emit translated (item);
}
finishTranslation (false);
}
void WebTranslator::handleSslErrors (QNetworkReply *reply, const QList<QSslError> &) {
if (ignoreSslErrors_) {
reply->ignoreSslErrors ();
}
}
void WebTranslator::abortTranslation () {
if (!tryNextTranslator ()) {
emit error (tr ("Перевод отменен по таймауту."));
finishTranslation ();
}
}
void WebTranslator::loadFinished (bool ok) {
if (!ok && !tryNextTranslator ()) {
QString url = view_->url ().toString ();
emit error (tr ("Ошибка загрузки страницы (%1) для перевода.").arg (url));
finishTranslation ();
}
}
void WebTranslator::finishTranslation (bool markAsTranslated) {
translationTimeout_.stop ();
view_->stop ();
if (!queue_.isEmpty ()) {
if (markAsTranslated) {
emit translated (queue_.first ());
}
queue_.pop_front ();
}
isReady_ = true;
translateQueued ();
}
bool WebTranslator::tryNextTranslator (bool firstTime) {
QString script = firstTime ? translatorHelper_->currentScript ()
: translatorHelper_->nextScript ();
if (script.isEmpty ()) {
return false;
}
translationTimeout_.stop ();
view_->stop ();
addProxyToView ();
view_->page ()->mainFrame ()->evaluateJavaScript ("translate();");
isReady_ = false;
translationTimeout_.start ();
return true;
}
void WebTranslator::replyFinished (QNetworkReply *reply) {
emit proxy_->resourceLoaded (reply->url ().toString ());
}
void WebTranslator::applySettings () {
QSettings settings;
settings.beginGroup (settings_names::translationGroup);
#define GET(NAME) settings.value (settings_names::NAME, settings_values::NAME)
translationTimeout_.setInterval (GET (translationTimeout).toInt () * 1000);
translatorHelper_->loadScripts ();
if (!translatorHelper_->gotScripts ()) {
emit error (tr ("Нет сценариев для перевода. Измените настройки."));
}
bool debugMode = GET (translationDebugMode).toBool ();
setDebugMode (debugMode);
ignoreSslErrors_ = GET (ignoreSslErrors).toBool ();
forceRotateTranslators_ = GET (forceRotateTranslators).toBool ();
#undef GET
}
void WebTranslator::setDebugMode (bool isOn) {
view_->setVisible (isOn);
}

View File

@ -1,57 +0,0 @@
#ifndef WEBTRANSLATOR_H
#define WEBTRANSLATOR_H
#include <QObject>
#include <QMap>
#include <QTimer>
#include "processingitem.h"
class QWebView;
class QNetworkReply;
class QSslError;
class WebTranslatorProxy;
class TranslatorHelper;
class WebTranslator : public QObject {
Q_OBJECT
public:
explicit WebTranslator ();
~WebTranslator ();
signals:
void translated (ProcessingItem item);
void error (QString text);
public slots:
void translate (ProcessingItem item);
void applySettings ();
void setDebugMode (bool isOn);
private slots:
void loadFinished (bool ok);
void replyFinished (QNetworkReply *reply);
void addProxyToView ();
void abortTranslation ();
void proxyTranslated (const QString &text);
void handleSslErrors (QNetworkReply *reply, const QList<QSslError> &errors);
private:
void translateQueued ();
void finishTranslation (bool markAsTranslated = true);
bool tryNextTranslator (bool firstTime = false);
private:
WebTranslatorProxy *proxy_;
QWebView *view_;
TranslatorHelper *translatorHelper_;
QVector<ProcessingItem> queue_;
bool isReady_;
bool ignoreSslErrors_;
bool forceRotateTranslators_;
QTimer translationTimeout_;
};
#endif // WEBTRANSLATOR_H

View File

@ -1,25 +0,0 @@
#include "webtranslatorproxy.h"
#include "processingitem.h"
WebTranslatorProxy::WebTranslatorProxy (QObject *parent)
: QObject (parent) {
}
void WebTranslatorProxy::setItem (const ProcessingItem &item) {
sourceText_ = item.recognized;
sourceLanguage_ = item.ocrLanguage;
resultLanguage_ = item.translateLanguage;
}
const QString &WebTranslatorProxy::sourceText () const {
return sourceText_;
}
const QString &WebTranslatorProxy::sourceLanguage () const {
return sourceLanguage_;
}
const QString &WebTranslatorProxy::resultLanguage () const {
return resultLanguage_;
}

View File

@ -1,37 +0,0 @@
#ifndef WEBTRANSLATORPROXY_H
#define WEBTRANSLATORPROXY_H
#include <QObject>
class ProcessingItem;
/*!
* \brief Proxy class between WebTranslator and QWebView.
*/
class WebTranslatorProxy : public QObject {
Q_OBJECT
Q_PROPERTY (QString sourceText READ sourceText)
Q_PROPERTY (QString sourceLanguage READ sourceLanguage)
Q_PROPERTY (QString resultLanguage READ resultLanguage)
public:
explicit WebTranslatorProxy (QObject *parent = 0);
void setItem (const ProcessingItem &item);
const QString &sourceText () const;
const QString &sourceLanguage () const;
const QString &resultLanguage () const;
signals:
void translated (const QString &text);
void resourceLoaded (const QString &url);
private:
QString sourceText_;
QString sourceLanguage_;
QString resultLanguage_;
};
#endif // WEBTRANSLATORPROXY_H

View File

@ -1,50 +1,41 @@
var isPageLoaded = false;
var isTranslationFinished = false; // async translation request
var isScheduled = false;
var lastText = '';
var active = window.location.href !== "about:blank";
function checkFinished () {
if (!isPageLoaded || !isTranslationFinished || isScheduled) return;
isScheduled = true;
setTimeout(function () {
var spans = [].slice.call (document.querySelectorAll (
'span.translation > span, #result_box > span'));
var text = spans.reduce (function (res, i) {
if (!active) return;
let spans = [].slice.call (document.querySelectorAll ('span.translation > span, #result_box > span'));
let text = spans.reduce (function (res, i) {
return res + ' ' + i.innerText;
}, '');
console.log (text);
st_wtp.translated (text);
isTranslationFinished = isScheduled = false;
}, 2000); // wait for gui fill
if (text === lastText || text === '')
return;
console.log ('translated text', text, 'old', lastText, 'size', text.length, lastText.length);
lastText = text;
active = false;
proxy.setTranslated (text);
}
function onResourceLoad (url) {
if (url.indexOf ('/translate_a/single') > -1) {
isTranslationFinished = true;
if (isPageLoaded) {
checkFinished ();
}
}
}
st_wtp.resourceLoaded.connect (onResourceLoad);
function onPageLoad () {
if (window.location.href.indexOf('about:blank') === 0) {
translate ();
function translate (text, from, to){
console.log('start translate', text, from, to)
active = true;
if (window.location.href.indexOf('//translate.google') !== -1
&& window.location.href.indexOf('&tl='+to+'&') !== -1) {
document.querySelector('textarea#source').value=text;
return;
}
isPageLoaded = true;
if (isTranslationFinished) {
checkFinished ();
}
}
window.onload = onPageLoad();
function translate (){
if (window.location.href.indexOf('https://translate.google') === 0) {
window.location = 'about:blank';
return;
}
var url = 'https://translate.google.com/#auto/' +
st_wtp.resultLanguage + '/' + st_wtp.sourceText;
// var url = 'https://translate.google.com/#auto/' + to + '/' + text;
let url = 'https://translate.google.com/#view=home&op=translate&sl=auto&tl=' + to + '&text=' + text;
console.log("setting url", url);
window.location = encodeURI (url);
}
function init() {
proxy.translate.connect (translate);
setInterval(checkFinished, 300);
}

View File

@ -1,36 +1,42 @@
var isPageLoaded = false;
var isTranslationFinished = true; // async translation request
var isScheduled = false;
var lastText = '';
var active = window.location.href !== "about:blank";
function checkFinished () {
if (!isPageLoaded || !isTranslationFinished || isScheduled) return;
isScheduled = true;
setTimeout(function () {
var spans = [].slice.call (document.querySelectorAll ('span.translation-chunk'));
var text = spans.reduce (function (res, i) {
return res + i.innerText + ' ';
}, '');
console.log (text);
st_wtp.translated (text);
isTranslationFinished = isScheduled = false;
}, 2000); // wait for gui fill
}
function onResourceLoad (url) {
if (url.indexOf ('/tr.json/translate?') > -1) {
isTranslationFinished = true;
checkFinished ();
}
}
st_wtp.resourceLoaded.connect (onResourceLoad);
function onPageLoad () {
isPageLoaded = true;
checkFinished ();
}
window.onload = onPageLoad();
if (!active) return;
function translate (){
var url = 'https://translate.yandex.ru/?lang=' + st_wtp.sourceLanguage + '-' +
st_wtp.resultLanguage + '&text=' + st_wtp.sourceText ;
url = url.replace(new RegExp(' ','g') , '%20')
window.location = (url);
var spans = [].slice.call (document.querySelectorAll ('span.translation-chunk'));
let text = spans.reduce (function (res, i) {
return res + ' ' + i.innerText;
}, '');
if (text === lastText || text === '')
return;
console.log ('translated text', text, 'old', lastText, 'size', text.length, lastText.length);
lastText = text;
active = false;
proxy.setTranslated (text);
}
function translate (text, from, to){
console.log('start translate', text, from, to)
active = true;
var langs = 'lang=' + from + '-' + to;
if (window.location.href.indexOf('//translate.yandex') !== -1
&& window.location.href.indexOf(langs) !== -1) {
document.querySelector('textarea#textarea').value=text
document.querySelector('div#textbox').dispatchEvent(
new Event("input", {bubbles: true, cancelable: true}));
return;
}
var url = 'https://translate.yandex.ru/?' + langs + '&text=' + text;
url = url.replace(new RegExp(' ','g') , '%20')
window.location = url;
}
function init() {
proxy.translate.connect (translate);
setInterval(checkFinished, 300);
}