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>
|
||||
<qresource prefix="/">
|
||||
<file>translations/translation_en.qm</file>
|
||||
<file>translations/translation_ru.qm</file>
|
||||
<file>images/STIconBlue.png</file>
|
||||
<file>images/STIconGreen.png</file>
|
||||
<file>images/STIconOrange.png</file>
|
||||
<file>images/STIconRed.png</file>
|
||||
<file>version.json</file>
|
||||
</qresource>
|
||||
<qresource prefix="/icons">
|
||||
<file alias="app.png">share/images/STIconBlue.png</file>
|
||||
<file alias="st_success.png">share/images/STIconGreen.png</file>
|
||||
<file alias="st_busy.png">share/images/STIconOrange.png</file>
|
||||
<file alias="st_error.png">share/images/STIconRed.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
92
screen-translator.pro
Normal file
@ -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
|
||||
# include <locale.h>
|
||||
#include <locale.h>
|
||||
#endif
|
||||
|
||||
#include "apptranslator.h"
|
||||
#include "manager.h"
|
||||
#include "singleapplication.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QTranslator>
|
||||
#include <QCommandLineParser>
|
||||
|
||||
#include <qtsingleapplication.h>
|
||||
#define STR2(XXX) #XXX
|
||||
#define STR(XXX) STR2(XXX)
|
||||
|
||||
#include <manager.h>
|
||||
#include <settings.h>
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication a(argc, argv);
|
||||
a.setApplicationName("ScreenTranslator");
|
||||
a.setOrganizationName("Gres");
|
||||
a.setApplicationVersion(STR(VERSION));
|
||||
|
||||
int main (int argc, char *argv[]) {
|
||||
QtSingleApplication a (argc, argv);
|
||||
if (a.sendMessage (QString ())) {
|
||||
return 0;
|
||||
a.setQuitOnLastWindowClosed(false);
|
||||
|
||||
{
|
||||
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.
|
||||
if (translator.load (QLocale::system (), "translation", "_", ":/translations") ||
|
||||
translator.load (":/translations/translation_en")) {
|
||||
a.installTranslator (&translator);
|
||||
{
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription(QObject::tr("OCR and translation tool"));
|
||||
parser.addHelpOption();
|
||||
parser.addVersionOption();
|
||||
|
||||
parser.process(a);
|
||||
}
|
||||
|
||||
SingleApplication guard;
|
||||
if (!guard.isValid())
|
||||
return 1;
|
||||
|
||||
// tesseract recomments
|
||||
setlocale(LC_NUMERIC, "C");
|
||||
|
||||
Manager manager;
|
||||
|
||||
return a.exec ();
|
||||
return a.exec();
|
||||
}
|
||||
|
608
src/manager.cpp
@ -1,463 +1,209 @@
|
||||
#include "manager.h"
|
||||
#include "capturer.h"
|
||||
#include "corrector.h"
|
||||
#include "debug.h"
|
||||
#include "recognizer.h"
|
||||
#include "representer.h"
|
||||
#include "settingseditor.h"
|
||||
#include "task.h"
|
||||
#include "translator.h"
|
||||
#include "trayicon.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QMenu>
|
||||
#include <QApplication>
|
||||
#include <QDesktopWidget>
|
||||
#include <QScreen>
|
||||
#include <QDesktopWidget>
|
||||
#include <QThread>
|
||||
#include <QSettings>
|
||||
#include <QClipboard>
|
||||
#include <QMessageBox>
|
||||
#include <QInputDialog>
|
||||
#include <QNetworkProxy>
|
||||
|
||||
#include "settings.h"
|
||||
#include "settingseditor.h"
|
||||
#include "selectiondialog.h"
|
||||
#include "globalactionhelper.h"
|
||||
#include "recognizer.h"
|
||||
#include "webtranslator.h"
|
||||
#include "resultdialog.h"
|
||||
#include "languagehelper.h"
|
||||
#include "stassert.h"
|
||||
#include "utils.h"
|
||||
#include "updater.h"
|
||||
Manager::Manager()
|
||||
{
|
||||
tray_ = std::make_unique<TrayIcon>(*this);
|
||||
capturer_ = std::make_unique<Capturer>(*this);
|
||||
recognizer_ = std::make_unique<Recognizer>(*this);
|
||||
translator_ = std::make_unique<Translator>(*this);
|
||||
corrector_ = std::make_unique<Corrector>(*this);
|
||||
representer_ = std::make_unique<Representer>(*this, *tray_);
|
||||
|
||||
Manager::Manager (QObject *parent) :
|
||||
QObject (parent),
|
||||
trayIcon_ (new QSystemTrayIcon (this)),
|
||||
dictionary_ (new LanguageHelper),
|
||||
resultDialog_ (new ResultDialog (*dictionary_)),
|
||||
updater_ (new Updater (this)), updateTimer_ (new QTimer (this)),
|
||||
captureAction_ (NULL), repeatCaptureAction_ (NULL),
|
||||
repeatAction_ (NULL), clipboardAction_ (NULL),
|
||||
useResultDialog_ (true), doTranslation_ (true), itemProcessingCount_ (0) {
|
||||
updateNormalIcon ();
|
||||
GlobalActionHelper::init ();
|
||||
qRegisterMetaType<ProcessingItem>();
|
||||
qRegisterMetaType<TaskPtr>();
|
||||
|
||||
// Recognizer
|
||||
Recognizer *recognizer = new Recognizer;
|
||||
connect (this, SIGNAL (requestRecognize (ProcessingItem)),
|
||||
recognizer, SLOT (recognize (ProcessingItem)));
|
||||
connect (recognizer, SIGNAL (recognized (ProcessingItem)),
|
||||
this, SIGNAL (requestTranslate (ProcessingItem)));
|
||||
connect (recognizer, SIGNAL (error (QString)),
|
||||
SLOT (showError (QString)));
|
||||
connect (this, SIGNAL (settingsEdited ()),
|
||||
recognizer, SLOT (applySettings ()));
|
||||
QThread *recognizerThread = new QThread (this);
|
||||
threads_ << recognizerThread;
|
||||
recognizer->moveToThread (recognizerThread);
|
||||
recognizerThread->start ();
|
||||
connect (qApp, SIGNAL (aboutToQuit ()), recognizerThread, SLOT (quit ()));
|
||||
|
||||
|
||||
// Translator
|
||||
WebTranslator *translator = new WebTranslator;
|
||||
connect (this, SIGNAL (requestTranslate (ProcessingItem)),
|
||||
translator, SLOT (translate (ProcessingItem)));
|
||||
connect (translator, SIGNAL (translated (ProcessingItem)),
|
||||
SLOT (showResult (ProcessingItem)));
|
||||
connect (translator, SIGNAL (error (QString)),
|
||||
SLOT (showError (QString)));
|
||||
connect (this, SIGNAL (settingsEdited ()),
|
||||
translator, SLOT (applySettings ()));
|
||||
|
||||
connect (this, SIGNAL (settingsEdited ()), this, SLOT (applySettings ()));
|
||||
|
||||
connect (updater_, SIGNAL (updated ()), SIGNAL (settingsEdited ()));
|
||||
connect (updater_, SIGNAL (error (QString)), SLOT (showError (QString)));
|
||||
updateTimer_->setSingleShot (true);
|
||||
connect (updateTimer_, SIGNAL (timeout ()), SLOT (checkForUpdates ()));
|
||||
|
||||
resultDialog_->setWindowIcon (trayIcon_->icon ());
|
||||
connect (this, SIGNAL (settingsEdited ()), resultDialog_, SLOT (applySettings ()));
|
||||
connect (resultDialog_, SIGNAL (requestRecognize (ProcessingItem)),
|
||||
this, SIGNAL (requestRecognize (ProcessingItem)));
|
||||
connect (resultDialog_, SIGNAL (requestTranslate (ProcessingItem)),
|
||||
this, SIGNAL (requestTranslate (ProcessingItem)));
|
||||
connect (resultDialog_, SIGNAL (requestClipboard ()), SLOT (copyLastToClipboard ()));
|
||||
connect (resultDialog_, SIGNAL (requestImageClipboard ()),
|
||||
SLOT (copyLastImageToClipboard ()));
|
||||
connect (resultDialog_, SIGNAL (requestEdition (ProcessingItem)),
|
||||
this, SLOT (editRecognized (ProcessingItem)));
|
||||
|
||||
connect (trayIcon_, SIGNAL (activated (QSystemTrayIcon::ActivationReason)),
|
||||
SLOT (processTrayAction (QSystemTrayIcon::ActivationReason)));
|
||||
|
||||
trayIcon_->setContextMenu (trayContextMenu ());
|
||||
updateActionsState ();
|
||||
trayIcon_->show ();
|
||||
|
||||
applySettings ();
|
||||
Settings settings;
|
||||
settings.load();
|
||||
updateSettings(settings);
|
||||
}
|
||||
|
||||
QMenu * Manager::trayContextMenu () {
|
||||
QMenu *menu = new QMenu ();
|
||||
captureAction_ = menu->addAction (tr ("Захват"), this, SLOT (capture ()));
|
||||
repeatCaptureAction_ = menu->addAction (tr ("Повторить захват"),
|
||||
this, SLOT (repeatCapture ()));
|
||||
QMenu *translateMenu = menu->addMenu (tr ("Результат"));
|
||||
repeatAction_ = translateMenu->addAction (tr ("Показать"), this,
|
||||
SLOT (showLast ()));
|
||||
clipboardAction_ = translateMenu->addAction (tr ("В буфер"), this,
|
||||
SLOT (copyLastToClipboard ()));
|
||||
menu->addAction (tr ("Настройки"), this, SLOT (settings ()));
|
||||
menu->addAction (tr ("О программе"), this, SLOT (about ()));
|
||||
menu->addAction (tr ("Выход"), this, SLOT (close ()));
|
||||
return menu;
|
||||
Manager::~Manager() = default;
|
||||
|
||||
void Manager::updateSettings(const Settings &settings)
|
||||
{
|
||||
LTRACE() << "updateSettings";
|
||||
tray_->updateSettings(settings);
|
||||
capturer_->updateSettings(settings);
|
||||
recognizer_->updateSettings(settings);
|
||||
translator_->updateSettings(settings);
|
||||
corrector_->updateSettings(settings);
|
||||
representer_->updateSettings(settings);
|
||||
}
|
||||
|
||||
void Manager::updateActionsState (bool isEnabled) {
|
||||
#ifdef Q_OS_LINUX
|
||||
// Avoid unneeded tray blinking (required to update context menu).
|
||||
QList<QAction *> actions;
|
||||
actions << captureAction_ << repeatCaptureAction_ << repeatAction_ << clipboardAction_;
|
||||
QList<bool> states;
|
||||
for (const QAction *action: actions) {
|
||||
states << action->isEnabled ();
|
||||
}
|
||||
#endif
|
||||
captureAction_->setEnabled (isEnabled);
|
||||
repeatCaptureAction_->setEnabled (isEnabled && !selections_.isEmpty ());
|
||||
const ProcessingItem &lastItem = resultDialog_->item ();
|
||||
repeatAction_->setEnabled (isEnabled && lastItem.isValid ());
|
||||
clipboardAction_->setEnabled (isEnabled && lastItem.isValid ());
|
||||
#ifdef Q_OS_LINUX
|
||||
for (int i = 0, end = actions.size (); i < end; ++i) {
|
||||
if (states.at (i) != actions.at (i)->isEnabled ()) {
|
||||
trayIcon_->hide ();
|
||||
trayIcon_->show ();
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
void Manager::finishTask(const TaskPtr &task)
|
||||
{
|
||||
SOFT_ASSERT(task, return );
|
||||
LTRACE() << "finishTask" << task->captured << task->error;
|
||||
|
||||
void Manager::applySettings () {
|
||||
#define GET(NAME) settings.value (settings_names::NAME, settings_values::NAME)
|
||||
QSettings settings;
|
||||
settings.beginGroup (settings_names::guiGroup);
|
||||
--activeTaskCount_;
|
||||
tray_->setActiveTaskCount(activeTaskCount_);
|
||||
|
||||
QStringList globalActionsFailed;
|
||||
Q_CHECK_PTR (captureAction_);
|
||||
GlobalActionHelper::removeGlobal (captureAction_);
|
||||
captureAction_->setShortcut (GET (captureHotkey).toString ());
|
||||
if (!GlobalActionHelper::makeGlobal (captureAction_)) {
|
||||
globalActionsFailed << captureAction_->shortcut ().toString ();
|
||||
}
|
||||
last_ = task;
|
||||
tray_->setTaskActionsEnabled(last_->isValid());
|
||||
|
||||
Q_CHECK_PTR (repeatCaptureAction_);
|
||||
GlobalActionHelper::removeGlobal (repeatCaptureAction_);
|
||||
repeatCaptureAction_->setShortcut (GET (repeatCaptureHotkey).toString ());
|
||||
if (!GlobalActionHelper::makeGlobal (repeatCaptureAction_)) {
|
||||
globalActionsFailed << repeatCaptureAction_->shortcut ().toString ();
|
||||
}
|
||||
|
||||
Q_CHECK_PTR (repeatAction_);
|
||||
GlobalActionHelper::removeGlobal (repeatAction_);
|
||||
repeatAction_->setShortcut (GET (repeatHotkey).toString ());
|
||||
if (!GlobalActionHelper::makeGlobal (repeatAction_)) {
|
||||
globalActionsFailed << repeatAction_->shortcut ().toString ();
|
||||
}
|
||||
|
||||
Q_CHECK_PTR (clipboardAction_);
|
||||
GlobalActionHelper::removeGlobal (clipboardAction_);
|
||||
clipboardAction_->setShortcut (GET (clipboardHotkey).toString ());
|
||||
if (!GlobalActionHelper::makeGlobal (clipboardAction_)) {
|
||||
globalActionsFailed << clipboardAction_->shortcut ().toString ();
|
||||
}
|
||||
|
||||
if (!globalActionsFailed.isEmpty ()) {
|
||||
showError (tr ("Failed to register global shortcuts:\n%1")
|
||||
.arg (globalActionsFailed.join ("\n")));
|
||||
}
|
||||
|
||||
// Depends on SettingsEditor button indexes. 1==dialog
|
||||
useResultDialog_ = GET (resultShowType).toBool ();
|
||||
|
||||
QNetworkProxy proxy = QNetworkProxy::applicationProxy ();
|
||||
QList<int> proxyTypes = proxyTypeOrder ();
|
||||
int proxyTypeIndex = std::min (GET (proxyType).toInt (), proxyTypes.size ());
|
||||
proxy.setType (QNetworkProxy::ProxyType (proxyTypes.at (std::max (proxyTypeIndex, 0))));
|
||||
proxy.setHostName (GET (proxyHostName).toString ());
|
||||
proxy.setPort (GET (proxyPort).toInt ());
|
||||
proxy.setUser (GET (proxyUser).toString ());
|
||||
if (GET (proxySavePassword).toBool ()) {
|
||||
proxy.setPassword (encode (GET (proxyPassword).toString ()));
|
||||
}
|
||||
QNetworkProxy::setApplicationProxy (proxy);
|
||||
|
||||
scheduleUpdate ();
|
||||
settings.endGroup ();
|
||||
|
||||
settings.beginGroup (settings_names::recogntionGroup);
|
||||
defaultOrcLanguage_ = GET (ocrLanguage).toString ();
|
||||
settings.endGroup ();
|
||||
|
||||
settings.beginGroup (settings_names::translationGroup);
|
||||
defaultTranslationLanguage_ = GET (translationLanguage).toString ();
|
||||
doTranslation_ = GET (doTranslation).toBool ();
|
||||
settings.endGroup ();
|
||||
|
||||
Q_CHECK_PTR (dictionary_);
|
||||
dictionary_->updateAvailableOcrLanguages ();
|
||||
#undef GET
|
||||
}
|
||||
|
||||
void Manager::scheduleUpdate (bool justChecked) {
|
||||
#define GET(NAME) settings.value (settings_names::NAME, settings_values::NAME)
|
||||
QSettings settings;
|
||||
settings.beginGroup (settings_names::guiGroup);
|
||||
updateTimer_->stop ();
|
||||
if (justChecked) {
|
||||
settings.setValue (settings_names::lastUpdateCheck, QDateTime::currentDateTime ());
|
||||
}
|
||||
QDateTime nextUpdateCheck = updater_->nextCheckTime (GET (lastUpdateCheck).toDateTime (),
|
||||
GET (autoUpdateType).toInt ());
|
||||
if (nextUpdateCheck.isValid ()) {
|
||||
updateTimer_->start (QDateTime::currentDateTime ().msecsTo (nextUpdateCheck));
|
||||
}
|
||||
#undef GET
|
||||
}
|
||||
|
||||
void Manager::checkForUpdates () {
|
||||
updater_->checkForUpdates ();
|
||||
scheduleUpdate (true);
|
||||
}
|
||||
|
||||
Manager::~Manager () {
|
||||
for (SelectionDialog *selection: selections_.values ()) {
|
||||
selection->hide ();
|
||||
delete selection;
|
||||
}
|
||||
trayIcon_->hide ();
|
||||
delete trayIcon_->contextMenu ();
|
||||
for (QThread *thread: threads_) {
|
||||
thread->quit ();
|
||||
thread->wait (1000000);
|
||||
}
|
||||
}
|
||||
|
||||
void Manager::capture () {
|
||||
QList<QScreen *> screens = QApplication::screens ();
|
||||
for (QScreen *screen: screens) {
|
||||
QRect geometry = screen->availableGeometry ();
|
||||
#if QT_VERSION >= QT_VERSION_CHECK (5,10,0)
|
||||
QPixmap pixmap = screen->grabWindow (0, 0, 0,
|
||||
geometry.width (), geometry.height ());
|
||||
#else
|
||||
QPixmap pixmap = screen->grabWindow (0, geometry.x (), geometry.y (),
|
||||
geometry.width (), geometry.height ());
|
||||
#endif
|
||||
|
||||
QString name = screen->name ();
|
||||
if (!selections_.contains (name)) {
|
||||
SelectionDialog *selection = new SelectionDialog (*dictionary_);
|
||||
selection->setWindowIcon (trayIcon_->icon ());
|
||||
connect (this, SIGNAL (closeSelections ()), selection, SLOT (close ()));
|
||||
connect (this, SIGNAL (settingsEdited ()), selection, SLOT (applySettings ()));
|
||||
connect (selection, SIGNAL (selected (ProcessingItem)),
|
||||
SLOT (handleSelection (ProcessingItem)));
|
||||
connect (selection, SIGNAL (rejected ()), SIGNAL (closeSelections ()));
|
||||
selections_[name] = selection;
|
||||
}
|
||||
SelectionDialog *selection = selections_[name];
|
||||
selection->setPixmap (pixmap, geometry);
|
||||
}
|
||||
updateActionsState ();
|
||||
}
|
||||
|
||||
void Manager::handleSelection (ProcessingItem item) {
|
||||
bool altMod = item.modifiers & Qt::AltModifier;
|
||||
bool doTranslation = (doTranslation_ && !altMod) || (!doTranslation_ && altMod);
|
||||
if (doTranslation) {
|
||||
item.translateLanguage = defaultTranslationLanguage_;
|
||||
}
|
||||
if (item.ocrLanguage.isEmpty ()) {
|
||||
item.ocrLanguage = defaultOrcLanguage_;
|
||||
}
|
||||
if (item.swapLanguages_) {
|
||||
QString translate = (item.translateLanguage.isEmpty ())
|
||||
? defaultTranslationLanguage_ : item.translateLanguage;
|
||||
if (doTranslation) {
|
||||
item.translateLanguage = dictionary_->ocrToTranslateCodes (item.ocrLanguage);
|
||||
}
|
||||
item.sourceLanguage.clear ();
|
||||
item.ocrLanguage = dictionary_->translateToOcrCodes (translate);
|
||||
if (item.ocrLanguage.isEmpty ()) {
|
||||
showError (tr ("Не найден подходящий язык распознавания."));
|
||||
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 ()) {
|
||||
if (!task->isValid()) {
|
||||
tray_->showError(task->error);
|
||||
return;
|
||||
}
|
||||
QList<QScreen *> screens = QApplication::screens ();
|
||||
for (QScreen *screen: screens) {
|
||||
QString name = screen->name ();
|
||||
if (!selections_.contains (name)) {
|
||||
continue;
|
||||
}
|
||||
SelectionDialog *selection = selections_[name];
|
||||
selection->show ();
|
||||
selection->activateWindow ();
|
||||
}
|
||||
|
||||
tray_->showSuccess();
|
||||
}
|
||||
|
||||
void Manager::settings () {
|
||||
SettingsEditor editor (*dictionary_);
|
||||
editor.setWindowIcon (trayIcon_->icon ());
|
||||
connect (&editor, SIGNAL (settingsEdited ()), SIGNAL (settingsEdited ()));
|
||||
connect (&editor, SIGNAL (updateCheckRequested ()), SLOT (checkForUpdates ()));
|
||||
updateActionsState (false);
|
||||
editor.exec ();
|
||||
updateActionsState (true);
|
||||
}
|
||||
void Manager::captured(const TaskPtr &task)
|
||||
{
|
||||
tray_->blockActions(false);
|
||||
|
||||
void Manager::close () {
|
||||
QApplication::quit ();
|
||||
}
|
||||
SOFT_ASSERT(task, return );
|
||||
LTRACE() << "captured" << task->captured << task->error;
|
||||
|
||||
void Manager::about () {
|
||||
QString text = tr ("Программа для распознавания текста на экране.\n" \
|
||||
"Создана с использованием Qt, tesseract-ocr, Google Translate.\n"
|
||||
"Автор: Gres (translator@gres.biz)\n"
|
||||
"Версия: %1 от %2 %3").arg (updater_->currentAppVersion ())
|
||||
.arg (__DATE__).arg (__TIME__);
|
||||
QString tips = tr ("\n\nПодсказки.\n"
|
||||
"Клик по иконке в трее:\n"
|
||||
"* левой кнопкой - отобразить последний результат\n"
|
||||
"* средней кнопкой - скопировать последний результат в буфер обмена\n"
|
||||
#ifdef Q_OS_WIN
|
||||
"* двойной клик - повторный захват последнего экрана\n"
|
||||
#endif
|
||||
"\n"
|
||||
"Захвата изображения при зажатых кнопках:\n"
|
||||
"* Ctrl - не выходить из режима захвата\n"
|
||||
"* Alt - выполнить перевод, если в настройках он выключен "
|
||||
"(и наоборот, не выполнять, если включен)\n"
|
||||
"");
|
||||
++activeTaskCount_;
|
||||
tray_->setActiveTaskCount(activeTaskCount_);
|
||||
|
||||
QMessageBox message (QMessageBox::Information, tr ("О программе"), text + tips,
|
||||
QMessageBox::Ok);
|
||||
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 ()));
|
||||
if (!task->isValid()) {
|
||||
finishTask(task);
|
||||
return;
|
||||
}
|
||||
changeIcon (IconTypeSuccess);
|
||||
if (useResultDialog_) {
|
||||
resultDialog_->showResult (item);
|
||||
}
|
||||
else {
|
||||
QString message = item.recognized + " - " + item.translated;
|
||||
trayIcon_->showMessage (tr ("Результат"), message, QSystemTrayIcon::Information);
|
||||
}
|
||||
updateActionsState ();
|
||||
|
||||
recognizer_->recognize(task);
|
||||
}
|
||||
|
||||
void Manager::showError (QString text) {
|
||||
qCritical () << text;
|
||||
changeIcon (IconTypeError);
|
||||
trayIcon_->showMessage (tr ("Ошибка"), text, QSystemTrayIcon::Critical);
|
||||
void Manager::captureCanceled()
|
||||
{
|
||||
tray_->blockActions(false);
|
||||
}
|
||||
|
||||
void Manager::changeIcon (int iconType, int timeoutMsec) {
|
||||
QString fileName;
|
||||
switch (iconType) {
|
||||
case IconTypeSuccess:
|
||||
fileName = ":/images/STIconGreen.png";
|
||||
break;
|
||||
case IconTypeError:
|
||||
fileName = ":/images/STIconRed.png";
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
trayIcon_->setIcon (QIcon (fileName));
|
||||
if (timeoutMsec > 0) {
|
||||
QTimer::singleShot (timeoutMsec, this, SLOT (updateNormalIcon ()));
|
||||
void Manager::recognized(const TaskPtr &task)
|
||||
{
|
||||
SOFT_ASSERT(task, return );
|
||||
LTRACE() << "recognized" << task->recognized << task->error;
|
||||
|
||||
if (!task->isValid()) {
|
||||
finishTask(task);
|
||||
return;
|
||||
}
|
||||
|
||||
corrector_->correct(task);
|
||||
}
|
||||
|
||||
void Manager::updateNormalIcon () {
|
||||
QString fileName = itemProcessingCount_ > 0
|
||||
? ":/images/STIconOrange.png" : ":/images/STIconBlue.png";
|
||||
trayIcon_->setIcon (QIcon (fileName));
|
||||
void Manager::corrected(const TaskPtr &task)
|
||||
{
|
||||
SOFT_ASSERT(task, return );
|
||||
LTRACE() << "corrected" << task->recognized << task->error;
|
||||
|
||||
if (!task->isValid()) {
|
||||
finishTask(task);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!task->targetLanguage.isEmpty())
|
||||
translator_->translate(task);
|
||||
else
|
||||
translated(task);
|
||||
}
|
||||
|
||||
void Manager::translated(const TaskPtr &task)
|
||||
{
|
||||
SOFT_ASSERT(task, return );
|
||||
LTRACE() << "translated" << task->recognized << task->error;
|
||||
|
||||
finishTask(task);
|
||||
|
||||
if (!task->isValid())
|
||||
return;
|
||||
|
||||
representer_->represent(task);
|
||||
}
|
||||
|
||||
void Manager::fatalError(const QString &text)
|
||||
{
|
||||
tray_->blockActions(false);
|
||||
tray_->showFatalError(text);
|
||||
}
|
||||
|
||||
void Manager::capture()
|
||||
{
|
||||
SOFT_ASSERT(capturer_, return );
|
||||
tray_->blockActions(true);
|
||||
capturer_->capture();
|
||||
tray_->setRepeatCaptureEnabled(true);
|
||||
}
|
||||
|
||||
void Manager::repeatCapture()
|
||||
{
|
||||
SOFT_ASSERT(capturer_, return );
|
||||
tray_->blockActions(true);
|
||||
capturer_->repeatCapture();
|
||||
}
|
||||
|
||||
void Manager::showLast()
|
||||
{
|
||||
if (!last_ || !last_->isValid())
|
||||
return;
|
||||
SOFT_ASSERT(representer_, return );
|
||||
representer_->represent(last_);
|
||||
}
|
||||
|
||||
void Manager::settings()
|
||||
{
|
||||
SettingsEditor editor;
|
||||
|
||||
Settings settings;
|
||||
settings.load();
|
||||
editor.setSettings(settings);
|
||||
|
||||
tray_->blockActions(true);
|
||||
auto result = editor.exec();
|
||||
tray_->blockActions(false);
|
||||
|
||||
if (result != QDialog::Accepted)
|
||||
return;
|
||||
|
||||
tray_->resetFatalError();
|
||||
|
||||
settings = editor.settings();
|
||||
settings.save();
|
||||
updateSettings(settings);
|
||||
}
|
||||
|
||||
void Manager::copyLastToClipboard()
|
||||
{
|
||||
if (!last_ || !last_->isValid())
|
||||
return;
|
||||
|
||||
QClipboard *clipboard = QApplication::clipboard();
|
||||
clipboard->setText(last_->recognized + QLatin1String(" - ") +
|
||||
last_->translated);
|
||||
tray_->showInformation(
|
||||
QObject::tr("The last result was copied to the clipboard."));
|
||||
}
|
||||
|
||||
void Manager::about()
|
||||
{
|
||||
auto text =
|
||||
QObject::tr(R"(Optical character recognition (OCR) and translation tool
|
||||
Author: Gres (translator@gres.biz)
|
||||
Version: %1)")
|
||||
.arg(QApplication::applicationVersion());
|
||||
|
||||
QMessageBox message(QMessageBox::Information, QObject::tr("About"), text,
|
||||
QMessageBox::Ok);
|
||||
message.setIconPixmap(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
|
||||
#define MANAGER_H
|
||||
#pragma once
|
||||
|
||||
#include <QPixmap>
|
||||
#include <QSystemTrayIcon>
|
||||
#include <QMap>
|
||||
#include "stfwd.h"
|
||||
|
||||
#include "processingitem.h"
|
||||
class QString;
|
||||
|
||||
class QAction;
|
||||
class QMenu;
|
||||
class Manager
|
||||
{
|
||||
public:
|
||||
Manager();
|
||||
~Manager();
|
||||
|
||||
class SelectionDialog;
|
||||
class ResultDialog;
|
||||
class LanguageHelper;
|
||||
class Updater;
|
||||
void captured(const TaskPtr &task);
|
||||
void captureCanceled();
|
||||
void recognized(const TaskPtr &task);
|
||||
void corrected(const TaskPtr &task);
|
||||
void translated(const TaskPtr &task);
|
||||
|
||||
class Manager : public QObject {
|
||||
Q_OBJECT
|
||||
void fatalError(const QString &text);
|
||||
void capture();
|
||||
void repeatCapture();
|
||||
void showLast();
|
||||
void settings();
|
||||
void copyLastToClipboard();
|
||||
void about();
|
||||
void quit();
|
||||
|
||||
enum IconType {
|
||||
IconTypeNormal, IconTypeWorking, IconTypeError, IconTypeSuccess
|
||||
};
|
||||
private:
|
||||
void updateSettings(const Settings &settings);
|
||||
void finishTask(const TaskPtr &task);
|
||||
|
||||
public:
|
||||
explicit Manager (QObject *parent = 0);
|
||||
~Manager ();
|
||||
|
||||
signals:
|
||||
void requestRecognize (ProcessingItem item);
|
||||
void requestTranslate (ProcessingItem item);
|
||||
void closeSelections ();
|
||||
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_;
|
||||
std::unique_ptr<TrayIcon> tray_;
|
||||
std::unique_ptr<Capturer> capturer_;
|
||||
std::unique_ptr<Recognizer> recognizer_;
|
||||
std::unique_ptr<Corrector> corrector_;
|
||||
std::unique_ptr<Translator> translator_;
|
||||
std::unique_ptr<Representer> representer_;
|
||||
TaskPtr last_;
|
||||
int activeTaskCount_{0};
|
||||
};
|
||||
|
||||
#endif // MANAGER_H
|
||||
|
48
src/ocr/recognizer.cpp
Normal file
@ -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
|
||||
#define SETTINGS_H
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include "stfwd.h"
|
||||
|
||||
namespace settings_names {
|
||||
//! UI
|
||||
const QString guiGroup = "GUI";
|
||||
const QString geometry = "geometry";
|
||||
const QString captureHotkey = "captureHotkey";
|
||||
const QString repeatCaptureHotkey = "repeatCaptureHotkey";
|
||||
const QString repeatHotkey = "repeatHotkey";
|
||||
const QString clipboardHotkey = "clipboardHotkey";
|
||||
const QString resultShowType = "resultShowType";
|
||||
const QString proxyType = "proxyType";
|
||||
const QString proxyHostName = "proxyHostName";
|
||||
const QString proxyPort = "proxyPort";
|
||||
const QString proxyUser = "proxyUser";
|
||||
const QString proxyPassword = "proxyPassword";
|
||||
const QString proxySavePassword = "proxySavePassword";
|
||||
const QString autoUpdateType = "autoUpdateType";
|
||||
const QString lastUpdateCheck = "lastUpdateCheck";
|
||||
#include <QStringList>
|
||||
|
||||
//! Recognition
|
||||
const QString recogntionGroup = "Recognition";
|
||||
const QString tessDataPlace = "tessdata_dir";
|
||||
const QString ocrLanguage = "language";
|
||||
const QString imageScale = "image_scale";
|
||||
#include <chrono>
|
||||
|
||||
//! Translation
|
||||
const QString translationGroup = "Translation";
|
||||
const QString doTranslation = "doTranslation";
|
||||
const QString ignoreSslErrors = "ignoreSslErrors";
|
||||
const QString forceRotateTranslators = "forceRotateTranslators";
|
||||
const QString sourceLanguage = "source_language";
|
||||
const QString translationLanguage = "translation_language";
|
||||
const QString translationTimeout = "translation_timeout";
|
||||
const QString translationDebugMode = "translation_debug";
|
||||
const QString translators = "translators";
|
||||
}
|
||||
enum class ResultMode { Widget, Tooltip };
|
||||
|
||||
namespace settings_values {
|
||||
const QString appName = "ScreenTranslator";
|
||||
const QString companyName = "Gres";
|
||||
struct Substitution {
|
||||
QString source;
|
||||
QString target;
|
||||
};
|
||||
using Substitutions = std::unordered_multimap<LanguageId, Substitution>;
|
||||
|
||||
//! UI
|
||||
const QString captureHotkey = "Ctrl+Alt+Z";
|
||||
const QString repeatCaptureHotkey = "Ctrl+Alt+S";
|
||||
const QString repeatHotkey = "Ctrl+Alt+X";
|
||||
const QString clipboardHotkey = "Ctrl+Alt+C";
|
||||
const QString resultShowType = "1";//dialog
|
||||
const int proxyType = 0;
|
||||
const QString proxyHostName = "";
|
||||
const int proxyPort = 8080;
|
||||
const QString proxyUser = "";
|
||||
const QString proxyPassword = "";
|
||||
const bool proxySavePassword = false;
|
||||
const int autoUpdateType = 0; //Never
|
||||
const QString lastUpdateCheck = "";
|
||||
class Settings
|
||||
{
|
||||
public:
|
||||
void save();
|
||||
void load();
|
||||
|
||||
//! Recognition
|
||||
#if defined(Q_OS_LINUX)
|
||||
const QString tessDataPlace = "/usr/share/tesseract-ocr/";
|
||||
#else
|
||||
const QString tessDataPlace = "./";
|
||||
#endif
|
||||
const QString ocrLanguage = "eng";
|
||||
const int imageScale = 5;
|
||||
QString captureHotkey{"Ctrl+Alt+Z"};
|
||||
QString repeatCaptureHotkey{"Ctrl+Alt+S"};
|
||||
QString showLastHotkey{"Ctrl+Alt+X"};
|
||||
QString clipboardHotkey{"Ctrl+Alt+C"};
|
||||
|
||||
//! Translation
|
||||
const bool doTranslation = true;
|
||||
const bool ignoreSslErrors = false;
|
||||
const bool forceRotateTranslators = false;
|
||||
const QString sourceLanguage = "auto";
|
||||
const QString translationLanguage = "ru";
|
||||
const int translationTimeout = 15; // secs
|
||||
const bool translationDebugMode = false;
|
||||
const QString translators = "";
|
||||
}
|
||||
int proxyType{0};
|
||||
QString proxyHostName{""};
|
||||
int proxyPort{8080};
|
||||
QString proxyUser{""};
|
||||
QString proxyPassword{""};
|
||||
bool proxySavePassword{false};
|
||||
|
||||
#endif // SETTINGS_H
|
||||
int autoUpdateType{0}; // Never
|
||||
QString lastUpdateCheck{""};
|
||||
Substitutions userSubstitutions;
|
||||
|
||||
bool debugMode{false};
|
||||
|
||||
QString tessdataPath{"tessdata"};
|
||||
QString sourceLanguage{"eng"};
|
||||
LanguageIds availableOcrLanguages_;
|
||||
|
||||
bool doTranslation{true};
|
||||
bool ignoreSslErrors{false};
|
||||
bool forceRotateTranslators{false};
|
||||
LanguageId targetLanguage{"rus"};
|
||||
std::chrono::seconds translationTimeout{15};
|
||||
QString translatorsDir{"translators"};
|
||||
QStringList translators{"google.js"};
|
||||
|
||||
ResultMode resultShowType{ResultMode::Widget}; // dialog
|
||||
};
|
||||
|
@ -1,288 +1,204 @@
|
||||
#include "settingseditor.h"
|
||||
#include "ui_settingseditor.h"
|
||||
#include "languagehelper.h"
|
||||
#include "translatorhelper.h"
|
||||
#include "recognizerhelper.h"
|
||||
#include "stassert.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <QSettings>
|
||||
#include <QFileDialog>
|
||||
#include <QDir>
|
||||
#include <QRegExpValidator>
|
||||
#include <QNetworkProxy>
|
||||
|
||||
#include "languagecodes.h"
|
||||
#include "settings.h"
|
||||
#include "ui_settingseditor.h"
|
||||
#include "widgetstate.h"
|
||||
|
||||
SettingsEditor::SettingsEditor (const LanguageHelper &dictionary, QWidget *parent) :
|
||||
QDialog (parent),
|
||||
ui (new Ui::SettingsEditor), translatorHelper_ (new TranslatorHelper),
|
||||
recognizerHelper_ (new RecognizerHelper), dictionary_ (dictionary),
|
||||
buttonGroup_ (new QButtonGroup (this)) {
|
||||
ui->setupUi (this);
|
||||
#include <QDir>
|
||||
#include <QFileDialog>
|
||||
#include <QNetworkProxy>
|
||||
#include <QRegExpValidator>
|
||||
#include <QSettings>
|
||||
#include <QStringListModel>
|
||||
|
||||
buttonGroup_->addButton (ui->trayRadio, 0);
|
||||
buttonGroup_->addButton (ui->dialogRadio, 1);
|
||||
connect (ui->updateButton, SIGNAL (clicked (bool)), SIGNAL (updateCheckRequested ()));
|
||||
QStringList updateTypes = QStringList () << tr ("Никогда") << tr ("Ежедневно")
|
||||
<< tr ("Еженедельно") << tr ("Ежемесячно");
|
||||
ui->updateCombo->addItems (updateTypes);
|
||||
SettingsEditor::SettingsEditor()
|
||||
: ui(new Ui::SettingsEditor)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
connect (ui->tessdataButton, SIGNAL (clicked ()), SLOT (openTessdataDialog ()));
|
||||
connect (ui->tessdataEdit, SIGNAL (textChanged (const QString&)),
|
||||
SLOT (initOcrLangCombo (const QString&)));
|
||||
|
||||
connect (ui->recognizerFixTable, SIGNAL (itemChanged (QTableWidgetItem*)),
|
||||
SLOT (recognizerFixTableItemChanged (QTableWidgetItem*)));
|
||||
|
||||
ui->translateLangCombo->addItems (dictionary_.translateLanguagesUi ());
|
||||
|
||||
typedef QNetworkProxy::ProxyType ProxyType;
|
||||
QMap<ProxyType, QString> proxyTypeNames;
|
||||
proxyTypeNames.insert (QNetworkProxy::NoProxy, tr ("Нет"));
|
||||
proxyTypeNames.insert (QNetworkProxy::Socks5Proxy, tr ("SOCKS 5"));
|
||||
proxyTypeNames.insert (QNetworkProxy::HttpProxy, tr ("HTTP"));
|
||||
QList<int> proxyOrder = proxyTypeOrder ();
|
||||
for (int type: proxyOrder) {
|
||||
ui->proxyTypeCombo->addItem (proxyTypeNames.value (QNetworkProxy::ProxyType (type)));
|
||||
{
|
||||
auto model = new QStringListModel(this);
|
||||
model->setStringList({tr("General"), tr("Recognition"), tr("Correction"),
|
||||
tr("Translation"), tr("Representation"),
|
||||
tr("Update")});
|
||||
ui->pagesList->setModel(model);
|
||||
auto selection = ui->pagesList->selectionModel();
|
||||
connect(selection, &QItemSelectionModel::currentRowChanged, //
|
||||
this, &SettingsEditor::updateCurrentPage);
|
||||
}
|
||||
|
||||
QRegExp urlRegexp (R"(^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$)");
|
||||
ui->proxyHostEdit->setValidator (new QRegExpValidator (urlRegexp, ui->proxyHostEdit));
|
||||
ui->proxyPassEdit->setEchoMode (QLineEdit::PasswordEchoOnEdit);
|
||||
// general
|
||||
// QMap<QNetworkProxy::ProxyType, QString> proxyTypeNames;
|
||||
// proxyTypeNames.insert(QNetworkProxy::NoProxy, tr("No"));
|
||||
// proxyTypeNames.insert(QNetworkProxy::DefaultProxy, tr("System"));
|
||||
// proxyTypeNames.insert(QNetworkProxy::Socks5Proxy, tr("SOCKS 5"));
|
||||
// proxyTypeNames.insert(QNetworkProxy::HttpProxy, tr("HTTP"));
|
||||
// QList<int> proxyOrder = proxyTypeOrder();
|
||||
// for (int type : proxyOrder) {
|
||||
// ui->proxyTypeCombo->addItem(
|
||||
// proxyTypeNames.value(QNetworkProxy::ProxyType(type)));
|
||||
// }
|
||||
|
||||
loadSettings ();
|
||||
loadState ();
|
||||
// QRegExp urlRegexp(
|
||||
// R"(^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$)");
|
||||
// ui->proxyHostEdit->setValidator(
|
||||
// new QRegExpValidator(urlRegexp, ui->proxyHostEdit));
|
||||
// ui->proxyPassEdit->setEchoMode(QLineEdit::PasswordEchoOnEdit);
|
||||
|
||||
// recognition
|
||||
connect(ui->tessdataButton, &QPushButton::clicked, //
|
||||
this, &SettingsEditor::openTessdataDialog);
|
||||
connect(ui->tessdataEdit, &QLineEdit::textChanged, //
|
||||
this, &SettingsEditor::updateTesseractLanguages);
|
||||
|
||||
// connect(ui->recognizerFixTable, SIGNAL(itemChanged(QTableWidgetItem *)),
|
||||
// SLOT(recognizerFixTableItemChanged(QTableWidgetItem *)));
|
||||
|
||||
// // ui->translateLangCombo->addItems(dictionary_.translateLanguagesUi());
|
||||
|
||||
// translation
|
||||
|
||||
updateTranslationLanguages();
|
||||
|
||||
// updates
|
||||
ui->updateCombo->addItems(
|
||||
{tr("Never"), tr("Daily"), tr("Weekly"), tr("Monthly")});
|
||||
|
||||
new WidgetState(this);
|
||||
}
|
||||
|
||||
SettingsEditor::~SettingsEditor () {
|
||||
saveState ();
|
||||
delete recognizerHelper_;
|
||||
delete translatorHelper_;
|
||||
SettingsEditor::~SettingsEditor()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void SettingsEditor::done (int result) {
|
||||
if (result == QDialog::Accepted) {
|
||||
saveSettings ();
|
||||
emit settingsEdited ();
|
||||
Settings SettingsEditor::settings() const
|
||||
{
|
||||
Settings settings;
|
||||
settings.captureHotkey = ui->captureEdit->keySequence().toString();
|
||||
settings.repeatCaptureHotkey =
|
||||
ui->repeatCaptureEdit->keySequence().toString();
|
||||
settings.showLastHotkey = ui->repeatEdit->keySequence().toString();
|
||||
settings.clipboardHotkey = ui->clipboardEdit->keySequence().toString();
|
||||
|
||||
settings.tessdataPath = ui->tessdataEdit->text();
|
||||
|
||||
settings.doTranslation = ui->doTranslationCheck->isChecked();
|
||||
settings.ignoreSslErrors = ui->ignoreSslCheck->isChecked();
|
||||
settings.debugMode = ui->translatorDebugCheck->isChecked();
|
||||
settings.translationTimeout =
|
||||
std::chrono::seconds(ui->translateTimeoutSpin->value());
|
||||
|
||||
settings.translators.clear();
|
||||
for (auto i = 0, end = ui->translatorList->count(); i < end; ++i) {
|
||||
auto item = ui->translatorList->item(i);
|
||||
if (item->checkState() == Qt::Checked)
|
||||
settings.translators.append(item->text());
|
||||
}
|
||||
QDialog::done (result);
|
||||
|
||||
settings.resultShowType =
|
||||
ui->trayRadio->isChecked() ? ResultMode::Tooltip : ResultMode::Widget;
|
||||
return settings;
|
||||
}
|
||||
|
||||
void SettingsEditor::saveSettings () const {
|
||||
using namespace settings_names;
|
||||
QSettings settings;
|
||||
settings.beginGroup (guiGroup);
|
||||
settings.setValue (captureHotkey, ui->captureEdit->keySequence ().toString ());
|
||||
settings.setValue (repeatCaptureHotkey, ui->repeatCaptureEdit->keySequence ().toString ());
|
||||
settings.setValue (repeatHotkey, ui->repeatEdit->keySequence ().toString ());
|
||||
settings.setValue (clipboardHotkey, ui->clipboardEdit->keySequence ().toString ());
|
||||
settings.setValue (resultShowType, buttonGroup_->checkedId ());
|
||||
settings.setValue (proxyType, ui->proxyTypeCombo->currentIndex ());
|
||||
settings.setValue (proxyHostName, ui->proxyHostEdit->text ());
|
||||
settings.setValue (proxyPort, ui->proxyPortSpin->value ());
|
||||
settings.setValue (proxyUser, ui->proxyUserEdit->text ());
|
||||
if (ui->proxySaveCheck->isChecked ()) {
|
||||
settings.setValue (proxyPassword, encode (ui->proxyPassEdit->text ()));
|
||||
}
|
||||
else {
|
||||
settings.remove (proxyPassword);
|
||||
QNetworkProxy proxy = QNetworkProxy::applicationProxy ();
|
||||
proxy.setPassword (ui->proxyPassEdit->text ());
|
||||
QNetworkProxy::setApplicationProxy (proxy);
|
||||
}
|
||||
settings.setValue (proxySavePassword, ui->proxySaveCheck->isChecked ());
|
||||
settings.setValue (autoUpdateType, ui->updateCombo->currentIndex ());
|
||||
settings.endGroup ();
|
||||
void SettingsEditor::setSettings(const Settings &settings)
|
||||
{
|
||||
ui->captureEdit->setKeySequence(settings.captureHotkey);
|
||||
ui->repeatCaptureEdit->setKeySequence(settings.repeatCaptureHotkey);
|
||||
ui->repeatEdit->setKeySequence(settings.showLastHotkey);
|
||||
ui->clipboardEdit->setKeySequence(settings.clipboardHotkey);
|
||||
|
||||
ui->tessdataEdit->setText(settings.tessdataPath);
|
||||
updateTesseractLanguages();
|
||||
|
||||
settings.beginGroup (recogntionGroup);
|
||||
settings.setValue (tessDataPlace, ui->tessdataEdit->text ());
|
||||
QString ocrLanguageVal = dictionary_.ocrUiToCode (ui->ocrLangCombo->currentText ());
|
||||
settings.setValue (ocrLanguage, ocrLanguageVal);
|
||||
settings.setValue (imageScale, ui->imageScaleSpin->value ());
|
||||
ui->doTranslationCheck->setChecked(settings.doTranslation);
|
||||
ui->ignoreSslCheck->setChecked(settings.ignoreSslErrors);
|
||||
ui->translatorDebugCheck->setChecked(settings.debugMode);
|
||||
ui->translateTimeoutSpin->setValue(settings.translationTimeout.count());
|
||||
translatorsDir_ = settings.translatorsDir;
|
||||
updateTranslators(settings.translators);
|
||||
|
||||
{ //Recognizer substitutions
|
||||
RecognizerHelper::Subs subs;
|
||||
QTableWidget *t = ui->recognizerFixTable; // Shortcut
|
||||
for (int i = 0, end = t->rowCount () - 1; i < end; ++i) {
|
||||
QComboBox *combo = static_cast<QComboBox *>(t->cellWidget (i, SubsColLanguage));
|
||||
QString langUi = combo->currentText ();
|
||||
RecognizerHelper::Sub sub;
|
||||
sub.language = dictionary_.ocrUiToCode (langUi);
|
||||
#define GET(COL) (t->item (i, COL) ? t->item (i, COL)->text () : QString ())
|
||||
sub.source = GET (SubsColSource);
|
||||
sub.target = GET (SubsColTarget);
|
||||
#undef GET
|
||||
if (langUi.isEmpty () || sub.language == langUi || sub.source.isEmpty ()) {
|
||||
continue;
|
||||
}
|
||||
subs.append (sub);
|
||||
}
|
||||
recognizerHelper_->setSubs (subs);
|
||||
recognizerHelper_->save ();
|
||||
}
|
||||
|
||||
settings.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 ();
|
||||
ui->trayRadio->setChecked(settings.resultShowType == ResultMode::Tooltip);
|
||||
ui->dialogRadio->setChecked(settings.resultShowType == ResultMode::Widget);
|
||||
}
|
||||
|
||||
void SettingsEditor::openTessdataDialog () {
|
||||
QString path = QFileDialog::getExistingDirectory (this, tr ("Path to tessdata"));
|
||||
if (path.isEmpty ()) {
|
||||
void SettingsEditor::updateCurrentPage()
|
||||
{
|
||||
ui->pagesView->setCurrentIndex(ui->pagesList->currentIndex().row());
|
||||
}
|
||||
|
||||
void SettingsEditor::openTessdataDialog()
|
||||
{
|
||||
const auto path =
|
||||
QFileDialog::getExistingDirectory(this, tr("Path to tessdata"));
|
||||
|
||||
if (path.isEmpty())
|
||||
return;
|
||||
}
|
||||
ui->tessdataEdit->setText (path);
|
||||
|
||||
ui->tessdataEdit->setText(path);
|
||||
}
|
||||
|
||||
void SettingsEditor::loadSettings () {
|
||||
#define GET(FIELD) settings.value (settings_names::FIELD, settings_values::FIELD)
|
||||
QSettings settings;
|
||||
void SettingsEditor::updateTesseractLanguages()
|
||||
{
|
||||
ui->tesseractLangCombo->clear();
|
||||
ui->correctLangCombo->clear();
|
||||
|
||||
settings.beginGroup (settings_names::guiGroup);
|
||||
ui->captureEdit->setKeySequence (QKeySequence (GET (captureHotkey).toString ()));
|
||||
ui->repeatCaptureEdit->setKeySequence (QKeySequence (GET (repeatCaptureHotkey).toString ()));
|
||||
ui->repeatEdit->setKeySequence (QKeySequence (GET (repeatHotkey).toString ()));
|
||||
ui->clipboardEdit->setKeySequence (QKeySequence (GET (clipboardHotkey).toString ()));
|
||||
QAbstractButton *button = buttonGroup_->button (GET (resultShowType).toInt ());
|
||||
Q_CHECK_PTR (button);
|
||||
button->setChecked (true);
|
||||
ui->proxyTypeCombo->setCurrentIndex (GET (proxyType).toInt ());
|
||||
ui->proxyHostEdit->setText (GET (proxyHostName).toString ());
|
||||
ui->proxyPortSpin->setValue (GET (proxyPort).toInt ());
|
||||
ui->proxyUserEdit->setText (GET (proxyUser).toString ());
|
||||
ui->proxySaveCheck->setChecked (GET (proxySavePassword).toBool ());
|
||||
if (ui->proxySaveCheck->isChecked ()) {
|
||||
ui->proxyPassEdit->setText (encode (GET (proxyPassword).toString ()));
|
||||
}
|
||||
else {
|
||||
ui->proxyPassEdit->setText (QNetworkProxy::applicationProxy ().password ());
|
||||
}
|
||||
ui->updateCombo->setCurrentIndex (GET (autoUpdateType).toInt ());
|
||||
settings.endGroup ();
|
||||
QDir dir(ui->tessdataEdit->text());
|
||||
if (!dir.exists())
|
||||
return;
|
||||
|
||||
settings.beginGroup (settings_names::recogntionGroup);
|
||||
ui->tessdataEdit->setText (GET (tessDataPlace).toString ());
|
||||
QString ocrLanguage = dictionary_.ocrCodeToUi (GET (ocrLanguage).toString ());
|
||||
ui->ocrLangCombo->setCurrentText (ocrLanguage);
|
||||
ui->imageScaleSpin->setValue (GET (imageScale).toInt ());
|
||||
LanguageIds names;
|
||||
LanguageCodes languages;
|
||||
|
||||
{//Recognizer substitutions
|
||||
recognizerHelper_->load ();
|
||||
RecognizerHelper::Subs subs = recognizerHelper_->subs ();
|
||||
ui->recognizerFixTable->setRowCount (subs.size ());
|
||||
int row = 0;
|
||||
for (const RecognizerHelper::Sub &sub: subs) {
|
||||
if (!initSubsTableRow (row, sub.language)) {
|
||||
continue;
|
||||
}
|
||||
ui->recognizerFixTable->setItem (row, SubsColSource, new QTableWidgetItem (sub.source));
|
||||
ui->recognizerFixTable->setItem (row, SubsColTarget, new QTableWidgetItem (sub.target));
|
||||
++row;
|
||||
}
|
||||
ui->recognizerFixTable->setRowCount (row + 1);
|
||||
initSubsTableRow (row);
|
||||
ui->recognizerFixTable->resizeColumnsToContents ();
|
||||
const auto files = dir.entryList({"*.traineddata"}, QDir::Files);
|
||||
for (const auto &file : files) {
|
||||
const auto lang = file.left(file.indexOf("."));
|
||||
if (const auto bundle = languages.findByTesseract(lang))
|
||||
names.append(bundle->name);
|
||||
}
|
||||
|
||||
settings.endGroup ();
|
||||
if (names.isEmpty())
|
||||
return;
|
||||
|
||||
|
||||
settings.beginGroup (settings_names::translationGroup);
|
||||
ui->doTranslationCheck->setChecked (GET (doTranslation).toBool ());
|
||||
ui->ignoreSslCheck->setChecked (GET (ignoreSslErrors).toBool ());
|
||||
ui->forceRotateCheck->setChecked (GET (forceRotateTranslators).toBool ());
|
||||
ui->translatorDebugCheck->setChecked (GET (translationDebugMode).toBool ());
|
||||
QString trLanguage = dictionary_.translateCodeToUi (GET (translationLanguage).toString ());
|
||||
ui->translateLangCombo->setCurrentText (trLanguage);
|
||||
ui->translateTimeoutSpin->setValue (GET (translationTimeout).toInt ());
|
||||
|
||||
{// 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
|
||||
std::sort(names.begin(), names.end());
|
||||
ui->tesseractLangCombo->addItems(names);
|
||||
ui->correctLangCombo->addItems(names);
|
||||
}
|
||||
|
||||
bool SettingsEditor::initSubsTableRow (int row, const QString &languageCode) {
|
||||
QString lang = dictionary_.ocrCodeToUi (languageCode);
|
||||
if (!languageCode.isEmpty () && lang == languageCode) {
|
||||
return false;
|
||||
}
|
||||
QComboBox *langCombo = new QComboBox (ui->recognizerFixTable);
|
||||
langCombo->setModel (ui->ocrLangCombo->model ());
|
||||
if (!languageCode.isEmpty ()) {
|
||||
langCombo->setCurrentText (lang);
|
||||
}
|
||||
else {
|
||||
langCombo->setCurrentIndex (ui->ocrLangCombo->currentIndex ());
|
||||
}
|
||||
ui->recognizerFixTable->setCellWidget (row, SubsColLanguage, langCombo);
|
||||
return true;
|
||||
void SettingsEditor::updateCorrectionsTable()
|
||||
{
|
||||
}
|
||||
|
||||
void SettingsEditor::saveState () const {
|
||||
QSettings settings;
|
||||
settings.beginGroup (settings_names::guiGroup);
|
||||
settings.setValue (objectName () + "_" + settings_names::geometry, saveGeometry ());
|
||||
}
|
||||
void SettingsEditor::updateTranslators(const QStringList &enabled)
|
||||
{
|
||||
ui->translatorList->clear();
|
||||
|
||||
void SettingsEditor::loadState () {
|
||||
QSettings settings;
|
||||
settings.beginGroup (settings_names::guiGroup);
|
||||
restoreGeometry (settings.value (objectName () + "_" + settings_names::geometry).toByteArray ());
|
||||
}
|
||||
QDir dir(translatorsDir_);
|
||||
if (!dir.exists())
|
||||
return;
|
||||
|
||||
void SettingsEditor::initOcrLangCombo (const QString &path) {
|
||||
ui->ocrLangCombo->clear ();
|
||||
ui->ocrLangCombo->addItems (dictionary_.availableOcrLanguagesUi (path));
|
||||
}
|
||||
auto files = dir.entryList({"*.js"}, QDir::Files);
|
||||
std::sort(files.begin(), files.end());
|
||||
ui->translatorList->addItems(files);
|
||||
|
||||
void SettingsEditor::recognizerFixTableItemChanged (QTableWidgetItem *item) {
|
||||
ST_ASSERT (item->column () < 3);
|
||||
int row = item->row ();
|
||||
QTableWidget *t = ui->recognizerFixTable;
|
||||
#define CHECK(COL) (!t->item (row, COL) || t->item (row, COL)->text ().isEmpty ())
|
||||
bool isRowEmpty = CHECK (SubsColSource) && CHECK (SubsColTarget);
|
||||
#undef CHECK
|
||||
int lastRow = ui->recognizerFixTable->rowCount () - 1;
|
||||
if (isRowEmpty && row != lastRow) {
|
||||
ui->recognizerFixTable->removeRow (row);
|
||||
}
|
||||
else if (!isRowEmpty && row == lastRow) {
|
||||
int newRow = lastRow + 1;
|
||||
ui->recognizerFixTable->insertRow (newRow);
|
||||
initSubsTableRow (newRow);
|
||||
for (auto i = 0, end = ui->translatorList->count(); i < end; ++i) {
|
||||
auto item = ui->translatorList->item(i);
|
||||
item->setCheckState(enabled.contains(item->text()) ? Qt::Checked
|
||||
: Qt::Unchecked);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
#define SETTINGSEDITOR_H
|
||||
#pragma once
|
||||
|
||||
#include <QDialog>
|
||||
#include <QButtonGroup>
|
||||
#include <QMap>
|
||||
|
||||
class QTableWidgetItem;
|
||||
namespace Ui {
|
||||
class SettingsEditor;
|
||||
#include "settings.h"
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
class SettingsEditor;
|
||||
}
|
||||
class LanguageHelper;
|
||||
class TranslatorHelper;
|
||||
class RecognizerHelper;
|
||||
|
||||
class SettingsEditor : public QDialog {
|
||||
class SettingsEditor : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
enum SubsCol {
|
||||
SubsColLanguage = 0, SubsColSource, SubsColTarget
|
||||
};
|
||||
public:
|
||||
explicit SettingsEditor();
|
||||
~SettingsEditor();
|
||||
|
||||
public:
|
||||
explicit SettingsEditor (const LanguageHelper &dictionary, QWidget *parent = 0);
|
||||
~SettingsEditor ();
|
||||
Settings settings() const;
|
||||
void setSettings(const Settings &settings);
|
||||
|
||||
signals:
|
||||
void settingsEdited ();
|
||||
void updateCheckRequested ();
|
||||
private:
|
||||
void updateCurrentPage();
|
||||
void openTessdataDialog();
|
||||
void updateTesseractLanguages();
|
||||
void updateCorrectionsTable();
|
||||
void updateTranslators(const QStringList &enabled);
|
||||
void updateTranslationLanguages();
|
||||
|
||||
public slots:
|
||||
void done (int result);
|
||||
|
||||
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_;
|
||||
Ui::SettingsEditor *ui;
|
||||
QString translatorsDir_;
|
||||
};
|
||||
|
||||
#endif // SETTINGSEDITOR_H
|
||||
|
@ -6,15 +6,25 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>553</width>
|
||||
<height>483</height>
|
||||
<width>889</width>
|
||||
<height>549</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Настройки</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_6">
|
||||
<item row="1" column="0" colspan="2">
|
||||
<item row="0" column="0">
|
||||
<widget class="QListView" name="pagesList">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="4">
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
@ -24,20 +34,23 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<item row="0" column="1" colspan="3">
|
||||
<widget class="QStackedWidget" name="pagesView">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="commonTab">
|
||||
<attribute name="title">
|
||||
<string>Общее</string>
|
||||
</attribute>
|
||||
<layout class="QGridLayout" name="gridLayout_8">
|
||||
<item row="0" column="0">
|
||||
<widget class="QWidget" name="pageGeneral">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Горячие клавиши</string>
|
||||
<string>Shortcuts</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
@ -46,7 +59,7 @@
|
||||
<string><html><head/><body><p>Сочетание клавиш для перехода в режим захвата.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Захватить</string>
|
||||
<string>Capture</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -59,7 +72,7 @@
|
||||
<string><html><head/><body><p>Сочетание клавиш для перехода в режим захвата, но с использованием последнего использованного, а не текущего, изображения.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Захватить повторно</string>
|
||||
<string>Repeat capture</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -72,7 +85,7 @@
|
||||
<string><html><head/><body><p>Сочетание клавиш для повторного отображения последнего результата.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Показать</string>
|
||||
<string>Show last result</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -85,7 +98,7 @@
|
||||
<string><html><head/><body><p>Сочетание клавиш для копирования последнего результата в буфер обмена.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Скопировать</string>
|
||||
<string>Copy result to clipboard</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -95,16 +108,16 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="title">
|
||||
<string>Прокси</string>
|
||||
<string>Proxy</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_12">
|
||||
<property name="text">
|
||||
<string>Тип:</string>
|
||||
<string>Type:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -114,7 +127,7 @@
|
||||
<item row="0" column="2">
|
||||
<widget class="QLabel" name="label_15">
|
||||
<property name="text">
|
||||
<string>Пользователь:</string>
|
||||
<string>User:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -124,7 +137,7 @@
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_13">
|
||||
<property name="text">
|
||||
<string>Адрес:</string>
|
||||
<string>Address:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -134,7 +147,7 @@
|
||||
<item row="1" column="2">
|
||||
<widget class="QLabel" name="label_16">
|
||||
<property name="text">
|
||||
<string>Пароль:</string>
|
||||
<string>Password:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -144,7 +157,7 @@
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_14">
|
||||
<property name="text">
|
||||
<string>Порт:</string>
|
||||
<string>Port:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -161,113 +174,63 @@
|
||||
<item row="2" column="2" colspan="2">
|
||||
<widget class="QCheckBox" name="proxySaveCheck">
|
||||
<property name="text">
|
||||
<string>Сохранять пароль (небезопасно)</string>
|
||||
<string>save password (unsafe)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="resultGroup">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Вывод результата</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_4">
|
||||
<item row="0" column="0">
|
||||
<widget class="QRadioButton" name="trayRadio">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Трей</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QRadioButton" name="dialogRadio">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Окно</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</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">
|
||||
<item>
|
||||
<spacer name="verticalSpacer_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>270</height>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="recognizeTab">
|
||||
<attribute name="title">
|
||||
<string>Распознавание</string>
|
||||
</attribute>
|
||||
<widget class="QWidget" name="pageRecognize">
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="toolTip">
|
||||
<string><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">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
@ -278,66 +241,50 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<item row="0" column="1" colspan="2">
|
||||
<widget class="QLineEdit" name="tessdataEdit">
|
||||
<property name="placeholderText">
|
||||
<string>Enter path</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QToolButton" name="tessdataButton">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
<item row="1" column="1" colspan="3">
|
||||
<widget class="QComboBox" name="tesseractLangCombo"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="pageCorrect">
|
||||
<layout class="QGridLayout" name="gridLayout_10">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_18">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Заполняется на основании содержания tessdata</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Язык распознавания</string>
|
||||
<string>Language:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>ocrLangCombo</cstring>
|
||||
<cstring>tesseractLangCombo</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1" colspan="2">
|
||||
<widget class="QComboBox" name="ocrLangCombo"/>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="correctLangCombo"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="toolTip">
|
||||
<string><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">
|
||||
<item row="1" column="0" colspan="2">
|
||||
<widget class="QLabel" name="label_11">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Символы, регулярно распознаваемые с ошибками. При обнаружении будут заменены на указанные.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Исправления:</string>
|
||||
<string>Corrections</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0" colspan="3">
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QTableWidget" name="recognizerFixTable">
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectRows</enum>
|
||||
@ -345,59 +292,45 @@
|
||||
<property name="sortingEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Язык</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Исходный текст</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Исправление</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="translateTab">
|
||||
<attribute name="title">
|
||||
<string>Перевод</string>
|
||||
</attribute>
|
||||
<layout class="QGridLayout" name="gridLayout_7">
|
||||
<widget class="QWidget" name="pageTranslate">
|
||||
<layout class="QGridLayout" name="gridLayout_9">
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="doTranslationCheck">
|
||||
<widget class="QCheckBox" name="translatorDebugCheck">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Необходимо ли переводить (вкл) распознанный текст.</p></body></html></string>
|
||||
<string><html><head/><body><p>Отображает окно переводчика. Следует использовать только для разработки переводчиков.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Переводить текст</string>
|
||||
<string>Debug mode</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_9">
|
||||
<property name="toolTip">
|
||||
<string><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">
|
||||
<item row="3" column="2">
|
||||
<widget class="QSpinBox" name="translateTimeoutSpin">
|
||||
<property name="suffix">
|
||||
<string> сек.</string>
|
||||
<string> secs</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1" colspan="2">
|
||||
<widget class="QComboBox" name="translateLangCombo"/>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="ignoreSslCheck">
|
||||
<property name="text">
|
||||
<string>Ignore SSL errors</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0" colspan="3">
|
||||
<widget class="QLabel" name="label_10">
|
||||
<property name="text">
|
||||
<string>Translators</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
@ -405,23 +338,13 @@
|
||||
<string><html><head/><body><p>Язык, на который осуществляется перевод.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Язык результата:</string>
|
||||
<string>Language:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>translateLangCombo</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0" colspan="3">
|
||||
<widget class="QLabel" name="label_10">
|
||||
<property name="text">
|
||||
<string>Переводчики:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0" colspan="3">
|
||||
<widget class="QListWidget" name="translatorList">
|
||||
<property name="toolTip">
|
||||
@ -438,29 +361,148 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1" colspan="2">
|
||||
<widget class="QCheckBox" name="translatorDebugCheck">
|
||||
<item row="1" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="doTranslationCheck">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Отображает окно переводчика. Следует использовать только для разработки переводчиков.</p></body></html></string>
|
||||
<string><html><head/><body><p>Необходимо ли переводить (вкл) распознанный текст.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Режим отладки</string>
|
||||
<string>Translate text</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1" colspan="2">
|
||||
<widget class="QComboBox" name="translateLangCombo"/>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="2">
|
||||
<widget class="QLabel" name="label_9">
|
||||
<property name="toolTip">
|
||||
<string><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">
|
||||
<widget class="QCheckBox" name="ignoreSslCheck">
|
||||
<property name="text">
|
||||
<string>Игнорировать ошибки SSL</string>
|
||||
<spacer name="verticalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="pageUpdate">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QWidget" name="widget_3" native="true">
|
||||
<layout class="QGridLayout" name="gridLayout_5">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_17">
|
||||
<property name="text">
|
||||
<string>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>
|
||||
</item>
|
||||
<item row="1" column="1" colspan="2">
|
||||
<widget class="QCheckBox" name="forceRotateCheck">
|
||||
<property name="text">
|
||||
<string>Принудительно менять переводчики</string>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>389</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
@ -469,29 +511,16 @@
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>tabWidget</tabstop>
|
||||
<tabstop>captureEdit</tabstop>
|
||||
<tabstop>repeatCaptureEdit</tabstop>
|
||||
<tabstop>repeatEdit</tabstop>
|
||||
<tabstop>clipboardEdit</tabstop>
|
||||
<tabstop>trayRadio</tabstop>
|
||||
<tabstop>dialogRadio</tabstop>
|
||||
<tabstop>proxyTypeCombo</tabstop>
|
||||
<tabstop>proxyHostEdit</tabstop>
|
||||
<tabstop>proxyPortSpin</tabstop>
|
||||
<tabstop>proxyUserEdit</tabstop>
|
||||
<tabstop>proxyPassEdit</tabstop>
|
||||
<tabstop>proxySaveCheck</tabstop>
|
||||
<tabstop>tessdataEdit</tabstop>
|
||||
<tabstop>tessdataButton</tabstop>
|
||||
<tabstop>ocrLangCombo</tabstop>
|
||||
<tabstop>imageScaleSpin</tabstop>
|
||||
<tabstop>recognizerFixTable</tabstop>
|
||||
<tabstop>doTranslationCheck</tabstop>
|
||||
<tabstop>translatorDebugCheck</tabstop>
|
||||
<tabstop>translateTimeoutSpin</tabstop>
|
||||
<tabstop>translateLangCombo</tabstop>
|
||||
<tabstop>translatorList</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections>
|
||||
|
@ -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 isTranslationFinished = false; // async translation request
|
||||
var isScheduled = false;
|
||||
var lastText = '';
|
||||
var active = window.location.href !== "about:blank";
|
||||
|
||||
function checkFinished () {
|
||||
if (!isPageLoaded || !isTranslationFinished || isScheduled) return;
|
||||
isScheduled = true;
|
||||
setTimeout(function () {
|
||||
var spans = [].slice.call (document.querySelectorAll (
|
||||
'span.translation > span, #result_box > span'));
|
||||
var text = spans.reduce (function (res, i) {
|
||||
return res + ' ' + i.innerText;
|
||||
}, '');
|
||||
console.log (text);
|
||||
st_wtp.translated (text);
|
||||
isTranslationFinished = isScheduled = false;
|
||||
}, 2000); // wait for gui fill
|
||||
if (!active) return;
|
||||
|
||||
let spans = [].slice.call (document.querySelectorAll ('span.translation > span, #result_box > span'));
|
||||
let text = spans.reduce (function (res, i) {
|
||||
return res + ' ' + i.innerText;
|
||||
}, '');
|
||||
|
||||
if (text === lastText || text === '')
|
||||
return;
|
||||
|
||||
console.log ('translated text', text, 'old', lastText, 'size', text.length, lastText.length);
|
||||
lastText = text;
|
||||
active = false;
|
||||
proxy.setTranslated (text);
|
||||
}
|
||||
function onResourceLoad (url) {
|
||||
if (url.indexOf ('/translate_a/single') > -1) {
|
||||
isTranslationFinished = true;
|
||||
if (isPageLoaded) {
|
||||
checkFinished ();
|
||||
}
|
||||
}
|
||||
}
|
||||
st_wtp.resourceLoaded.connect (onResourceLoad);
|
||||
function onPageLoad () {
|
||||
if (window.location.href.indexOf('about:blank') === 0) {
|
||||
translate ();
|
||||
|
||||
function translate (text, from, to){
|
||||
console.log('start translate', text, from, to)
|
||||
active = true;
|
||||
|
||||
if (window.location.href.indexOf('//translate.google') !== -1
|
||||
&& window.location.href.indexOf('&tl='+to+'&') !== -1) {
|
||||
document.querySelector('textarea#source').value=text;
|
||||
return;
|
||||
}
|
||||
|
||||
isPageLoaded = true;
|
||||
if (isTranslationFinished) {
|
||||
checkFinished ();
|
||||
}
|
||||
}
|
||||
window.onload = onPageLoad();
|
||||
|
||||
function translate (){
|
||||
if (window.location.href.indexOf('https://translate.google') === 0) {
|
||||
window.location = 'about:blank';
|
||||
return;
|
||||
}
|
||||
|
||||
var url = 'https://translate.google.com/#auto/' +
|
||||
st_wtp.resultLanguage + '/' + st_wtp.sourceText;
|
||||
// var url = 'https://translate.google.com/#auto/' + to + '/' + text;
|
||||
let url = 'https://translate.google.com/#view=home&op=translate&sl=auto&tl=' + to + '&text=' + text;
|
||||
console.log("setting url", url);
|
||||
window.location = encodeURI (url);
|
||||
|
||||
}
|
||||
|
||||
function init() {
|
||||
proxy.translate.connect (translate);
|
||||
setInterval(checkFinished, 300);
|
||||
}
|
||||
|
||||
|
@ -1,36 +1,42 @@
|
||||
var isPageLoaded = false;
|
||||
var isTranslationFinished = true; // async translation request
|
||||
var isScheduled = false;
|
||||
var lastText = '';
|
||||
var active = window.location.href !== "about:blank";
|
||||
|
||||
function checkFinished () {
|
||||
if (!isPageLoaded || !isTranslationFinished || isScheduled) return;
|
||||
isScheduled = true;
|
||||
setTimeout(function () {
|
||||
var spans = [].slice.call (document.querySelectorAll ('span.translation-chunk'));
|
||||
var text = spans.reduce (function (res, i) {
|
||||
return res + i.innerText + ' ';
|
||||
}, '');
|
||||
console.log (text);
|
||||
st_wtp.translated (text);
|
||||
isTranslationFinished = isScheduled = false;
|
||||
}, 2000); // wait for gui fill
|
||||
}
|
||||
function onResourceLoad (url) {
|
||||
if (url.indexOf ('/tr.json/translate?') > -1) {
|
||||
isTranslationFinished = true;
|
||||
checkFinished ();
|
||||
}
|
||||
}
|
||||
st_wtp.resourceLoaded.connect (onResourceLoad);
|
||||
function onPageLoad () {
|
||||
isPageLoaded = true;
|
||||
checkFinished ();
|
||||
}
|
||||
window.onload = onPageLoad();
|
||||
if (!active) return;
|
||||
|
||||
function translate (){
|
||||
var url = 'https://translate.yandex.ru/?lang=' + st_wtp.sourceLanguage + '-' +
|
||||
st_wtp.resultLanguage + '&text=' + st_wtp.sourceText ;
|
||||
url = url.replace(new RegExp(' ','g') , '%20')
|
||||
window.location = (url);
|
||||
var spans = [].slice.call (document.querySelectorAll ('span.translation-chunk'));
|
||||
let text = spans.reduce (function (res, i) {
|
||||
return res + ' ' + i.innerText;
|
||||
}, '');
|
||||
|
||||
if (text === lastText || text === '')
|
||||
return;
|
||||
|
||||
console.log ('translated text', text, 'old', lastText, 'size', text.length, lastText.length);
|
||||
lastText = text;
|
||||
active = false;
|
||||
proxy.setTranslated (text);
|
||||
}
|
||||
|
||||
function translate (text, from, to){
|
||||
console.log('start translate', text, from, to)
|
||||
active = true;
|
||||
|
||||
var langs = 'lang=' + from + '-' + to;
|
||||
if (window.location.href.indexOf('//translate.yandex') !== -1
|
||||
&& window.location.href.indexOf(langs) !== -1) {
|
||||
document.querySelector('textarea#textarea').value=text
|
||||
document.querySelector('div#textbox').dispatchEvent(
|
||||
new Event("input", {bubbles: true, cancelable: true}));
|
||||
return;
|
||||
}
|
||||
|
||||
var url = 'https://translate.yandex.ru/?' + langs + '&text=' + text;
|
||||
url = url.replace(new RegExp(' ','g') , '%20')
|
||||
window.location = url;
|
||||
}
|
||||
|
||||
function init() {
|
||||
proxy.translate.connect (translate);
|
||||
setInterval(checkFinished, 300);
|
||||
}
|
||||
|