Refactor. WIP
16
.clang-format
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
BasedOnStyle: Google
|
||||||
|
AccessModifierOffset: '-2'
|
||||||
|
AllowShortCaseLabelsOnASingleLine: 'true'
|
||||||
|
AllowShortFunctionsOnASingleLine: InlineOnly
|
||||||
|
AllowShortIfStatementsOnASingleLine: 'false'
|
||||||
|
BreakBeforeBraces: Linux
|
||||||
|
BreakConstructorInitializers: BeforeComma
|
||||||
|
ConstructorInitializerAllOnOneLineOrOnePerLine: 'false'
|
||||||
|
ConstructorInitializerIndentWidth: '2'
|
||||||
|
Standard: Cpp11
|
||||||
|
TabWidth: '2'
|
||||||
|
UseTab: Never
|
||||||
|
IncludeBlocks: Preserve
|
||||||
|
|
||||||
|
...
|
20
.travis.yml
@ -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
|
|
204
3rd-party/qtsingleapplication/qtlocalpeer.cpp
vendored
@ -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)
|
|
||||||
}
|
|
77
3rd-party/qtsingleapplication/qtlocalpeer.h
vendored
@ -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
|
|
193
3rd-party/qtsingleapplication/qtlockedfile.cpp
vendored
@ -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.
|
|
||||||
*/
|
|
97
3rd-party/qtsingleapplication/qtlockedfile.h
vendored
@ -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
|
|
115
3rd-party/qtsingleapplication/qtlockedfile_unix.cpp
vendored
@ -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();
|
|
||||||
}
|
|
||||||
|
|
211
3rd-party/qtsingleapplication/qtlockedfile_win.cpp
vendored
@ -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);
|
|
||||||
}
|
|
@ -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
|
|
||||||
*/
|
|
105
3rd-party/qtsingleapplication/qtsingleapplication.h
vendored
@ -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
|
|
@ -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
|
|
||||||
}
|
|
@ -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()
|
|
||||||
*/
|
|
@ -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
|
|
@ -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)
|
|
||||||
}
|
|
@ -1,11 +1,11 @@
|
|||||||
<RCC>
|
<RCC>
|
||||||
<qresource prefix="/">
|
<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>
|
<file>version.json</file>
|
||||||
</qresource>
|
</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>
|
</RCC>
|
||||||
|
92
screen-translator.pro
Normal 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
|
@ -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
|
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.0 KiB |
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 97 KiB |
93
src/capture/captureoverlay.cpp
Normal 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);
|
||||||
|
}
|
30
src/capture/captureoverlay.h
Normal 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
@ -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
@ -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
@ -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
@ -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_;
|
||||||
|
};
|
@ -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
|
|
@ -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
|
|
@ -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);
|
|
||||||
}
|
|
@ -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
@ -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
@ -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_;
|
||||||
|
};
|
@ -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");
|
|
||||||
|
|
||||||
}
|
|
@ -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
|
|
55
src/main.cpp
@ -1,35 +1,48 @@
|
|||||||
#ifdef Q_OS_LINUX
|
#ifdef Q_OS_LINUX
|
||||||
# include <locale.h>
|
#include <locale.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "apptranslator.h"
|
||||||
|
#include "manager.h"
|
||||||
|
#include "singleapplication.h"
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QTranslator>
|
#include <QCommandLineParser>
|
||||||
|
|
||||||
#include <qtsingleapplication.h>
|
#define STR2(XXX) #XXX
|
||||||
|
#define STR(XXX) STR2(XXX)
|
||||||
|
|
||||||
#include <manager.h>
|
int main(int argc, char *argv[])
|
||||||
#include <settings.h>
|
{
|
||||||
|
QApplication a(argc, argv);
|
||||||
|
a.setApplicationName("ScreenTranslator");
|
||||||
|
a.setOrganizationName("Gres");
|
||||||
|
a.setApplicationVersion(STR(VERSION));
|
||||||
|
|
||||||
int main (int argc, char *argv[]) {
|
a.setQuitOnLastWindowClosed(false);
|
||||||
QtSingleApplication a (argc, argv);
|
|
||||||
if (a.sendMessage (QString ())) {
|
{
|
||||||
return 0;
|
AppTranslator appTranslator({"screentranslator"});
|
||||||
|
appTranslator.retranslate();
|
||||||
}
|
}
|
||||||
#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.
|
QCommandLineParser parser;
|
||||||
if (translator.load (QLocale::system (), "translation", "_", ":/translations") ||
|
parser.setApplicationDescription(QObject::tr("OCR and translation tool"));
|
||||||
translator.load (":/translations/translation_en")) {
|
parser.addHelpOption();
|
||||||
a.installTranslator (&translator);
|
parser.addVersionOption();
|
||||||
|
|
||||||
|
parser.process(a);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SingleApplication guard;
|
||||||
|
if (!guard.isValid())
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
// tesseract recomments
|
||||||
|
setlocale(LC_NUMERIC, "C");
|
||||||
|
|
||||||
Manager manager;
|
Manager manager;
|
||||||
|
|
||||||
return a.exec ();
|
return a.exec();
|
||||||
}
|
}
|
||||||
|
608
src/manager.cpp
@ -1,463 +1,209 @@
|
|||||||
#include "manager.h"
|
#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 <QApplication>
|
||||||
#include <QDesktopWidget>
|
|
||||||
#include <QScreen>
|
|
||||||
#include <QDesktopWidget>
|
|
||||||
#include <QThread>
|
|
||||||
#include <QSettings>
|
|
||||||
#include <QClipboard>
|
#include <QClipboard>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QInputDialog>
|
|
||||||
#include <QNetworkProxy>
|
|
||||||
|
|
||||||
#include "settings.h"
|
Manager::Manager()
|
||||||
#include "settingseditor.h"
|
{
|
||||||
#include "selectiondialog.h"
|
tray_ = std::make_unique<TrayIcon>(*this);
|
||||||
#include "globalactionhelper.h"
|
capturer_ = std::make_unique<Capturer>(*this);
|
||||||
#include "recognizer.h"
|
recognizer_ = std::make_unique<Recognizer>(*this);
|
||||||
#include "webtranslator.h"
|
translator_ = std::make_unique<Translator>(*this);
|
||||||
#include "resultdialog.h"
|
corrector_ = std::make_unique<Corrector>(*this);
|
||||||
#include "languagehelper.h"
|
representer_ = std::make_unique<Representer>(*this, *tray_);
|
||||||
#include "stassert.h"
|
|
||||||
#include "utils.h"
|
|
||||||
#include "updater.h"
|
|
||||||
|
|
||||||
Manager::Manager (QObject *parent) :
|
qRegisterMetaType<TaskPtr>();
|
||||||
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>();
|
|
||||||
|
|
||||||
// Recognizer
|
Settings settings;
|
||||||
Recognizer *recognizer = new Recognizer;
|
settings.load();
|
||||||
connect (this, SIGNAL (requestRecognize (ProcessingItem)),
|
updateSettings(settings);
|
||||||
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 ();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QMenu * Manager::trayContextMenu () {
|
Manager::~Manager() = default;
|
||||||
QMenu *menu = new QMenu ();
|
|
||||||
captureAction_ = menu->addAction (tr ("Захват"), this, SLOT (capture ()));
|
void Manager::updateSettings(const Settings &settings)
|
||||||
repeatCaptureAction_ = menu->addAction (tr ("Повторить захват"),
|
{
|
||||||
this, SLOT (repeatCapture ()));
|
LTRACE() << "updateSettings";
|
||||||
QMenu *translateMenu = menu->addMenu (tr ("Результат"));
|
tray_->updateSettings(settings);
|
||||||
repeatAction_ = translateMenu->addAction (tr ("Показать"), this,
|
capturer_->updateSettings(settings);
|
||||||
SLOT (showLast ()));
|
recognizer_->updateSettings(settings);
|
||||||
clipboardAction_ = translateMenu->addAction (tr ("В буфер"), this,
|
translator_->updateSettings(settings);
|
||||||
SLOT (copyLastToClipboard ()));
|
corrector_->updateSettings(settings);
|
||||||
menu->addAction (tr ("Настройки"), this, SLOT (settings ()));
|
representer_->updateSettings(settings);
|
||||||
menu->addAction (tr ("О программе"), this, SLOT (about ()));
|
|
||||||
menu->addAction (tr ("Выход"), this, SLOT (close ()));
|
|
||||||
return menu;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Manager::updateActionsState (bool isEnabled) {
|
void Manager::finishTask(const TaskPtr &task)
|
||||||
#ifdef Q_OS_LINUX
|
{
|
||||||
// Avoid unneeded tray blinking (required to update context menu).
|
SOFT_ASSERT(task, return );
|
||||||
QList<QAction *> actions;
|
LTRACE() << "finishTask" << task->captured << task->error;
|
||||||
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::applySettings () {
|
--activeTaskCount_;
|
||||||
#define GET(NAME) settings.value (settings_names::NAME, settings_values::NAME)
|
tray_->setActiveTaskCount(activeTaskCount_);
|
||||||
QSettings settings;
|
|
||||||
settings.beginGroup (settings_names::guiGroup);
|
|
||||||
|
|
||||||
QStringList globalActionsFailed;
|
last_ = task;
|
||||||
Q_CHECK_PTR (captureAction_);
|
tray_->setTaskActionsEnabled(last_->isValid());
|
||||||
GlobalActionHelper::removeGlobal (captureAction_);
|
|
||||||
captureAction_->setShortcut (GET (captureHotkey).toString ());
|
|
||||||
if (!GlobalActionHelper::makeGlobal (captureAction_)) {
|
|
||||||
globalActionsFailed << captureAction_->shortcut ().toString ();
|
|
||||||
}
|
|
||||||
|
|
||||||
Q_CHECK_PTR (repeatCaptureAction_);
|
if (!task->isValid()) {
|
||||||
GlobalActionHelper::removeGlobal (repeatCaptureAction_);
|
tray_->showError(task->error);
|
||||||
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 ("Не найден подходящий язык распознавания."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (item.sourceLanguage.isEmpty ()) {
|
|
||||||
item.sourceLanguage = dictionary_->ocrToTranslateCodes (item.ocrLanguage);
|
|
||||||
}
|
|
||||||
emit requestRecognize (item);
|
|
||||||
++itemProcessingCount_;
|
|
||||||
updateNormalIcon ();
|
|
||||||
if (!(item.modifiers & Qt::ControlModifier)) {
|
|
||||||
emit closeSelections ();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Manager::repeatCapture () {
|
|
||||||
if (selections_.isEmpty ()) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
QList<QScreen *> screens = QApplication::screens ();
|
|
||||||
for (QScreen *screen: screens) {
|
tray_->showSuccess();
|
||||||
QString name = screen->name ();
|
|
||||||
if (!selections_.contains (name)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
SelectionDialog *selection = selections_[name];
|
|
||||||
selection->show ();
|
|
||||||
selection->activateWindow ();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Manager::settings () {
|
void Manager::captured(const TaskPtr &task)
|
||||||
SettingsEditor editor (*dictionary_);
|
{
|
||||||
editor.setWindowIcon (trayIcon_->icon ());
|
tray_->blockActions(false);
|
||||||
connect (&editor, SIGNAL (settingsEdited ()), SIGNAL (settingsEdited ()));
|
|
||||||
connect (&editor, SIGNAL (updateCheckRequested ()), SLOT (checkForUpdates ()));
|
|
||||||
updateActionsState (false);
|
|
||||||
editor.exec ();
|
|
||||||
updateActionsState (true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Manager::close () {
|
SOFT_ASSERT(task, return );
|
||||||
QApplication::quit ();
|
LTRACE() << "captured" << task->captured << task->error;
|
||||||
}
|
|
||||||
|
|
||||||
void Manager::about () {
|
++activeTaskCount_;
|
||||||
QString text = tr ("Программа для распознавания текста на экране.\n" \
|
tray_->setActiveTaskCount(activeTaskCount_);
|
||||||
"Создана с использованием 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"
|
|
||||||
"");
|
|
||||||
|
|
||||||
QMessageBox message (QMessageBox::Information, tr ("О программе"), text + tips,
|
if (!task->isValid()) {
|
||||||
QMessageBox::Ok);
|
finishTask(task);
|
||||||
message.setIconPixmap (trayIcon_->icon ().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;
|
return;
|
||||||
}
|
}
|
||||||
changeIcon (IconTypeSuccess);
|
|
||||||
if (useResultDialog_) {
|
recognizer_->recognize(task);
|
||||||
resultDialog_->showResult (item);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
QString message = item.recognized + " - " + item.translated;
|
|
||||||
trayIcon_->showMessage (tr ("Результат"), message, QSystemTrayIcon::Information);
|
|
||||||
}
|
|
||||||
updateActionsState ();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Manager::showError (QString text) {
|
void Manager::captureCanceled()
|
||||||
qCritical () << text;
|
{
|
||||||
changeIcon (IconTypeError);
|
tray_->blockActions(false);
|
||||||
trayIcon_->showMessage (tr ("Ошибка"), text, QSystemTrayIcon::Critical);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Manager::changeIcon (int iconType, int timeoutMsec) {
|
void Manager::recognized(const TaskPtr &task)
|
||||||
QString fileName;
|
{
|
||||||
switch (iconType) {
|
SOFT_ASSERT(task, return );
|
||||||
case IconTypeSuccess:
|
LTRACE() << "recognized" << task->recognized << task->error;
|
||||||
fileName = ":/images/STIconGreen.png";
|
|
||||||
break;
|
if (!task->isValid()) {
|
||||||
case IconTypeError:
|
finishTask(task);
|
||||||
fileName = ":/images/STIconRed.png";
|
return;
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
trayIcon_->setIcon (QIcon (fileName));
|
|
||||||
if (timeoutMsec > 0) {
|
|
||||||
QTimer::singleShot (timeoutMsec, this, SLOT (updateNormalIcon ()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
corrector_->correct(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Manager::updateNormalIcon () {
|
void Manager::corrected(const TaskPtr &task)
|
||||||
QString fileName = itemProcessingCount_ > 0
|
{
|
||||||
? ":/images/STIconOrange.png" : ":/images/STIconBlue.png";
|
SOFT_ASSERT(task, return );
|
||||||
trayIcon_->setIcon (QIcon (fileName));
|
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(QIcon(":/icons/app.png").pixmap(QSize(64, 64)));
|
||||||
|
message.exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Manager::quit()
|
||||||
|
{
|
||||||
|
QApplication::quit();
|
||||||
}
|
}
|
||||||
|
108
src/manager.h
@ -1,84 +1,40 @@
|
|||||||
#ifndef MANAGER_H
|
#pragma once
|
||||||
#define MANAGER_H
|
|
||||||
|
|
||||||
#include <QPixmap>
|
#include "stfwd.h"
|
||||||
#include <QSystemTrayIcon>
|
|
||||||
#include <QMap>
|
|
||||||
|
|
||||||
#include "processingitem.h"
|
class QString;
|
||||||
|
|
||||||
class QAction;
|
class Manager
|
||||||
class QMenu;
|
{
|
||||||
|
public:
|
||||||
|
Manager();
|
||||||
|
~Manager();
|
||||||
|
|
||||||
class SelectionDialog;
|
void captured(const TaskPtr &task);
|
||||||
class ResultDialog;
|
void captureCanceled();
|
||||||
class LanguageHelper;
|
void recognized(const TaskPtr &task);
|
||||||
class Updater;
|
void corrected(const TaskPtr &task);
|
||||||
|
void translated(const TaskPtr &task);
|
||||||
|
|
||||||
class Manager : public QObject {
|
void fatalError(const QString &text);
|
||||||
Q_OBJECT
|
void capture();
|
||||||
|
void repeatCapture();
|
||||||
|
void showLast();
|
||||||
|
void settings();
|
||||||
|
void copyLastToClipboard();
|
||||||
|
void about();
|
||||||
|
void quit();
|
||||||
|
|
||||||
enum IconType {
|
private:
|
||||||
IconTypeNormal, IconTypeWorking, IconTypeError, IconTypeSuccess
|
void updateSettings(const Settings &settings);
|
||||||
};
|
void finishTask(const TaskPtr &task);
|
||||||
|
|
||||||
public:
|
std::unique_ptr<TrayIcon> tray_;
|
||||||
explicit Manager (QObject *parent = 0);
|
std::unique_ptr<Capturer> capturer_;
|
||||||
~Manager ();
|
std::unique_ptr<Recognizer> recognizer_;
|
||||||
|
std::unique_ptr<Corrector> corrector_;
|
||||||
signals:
|
std::unique_ptr<Translator> translator_;
|
||||||
void requestRecognize (ProcessingItem item);
|
std::unique_ptr<Representer> representer_;
|
||||||
void requestTranslate (ProcessingItem item);
|
TaskPtr last_;
|
||||||
void closeSelections ();
|
int activeTaskCount_{0};
|
||||||
void settingsEdited ();
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void capture ();
|
|
||||||
void repeatCapture ();
|
|
||||||
void settings ();
|
|
||||||
void close ();
|
|
||||||
void about ();
|
|
||||||
void showLast ();
|
|
||||||
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 ();
|
|
||||||
|
|
||||||
private:
|
|
||||||
QMenu * trayContextMenu ();
|
|
||||||
void updateActionsState (bool isEnabled = true);
|
|
||||||
void changeIcon (int iconType, int timeoutMsec = 3000);
|
|
||||||
void scheduleUpdate (bool justChecked = false);
|
|
||||||
|
|
||||||
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_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // MANAGER_H
|
|
||||||
|
48
src/ocr/recognizer.cpp
Normal 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
@ -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_;
|
||||||
|
};
|
46
src/ocr/recognizerworker.cpp
Normal 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();
|
||||||
|
}
|
24
src/ocr/recognizerworker.h
Normal 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
@ -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
@ -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_;
|
||||||
|
};
|
@ -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;
|
|
||||||
}
|
|
@ -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
|
|
@ -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);
|
|
||||||
}
|
|
@ -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
|
|
@ -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) {
|
|
||||||
}
|
|
@ -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
|
|
42
src/represent/representer.cpp
Normal 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);
|
||||||
|
}
|
25
src/represent/representer.h
Normal 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_;
|
||||||
|
};
|
116
src/represent/resultwidget.cpp
Normal 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);
|
||||||
|
}
|
24
src/represent/resultwidget.h
Normal 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_;
|
||||||
|
};
|
@ -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 ();
|
|
||||||
}
|
|
@ -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
|
|
@ -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>
|
|
@ -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 ();
|
|
||||||
}
|
|
@ -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
|
|
@ -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>
|
|
131
src/service/apptranslator.cpp
Normal 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();
|
||||||
|
//}
|
25
src/service/apptranslator.h
Normal 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
@ -0,0 +1,6 @@
|
|||||||
|
#include "debug.h"
|
||||||
|
|
||||||
|
namespace debug
|
||||||
|
{
|
||||||
|
std::atomic_bool isTrace;
|
||||||
|
}
|
71
src/service/debug.h
Normal 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
|
481
src/service/globalaction.cpp
Normal 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
|
29
src/service/globalaction.h
Normal 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;
|
||||||
|
};
|
31
src/service/singleapplication.cpp
Normal 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();
|
||||||
|
}
|
14
src/service/singleapplication.h
Normal 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
@ -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
@ -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
@ -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();
|
||||||
|
}
|
117
src/settings.h
@ -1,81 +1,54 @@
|
|||||||
#ifndef SETTINGS_H
|
#pragma once
|
||||||
#define SETTINGS_H
|
|
||||||
|
|
||||||
#include <QString>
|
#include "stfwd.h"
|
||||||
|
|
||||||
namespace settings_names {
|
#include <QStringList>
|
||||||
//! 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";
|
|
||||||
|
|
||||||
//! Recognition
|
#include <chrono>
|
||||||
const QString recogntionGroup = "Recognition";
|
|
||||||
const QString tessDataPlace = "tessdata_dir";
|
|
||||||
const QString ocrLanguage = "language";
|
|
||||||
const QString imageScale = "image_scale";
|
|
||||||
|
|
||||||
//! Translation
|
enum class ResultMode { Widget, Tooltip };
|
||||||
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";
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace settings_values {
|
struct Substitution {
|
||||||
const QString appName = "ScreenTranslator";
|
QString source;
|
||||||
const QString companyName = "Gres";
|
QString target;
|
||||||
|
};
|
||||||
|
using Substitutions = std::unordered_multimap<LanguageId, Substitution>;
|
||||||
|
|
||||||
//! UI
|
class Settings
|
||||||
const QString captureHotkey = "Ctrl+Alt+Z";
|
{
|
||||||
const QString repeatCaptureHotkey = "Ctrl+Alt+S";
|
public:
|
||||||
const QString repeatHotkey = "Ctrl+Alt+X";
|
void save();
|
||||||
const QString clipboardHotkey = "Ctrl+Alt+C";
|
void load();
|
||||||
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 = "";
|
|
||||||
|
|
||||||
//! Recognition
|
QString captureHotkey{"Ctrl+Alt+Z"};
|
||||||
#if defined(Q_OS_LINUX)
|
QString repeatCaptureHotkey{"Ctrl+Alt+S"};
|
||||||
const QString tessDataPlace = "/usr/share/tesseract-ocr/";
|
QString showLastHotkey{"Ctrl+Alt+X"};
|
||||||
#else
|
QString clipboardHotkey{"Ctrl+Alt+C"};
|
||||||
const QString tessDataPlace = "./";
|
|
||||||
#endif
|
|
||||||
const QString ocrLanguage = "eng";
|
|
||||||
const int imageScale = 5;
|
|
||||||
|
|
||||||
//! Translation
|
int proxyType{0};
|
||||||
const bool doTranslation = true;
|
QString proxyHostName{""};
|
||||||
const bool ignoreSslErrors = false;
|
int proxyPort{8080};
|
||||||
const bool forceRotateTranslators = false;
|
QString proxyUser{""};
|
||||||
const QString sourceLanguage = "auto";
|
QString proxyPassword{""};
|
||||||
const QString translationLanguage = "ru";
|
bool proxySavePassword{false};
|
||||||
const int translationTimeout = 15; // secs
|
|
||||||
const bool translationDebugMode = false;
|
|
||||||
const QString translators = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
#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
|
||||||
|
};
|
||||||
|
@ -1,288 +1,204 @@
|
|||||||
#include "settingseditor.h"
|
#include "settingseditor.h"
|
||||||
#include "ui_settingseditor.h"
|
#include "languagecodes.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 "settings.h"
|
#include "settings.h"
|
||||||
|
#include "ui_settingseditor.h"
|
||||||
|
#include "widgetstate.h"
|
||||||
|
|
||||||
SettingsEditor::SettingsEditor (const LanguageHelper &dictionary, QWidget *parent) :
|
#include <QDir>
|
||||||
QDialog (parent),
|
#include <QFileDialog>
|
||||||
ui (new Ui::SettingsEditor), translatorHelper_ (new TranslatorHelper),
|
#include <QNetworkProxy>
|
||||||
recognizerHelper_ (new RecognizerHelper), dictionary_ (dictionary),
|
#include <QRegExpValidator>
|
||||||
buttonGroup_ (new QButtonGroup (this)) {
|
#include <QSettings>
|
||||||
ui->setupUi (this);
|
#include <QStringListModel>
|
||||||
|
|
||||||
buttonGroup_->addButton (ui->trayRadio, 0);
|
SettingsEditor::SettingsEditor()
|
||||||
buttonGroup_->addButton (ui->dialogRadio, 1);
|
: ui(new Ui::SettingsEditor)
|
||||||
connect (ui->updateButton, SIGNAL (clicked (bool)), SIGNAL (updateCheckRequested ()));
|
{
|
||||||
QStringList updateTypes = QStringList () << tr ("Никогда") << tr ("Ежедневно")
|
ui->setupUi(this);
|
||||||
<< tr ("Еженедельно") << tr ("Ежемесячно");
|
|
||||||
ui->updateCombo->addItems (updateTypes);
|
|
||||||
|
|
||||||
connect (ui->tessdataButton, SIGNAL (clicked ()), SLOT (openTessdataDialog ()));
|
{
|
||||||
connect (ui->tessdataEdit, SIGNAL (textChanged (const QString&)),
|
auto model = new QStringListModel(this);
|
||||||
SLOT (initOcrLangCombo (const QString&)));
|
model->setStringList({tr("General"), tr("Recognition"), tr("Correction"),
|
||||||
|
tr("Translation"), tr("Representation"),
|
||||||
connect (ui->recognizerFixTable, SIGNAL (itemChanged (QTableWidgetItem*)),
|
tr("Update")});
|
||||||
SLOT (recognizerFixTableItemChanged (QTableWidgetItem*)));
|
ui->pagesList->setModel(model);
|
||||||
|
auto selection = ui->pagesList->selectionModel();
|
||||||
ui->translateLangCombo->addItems (dictionary_.translateLanguagesUi ());
|
connect(selection, &QItemSelectionModel::currentRowChanged, //
|
||||||
|
this, &SettingsEditor::updateCurrentPage);
|
||||||
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)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QRegExp urlRegexp (R"(^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$)");
|
// general
|
||||||
ui->proxyHostEdit->setValidator (new QRegExpValidator (urlRegexp, ui->proxyHostEdit));
|
// QMap<QNetworkProxy::ProxyType, QString> proxyTypeNames;
|
||||||
ui->proxyPassEdit->setEchoMode (QLineEdit::PasswordEchoOnEdit);
|
// 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 ();
|
// QRegExp urlRegexp(
|
||||||
loadState ();
|
// 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 () {
|
SettingsEditor::~SettingsEditor()
|
||||||
saveState ();
|
{
|
||||||
delete recognizerHelper_;
|
|
||||||
delete translatorHelper_;
|
|
||||||
delete ui;
|
delete ui;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SettingsEditor::done (int result) {
|
Settings SettingsEditor::settings() const
|
||||||
if (result == QDialog::Accepted) {
|
{
|
||||||
saveSettings ();
|
Settings settings;
|
||||||
emit settingsEdited ();
|
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());
|
||||||
}
|
}
|
||||||
QDialog::done (result);
|
|
||||||
|
settings.resultShowType =
|
||||||
|
ui->trayRadio->isChecked() ? ResultMode::Tooltip : ResultMode::Widget;
|
||||||
|
return settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SettingsEditor::saveSettings () const {
|
void SettingsEditor::setSettings(const Settings &settings)
|
||||||
using namespace settings_names;
|
{
|
||||||
QSettings settings;
|
ui->captureEdit->setKeySequence(settings.captureHotkey);
|
||||||
settings.beginGroup (guiGroup);
|
ui->repeatCaptureEdit->setKeySequence(settings.repeatCaptureHotkey);
|
||||||
settings.setValue (captureHotkey, ui->captureEdit->keySequence ().toString ());
|
ui->repeatEdit->setKeySequence(settings.showLastHotkey);
|
||||||
settings.setValue (repeatCaptureHotkey, ui->repeatCaptureEdit->keySequence ().toString ());
|
ui->clipboardEdit->setKeySequence(settings.clipboardHotkey);
|
||||||
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 ();
|
|
||||||
|
|
||||||
|
ui->tessdataEdit->setText(settings.tessdataPath);
|
||||||
|
updateTesseractLanguages();
|
||||||
|
|
||||||
settings.beginGroup (recogntionGroup);
|
ui->doTranslationCheck->setChecked(settings.doTranslation);
|
||||||
settings.setValue (tessDataPlace, ui->tessdataEdit->text ());
|
ui->ignoreSslCheck->setChecked(settings.ignoreSslErrors);
|
||||||
QString ocrLanguageVal = dictionary_.ocrUiToCode (ui->ocrLangCombo->currentText ());
|
ui->translatorDebugCheck->setChecked(settings.debugMode);
|
||||||
settings.setValue (ocrLanguage, ocrLanguageVal);
|
ui->translateTimeoutSpin->setValue(settings.translationTimeout.count());
|
||||||
settings.setValue (imageScale, ui->imageScaleSpin->value ());
|
translatorsDir_ = settings.translatorsDir;
|
||||||
|
updateTranslators(settings.translators);
|
||||||
|
|
||||||
{ //Recognizer substitutions
|
ui->trayRadio->setChecked(settings.resultShowType == ResultMode::Tooltip);
|
||||||
RecognizerHelper::Subs subs;
|
ui->dialogRadio->setChecked(settings.resultShowType == ResultMode::Widget);
|
||||||
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.endGroup ();
|
|
||||||
|
|
||||||
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 ());
|
|
||||||
|
|
||||||
{//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);
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.endGroup ();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SettingsEditor::openTessdataDialog () {
|
void SettingsEditor::updateCurrentPage()
|
||||||
QString path = QFileDialog::getExistingDirectory (this, tr ("Path to tessdata"));
|
{
|
||||||
if (path.isEmpty ()) {
|
ui->pagesView->setCurrentIndex(ui->pagesList->currentIndex().row());
|
||||||
|
}
|
||||||
|
|
||||||
|
void SettingsEditor::openTessdataDialog()
|
||||||
|
{
|
||||||
|
const auto path =
|
||||||
|
QFileDialog::getExistingDirectory(this, tr("Path to tessdata"));
|
||||||
|
|
||||||
|
if (path.isEmpty())
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
ui->tessdataEdit->setText (path);
|
ui->tessdataEdit->setText(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SettingsEditor::loadSettings () {
|
void SettingsEditor::updateTesseractLanguages()
|
||||||
#define GET(FIELD) settings.value (settings_names::FIELD, settings_values::FIELD)
|
{
|
||||||
QSettings settings;
|
ui->tesseractLangCombo->clear();
|
||||||
|
ui->correctLangCombo->clear();
|
||||||
|
|
||||||
settings.beginGroup (settings_names::guiGroup);
|
QDir dir(ui->tessdataEdit->text());
|
||||||
ui->captureEdit->setKeySequence (QKeySequence (GET (captureHotkey).toString ()));
|
if (!dir.exists())
|
||||||
ui->repeatCaptureEdit->setKeySequence (QKeySequence (GET (repeatCaptureHotkey).toString ()));
|
return;
|
||||||
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 ();
|
|
||||||
|
|
||||||
settings.beginGroup (settings_names::recogntionGroup);
|
LanguageIds names;
|
||||||
ui->tessdataEdit->setText (GET (tessDataPlace).toString ());
|
LanguageCodes languages;
|
||||||
QString ocrLanguage = dictionary_.ocrCodeToUi (GET (ocrLanguage).toString ());
|
|
||||||
ui->ocrLangCombo->setCurrentText (ocrLanguage);
|
|
||||||
ui->imageScaleSpin->setValue (GET (imageScale).toInt ());
|
|
||||||
|
|
||||||
{//Recognizer substitutions
|
const auto files = dir.entryList({"*.traineddata"}, QDir::Files);
|
||||||
recognizerHelper_->load ();
|
for (const auto &file : files) {
|
||||||
RecognizerHelper::Subs subs = recognizerHelper_->subs ();
|
const auto lang = file.left(file.indexOf("."));
|
||||||
ui->recognizerFixTable->setRowCount (subs.size ());
|
if (const auto bundle = languages.findByTesseract(lang))
|
||||||
int row = 0;
|
names.append(bundle->name);
|
||||||
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 ();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
settings.endGroup ();
|
if (names.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::sort(names.begin(), names.end());
|
||||||
settings.beginGroup (settings_names::translationGroup);
|
ui->tesseractLangCombo->addItems(names);
|
||||||
ui->doTranslationCheck->setChecked (GET (doTranslation).toBool ());
|
ui->correctLangCombo->addItems(names);
|
||||||
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 ());
|
|
||||||
|
|
||||||
{// 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.endGroup ();
|
|
||||||
#undef GET
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SettingsEditor::initSubsTableRow (int row, const QString &languageCode) {
|
void SettingsEditor::updateCorrectionsTable()
|
||||||
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 {
|
void SettingsEditor::updateTranslators(const QStringList &enabled)
|
||||||
QSettings settings;
|
{
|
||||||
settings.beginGroup (settings_names::guiGroup);
|
ui->translatorList->clear();
|
||||||
settings.setValue (objectName () + "_" + settings_names::geometry, saveGeometry ());
|
|
||||||
}
|
|
||||||
|
|
||||||
void SettingsEditor::loadState () {
|
QDir dir(translatorsDir_);
|
||||||
QSettings settings;
|
if (!dir.exists())
|
||||||
settings.beginGroup (settings_names::guiGroup);
|
return;
|
||||||
restoreGeometry (settings.value (objectName () + "_" + settings_names::geometry).toByteArray ());
|
|
||||||
}
|
|
||||||
|
|
||||||
void SettingsEditor::initOcrLangCombo (const QString &path) {
|
auto files = dir.entryList({"*.js"}, QDir::Files);
|
||||||
ui->ocrLangCombo->clear ();
|
std::sort(files.begin(), files.end());
|
||||||
ui->ocrLangCombo->addItems (dictionary_.availableOcrLanguagesUi (path));
|
ui->translatorList->addItems(files);
|
||||||
}
|
|
||||||
|
|
||||||
void SettingsEditor::recognizerFixTableItemChanged (QTableWidgetItem *item) {
|
for (auto i = 0, end = ui->translatorList->count(); i < end; ++i) {
|
||||||
ST_ASSERT (item->column () < 3);
|
auto item = ui->translatorList->item(i);
|
||||||
int row = item->row ();
|
item->setCheckState(enabled.contains(item->text()) ? Qt::Checked
|
||||||
QTableWidget *t = ui->recognizerFixTable;
|
: Qt::Unchecked);
|
||||||
#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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SettingsEditor::updateTranslationLanguages()
|
||||||
|
{
|
||||||
|
LanguageIds names;
|
||||||
|
LanguageCodes languages;
|
||||||
|
|
||||||
|
for (const auto &bundle : languages.all()) {
|
||||||
|
if (!bundle.second.iso639_1.isEmpty())
|
||||||
|
names.append(bundle.second.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
ui->translateLangCombo->clear();
|
||||||
|
std::sort(names.begin(), names.end());
|
||||||
|
ui->translateLangCombo->addItems(names);
|
||||||
|
}
|
||||||
|
@ -1,54 +1,33 @@
|
|||||||
#ifndef SETTINGSEDITOR_H
|
#pragma once
|
||||||
#define SETTINGSEDITOR_H
|
|
||||||
|
|
||||||
#include <QDialog>
|
#include <QDialog>
|
||||||
#include <QButtonGroup>
|
|
||||||
#include <QMap>
|
|
||||||
|
|
||||||
class QTableWidgetItem;
|
#include "settings.h"
|
||||||
namespace Ui {
|
|
||||||
class SettingsEditor;
|
namespace Ui
|
||||||
|
{
|
||||||
|
class SettingsEditor;
|
||||||
}
|
}
|
||||||
class LanguageHelper;
|
|
||||||
class TranslatorHelper;
|
|
||||||
class RecognizerHelper;
|
|
||||||
|
|
||||||
class SettingsEditor : public QDialog {
|
class SettingsEditor : public QDialog
|
||||||
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
enum SubsCol {
|
public:
|
||||||
SubsColLanguage = 0, SubsColSource, SubsColTarget
|
explicit SettingsEditor();
|
||||||
};
|
~SettingsEditor();
|
||||||
|
|
||||||
public:
|
Settings settings() const;
|
||||||
explicit SettingsEditor (const LanguageHelper &dictionary, QWidget *parent = 0);
|
void setSettings(const Settings &settings);
|
||||||
~SettingsEditor ();
|
|
||||||
|
|
||||||
signals:
|
private:
|
||||||
void settingsEdited ();
|
void updateCurrentPage();
|
||||||
void updateCheckRequested ();
|
void openTessdataDialog();
|
||||||
|
void updateTesseractLanguages();
|
||||||
|
void updateCorrectionsTable();
|
||||||
|
void updateTranslators(const QStringList &enabled);
|
||||||
|
void updateTranslationLanguages();
|
||||||
|
|
||||||
public slots:
|
Ui::SettingsEditor *ui;
|
||||||
void done (int result);
|
QString translatorsDir_;
|
||||||
|
|
||||||
private slots:
|
|
||||||
void saveSettings () const;
|
|
||||||
void openTessdataDialog ();
|
|
||||||
void initOcrLangCombo (const QString &path);
|
|
||||||
void recognizerFixTableItemChanged (QTableWidgetItem *item);
|
|
||||||
|
|
||||||
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_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // SETTINGSEDITOR_H
|
|
||||||
|
@ -6,15 +6,25 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>553</width>
|
<width>889</width>
|
||||||
<height>483</height>
|
<height>549</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
<string>Настройки</string>
|
<string>Настройки</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout" name="gridLayout_6">
|
<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">
|
<widget class="QDialogButtonBox" name="buttonBox">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Horizontal</enum>
|
<enum>Qt::Horizontal</enum>
|
||||||
@ -24,20 +34,23 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="0" column="0" colspan="2">
|
<item row="0" column="1" colspan="3">
|
||||||
<widget class="QTabWidget" name="tabWidget">
|
<widget class="QStackedWidget" name="pagesView">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
<property name="currentIndex">
|
<property name="currentIndex">
|
||||||
<number>0</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
<widget class="QWidget" name="commonTab">
|
<widget class="QWidget" name="pageGeneral">
|
||||||
<attribute name="title">
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
<string>Общее</string>
|
<item>
|
||||||
</attribute>
|
|
||||||
<layout class="QGridLayout" name="gridLayout_8">
|
|
||||||
<item row="0" column="0">
|
|
||||||
<widget class="QGroupBox" name="groupBox">
|
<widget class="QGroupBox" name="groupBox">
|
||||||
<property name="title">
|
<property name="title">
|
||||||
<string>Горячие клавиши</string>
|
<string>Shortcuts</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout" name="gridLayout">
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
<item row="0" column="0">
|
<item row="0" column="0">
|
||||||
@ -46,7 +59,7 @@
|
|||||||
<string><html><head/><body><p>Сочетание клавиш для перехода в режим захвата.</p></body></html></string>
|
<string><html><head/><body><p>Сочетание клавиш для перехода в режим захвата.</p></body></html></string>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Захватить</string>
|
<string>Capture</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@ -59,7 +72,7 @@
|
|||||||
<string><html><head/><body><p>Сочетание клавиш для перехода в режим захвата, но с использованием последнего использованного, а не текущего, изображения.</p></body></html></string>
|
<string><html><head/><body><p>Сочетание клавиш для перехода в режим захвата, но с использованием последнего использованного, а не текущего, изображения.</p></body></html></string>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Захватить повторно</string>
|
<string>Repeat capture</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@ -72,7 +85,7 @@
|
|||||||
<string><html><head/><body><p>Сочетание клавиш для повторного отображения последнего результата.</p></body></html></string>
|
<string><html><head/><body><p>Сочетание клавиш для повторного отображения последнего результата.</p></body></html></string>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Показать</string>
|
<string>Show last result</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@ -85,7 +98,7 @@
|
|||||||
<string><html><head/><body><p>Сочетание клавиш для копирования последнего результата в буфер обмена.</p></body></html></string>
|
<string><html><head/><body><p>Сочетание клавиш для копирования последнего результата в буфер обмена.</p></body></html></string>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Скопировать</string>
|
<string>Copy result to clipboard</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@ -95,16 +108,16 @@
|
|||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="0">
|
<item>
|
||||||
<widget class="QGroupBox" name="groupBox_2">
|
<widget class="QGroupBox" name="groupBox_2">
|
||||||
<property name="title">
|
<property name="title">
|
||||||
<string>Прокси</string>
|
<string>Proxy</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout" name="gridLayout_3">
|
<layout class="QGridLayout" name="gridLayout_3">
|
||||||
<item row="0" column="0">
|
<item row="0" column="0">
|
||||||
<widget class="QLabel" name="label_12">
|
<widget class="QLabel" name="label_12">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Тип:</string>
|
<string>Type:</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@ -114,7 +127,7 @@
|
|||||||
<item row="0" column="2">
|
<item row="0" column="2">
|
||||||
<widget class="QLabel" name="label_15">
|
<widget class="QLabel" name="label_15">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Пользователь:</string>
|
<string>User:</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@ -124,7 +137,7 @@
|
|||||||
<item row="1" column="0">
|
<item row="1" column="0">
|
||||||
<widget class="QLabel" name="label_13">
|
<widget class="QLabel" name="label_13">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Адрес:</string>
|
<string>Address:</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@ -134,7 +147,7 @@
|
|||||||
<item row="1" column="2">
|
<item row="1" column="2">
|
||||||
<widget class="QLabel" name="label_16">
|
<widget class="QLabel" name="label_16">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Пароль:</string>
|
<string>Password:</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@ -144,7 +157,7 @@
|
|||||||
<item row="2" column="0">
|
<item row="2" column="0">
|
||||||
<widget class="QLabel" name="label_14">
|
<widget class="QLabel" name="label_14">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Порт:</string>
|
<string>Port:</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@ -161,113 +174,63 @@
|
|||||||
<item row="2" column="2" colspan="2">
|
<item row="2" column="2" colspan="2">
|
||||||
<widget class="QCheckBox" name="proxySaveCheck">
|
<widget class="QCheckBox" name="proxySaveCheck">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Сохранять пароль (небезопасно)</string>
|
<string>save password (unsafe)</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="0">
|
<item>
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
<spacer name="verticalSpacer_4">
|
||||||
<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>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</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">
|
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Vertical</enum>
|
<enum>Qt::Vertical</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizeHint" stdset="0">
|
<property name="sizeHint" stdset="0">
|
||||||
<size>
|
<size>
|
||||||
<width>20</width>
|
<width>20</width>
|
||||||
<height>270</height>
|
<height>40</height>
|
||||||
</size>
|
</size>
|
||||||
</property>
|
</property>
|
||||||
</spacer>
|
</spacer>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QWidget" name="recognizeTab">
|
<widget class="QWidget" name="pageRecognize">
|
||||||
<attribute name="title">
|
|
||||||
<string>Распознавание</string>
|
|
||||||
</attribute>
|
|
||||||
<layout class="QGridLayout" name="gridLayout_2">
|
<layout class="QGridLayout" name="gridLayout_2">
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLabel" name="label_4">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string><html><head/><body><p>Заполняется на основании содержания tessdata</p></body></html></string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>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">
|
<item row="0" column="0">
|
||||||
<widget class="QLabel" name="label_2">
|
<widget class="QLabel" name="label_2">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
@ -278,66 +241,50 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="0" column="1">
|
<item row="0" column="1" colspan="2">
|
||||||
<widget class="QLineEdit" name="tessdataEdit">
|
<widget class="QLineEdit" name="tessdataEdit">
|
||||||
<property name="placeholderText">
|
<property name="placeholderText">
|
||||||
<string>Enter path</string>
|
<string>Enter path</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="0" column="2">
|
<item row="1" column="1" colspan="3">
|
||||||
<widget class="QToolButton" name="tessdataButton">
|
<widget class="QComboBox" name="tesseractLangCombo"/>
|
||||||
<property name="text">
|
|
||||||
<string>...</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="0">
|
</layout>
|
||||||
<widget class="QLabel" name="label_4">
|
</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">
|
<property name="toolTip">
|
||||||
<string><html><head/><body><p>Заполняется на основании содержания tessdata</p></body></html></string>
|
<string><html><head/><body><p>Заполняется на основании содержания tessdata</p></body></html></string>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Язык распознавания</string>
|
<string>Language:</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="buddy">
|
<property name="buddy">
|
||||||
<cstring>ocrLangCombo</cstring>
|
<cstring>tesseractLangCombo</cstring>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="1" colspan="2">
|
<item row="0" column="1">
|
||||||
<widget class="QComboBox" name="ocrLangCombo"/>
|
<widget class="QComboBox" name="correctLangCombo"/>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="0">
|
<item row="1" column="0" colspan="2">
|
||||||
<widget class="QLabel" name="label_5">
|
|
||||||
<property name="toolTip">
|
|
||||||
<string><html><head/><body><p>Масштабирование изображения для улучшения распознания. Больше - лучше (до определенных пределов), но медленнее и потребляет больше памяти.</p><p>Рекомендуемые значения от 5 до 10.</p></body></html></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">
|
|
||||||
<widget class="QLabel" name="label_11">
|
<widget class="QLabel" name="label_11">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string><html><head/><body><p>Символы, регулярно распознаваемые с ошибками. При обнаружении будут заменены на указанные.</p></body></html></string>
|
<string><html><head/><body><p>Символы, регулярно распознаваемые с ошибками. При обнаружении будут заменены на указанные.</p></body></html></string>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Исправления:</string>
|
<string>Corrections</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="alignment">
|
<property name="alignment">
|
||||||
<set>Qt::AlignCenter</set>
|
<set>Qt::AlignCenter</set>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="4" column="0" colspan="3">
|
<item row="2" column="0" colspan="2">
|
||||||
<widget class="QTableWidget" name="recognizerFixTable">
|
<widget class="QTableWidget" name="recognizerFixTable">
|
||||||
<property name="selectionBehavior">
|
<property name="selectionBehavior">
|
||||||
<enum>QAbstractItemView::SelectRows</enum>
|
<enum>QAbstractItemView::SelectRows</enum>
|
||||||
@ -345,59 +292,45 @@
|
|||||||
<property name="sortingEnabled">
|
<property name="sortingEnabled">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
</property>
|
</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>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QWidget" name="translateTab">
|
<widget class="QWidget" name="pageTranslate">
|
||||||
<attribute name="title">
|
<layout class="QGridLayout" name="gridLayout_9">
|
||||||
<string>Перевод</string>
|
|
||||||
</attribute>
|
|
||||||
<layout class="QGridLayout" name="gridLayout_7">
|
|
||||||
<item row="0" column="0">
|
<item row="0" column="0">
|
||||||
<widget class="QCheckBox" name="doTranslationCheck">
|
<widget class="QCheckBox" name="translatorDebugCheck">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string><html><head/><body><p>Необходимо ли переводить (вкл) распознанный текст.</p></body></html></string>
|
<string><html><head/><body><p>Отображает окно переводчика. Следует использовать только для разработки переводчиков.</p></body></html></string>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Переводить текст</string>
|
<string>Debug mode</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="3" column="0">
|
<item row="3" column="2">
|
||||||
<widget class="QLabel" name="label_9">
|
|
||||||
<property name="toolTip">
|
|
||||||
<string><html><head/><body><p>Максимальное время, которое может быть затрачено на перевод, чтобы он не считался &quot;зависшим&quot;.</p></body></html></string>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Максимальное время перевода:</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="3" column="1" colspan="2">
|
|
||||||
<widget class="QSpinBox" name="translateTimeoutSpin">
|
<widget class="QSpinBox" name="translateTimeoutSpin">
|
||||||
<property name="suffix">
|
<property name="suffix">
|
||||||
<string> сек.</string>
|
<string> secs</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="4" column="1" colspan="2">
|
<item row="2" column="0" colspan="2">
|
||||||
<widget class="QComboBox" name="translateLangCombo"/>
|
<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>
|
||||||
<item row="4" column="0">
|
<item row="4" column="0">
|
||||||
<widget class="QLabel" name="label_6">
|
<widget class="QLabel" name="label_6">
|
||||||
@ -405,23 +338,13 @@
|
|||||||
<string><html><head/><body><p>Язык, на который осуществляется перевод.</p></body></html></string>
|
<string><html><head/><body><p>Язык, на который осуществляется перевод.</p></body></html></string>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Язык результата:</string>
|
<string>Language:</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="buddy">
|
<property name="buddy">
|
||||||
<cstring>translateLangCombo</cstring>
|
<cstring>translateLangCombo</cstring>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</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">
|
<item row="6" column="0" colspan="3">
|
||||||
<widget class="QListWidget" name="translatorList">
|
<widget class="QListWidget" name="translatorList">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
@ -438,29 +361,148 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="0" column="1" colspan="2">
|
<item row="1" column="0" colspan="2">
|
||||||
<widget class="QCheckBox" name="translatorDebugCheck">
|
<widget class="QCheckBox" name="doTranslationCheck">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string><html><head/><body><p>Отображает окно переводчика. Следует использовать только для разработки переводчиков.</p></body></html></string>
|
<string><html><head/><body><p>Необходимо ли переводить (вкл) распознанный текст.</p></body></html></string>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Режим отладки</string>
|
<string>Translate text</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</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><html><head/><body><p>Максимальное время, которое может быть затрачено на перевод, чтобы он не считался &quot;зависшим&quot;.</p></body></html></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">
|
<item row="1" column="0">
|
||||||
<widget class="QCheckBox" name="ignoreSslCheck">
|
<spacer name="verticalSpacer_3">
|
||||||
<property name="text">
|
<property name="orientation">
|
||||||
<string>Игнорировать ошибки SSL</string>
|
<enum>Qt::Vertical</enum>
|
||||||
</property>
|
</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>Check for updates:</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>Check now</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="1" colspan="2">
|
<item>
|
||||||
<widget class="QCheckBox" name="forceRotateCheck">
|
<spacer name="verticalSpacer">
|
||||||
<property name="text">
|
<property name="orientation">
|
||||||
<string>Принудительно менять переводчики</string>
|
<enum>Qt::Vertical</enum>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>389</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
@ -469,29 +511,16 @@
|
|||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<tabstops>
|
<tabstops>
|
||||||
<tabstop>tabWidget</tabstop>
|
|
||||||
<tabstop>captureEdit</tabstop>
|
<tabstop>captureEdit</tabstop>
|
||||||
<tabstop>repeatCaptureEdit</tabstop>
|
<tabstop>repeatCaptureEdit</tabstop>
|
||||||
<tabstop>repeatEdit</tabstop>
|
<tabstop>repeatEdit</tabstop>
|
||||||
<tabstop>clipboardEdit</tabstop>
|
<tabstop>clipboardEdit</tabstop>
|
||||||
<tabstop>trayRadio</tabstop>
|
|
||||||
<tabstop>dialogRadio</tabstop>
|
|
||||||
<tabstop>proxyTypeCombo</tabstop>
|
<tabstop>proxyTypeCombo</tabstop>
|
||||||
<tabstop>proxyHostEdit</tabstop>
|
<tabstop>proxyHostEdit</tabstop>
|
||||||
<tabstop>proxyPortSpin</tabstop>
|
<tabstop>proxyPortSpin</tabstop>
|
||||||
<tabstop>proxyUserEdit</tabstop>
|
<tabstop>proxyUserEdit</tabstop>
|
||||||
<tabstop>proxyPassEdit</tabstop>
|
<tabstop>proxyPassEdit</tabstop>
|
||||||
<tabstop>proxySaveCheck</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>
|
</tabstops>
|
||||||
<resources/>
|
<resources/>
|
||||||
<connections>
|
<connections>
|
||||||
|
@ -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
@ -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
@ -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);
|
210
src/translate/translator.cpp
Normal 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();
|
||||||
|
}
|
37
src/translate/translator.h
Normal 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
@ -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
@ -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};
|
||||||
|
};
|
17
src/translate/webpageproxy.cpp
Normal 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);
|
||||||
|
}
|
23
src/translate/webpageproxy.h
Normal 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_;
|
||||||
|
};
|
@ -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 ();
|
|
||||||
}
|
|
@ -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
@ -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
@ -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};
|
||||||
|
};
|
264
src/updater.cpp
@ -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 ();
|
|
||||||
}
|
|
@ -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
|
|
@ -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;
|
|
||||||
}
|
|
10
src/utils.h
@ -1,10 +0,0 @@
|
|||||||
#ifndef UTILS_H
|
|
||||||
#define UTILS_H
|
|
||||||
|
|
||||||
#include <QString>
|
|
||||||
|
|
||||||
QString encode (const QString &source);
|
|
||||||
|
|
||||||
QList<int> proxyTypeOrder ();
|
|
||||||
|
|
||||||
#endif // UTILS_H
|
|
@ -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);
|
|
||||||
}
|
|
@ -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
|
|
@ -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_;
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
@ -1,50 +1,41 @@
|
|||||||
var isPageLoaded = false;
|
var lastText = '';
|
||||||
var isTranslationFinished = false; // async translation request
|
var active = window.location.href !== "about:blank";
|
||||||
var isScheduled = false;
|
|
||||||
|
|
||||||
function checkFinished () {
|
function checkFinished () {
|
||||||
if (!isPageLoaded || !isTranslationFinished || isScheduled) return;
|
if (!active) return;
|
||||||
isScheduled = true;
|
|
||||||
setTimeout(function () {
|
let spans = [].slice.call (document.querySelectorAll ('span.translation > span, #result_box > span'));
|
||||||
var spans = [].slice.call (document.querySelectorAll (
|
let text = spans.reduce (function (res, i) {
|
||||||
'span.translation > span, #result_box > span'));
|
return res + ' ' + i.innerText;
|
||||||
var text = spans.reduce (function (res, i) {
|
}, '');
|
||||||
return res + ' ' + i.innerText;
|
|
||||||
}, '');
|
if (text === lastText || text === '')
|
||||||
console.log (text);
|
return;
|
||||||
st_wtp.translated (text);
|
|
||||||
isTranslationFinished = isScheduled = false;
|
console.log ('translated text', text, 'old', lastText, 'size', text.length, lastText.length);
|
||||||
}, 2000); // wait for gui fill
|
lastText = text;
|
||||||
|
active = false;
|
||||||
|
proxy.setTranslated (text);
|
||||||
}
|
}
|
||||||
function onResourceLoad (url) {
|
|
||||||
if (url.indexOf ('/translate_a/single') > -1) {
|
function translate (text, from, to){
|
||||||
isTranslationFinished = true;
|
console.log('start translate', text, from, to)
|
||||||
if (isPageLoaded) {
|
active = true;
|
||||||
checkFinished ();
|
|
||||||
}
|
if (window.location.href.indexOf('//translate.google') !== -1
|
||||||
}
|
&& window.location.href.indexOf('&tl='+to+'&') !== -1) {
|
||||||
}
|
document.querySelector('textarea#source').value=text;
|
||||||
st_wtp.resourceLoaded.connect (onResourceLoad);
|
|
||||||
function onPageLoad () {
|
|
||||||
if (window.location.href.indexOf('about:blank') === 0) {
|
|
||||||
translate ();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// var url = 'https://translate.google.com/#auto/' + to + '/' + text;
|
||||||
isPageLoaded = true;
|
let url = 'https://translate.google.com/#view=home&op=translate&sl=auto&tl=' + to + '&text=' + text;
|
||||||
if (isTranslationFinished) {
|
console.log("setting url", url);
|
||||||
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;
|
|
||||||
window.location = encodeURI (url);
|
window.location = encodeURI (url);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
proxy.translate.connect (translate);
|
||||||
|
setInterval(checkFinished, 300);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -1,36 +1,42 @@
|
|||||||
var isPageLoaded = false;
|
var lastText = '';
|
||||||
var isTranslationFinished = true; // async translation request
|
var active = window.location.href !== "about:blank";
|
||||||
var isScheduled = false;
|
|
||||||
|
|
||||||
function checkFinished () {
|
function checkFinished () {
|
||||||
if (!isPageLoaded || !isTranslationFinished || isScheduled) return;
|
if (!active) 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();
|
|
||||||
|
|
||||||
function translate (){
|
var spans = [].slice.call (document.querySelectorAll ('span.translation-chunk'));
|
||||||
var url = 'https://translate.yandex.ru/?lang=' + st_wtp.sourceLanguage + '-' +
|
let text = spans.reduce (function (res, i) {
|
||||||
st_wtp.resultLanguage + '&text=' + st_wtp.sourceText ;
|
return res + ' ' + i.innerText;
|
||||||
url = url.replace(new RegExp(' ','g') , '%20')
|
}, '');
|
||||||
window.location = (url);
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|