diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..c1e50a1 --- /dev/null +++ b/.clang-format @@ -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 + +... diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 4333a2a..0000000 --- a/.travis.yml +++ /dev/null @@ -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 diff --git a/3rd-party/qtsingleapplication/qtlocalpeer.cpp b/3rd-party/qtsingleapplication/qtlocalpeer.cpp deleted file mode 100644 index c7ce527..0000000 --- a/3rd-party/qtsingleapplication/qtlocalpeer.cpp +++ /dev/null @@ -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 -#include -#include - -#if defined(Q_OS_WIN) -#include -#include -typedef BOOL(WINAPI*PProcessIdToSessionId)(DWORD,DWORD*); -static PProcessIdToSessionId pProcessIdToSessionId = 0; -#endif -#if defined(Q_OS_UNIX) -#include -#include -#include -#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) -} diff --git a/3rd-party/qtsingleapplication/qtlocalpeer.h b/3rd-party/qtsingleapplication/qtlocalpeer.h deleted file mode 100644 index 1b533b1..0000000 --- a/3rd-party/qtsingleapplication/qtlocalpeer.h +++ /dev/null @@ -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 -#include -#include - -#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 diff --git a/3rd-party/qtsingleapplication/qtlockedfile.cpp b/3rd-party/qtsingleapplication/qtlockedfile.cpp deleted file mode 100644 index c142a86..0000000 --- a/3rd-party/qtsingleapplication/qtlockedfile.cpp +++ /dev/null @@ -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. -*/ diff --git a/3rd-party/qtsingleapplication/qtlockedfile.h b/3rd-party/qtsingleapplication/qtlockedfile.h deleted file mode 100644 index 84c18e5..0000000 --- a/3rd-party/qtsingleapplication/qtlockedfile.h +++ /dev/null @@ -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 -#ifdef Q_OS_WIN -#include -#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 rmutexes; - QString mutexname; - - Qt::HANDLE getMutexHandle(int idx, bool doCreate); - bool waitMutex(Qt::HANDLE mutex, bool doBlock); - -#endif - LockMode m_lock_mode; -}; -} -#endif diff --git a/3rd-party/qtsingleapplication/qtlockedfile_unix.cpp b/3rd-party/qtsingleapplication/qtlockedfile_unix.cpp deleted file mode 100644 index 976c1b9..0000000 --- a/3rd-party/qtsingleapplication/qtlockedfile_unix.cpp +++ /dev/null @@ -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 -#include -#include -#include - -#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(); -} - diff --git a/3rd-party/qtsingleapplication/qtlockedfile_win.cpp b/3rd-party/qtsingleapplication/qtlockedfile_win.cpp deleted file mode 100644 index 5e21262..0000000 --- a/3rd-party/qtsingleapplication/qtlockedfile_win.cpp +++ /dev/null @@ -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 -#include - -#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); -} diff --git a/3rd-party/qtsingleapplication/qtsingleapplication.cpp b/3rd-party/qtsingleapplication/qtsingleapplication.cpp deleted file mode 100644 index d0fb15d..0000000 --- a/3rd-party/qtsingleapplication/qtsingleapplication.cpp +++ /dev/null @@ -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 - - -/*! - \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 -*/ diff --git a/3rd-party/qtsingleapplication/qtsingleapplication.h b/3rd-party/qtsingleapplication/qtsingleapplication.h deleted file mode 100644 index 049406f..0000000 --- a/3rd-party/qtsingleapplication/qtsingleapplication.h +++ /dev/null @@ -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 - -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 diff --git a/3rd-party/qtsingleapplication/qtsingleapplication.pri b/3rd-party/qtsingleapplication/qtsingleapplication.pri deleted file mode 100644 index 51f30cc..0000000 --- a/3rd-party/qtsingleapplication/qtsingleapplication.pri +++ /dev/null @@ -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 -} diff --git a/3rd-party/qtsingleapplication/qtsinglecoreapplication.cpp b/3rd-party/qtsingleapplication/qtsinglecoreapplication.cpp deleted file mode 100644 index 5634537..0000000 --- a/3rd-party/qtsingleapplication/qtsinglecoreapplication.cpp +++ /dev/null @@ -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() -*/ diff --git a/3rd-party/qtsingleapplication/qtsinglecoreapplication.h b/3rd-party/qtsingleapplication/qtsinglecoreapplication.h deleted file mode 100644 index b87fffe..0000000 --- a/3rd-party/qtsingleapplication/qtsinglecoreapplication.h +++ /dev/null @@ -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 - -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 diff --git a/3rd-party/qtsingleapplication/qtsinglecoreapplication.pri b/3rd-party/qtsingleapplication/qtsinglecoreapplication.pri deleted file mode 100644 index d2d6cc3..0000000 --- a/3rd-party/qtsingleapplication/qtsinglecoreapplication.pri +++ /dev/null @@ -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) -} diff --git a/recources.qrc b/recources.qrc index e636270..e95ac19 100644 --- a/recources.qrc +++ b/recources.qrc @@ -1,11 +1,11 @@ - translations/translation_en.qm - translations/translation_ru.qm - images/STIconBlue.png - images/STIconGreen.png - images/STIconOrange.png - images/STIconRed.png version.json + + share/images/STIconBlue.png + share/images/STIconGreen.png + share/images/STIconOrange.png + share/images/STIconRed.png + diff --git a/screen-translator.pro b/screen-translator.pro new file mode 100644 index 0000000..59cc9e1 --- /dev/null +++ b/screen-translator.pro @@ -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 diff --git a/screentranslator.pro b/screentranslator.pro deleted file mode 100644 index 43c7834..0000000 --- a/screentranslator.pro +++ /dev/null @@ -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 diff --git a/images/STIcon.xcf b/share/images/STIcon.xcf similarity index 100% rename from images/STIcon.xcf rename to share/images/STIcon.xcf diff --git a/images/STIconBlue.png b/share/images/STIconBlue.png similarity index 100% rename from images/STIconBlue.png rename to share/images/STIconBlue.png diff --git a/images/STIconGreen.png b/share/images/STIconGreen.png similarity index 100% rename from images/STIconGreen.png rename to share/images/STIconGreen.png diff --git a/images/STIconOrange.png b/share/images/STIconOrange.png similarity index 100% rename from images/STIconOrange.png rename to share/images/STIconOrange.png diff --git a/images/STIconRed.png b/share/images/STIconRed.png similarity index 100% rename from images/STIconRed.png rename to share/images/STIconRed.png diff --git a/images/icon.ico b/share/images/icon.ico similarity index 100% rename from images/icon.ico rename to share/images/icon.ico diff --git a/src/capture/captureoverlay.cpp b/src/capture/captureoverlay.cpp new file mode 100644 index 0000000..a2d36d3 --- /dev/null +++ b/src/capture/captureoverlay.cpp @@ -0,0 +1,93 @@ +#include "captureoverlay.h" +#include "capturer.h" +#include "task.h" + +#include +#include +#include + +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->captured = selectedPixmap; + task->capturePoint = pos() + selection.topLeft(); + // TODO add customization menus + capturer_.captured(task); +} diff --git a/src/capture/captureoverlay.h b/src/capture/captureoverlay.h new file mode 100644 index 0000000..f8bedc0 --- /dev/null +++ b/src/capture/captureoverlay.h @@ -0,0 +1,30 @@ +#pragma once + +#include + +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_; +}; diff --git a/src/capture/capturer.cpp b/src/capture/capturer.cpp new file mode 100644 index 0000000..1bfabca --- /dev/null +++ b/src/capture/capturer.cpp @@ -0,0 +1,80 @@ +#include "capturer.h" +#include "captureoverlay.h" +#include "manager.h" +#include "settings.h" +#include "task.h" + +#include + +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(); +} diff --git a/src/capture/capturer.h b/src/capture/capturer.h new file mode 100644 index 0000000..fdd5192 --- /dev/null +++ b/src/capture/capturer.h @@ -0,0 +1,33 @@ +#pragma once + +#include "stfwd.h" + +#include + +#include + +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 overlays_; + + LanguageId sourceLanguage_; + LanguageId targetLanguage_; + QStringList translators_; +}; diff --git a/src/correct/corrector.cpp b/src/correct/corrector.cpp new file mode 100644 index 0000000..b259cbc --- /dev/null +++ b/src/correct/corrector.cpp @@ -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; +} diff --git a/src/correct/corrector.h b/src/correct/corrector.h new file mode 100644 index 0000000..a74a494 --- /dev/null +++ b/src/correct/corrector.h @@ -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_; +}; diff --git a/src/globalactionhelper.cpp b/src/globalactionhelper.cpp deleted file mode 100644 index 4361178..0000000 --- a/src/globalactionhelper.cpp +++ /dev/null @@ -1,367 +0,0 @@ -#include "globalactionhelper.h" - -#include -#include - -QHash, 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 -# include -# include - -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(message); - if (event->response_type == XCB_KEY_PRESS) { - xcb_key_press_event_t *keyEvent = static_cast(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 - - -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(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 diff --git a/src/globalactionhelper.h b/src/globalactionhelper.h deleted file mode 100644 index 7924e51..0000000 --- a/src/globalactionhelper.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef GLOBALACTIONHELPER_H -#define GLOBALACTIONHELPER_H - -// Some functions copied from QXT lib - -#include -#include - -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, 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 diff --git a/src/imageprocessing.cpp b/src/imageprocessing.cpp deleted file mode 100644 index 6e5084f..0000000 --- a/src/imageprocessing.cpp +++ /dev/null @@ -1,155 +0,0 @@ -#include - -#include - -#include "imageprocessing.h" -#include "stassert.h" - -#if defined(Q_OS_LINUX) -# include -# include -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 -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 _bwCT; - _bwCT.append (qRgb (255,255,255)); - _bwCT.append (qRgb (0,0,0)); - - QVector _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); -} diff --git a/src/imageprocessing.h b/src/imageprocessing.h deleted file mode 100644 index 61c8c1c..0000000 --- a/src/imageprocessing.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef IMAGEPROCESSING_H -#define IMAGEPROCESSING_H - -#include - -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 diff --git a/src/languagecodes.cpp b/src/languagecodes.cpp new file mode 100644 index 0000000..c876994 --- /dev/null +++ b/src/languagecodes.cpp @@ -0,0 +1,240 @@ +#include "languagecodes.h" + +#include + +#define S(XXX) QStringLiteral(XXX) +#define I(XXX) LanguageId(QStringLiteral(XXX)) +#define TR(XXX) QT_TR_NOOP(XXX) +const std::unordered_map + 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::findById( + const LanguageId &id) const +{ + auto it = codes_.find(id); + if (it != codes_.cend()) + return it->second; + return {}; +} + +std::optional LanguageCodes::findByName( + const QString &name) const +{ + auto it = std::find_if(codes_.cbegin(), codes_.cend(), + [name](const std::pair &i) { + return name == i.second.name; + }); + if (it != codes_.cend()) + return it->second; + return {}; +} + +std::optional LanguageCodes::findByTesseract( + const QString &name) const +{ + auto it = std::find_if(codes_.cbegin(), codes_.cend(), + [name](const std::pair &i) { + return name == i.second.tesseract; + }); + if (it != codes_.cend()) + return it->second; + return {}; +} + +const std::unordered_map + &LanguageCodes::all() const +{ + return codes_; +} diff --git a/src/languagecodes.h b/src/languagecodes.h new file mode 100644 index 0000000..6f3ee85 --- /dev/null +++ b/src/languagecodes.h @@ -0,0 +1,27 @@ +#pragma once + +#include + +#include +#include + +using LanguageId = QString; + +class LanguageCodes +{ +public: + struct Bundle { + LanguageId id; + QString iso639_1; + QString tesseract; + QString name; + }; + + std::optional findById(const LanguageId& id) const; + std::optional findByName(const QString& name) const; + std::optional findByTesseract(const QString& name) const; + const std::unordered_map& all() const; + +private: + const static std::unordered_map codes_; +}; diff --git a/src/languagehelper.cpp b/src/languagehelper.cpp deleted file mode 100644 index 87e02f9..0000000 --- a/src/languagehelper.cpp +++ /dev/null @@ -1,301 +0,0 @@ -#include -#include - -#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"); - -} diff --git a/src/languagehelper.h b/src/languagehelper.h deleted file mode 100644 index ac04d86..0000000 --- a/src/languagehelper.h +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef LANGUAGEHELPER_H -#define LANGUAGEHELPER_H - -#include -#include -#include - -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 translateLanguages_; - QMap ocrLanguages_; -}; - - -#endif // LANGUAGEHELPER_H diff --git a/src/main.cpp b/src/main.cpp index d3fd62a..f8b8430 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,35 +1,48 @@ #ifdef Q_OS_LINUX -# include +#include #endif +#include "apptranslator.h" +#include "manager.h" +#include "singleapplication.h" + #include -#include +#include -#include +#define STR2(XXX) #XXX +#define STR(XXX) STR2(XXX) -#include -#include +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(); } diff --git a/src/manager.cpp b/src/manager.cpp index ba1a099..8de2951 100644 --- a/src/manager.cpp +++ b/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 -#include #include -#include -#include -#include -#include -#include #include #include -#include -#include -#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(*this); + capturer_ = std::make_unique(*this); + recognizer_ = std::make_unique(*this); + translator_ = std::make_unique(*this); + corrector_ = std::make_unique(*this); + representer_ = std::make_unique(*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(); + qRegisterMetaType(); - // 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 actions; - actions << captureAction_ << repeatCaptureAction_ << repeatAction_ << clipboardAction_; - QList 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 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 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 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(); } diff --git a/src/manager.h b/src/manager.h index 90e4714..9491dab 100644 --- a/src/manager.h +++ b/src/manager.h @@ -1,84 +1,40 @@ -#ifndef MANAGER_H -#define MANAGER_H +#pragma once -#include -#include -#include +#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 selections_; - ResultDialog *resultDialog_; - Updater *updater_; - QTimer *updateTimer_; - QAction *captureAction_; - QAction *repeatCaptureAction_; - QAction *repeatAction_; - QAction *clipboardAction_; - bool useResultDialog_; - //! Used threads. For proper termination. - QList threads_; - QString defaultTranslationLanguage_; - QString defaultOrcLanguage_; - bool doTranslation_; - int itemProcessingCount_; + std::unique_ptr tray_; + std::unique_ptr capturer_; + std::unique_ptr recognizer_; + std::unique_ptr corrector_; + std::unique_ptr translator_; + std::unique_ptr representer_; + TaskPtr last_; + int activeTaskCount_{0}; }; - -#endif // MANAGER_H diff --git a/src/ocr/recognizer.cpp b/src/ocr/recognizer.cpp new file mode 100644 index 0000000..f757ad6 --- /dev/null +++ b/src/ocr/recognizer.cpp @@ -0,0 +1,48 @@ +#include "recognizer.h" +#include "manager.h" +#include "recognizerworker.h" +#include "settings.h" +#include "tesseract.h" + +#include + +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); +} diff --git a/src/ocr/recognizer.h b/src/ocr/recognizer.h new file mode 100644 index 0000000..3d66c5b --- /dev/null +++ b/src/ocr/recognizer.h @@ -0,0 +1,25 @@ +#pragma once + +#include "stfwd.h" + +#include + +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_; +}; diff --git a/src/ocr/recognizerworker.cpp b/src/ocr/recognizerworker.cpp new file mode 100644 index 0000000..db76645 --- /dev/null +++ b/src/ocr/recognizerworker.cpp @@ -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(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(); +} diff --git a/src/ocr/recognizerworker.h b/src/ocr/recognizerworker.h new file mode 100644 index 0000000..ac87039 --- /dev/null +++ b/src/ocr/recognizerworker.h @@ -0,0 +1,24 @@ +#pragma once + +#include "stfwd.h" + +#include + +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> engines_; + QString tessdataPath_; +}; diff --git a/src/ocr/tesseract.cpp b/src/ocr/tesseract.cpp new file mode 100644 index 0000000..ba6442a --- /dev/null +++ b/src/ocr/tesseract.cpp @@ -0,0 +1,220 @@ +#include "tesseract.h" +#include "debug.h" +#include "languagecodes.h" +#include "task.h" + +#include +#include + +#if defined(Q_OS_LINUX) +#include +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 +#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 _bwCT; + _bwCT.append(qRgb(255, 255, 255)); + _bwCT.append(qRgb(0, 0, 0)); + + QVector _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(); + + 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(); +} diff --git a/src/ocr/tesseract.h b/src/ocr/tesseract.h new file mode 100644 index 0000000..aeb8a77 --- /dev/null +++ b/src/ocr/tesseract.h @@ -0,0 +1,31 @@ +#pragma once + +#include "stfwd.h" + +#include + +#include + +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 engine_; + QString error_; +}; diff --git a/src/processingitem.cpp b/src/processingitem.cpp deleted file mode 100644 index 5759491..0000000 --- a/src/processingitem.cpp +++ /dev/null @@ -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; -} diff --git a/src/processingitem.h b/src/processingitem.h deleted file mode 100644 index c5b7576..0000000 --- a/src/processingitem.h +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef PROCESSINGITEM_H -#define PROCESSINGITEM_H - -#include - -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 diff --git a/src/recognizer.cpp b/src/recognizer.cpp deleted file mode 100644 index e7ae271..0000000 --- a/src/recognizer.cpp +++ /dev/null @@ -1,90 +0,0 @@ -#include "recognizer.h" - -#include - -#include -#include - -#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); -} diff --git a/src/recognizer.h b/src/recognizer.h deleted file mode 100644 index 529f061..0000000 --- a/src/recognizer.h +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef RECOGNIZER_H -#define RECOGNIZER_H - -#include -#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 diff --git a/src/recognizerhelper.cpp b/src/recognizerhelper.cpp deleted file mode 100644 index d34de3d..0000000 --- a/src/recognizerhelper.cpp +++ /dev/null @@ -1,88 +0,0 @@ -#include -#include -#include -#include -#include - -#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) { -} diff --git a/src/recognizerhelper.h b/src/recognizerhelper.h deleted file mode 100644 index eff2e15..0000000 --- a/src/recognizerhelper.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef RECOGNIZERHELPER_H -#define RECOGNIZERHELPER_H - -#include - -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 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 diff --git a/src/represent/representer.cpp b/src/represent/representer.cpp new file mode 100644 index 0000000..6b4151a --- /dev/null +++ b/src/represent/representer.cpp @@ -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(); + + widget_->show(task); +} diff --git a/src/represent/representer.h b/src/represent/representer.h new file mode 100644 index 0000000..732aac3 --- /dev/null +++ b/src/represent/representer.h @@ -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 widget_; + ResultMode mode_; +}; diff --git a/src/represent/resultwidget.cpp b/src/represent/resultwidget.cpp new file mode 100644 index 0000000..9cf1852 --- /dev/null +++ b/src/represent/resultwidget.cpp @@ -0,0 +1,116 @@ +#include "resultwidget.h" +#include "debug.h" +#include "task.h" + +#include +#include +#include +#include +#include + +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(event)->button(); + if (button == Qt::LeftButton) { + hide(); + } + // else if (button == Qt::RightButton) { + // QAction *action = contextMenu_->exec(QCursor::pos()); + // if (recognizeSubMenu_->findChildren().contains(action)) { + // ProcessingItem item = item_; + // task->translated = task->recognized = QString(); + // task->ocrLanguage = dictionary_.ocrUiToCode(action->text()); + // emit requestRecognize(item); + // } else if (translateSubMenu_->findChildren().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); +} diff --git a/src/represent/resultwidget.h b/src/represent/resultwidget.h new file mode 100644 index 0000000..2480ef4 --- /dev/null +++ b/src/represent/resultwidget.h @@ -0,0 +1,24 @@ +#pragma once + +#include "stfwd.h" + +#include + +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_; +}; diff --git a/src/resultdialog.cpp b/src/resultdialog.cpp deleted file mode 100644 index ac1cf49..0000000 --- a/src/resultdialog.cpp +++ /dev/null @@ -1,121 +0,0 @@ -#include "resultdialog.h" -#include "ui_resultdialog.h" -#include "stassert.h" -#include "languagehelper.h" - -#include -#include -#include - -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(event)->button (); - if (button == Qt::RightButton) { - QAction *action = contextMenu_->exec (QCursor::pos ()); - if (recognizeSubMenu_->findChildren ().contains (action)) { - ProcessingItem item = item_; - item.translated = item.recognized = QString (); - item.ocrLanguage = dictionary_.ocrUiToCode (action->text ()); - emit requestRecognize (item); - } - else if (translateSubMenu_->findChildren ().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 (); -} diff --git a/src/resultdialog.h b/src/resultdialog.h deleted file mode 100644 index 4b6be88..0000000 --- a/src/resultdialog.h +++ /dev/null @@ -1,51 +0,0 @@ -#ifndef RESULTDIALOG_H -#define RESULTDIALOG_H - -#include -#include - -#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 diff --git a/src/resultdialog.ui b/src/resultdialog.ui deleted file mode 100644 index f7c8044..0000000 --- a/src/resultdialog.ui +++ /dev/null @@ -1,116 +0,0 @@ - - - ResultDialog - - - - 0 - 0 - 260 - 222 - - - - Результат - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::StyledPanel - - - QFrame::Plain - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - Qt::AlignCenter - - - true - - - - - - - Qt::Horizontal - - - - - - - - - - Qt::AlignCenter - - - true - - - - - - - Qt::Horizontal - - - - - - - - - - Qt::AlignCenter - - - true - - - - - - - - - - - diff --git a/src/selectiondialog.cpp b/src/selectiondialog.cpp deleted file mode 100644 index edb51d7..0000000 --- a/src/selectiondialog.cpp +++ /dev/null @@ -1,122 +0,0 @@ -#include "selectiondialog.h" -#include "ui_selectiondialog.h" -#include "languagehelper.h" -#include "stassert.h" - -#include -#include -#include -#include - -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 (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 (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 (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 (); -} diff --git a/src/selectiondialog.h b/src/selectiondialog.h deleted file mode 100644 index 4826713..0000000 --- a/src/selectiondialog.h +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef SELECTIONDIALOG_H -#define SELECTIONDIALOG_H - -#include -#include -#include - -#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 diff --git a/src/selectiondialog.ui b/src/selectiondialog.ui deleted file mode 100644 index 6ec66b4..0000000 --- a/src/selectiondialog.ui +++ /dev/null @@ -1,43 +0,0 @@ - - - SelectionDialog - - - - 0 - 0 - 400 - 300 - - - - Dialog - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - CrossCursor - - - - - - - - - - - diff --git a/src/service/apptranslator.cpp b/src/service/apptranslator.cpp new file mode 100644 index 0000000..bf0b851 --- /dev/null +++ b/src/service/apptranslator.cpp @@ -0,0 +1,131 @@ +#include "apptranslator.h" +#include "debug.h" + +#include +#include +#include +#include + +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(); + 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(); +//} diff --git a/src/service/apptranslator.h b/src/service/apptranslator.h new file mode 100644 index 0000000..bb9e8ef --- /dev/null +++ b/src/service/apptranslator.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +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_; +}; diff --git a/src/service/debug.cpp b/src/service/debug.cpp new file mode 100644 index 0000000..90dc461 --- /dev/null +++ b/src/service/debug.cpp @@ -0,0 +1,6 @@ +#include "debug.h" + +namespace debug +{ +std::atomic_bool isTrace; +} diff --git a/src/service/debug.h b/src/service/debug.h new file mode 100644 index 0000000..abcc306 --- /dev/null +++ b/src/service/debug.h @@ -0,0 +1,71 @@ +#pragma once + +#include + +#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 diff --git a/src/service/globalaction.cpp b/src/service/globalaction.cpp new file mode 100644 index 0000000..e7a95e4 --- /dev/null +++ b/src/service/globalaction.cpp @@ -0,0 +1,481 @@ +#include "globalaction.h" +#include "debug.h" + +#include + +QHash, 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 +#include +#include + +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(message); + if (event->response_type == XCB_KEY_PRESS) { + xcb_key_press_event_t *keyEvent = + static_cast(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 + +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(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 + +static bool isInited = false; +static QHash, 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 diff --git a/src/service/globalaction.h b/src/service/globalaction.h new file mode 100644 index 0000000..3356c14 --- /dev/null +++ b/src/service/globalaction.h @@ -0,0 +1,29 @@ +#pragma once + +// Some functions copied from QXT lib + +#include +#include + +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, 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; +}; diff --git a/src/service/singleapplication.cpp b/src/service/singleapplication.cpp new file mode 100644 index 0000000..e68c80b --- /dev/null +++ b/src/service/singleapplication.cpp @@ -0,0 +1,31 @@ +#include "singleapplication.h" +#include "debug.h" + +#include +#include +#include + +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(); +} diff --git a/src/service/singleapplication.h b/src/service/singleapplication.h new file mode 100644 index 0000000..131688f --- /dev/null +++ b/src/service/singleapplication.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +class SingleApplication +{ +public: + explicit SingleApplication(const QString &baseName = {}); + + bool isValid() const; + +private: + QLockFile lockFile_; +}; diff --git a/src/service/widgetstate.cpp b/src/service/widgetstate.cpp new file mode 100644 index 0000000..94d7291 --- /dev/null +++ b/src/service/widgetstate.cpp @@ -0,0 +1,118 @@ +#include "widgetstate.h" +#include "debug.h" + +#include +#include +#include +#include +#include +#include + +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 +void handleState(QSettings *settings, QWidget *widget, Action action) +{ + auto instance = qobject_cast(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(settings, widget, action); + handleState(settings, widget, action); + handleState(settings, widget, action); + } + + settings->beginGroup(widget->objectName()); + const auto children = + widget->findChildren(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(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); +} diff --git a/src/service/widgetstate.h b/src/service/widgetstate.h new file mode 100644 index 0000000..2c46245 --- /dev/null +++ b/src/service/widgetstate.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +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); +}; diff --git a/src/settings.cpp b/src/settings.cpp new file mode 100644 index 0000000..a2c4ad3 --- /dev/null +++ b/src/settings.cpp @@ -0,0 +1,152 @@ +#include "settings.h" + +#include + +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(); +} diff --git a/src/settings.h b/src/settings.h index 1f67a4e..5a55f7a 100644 --- a/src/settings.h +++ b/src/settings.h @@ -1,81 +1,54 @@ -#ifndef SETTINGS_H -#define SETTINGS_H +#pragma once -#include +#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 - //! Recognition - const QString recogntionGroup = "Recognition"; - const QString tessDataPlace = "tessdata_dir"; - const QString ocrLanguage = "language"; - const QString imageScale = "image_scale"; +#include - //! 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; - //! 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 +}; diff --git a/src/settingseditor.cpp b/src/settingseditor.cpp index 2ebd8f4..3887af6 100644 --- a/src/settingseditor.cpp +++ b/src/settingseditor.cpp @@ -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 -#include -#include -#include -#include - +#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 +#include +#include +#include +#include +#include - 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 proxyTypeNames; - proxyTypeNames.insert (QNetworkProxy::NoProxy, tr ("Нет")); - proxyTypeNames.insert (QNetworkProxy::Socks5Proxy, tr ("SOCKS 5")); - proxyTypeNames.insert (QNetworkProxy::HttpProxy, tr ("HTTP")); - QList 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 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 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(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); +} diff --git a/src/settingseditor.h b/src/settingseditor.h index 8b6f669..c8b65c7 100644 --- a/src/settingseditor.h +++ b/src/settingseditor.h @@ -1,54 +1,33 @@ -#ifndef SETTINGSEDITOR_H -#define SETTINGSEDITOR_H +#pragma once #include -#include -#include -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 diff --git a/src/settingseditor.ui b/src/settingseditor.ui index 77a9af4..229224e 100644 --- a/src/settingseditor.ui +++ b/src/settingseditor.ui @@ -6,15 +6,25 @@ 0 0 - 553 - 483 + 889 + 549 Настройки - + + + + + 0 + 0 + + + + + Qt::Horizontal @@ -24,20 +34,23 @@ - - + + + + + 0 + 0 + + 0 - - - Общее - - - + + + - Горячие клавиши + Shortcuts @@ -46,7 +59,7 @@ <html><head/><body><p>Сочетание клавиш для перехода в режим захвата.</p></body></html> - Захватить + Capture @@ -59,7 +72,7 @@ <html><head/><body><p>Сочетание клавиш для перехода в режим захвата, но с использованием последнего использованного, а не текущего, изображения.</p></body></html> - Захватить повторно + Repeat capture @@ -72,7 +85,7 @@ <html><head/><body><p>Сочетание клавиш для повторного отображения последнего результата.</p></body></html> - Показать + Show last result @@ -85,7 +98,7 @@ <html><head/><body><p>Сочетание клавиш для копирования последнего результата в буфер обмена.</p></body></html> - Скопировать + Copy result to clipboard @@ -95,16 +108,16 @@ - + - Прокси + Proxy - Тип: + Type: @@ -114,7 +127,7 @@ - Пользователь: + User: @@ -124,7 +137,7 @@ - Адрес: + Address: @@ -134,7 +147,7 @@ - Пароль: + Password: @@ -144,7 +157,7 @@ - Порт: + Port: @@ -161,113 +174,63 @@ - Сохранять пароль (небезопасно) + save password (unsafe) - - - - - - - 0 - 0 - - - - Вывод результата - - - - - - - 0 - 0 - - - - Трей - - - - - - - - 0 - 0 - - - - Окно - - - true - - - - - - - - - - Обновление - - - - - - Проверять обновления: - - - - - - - - - - - 0 - 0 - - - - Проверить - - - - - - - - - - + + Qt::Vertical 20 - 270 + 40 - - - Распознавание - + + + + + <html><head/><body><p>Заполняется на основании содержания tessdata</p></body></html> + + + Language + + + tesseractLangCombo + + + + + + + ... + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + @@ -278,66 +241,50 @@ - + Enter path - - - - ... - - + + - - + + + + + + <html><head/><body><p>Заполняется на основании содержания tessdata</p></body></html> - Язык распознавания + Language: - ocrLangCombo + tesseractLangCombo - - + + - - - - <html><head/><body><p>Масштабирование изображения для улучшения распознания. Больше - лучше (до определенных пределов), но медленнее и потребляет больше памяти.</p><p>Рекомендуемые значения от 5 до 10.</p></body></html> - - - Увеличение масштаба - - - imageScaleSpin - - - - - - - + <html><head/><body><p>Символы, регулярно распознаваемые с ошибками. При обнаружении будут заменены на указанные.</p></body></html> - Исправления: + Corrections Qt::AlignCenter - + QAbstractItemView::SelectRows @@ -345,59 +292,45 @@ true - - - Язык - - - - - Исходный текст - - - - - Исправление - - - - - Перевод - - + + - + - <html><head/><body><p>Необходимо ли переводить (вкл) распознанный текст.</p></body></html> + <html><head/><body><p>Отображает окно переводчика. Следует использовать только для разработки переводчиков.</p></body></html> - Переводить текст + Debug mode - - - - <html><head/><body><p>Максимальное время, которое может быть затрачено на перевод, чтобы он не считался &quot;зависшим&quot;.</p></body></html> - - - Максимальное время перевода: - - - - + - сек. + secs - - + + + + Ignore SSL errors + + + + + + + Translators + + + Qt::AlignCenter + + @@ -405,23 +338,13 @@ <html><head/><body><p>Язык, на который осуществляется перевод.</p></body></html> - Язык результата: + Language: translateLangCombo - - - - Переводчики: - - - Qt::AlignCenter - - - @@ -438,29 +361,148 @@ - - + + - <html><head/><body><p>Отображает окно переводчика. Следует использовать только для разработки переводчиков.</p></body></html> + <html><head/><body><p>Необходимо ли переводить (вкл) распознанный текст.</p></body></html> - Режим отладки + Translate text + + + + + + + <html><head/><body><p>Максимальное время, которое может быть затрачено на перевод, чтобы он не считался &quot;зависшим&quot;.</p></body></html> + + + Single translator timeout: + + + + + + + + + + + + 0 + 0 + + + + Result type + + + + + + + 0 + 0 + + + + Tray + + + + + + + + 0 + 0 + + + + Window + + + true + + + + + + - - - Игнорировать ошибки SSL + + + Qt::Vertical + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + Check for updates: + + + + + + + + + + + 0 + 0 + + + + Check now + + + + - - - - Принудительно менять переводчики + + + + Qt::Vertical - + + + 20 + 389 + + + @@ -469,29 +511,16 @@ - tabWidget captureEdit repeatCaptureEdit repeatEdit clipboardEdit - trayRadio - dialogRadio proxyTypeCombo proxyHostEdit proxyPortSpin proxyUserEdit proxyPassEdit proxySaveCheck - tessdataEdit - tessdataButton - ocrLangCombo - imageScaleSpin - recognizerFixTable - doTranslationCheck - translatorDebugCheck - translateTimeoutSpin - translateLangCombo - translatorList diff --git a/src/stassert.h b/src/stassert.h deleted file mode 100644 index d330cee..0000000 --- a/src/stassert.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef ST_ASSERT_H -#define ST_ASSERT_H - -#include - -#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 - diff --git a/src/stfwd.h b/src/stfwd.h new file mode 100644 index 0000000..bfd7b81 --- /dev/null +++ b/src/stfwd.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +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; +using LanguageId = QString; +using LanguageIds = QStringList; diff --git a/src/task.h b/src/task.h new file mode 100644 index 0000000..940e4df --- /dev/null +++ b/src/task.h @@ -0,0 +1,32 @@ +#pragma once + +#include "stfwd.h" + +#include + +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; + +Q_DECLARE_METATYPE(TaskPtr); diff --git a/src/translate/translator.cpp b/src/translate/translator.cpp new file mode 100644 index 0000000..f273cf6 --- /dev/null +++ b/src/translate/translator.cpp @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +static std::map loadScripts(const QString &dir, + const QStringList &scriptNames) +{ + std::map 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(*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 idlePages; + std::unordered_set 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 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(); +} diff --git a/src/translate/translator.h b/src/translate/translator.h new file mode 100644 index 0000000..7c1a7ab --- /dev/null +++ b/src/translate/translator.h @@ -0,0 +1,37 @@ +#pragma once + +#include "stfwd.h" + +#include + +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 queue_; + std::map> pages_; +}; diff --git a/src/translate/webpage.cpp b/src/translate/webpage.cpp new file mode 100644 index 0000000..103fba9 --- /dev/null +++ b/src/translate/webpage.cpp @@ -0,0 +1,166 @@ +#include "webpage.h" +#include "debug.h" +#include "languagecodes.h" +#include "task.h" +#include "translator.h" +#include "webpageproxy.h" + +#include +#include +#include +#include + +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()); +} diff --git a/src/translate/webpage.h b/src/translate/webpage.h new file mode 100644 index 0000000..8699868 --- /dev/null +++ b/src/translate/webpage.h @@ -0,0 +1,50 @@ +#pragma once + +#include "stfwd.h" + +#include +#include + +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 proxy_; + TaskPtr task_; + bool ignoreSslErrors_{false}; + bool isBusy_{false}; + QDateTime nextIdleTime_; + std::chrono::seconds timeout_{15}; +}; diff --git a/src/translate/webpageproxy.cpp b/src/translate/webpageproxy.cpp new file mode 100644 index 0000000..d2aeecb --- /dev/null +++ b/src/translate/webpageproxy.cpp @@ -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); +} diff --git a/src/translate/webpageproxy.h b/src/translate/webpageproxy.h new file mode 100644 index 0000000..2c326d0 --- /dev/null +++ b/src/translate/webpageproxy.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +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_; +}; diff --git a/src/translatorhelper.cpp b/src/translatorhelper.cpp deleted file mode 100644 index 95800b7..0000000 --- a/src/translatorhelper.cpp +++ /dev/null @@ -1,88 +0,0 @@ -#include -#include -#include -#include - -#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 (); -} diff --git a/src/translatorhelper.h b/src/translatorhelper.h deleted file mode 100644 index 9083c76..0000000 --- a/src/translatorhelper.h +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef TRANSLATORHELPER_H -#define TRANSLATORHELPER_H - -#include - -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 diff --git a/src/trayicon.cpp b/src/trayicon.cpp new file mode 100644 index 0000000..bdb6560 --- /dev/null +++ b/src/trayicon.cpp @@ -0,0 +1,221 @@ +#include "trayicon.h" +#include "debug.h" +#include "globalaction.h" +#include "manager.h" +#include "settings.h" + +#include +#include + +TrayIcon::TrayIcon(Manager &manager) + : manager_(manager) + , tray_(std::make_unique()) + , iconUpdateTimer_(std::make_unique()) +{ + 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 blockable{captureAction_, repeatCaptureAction_, + showLastAction_, settingsAction_}; + for (auto &action : blockable) action->setEnabled(false); + return; + } + + captureAction_->setEnabled(true); + settingsAction_->setEnabled(true); + + QVector taskActions{showLastAction_, clipboardAction_}; + for (auto &action : taskActions) action->setEnabled(gotTask_); + + repeatCaptureAction_->setEnabled(canRepeatCapture_); +} + +void TrayIcon::setIcon(TrayIcon::Icon icon, Duration duration) +{ + QMap 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; +} diff --git a/src/trayicon.h b/src/trayicon.h new file mode 100644 index 0000000..f47a406 --- /dev/null +++ b/src/trayicon.h @@ -0,0 +1,55 @@ +#pragma once + +#include "stfwd.h" + +#include + +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 tray_; + + QAction *captureAction_{nullptr}; + QAction *repeatCaptureAction_{nullptr}; + QAction *showLastAction_{nullptr}; + QAction *clipboardAction_{nullptr}; + QAction *settingsAction_{nullptr}; + + std::unique_ptr iconUpdateTimer_; + int activeTaskCount_{0}; + bool isFatalError_{false}; + Icon permanentIcon_{Icon::Idle}; + + bool gotTask_{false}; + bool canRepeatCapture_{false}; + bool isActionsBlocked_{false}; +}; diff --git a/src/updater.cpp b/src/updater.cpp deleted file mode 100644 index fbe15de..0000000 --- a/src/updater.cpp +++ /dev/null @@ -1,264 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include - -#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 (); -} diff --git a/src/updater.h b/src/updater.h deleted file mode 100644 index 8b5d17a..0000000 --- a/src/updater.h +++ /dev/null @@ -1,65 +0,0 @@ -#ifndef UPDATER_H -#define UPDATER_H - -#include -#include -#include - -/*! - * \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 diff --git a/src/utils.cpp b/src/utils.cpp deleted file mode 100644 index d56fb61..0000000 --- a/src/utils.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#include - -#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 proxyTypeOrder () { - QList proxyOrder; - proxyOrder << QNetworkProxy::NoProxy << QNetworkProxy::Socks5Proxy << QNetworkProxy::HttpProxy; - return proxyOrder; -} diff --git a/src/utils.h b/src/utils.h deleted file mode 100644 index 680c36a..0000000 --- a/src/utils.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef UTILS_H -#define UTILS_H - -#include - -QString encode (const QString &source); - -QList proxyTypeOrder (); - -#endif // UTILS_H diff --git a/src/webtranslator.cpp b/src/webtranslator.cpp deleted file mode 100644 index 3d36115..0000000 --- a/src/webtranslator.cpp +++ /dev/null @@ -1,156 +0,0 @@ -#include -#include -#include -#include -#include - -#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)), - this, SLOT (handleSslErrors (QNetworkReply*,QList))); - - 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 &) { - 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); -} diff --git a/src/webtranslator.h b/src/webtranslator.h deleted file mode 100644 index b423a72..0000000 --- a/src/webtranslator.h +++ /dev/null @@ -1,57 +0,0 @@ -#ifndef WEBTRANSLATOR_H -#define WEBTRANSLATOR_H - -#include -#include -#include - -#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 &errors); - - private: - void translateQueued (); - void finishTranslation (bool markAsTranslated = true); - bool tryNextTranslator (bool firstTime = false); - - private: - WebTranslatorProxy *proxy_; - QWebView *view_; - TranslatorHelper *translatorHelper_; - QVector queue_; - bool isReady_; - bool ignoreSslErrors_; - bool forceRotateTranslators_; - QTimer translationTimeout_; -}; - -#endif // WEBTRANSLATOR_H diff --git a/src/webtranslatorproxy.cpp b/src/webtranslatorproxy.cpp deleted file mode 100644 index f2ea9e7..0000000 --- a/src/webtranslatorproxy.cpp +++ /dev/null @@ -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_; -} - diff --git a/src/webtranslatorproxy.h b/src/webtranslatorproxy.h deleted file mode 100644 index 20ee7f8..0000000 --- a/src/webtranslatorproxy.h +++ /dev/null @@ -1,37 +0,0 @@ -#ifndef WEBTRANSLATORPROXY_H -#define WEBTRANSLATORPROXY_H - -#include - -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 diff --git a/translators/google.js b/translators/google.js index 8b187f9..1749acd 100644 --- a/translators/google.js +++ b/translators/google.js @@ -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); +} + diff --git a/translators/yandex.js b/translators/yandex.js index 5eb745c..3a3f915 100644 --- a/translators/yandex.js +++ b/translators/yandex.js @@ -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); }