Refactor. WIP
							
								
								
									
										16
									
								
								.clang-format
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,16 @@ | ||||
| --- | ||||
| BasedOnStyle: Google | ||||
| AccessModifierOffset: '-2' | ||||
| AllowShortCaseLabelsOnASingleLine: 'true' | ||||
| AllowShortFunctionsOnASingleLine: InlineOnly | ||||
| AllowShortIfStatementsOnASingleLine: 'false' | ||||
| BreakBeforeBraces: Linux | ||||
| BreakConstructorInitializers: BeforeComma | ||||
| ConstructorInitializerAllOnOneLineOrOnePerLine: 'false' | ||||
| ConstructorInitializerIndentWidth: '2' | ||||
| Standard: Cpp11 | ||||
| TabWidth: '2' | ||||
| UseTab: Never | ||||
| IncludeBlocks: Preserve | ||||
| 
 | ||||
| ... | ||||
							
								
								
									
										20
									
								
								.travis.yml
									
									
									
									
									
								
							
							
						
						| @ -1,20 +0,0 @@ | ||||
| language: cpp | ||||
| compiler: | ||||
|   - gcc | ||||
|   - clang | ||||
| sudo: required | ||||
| dist: trusty | ||||
| install: ./scripts/install_deps.sh | ||||
| script: cd scripts && ./make_all.sh | ||||
| branches: | ||||
|   only: | ||||
|     - master | ||||
|     - develop | ||||
|     - /release.*/ | ||||
|     - /.*travis/ | ||||
| os: | ||||
|   - linux | ||||
| notifications: | ||||
|   email: | ||||
|     on_success: change | ||||
|     on_failure: change | ||||
							
								
								
									
										204
									
								
								3rd-party/qtsingleapplication/qtlocalpeer.cpp
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -1,204 +0,0 @@ | ||||
| /****************************************************************************
 | ||||
| ** | ||||
| ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). | ||||
| ** Contact: http://www.qt-project.org/legal
 | ||||
| ** | ||||
| ** This file is part of the Qt Solutions component. | ||||
| ** | ||||
| ** $QT_BEGIN_LICENSE:BSD$ | ||||
| ** You may use this file under the terms of the BSD license as follows: | ||||
| ** | ||||
| ** "Redistribution and use in source and binary forms, with or without | ||||
| ** modification, are permitted provided that the following conditions are | ||||
| ** met: | ||||
| **   * Redistributions of source code must retain the above copyright | ||||
| **     notice, this list of conditions and the following disclaimer. | ||||
| **   * Redistributions in binary form must reproduce the above copyright | ||||
| **     notice, this list of conditions and the following disclaimer in | ||||
| **     the documentation and/or other materials provided with the | ||||
| **     distribution. | ||||
| **   * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names | ||||
| **     of its contributors may be used to endorse or promote products derived | ||||
| **     from this software without specific prior written permission. | ||||
| ** | ||||
| ** | ||||
| ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||||
| ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||||
| ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | ||||
| ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||||
| ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||||
| ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | ||||
| ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||||
| ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||||
| ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||||
| ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||
| ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." | ||||
| ** | ||||
| ** $QT_END_LICENSE$ | ||||
| ** | ||||
| ****************************************************************************/ | ||||
| 
 | ||||
| 
 | ||||
| #include "qtlocalpeer.h" | ||||
| #include <QCoreApplication> | ||||
| #include <QDataStream> | ||||
| #include <QTime> | ||||
| 
 | ||||
| #if defined(Q_OS_WIN) | ||||
| #include <QLibrary> | ||||
| #include <qt_windows.h> | ||||
| typedef BOOL(WINAPI*PProcessIdToSessionId)(DWORD,DWORD*); | ||||
| static PProcessIdToSessionId pProcessIdToSessionId = 0; | ||||
| #endif | ||||
| #if defined(Q_OS_UNIX) | ||||
| #include <sys/types.h> | ||||
| #include <time.h> | ||||
| #include <unistd.h> | ||||
| #endif | ||||
| 
 | ||||
| namespace QtLP_Private { | ||||
| #include "qtlockedfile.cpp" | ||||
| #if defined(Q_OS_WIN) | ||||
| #include "qtlockedfile_win.cpp" | ||||
| #else | ||||
| #include "qtlockedfile_unix.cpp" | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| const char* QtLocalPeer::ack = "ack"; | ||||
| 
 | ||||
| QtLocalPeer::QtLocalPeer(QObject* parent, const QString &appId) | ||||
|     : QObject(parent), id(appId) | ||||
| { | ||||
|     QString prefix = id; | ||||
|     if (id.isEmpty()) { | ||||
|         id = QCoreApplication::applicationFilePath(); | ||||
| #if defined(Q_OS_WIN) | ||||
|         id = id.toLower(); | ||||
| #endif | ||||
|         prefix = id.section(QLatin1Char('/'), -1); | ||||
|     } | ||||
|     prefix.remove(QRegExp("[^a-zA-Z]")); | ||||
|     prefix.truncate(6); | ||||
| 
 | ||||
|     QByteArray idc = id.toUtf8(); | ||||
|     quint16 idNum = qChecksum(idc.constData(), idc.size()); | ||||
|     socketName = QLatin1String("qtsingleapp-") + prefix | ||||
|                  + QLatin1Char('-') + QString::number(idNum, 16); | ||||
| 
 | ||||
| #if defined(Q_OS_WIN) | ||||
|     if (!pProcessIdToSessionId) { | ||||
|         QLibrary lib("kernel32"); | ||||
|         pProcessIdToSessionId = (PProcessIdToSessionId)lib.resolve("ProcessIdToSessionId"); | ||||
|     } | ||||
|     if (pProcessIdToSessionId) { | ||||
|         DWORD sessionId = 0; | ||||
|         pProcessIdToSessionId(GetCurrentProcessId(), &sessionId); | ||||
|         socketName += QLatin1Char('-') + QString::number(sessionId, 16); | ||||
|     } | ||||
| #else | ||||
|     socketName += QLatin1Char('-') + QString::number(::getuid(), 16); | ||||
| #endif | ||||
| 
 | ||||
|     server = new QLocalServer(this); | ||||
|     QString lockName = QDir(QDir::tempPath()).absolutePath() | ||||
|                        + QLatin1Char('/') + socketName | ||||
|                        + QLatin1String("-lockfile"); | ||||
|     lockFile.setFileName(lockName); | ||||
|     lockFile.open(QIODevice::ReadWrite); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| bool QtLocalPeer::isClient() | ||||
| { | ||||
|     if (lockFile.isLocked()) | ||||
|         return false; | ||||
| 
 | ||||
|     if (!lockFile.lock(QtLP_Private::QtLockedFile::WriteLock, false)) | ||||
|         return true; | ||||
| 
 | ||||
|     bool res = server->listen(socketName); | ||||
| #if defined(Q_OS_UNIX) && (QT_VERSION >= QT_VERSION_CHECK(4,5,0)) | ||||
|     // ### Workaround
 | ||||
|     if (!res && server->serverError() == QAbstractSocket::AddressInUseError) { | ||||
|         QFile::remove(QDir::cleanPath(QDir::tempPath())+QLatin1Char('/')+socketName); | ||||
|         res = server->listen(socketName); | ||||
|     } | ||||
| #endif | ||||
|     if (!res) | ||||
|         qWarning("QtSingleCoreApplication: listen on local socket failed, %s", qPrintable(server->errorString())); | ||||
|     QObject::connect(server, SIGNAL(newConnection()), SLOT(receiveConnection())); | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| bool QtLocalPeer::sendMessage(const QString &message, int timeout) | ||||
| { | ||||
|     if (!isClient()) | ||||
|         return false; | ||||
| 
 | ||||
|     QLocalSocket socket; | ||||
|     bool connOk = false; | ||||
|     for(int i = 0; i < 2; i++) { | ||||
|         // Try twice, in case the other instance is just starting up
 | ||||
|         socket.connectToServer(socketName); | ||||
|         connOk = socket.waitForConnected(timeout/2); | ||||
|         if (connOk || i) | ||||
|             break; | ||||
|         int ms = 250; | ||||
| #if defined(Q_OS_WIN) | ||||
|         Sleep(DWORD(ms)); | ||||
| #else | ||||
|         struct timespec ts = { ms / 1000, (ms % 1000) * 1000 * 1000 }; | ||||
|         nanosleep(&ts, NULL); | ||||
| #endif | ||||
|     } | ||||
|     if (!connOk) | ||||
|         return false; | ||||
| 
 | ||||
|     QByteArray uMsg(message.toUtf8()); | ||||
|     QDataStream ds(&socket); | ||||
|     ds.writeBytes(uMsg.constData(), uMsg.size()); | ||||
|     bool res = socket.waitForBytesWritten(timeout); | ||||
|     if (res) { | ||||
|         res &= socket.waitForReadyRead(timeout);   // wait for ack
 | ||||
|         if (res) | ||||
|             res &= (socket.read(qstrlen(ack)) == ack); | ||||
|     } | ||||
|     return res; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void QtLocalPeer::receiveConnection() | ||||
| { | ||||
|     QLocalSocket* socket = server->nextPendingConnection(); | ||||
|     if (!socket) | ||||
|         return; | ||||
| 
 | ||||
|     while (socket->bytesAvailable() < (int)sizeof(quint32)) | ||||
|         socket->waitForReadyRead(); | ||||
|     QDataStream ds(socket); | ||||
|     QByteArray uMsg; | ||||
|     quint32 remaining; | ||||
|     ds >> remaining; | ||||
|     uMsg.resize(remaining); | ||||
|     int got = 0; | ||||
|     char* uMsgBuf = uMsg.data(); | ||||
|     do { | ||||
|         got = ds.readRawData(uMsgBuf, remaining); | ||||
|         remaining -= got; | ||||
|         uMsgBuf += got; | ||||
|     } while (remaining && got >= 0 && socket->waitForReadyRead(2000)); | ||||
|     if (got < 0) { | ||||
|         qWarning("QtLocalPeer: Message reception failed %s", socket->errorString().toLatin1().constData()); | ||||
|         delete socket; | ||||
|         return; | ||||
|     } | ||||
|     QString message(QString::fromUtf8(uMsg)); | ||||
|     socket->write(ack, qstrlen(ack)); | ||||
|     socket->waitForBytesWritten(1000); | ||||
|     socket->waitForDisconnected(1000); // make sure client reads ack
 | ||||
|     delete socket; | ||||
|     emit messageReceived(message); //### (might take a long time to return)
 | ||||
| } | ||||
							
								
								
									
										77
									
								
								3rd-party/qtsingleapplication/qtlocalpeer.h
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -1,77 +0,0 @@ | ||||
| /****************************************************************************
 | ||||
| ** | ||||
| ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). | ||||
| ** Contact: http://www.qt-project.org/legal
 | ||||
| ** | ||||
| ** This file is part of the Qt Solutions component. | ||||
| ** | ||||
| ** $QT_BEGIN_LICENSE:BSD$ | ||||
| ** You may use this file under the terms of the BSD license as follows: | ||||
| ** | ||||
| ** "Redistribution and use in source and binary forms, with or without | ||||
| ** modification, are permitted provided that the following conditions are | ||||
| ** met: | ||||
| **   * Redistributions of source code must retain the above copyright | ||||
| **     notice, this list of conditions and the following disclaimer. | ||||
| **   * Redistributions in binary form must reproduce the above copyright | ||||
| **     notice, this list of conditions and the following disclaimer in | ||||
| **     the documentation and/or other materials provided with the | ||||
| **     distribution. | ||||
| **   * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names | ||||
| **     of its contributors may be used to endorse or promote products derived | ||||
| **     from this software without specific prior written permission. | ||||
| ** | ||||
| ** | ||||
| ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||||
| ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||||
| ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | ||||
| ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||||
| ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||||
| ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | ||||
| ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||||
| ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||||
| ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||||
| ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||
| ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." | ||||
| ** | ||||
| ** $QT_END_LICENSE$ | ||||
| ** | ||||
| ****************************************************************************/ | ||||
| 
 | ||||
| #ifndef QTLOCALPEER_H | ||||
| #define QTLOCALPEER_H | ||||
| 
 | ||||
| #include <QLocalServer> | ||||
| #include <QLocalSocket> | ||||
| #include <QDir> | ||||
| 
 | ||||
| #include "qtlockedfile.h" | ||||
| 
 | ||||
| class QtLocalPeer : public QObject | ||||
| { | ||||
|     Q_OBJECT | ||||
| 
 | ||||
| public: | ||||
|     QtLocalPeer(QObject *parent = 0, const QString &appId = QString()); | ||||
|     bool isClient(); | ||||
|     bool sendMessage(const QString &message, int timeout); | ||||
|     QString applicationId() const | ||||
|         { return id; } | ||||
| 
 | ||||
| Q_SIGNALS: | ||||
|     void messageReceived(const QString &message); | ||||
| 
 | ||||
| protected Q_SLOTS: | ||||
|     void receiveConnection(); | ||||
| 
 | ||||
| protected: | ||||
|     QString id; | ||||
|     QString socketName; | ||||
|     QLocalServer* server; | ||||
|     QtLP_Private::QtLockedFile lockFile; | ||||
| 
 | ||||
| private: | ||||
|     static const char* ack; | ||||
| }; | ||||
| 
 | ||||
| #endif // QTLOCALPEER_H
 | ||||
							
								
								
									
										193
									
								
								3rd-party/qtsingleapplication/qtlockedfile.cpp
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -1,193 +0,0 @@ | ||||
| /****************************************************************************
 | ||||
| ** | ||||
| ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). | ||||
| ** Contact: http://www.qt-project.org/legal
 | ||||
| ** | ||||
| ** This file is part of the Qt Solutions component. | ||||
| ** | ||||
| ** $QT_BEGIN_LICENSE:BSD$ | ||||
| ** You may use this file under the terms of the BSD license as follows: | ||||
| ** | ||||
| ** "Redistribution and use in source and binary forms, with or without | ||||
| ** modification, are permitted provided that the following conditions are | ||||
| ** met: | ||||
| **   * Redistributions of source code must retain the above copyright | ||||
| **     notice, this list of conditions and the following disclaimer. | ||||
| **   * Redistributions in binary form must reproduce the above copyright | ||||
| **     notice, this list of conditions and the following disclaimer in | ||||
| **     the documentation and/or other materials provided with the | ||||
| **     distribution. | ||||
| **   * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names | ||||
| **     of its contributors may be used to endorse or promote products derived | ||||
| **     from this software without specific prior written permission. | ||||
| ** | ||||
| ** | ||||
| ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||||
| ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||||
| ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | ||||
| ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||||
| ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||||
| ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | ||||
| ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||||
| ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||||
| ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||||
| ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||
| ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." | ||||
| ** | ||||
| ** $QT_END_LICENSE$ | ||||
| ** | ||||
| ****************************************************************************/ | ||||
| 
 | ||||
| #include "qtlockedfile.h" | ||||
| 
 | ||||
| /*!
 | ||||
|     \class QtLockedFile | ||||
| 
 | ||||
|     \brief The QtLockedFile class extends QFile with advisory locking | ||||
|     functions. | ||||
| 
 | ||||
|     A file may be locked in read or write mode. Multiple instances of | ||||
|     \e QtLockedFile, created in multiple processes running on the same | ||||
|     machine, may have a file locked in read mode. Exactly one instance | ||||
|     may have it locked in write mode. A read and a write lock cannot | ||||
|     exist simultaneously on the same file. | ||||
| 
 | ||||
|     The file locks are advisory. This means that nothing prevents | ||||
|     another process from manipulating a locked file using QFile or | ||||
|     file system functions offered by the OS. Serialization is only | ||||
|     guaranteed if all processes that access the file use | ||||
|     QLockedFile. Also, while holding a lock on a file, a process | ||||
|     must not open the same file again (through any API), or locks | ||||
|     can be unexpectedly lost. | ||||
| 
 | ||||
|     The lock provided by an instance of \e QtLockedFile is released | ||||
|     whenever the program terminates. This is true even when the | ||||
|     program crashes and no destructors are called. | ||||
| */ | ||||
| 
 | ||||
| /*! \enum QtLockedFile::LockMode
 | ||||
| 
 | ||||
|     This enum describes the available lock modes. | ||||
| 
 | ||||
|     \value ReadLock A read lock. | ||||
|     \value WriteLock A write lock. | ||||
|     \value NoLock Neither a read lock nor a write lock. | ||||
| */ | ||||
| 
 | ||||
| /*!
 | ||||
|     Constructs an unlocked \e QtLockedFile object. This constructor | ||||
|     behaves in the same way as \e QFile::QFile(). | ||||
| 
 | ||||
|     \sa QFile::QFile() | ||||
| */ | ||||
| QtLockedFile::QtLockedFile() | ||||
|     : QFile() | ||||
| { | ||||
| #ifdef Q_OS_WIN | ||||
|     wmutex = 0; | ||||
|     rmutex = 0; | ||||
| #endif | ||||
|     m_lock_mode = NoLock; | ||||
| } | ||||
| 
 | ||||
| /*!
 | ||||
|     Constructs an unlocked QtLockedFile object with file \a name. This | ||||
|     constructor behaves in the same way as \e QFile::QFile(const | ||||
|     QString&). | ||||
| 
 | ||||
|     \sa QFile::QFile() | ||||
| */ | ||||
| QtLockedFile::QtLockedFile(const QString &name) | ||||
|     : QFile(name) | ||||
| { | ||||
| #ifdef Q_OS_WIN | ||||
|     wmutex = 0; | ||||
|     rmutex = 0; | ||||
| #endif | ||||
|     m_lock_mode = NoLock; | ||||
| } | ||||
| 
 | ||||
| /*!
 | ||||
|   Opens the file in OpenMode \a mode. | ||||
| 
 | ||||
|   This is identical to QFile::open(), with the one exception that the | ||||
|   Truncate mode flag is disallowed. Truncation would conflict with the | ||||
|   advisory file locking, since the file would be modified before the | ||||
|   write lock is obtained. If truncation is required, use resize(0) | ||||
|   after obtaining the write lock. | ||||
| 
 | ||||
|   Returns true if successful; otherwise false. | ||||
| 
 | ||||
|   \sa QFile::open(), QFile::resize() | ||||
| */ | ||||
| bool QtLockedFile::open(OpenMode mode) | ||||
| { | ||||
|     if (mode & QIODevice::Truncate) { | ||||
|         qWarning("QtLockedFile::open(): Truncate mode not allowed."); | ||||
|         return false; | ||||
|     } | ||||
|     return QFile::open(mode); | ||||
| } | ||||
| 
 | ||||
| /*!
 | ||||
|     Returns \e true if this object has a in read or write lock; | ||||
|     otherwise returns \e false. | ||||
| 
 | ||||
|     \sa lockMode() | ||||
| */ | ||||
| bool QtLockedFile::isLocked() const | ||||
| { | ||||
|     return m_lock_mode != NoLock; | ||||
| } | ||||
| 
 | ||||
| /*!
 | ||||
|     Returns the type of lock currently held by this object, or \e | ||||
|     QtLockedFile::NoLock. | ||||
| 
 | ||||
|     \sa isLocked() | ||||
| */ | ||||
| QtLockedFile::LockMode QtLockedFile::lockMode() const | ||||
| { | ||||
|     return m_lock_mode; | ||||
| } | ||||
| 
 | ||||
| /*!
 | ||||
|     \fn bool QtLockedFile::lock(LockMode mode, bool block = true) | ||||
| 
 | ||||
|     Obtains a lock of type \a mode. The file must be opened before it | ||||
|     can be locked. | ||||
| 
 | ||||
|     If \a block is true, this function will block until the lock is | ||||
|     aquired. If \a block is false, this function returns \e false | ||||
|     immediately if the lock cannot be aquired. | ||||
| 
 | ||||
|     If this object already has a lock of type \a mode, this function | ||||
|     returns \e true immediately. If this object has a lock of a | ||||
|     different type than \a mode, the lock is first released and then a | ||||
|     new lock is obtained. | ||||
| 
 | ||||
|     This function returns \e true if, after it executes, the file is | ||||
|     locked by this object, and \e false otherwise. | ||||
| 
 | ||||
|     \sa unlock(), isLocked(), lockMode() | ||||
| */ | ||||
| 
 | ||||
| /*!
 | ||||
|     \fn bool QtLockedFile::unlock() | ||||
| 
 | ||||
|     Releases a lock. | ||||
| 
 | ||||
|     If the object has no lock, this function returns immediately. | ||||
| 
 | ||||
|     This function returns \e true if, after it executes, the file is | ||||
|     not locked by this object, and \e false otherwise. | ||||
| 
 | ||||
|     \sa lock(), isLocked(), lockMode() | ||||
| */ | ||||
| 
 | ||||
| /*!
 | ||||
|     \fn QtLockedFile::~QtLockedFile() | ||||
| 
 | ||||
|     Destroys the \e QtLockedFile object. If any locks were held, they | ||||
|     are released. | ||||
| */ | ||||
							
								
								
									
										97
									
								
								3rd-party/qtsingleapplication/qtlockedfile.h
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -1,97 +0,0 @@ | ||||
| /****************************************************************************
 | ||||
| ** | ||||
| ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). | ||||
| ** Contact: http://www.qt-project.org/legal
 | ||||
| ** | ||||
| ** This file is part of the Qt Solutions component. | ||||
| ** | ||||
| ** $QT_BEGIN_LICENSE:BSD$ | ||||
| ** You may use this file under the terms of the BSD license as follows: | ||||
| ** | ||||
| ** "Redistribution and use in source and binary forms, with or without | ||||
| ** modification, are permitted provided that the following conditions are | ||||
| ** met: | ||||
| **   * Redistributions of source code must retain the above copyright | ||||
| **     notice, this list of conditions and the following disclaimer. | ||||
| **   * Redistributions in binary form must reproduce the above copyright | ||||
| **     notice, this list of conditions and the following disclaimer in | ||||
| **     the documentation and/or other materials provided with the | ||||
| **     distribution. | ||||
| **   * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names | ||||
| **     of its contributors may be used to endorse or promote products derived | ||||
| **     from this software without specific prior written permission. | ||||
| ** | ||||
| ** | ||||
| ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||||
| ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||||
| ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | ||||
| ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||||
| ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||||
| ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | ||||
| ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||||
| ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||||
| ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||||
| ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||
| ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." | ||||
| ** | ||||
| ** $QT_END_LICENSE$ | ||||
| ** | ||||
| ****************************************************************************/ | ||||
| 
 | ||||
| #ifndef QTLOCKEDFILE_H | ||||
| #define QTLOCKEDFILE_H | ||||
| 
 | ||||
| #include <QFile> | ||||
| #ifdef Q_OS_WIN | ||||
| #include <QVector> | ||||
| #endif | ||||
| 
 | ||||
| #if defined(Q_OS_WIN) | ||||
| #  if !defined(QT_QTLOCKEDFILE_EXPORT) && !defined(QT_QTLOCKEDFILE_IMPORT) | ||||
| #    define QT_QTLOCKEDFILE_EXPORT | ||||
| #  elif defined(QT_QTLOCKEDFILE_IMPORT) | ||||
| #    if defined(QT_QTLOCKEDFILE_EXPORT) | ||||
| #      undef QT_QTLOCKEDFILE_EXPORT | ||||
| #    endif | ||||
| #    define QT_QTLOCKEDFILE_EXPORT __declspec(dllimport) | ||||
| #  elif defined(QT_QTLOCKEDFILE_EXPORT) | ||||
| #    undef QT_QTLOCKEDFILE_EXPORT | ||||
| #    define QT_QTLOCKEDFILE_EXPORT __declspec(dllexport) | ||||
| #  endif | ||||
| #else | ||||
| #  define QT_QTLOCKEDFILE_EXPORT | ||||
| #endif | ||||
| 
 | ||||
| namespace QtLP_Private { | ||||
| 
 | ||||
| class QT_QTLOCKEDFILE_EXPORT QtLockedFile : public QFile | ||||
| { | ||||
| public: | ||||
|     enum LockMode { NoLock = 0, ReadLock, WriteLock }; | ||||
| 
 | ||||
|     QtLockedFile(); | ||||
|     QtLockedFile(const QString &name); | ||||
|     ~QtLockedFile(); | ||||
| 
 | ||||
|     bool open(OpenMode mode); | ||||
| 
 | ||||
|     bool lock(LockMode mode, bool block = true); | ||||
|     bool unlock(); | ||||
|     bool isLocked() const; | ||||
|     LockMode lockMode() const; | ||||
| 
 | ||||
| private: | ||||
| #ifdef Q_OS_WIN | ||||
|     Qt::HANDLE wmutex; | ||||
|     Qt::HANDLE rmutex; | ||||
|     QVector<Qt::HANDLE> rmutexes; | ||||
|     QString mutexname; | ||||
| 
 | ||||
|     Qt::HANDLE getMutexHandle(int idx, bool doCreate); | ||||
|     bool waitMutex(Qt::HANDLE mutex, bool doBlock); | ||||
| 
 | ||||
| #endif | ||||
|     LockMode m_lock_mode; | ||||
| }; | ||||
| } | ||||
| #endif | ||||
							
								
								
									
										115
									
								
								3rd-party/qtsingleapplication/qtlockedfile_unix.cpp
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -1,115 +0,0 @@ | ||||
| /****************************************************************************
 | ||||
| ** | ||||
| ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). | ||||
| ** Contact: http://www.qt-project.org/legal
 | ||||
| ** | ||||
| ** This file is part of the Qt Solutions component. | ||||
| ** | ||||
| ** $QT_BEGIN_LICENSE:BSD$ | ||||
| ** You may use this file under the terms of the BSD license as follows: | ||||
| ** | ||||
| ** "Redistribution and use in source and binary forms, with or without | ||||
| ** modification, are permitted provided that the following conditions are | ||||
| ** met: | ||||
| **   * Redistributions of source code must retain the above copyright | ||||
| **     notice, this list of conditions and the following disclaimer. | ||||
| **   * Redistributions in binary form must reproduce the above copyright | ||||
| **     notice, this list of conditions and the following disclaimer in | ||||
| **     the documentation and/or other materials provided with the | ||||
| **     distribution. | ||||
| **   * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names | ||||
| **     of its contributors may be used to endorse or promote products derived | ||||
| **     from this software without specific prior written permission. | ||||
| ** | ||||
| ** | ||||
| ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||||
| ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||||
| ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | ||||
| ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||||
| ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||||
| ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | ||||
| ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||||
| ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||||
| ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||||
| ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||
| ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." | ||||
| ** | ||||
| ** $QT_END_LICENSE$ | ||||
| ** | ||||
| ****************************************************************************/ | ||||
| 
 | ||||
| #include <string.h> | ||||
| #include <errno.h> | ||||
| #include <unistd.h> | ||||
| #include <fcntl.h> | ||||
| 
 | ||||
| #include "qtlockedfile.h" | ||||
| 
 | ||||
| bool QtLockedFile::lock(LockMode mode, bool block) | ||||
| { | ||||
|     if (!isOpen()) { | ||||
|         qWarning("QtLockedFile::lock(): file is not opened"); | ||||
|         return false; | ||||
|     } | ||||
|   | ||||
|     if (mode == NoLock) | ||||
|         return unlock(); | ||||
|             | ||||
|     if (mode == m_lock_mode) | ||||
|         return true; | ||||
| 
 | ||||
|     if (m_lock_mode != NoLock) | ||||
|         unlock(); | ||||
| 
 | ||||
|     struct flock fl; | ||||
|     fl.l_whence = SEEK_SET; | ||||
|     fl.l_start = 0; | ||||
|     fl.l_len = 0; | ||||
|     fl.l_type = (mode == ReadLock) ? F_RDLCK : F_WRLCK; | ||||
|     int cmd = block ? F_SETLKW : F_SETLK; | ||||
|     int ret = fcntl(handle(), cmd, &fl); | ||||
|      | ||||
|     if (ret == -1) { | ||||
|         if (errno != EINTR && errno != EAGAIN) | ||||
|             qWarning("QtLockedFile::lock(): fcntl: %s", strerror(errno)); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|      | ||||
|     m_lock_mode = mode; | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| bool QtLockedFile::unlock() | ||||
| { | ||||
|     if (!isOpen()) { | ||||
|         qWarning("QtLockedFile::unlock(): file is not opened"); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     if (!isLocked()) | ||||
|         return true; | ||||
| 
 | ||||
|     struct flock fl; | ||||
|     fl.l_whence = SEEK_SET; | ||||
|     fl.l_start = 0; | ||||
|     fl.l_len = 0; | ||||
|     fl.l_type = F_UNLCK; | ||||
|     int ret = fcntl(handle(), F_SETLKW, &fl); | ||||
|      | ||||
|     if (ret == -1) { | ||||
|         qWarning("QtLockedFile::lock(): fcntl: %s", strerror(errno)); | ||||
|         return false; | ||||
|     } | ||||
|      | ||||
|     m_lock_mode = NoLock; | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| QtLockedFile::~QtLockedFile() | ||||
| { | ||||
|     if (isOpen()) | ||||
|         unlock(); | ||||
| } | ||||
| 
 | ||||
							
								
								
									
										211
									
								
								3rd-party/qtsingleapplication/qtlockedfile_win.cpp
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -1,211 +0,0 @@ | ||||
| /****************************************************************************
 | ||||
| ** | ||||
| ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). | ||||
| ** Contact: http://www.qt-project.org/legal
 | ||||
| ** | ||||
| ** This file is part of the Qt Solutions component. | ||||
| ** | ||||
| ** $QT_BEGIN_LICENSE:BSD$ | ||||
| ** You may use this file under the terms of the BSD license as follows: | ||||
| ** | ||||
| ** "Redistribution and use in source and binary forms, with or without | ||||
| ** modification, are permitted provided that the following conditions are | ||||
| ** met: | ||||
| **   * Redistributions of source code must retain the above copyright | ||||
| **     notice, this list of conditions and the following disclaimer. | ||||
| **   * Redistributions in binary form must reproduce the above copyright | ||||
| **     notice, this list of conditions and the following disclaimer in | ||||
| **     the documentation and/or other materials provided with the | ||||
| **     distribution. | ||||
| **   * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names | ||||
| **     of its contributors may be used to endorse or promote products derived | ||||
| **     from this software without specific prior written permission. | ||||
| ** | ||||
| ** | ||||
| ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||||
| ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||||
| ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | ||||
| ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||||
| ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||||
| ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | ||||
| ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||||
| ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||||
| ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||||
| ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||
| ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." | ||||
| ** | ||||
| ** $QT_END_LICENSE$ | ||||
| ** | ||||
| ****************************************************************************/ | ||||
| 
 | ||||
| #include "qtlockedfile.h" | ||||
| #include <qt_windows.h> | ||||
| #include <QFileInfo> | ||||
| 
 | ||||
| #define MUTEX_PREFIX "QtLockedFile mutex " | ||||
| // Maximum number of concurrent read locks. Must not be greater than MAXIMUM_WAIT_OBJECTS
 | ||||
| #define MAX_READERS MAXIMUM_WAIT_OBJECTS | ||||
| 
 | ||||
| #if QT_VERSION >= 0x050000 | ||||
| #define QT_WA(unicode, ansi) unicode | ||||
| #endif | ||||
| 
 | ||||
| Qt::HANDLE QtLockedFile::getMutexHandle(int idx, bool doCreate) | ||||
| { | ||||
|     if (mutexname.isEmpty()) { | ||||
|         QFileInfo fi(*this); | ||||
|         mutexname = QString::fromLatin1(MUTEX_PREFIX) | ||||
|                     + fi.absoluteFilePath().toLower(); | ||||
|     } | ||||
|     QString mname(mutexname); | ||||
|     if (idx >= 0) | ||||
|         mname += QString::number(idx); | ||||
| 
 | ||||
|     Qt::HANDLE mutex; | ||||
|     if (doCreate) { | ||||
|         QT_WA( { mutex = CreateMutexW(NULL, FALSE, (TCHAR*)mname.utf16()); }, | ||||
|                { mutex = CreateMutexA(NULL, FALSE, mname.toLocal8Bit().constData()); } ); | ||||
|         if (!mutex) { | ||||
|             qErrnoWarning("QtLockedFile::lock(): CreateMutex failed"); | ||||
|             return 0; | ||||
|         } | ||||
|     } | ||||
|     else { | ||||
|         QT_WA( { mutex = OpenMutexW(SYNCHRONIZE | MUTEX_MODIFY_STATE, FALSE, (TCHAR*)mname.utf16()); }, | ||||
|                { mutex = OpenMutexA(SYNCHRONIZE | MUTEX_MODIFY_STATE, FALSE, mname.toLocal8Bit().constData()); } ); | ||||
|         if (!mutex) { | ||||
|             if (GetLastError() != ERROR_FILE_NOT_FOUND) | ||||
|                 qErrnoWarning("QtLockedFile::lock(): OpenMutex failed"); | ||||
|             return 0; | ||||
|         } | ||||
|     } | ||||
|     return mutex; | ||||
| } | ||||
| 
 | ||||
| bool QtLockedFile::waitMutex(Qt::HANDLE mutex, bool doBlock) | ||||
| { | ||||
|     Q_ASSERT(mutex); | ||||
|     DWORD res = WaitForSingleObject(mutex, doBlock ? INFINITE : 0); | ||||
|     switch (res) { | ||||
|     case WAIT_OBJECT_0: | ||||
|     case WAIT_ABANDONED: | ||||
|         return true; | ||||
|         break; | ||||
|     case WAIT_TIMEOUT: | ||||
|         break; | ||||
|     default: | ||||
|         qErrnoWarning("QtLockedFile::lock(): WaitForSingleObject failed"); | ||||
|     } | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| bool QtLockedFile::lock(LockMode mode, bool block) | ||||
| { | ||||
|     if (!isOpen()) { | ||||
|         qWarning("QtLockedFile::lock(): file is not opened"); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     if (mode == NoLock) | ||||
|         return unlock(); | ||||
| 
 | ||||
|     if (mode == m_lock_mode) | ||||
|         return true; | ||||
| 
 | ||||
|     if (m_lock_mode != NoLock) | ||||
|         unlock(); | ||||
| 
 | ||||
|     if (!wmutex && !(wmutex = getMutexHandle(-1, true))) | ||||
|         return false; | ||||
| 
 | ||||
|     if (!waitMutex(wmutex, block)) | ||||
|         return false; | ||||
| 
 | ||||
|     if (mode == ReadLock) { | ||||
|         int idx = 0; | ||||
|         for (; idx < MAX_READERS; idx++) { | ||||
|             rmutex = getMutexHandle(idx, false); | ||||
|             if (!rmutex || waitMutex(rmutex, false)) | ||||
|                 break; | ||||
|             CloseHandle(rmutex); | ||||
|         } | ||||
|         bool ok = true; | ||||
|         if (idx >= MAX_READERS) { | ||||
|             qWarning("QtLockedFile::lock(): too many readers"); | ||||
|             rmutex = 0; | ||||
|             ok = false; | ||||
|         } | ||||
|         else if (!rmutex) { | ||||
|             rmutex = getMutexHandle(idx, true); | ||||
|             if (!rmutex || !waitMutex(rmutex, false)) | ||||
|                 ok = false; | ||||
|         } | ||||
|         if (!ok && rmutex) { | ||||
|             CloseHandle(rmutex); | ||||
|             rmutex = 0; | ||||
|         } | ||||
|         ReleaseMutex(wmutex); | ||||
|         if (!ok) | ||||
|             return false; | ||||
|     } | ||||
|     else { | ||||
|         Q_ASSERT(rmutexes.isEmpty()); | ||||
|         for (int i = 0; i < MAX_READERS; i++) { | ||||
|             Qt::HANDLE mutex = getMutexHandle(i, false); | ||||
|             if (mutex) | ||||
|                 rmutexes.append(mutex); | ||||
|         } | ||||
|         if (rmutexes.size()) { | ||||
|             DWORD res = WaitForMultipleObjects(rmutexes.size(), rmutexes.constData(), | ||||
|                                                TRUE, block ? INFINITE : 0); | ||||
|             if (res != WAIT_OBJECT_0 && res != WAIT_ABANDONED) { | ||||
|                 if (res != WAIT_TIMEOUT) | ||||
|                     qErrnoWarning("QtLockedFile::lock(): WaitForMultipleObjects failed"); | ||||
|                 m_lock_mode = WriteLock;  // trick unlock() to clean up - semiyucky
 | ||||
|                 unlock(); | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     m_lock_mode = mode; | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| bool QtLockedFile::unlock() | ||||
| { | ||||
|     if (!isOpen()) { | ||||
|         qWarning("QtLockedFile::unlock(): file is not opened"); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     if (!isLocked()) | ||||
|         return true; | ||||
| 
 | ||||
|     if (m_lock_mode == ReadLock) { | ||||
|         ReleaseMutex(rmutex); | ||||
|         CloseHandle(rmutex); | ||||
|         rmutex = 0; | ||||
|     } | ||||
|     else { | ||||
|         foreach(Qt::HANDLE mutex, rmutexes) { | ||||
|             ReleaseMutex(mutex); | ||||
|             CloseHandle(mutex); | ||||
|         } | ||||
|         rmutexes.clear(); | ||||
|         ReleaseMutex(wmutex); | ||||
|     } | ||||
| 
 | ||||
|     m_lock_mode = QtLockedFile::NoLock; | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| QtLockedFile::~QtLockedFile() | ||||
| { | ||||
|     if (isOpen()) | ||||
|         unlock(); | ||||
|     if (wmutex) | ||||
|         CloseHandle(wmutex); | ||||
| } | ||||
| @ -1,347 +0,0 @@ | ||||
| /****************************************************************************
 | ||||
| ** | ||||
| ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). | ||||
| ** Contact: http://www.qt-project.org/legal
 | ||||
| ** | ||||
| ** This file is part of the Qt Solutions component. | ||||
| ** | ||||
| ** $QT_BEGIN_LICENSE:BSD$ | ||||
| ** You may use this file under the terms of the BSD license as follows: | ||||
| ** | ||||
| ** "Redistribution and use in source and binary forms, with or without | ||||
| ** modification, are permitted provided that the following conditions are | ||||
| ** met: | ||||
| **   * Redistributions of source code must retain the above copyright | ||||
| **     notice, this list of conditions and the following disclaimer. | ||||
| **   * Redistributions in binary form must reproduce the above copyright | ||||
| **     notice, this list of conditions and the following disclaimer in | ||||
| **     the documentation and/or other materials provided with the | ||||
| **     distribution. | ||||
| **   * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names | ||||
| **     of its contributors may be used to endorse or promote products derived | ||||
| **     from this software without specific prior written permission. | ||||
| ** | ||||
| ** | ||||
| ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||||
| ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||||
| ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | ||||
| ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||||
| ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||||
| ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | ||||
| ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||||
| ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||||
| ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||||
| ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||
| ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." | ||||
| ** | ||||
| ** $QT_END_LICENSE$ | ||||
| ** | ||||
| ****************************************************************************/ | ||||
| 
 | ||||
| 
 | ||||
| #include "qtsingleapplication.h" | ||||
| #include "qtlocalpeer.h" | ||||
| #include <QWidget> | ||||
| 
 | ||||
| 
 | ||||
| /*!
 | ||||
|     \class QtSingleApplication qtsingleapplication.h | ||||
|     \brief The QtSingleApplication class provides an API to detect and | ||||
|     communicate with running instances of an application. | ||||
| 
 | ||||
|     This class allows you to create applications where only one | ||||
|     instance should be running at a time. I.e., if the user tries to | ||||
|     launch another instance, the already running instance will be | ||||
|     activated instead. Another usecase is a client-server system, | ||||
|     where the first started instance will assume the role of server, | ||||
|     and the later instances will act as clients of that server. | ||||
| 
 | ||||
|     By default, the full path of the executable file is used to | ||||
|     determine whether two processes are instances of the same | ||||
|     application. You can also provide an explicit identifier string | ||||
|     that will be compared instead. | ||||
| 
 | ||||
|     The application should create the QtSingleApplication object early | ||||
|     in the startup phase, and call isRunning() to find out if another | ||||
|     instance of this application is already running. If isRunning() | ||||
|     returns false, it means that no other instance is running, and | ||||
|     this instance has assumed the role as the running instance. In | ||||
|     this case, the application should continue with the initialization | ||||
|     of the application user interface before entering the event loop | ||||
|     with exec(), as normal. | ||||
| 
 | ||||
|     The messageReceived() signal will be emitted when the running | ||||
|     application receives messages from another instance of the same | ||||
|     application. When a message is received it might be helpful to the | ||||
|     user to raise the application so that it becomes visible. To | ||||
|     facilitate this, QtSingleApplication provides the | ||||
|     setActivationWindow() function and the activateWindow() slot. | ||||
| 
 | ||||
|     If isRunning() returns true, another instance is already | ||||
|     running. It may be alerted to the fact that another instance has | ||||
|     started by using the sendMessage() function. Also data such as | ||||
|     startup parameters (e.g. the name of the file the user wanted this | ||||
|     new instance to open) can be passed to the running instance with | ||||
|     this function. Then, the application should terminate (or enter | ||||
|     client mode). | ||||
| 
 | ||||
|     If isRunning() returns true, but sendMessage() fails, that is an | ||||
|     indication that the running instance is frozen. | ||||
| 
 | ||||
|     Here's an example that shows how to convert an existing | ||||
|     application to use QtSingleApplication. It is very simple and does | ||||
|     not make use of all QtSingleApplication's functionality (see the | ||||
|     examples for that). | ||||
| 
 | ||||
|     \code | ||||
|     // Original
 | ||||
|     int main(int argc, char **argv) | ||||
|     { | ||||
|         QApplication app(argc, argv); | ||||
| 
 | ||||
|         MyMainWidget mmw; | ||||
|         mmw.show(); | ||||
|         return app.exec(); | ||||
|     } | ||||
| 
 | ||||
|     // Single instance
 | ||||
|     int main(int argc, char **argv) | ||||
|     { | ||||
|         QtSingleApplication app(argc, argv); | ||||
| 
 | ||||
|         if (app.isRunning()) | ||||
|             return !app.sendMessage(someDataString); | ||||
| 
 | ||||
|         MyMainWidget mmw; | ||||
|         app.setActivationWindow(&mmw); | ||||
|         mmw.show(); | ||||
|         return app.exec(); | ||||
|     } | ||||
|     \endcode | ||||
| 
 | ||||
|     Once this QtSingleApplication instance is destroyed (normally when | ||||
|     the process exits or crashes), when the user next attempts to run the | ||||
|     application this instance will not, of course, be encountered. The | ||||
|     next instance to call isRunning() or sendMessage() will assume the | ||||
|     role as the new running instance. | ||||
| 
 | ||||
|     For console (non-GUI) applications, QtSingleCoreApplication may be | ||||
|     used instead of this class, to avoid the dependency on the QtGui | ||||
|     library. | ||||
| 
 | ||||
|     \sa QtSingleCoreApplication | ||||
| */ | ||||
| 
 | ||||
| 
 | ||||
| void QtSingleApplication::sysInit(const QString &appId) | ||||
| { | ||||
|     actWin = 0; | ||||
|     peer = new QtLocalPeer(this, appId); | ||||
|     connect(peer, SIGNAL(messageReceived(const QString&)), SIGNAL(messageReceived(const QString&))); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /*!
 | ||||
|     Creates a QtSingleApplication object. The application identifier | ||||
|     will be QCoreApplication::applicationFilePath(). \a argc, \a | ||||
|     argv, and \a GUIenabled are passed on to the QAppliation constructor. | ||||
| 
 | ||||
|     If you are creating a console application (i.e. setting \a | ||||
|     GUIenabled to false), you may consider using | ||||
|     QtSingleCoreApplication instead. | ||||
| */ | ||||
| 
 | ||||
| QtSingleApplication::QtSingleApplication(int &argc, char **argv, bool GUIenabled) | ||||
|     : QApplication(argc, argv, GUIenabled) | ||||
| { | ||||
|     sysInit(); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /*!
 | ||||
|     Creates a QtSingleApplication object with the application | ||||
|     identifier \a appId. \a argc and \a argv are passed on to the | ||||
|     QAppliation constructor. | ||||
| */ | ||||
| 
 | ||||
| QtSingleApplication::QtSingleApplication(const QString &appId, int &argc, char **argv) | ||||
|     : QApplication(argc, argv) | ||||
| { | ||||
|     sysInit(appId); | ||||
| } | ||||
| 
 | ||||
| #if QT_VERSION < 0x050000 | ||||
| 
 | ||||
| /*!
 | ||||
|     Creates a QtSingleApplication object. The application identifier | ||||
|     will be QCoreApplication::applicationFilePath(). \a argc, \a | ||||
|     argv, and \a type are passed on to the QAppliation constructor. | ||||
| */ | ||||
| QtSingleApplication::QtSingleApplication(int &argc, char **argv, Type type) | ||||
|     : QApplication(argc, argv, type) | ||||
| { | ||||
|     sysInit(); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| #  if defined(Q_WS_X11) | ||||
| /*!
 | ||||
|   Special constructor for X11, ref. the documentation of | ||||
|   QApplication's corresponding constructor. The application identifier | ||||
|   will be QCoreApplication::applicationFilePath(). \a dpy, \a visual, | ||||
|   and \a cmap are passed on to the QApplication constructor. | ||||
| */ | ||||
| QtSingleApplication::QtSingleApplication(Display* dpy, Qt::HANDLE visual, Qt::HANDLE cmap) | ||||
|     : QApplication(dpy, visual, cmap) | ||||
| { | ||||
|     sysInit(); | ||||
| } | ||||
| 
 | ||||
| /*!
 | ||||
|   Special constructor for X11, ref. the documentation of | ||||
|   QApplication's corresponding constructor. The application identifier | ||||
|   will be QCoreApplication::applicationFilePath(). \a dpy, \a argc, \a | ||||
|   argv, \a visual, and \a cmap are passed on to the QApplication | ||||
|   constructor. | ||||
| */ | ||||
| QtSingleApplication::QtSingleApplication(Display *dpy, int &argc, char **argv, Qt::HANDLE visual, Qt::HANDLE cmap) | ||||
|     : QApplication(dpy, argc, argv, visual, cmap) | ||||
| { | ||||
|     sysInit(); | ||||
| } | ||||
| 
 | ||||
| /*!
 | ||||
|   Special constructor for X11, ref. the documentation of | ||||
|   QApplication's corresponding constructor. The application identifier | ||||
|   will be \a appId. \a dpy, \a argc, \a | ||||
|   argv, \a visual, and \a cmap are passed on to the QApplication | ||||
|   constructor. | ||||
| */ | ||||
| QtSingleApplication::QtSingleApplication(Display* dpy, const QString &appId, int argc, char **argv, Qt::HANDLE visual, Qt::HANDLE cmap) | ||||
|     : QApplication(dpy, argc, argv, visual, cmap) | ||||
| { | ||||
|     sysInit(appId); | ||||
| } | ||||
| #  endif // Q_WS_X11
 | ||||
| #endif // QT_VERSION < 0x050000
 | ||||
| 
 | ||||
| 
 | ||||
| /*!
 | ||||
|     Returns true if another instance of this application is running; | ||||
|     otherwise false. | ||||
| 
 | ||||
|     This function does not find instances of this application that are | ||||
|     being run by a different user (on Windows: that are running in | ||||
|     another session). | ||||
| 
 | ||||
|     \sa sendMessage() | ||||
| */ | ||||
| 
 | ||||
| bool QtSingleApplication::isRunning() | ||||
| { | ||||
|     return peer->isClient(); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /*!
 | ||||
|     Tries to send the text \a message to the currently running | ||||
|     instance. The QtSingleApplication object in the running instance | ||||
|     will emit the messageReceived() signal when it receives the | ||||
|     message. | ||||
| 
 | ||||
|     This function returns true if the message has been sent to, and | ||||
|     processed by, the current instance. If there is no instance | ||||
|     currently running, or if the running instance fails to process the | ||||
|     message within \a timeout milliseconds, this function return false. | ||||
| 
 | ||||
|     \sa isRunning(), messageReceived() | ||||
| */ | ||||
| bool QtSingleApplication::sendMessage(const QString &message, int timeout) | ||||
| { | ||||
|     return peer->sendMessage(message, timeout); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /*!
 | ||||
|     Returns the application identifier. Two processes with the same | ||||
|     identifier will be regarded as instances of the same application. | ||||
| */ | ||||
| QString QtSingleApplication::id() const | ||||
| { | ||||
|     return peer->applicationId(); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /*!
 | ||||
|   Sets the activation window of this application to \a aw. The | ||||
|   activation window is the widget that will be activated by | ||||
|   activateWindow(). This is typically the application's main window. | ||||
| 
 | ||||
|   If \a activateOnMessage is true (the default), the window will be | ||||
|   activated automatically every time a message is received, just prior | ||||
|   to the messageReceived() signal being emitted. | ||||
| 
 | ||||
|   \sa activateWindow(), messageReceived() | ||||
| */ | ||||
| 
 | ||||
| void QtSingleApplication::setActivationWindow(QWidget* aw, bool activateOnMessage) | ||||
| { | ||||
|     actWin = aw; | ||||
|     if (activateOnMessage) | ||||
|         connect(peer, SIGNAL(messageReceived(const QString&)), this, SLOT(activateWindow())); | ||||
|     else | ||||
|         disconnect(peer, SIGNAL(messageReceived(const QString&)), this, SLOT(activateWindow())); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /*!
 | ||||
|     Returns the applications activation window if one has been set by | ||||
|     calling setActivationWindow(), otherwise returns 0. | ||||
| 
 | ||||
|     \sa setActivationWindow() | ||||
| */ | ||||
| QWidget* QtSingleApplication::activationWindow() const | ||||
| { | ||||
|     return actWin; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /*!
 | ||||
|   De-minimizes, raises, and activates this application's activation window. | ||||
|   This function does nothing if no activation window has been set. | ||||
| 
 | ||||
|   This is a convenience function to show the user that this | ||||
|   application instance has been activated when he has tried to start | ||||
|   another instance. | ||||
| 
 | ||||
|   This function should typically be called in response to the | ||||
|   messageReceived() signal. By default, that will happen | ||||
|   automatically, if an activation window has been set. | ||||
| 
 | ||||
|   \sa setActivationWindow(), messageReceived(), initialize() | ||||
| */ | ||||
| void QtSingleApplication::activateWindow() | ||||
| { | ||||
|     if (actWin) { | ||||
|         actWin->setWindowState(actWin->windowState() & ~Qt::WindowMinimized); | ||||
|         actWin->raise(); | ||||
|         actWin->activateWindow(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /*!
 | ||||
|     \fn void QtSingleApplication::messageReceived(const QString& message) | ||||
| 
 | ||||
|     This signal is emitted when the current instance receives a \a | ||||
|     message from another instance of this application. | ||||
| 
 | ||||
|     \sa sendMessage(), setActivationWindow(), activateWindow() | ||||
| */ | ||||
| 
 | ||||
| 
 | ||||
| /*!
 | ||||
|     \fn void QtSingleApplication::initialize(bool dummy = true) | ||||
| 
 | ||||
|     \obsolete | ||||
| */ | ||||
							
								
								
									
										105
									
								
								3rd-party/qtsingleapplication/qtsingleapplication.h
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -1,105 +0,0 @@ | ||||
| /****************************************************************************
 | ||||
| ** | ||||
| ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). | ||||
| ** Contact: http://www.qt-project.org/legal
 | ||||
| ** | ||||
| ** This file is part of the Qt Solutions component. | ||||
| ** | ||||
| ** $QT_BEGIN_LICENSE:BSD$ | ||||
| ** You may use this file under the terms of the BSD license as follows: | ||||
| ** | ||||
| ** "Redistribution and use in source and binary forms, with or without | ||||
| ** modification, are permitted provided that the following conditions are | ||||
| ** met: | ||||
| **   * Redistributions of source code must retain the above copyright | ||||
| **     notice, this list of conditions and the following disclaimer. | ||||
| **   * Redistributions in binary form must reproduce the above copyright | ||||
| **     notice, this list of conditions and the following disclaimer in | ||||
| **     the documentation and/or other materials provided with the | ||||
| **     distribution. | ||||
| **   * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names | ||||
| **     of its contributors may be used to endorse or promote products derived | ||||
| **     from this software without specific prior written permission. | ||||
| ** | ||||
| ** | ||||
| ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||||
| ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||||
| ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | ||||
| ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||||
| ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||||
| ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | ||||
| ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||||
| ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||||
| ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||||
| ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||
| ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." | ||||
| ** | ||||
| ** $QT_END_LICENSE$ | ||||
| ** | ||||
| ****************************************************************************/ | ||||
| 
 | ||||
| #ifndef QTSINGLEAPPLICATION_H | ||||
| #define QTSINGLEAPPLICATION_H | ||||
| 
 | ||||
| #include <QApplication> | ||||
| 
 | ||||
| class QtLocalPeer; | ||||
| 
 | ||||
| #if defined(Q_OS_WIN) | ||||
| #  if !defined(QT_QTSINGLEAPPLICATION_EXPORT) && !defined(QT_QTSINGLEAPPLICATION_IMPORT) | ||||
| #    define QT_QTSINGLEAPPLICATION_EXPORT | ||||
| #  elif defined(QT_QTSINGLEAPPLICATION_IMPORT) | ||||
| #    if defined(QT_QTSINGLEAPPLICATION_EXPORT) | ||||
| #      undef QT_QTSINGLEAPPLICATION_EXPORT | ||||
| #    endif | ||||
| #    define QT_QTSINGLEAPPLICATION_EXPORT __declspec(dllimport) | ||||
| #  elif defined(QT_QTSINGLEAPPLICATION_EXPORT) | ||||
| #    undef QT_QTSINGLEAPPLICATION_EXPORT | ||||
| #    define QT_QTSINGLEAPPLICATION_EXPORT __declspec(dllexport) | ||||
| #  endif | ||||
| #else | ||||
| #  define QT_QTSINGLEAPPLICATION_EXPORT | ||||
| #endif | ||||
| 
 | ||||
| class QT_QTSINGLEAPPLICATION_EXPORT QtSingleApplication : public QApplication | ||||
| { | ||||
|     Q_OBJECT | ||||
| 
 | ||||
| public: | ||||
|     QtSingleApplication(int &argc, char **argv, bool GUIenabled = true); | ||||
|     QtSingleApplication(const QString &id, int &argc, char **argv); | ||||
| #if QT_VERSION < 0x050000 | ||||
|     QtSingleApplication(int &argc, char **argv, Type type); | ||||
| #  if defined(Q_WS_X11) | ||||
|     QtSingleApplication(Display* dpy, Qt::HANDLE visual = 0, Qt::HANDLE colormap = 0); | ||||
|     QtSingleApplication(Display *dpy, int &argc, char **argv, Qt::HANDLE visual = 0, Qt::HANDLE cmap= 0); | ||||
|     QtSingleApplication(Display* dpy, const QString &appId, int argc, char **argv, Qt::HANDLE visual = 0, Qt::HANDLE colormap = 0); | ||||
| #  endif // Q_WS_X11
 | ||||
| #endif // QT_VERSION < 0x050000
 | ||||
| 
 | ||||
|     bool isRunning(); | ||||
|     QString id() const; | ||||
| 
 | ||||
|     void setActivationWindow(QWidget* aw, bool activateOnMessage = true); | ||||
|     QWidget* activationWindow() const; | ||||
| 
 | ||||
|     // Obsolete:
 | ||||
|     void initialize(bool dummy = true) | ||||
|         { isRunning(); Q_UNUSED(dummy) } | ||||
| 
 | ||||
| public Q_SLOTS: | ||||
|     bool sendMessage(const QString &message, int timeout = 5000); | ||||
|     void activateWindow(); | ||||
| 
 | ||||
| 
 | ||||
| Q_SIGNALS: | ||||
|     void messageReceived(const QString &message); | ||||
| 
 | ||||
| 
 | ||||
| private: | ||||
|     void sysInit(const QString &appId = QString()); | ||||
|     QtLocalPeer *peer; | ||||
|     QWidget *actWin; | ||||
| }; | ||||
| 
 | ||||
| #endif // QTSINGLEAPPLICATION_H
 | ||||
| @ -1,17 +0,0 @@ | ||||
| #include(../common.pri) | ||||
| INCLUDEPATH += $$PWD | ||||
| DEPENDPATH += $$PWD | ||||
| QT *= network | ||||
| greaterThan(QT_MAJOR_VERSION, 4): QT *= widgets | ||||
| 
 | ||||
| qtsingleapplication-uselib:!qtsingleapplication-buildlib { | ||||
|     LIBS += -L$$QTSINGLEAPPLICATION_LIBDIR -l$$QTSINGLEAPPLICATION_LIBNAME | ||||
| } else { | ||||
|     SOURCES += $$PWD/qtsingleapplication.cpp $$PWD/qtlocalpeer.cpp | ||||
|     HEADERS += $$PWD/qtsingleapplication.h $$PWD/qtlocalpeer.h | ||||
| } | ||||
| 
 | ||||
| win32 { | ||||
|     contains(TEMPLATE, lib):contains(CONFIG, shared):DEFINES += QT_QTSINGLEAPPLICATION_EXPORT | ||||
|     else:qtsingleapplication-uselib:DEFINES += QT_QTSINGLEAPPLICATION_IMPORT | ||||
| } | ||||
| @ -1,149 +0,0 @@ | ||||
| /****************************************************************************
 | ||||
| ** | ||||
| ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). | ||||
| ** Contact: http://www.qt-project.org/legal
 | ||||
| ** | ||||
| ** This file is part of the Qt Solutions component. | ||||
| ** | ||||
| ** $QT_BEGIN_LICENSE:BSD$ | ||||
| ** You may use this file under the terms of the BSD license as follows: | ||||
| ** | ||||
| ** "Redistribution and use in source and binary forms, with or without | ||||
| ** modification, are permitted provided that the following conditions are | ||||
| ** met: | ||||
| **   * Redistributions of source code must retain the above copyright | ||||
| **     notice, this list of conditions and the following disclaimer. | ||||
| **   * Redistributions in binary form must reproduce the above copyright | ||||
| **     notice, this list of conditions and the following disclaimer in | ||||
| **     the documentation and/or other materials provided with the | ||||
| **     distribution. | ||||
| **   * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names | ||||
| **     of its contributors may be used to endorse or promote products derived | ||||
| **     from this software without specific prior written permission. | ||||
| ** | ||||
| ** | ||||
| ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||||
| ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||||
| ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | ||||
| ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||||
| ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||||
| ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | ||||
| ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||||
| ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||||
| ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||||
| ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||
| ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." | ||||
| ** | ||||
| ** $QT_END_LICENSE$ | ||||
| ** | ||||
| ****************************************************************************/ | ||||
| 
 | ||||
| 
 | ||||
| #include "qtsinglecoreapplication.h" | ||||
| #include "qtlocalpeer.h" | ||||
| 
 | ||||
| /*!
 | ||||
|     \class QtSingleCoreApplication qtsinglecoreapplication.h | ||||
|     \brief A variant of the QtSingleApplication class for non-GUI applications. | ||||
| 
 | ||||
|     This class is a variant of QtSingleApplication suited for use in | ||||
|     console (non-GUI) applications. It is an extension of | ||||
|     QCoreApplication (instead of QApplication). It does not require | ||||
|     the QtGui library. | ||||
| 
 | ||||
|     The API and usage is identical to QtSingleApplication, except that | ||||
|     functions relating to the "activation window" are not present, for | ||||
|     obvious reasons. Please refer to the QtSingleApplication | ||||
|     documentation for explanation of the usage. | ||||
| 
 | ||||
|     A QtSingleCoreApplication instance can communicate to a | ||||
|     QtSingleApplication instance if they share the same application | ||||
|     id. Hence, this class can be used to create a light-weight | ||||
|     command-line tool that sends commands to a GUI application. | ||||
| 
 | ||||
|     \sa QtSingleApplication | ||||
| */ | ||||
| 
 | ||||
| /*!
 | ||||
|     Creates a QtSingleCoreApplication object. The application identifier | ||||
|     will be QCoreApplication::applicationFilePath(). \a argc and \a | ||||
|     argv are passed on to the QCoreAppliation constructor. | ||||
| */ | ||||
| 
 | ||||
| QtSingleCoreApplication::QtSingleCoreApplication(int &argc, char **argv) | ||||
|     : QCoreApplication(argc, argv) | ||||
| { | ||||
|     peer = new QtLocalPeer(this); | ||||
|     connect(peer, SIGNAL(messageReceived(const QString&)), SIGNAL(messageReceived(const QString&))); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /*!
 | ||||
|     Creates a QtSingleCoreApplication object with the application | ||||
|     identifier \a appId. \a argc and \a argv are passed on to the | ||||
|     QCoreAppliation constructor. | ||||
| */ | ||||
| QtSingleCoreApplication::QtSingleCoreApplication(const QString &appId, int &argc, char **argv) | ||||
|     : QCoreApplication(argc, argv) | ||||
| { | ||||
|     peer = new QtLocalPeer(this, appId); | ||||
|     connect(peer, SIGNAL(messageReceived(const QString&)), SIGNAL(messageReceived(const QString&))); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /*!
 | ||||
|     Returns true if another instance of this application is running; | ||||
|     otherwise false. | ||||
| 
 | ||||
|     This function does not find instances of this application that are | ||||
|     being run by a different user (on Windows: that are running in | ||||
|     another session). | ||||
| 
 | ||||
|     \sa sendMessage() | ||||
| */ | ||||
| 
 | ||||
| bool QtSingleCoreApplication::isRunning() | ||||
| { | ||||
|     return peer->isClient(); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /*!
 | ||||
|     Tries to send the text \a message to the currently running | ||||
|     instance. The QtSingleCoreApplication object in the running instance | ||||
|     will emit the messageReceived() signal when it receives the | ||||
|     message. | ||||
| 
 | ||||
|     This function returns true if the message has been sent to, and | ||||
|     processed by, the current instance. If there is no instance | ||||
|     currently running, or if the running instance fails to process the | ||||
|     message within \a timeout milliseconds, this function return false. | ||||
| 
 | ||||
|     \sa isRunning(), messageReceived() | ||||
| */ | ||||
| 
 | ||||
| bool QtSingleCoreApplication::sendMessage(const QString &message, int timeout) | ||||
| { | ||||
|     return peer->sendMessage(message, timeout); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /*!
 | ||||
|     Returns the application identifier. Two processes with the same | ||||
|     identifier will be regarded as instances of the same application. | ||||
| */ | ||||
| 
 | ||||
| QString QtSingleCoreApplication::id() const | ||||
| { | ||||
|     return peer->applicationId(); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /*!
 | ||||
|     \fn void QtSingleCoreApplication::messageReceived(const QString& message) | ||||
| 
 | ||||
|     This signal is emitted when the current instance receives a \a | ||||
|     message from another instance of this application. | ||||
| 
 | ||||
|     \sa sendMessage() | ||||
| */ | ||||
| @ -1,71 +0,0 @@ | ||||
| /****************************************************************************
 | ||||
| ** | ||||
| ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). | ||||
| ** Contact: http://www.qt-project.org/legal
 | ||||
| ** | ||||
| ** This file is part of the Qt Solutions component. | ||||
| ** | ||||
| ** $QT_BEGIN_LICENSE:BSD$ | ||||
| ** You may use this file under the terms of the BSD license as follows: | ||||
| ** | ||||
| ** "Redistribution and use in source and binary forms, with or without | ||||
| ** modification, are permitted provided that the following conditions are | ||||
| ** met: | ||||
| **   * Redistributions of source code must retain the above copyright | ||||
| **     notice, this list of conditions and the following disclaimer. | ||||
| **   * Redistributions in binary form must reproduce the above copyright | ||||
| **     notice, this list of conditions and the following disclaimer in | ||||
| **     the documentation and/or other materials provided with the | ||||
| **     distribution. | ||||
| **   * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names | ||||
| **     of its contributors may be used to endorse or promote products derived | ||||
| **     from this software without specific prior written permission. | ||||
| ** | ||||
| ** | ||||
| ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||||
| ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||||
| ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | ||||
| ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||||
| ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||||
| ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | ||||
| ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||||
| ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||||
| ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||||
| ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||
| ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." | ||||
| ** | ||||
| ** $QT_END_LICENSE$ | ||||
| ** | ||||
| ****************************************************************************/ | ||||
| 
 | ||||
| #ifndef QTSINGLECOREAPPLICATION_H | ||||
| #define QTSINGLECOREAPPLICATION_H | ||||
| 
 | ||||
| #include <QCoreApplication> | ||||
| 
 | ||||
| class QtLocalPeer; | ||||
| 
 | ||||
| class QtSingleCoreApplication : public QCoreApplication | ||||
| { | ||||
|     Q_OBJECT | ||||
| 
 | ||||
| public: | ||||
|     QtSingleCoreApplication(int &argc, char **argv); | ||||
|     QtSingleCoreApplication(const QString &id, int &argc, char **argv); | ||||
| 
 | ||||
|     bool isRunning(); | ||||
|     QString id() const; | ||||
| 
 | ||||
| public Q_SLOTS: | ||||
|     bool sendMessage(const QString &message, int timeout = 5000); | ||||
| 
 | ||||
| 
 | ||||
| Q_SIGNALS: | ||||
|     void messageReceived(const QString &message); | ||||
| 
 | ||||
| 
 | ||||
| private: | ||||
|     QtLocalPeer* peer; | ||||
| }; | ||||
| 
 | ||||
| #endif // QTSINGLECOREAPPLICATION_H
 | ||||
| @ -1,10 +0,0 @@ | ||||
| INCLUDEPATH	+= $$PWD | ||||
| DEPENDPATH      += $$PWD | ||||
| HEADERS		+= $$PWD/qtsinglecoreapplication.h $$PWD/qtlocalpeer.h | ||||
| SOURCES		+= $$PWD/qtsinglecoreapplication.cpp $$PWD/qtlocalpeer.cpp | ||||
| 
 | ||||
| QT *= network | ||||
| 
 | ||||
| win32:contains(TEMPLATE, lib):contains(CONFIG, shared) { | ||||
|     DEFINES += QT_QTSINGLECOREAPPLICATION_EXPORT=__declspec(dllexport) | ||||
| } | ||||
| @ -1,11 +1,11 @@ | ||||
| <RCC> | ||||
|     <qresource prefix="/"> | ||||
|         <file>translations/translation_en.qm</file> | ||||
|         <file>translations/translation_ru.qm</file> | ||||
|         <file>images/STIconBlue.png</file> | ||||
|         <file>images/STIconGreen.png</file> | ||||
|         <file>images/STIconOrange.png</file> | ||||
|         <file>images/STIconRed.png</file> | ||||
|         <file>version.json</file> | ||||
|     </qresource> | ||||
|     <qresource prefix="/icons"> | ||||
|         <file alias="app.png">share/images/STIconBlue.png</file> | ||||
|         <file alias="st_success.png">share/images/STIconGreen.png</file> | ||||
|         <file alias="st_busy.png">share/images/STIconOrange.png</file> | ||||
|         <file alias="st_error.png">share/images/STIconRed.png</file> | ||||
|     </qresource> | ||||
| </RCC> | ||||
|  | ||||
							
								
								
									
										92
									
								
								screen-translator.pro
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,92 @@ | ||||
| QT = core gui widgets network webenginewidgets | ||||
| 
 | ||||
| TARGET = screen-translator | ||||
| TEMPLATE = app | ||||
| CONFIG += c++17 | ||||
| 
 | ||||
| DEPS_DIR=$$(ST_DEPS_DIR) | ||||
| isEmpty(DEPS_DIR):DEPS_DIR=$$PWD/../deps | ||||
| INCLUDEPATH += $$DEPS_DIR/include | ||||
| LIBS += -L$$DEPS_DIR/lib | ||||
| LIBS += -ltesseract -lleptonica | ||||
| 
 | ||||
| win32{ | ||||
|   LIBS += -lUser32 | ||||
| } | ||||
| linux{ | ||||
|   QT += x11extras | ||||
|   LIBS += -lX11 | ||||
| } | ||||
| 
 | ||||
| DEFINES += VERSION="3.0.0" | ||||
| VERSION = 3.0.0.0 | ||||
| QMAKE_TARGET_COMPANY = Gres | ||||
| QMAKE_TARGET_PRODUCT = Screen Translator | ||||
| QMAKE_TARGET_COPYRIGHT = Copyright (c) Gres | ||||
| RC_ICONS = $$PWD/share/images/icon.ico | ||||
| 
 | ||||
| INCLUDEPATH += src src/service src/capture src/ocr \ | ||||
|   src/represent src/translate src/correct | ||||
| 
 | ||||
| HEADERS += \ | ||||
|   src/capture/captureoverlay.h \ | ||||
|   src/capture/capturer.h \ | ||||
|   src/correct/corrector.h \ | ||||
|   src/languagecodes.h \ | ||||
|   src/manager.h \ | ||||
|   src/ocr/recognizer.h \ | ||||
|   src/ocr/recognizerworker.h \ | ||||
|   src/ocr/tesseract.h \ | ||||
|   src/represent/representer.h \ | ||||
|   src/represent/resultwidget.h \ | ||||
|   src/service/apptranslator.h \ | ||||
|   src/service/debug.h \ | ||||
|   src/service/globalaction.h \ | ||||
|   src/service/singleapplication.h \ | ||||
|   src/service/widgetstate.h \ | ||||
|   src/settings.h \ | ||||
|   src/settingseditor.h \ | ||||
|   src/stfwd.h \ | ||||
|   src/task.h \ | ||||
|   src/translate/translator.h \ | ||||
|   src/translate/webpage.h \ | ||||
|   src/translate/webpageproxy.h \ | ||||
|   src/trayicon.h | ||||
| 
 | ||||
| SOURCES += \ | ||||
|   src/capture/captureoverlay.cpp \ | ||||
|   src/capture/capturer.cpp \ | ||||
|   src/correct/corrector.cpp \ | ||||
|   src/languagecodes.cpp \ | ||||
|   src/main.cpp \ | ||||
|   src/manager.cpp \ | ||||
|   src/ocr/recognizer.cpp \ | ||||
|   src/ocr/recognizerworker.cpp \ | ||||
|   src/ocr/tesseract.cpp \ | ||||
|   src/represent/representer.cpp \ | ||||
|   src/represent/resultwidget.cpp \ | ||||
|   src/service/apptranslator.cpp \ | ||||
|   src/service/debug.cpp \ | ||||
|   src/service/globalaction.cpp \ | ||||
|   src/service/singleapplication.cpp \ | ||||
|   src/service/widgetstate.cpp \ | ||||
|   src/settings.cpp \ | ||||
|   src/settingseditor.cpp \ | ||||
|   src/translate/translator.cpp \ | ||||
|   src/translate/webpage.cpp \ | ||||
|   src/translate/webpageproxy.cpp \ | ||||
|   src/trayicon.cpp | ||||
| 
 | ||||
| RESOURCES += \ | ||||
|   recources.qrc | ||||
| 
 | ||||
| FORMS += \ | ||||
|   src/settingseditor.ui | ||||
| 
 | ||||
| OTHER_FILES += \ | ||||
|   translators/*.js \ | ||||
|   version.json | ||||
| 
 | ||||
| #TRANSLATIONS += \ | ||||
| #    translations/translation_en.ts \ | ||||
| #    translations/translation_ru.ts | ||||
| @ -1,96 +0,0 @@ | ||||
| #------------------------------------------------- | ||||
| # | ||||
| # Project created by QtCreator 2013-11-22T12:00:23 | ||||
| # | ||||
| #------------------------------------------------- | ||||
| 
 | ||||
| QT       += core gui network webkitwidgets | ||||
| 
 | ||||
| greaterThan(QT_MAJOR_VERSION, 4): QT += widgets | ||||
| 
 | ||||
| TARGET = ScreenTranslator | ||||
| TEMPLATE = app | ||||
| CONFIG += c++11 | ||||
| 
 | ||||
| INCLUDEPATH += $$(DEPS_DIR)/include | ||||
| LIBS += -L$$(DEPS_DIR)/lib | ||||
| 
 | ||||
| win32{ | ||||
|     LIBS += --lUser32 -lws2_32 | ||||
| } | ||||
| linux{ | ||||
|     QT += x11extras | ||||
|     LIBS += -lX11 -Wl,-rpath,. | ||||
| } | ||||
| 
 | ||||
| LIBS += -ltesseract -lleptonica | ||||
| 
 | ||||
| include(3rd-party/qtsingleapplication/qtsingleapplication.pri) | ||||
| 
 | ||||
| INCLUDEPATH += src | ||||
| 
 | ||||
| SOURCES += \ | ||||
|     src/main.cpp \ | ||||
|     src/manager.cpp \ | ||||
|     src/settingseditor.cpp \ | ||||
|     src/selectiondialog.cpp \ | ||||
|     src/globalactionhelper.cpp \ | ||||
|     src/recognizer.cpp \ | ||||
|     src/resultdialog.cpp \ | ||||
|     src/processingitem.cpp \ | ||||
|     src/imageprocessing.cpp \ | ||||
|     src/languagehelper.cpp \ | ||||
|     src/webtranslator.cpp \ | ||||
|     src/webtranslatorproxy.cpp \ | ||||
|     src/translatorhelper.cpp \ | ||||
|     src/recognizerhelper.cpp \ | ||||
|     src/utils.cpp \ | ||||
|     src/updater.cpp | ||||
| 
 | ||||
| HEADERS += \ | ||||
|     src/manager.h \ | ||||
|     src/settingseditor.h \ | ||||
|     src/selectiondialog.h \ | ||||
|     src/globalactionhelper.h \ | ||||
|     src/recognizer.h \ | ||||
|     src/settings.h \ | ||||
|     src/processingitem.h \ | ||||
|     src/resultdialog.h \ | ||||
|     src/imageprocessing.h \ | ||||
|     src/languagehelper.h \ | ||||
|     src/webtranslator.h \ | ||||
|     src/webtranslatorproxy.h \ | ||||
|     src/stassert.h \ | ||||
|     src/translatorhelper.h \ | ||||
|     src/recognizerhelper.h \ | ||||
|     src/utils.h \ | ||||
|     src/updater.h | ||||
| 
 | ||||
| FORMS += \ | ||||
|     src/settingseditor.ui \ | ||||
|     src/selectiondialog.ui \ | ||||
|     src/resultdialog.ui | ||||
| 
 | ||||
| RESOURCES += \ | ||||
|     recources.qrc | ||||
| 
 | ||||
| TRANSLATIONS += \ | ||||
|     translations/translation_en.ts \ | ||||
|     translations/translation_ru.ts | ||||
| 
 | ||||
| OTHER_FILES += \ | ||||
|     images/* \ | ||||
|     translators/* \ | ||||
|     scripts/* \ | ||||
|     distr/* \ | ||||
|     version.json \ | ||||
|     README.md \ | ||||
|     share/uncrustify.cfg \ | ||||
|     .travis.yml \ | ||||
|     TODO.md | ||||
| 
 | ||||
| QMAKE_TARGET_COMPANY = Gres | ||||
| QMAKE_TARGET_PRODUCT = Screen Translator | ||||
| QMAKE_TARGET_COPYRIGHT = Copyright (c) Gres | ||||
| VERSION = 2.0.0.0 | ||||
| RC_ICONS = images/icon.ico | ||||
| Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB | 
| Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB | 
| Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB | 
| Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.0 KiB | 
| Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 97 KiB | 
							
								
								
									
										93
									
								
								src/capture/captureoverlay.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,93 @@ | ||||
| #include "captureoverlay.h" | ||||
| #include "capturer.h" | ||||
| #include "task.h" | ||||
| 
 | ||||
| #include <QMouseEvent> | ||||
| #include <QPainter> | ||||
| #include <QScreen> | ||||
| 
 | ||||
| CaptureOverlay::CaptureOverlay(Capturer &capturer) | ||||
|   : capturer_(capturer) | ||||
| { | ||||
|   setWindowFlags(Qt::FramelessWindowHint | Qt::NoDropShadowWindowHint | | ||||
|                  Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint); | ||||
|   setCursor(Qt::CrossCursor); | ||||
| } | ||||
| 
 | ||||
| void CaptureOverlay::setScreen(QScreen &screen) | ||||
| { | ||||
|   const auto geometry = screen.availableGeometry(); | ||||
| #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) | ||||
|   const auto pixmap = | ||||
|       screen.grabWindow(0, 0, 0, geometry.width(), geometry.height()); | ||||
| #else | ||||
|   const auto pixmap = screen.grabWindow(0, geometry.x(), geometry.y(), | ||||
|                                         geometry.width(), geometry.height()); | ||||
| #endif | ||||
|   pixmap_ = pixmap; | ||||
| 
 | ||||
|   auto palette = this->palette(); | ||||
|   palette.setBrush(backgroundRole(), pixmap); | ||||
|   setPalette(palette); | ||||
|   setGeometry(geometry); | ||||
| } | ||||
| 
 | ||||
| void CaptureOverlay::showEvent(QShowEvent * /*event*/) | ||||
| { | ||||
|   startSelectPos_ = currentSelectPos_ = QPoint(); | ||||
| } | ||||
| 
 | ||||
| void CaptureOverlay::keyPressEvent(QKeyEvent *event) | ||||
| { | ||||
|   if (event->key() == Qt::Key_Escape) | ||||
|     capturer_.canceled(); | ||||
| } | ||||
| 
 | ||||
| void CaptureOverlay::paintEvent(QPaintEvent * /*event*/) | ||||
| { | ||||
|   auto selection = QRect(startSelectPos_, currentSelectPos_).normalized(); | ||||
|   if (!selection.isValid()) | ||||
|     return; | ||||
| 
 | ||||
|   QPainter painter(this); | ||||
|   painter.setPen(Qt::red); | ||||
|   painter.drawRect(selection); | ||||
| } | ||||
| 
 | ||||
| void CaptureOverlay::mousePressEvent(QMouseEvent *event) | ||||
| { | ||||
|   if (startSelectPos_.isNull()) | ||||
|     startSelectPos_ = event->pos(); | ||||
| } | ||||
| 
 | ||||
| void CaptureOverlay::mouseMoveEvent(QMouseEvent *event) | ||||
| { | ||||
|   if (startSelectPos_.isNull()) | ||||
|     return; | ||||
| 
 | ||||
|   currentSelectPos_ = event->pos(); | ||||
|   repaint(); | ||||
| } | ||||
| 
 | ||||
| void CaptureOverlay::mouseReleaseEvent(QMouseEvent *event) | ||||
| { | ||||
|   if (startSelectPos_.isNull() || pixmap_.isNull()) | ||||
|     return; | ||||
| 
 | ||||
|   const auto endPos = event->pos(); | ||||
|   const auto selection = QRect(startSelectPos_, endPos).normalized(); | ||||
|   const auto selectedPixmap = pixmap_.copy(selection); | ||||
| 
 | ||||
|   startSelectPos_ = currentSelectPos_ = QPoint(); | ||||
| 
 | ||||
|   if (selectedPixmap.width() < 3 || selectedPixmap.height() < 3) { | ||||
|     capturer_.canceled(); | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   auto task = std::make_shared<Task>(); | ||||
|   task->captured = selectedPixmap; | ||||
|   task->capturePoint = pos() + selection.topLeft(); | ||||
|   // TODO add customization menus
 | ||||
|   capturer_.captured(task); | ||||
| } | ||||
							
								
								
									
										30
									
								
								src/capture/captureoverlay.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,30 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <QWidget> | ||||
| 
 | ||||
| class Capturer; | ||||
| class QScreen; | ||||
| 
 | ||||
| class CaptureOverlay : public QWidget | ||||
| { | ||||
|   Q_OBJECT | ||||
| 
 | ||||
| public: | ||||
|   explicit CaptureOverlay(Capturer &capturer); | ||||
| 
 | ||||
|   void setScreen(QScreen &screen); | ||||
| 
 | ||||
| protected: | ||||
|   void showEvent(QShowEvent *event) override; | ||||
|   void keyPressEvent(QKeyEvent *event) override; | ||||
|   void mousePressEvent(QMouseEvent *event) override; | ||||
|   void mouseMoveEvent(QMouseEvent *event) override; | ||||
|   void mouseReleaseEvent(QMouseEvent *event) override; | ||||
|   void paintEvent(QPaintEvent *event) override; | ||||
| 
 | ||||
| private: | ||||
|   Capturer &capturer_; | ||||
|   QPixmap pixmap_; | ||||
|   QPoint startSelectPos_; | ||||
|   QPoint currentSelectPos_; | ||||
| }; | ||||
							
								
								
									
										80
									
								
								src/capture/capturer.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,80 @@ | ||||
| #include "capturer.h" | ||||
| #include "captureoverlay.h" | ||||
| #include "manager.h" | ||||
| #include "settings.h" | ||||
| #include "task.h" | ||||
| 
 | ||||
| #include <QApplication> | ||||
| 
 | ||||
| Capturer::Capturer(Manager &manager) | ||||
|   : manager_(manager) | ||||
| { | ||||
| } | ||||
| 
 | ||||
| void Capturer::capture() | ||||
| { | ||||
|   showOverlays(true); | ||||
| } | ||||
| 
 | ||||
| void Capturer::repeatCapture() | ||||
| { | ||||
|   showOverlays(false); | ||||
| } | ||||
| 
 | ||||
| void Capturer::updateSettings(const Settings &settings) | ||||
| { | ||||
|   sourceLanguage_ = settings.sourceLanguage; | ||||
|   targetLanguage_ = settings.targetLanguage; | ||||
|   translators_ = settings.translators; | ||||
| } | ||||
| 
 | ||||
| void Capturer::captured(const TaskPtr &task) | ||||
| { | ||||
|   hideOverlays(); | ||||
|   // TODO respect more overlay's options
 | ||||
|   // TODO process modifiers
 | ||||
|   task->translators = translators_; | ||||
|   task->sourceLanguage = sourceLanguage_; | ||||
|   task->targetLanguage = targetLanguage_; | ||||
|   manager_.captured(task); | ||||
| } | ||||
| 
 | ||||
| void Capturer::canceled() | ||||
| { | ||||
|   hideOverlays(); | ||||
|   manager_.captureCanceled(); | ||||
| } | ||||
| 
 | ||||
| void Capturer::showOverlays(bool capturePixmap) | ||||
| { | ||||
|   const auto screens = QApplication::screens(); | ||||
|   const auto screensSize = screens.size(); | ||||
|   int overlaysSize = overlays_.size(); | ||||
|   if (screensSize > overlaysSize) | ||||
|     overlays_.reserve(screensSize); | ||||
| 
 | ||||
|   for (auto i = 0, end = screensSize; i < end; ++i) { | ||||
|     if (i == overlaysSize) { | ||||
|       overlays_.push_back(new CaptureOverlay(*this)); | ||||
|       ++overlaysSize; | ||||
|     } | ||||
| 
 | ||||
|     const auto screen = screens[i]; | ||||
|     auto &overlay = overlays_[i]; | ||||
|     overlay->hide(); | ||||
|     if (capturePixmap) | ||||
|       overlay->setScreen(*screen); | ||||
|     overlay->show(); | ||||
|     overlay->activateWindow(); | ||||
|   } | ||||
| 
 | ||||
|   if (screensSize < overlaysSize) { | ||||
|     for (auto i = overlaysSize - 1; i >= screensSize; --i) | ||||
|       overlays_[i]->deleteLater(); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void Capturer::hideOverlays() | ||||
| { | ||||
|   for (const auto &overlay : overlays_) overlay->hide(); | ||||
| } | ||||
							
								
								
									
										33
									
								
								src/capture/capturer.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,33 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include "stfwd.h" | ||||
| 
 | ||||
| #include <QStringList> | ||||
| 
 | ||||
| #include <vector> | ||||
| 
 | ||||
| class CaptureOverlay; | ||||
| 
 | ||||
| class Capturer | ||||
| { | ||||
| public: | ||||
|   explicit Capturer(Manager &manager); | ||||
| 
 | ||||
|   void capture(); | ||||
|   void repeatCapture(); | ||||
|   void updateSettings(const Settings &settings); | ||||
| 
 | ||||
|   void captured(const TaskPtr &task); | ||||
|   void canceled(); | ||||
| 
 | ||||
| private: | ||||
|   void showOverlays(bool capturePixmap); | ||||
|   void hideOverlays(); | ||||
| 
 | ||||
|   Manager &manager_; | ||||
|   std::vector<CaptureOverlay *> overlays_; | ||||
| 
 | ||||
|   LanguageId sourceLanguage_; | ||||
|   LanguageId targetLanguage_; | ||||
|   QStringList translators_; | ||||
| }; | ||||
							
								
								
									
										56
									
								
								src/correct/corrector.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,56 @@ | ||||
| #include "corrector.h" | ||||
| #include "debug.h" | ||||
| #include "manager.h" | ||||
| #include "task.h" | ||||
| 
 | ||||
| Corrector::Corrector(Manager &manager) | ||||
|   : manager_(manager) | ||||
| { | ||||
| } | ||||
| 
 | ||||
| void Corrector::correct(const TaskPtr &task) | ||||
| { | ||||
|   SOFT_ASSERT(task, return ); | ||||
|   SOFT_ASSERT(task->isValid(), return ); | ||||
| 
 | ||||
|   if (!userSubstitutions_.empty()) | ||||
|     task->recognized = substituteUser(task->recognized, task->sourceLanguage); | ||||
| 
 | ||||
|   manager_.corrected(task); | ||||
| } | ||||
| 
 | ||||
| void Corrector::updateSettings(const Settings &settings) | ||||
| { | ||||
|   userSubstitutions_ = settings.userSubstitutions; | ||||
| } | ||||
| 
 | ||||
| QString Corrector::substituteUser(const QString &source, | ||||
|                                   const LanguageId &language) const | ||||
| { | ||||
|   auto result = source; | ||||
| 
 | ||||
|   const auto range = userSubstitutions_.equal_range(language); | ||||
|   if (range.first == userSubstitutions_.cend()) | ||||
|     return result; | ||||
| 
 | ||||
|   while (true) { | ||||
|     auto bestMatch = range.first; | ||||
|     auto bestMatchLen = 0; | ||||
| 
 | ||||
|     for (auto it = range.first; it != range.second; ++it) { | ||||
|       const auto &sub = it->second; | ||||
|       const auto len = sub.source.length(); | ||||
|       if (len > bestMatchLen) { | ||||
|         bestMatchLen = len; | ||||
|         bestMatch = it; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     if (bestMatchLen < 1) | ||||
|       break; | ||||
| 
 | ||||
|     result.replace(bestMatch->second.source, bestMatch->second.target); | ||||
|   } | ||||
| 
 | ||||
|   return result; | ||||
| } | ||||
							
								
								
									
										20
									
								
								src/correct/corrector.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,20 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include "settings.h" | ||||
| #include "stfwd.h" | ||||
| 
 | ||||
| class Corrector | ||||
| { | ||||
| public: | ||||
|   explicit Corrector(Manager &manager); | ||||
| 
 | ||||
|   void correct(const TaskPtr &task); | ||||
|   void updateSettings(const Settings &settings); | ||||
| 
 | ||||
| private: | ||||
|   QString substituteUser(const QString &source, | ||||
|                          const LanguageId &language) const; | ||||
| 
 | ||||
|   Manager &manager_; | ||||
|   Substitutions userSubstitutions_; | ||||
| }; | ||||
| @ -1,367 +0,0 @@ | ||||
| #include "globalactionhelper.h" | ||||
| 
 | ||||
| #include <QDebug> | ||||
| #include <QApplication> | ||||
| 
 | ||||
| QHash<QPair<quint32, quint32>, QAction *> GlobalActionHelper::actions_; | ||||
| 
 | ||||
| void GlobalActionHelper::init () { | ||||
|   qApp->installNativeEventFilter (new GlobalActionHelper); | ||||
| } | ||||
| 
 | ||||
| bool GlobalActionHelper::makeGlobal (QAction *action) { | ||||
|   QKeySequence hotKey = action->shortcut (); | ||||
|   if (hotKey.isEmpty ()) { | ||||
|     return true; | ||||
|   } | ||||
|   Qt::KeyboardModifiers allMods = Qt::ShiftModifier | Qt::ControlModifier | | ||||
|                                   Qt::AltModifier | Qt::MetaModifier; | ||||
|   Qt::Key key = hotKey.isEmpty () ? | ||||
|                 Qt::Key (0) : | ||||
|                 Qt::Key ((hotKey[0] ^ allMods) & hotKey[0]); | ||||
|   Qt::KeyboardModifiers mods = hotKey.isEmpty () ? | ||||
|                                Qt::KeyboardModifiers (0) : | ||||
|                                Qt::KeyboardModifiers (hotKey[0] & allMods); | ||||
|   const quint32 nativeKey = nativeKeycode (key); | ||||
|   const quint32 nativeMods = nativeModifiers (mods); | ||||
|   const bool res = registerHotKey (nativeKey, nativeMods); | ||||
|   if (res) { | ||||
|     actions_.insert (qMakePair (nativeKey, nativeMods), action); | ||||
|   } | ||||
|   else { | ||||
|     qWarning () << "Failed to register global hotkey:" << hotKey.toString (); | ||||
|   } | ||||
|   return res; | ||||
| } | ||||
| 
 | ||||
| bool GlobalActionHelper::removeGlobal (QAction *action) { | ||||
|   QKeySequence hotKey = action->shortcut (); | ||||
|   if (hotKey.isEmpty ()) { | ||||
|     return true; | ||||
|   } | ||||
|   Qt::KeyboardModifiers allMods = Qt::ShiftModifier | Qt::ControlModifier | | ||||
|                                   Qt::AltModifier | Qt::MetaModifier; | ||||
|   Qt::Key key = hotKey.isEmpty () ? | ||||
|                 Qt::Key (0) : | ||||
|                 Qt::Key ((hotKey[0] ^ allMods) & hotKey[0]); | ||||
|   Qt::KeyboardModifiers mods = hotKey.isEmpty () ? | ||||
|                                Qt::KeyboardModifiers (0) : | ||||
|                                Qt::KeyboardModifiers (hotKey[0] & allMods); | ||||
|   const quint32 nativeKey = nativeKeycode (key); | ||||
|   const quint32 nativeMods = nativeModifiers (mods); | ||||
|   if (!actions_.contains (qMakePair (nativeKey, nativeMods))) { | ||||
|     return true; | ||||
|   } | ||||
|   const bool res = unregisterHotKey (nativeKey, nativeMods); | ||||
|   if (res) { | ||||
|     actions_.remove (qMakePair (nativeKey, nativeMods)); | ||||
|   } | ||||
|   else { | ||||
|     qWarning () << "Failed to unregister global hotkey:" << hotKey.toString (); | ||||
|   } | ||||
|   return res; | ||||
| } | ||||
| 
 | ||||
| void GlobalActionHelper::triggerHotKey (quint32 nativeKey, quint32 nativeMods) { | ||||
|   QAction *action = actions_.value (qMakePair (nativeKey, nativeMods)); | ||||
|   if (action && action->isEnabled ()) { | ||||
|     action->activate (QAction::Trigger); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| #if defined(Q_OS_LINUX) | ||||
| #  include <QX11Info> | ||||
| #  include <X11/Xlib.h> | ||||
| #  include <xcb/xcb_event.h> | ||||
| 
 | ||||
| static bool error = false; | ||||
| 
 | ||||
| static int customHandler (Display *display, XErrorEvent *event) { | ||||
|   Q_UNUSED (display); | ||||
|   switch (event->error_code) { | ||||
|     case BadAccess: | ||||
|     case BadValue: | ||||
|     case BadWindow: | ||||
|       if (event->request_code == 33 /* X_GrabKey */ || | ||||
|           event->request_code == 34 /* X_UngrabKey */) { | ||||
|         error = true; | ||||
|       } | ||||
|     default: | ||||
|       return 0; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| bool GlobalActionHelper::registerHotKey (quint32 nativeKey, quint32 nativeMods) { | ||||
|   Display *display = QX11Info::display (); | ||||
|   Window window = QX11Info::appRootWindow (); | ||||
|   Bool owner = True; | ||||
|   int pointer = GrabModeAsync; | ||||
|   int keyboard = GrabModeAsync; | ||||
|   error = false; | ||||
|   int (*handler)(Display *display, XErrorEvent *event) = XSetErrorHandler (customHandler); | ||||
|   XGrabKey (display, nativeKey, nativeMods, window, owner, pointer, keyboard); | ||||
|   // allow numlock
 | ||||
|   XGrabKey (display, nativeKey, nativeMods | Mod2Mask, window, owner, pointer, keyboard); | ||||
|   XSync (display, False); | ||||
|   XSetErrorHandler (handler); | ||||
|   return !error; | ||||
| } | ||||
| 
 | ||||
| bool GlobalActionHelper::unregisterHotKey (quint32 nativeKey, quint32 nativeMods) { | ||||
|   Display *display = QX11Info::display (); | ||||
|   Window window = QX11Info::appRootWindow (); | ||||
|   error = false; | ||||
|   int (*handler)(Display *display, XErrorEvent *event) = XSetErrorHandler (customHandler); | ||||
|   XUngrabKey (display, nativeKey, nativeMods, window); | ||||
|   // allow numlock
 | ||||
|   XUngrabKey (display, nativeKey, nativeMods | Mod2Mask, window); | ||||
|   XSync (display, False); | ||||
|   XSetErrorHandler (handler); | ||||
|   return !error; | ||||
| } | ||||
| 
 | ||||
| bool GlobalActionHelper::nativeEventFilter (const QByteArray &eventType, | ||||
|                                             void *message, long *result) { | ||||
|   Q_UNUSED (eventType); | ||||
|   Q_UNUSED (result); | ||||
|   xcb_generic_event_t *event = static_cast<xcb_generic_event_t *>(message); | ||||
|   if (event->response_type == XCB_KEY_PRESS) { | ||||
|     xcb_key_press_event_t *keyEvent = static_cast<xcb_key_press_event_t *>(message); | ||||
|     const quint32 keycode = keyEvent->detail; | ||||
|     const quint32 modifiers = keyEvent->state & ~XCB_MOD_MASK_2; | ||||
|     triggerHotKey (keycode, modifiers); | ||||
|   } | ||||
|   return false; | ||||
| } | ||||
| 
 | ||||
| quint32 GlobalActionHelper::nativeKeycode (Qt::Key key) { | ||||
|   Display *display = QX11Info::display (); | ||||
|   KeySym keySym =  XStringToKeysym (qPrintable (QKeySequence (key).toString ())); | ||||
|   return XKeysymToKeycode (display, keySym); | ||||
| } | ||||
| 
 | ||||
| quint32 GlobalActionHelper::nativeModifiers (Qt::KeyboardModifiers modifiers) { | ||||
|   quint32 native = 0; | ||||
|   if (modifiers & Qt::ShiftModifier) { | ||||
|     native |= ShiftMask; | ||||
|   } | ||||
|   if (modifiers & Qt::ControlModifier) { | ||||
|     native |= ControlMask; | ||||
|   } | ||||
|   if (modifiers & Qt::AltModifier) { | ||||
|     native |= Mod1Mask; | ||||
|   } | ||||
|   if (modifiers & Qt::MetaModifier) { | ||||
|     native |= Mod4Mask; | ||||
|   } | ||||
|   return native; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| #elif defined(Q_OS_WIN) | ||||
| #  include <qt_windows.h> | ||||
| 
 | ||||
| 
 | ||||
| bool GlobalActionHelper::registerHotKey (quint32 nativeKey, quint32 nativeMods) { | ||||
|   return RegisterHotKey (0, nativeMods ^ nativeKey, nativeMods, nativeKey); | ||||
| } | ||||
| 
 | ||||
| bool GlobalActionHelper::unregisterHotKey (quint32 nativeKey, quint32 nativeMods) { | ||||
|   return UnregisterHotKey (0, nativeMods ^ nativeKey); | ||||
| } | ||||
| 
 | ||||
| bool GlobalActionHelper::nativeEventFilter (const QByteArray &eventType, | ||||
|                                             void *message, long *result) { | ||||
|   Q_UNUSED (eventType); | ||||
|   Q_UNUSED (result); | ||||
|   MSG *msg = static_cast<MSG *>(message); | ||||
|   if (msg->message == WM_HOTKEY) { | ||||
|     const quint32 keycode = HIWORD (msg->lParam); | ||||
|     const quint32 modifiers = LOWORD (msg->lParam); | ||||
|     triggerHotKey (keycode, modifiers); | ||||
|   } | ||||
|   return false; | ||||
| } | ||||
| 
 | ||||
| quint32 GlobalActionHelper::nativeKeycode (Qt::Key key) { | ||||
|   switch (key) { | ||||
|     case Qt::Key_Escape: | ||||
|       return VK_ESCAPE; | ||||
|     case Qt::Key_Tab: | ||||
|     case Qt::Key_Backtab: | ||||
|       return VK_TAB; | ||||
|     case Qt::Key_Backspace: | ||||
|       return VK_BACK; | ||||
|     case Qt::Key_Return: | ||||
|     case Qt::Key_Enter: | ||||
|       return VK_RETURN; | ||||
|     case Qt::Key_Insert: | ||||
|       return VK_INSERT; | ||||
|     case Qt::Key_Delete: | ||||
|       return VK_DELETE; | ||||
|     case Qt::Key_Pause: | ||||
|       return VK_PAUSE; | ||||
|     case Qt::Key_Print: | ||||
|       return VK_PRINT; | ||||
|     case Qt::Key_Clear: | ||||
|       return VK_CLEAR; | ||||
|     case Qt::Key_Home: | ||||
|       return VK_HOME; | ||||
|     case Qt::Key_End: | ||||
|       return VK_END; | ||||
|     case Qt::Key_Left: | ||||
|       return VK_LEFT; | ||||
|     case Qt::Key_Up: | ||||
|       return VK_UP; | ||||
|     case Qt::Key_Right: | ||||
|       return VK_RIGHT; | ||||
|     case Qt::Key_Down: | ||||
|       return VK_DOWN; | ||||
|     case Qt::Key_PageUp: | ||||
|       return VK_PRIOR; | ||||
|     case Qt::Key_PageDown: | ||||
|       return VK_NEXT; | ||||
|     case Qt::Key_F1: | ||||
|       return VK_F1; | ||||
|     case Qt::Key_F2: | ||||
|       return VK_F2; | ||||
|     case Qt::Key_F3: | ||||
|       return VK_F3; | ||||
|     case Qt::Key_F4: | ||||
|       return VK_F4; | ||||
|     case Qt::Key_F5: | ||||
|       return VK_F5; | ||||
|     case Qt::Key_F6: | ||||
|       return VK_F6; | ||||
|     case Qt::Key_F7: | ||||
|       return VK_F7; | ||||
|     case Qt::Key_F8: | ||||
|       return VK_F8; | ||||
|     case Qt::Key_F9: | ||||
|       return VK_F9; | ||||
|     case Qt::Key_F10: | ||||
|       return VK_F10; | ||||
|     case Qt::Key_F11: | ||||
|       return VK_F11; | ||||
|     case Qt::Key_F12: | ||||
|       return VK_F12; | ||||
|     case Qt::Key_F13: | ||||
|       return VK_F13; | ||||
|     case Qt::Key_F14: | ||||
|       return VK_F14; | ||||
|     case Qt::Key_F15: | ||||
|       return VK_F15; | ||||
|     case Qt::Key_F16: | ||||
|       return VK_F16; | ||||
|     case Qt::Key_F17: | ||||
|       return VK_F17; | ||||
|     case Qt::Key_F18: | ||||
|       return VK_F18; | ||||
|     case Qt::Key_F19: | ||||
|       return VK_F19; | ||||
|     case Qt::Key_F20: | ||||
|       return VK_F20; | ||||
|     case Qt::Key_F21: | ||||
|       return VK_F21; | ||||
|     case Qt::Key_F22: | ||||
|       return VK_F22; | ||||
|     case Qt::Key_F23: | ||||
|       return VK_F23; | ||||
|     case Qt::Key_F24: | ||||
|       return VK_F24; | ||||
|     case Qt::Key_Space: | ||||
|       return VK_SPACE; | ||||
|     case Qt::Key_Asterisk: | ||||
|       return VK_MULTIPLY; | ||||
|     case Qt::Key_Plus: | ||||
|       return VK_ADD; | ||||
|     case Qt::Key_Comma: | ||||
|       return VK_SEPARATOR; | ||||
|     case Qt::Key_Minus: | ||||
|       return VK_SUBTRACT; | ||||
|     case Qt::Key_Slash: | ||||
|       return VK_DIVIDE; | ||||
|     case Qt::Key_MediaNext: | ||||
|       return VK_MEDIA_NEXT_TRACK; | ||||
|     case Qt::Key_MediaPrevious: | ||||
|       return VK_MEDIA_PREV_TRACK; | ||||
|     case Qt::Key_MediaPlay: | ||||
|       return VK_MEDIA_PLAY_PAUSE; | ||||
|     case Qt::Key_MediaStop: | ||||
|       return VK_MEDIA_STOP; | ||||
|     case Qt::Key_VolumeDown: | ||||
|       return VK_VOLUME_DOWN; | ||||
|     case Qt::Key_VolumeUp: | ||||
|       return VK_VOLUME_UP; | ||||
|     case Qt::Key_VolumeMute: | ||||
|       return VK_VOLUME_MUTE; | ||||
| 
 | ||||
|     // numbers
 | ||||
|     case Qt::Key_0: | ||||
|     case Qt::Key_1: | ||||
|     case Qt::Key_2: | ||||
|     case Qt::Key_3: | ||||
|     case Qt::Key_4: | ||||
|     case Qt::Key_5: | ||||
|     case Qt::Key_6: | ||||
|     case Qt::Key_7: | ||||
|     case Qt::Key_8: | ||||
|     case Qt::Key_9: | ||||
|       return key; | ||||
| 
 | ||||
|     // letters
 | ||||
|     case Qt::Key_A: | ||||
|     case Qt::Key_B: | ||||
|     case Qt::Key_C: | ||||
|     case Qt::Key_D: | ||||
|     case Qt::Key_E: | ||||
|     case Qt::Key_F: | ||||
|     case Qt::Key_G: | ||||
|     case Qt::Key_H: | ||||
|     case Qt::Key_I: | ||||
|     case Qt::Key_J: | ||||
|     case Qt::Key_K: | ||||
|     case Qt::Key_L: | ||||
|     case Qt::Key_M: | ||||
|     case Qt::Key_N: | ||||
|     case Qt::Key_O: | ||||
|     case Qt::Key_P: | ||||
|     case Qt::Key_Q: | ||||
|     case Qt::Key_R: | ||||
|     case Qt::Key_S: | ||||
|     case Qt::Key_T: | ||||
|     case Qt::Key_U: | ||||
|     case Qt::Key_V: | ||||
|     case Qt::Key_W: | ||||
|     case Qt::Key_X: | ||||
|     case Qt::Key_Y: | ||||
|     case Qt::Key_Z: | ||||
|       return key; | ||||
| 
 | ||||
|     default: | ||||
|       return 0; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| quint32 GlobalActionHelper::nativeModifiers (Qt::KeyboardModifiers modifiers) { | ||||
|   // MOD_ALT, MOD_CONTROL, (MOD_KEYUP), MOD_SHIFT, MOD_WIN
 | ||||
|   quint32 native = 0; | ||||
|   if (modifiers & Qt::ShiftModifier) { | ||||
|     native |= MOD_SHIFT; | ||||
|   } | ||||
|   if (modifiers & Qt::ControlModifier) { | ||||
|     native |= MOD_CONTROL; | ||||
|   } | ||||
|   if (modifiers & Qt::AltModifier) { | ||||
|     native |= MOD_ALT; | ||||
|   } | ||||
|   if (modifiers & Qt::MetaModifier) { | ||||
|     native |= MOD_WIN; | ||||
|   } | ||||
|   //if (modifiers & Qt::KeypadModifier)
 | ||||
|   //if (modifiers & Qt::GroupSwitchModifier)
 | ||||
|   return native; | ||||
| } | ||||
| #endif | ||||
| @ -1,29 +0,0 @@ | ||||
| #ifndef GLOBALACTIONHELPER_H | ||||
| #define GLOBALACTIONHELPER_H | ||||
| 
 | ||||
| // Some functions copied from QXT lib
 | ||||
| 
 | ||||
| #include <QAbstractNativeEventFilter> | ||||
| #include <QAction> | ||||
| 
 | ||||
| class GlobalActionHelper : public QAbstractNativeEventFilter { | ||||
|   public: | ||||
|     bool nativeEventFilter (const QByteArray &eventType, void *message, | ||||
|                             long *result); | ||||
| 
 | ||||
|     static void init (); | ||||
|     static bool makeGlobal (QAction *action); | ||||
|     static bool removeGlobal (QAction *action); | ||||
| 
 | ||||
|   private: | ||||
|     static QHash<QPair<quint32, quint32>, QAction *> actions_; | ||||
| 
 | ||||
|     static quint32 nativeKeycode (Qt::Key key); | ||||
|     static quint32 nativeModifiers (Qt::KeyboardModifiers modifiers); | ||||
|     static bool registerHotKey (quint32 nativeKey, quint32 nativeMods); | ||||
|     static bool unregisterHotKey (quint32 nativeKey, quint32 nativeMods); | ||||
|     static void triggerHotKey (quint32 nativeKey, quint32 nativeMods); | ||||
| 
 | ||||
| }; | ||||
| 
 | ||||
| #endif // GLOBALACTIONHELPER_H
 | ||||
| @ -1,155 +0,0 @@ | ||||
| #include <QDebug> | ||||
| 
 | ||||
| #include <leptonica/allheaders.h> | ||||
| 
 | ||||
| #include "imageprocessing.h" | ||||
| #include "stassert.h" | ||||
| 
 | ||||
| #if defined(Q_OS_LINUX) | ||||
| #  include <fstream> | ||||
| #  include <limits> | ||||
| qint64 getFreeMemory () { | ||||
|   std::string token; | ||||
|   std::ifstream file ("/proc/meminfo"); | ||||
|   qint64 freeMem = 0; | ||||
|   while (file >> token) { | ||||
|     if (token == "MemFree:" || token == "Buffers:" || token == "Cached:") { | ||||
|       unsigned long mem = 0; | ||||
|       freeMem += (file >> mem) ? mem : 0; | ||||
|     } | ||||
|   } | ||||
|   return freeMem * 1024; | ||||
| } | ||||
| #elif defined(Q_OS_WIN) | ||||
| #  include <windows.h> | ||||
| qint64 getFreeMemory () { | ||||
|   MEMORYSTATUSEX statex; | ||||
|   statex.dwLength = sizeof (statex); | ||||
|   if (GlobalMemoryStatusEx (&statex)) { | ||||
|     return statex.ullAvailPhys; | ||||
|   } | ||||
|   return -1; | ||||
| } | ||||
| #endif | ||||
| 
 | ||||
| Pix * convertImage (const QImage &image) { | ||||
|   PIX *pix; | ||||
| 
 | ||||
|   int width = image.width (); | ||||
|   int height = image.height (); | ||||
|   int depth = image.depth (); | ||||
|   int bytesPerLine = image.bytesPerLine (); | ||||
|   int wpl = bytesPerLine / 4; | ||||
| 
 | ||||
|   pix = pixCreate (width, height, depth); | ||||
|   pixSetWpl (pix, wpl); | ||||
|   pixSetColormap (pix, NULL); | ||||
|   memmove (pix->data, image.bits (), bytesPerLine * height); | ||||
| 
 | ||||
|   const qreal toDPM = 1.0 / 0.0254; | ||||
|   int resolutionX = image.dotsPerMeterX () / toDPM; | ||||
|   int resolutionY = image.dotsPerMeterY () / toDPM; | ||||
| 
 | ||||
|   if (resolutionX < 300) { | ||||
|     resolutionX = 300; | ||||
|   } | ||||
|   if (resolutionY < 300) { | ||||
|     resolutionY = 300; | ||||
|   } | ||||
|   pixSetResolution (pix, resolutionX, resolutionY); | ||||
|   return pix; | ||||
| } | ||||
| 
 | ||||
| QImage convertImage (Pix &image) { | ||||
|   int width = pixGetWidth (&image); | ||||
|   int height = pixGetHeight (&image); | ||||
|   int depth = pixGetDepth (&image); | ||||
|   int bytesPerLine = pixGetWpl (&image) * 4; | ||||
|   l_uint32 *datas = pixGetData (&image); | ||||
| 
 | ||||
|   QImage::Format format; | ||||
|   if (depth == 1) { | ||||
|     format = QImage::Format_Mono; | ||||
|   } | ||||
|   else if (depth == 8) { | ||||
|     format = QImage::Format_Indexed8; | ||||
|   } | ||||
|   else { | ||||
|     format = QImage::Format_RGB32; | ||||
|   } | ||||
| 
 | ||||
|   QImage result ((uchar *)datas, width, height, bytesPerLine, format); | ||||
| 
 | ||||
|   // Set resolution
 | ||||
|   l_int32 xres, yres; | ||||
|   pixGetResolution (&image, &xres, &yres); | ||||
|   const qreal toDPM = 1.0 / 0.0254; | ||||
|   result.setDotsPerMeterX (xres * toDPM); | ||||
|   result.setDotsPerMeterY (yres * toDPM); | ||||
| 
 | ||||
|   // Handle pallete
 | ||||
|   QVector<QRgb> _bwCT; | ||||
|   _bwCT.append (qRgb (255,255,255)); | ||||
|   _bwCT.append (qRgb (0,0,0)); | ||||
| 
 | ||||
|   QVector<QRgb> _grayscaleCT (256); | ||||
|   for (int i = 0; i < 256; i++) { | ||||
|     _grayscaleCT.append (qRgb (i, i, i)); | ||||
|   } | ||||
|   switch (depth) { | ||||
|     case 1: | ||||
|       result.setColorTable (_bwCT); | ||||
|       break; | ||||
|     case 8: | ||||
|       result.setColorTable (_grayscaleCT); | ||||
|       break; | ||||
|     default: | ||||
|       result.setColorTable (_grayscaleCT); | ||||
|   } | ||||
| 
 | ||||
|   if (result.isNull ()) { | ||||
|     static QImage none (0,0,QImage::Format_Invalid); | ||||
|     qDebug ("Invalid format!!!\n"); | ||||
|     return none; | ||||
|   } | ||||
| 
 | ||||
|   return result; | ||||
| } | ||||
| 
 | ||||
| Pix * prepareImage (const QImage &image, int preferredScale) { | ||||
|   Pix *pix = convertImage (image); | ||||
|   ST_ASSERT (pix != NULL); | ||||
| 
 | ||||
|   Pix *gray = pixConvertRGBToGray (pix, 0.0, 0.0, 0.0); | ||||
|   ST_ASSERT (gray != NULL); | ||||
|   pixDestroy (&pix); | ||||
| 
 | ||||
|   Pix *scaled = gray; | ||||
|   if (preferredScale > 0) { | ||||
|     const auto MAX_INT16 = 0x7fff; | ||||
|     float maxScaleX = MAX_INT16 / double (gray->w); | ||||
|     float scaleX = std::min (float (preferredScale), maxScaleX); | ||||
|     float maxScaleY = MAX_INT16 / double (gray->h); | ||||
|     float scaleY = std::min (float (preferredScale), maxScaleY); | ||||
|     float scale = std::min (scaleX, scaleY); | ||||
| 
 | ||||
|     qint64 availableMemory = getFreeMemory () * 0.95; | ||||
|     if (availableMemory > 0) { | ||||
|       qint32 actualSize = gray->w * gray->h * gray->d / 8; | ||||
|       float maxScaleMemory = float (availableMemory) / actualSize; | ||||
|       scale = std::min (scale, maxScaleMemory); | ||||
|     } | ||||
|     scaled = pixScale (gray, scale, scale); | ||||
|     if (scaled == NULL) { | ||||
|       scaled = gray; | ||||
|     } | ||||
|   } | ||||
|   if (scaled != gray) { | ||||
|     pixDestroy (&gray); | ||||
|   } | ||||
|   return scaled; | ||||
| } | ||||
| 
 | ||||
| void cleanupImage (Pix **image) { | ||||
|   pixDestroy (image); | ||||
| } | ||||
| @ -1,18 +0,0 @@ | ||||
| #ifndef IMAGEPROCESSING_H | ||||
| #define IMAGEPROCESSING_H | ||||
| 
 | ||||
| #include <QImage> | ||||
| 
 | ||||
| class Pix; | ||||
| 
 | ||||
| //! Convert QImage to Leptonica's PIX.
 | ||||
| Pix * convertImage (const QImage &image); | ||||
| //! Convert Leptonica's PIX to QImage.
 | ||||
| QImage convertImage (Pix &image); | ||||
| 
 | ||||
| //! Propare image for OCR.
 | ||||
| Pix * prepareImage (const QImage &image, int preferredScale); | ||||
| //! Free allocated resources for image.
 | ||||
| void cleanupImage (Pix **image); | ||||
| 
 | ||||
| #endif // IMAGEPROCESSING_H
 | ||||
							
								
								
									
										240
									
								
								src/languagecodes.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,240 @@ | ||||
| #include "languagecodes.h" | ||||
| 
 | ||||
| #include <QObject> | ||||
| 
 | ||||
| #define S(XXX) QStringLiteral(XXX) | ||||
| #define I(XXX) LanguageId(QStringLiteral(XXX)) | ||||
| #define TR(XXX) QT_TR_NOOP(XXX) | ||||
| const std::unordered_map<LanguageId, LanguageCodes::Bundle> | ||||
|     LanguageCodes::codes_{ | ||||
|         // clang-format off
 | ||||
| //  {I("abk"), {I("abk"), S("ab"), S("abk"), TR("Abkhazian")}},
 | ||||
| //  {I("aar"), {I("aar"), S("aa"), S("aar"), TR("Afar")}},
 | ||||
|   {I("afr"), {I("afr"), S("af"), S("afr"), TR("Afrikaans")}}, | ||||
| //  {I("aka"), {I("aka"), S("ak"), S("aka"), TR("Akan")}},
 | ||||
|   {I("alb"), {I("alb"), S("sq"), S("alb"), TR("Albanian")}}, | ||||
| //  {I("amh"), {I("amh"), S("am"), S("amh"), TR("Amharic")}},
 | ||||
|   {I("ara"), {I("ara"), S("ar"), S("ara"), TR("Arabic")}}, | ||||
| //  {I("arg"), {I("arg"), S("an"), S("arg"), TR("Aragonese")}},
 | ||||
|   {I("arm"), {I("arm"), S("hy"), S("arm"), TR("Armenian")}}, | ||||
| //  {I("asm"), {I("asm"), S("as"), S("asm"), TR("Assamese")}},
 | ||||
| //  {I("ava"), {I("ava"), S("av"), S("ava"), TR("Avaric")}},
 | ||||
| //  {I("ave"), {I("ave"), S("ae"), S("ave"), TR("Avestan")}},
 | ||||
| //  {I("aym"), {I("aym"), S("ay"), S("aym"), TR("Aymara")}},
 | ||||
|   {I("aze"), {I("aze"), S("az"), S("aze"), TR("Azerbaijani")}}, | ||||
| //  {I("bam"), {I("bam"), S("bm"), S("bam"), TR("Bambara")}},
 | ||||
| //  {I("bak"), {I("bak"), S("ba"), S("bak"), TR("Bashkir")}},
 | ||||
|   {I("baq"), {I("baq"), S("eu"), S("baq"), TR("Basque")}}, | ||||
|   {I("bel"), {I("bel"), S("be"), S("bel"), TR("Belarusian")}}, | ||||
| //  {I("ben"), {I("ben"), S("bn"), S("ben"), TR("Bengali")}},
 | ||||
| //  {I("bih"), {I("bih"), S("bh"), S("bih"), TR("Bihari languages")}},
 | ||||
| //  {I("bis"), {I("bis"), S("bi"), S("bis"), TR("Bislama")}},
 | ||||
| //  {I("bos"), {I("bos"), S("bs"), S("bos"), TR("Bosnian")}},
 | ||||
| //  {I("bre"), {I("bre"), S("br"), S("bre"), TR("Breton")}},
 | ||||
|   {I("bul"), {I("bul"), S("bg"), S("bul"), TR("Bulgarian")}}, | ||||
| //  {I("bur"), {I("bur"), S("my"), S("bur"), TR("Burmese")}},
 | ||||
|   {I("cat"), {I("cat"), S("ca"), S("cat"), TR("Catalan, Valencian")}}, | ||||
| //  {I("cha"), {I("cha"), S("ch"), S("cha"), TR("Chamorro")}},
 | ||||
| //  {I("che"), {I("che"), S("ce"), S("che"), TR("Chechen")}},
 | ||||
| //  {I("nya"), {I("nya"), S("ny"), S("nya"), TR("Chichewa, Chewa, Nyanja")}},
 | ||||
| //  {I("chi"), {I("chi"), S("zh"), S("chi"), TR("Chinese")}},
 | ||||
| //  {I("chv"), {I("chv"), S("cv"), S("chv"), TR("Chuvash")}},
 | ||||
| //  {I("cor"), {I("cor"), S("kw"), S("cor"), TR("Cornish")}},
 | ||||
| //  {I("cos"), {I("cos"), S("co"), S("cos"), TR("Corsican")}},
 | ||||
| //  {I("cre"), {I("cre"), S("cr"), S("cre"), TR("Cree")}},
 | ||||
|   {I("hrv"), {I("hrv"), S("hr"), S("hrv"), TR("Croatian")}}, | ||||
|   {I("cze"), {I("cze"), S("cs"), S("cze"), TR("Czech")}}, | ||||
|   {I("dan"), {I("dan"), S("da"), S("dan"), TR("Danish")}}, | ||||
| //  {I("div"), {I("div"), S("dv"), S("div"), TR("Divehi, Dhivehi, Maldivian")}},
 | ||||
| //  {I("dut"), {I("dut"), S("nl"), S("dut"), TR("Dutch, Flemish")}},
 | ||||
| //  {I("dzo"), {I("dzo"), S("dz"), S("dzo"), TR("Dzongkha")}},
 | ||||
|   {I("eng"), {I("eng"), S("en"), S("eng"), TR("English")}}, | ||||
| //  {I("epo"), {I("epo"), S("eo"), S("epo"), TR("Esperanto")}},
 | ||||
|   {I("est"), {I("est"), S("et"), S("est"), TR("Estonian")}}, | ||||
| //  {I("ewe"), {I("ewe"), S("ee"), S("ewe"), TR("Ewe")}},
 | ||||
| //  {I("fao"), {I("fao"), S("fo"), S("fao"), TR("Faroese")}},
 | ||||
| //  {I("fij"), {I("fij"), S("fj"), S("fij"), TR("Fijian")}},
 | ||||
|   {I("fin"), {I("fin"), S("fi"), S("fin"), TR("Finnish")}}, | ||||
|   {I("fre"), {I("fre"), S("fr"), S("fre"), TR("French")}}, | ||||
| //  {I("ful"), {I("ful"), S("ff"), S("ful"), TR("Fulah")}},
 | ||||
|   {I("glg"), {I("glg"), S("gl"), S("glg"), TR("Galician")}}, | ||||
|   {I("geo"), {I("geo"), S("ka"), S("geo"), TR("Georgian")}}, | ||||
|   {I("ger"), {I("ger"), S("de"), S("ger"), TR("German")}}, | ||||
|   {I("gre"), {I("gre"), S("el"), S("gre"), TR("Greek")}}, | ||||
| //  {I("grn"), {I("grn"), S("gn"), S("grn"), TR("Guarani")}},
 | ||||
| //  {I("guj"), {I("guj"), S("gu"), S("guj"), TR("Gujarati")}},
 | ||||
|   {I("hat"), {I("hat"), S("ht"), S("hat"), TR("Haitian, Haitian Creole")}}, | ||||
| //  {I("hau"), {I("hau"), S("ha"), S("hau"), TR("Hausa")}},
 | ||||
|   {I("heb"), {I("heb"), S("he"), S("heb"), TR("Hebrew")}}, | ||||
| //  {I("her"), {I("her"), S("hz"), S("her"), TR("Herero")}},
 | ||||
|   {I("hin"), {I("hin"), S("hi"), S("hin"), TR("Hindi")}}, | ||||
| //  {I("hmo"), {I("hmo"), S("ho"), S("hmo"), TR("Hiri Motu")}},
 | ||||
|   {I("hun"), {I("hun"), S("hu"), S("hun"), TR("Hungarian")}}, | ||||
| //  {I("ina"), {I("ina"), S("ia"), S("ina"), TR("Interlingua (International Auxiliary Language Association)")}},
 | ||||
|   {I("ind"), {I("ind"), S("id"), S("ind"), TR("Indonesian")}}, | ||||
| //  {I("ile"), {I("ile"), S("ie"), S("ile"), TR("Interlingue, Occidental")}},
 | ||||
|   {I("gle"), {I("gle"), S("ga"), S("gle"), TR("Irish")}}, | ||||
| //  {I("ibo"), {I("ibo"), S("ig"), S("ibo"), TR("Igbo")}},
 | ||||
| //  {I("ipk"), {I("ipk"), S("ik"), S("ipk"), TR("Inupiaq")}},
 | ||||
| //  {I("ido"), {I("ido"), S("io"), S("ido"), TR("Ido")}},
 | ||||
|   {I("ice"), {I("ice"), S("is"), S("ice"), TR("Icelandic")}}, | ||||
|   {I("ita"), {I("ita"), S("it"), S("ita"), TR("Italian")}}, | ||||
| //  {I("iku"), {I("iku"), S("iu"), S("iku"), TR("Inuktitut")}},
 | ||||
|   {I("jpn"), {I("jpn"), S("ja"), S("jpn"), TR("Japanese")}}, | ||||
| //  {I("jav"), {I("jav"), S("jv"), S("jav"), TR("Javanese")}},
 | ||||
| //  {I("kal"), {I("kal"), S("kl"), S("kal"), TR("Kalaallisut, Greenlandic")}},
 | ||||
| //  {I("kan"), {I("kan"), S("kn"), S("kan"), TR("Kannada")}},
 | ||||
| //  {I("kau"), {I("kau"), S("kr"), S("kau"), TR("Kanuri")}},
 | ||||
| //  {I("kas"), {I("kas"), S("ks"), S("kas"), TR("Kashmiri")}},
 | ||||
| //  {I("kaz"), {I("kaz"), S("kk"), S("kaz"), TR("Kazakh")}},
 | ||||
| //  {I("khm"), {I("khm"), S("km"), S("khm"), TR("Central Khmer")}},
 | ||||
| //  {I("kik"), {I("kik"), S("ki"), S("kik"), TR("Kikuyu, Gikuyu")}},
 | ||||
| //  {I("kin"), {I("kin"), S("rw"), S("kin"), TR("Kinyarwanda")}},
 | ||||
| //  {I("kir"), {I("kir"), S("ky"), S("kir"), TR("Kirghiz, Kyrgyz")}},
 | ||||
| //  {I("kom"), {I("kom"), S("kv"), S("kom"), TR("Komi")}},
 | ||||
| //  {I("kon"), {I("kon"), S("kg"), S("kon"), TR("Kongo")}},
 | ||||
|   {I("kor"), {I("kor"), S("ko"), S("kor"), TR("Korean")}}, | ||||
| //  {I("kur"), {I("kur"), S("ku"), S("kur"), TR("Kurdish")}},
 | ||||
| //  {I("kua"), {I("kua"), S("kj"), S("kua"), TR("Kuanyama, Kwanyama")}},
 | ||||
| //  {I("lat"), {I("lat"), S("la"), S("lat"), TR("Latin")}},
 | ||||
| //  {I("ltz"), {I("ltz"), S("lb"), S("ltz"), TR("Luxembourgish, Letzeburgesch")}},
 | ||||
| //  {I("lug"), {I("lug"), S("lg"), S("lug"), TR("Ganda")}},
 | ||||
| //  {I("lim"), {I("lim"), S("li"), S("lim"), TR("Limburgan, Limburger, Limburgish")}},
 | ||||
| //  {I("lin"), {I("lin"), S("ln"), S("lin"), TR("Lingala")}},
 | ||||
| //  {I("lao"), {I("lao"), S("lo"), S("lao"), TR("Lao")}},
 | ||||
|   {I("lit"), {I("lit"), S("lt"), S("lit"), TR("Lithuanian")}}, | ||||
| //  {I("lub"), {I("lub"), S("lu"), S("lub"), TR("Luba-Katanga")}},
 | ||||
|   {I("lav"), {I("lav"), S("lv"), S("lav"), TR("Latvian")}}, | ||||
| //  {I("glv"), {I("glv"), S("gv"), S("glv"), TR("Manx")}},
 | ||||
|   {I("mac"), {I("mac"), S("mk"), S("mac"), TR("Macedonian")}}, | ||||
| //  {I("mlg"), {I("mlg"), S("mg"), S("mlg"), TR("Malagasy")}},
 | ||||
|   {I("may"), {I("may"), S("ms"), S("may"), TR("Malay")}}, | ||||
| //  {I("mal"), {I("mal"), S("ml"), S("mal"), TR("Malayalam")}},
 | ||||
|   {I("mlt"), {I("mlt"), S("mt"), S("mlt"), TR("Maltese")}}, | ||||
| //  {I("mao"), {I("mao"), S("mi"), S("mao"), TR("Maori")}},
 | ||||
| //  {I("mar"), {I("mar"), S("mr"), S("mar"), TR("Marathi")}},
 | ||||
| //  {I("mah"), {I("mah"), S("mh"), S("mah"), TR("Marshallese")}},
 | ||||
| //  {I("mon"), {I("mon"), S("mn"), S("mon"), TR("Mongolian")}},
 | ||||
| //  {I("nau"), {I("nau"), S("na"), S("nau"), TR("Nauru")}},
 | ||||
| //  {I("nav"), {I("nav"), S("nv"), S("nav"), TR("Navajo, Navaho")}},
 | ||||
| //  {I("nde"), {I("nde"), S("nd"), S("nde"), TR("North Ndebele")}},
 | ||||
| //  {I("nep"), {I("nep"), S("ne"), S("nep"), TR("Nepali")}},
 | ||||
| //  {I("ndo"), {I("ndo"), S("ng"), S("ndo"), TR("Ndonga")}},
 | ||||
| //  {I("nob"), {I("nob"), S("nb"), S("nob"), TR("Norwegian Bokmål")}},
 | ||||
| //  {I("nno"), {I("nno"), S("nn"), S("nno"), TR("Norwegian Nynorsk")}},
 | ||||
|   {I("nor"), {I("nor"), S("no"), S("nor"), TR("Norwegian")}}, | ||||
| //  {I("iii"), {I("iii"), S("ii"), S("iii"), TR("Sichuan Yi, Nuosu")}},
 | ||||
| //  {I("nbl"), {I("nbl"), S("nr"), S("nbl"), TR("South Ndebele")}},
 | ||||
| //  {I("oci"), {I("oci"), S("oc"), S("oci"), TR("Occitan")}},
 | ||||
| //  {I("oji"), {I("oji"), S("oj"), S("oji"), TR("Ojibwa")}},
 | ||||
| //  {I("chu"), {I("chu"), S("cu"), S("chu"), TR("Church Slavic, Old Slavonic, Church Slavonic, Old Bulgarian, Old Church Slavonic")}},
 | ||||
| //  {I("orm"), {I("orm"), S("om"), S("orm"), TR("Oromo")}},
 | ||||
| //  {I("ori"), {I("ori"), S("or"), S("ori"), TR("Oriya")}},
 | ||||
| //  {I("oss"), {I("oss"), S("os"), S("oss"), TR("Ossetian, Ossetic")}},
 | ||||
| //  {I("pan"), {I("pan"), S("pa"), S("pan"), TR("Punjabi, Panjabi")}},
 | ||||
| //  {I("pli"), {I("pli"), S("pi"), S("pli"), TR("Pali")}},
 | ||||
|   {I("per"), {I("per"), S("fa"), S("per"), TR("Persian")}}, | ||||
|   {I("pol"), {I("pol"), S("pl"), S("pol"), TR("Polish")}}, | ||||
| //  {I("pus"), {I("pus"), S("ps"), S("pus"), TR("Pashto, Pushto")}},
 | ||||
|   {I("por"), {I("por"), S("pt"), S("por"), TR("Portuguese")}}, | ||||
| //  {I("que"), {I("que"), S("qu"), S("que"), TR("Quechua")}},
 | ||||
| //  {I("roh"), {I("roh"), S("rm"), S("roh"), TR("Romansh")}},
 | ||||
| //  {I("run"), {I("run"), S("rn"), S("run"), TR("Rundi")}},
 | ||||
|   {I("rum"), {I("rum"), S("ro"), S("rum"), TR("Romanian, Moldavian, Moldovan")}}, | ||||
|   {I("rus"), {I("rus"), S("ru"), S("rus"), TR("Russian")}}, | ||||
| //  {I("san"), {I("san"), S("sa"), S("san"), TR("Sanskrit")}},
 | ||||
| //  {I("srd"), {I("srd"), S("sc"), S("srd"), TR("Sardinian")}},
 | ||||
| //  {I("snd"), {I("snd"), S("sd"), S("snd"), TR("Sindhi")}},
 | ||||
| //  {I("sme"), {I("sme"), S("se"), S("sme"), TR("Northern Sami")}},
 | ||||
| //  {I("smo"), {I("smo"), S("sm"), S("smo"), TR("Samoan")}},
 | ||||
| //  {I("sag"), {I("sag"), S("sg"), S("sag"), TR("Sango")}},
 | ||||
|   {I("srp"), {I("srp"), S("sr"), S("srp"), TR("Serbian")}}, | ||||
| //  {I("gla"), {I("gla"), S("gd"), S("gla"), TR("Gaelic, Scottish Gaelic")}},
 | ||||
| //  {I("sna"), {I("sna"), S("sn"), S("sna"), TR("Shona")}},
 | ||||
| //  {I("sin"), {I("sin"), S("si"), S("sin"), TR("Sinhala, Sinhalese")}},
 | ||||
|   {I("slo"), {I("slo"), S("sk"), S("slo"), TR("Slovak")}}, | ||||
|   {I("slv"), {I("slv"), S("sl"), S("slv"), TR("Slovenian")}}, | ||||
| //  {I("som"), {I("som"), S("so"), S("som"), TR("Somali")}},
 | ||||
| //  {I("sot"), {I("sot"), S("st"), S("sot"), TR("Southern Sotho")}},
 | ||||
|   {I("spa"), {I("spa"), S("es"), S("spa"), TR("Spanish, Castilian")}}, | ||||
| //  {I("sun"), {I("sun"), S("su"), S("sun"), TR("Sundanese")}},
 | ||||
|   {I("swa"), {I("swa"), S("sw"), S("swa"), TR("Swahili")}}, | ||||
| //  {I("ssw"), {I("ssw"), S("ss"), S("ssw"), TR("Swati")}},
 | ||||
|   {I("swe"), {I("swe"), S("sv"), S("swe"), TR("Swedish")}}, | ||||
| //  {I("tam"), {I("tam"), S("ta"), S("tam"), TR("Tamil")}},
 | ||||
| //  {I("tel"), {I("tel"), S("te"), S("tel"), TR("Telugu")}},
 | ||||
| //  {I("tgk"), {I("tgk"), S("tg"), S("tgk"), TR("Tajik")}},
 | ||||
|   {I("tha"), {I("tha"), S("th"), S("tha"), TR("Thai")}}, | ||||
| //  {I("tir"), {I("tir"), S("ti"), S("tir"), TR("Tigrinya")}},
 | ||||
| //  {I("tib"), {I("tib"), S("bo"), S("tib"), TR("Tibetan")}},
 | ||||
| //  {I("tuk"), {I("tuk"), S("tk"), S("tuk"), TR("Turkmen")}},
 | ||||
| //  {I("tgl"), {I("tgl"), S("tl"), S("tgl"), TR("Tagalog")}},
 | ||||
| //  {I("tsn"), {I("tsn"), S("tn"), S("tsn"), TR("Tswana")}},
 | ||||
| //  {I("ton"), {I("ton"), S("to"), S("ton"), TR("Tonga (Tonga Islands)")}},
 | ||||
|   {I("tur"), {I("tur"), S("tr"), S("tur"), TR("Turkish")}}, | ||||
| //  {I("tso"), {I("tso"), S("ts"), S("tso"), TR("Tsonga")}},
 | ||||
| //  {I("tat"), {I("tat"), S("tt"), S("tat"), TR("Tatar")}},
 | ||||
| //  {I("twi"), {I("twi"), S("tw"), S("twi"), TR("Twi")}},
 | ||||
| //  {I("tah"), {I("tah"), S("ty"), S("tah"), TR("Tahitian")}},
 | ||||
| //  {I("uig"), {I("uig"), S("ug"), S("uig"), TR("Uighur, Uyghur")}},
 | ||||
|   {I("ukr"), {I("ukr"), S("uk"), S("ukr"), TR("Ukrainian")}}, | ||||
|   {I("urd"), {I("urd"), S("ur"), S("urd"), TR("Urdu")}}, | ||||
| //  {I("uzb"), {I("uzb"), S("uz"), S("uzb"), TR("Uzbek")}},
 | ||||
| //  {I("ven"), {I("ven"), S("ve"), S("ven"), TR("Venda")}},
 | ||||
|   {I("vie"), {I("vie"), S("vi"), S("vie"), TR("Vietnamese")}}, | ||||
| //  {I("vol"), {I("vol"), S("vo"), S("vol"), TR("Volapük")}},
 | ||||
| //  {I("wln"), {I("wln"), S("wa"), S("wln"), TR("Walloon")}},
 | ||||
|   {I("wel"), {I("wel"), S("cy"), S("wel"), TR("Welsh")}}, | ||||
| //  {I("wol"), {I("wol"), S("wo"), S("wol"), TR("Wolof")}},
 | ||||
| //  {I("fry"), {I("fry"), S("fy"), S("fry"), TR("Western Frisian")}},
 | ||||
| //  {I("xho"), {I("xho"), S("xh"), S("xho"), TR("Xhosa")}},
 | ||||
|   {I("yid"), {I("yid"), S("yi"), S("yid"), TR("Yiddish")}}, | ||||
| //  {I("yor"), {I("yor"), S("yo"), S("yor"), TR("Yoruba")}},
 | ||||
| //  {I("zha"), {I("zha"), S("za"), S("zha"), TR("Zhuang, Chuang")}},
 | ||||
| //  {I("zul"), {I("zul"), S("zu"), S("zul"), TR("Zulu")}},
 | ||||
|   // custom
 | ||||
|   {I("chi_sim"), {I("chi_sim"), S("zh-CN"), S("chi_sim"), TR("Chinese (Simplified)")}}, | ||||
|   {I("chi_tra"), {I("chi_tra"), S("zh-TW"), S("chi_tra"), TR("Chinese (Traditional)")}}, | ||||
|         // clang-format on
 | ||||
|     }; | ||||
| #undef I | ||||
| #undef S | ||||
| 
 | ||||
| std::optional<LanguageCodes::Bundle> LanguageCodes::findById( | ||||
|     const LanguageId &id) const | ||||
| { | ||||
|   auto it = codes_.find(id); | ||||
|   if (it != codes_.cend()) | ||||
|     return it->second; | ||||
|   return {}; | ||||
| } | ||||
| 
 | ||||
| std::optional<LanguageCodes::Bundle> LanguageCodes::findByName( | ||||
|     const QString &name) const | ||||
| { | ||||
|   auto it = std::find_if(codes_.cbegin(), codes_.cend(), | ||||
|                          [name](const std::pair<LanguageId, Bundle> &i) { | ||||
|                            return name == i.second.name; | ||||
|                          }); | ||||
|   if (it != codes_.cend()) | ||||
|     return it->second; | ||||
|   return {}; | ||||
| } | ||||
| 
 | ||||
| std::optional<LanguageCodes::Bundle> LanguageCodes::findByTesseract( | ||||
|     const QString &name) const | ||||
| { | ||||
|   auto it = std::find_if(codes_.cbegin(), codes_.cend(), | ||||
|                          [name](const std::pair<LanguageId, Bundle> &i) { | ||||
|                            return name == i.second.tesseract; | ||||
|                          }); | ||||
|   if (it != codes_.cend()) | ||||
|     return it->second; | ||||
|   return {}; | ||||
| } | ||||
| 
 | ||||
| const std::unordered_map<LanguageId, LanguageCodes::Bundle> | ||||
|     &LanguageCodes::all() const | ||||
| { | ||||
|   return codes_; | ||||
| } | ||||
							
								
								
									
										27
									
								
								src/languagecodes.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,27 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <QString> | ||||
| 
 | ||||
| #include <optional> | ||||
| #include <unordered_map> | ||||
| 
 | ||||
| using LanguageId = QString; | ||||
| 
 | ||||
| class LanguageCodes | ||||
| { | ||||
| public: | ||||
|   struct Bundle { | ||||
|     LanguageId id; | ||||
|     QString iso639_1; | ||||
|     QString tesseract; | ||||
|     QString name; | ||||
|   }; | ||||
| 
 | ||||
|   std::optional<Bundle> findById(const LanguageId& id) const; | ||||
|   std::optional<Bundle> findByName(const QString& name) const; | ||||
|   std::optional<Bundle> findByTesseract(const QString& name) const; | ||||
|   const std::unordered_map<LanguageId, Bundle>& all() const; | ||||
| 
 | ||||
| private: | ||||
|   const static std::unordered_map<LanguageId, Bundle> codes_; | ||||
| }; | ||||
| @ -1,301 +0,0 @@ | ||||
| #include <QDir> | ||||
| #include <QSettings> | ||||
| 
 | ||||
| #include "languagehelper.h" | ||||
| #include "settings.h" | ||||
| #include "stassert.h" | ||||
| 
 | ||||
| LanguageHelper::LanguageHelper () { | ||||
|   init (); | ||||
| } | ||||
| 
 | ||||
| QStringList LanguageHelper::availableOcrLanguagesUi () const { | ||||
|   QStringList uiItems; | ||||
|   for (const QString &item: availableOcrLanguages_) { | ||||
|     uiItems << ocrCodeToUi (item); | ||||
|   } | ||||
|   uiItems.sort (); | ||||
|   return uiItems; | ||||
| } | ||||
| 
 | ||||
| const QStringList &LanguageHelper::availableOcrLanguages () const { | ||||
|   return availableOcrLanguages_; | ||||
| } | ||||
| 
 | ||||
| QStringList LanguageHelper::availableOcrLanguages (const QString &path) const { | ||||
|   QDir dir (path); | ||||
|   if (!dir.exists ()) { | ||||
|     return QStringList (); | ||||
|   } | ||||
|   QStringList items; | ||||
|   QStringList files = dir.entryList ({"*.traineddata"}, QDir::Files); | ||||
|   for (const QString &file: files) { | ||||
|     QString lang = file.left (file.indexOf (".")); | ||||
|     items << lang; | ||||
|   } | ||||
|   return items; | ||||
| } | ||||
| 
 | ||||
| QStringList LanguageHelper::availableOcrLanguagesUi (const QString &path) const { | ||||
|   QStringList uiItems, items; | ||||
|   items = availableOcrLanguages (path); | ||||
|   for (const QString &item: items) { | ||||
|     uiItems << ocrCodeToUi (item); | ||||
|   } | ||||
|   uiItems.sort (); | ||||
|   return uiItems; | ||||
| } | ||||
| 
 | ||||
| QStringList LanguageHelper::translateLanguagesUi () const { | ||||
|   QStringList uiItems = translateLanguages_.keys (); | ||||
|   uiItems.sort (); | ||||
|   return uiItems; | ||||
| } | ||||
| 
 | ||||
| QStringList LanguageHelper::translateLanguages () const { | ||||
|   return translateLanguages_.values (); | ||||
| } | ||||
| 
 | ||||
| QString LanguageHelper::translateCodeToUi (const QString &text) const { | ||||
|   return translateLanguages_.key (text, text); | ||||
| } | ||||
| 
 | ||||
| QString LanguageHelper::translateUiToCode (const QString &text) const { | ||||
|   return translateLanguages_.value (text, text); | ||||
| } | ||||
| 
 | ||||
| QString LanguageHelper::ocrCodeToUi (const QString &text) const { | ||||
|   return ocrLanguages_.key (text, text); | ||||
| } | ||||
| 
 | ||||
| QString LanguageHelper::ocrUiToCode (const QString &text) const { | ||||
|   return ocrLanguages_.value (text, text); | ||||
| } | ||||
| 
 | ||||
| QString LanguageHelper::ocrToTranslateCodes (const QString &text) const { | ||||
|   QString ocrUi = ocrCodeToUi (text); | ||||
|   QString translateCode = translateUiToCode (ocrUi); | ||||
|   if (translateCode == ocrUi) { | ||||
|     translateCode = "auto"; | ||||
|   } | ||||
|   return translateCode; | ||||
| } | ||||
| 
 | ||||
| QString LanguageHelper::translateToOcrCodes (const QString &text) const { | ||||
|   QString translateUi = translateCodeToUi (text); | ||||
|   QString ocrCode = ocrUiToCode (translateUi); | ||||
|   if (translateUi == ocrCode) { | ||||
|     return QString (); | ||||
|   } | ||||
|   return ocrCode; | ||||
| } | ||||
| 
 | ||||
| void LanguageHelper::init () { | ||||
|   initOcrLanguages (); | ||||
|   initTranslateLanguages (); | ||||
|   updateAvailableOcrLanguages (); | ||||
| } | ||||
| 
 | ||||
| void LanguageHelper::updateAvailableOcrLanguages () { | ||||
|   availableOcrLanguages_.clear (); | ||||
|   QSettings settings; | ||||
|   settings.beginGroup (settings_names::recogntionGroup); | ||||
|   QString tessDataPlace = settings.value (settings_names::tessDataPlace, | ||||
|                                           settings_values::tessDataPlace).toString (); | ||||
|   availableOcrLanguages_ = availableOcrLanguages (tessDataPlace); | ||||
| } | ||||
| 
 | ||||
| void LanguageHelper::updateMenu (QMenu *menu, const QStringList &languages, int groupSize) const { | ||||
|   ST_ASSERT (menu != NULL); | ||||
|   menu->clear (); | ||||
|   if (languages.isEmpty ()) { | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   if (languages.size () <= groupSize) { | ||||
|     for (const QString &language: languages) { | ||||
|       menu->addAction (language); | ||||
|     } | ||||
|   } | ||||
|   else { | ||||
|     int subIndex = groupSize; | ||||
|     QMenu *subMenu = NULL; | ||||
|     QString prevLetter; | ||||
|     for (const QString &language: languages) { | ||||
|       QString curLetter = language.left (1); | ||||
|       if (++subIndex >= groupSize && prevLetter != curLetter) { | ||||
|         if (subMenu != NULL) { | ||||
|           subMenu->setTitle (subMenu->title () + " - " + prevLetter); | ||||
|         } | ||||
|         subMenu = menu->addMenu (curLetter); | ||||
|         subIndex = 0; | ||||
|       } | ||||
|       prevLetter = curLetter; | ||||
|       subMenu->addAction (language); | ||||
|     } | ||||
|     subMenu->setTitle (subMenu->title () + " - " + prevLetter); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void LanguageHelper::initTranslateLanguages () { | ||||
|   translateLanguages_.insert (QObject::tr ("Afrikaans"),"af"); | ||||
|   translateLanguages_.insert (QObject::tr ("Albanian"),"sq"); | ||||
|   translateLanguages_.insert (QObject::tr ("Arabic"),"ar"); | ||||
|   translateLanguages_.insert (QObject::tr ("Armenian"),"hy"); | ||||
|   translateLanguages_.insert (QObject::tr ("Azerbaijani"),"az"); | ||||
|   translateLanguages_.insert (QObject::tr ("Basque"),"eu"); | ||||
|   translateLanguages_.insert (QObject::tr ("Belarusian"),"be"); | ||||
|   translateLanguages_.insert (QObject::tr ("Bulgarian"),"bg"); | ||||
|   translateLanguages_.insert (QObject::tr ("Catalan"),"ca"); | ||||
|   translateLanguages_.insert (QObject::tr ("Chinese (Simplified)"),"zh-CN"); | ||||
|   translateLanguages_.insert (QObject::tr ("Chinese (Traditional)"),"zh-TW"); | ||||
|   translateLanguages_.insert (QObject::tr ("Croatian"),"hr"); | ||||
|   translateLanguages_.insert (QObject::tr ("Czech"),"cs"); | ||||
|   translateLanguages_.insert (QObject::tr ("Danish"),"da"); | ||||
|   translateLanguages_.insert (QObject::tr ("Dutch"),"nl"); | ||||
|   translateLanguages_.insert (QObject::tr ("English"),"en"); | ||||
|   translateLanguages_.insert (QObject::tr ("Estonian"),"et"); | ||||
|   translateLanguages_.insert (QObject::tr ("Filipino"),"tl"); | ||||
|   translateLanguages_.insert (QObject::tr ("Finnish"),"fi"); | ||||
|   translateLanguages_.insert (QObject::tr ("French"),"fr"); | ||||
|   translateLanguages_.insert (QObject::tr ("Galician"),"gl"); | ||||
|   translateLanguages_.insert (QObject::tr ("Georgian"),"ka"); | ||||
|   translateLanguages_.insert (QObject::tr ("German"),"de"); | ||||
|   translateLanguages_.insert (QObject::tr ("Greek"),"el"); | ||||
|   translateLanguages_.insert (QObject::tr ("Haitian Creole"),"ht"); | ||||
|   translateLanguages_.insert (QObject::tr ("Hebrew"),"iw"); | ||||
|   translateLanguages_.insert (QObject::tr ("Hindi"),"hi"); | ||||
|   translateLanguages_.insert (QObject::tr ("Hungarian"),"hu"); | ||||
|   translateLanguages_.insert (QObject::tr ("Icelandic"),"is"); | ||||
|   translateLanguages_.insert (QObject::tr ("Indonesian"),"id"); | ||||
|   translateLanguages_.insert (QObject::tr ("Irish"),"ga"); | ||||
|   translateLanguages_.insert (QObject::tr ("Italian"),"it"); | ||||
|   translateLanguages_.insert (QObject::tr ("Japanese"),"ja"); | ||||
|   translateLanguages_.insert (QObject::tr ("Korean"),"ko"); | ||||
|   translateLanguages_.insert (QObject::tr ("Latvian"),"lv"); | ||||
|   translateLanguages_.insert (QObject::tr ("Lithuanian"),"lt"); | ||||
|   translateLanguages_.insert (QObject::tr ("Macedonian"),"mk"); | ||||
|   translateLanguages_.insert (QObject::tr ("Malay"),"ms"); | ||||
|   translateLanguages_.insert (QObject::tr ("Maltese"),"mt"); | ||||
|   translateLanguages_.insert (QObject::tr ("Norwegian"),"no"); | ||||
|   translateLanguages_.insert (QObject::tr ("Persian"),"fa"); | ||||
|   translateLanguages_.insert (QObject::tr ("Polish"),"pl"); | ||||
|   translateLanguages_.insert (QObject::tr ("Portuguese"),"pt"); | ||||
|   translateLanguages_.insert (QObject::tr ("Romanian"),"ro"); | ||||
|   translateLanguages_.insert (QObject::tr ("Russian"),"ru"); | ||||
|   translateLanguages_.insert (QObject::tr ("Serbian"),"sr"); | ||||
|   translateLanguages_.insert (QObject::tr ("Slovak"),"sk"); | ||||
|   translateLanguages_.insert (QObject::tr ("Slovenian"),"sl"); | ||||
|   translateLanguages_.insert (QObject::tr ("Spanish"),"es"); | ||||
|   translateLanguages_.insert (QObject::tr ("Swahili"),"sw"); | ||||
|   translateLanguages_.insert (QObject::tr ("Swedish"),"sv"); | ||||
|   translateLanguages_.insert (QObject::tr ("Thai"),"th"); | ||||
|   translateLanguages_.insert (QObject::tr ("Turkish"),"tr"); | ||||
|   translateLanguages_.insert (QObject::tr ("Ukrainian"),"uk"); | ||||
|   translateLanguages_.insert (QObject::tr ("Urdu"),"ur"); | ||||
|   translateLanguages_.insert (QObject::tr ("Vietnamese"),"vi"); | ||||
|   translateLanguages_.insert (QObject::tr ("Welsh"),"cy"); | ||||
|   translateLanguages_.insert (QObject::tr ("Yiddish"),"yi"); | ||||
| } | ||||
| 
 | ||||
| void LanguageHelper::initOcrLanguages () { | ||||
|   ocrLanguages_.insert (QObject::tr ("Ancient Greek"),"grc"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Esperanto alternative"),"epo_alt"); | ||||
|   ocrLanguages_.insert (QObject::tr ("English"),"eng"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Ukrainian"),"ukr"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Turkish"),"tur"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Thai"),"tha"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Tagalog"),"tgl"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Telugu"),"tel"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Tamil"),"tam"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Swedish"),"swe"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Swahili"),"swa"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Serbian"),"srp"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Albanian"),"sqi"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Spanish"),"spa"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Slovenian"),"slv"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Slovakian"),"slk"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Romanian"),"ron"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Portuguese"),"por"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Polish"),"pol"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Norwegian"),"nor"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Dutch"),"nld"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Malay"),"msa"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Maltese"),"mlt"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Macedonian"),"mkd"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Malayalam"),"mal"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Lithuanian"),"lit"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Latvian"),"lav"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Korean"),"kor"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Kannada"),"kan"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Italian"),"ita"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Icelandic"),"isl"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Indonesian"),"ind"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Cherokee"),"chr"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Hungarian"),"hun"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Croatian"),"hrv"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Hindi"),"hin"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Hebrew"),"heb"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Galician"),"glg"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Middle French (ca. 1400-1600)"),"frm"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Frankish"),"frk"); | ||||
|   ocrLanguages_.insert (QObject::tr ("French"),"fra"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Finnish"),"fin"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Basque"),"eus"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Estonian"),"est"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Math / equation"),"equ"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Esperanto"),"epo"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Middle English (1100-1500)"),"enm"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Greek"),"ell"); | ||||
|   ocrLanguages_.insert (QObject::tr ("German"),"deu"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Danish"),"dan"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Czech"),"ces"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Catalan"),"cat"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Bulgarian"),"bul"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Bengali"),"ben"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Belarusian"),"bel"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Azerbaijani"),"aze"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Arabic"),"ara"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Afrikaans"),"afr"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Japanese"),"jpn"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Chinese (Simplified)"),"chi_sim"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Chinese (Traditional)"),"chi_tra"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Russian"),"rus"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Vietnamese"),"vie"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Amharic"),"amh"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Assamese"),"asm"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Tibetan"),"bod"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Bosnian"),"bos"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Cebuano"),"ceb"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Welsh"),"cym"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Dzongkha"),"dzo"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Persian"),"fas"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Irish"),"gle"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Gujarati"),"guj"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Haitian"),"hat"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Inuktitut"),"iku"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Javanese"),"jav"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Georgian"),"kat"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Kazakh"),"kaz"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Khmer"),"khm"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Kirghiz"),"kir"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Kurdish"),"kur"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Lao"),"lao"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Latin"),"lat"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Marathi"),"mar"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Burmese"),"mya"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Nepali"),"nep"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Oriya"),"ori"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Panjabi"),"pan"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Pushto"),"pus"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Sanskrit"),"san"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Sinhala"),"sin"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Syriac"),"syr"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Tajik"),"tgk"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Tigrinya"),"tir"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Uighur"),"uig"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Urdu"),"urd"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Uzbek"),"uzb"); | ||||
|   ocrLanguages_.insert (QObject::tr ("Yiddish"),"yid"); | ||||
| 
 | ||||
| } | ||||
| @ -1,43 +0,0 @@ | ||||
| #ifndef LANGUAGEHELPER_H | ||||
| #define LANGUAGEHELPER_H | ||||
| 
 | ||||
| #include <QMap> | ||||
| #include <QStringList> | ||||
| #include <QMenu> | ||||
| 
 | ||||
| class LanguageHelper { | ||||
|   public: | ||||
|     LanguageHelper (); | ||||
| 
 | ||||
|     QStringList availableOcrLanguagesUi () const; | ||||
|     const QStringList &availableOcrLanguages () const; | ||||
|     QStringList availableOcrLanguagesUi (const QString &path) const; | ||||
|     QStringList translateLanguagesUi () const; | ||||
|     QStringList translateLanguages () const; | ||||
| 
 | ||||
|     QString translateCodeToUi (const QString &text) const; | ||||
|     QString translateUiToCode (const QString &text) const; | ||||
|     QString ocrCodeToUi (const QString &text) const; | ||||
|     QString ocrUiToCode (const QString &text) const; | ||||
|     QString ocrToTranslateCodes (const QString &text) const; | ||||
|     QString translateToOcrCodes (const QString &text) const; | ||||
| 
 | ||||
|     void updateAvailableOcrLanguages (); | ||||
| 
 | ||||
|     //! Update languages menu. Group items into submenus if needed.
 | ||||
|     void updateMenu (QMenu *menu, const QStringList &languages, int groupSize = 10) const; | ||||
| 
 | ||||
|   private: | ||||
|     QStringList availableOcrLanguages (const QString &path) const; | ||||
|     void init (); | ||||
|     void initTranslateLanguages (); | ||||
|     void initOcrLanguages (); | ||||
| 
 | ||||
|   private: | ||||
|     QStringList availableOcrLanguages_; | ||||
|     QMap<QString, QString> translateLanguages_; | ||||
|     QMap<QString, QString> ocrLanguages_; | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| #endif // LANGUAGEHELPER_H
 | ||||
							
								
								
									
										55
									
								
								src/main.cpp
									
									
									
									
									
								
							
							
						
						| @ -1,35 +1,48 @@ | ||||
| #ifdef Q_OS_LINUX | ||||
| #  include <locale.h> | ||||
| #include <locale.h> | ||||
| #endif | ||||
| 
 | ||||
| #include "apptranslator.h" | ||||
| #include "manager.h" | ||||
| #include "singleapplication.h" | ||||
| 
 | ||||
| #include <QApplication> | ||||
| #include <QTranslator> | ||||
| #include <QCommandLineParser> | ||||
| 
 | ||||
| #include <qtsingleapplication.h> | ||||
| #define STR2(XXX) #XXX | ||||
| #define STR(XXX) STR2(XXX) | ||||
| 
 | ||||
| #include <manager.h> | ||||
| #include <settings.h> | ||||
| int main(int argc, char *argv[]) | ||||
| { | ||||
|   QApplication a(argc, argv); | ||||
|   a.setApplicationName("ScreenTranslator"); | ||||
|   a.setOrganizationName("Gres"); | ||||
|   a.setApplicationVersion(STR(VERSION)); | ||||
| 
 | ||||
| int main (int argc, char *argv[]) { | ||||
|   QtSingleApplication a (argc, argv); | ||||
|   if (a.sendMessage (QString ())) { | ||||
|     return 0; | ||||
|   a.setQuitOnLastWindowClosed(false); | ||||
| 
 | ||||
|   { | ||||
|     AppTranslator appTranslator({"screentranslator"}); | ||||
|     appTranslator.retranslate(); | ||||
|   } | ||||
| #ifdef Q_OS_LINUX | ||||
|   setlocale (LC_NUMERIC, "C"); | ||||
| #endif | ||||
|   a.setQuitOnLastWindowClosed (false); | ||||
|   a.setApplicationName (settings_values::appName); | ||||
|   a.setOrganizationName (settings_values::companyName); | ||||
| 
 | ||||
|   QTranslator translator; | ||||
|   // Set default to english.
 | ||||
|   if (translator.load (QLocale::system (), "translation", "_", ":/translations") || | ||||
|       translator.load (":/translations/translation_en")) { | ||||
|     a.installTranslator (&translator); | ||||
|   { | ||||
|     QCommandLineParser parser; | ||||
|     parser.setApplicationDescription(QObject::tr("OCR and translation tool")); | ||||
|     parser.addHelpOption(); | ||||
|     parser.addVersionOption(); | ||||
| 
 | ||||
|     parser.process(a); | ||||
|   } | ||||
| 
 | ||||
|   SingleApplication guard; | ||||
|   if (!guard.isValid()) | ||||
|     return 1; | ||||
| 
 | ||||
|   // tesseract recomments
 | ||||
|   setlocale(LC_NUMERIC, "C"); | ||||
| 
 | ||||
|   Manager manager; | ||||
| 
 | ||||
|   return a.exec (); | ||||
|   return a.exec(); | ||||
| } | ||||
|  | ||||
							
								
								
									
										608
									
								
								src/manager.cpp
									
									
									
									
									
								
							
							
						
						| @ -1,463 +1,209 @@ | ||||
| #include "manager.h" | ||||
| #include "capturer.h" | ||||
| #include "corrector.h" | ||||
| #include "debug.h" | ||||
| #include "recognizer.h" | ||||
| #include "representer.h" | ||||
| #include "settingseditor.h" | ||||
| #include "task.h" | ||||
| #include "translator.h" | ||||
| #include "trayicon.h" | ||||
| 
 | ||||
| #include <QDebug> | ||||
| #include <QMenu> | ||||
| #include <QApplication> | ||||
| #include <QDesktopWidget> | ||||
| #include <QScreen> | ||||
| #include <QDesktopWidget> | ||||
| #include <QThread> | ||||
| #include <QSettings> | ||||
| #include <QClipboard> | ||||
| #include <QMessageBox> | ||||
| #include <QInputDialog> | ||||
| #include <QNetworkProxy> | ||||
| 
 | ||||
| #include "settings.h" | ||||
| #include "settingseditor.h" | ||||
| #include "selectiondialog.h" | ||||
| #include "globalactionhelper.h" | ||||
| #include "recognizer.h" | ||||
| #include "webtranslator.h" | ||||
| #include "resultdialog.h" | ||||
| #include "languagehelper.h" | ||||
| #include "stassert.h" | ||||
| #include "utils.h" | ||||
| #include "updater.h" | ||||
| Manager::Manager() | ||||
| { | ||||
|   tray_ = std::make_unique<TrayIcon>(*this); | ||||
|   capturer_ = std::make_unique<Capturer>(*this); | ||||
|   recognizer_ = std::make_unique<Recognizer>(*this); | ||||
|   translator_ = std::make_unique<Translator>(*this); | ||||
|   corrector_ = std::make_unique<Corrector>(*this); | ||||
|   representer_ = std::make_unique<Representer>(*this, *tray_); | ||||
| 
 | ||||
| Manager::Manager (QObject *parent) : | ||||
|   QObject (parent), | ||||
|   trayIcon_ (new QSystemTrayIcon (this)), | ||||
|   dictionary_ (new LanguageHelper), | ||||
|   resultDialog_ (new ResultDialog (*dictionary_)), | ||||
|   updater_ (new Updater (this)), updateTimer_ (new QTimer (this)), | ||||
|   captureAction_ (NULL), repeatCaptureAction_ (NULL), | ||||
|   repeatAction_ (NULL), clipboardAction_ (NULL), | ||||
|   useResultDialog_ (true), doTranslation_ (true), itemProcessingCount_ (0) { | ||||
|   updateNormalIcon (); | ||||
|   GlobalActionHelper::init (); | ||||
|   qRegisterMetaType<ProcessingItem>(); | ||||
|   qRegisterMetaType<TaskPtr>(); | ||||
| 
 | ||||
|   // Recognizer
 | ||||
|   Recognizer *recognizer = new Recognizer; | ||||
|   connect (this, SIGNAL (requestRecognize (ProcessingItem)), | ||||
|            recognizer, SLOT (recognize (ProcessingItem))); | ||||
|   connect (recognizer, SIGNAL (recognized (ProcessingItem)), | ||||
|            this, SIGNAL (requestTranslate (ProcessingItem))); | ||||
|   connect (recognizer, SIGNAL (error (QString)), | ||||
|            SLOT (showError (QString))); | ||||
|   connect (this, SIGNAL (settingsEdited ()), | ||||
|            recognizer, SLOT (applySettings ())); | ||||
|   QThread *recognizerThread = new QThread (this); | ||||
|   threads_ << recognizerThread; | ||||
|   recognizer->moveToThread (recognizerThread); | ||||
|   recognizerThread->start (); | ||||
|   connect (qApp, SIGNAL (aboutToQuit ()), recognizerThread, SLOT (quit ())); | ||||
| 
 | ||||
| 
 | ||||
|   // Translator
 | ||||
|   WebTranslator *translator = new WebTranslator; | ||||
|   connect (this, SIGNAL (requestTranslate (ProcessingItem)), | ||||
|            translator, SLOT (translate (ProcessingItem))); | ||||
|   connect (translator, SIGNAL (translated (ProcessingItem)), | ||||
|            SLOT (showResult (ProcessingItem))); | ||||
|   connect (translator, SIGNAL (error (QString)), | ||||
|            SLOT (showError (QString))); | ||||
|   connect (this, SIGNAL (settingsEdited ()), | ||||
|            translator, SLOT (applySettings ())); | ||||
| 
 | ||||
|   connect (this, SIGNAL (settingsEdited ()), this, SLOT (applySettings ())); | ||||
| 
 | ||||
|   connect (updater_, SIGNAL (updated ()), SIGNAL (settingsEdited ())); | ||||
|   connect (updater_, SIGNAL (error (QString)), SLOT (showError (QString))); | ||||
|   updateTimer_->setSingleShot (true); | ||||
|   connect (updateTimer_, SIGNAL (timeout ()), SLOT (checkForUpdates ())); | ||||
| 
 | ||||
|   resultDialog_->setWindowIcon (trayIcon_->icon ()); | ||||
|   connect (this, SIGNAL (settingsEdited ()), resultDialog_, SLOT (applySettings ())); | ||||
|   connect (resultDialog_, SIGNAL (requestRecognize (ProcessingItem)), | ||||
|            this, SIGNAL (requestRecognize (ProcessingItem))); | ||||
|   connect (resultDialog_, SIGNAL (requestTranslate (ProcessingItem)), | ||||
|            this, SIGNAL (requestTranslate (ProcessingItem))); | ||||
|   connect (resultDialog_, SIGNAL (requestClipboard ()), SLOT (copyLastToClipboard ())); | ||||
|   connect (resultDialog_, SIGNAL (requestImageClipboard ()), | ||||
|            SLOT (copyLastImageToClipboard ())); | ||||
|   connect (resultDialog_, SIGNAL (requestEdition (ProcessingItem)), | ||||
|            this, SLOT (editRecognized (ProcessingItem))); | ||||
| 
 | ||||
|   connect (trayIcon_, SIGNAL (activated (QSystemTrayIcon::ActivationReason)), | ||||
|            SLOT (processTrayAction (QSystemTrayIcon::ActivationReason))); | ||||
| 
 | ||||
|   trayIcon_->setContextMenu (trayContextMenu ()); | ||||
|   updateActionsState (); | ||||
|   trayIcon_->show (); | ||||
| 
 | ||||
|   applySettings (); | ||||
|   Settings settings; | ||||
|   settings.load(); | ||||
|   updateSettings(settings); | ||||
| } | ||||
| 
 | ||||
| QMenu * Manager::trayContextMenu () { | ||||
|   QMenu *menu = new QMenu (); | ||||
|   captureAction_ = menu->addAction (tr ("Захват"), this, SLOT (capture ())); | ||||
|   repeatCaptureAction_ = menu->addAction (tr ("Повторить захват"), | ||||
|                                           this, SLOT (repeatCapture ())); | ||||
|   QMenu *translateMenu = menu->addMenu (tr ("Результат")); | ||||
|   repeatAction_ = translateMenu->addAction (tr ("Показать"), this, | ||||
|                                             SLOT (showLast ())); | ||||
|   clipboardAction_ = translateMenu->addAction (tr ("В буфер"), this, | ||||
|                                                SLOT (copyLastToClipboard ())); | ||||
|   menu->addAction (tr ("Настройки"), this, SLOT (settings ())); | ||||
|   menu->addAction (tr ("О программе"), this, SLOT (about ())); | ||||
|   menu->addAction (tr ("Выход"), this, SLOT (close ())); | ||||
|   return menu; | ||||
| Manager::~Manager() = default; | ||||
| 
 | ||||
| void Manager::updateSettings(const Settings &settings) | ||||
| { | ||||
|   LTRACE() << "updateSettings"; | ||||
|   tray_->updateSettings(settings); | ||||
|   capturer_->updateSettings(settings); | ||||
|   recognizer_->updateSettings(settings); | ||||
|   translator_->updateSettings(settings); | ||||
|   corrector_->updateSettings(settings); | ||||
|   representer_->updateSettings(settings); | ||||
| } | ||||
| 
 | ||||
| void Manager::updateActionsState (bool isEnabled) { | ||||
| #ifdef Q_OS_LINUX | ||||
|   // Avoid unneeded tray blinking (required to update context menu).
 | ||||
|   QList<QAction *> actions; | ||||
|   actions << captureAction_ << repeatCaptureAction_ << repeatAction_ << clipboardAction_; | ||||
|   QList<bool> states; | ||||
|   for (const QAction *action: actions) { | ||||
|     states << action->isEnabled (); | ||||
|   } | ||||
| #endif | ||||
|   captureAction_->setEnabled (isEnabled); | ||||
|   repeatCaptureAction_->setEnabled (isEnabled && !selections_.isEmpty ()); | ||||
|   const ProcessingItem &lastItem = resultDialog_->item (); | ||||
|   repeatAction_->setEnabled (isEnabled && lastItem.isValid ()); | ||||
|   clipboardAction_->setEnabled (isEnabled && lastItem.isValid ()); | ||||
| #ifdef Q_OS_LINUX | ||||
|   for (int i = 0, end = actions.size (); i < end; ++i) { | ||||
|     if (states.at (i) != actions.at (i)->isEnabled ()) { | ||||
|       trayIcon_->hide (); | ||||
|       trayIcon_->show (); | ||||
|       break; | ||||
|     } | ||||
|   } | ||||
| #endif | ||||
| } | ||||
| void Manager::finishTask(const TaskPtr &task) | ||||
| { | ||||
|   SOFT_ASSERT(task, return ); | ||||
|   LTRACE() << "finishTask" << task->captured << task->error; | ||||
| 
 | ||||
| void Manager::applySettings () { | ||||
| #define GET(NAME) settings.value (settings_names::NAME, settings_values::NAME) | ||||
|   QSettings settings; | ||||
|   settings.beginGroup (settings_names::guiGroup); | ||||
|   --activeTaskCount_; | ||||
|   tray_->setActiveTaskCount(activeTaskCount_); | ||||
| 
 | ||||
|   QStringList globalActionsFailed; | ||||
|   Q_CHECK_PTR (captureAction_); | ||||
|   GlobalActionHelper::removeGlobal (captureAction_); | ||||
|   captureAction_->setShortcut (GET (captureHotkey).toString ()); | ||||
|   if (!GlobalActionHelper::makeGlobal (captureAction_)) { | ||||
|     globalActionsFailed << captureAction_->shortcut ().toString (); | ||||
|   } | ||||
|   last_ = task; | ||||
|   tray_->setTaskActionsEnabled(last_->isValid()); | ||||
| 
 | ||||
|   Q_CHECK_PTR (repeatCaptureAction_); | ||||
|   GlobalActionHelper::removeGlobal (repeatCaptureAction_); | ||||
|   repeatCaptureAction_->setShortcut (GET (repeatCaptureHotkey).toString ()); | ||||
|   if (!GlobalActionHelper::makeGlobal (repeatCaptureAction_)) { | ||||
|     globalActionsFailed << repeatCaptureAction_->shortcut ().toString (); | ||||
|   } | ||||
| 
 | ||||
|   Q_CHECK_PTR (repeatAction_); | ||||
|   GlobalActionHelper::removeGlobal (repeatAction_); | ||||
|   repeatAction_->setShortcut (GET (repeatHotkey).toString ()); | ||||
|   if (!GlobalActionHelper::makeGlobal (repeatAction_)) { | ||||
|     globalActionsFailed << repeatAction_->shortcut ().toString (); | ||||
|   } | ||||
| 
 | ||||
|   Q_CHECK_PTR (clipboardAction_); | ||||
|   GlobalActionHelper::removeGlobal (clipboardAction_); | ||||
|   clipboardAction_->setShortcut (GET (clipboardHotkey).toString ()); | ||||
|   if (!GlobalActionHelper::makeGlobal (clipboardAction_)) { | ||||
|     globalActionsFailed << clipboardAction_->shortcut ().toString (); | ||||
|   } | ||||
| 
 | ||||
|   if (!globalActionsFailed.isEmpty ()) { | ||||
|     showError (tr ("Failed to register global shortcuts:\n%1") | ||||
|                .arg (globalActionsFailed.join ("\n"))); | ||||
|   } | ||||
| 
 | ||||
|   // Depends on SettingsEditor button indexes. 1==dialog
 | ||||
|   useResultDialog_ = GET (resultShowType).toBool (); | ||||
| 
 | ||||
|   QNetworkProxy proxy = QNetworkProxy::applicationProxy (); | ||||
|   QList<int> proxyTypes = proxyTypeOrder (); | ||||
|   int proxyTypeIndex = std::min (GET (proxyType).toInt (), proxyTypes.size ()); | ||||
|   proxy.setType (QNetworkProxy::ProxyType (proxyTypes.at (std::max (proxyTypeIndex, 0)))); | ||||
|   proxy.setHostName (GET (proxyHostName).toString ()); | ||||
|   proxy.setPort (GET (proxyPort).toInt ()); | ||||
|   proxy.setUser (GET (proxyUser).toString ()); | ||||
|   if (GET (proxySavePassword).toBool ()) { | ||||
|     proxy.setPassword (encode (GET (proxyPassword).toString ())); | ||||
|   } | ||||
|   QNetworkProxy::setApplicationProxy (proxy); | ||||
| 
 | ||||
|   scheduleUpdate (); | ||||
|   settings.endGroup (); | ||||
| 
 | ||||
|   settings.beginGroup (settings_names::recogntionGroup); | ||||
|   defaultOrcLanguage_ = GET (ocrLanguage).toString (); | ||||
|   settings.endGroup (); | ||||
| 
 | ||||
|   settings.beginGroup (settings_names::translationGroup); | ||||
|   defaultTranslationLanguage_ = GET (translationLanguage).toString (); | ||||
|   doTranslation_ = GET (doTranslation).toBool (); | ||||
|   settings.endGroup (); | ||||
| 
 | ||||
|   Q_CHECK_PTR (dictionary_); | ||||
|   dictionary_->updateAvailableOcrLanguages (); | ||||
| #undef GET | ||||
| } | ||||
| 
 | ||||
| void Manager::scheduleUpdate (bool justChecked) { | ||||
| #define GET(NAME) settings.value (settings_names::NAME, settings_values::NAME) | ||||
|   QSettings settings; | ||||
|   settings.beginGroup (settings_names::guiGroup); | ||||
|   updateTimer_->stop (); | ||||
|   if (justChecked) { | ||||
|     settings.setValue (settings_names::lastUpdateCheck, QDateTime::currentDateTime ()); | ||||
|   } | ||||
|   QDateTime nextUpdateCheck = updater_->nextCheckTime (GET (lastUpdateCheck).toDateTime (), | ||||
|                                                        GET (autoUpdateType).toInt ()); | ||||
|   if (nextUpdateCheck.isValid ()) { | ||||
|     updateTimer_->start (QDateTime::currentDateTime ().msecsTo (nextUpdateCheck)); | ||||
|   } | ||||
| #undef GET | ||||
| } | ||||
| 
 | ||||
| void Manager::checkForUpdates () { | ||||
|   updater_->checkForUpdates (); | ||||
|   scheduleUpdate (true); | ||||
| } | ||||
| 
 | ||||
| Manager::~Manager () { | ||||
|   for (SelectionDialog *selection: selections_.values ()) { | ||||
|     selection->hide (); | ||||
|     delete selection; | ||||
|   } | ||||
|   trayIcon_->hide (); | ||||
|   delete trayIcon_->contextMenu (); | ||||
|   for (QThread *thread: threads_) { | ||||
|     thread->quit (); | ||||
|     thread->wait (1000000); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void Manager::capture () { | ||||
|   QList<QScreen *> screens = QApplication::screens (); | ||||
|   for (QScreen *screen: screens) { | ||||
|     QRect geometry = screen->availableGeometry (); | ||||
| #if QT_VERSION >= QT_VERSION_CHECK (5,10,0) | ||||
|     QPixmap pixmap = screen->grabWindow (0, 0, 0, | ||||
|                                          geometry.width (), geometry.height ()); | ||||
| #else | ||||
|     QPixmap pixmap = screen->grabWindow (0, geometry.x (), geometry.y (), | ||||
|                                          geometry.width (), geometry.height ()); | ||||
| #endif | ||||
| 
 | ||||
|     QString name = screen->name (); | ||||
|     if (!selections_.contains (name)) { | ||||
|       SelectionDialog *selection = new SelectionDialog (*dictionary_); | ||||
|       selection->setWindowIcon (trayIcon_->icon ()); | ||||
|       connect (this, SIGNAL (closeSelections ()), selection, SLOT (close ())); | ||||
|       connect (this, SIGNAL (settingsEdited ()), selection, SLOT (applySettings ())); | ||||
|       connect (selection, SIGNAL (selected (ProcessingItem)), | ||||
|                SLOT (handleSelection (ProcessingItem))); | ||||
|       connect (selection, SIGNAL (rejected ()), SIGNAL (closeSelections ())); | ||||
|       selections_[name] = selection; | ||||
|     } | ||||
|     SelectionDialog *selection = selections_[name]; | ||||
|     selection->setPixmap (pixmap, geometry); | ||||
|   } | ||||
|   updateActionsState (); | ||||
| } | ||||
| 
 | ||||
| void Manager::handleSelection (ProcessingItem item) { | ||||
|   bool altMod = item.modifiers & Qt::AltModifier; | ||||
|   bool doTranslation = (doTranslation_ && !altMod) || (!doTranslation_ && altMod); | ||||
|   if (doTranslation) { | ||||
|     item.translateLanguage = defaultTranslationLanguage_; | ||||
|   } | ||||
|   if (item.ocrLanguage.isEmpty ()) { | ||||
|     item.ocrLanguage = defaultOrcLanguage_; | ||||
|   } | ||||
|   if (item.swapLanguages_) { | ||||
|     QString translate = (item.translateLanguage.isEmpty ()) | ||||
|                         ? defaultTranslationLanguage_ : item.translateLanguage; | ||||
|     if (doTranslation) { | ||||
|       item.translateLanguage = dictionary_->ocrToTranslateCodes (item.ocrLanguage); | ||||
|     } | ||||
|     item.sourceLanguage.clear (); | ||||
|     item.ocrLanguage = dictionary_->translateToOcrCodes (translate); | ||||
|     if (item.ocrLanguage.isEmpty ()) { | ||||
|       showError (tr ("Не найден подходящий язык распознавания.")); | ||||
|       return; | ||||
|     } | ||||
|   } | ||||
|   if (item.sourceLanguage.isEmpty ()) { | ||||
|     item.sourceLanguage = dictionary_->ocrToTranslateCodes (item.ocrLanguage); | ||||
|   } | ||||
|   emit requestRecognize (item); | ||||
|   ++itemProcessingCount_; | ||||
|   updateNormalIcon (); | ||||
|   if (!(item.modifiers & Qt::ControlModifier)) { | ||||
|     emit closeSelections (); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void Manager::repeatCapture () { | ||||
|   if (selections_.isEmpty ()) { | ||||
|   if (!task->isValid()) { | ||||
|     tray_->showError(task->error); | ||||
|     return; | ||||
|   } | ||||
|   QList<QScreen *> screens = QApplication::screens (); | ||||
|   for (QScreen *screen: screens) { | ||||
|     QString name = screen->name (); | ||||
|     if (!selections_.contains (name)) { | ||||
|       continue; | ||||
|     } | ||||
|     SelectionDialog *selection = selections_[name]; | ||||
|     selection->show (); | ||||
|     selection->activateWindow (); | ||||
|   } | ||||
| 
 | ||||
|   tray_->showSuccess(); | ||||
| } | ||||
| 
 | ||||
| void Manager::settings () { | ||||
|   SettingsEditor editor (*dictionary_); | ||||
|   editor.setWindowIcon (trayIcon_->icon ()); | ||||
|   connect (&editor, SIGNAL (settingsEdited ()), SIGNAL (settingsEdited ())); | ||||
|   connect (&editor, SIGNAL (updateCheckRequested ()), SLOT (checkForUpdates ())); | ||||
|   updateActionsState (false); | ||||
|   editor.exec (); | ||||
|   updateActionsState (true); | ||||
| } | ||||
| void Manager::captured(const TaskPtr &task) | ||||
| { | ||||
|   tray_->blockActions(false); | ||||
| 
 | ||||
| void Manager::close () { | ||||
|   QApplication::quit (); | ||||
| } | ||||
|   SOFT_ASSERT(task, return ); | ||||
|   LTRACE() << "captured" << task->captured << task->error; | ||||
| 
 | ||||
| void Manager::about () { | ||||
|   QString text = tr ("Программа для распознавания текста на экране.\n" \ | ||||
|                      "Создана с использованием Qt, tesseract-ocr, Google Translate.\n" | ||||
|                      "Автор: Gres (translator@gres.biz)\n" | ||||
|                      "Версия: %1 от %2 %3").arg (updater_->currentAppVersion ()) | ||||
|                  .arg (__DATE__).arg (__TIME__); | ||||
|   QString tips = tr ("\n\nПодсказки.\n" | ||||
|                      "Клик по иконке в трее:\n" | ||||
|                      "* левой кнопкой - отобразить последний результат\n" | ||||
|                      "* средней кнопкой - скопировать последний результат в буфер обмена\n" | ||||
| #ifdef Q_OS_WIN | ||||
|                      "* двойной клик - повторный захват последнего экрана\n" | ||||
| #endif | ||||
|                      "\n" | ||||
|                      "Захвата изображения при зажатых кнопках:\n" | ||||
|                      "* Ctrl - не выходить из режима захвата\n" | ||||
|                      "* Alt - выполнить перевод, если в настройках он выключен " | ||||
|                      "(и наоборот, не выполнять, если включен)\n" | ||||
|                      ""); | ||||
|   ++activeTaskCount_; | ||||
|   tray_->setActiveTaskCount(activeTaskCount_); | ||||
| 
 | ||||
|   QMessageBox message (QMessageBox::Information, tr ("О программе"), text + tips, | ||||
|                        QMessageBox::Ok); | ||||
|   message.setIconPixmap (trayIcon_->icon ().pixmap (QSize (64, 64))); | ||||
|   message.exec (); | ||||
| } | ||||
| 
 | ||||
| void Manager::processTrayAction (QSystemTrayIcon::ActivationReason reason) { | ||||
|   if (reason == QSystemTrayIcon::Trigger && repeatAction_->isEnabled ()) { | ||||
|     showLast (); | ||||
|   } | ||||
|   else if (reason == QSystemTrayIcon::MiddleClick && clipboardAction_->isEnabled ()) { | ||||
|     copyLastToClipboard  (); | ||||
|     trayIcon_->showMessage (tr ("Результат"), | ||||
|                             tr ("Последний результат был скопирован в буфер обмена."), | ||||
|                             QSystemTrayIcon::Information); | ||||
|   } | ||||
|   else if (reason == QSystemTrayIcon::DoubleClick && repeatCaptureAction_->isEnabled ()) { | ||||
|     repeatCapture (); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void Manager::editRecognized (ProcessingItem item) { | ||||
|   QString fixed = QInputDialog::getMultiLineText ( | ||||
|     NULL, tr ("Правка"), tr ("Исправьте распознанный текст"), item.recognized); | ||||
|   if (!fixed.isEmpty ()) { | ||||
|     item.recognized = fixed; | ||||
|     ++itemProcessingCount_; | ||||
|     updateNormalIcon (); | ||||
|     emit requestTranslate (item); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void Manager::showLast () { | ||||
|   const ProcessingItem &item = resultDialog_->item (); | ||||
|   if (item.isValid ()) { | ||||
|     ++itemProcessingCount_; | ||||
|     showResult (item); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void Manager::copyLastToClipboard () { | ||||
|   const ProcessingItem &item = resultDialog_->item (); | ||||
|   if (item.isValid ()) { | ||||
|     QClipboard *clipboard = QApplication::clipboard (); | ||||
|     QString message = item.recognized; | ||||
|     if (!item.translated.isEmpty ()) { | ||||
|       message += " - " + item.translated; | ||||
|     } | ||||
|     clipboard->setText (message); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void Manager::copyLastImageToClipboard () { | ||||
|   const ProcessingItem &item = resultDialog_->item (); | ||||
|   if (item.isValid ()) { | ||||
|     QClipboard *clipboard = QApplication::clipboard (); | ||||
|     clipboard->setPixmap (item.source); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void Manager::showResult (ProcessingItem item) { | ||||
|   --itemProcessingCount_; | ||||
|   if (!item.isValid ()) { | ||||
|     // delay because it can show error
 | ||||
|     QTimer::singleShot (3000, this, SLOT (updateNormalIcon ())); | ||||
|   if (!task->isValid()) { | ||||
|     finishTask(task); | ||||
|     return; | ||||
|   } | ||||
|   changeIcon (IconTypeSuccess); | ||||
|   if (useResultDialog_) { | ||||
|     resultDialog_->showResult (item); | ||||
|   } | ||||
|   else { | ||||
|     QString message = item.recognized + " - " + item.translated; | ||||
|     trayIcon_->showMessage (tr ("Результат"), message, QSystemTrayIcon::Information); | ||||
|   } | ||||
|   updateActionsState (); | ||||
| 
 | ||||
|   recognizer_->recognize(task); | ||||
| } | ||||
| 
 | ||||
| void Manager::showError (QString text) { | ||||
|   qCritical () << text; | ||||
|   changeIcon (IconTypeError); | ||||
|   trayIcon_->showMessage (tr ("Ошибка"), text, QSystemTrayIcon::Critical); | ||||
| void Manager::captureCanceled() | ||||
| { | ||||
|   tray_->blockActions(false); | ||||
| } | ||||
| 
 | ||||
| void Manager::changeIcon (int iconType, int timeoutMsec) { | ||||
|   QString fileName; | ||||
|   switch (iconType) { | ||||
|     case IconTypeSuccess: | ||||
|       fileName = ":/images/STIconGreen.png"; | ||||
|       break; | ||||
|     case IconTypeError: | ||||
|       fileName = ":/images/STIconRed.png"; | ||||
|       break; | ||||
|     default: | ||||
|       return; | ||||
|   } | ||||
|   trayIcon_->setIcon (QIcon (fileName)); | ||||
|   if (timeoutMsec > 0) { | ||||
|     QTimer::singleShot (timeoutMsec, this, SLOT (updateNormalIcon ())); | ||||
| void Manager::recognized(const TaskPtr &task) | ||||
| { | ||||
|   SOFT_ASSERT(task, return ); | ||||
|   LTRACE() << "recognized" << task->recognized << task->error; | ||||
| 
 | ||||
|   if (!task->isValid()) { | ||||
|     finishTask(task); | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   corrector_->correct(task); | ||||
| } | ||||
| 
 | ||||
| void Manager::updateNormalIcon () { | ||||
|   QString fileName = itemProcessingCount_ > 0 | ||||
|                      ? ":/images/STIconOrange.png" : ":/images/STIconBlue.png"; | ||||
|   trayIcon_->setIcon (QIcon (fileName)); | ||||
| void Manager::corrected(const TaskPtr &task) | ||||
| { | ||||
|   SOFT_ASSERT(task, return ); | ||||
|   LTRACE() << "corrected" << task->recognized << task->error; | ||||
| 
 | ||||
|   if (!task->isValid()) { | ||||
|     finishTask(task); | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   if (!task->targetLanguage.isEmpty()) | ||||
|     translator_->translate(task); | ||||
|   else | ||||
|     translated(task); | ||||
| } | ||||
| 
 | ||||
| void Manager::translated(const TaskPtr &task) | ||||
| { | ||||
|   SOFT_ASSERT(task, return ); | ||||
|   LTRACE() << "translated" << task->recognized << task->error; | ||||
| 
 | ||||
|   finishTask(task); | ||||
| 
 | ||||
|   if (!task->isValid()) | ||||
|     return; | ||||
| 
 | ||||
|   representer_->represent(task); | ||||
| } | ||||
| 
 | ||||
| void Manager::fatalError(const QString &text) | ||||
| { | ||||
|   tray_->blockActions(false); | ||||
|   tray_->showFatalError(text); | ||||
| } | ||||
| 
 | ||||
| void Manager::capture() | ||||
| { | ||||
|   SOFT_ASSERT(capturer_, return ); | ||||
|   tray_->blockActions(true); | ||||
|   capturer_->capture(); | ||||
|   tray_->setRepeatCaptureEnabled(true); | ||||
| } | ||||
| 
 | ||||
| void Manager::repeatCapture() | ||||
| { | ||||
|   SOFT_ASSERT(capturer_, return ); | ||||
|   tray_->blockActions(true); | ||||
|   capturer_->repeatCapture(); | ||||
| } | ||||
| 
 | ||||
| void Manager::showLast() | ||||
| { | ||||
|   if (!last_ || !last_->isValid()) | ||||
|     return; | ||||
|   SOFT_ASSERT(representer_, return ); | ||||
|   representer_->represent(last_); | ||||
| } | ||||
| 
 | ||||
| void Manager::settings() | ||||
| { | ||||
|   SettingsEditor editor; | ||||
| 
 | ||||
|   Settings settings; | ||||
|   settings.load(); | ||||
|   editor.setSettings(settings); | ||||
| 
 | ||||
|   tray_->blockActions(true); | ||||
|   auto result = editor.exec(); | ||||
|   tray_->blockActions(false); | ||||
| 
 | ||||
|   if (result != QDialog::Accepted) | ||||
|     return; | ||||
| 
 | ||||
|   tray_->resetFatalError(); | ||||
| 
 | ||||
|   settings = editor.settings(); | ||||
|   settings.save(); | ||||
|   updateSettings(settings); | ||||
| } | ||||
| 
 | ||||
| void Manager::copyLastToClipboard() | ||||
| { | ||||
|   if (!last_ || !last_->isValid()) | ||||
|     return; | ||||
| 
 | ||||
|   QClipboard *clipboard = QApplication::clipboard(); | ||||
|   clipboard->setText(last_->recognized + QLatin1String(" - ") + | ||||
|                      last_->translated); | ||||
|   tray_->showInformation( | ||||
|       QObject::tr("The last result was copied to the clipboard.")); | ||||
| } | ||||
| 
 | ||||
| void Manager::about() | ||||
| { | ||||
|   auto text = | ||||
|       QObject::tr(R"(Optical character recognition (OCR) and translation tool | ||||
| Author: Gres (translator@gres.biz) | ||||
| Version: %1)") | ||||
|           .arg(QApplication::applicationVersion()); | ||||
| 
 | ||||
|   QMessageBox message(QMessageBox::Information, QObject::tr("About"), text, | ||||
|                       QMessageBox::Ok); | ||||
|   message.setIconPixmap(QIcon(":/icons/app.png").pixmap(QSize(64, 64))); | ||||
|   message.exec(); | ||||
| } | ||||
| 
 | ||||
| void Manager::quit() | ||||
| { | ||||
|   QApplication::quit(); | ||||
| } | ||||
|  | ||||
							
								
								
									
										108
									
								
								src/manager.h
									
									
									
									
									
								
							
							
						
						| @ -1,84 +1,40 @@ | ||||
| #ifndef MANAGER_H | ||||
| #define MANAGER_H | ||||
| #pragma once | ||||
| 
 | ||||
| #include <QPixmap> | ||||
| #include <QSystemTrayIcon> | ||||
| #include <QMap> | ||||
| #include "stfwd.h" | ||||
| 
 | ||||
| #include "processingitem.h" | ||||
| class QString; | ||||
| 
 | ||||
| class QAction; | ||||
| class QMenu; | ||||
| class Manager | ||||
| { | ||||
| public: | ||||
|   Manager(); | ||||
|   ~Manager(); | ||||
| 
 | ||||
| class SelectionDialog; | ||||
| class ResultDialog; | ||||
| class LanguageHelper; | ||||
| class Updater; | ||||
|   void captured(const TaskPtr &task); | ||||
|   void captureCanceled(); | ||||
|   void recognized(const TaskPtr &task); | ||||
|   void corrected(const TaskPtr &task); | ||||
|   void translated(const TaskPtr &task); | ||||
| 
 | ||||
| class Manager : public QObject { | ||||
|   Q_OBJECT | ||||
|   void fatalError(const QString &text); | ||||
|   void capture(); | ||||
|   void repeatCapture(); | ||||
|   void showLast(); | ||||
|   void settings(); | ||||
|   void copyLastToClipboard(); | ||||
|   void about(); | ||||
|   void quit(); | ||||
| 
 | ||||
|   enum IconType { | ||||
|     IconTypeNormal, IconTypeWorking, IconTypeError, IconTypeSuccess | ||||
|   }; | ||||
| private: | ||||
|   void updateSettings(const Settings &settings); | ||||
|   void finishTask(const TaskPtr &task); | ||||
| 
 | ||||
|   public: | ||||
|     explicit Manager (QObject *parent = 0); | ||||
|     ~Manager (); | ||||
| 
 | ||||
|   signals: | ||||
|     void requestRecognize (ProcessingItem item); | ||||
|     void requestTranslate (ProcessingItem item); | ||||
|     void closeSelections (); | ||||
|     void settingsEdited (); | ||||
| 
 | ||||
|   private slots: | ||||
|     void capture (); | ||||
|     void repeatCapture (); | ||||
|     void settings (); | ||||
|     void close (); | ||||
|     void about (); | ||||
|     void showLast (); | ||||
|     void copyLastToClipboard (); | ||||
|     void copyLastImageToClipboard (); | ||||
| 
 | ||||
|     void applySettings (); | ||||
|     void checkForUpdates (); | ||||
| 
 | ||||
|     void processTrayAction (QSystemTrayIcon::ActivationReason reason); | ||||
| 
 | ||||
|     void editRecognized (ProcessingItem item); | ||||
|     void handleSelection (ProcessingItem item); | ||||
|     void showResult (ProcessingItem item); | ||||
|     void showError (QString text); | ||||
| 
 | ||||
|     void updateNormalIcon (); | ||||
| 
 | ||||
|   private: | ||||
|     QMenu * trayContextMenu (); | ||||
|     void updateActionsState (bool isEnabled = true); | ||||
|     void changeIcon (int iconType, int timeoutMsec = 3000); | ||||
|     void scheduleUpdate (bool justChecked = false); | ||||
| 
 | ||||
|   private: | ||||
|     QSystemTrayIcon *trayIcon_; | ||||
|     LanguageHelper *dictionary_; | ||||
|     //! Selection dialogs for each screen. Key - screen name.
 | ||||
|     QMap<QString, SelectionDialog *> selections_; | ||||
|     ResultDialog *resultDialog_; | ||||
|     Updater *updater_; | ||||
|     QTimer *updateTimer_; | ||||
|     QAction *captureAction_; | ||||
|     QAction *repeatCaptureAction_; | ||||
|     QAction *repeatAction_; | ||||
|     QAction *clipboardAction_; | ||||
|     bool useResultDialog_; | ||||
|     //! Used threads. For proper termination.
 | ||||
|     QList<QThread *> threads_; | ||||
|     QString defaultTranslationLanguage_; | ||||
|     QString defaultOrcLanguage_; | ||||
|     bool doTranslation_; | ||||
|     int itemProcessingCount_; | ||||
|   std::unique_ptr<TrayIcon> tray_; | ||||
|   std::unique_ptr<Capturer> capturer_; | ||||
|   std::unique_ptr<Recognizer> recognizer_; | ||||
|   std::unique_ptr<Corrector> corrector_; | ||||
|   std::unique_ptr<Translator> translator_; | ||||
|   std::unique_ptr<Representer> representer_; | ||||
|   TaskPtr last_; | ||||
|   int activeTaskCount_{0}; | ||||
| }; | ||||
| 
 | ||||
| #endif // MANAGER_H
 | ||||
|  | ||||
							
								
								
									
										48
									
								
								src/ocr/recognizer.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,48 @@ | ||||
| #include "recognizer.h" | ||||
| #include "manager.h" | ||||
| #include "recognizerworker.h" | ||||
| #include "settings.h" | ||||
| #include "tesseract.h" | ||||
| 
 | ||||
| #include <QThread> | ||||
| 
 | ||||
| Recognizer::Recognizer(Manager &manager) | ||||
|   : manager_(manager) | ||||
|   , workerThread_(new QThread(this)) | ||||
| { | ||||
|   auto worker = new RecognizeWorker; | ||||
|   connect(this, &Recognizer::reset,  //
 | ||||
|           worker, &RecognizeWorker::reset); | ||||
|   connect(this, &Recognizer::recognize,  //
 | ||||
|           worker, &RecognizeWorker::handle); | ||||
|   connect(worker, &RecognizeWorker::finished,  //
 | ||||
|           this, &Recognizer::recognized); | ||||
|   connect(workerThread_, &QThread::finished,  //
 | ||||
|           worker, &QObject::deleteLater); | ||||
| 
 | ||||
|   workerThread_->start(); | ||||
|   worker->moveToThread(workerThread_); | ||||
| } | ||||
| 
 | ||||
| void Recognizer::recognized(const TaskPtr &task) | ||||
| { | ||||
|   manager_.recognized(task); | ||||
| } | ||||
| 
 | ||||
| Recognizer::~Recognizer() | ||||
| { | ||||
|   workerThread_->quit(); | ||||
|   const auto timeoutMs = 2000; | ||||
|   if (!workerThread_->wait(timeoutMs)) | ||||
|     workerThread_->terminate(); | ||||
| } | ||||
| 
 | ||||
| void Recognizer::updateSettings(const Settings &settings) | ||||
| { | ||||
|   if (settings.tessdataPath.isEmpty()) { | ||||
|     manager_.fatalError(tr("Tessdata path is empty")); | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   emit reset(settings.tessdataPath); | ||||
| } | ||||
							
								
								
									
										25
									
								
								src/ocr/recognizer.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,25 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include "stfwd.h" | ||||
| 
 | ||||
| #include <QObject> | ||||
| 
 | ||||
| class Recognizer : public QObject | ||||
| { | ||||
|   Q_OBJECT | ||||
| public: | ||||
|   explicit Recognizer(Manager &manager); | ||||
|   ~Recognizer(); | ||||
| 
 | ||||
|   void updateSettings(const Settings &settings); | ||||
| 
 | ||||
| signals: | ||||
|   void recognize(const TaskPtr &task); | ||||
|   void reset(const QString &tessdataPath); | ||||
| 
 | ||||
| private: | ||||
|   void recognized(const TaskPtr &task); | ||||
| 
 | ||||
|   Manager &manager_; | ||||
|   QThread *workerThread_; | ||||
| }; | ||||
							
								
								
									
										46
									
								
								src/ocr/recognizerworker.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,46 @@ | ||||
| #include "recognizerworker.h" | ||||
| #include "debug.h" | ||||
| #include "task.h" | ||||
| #include "tesseract.h" | ||||
| 
 | ||||
| RecognizeWorker::~RecognizeWorker() = default; | ||||
| 
 | ||||
| void RecognizeWorker::handle(const TaskPtr &task) | ||||
| { | ||||
|   SOFT_ASSERT(task, return ); | ||||
|   SOFT_ASSERT(task->isValid(), return ); | ||||
|   SOFT_ASSERT(!tessdataPath_.isEmpty(), return ); | ||||
| 
 | ||||
|   auto result = task; | ||||
| 
 | ||||
|   if (!engines_.count(task->sourceLanguage)) { | ||||
|     auto engine = | ||||
|         std::make_unique<Tesseract>(task->sourceLanguage, tessdataPath_); | ||||
| 
 | ||||
|     if (!engine->isValid()) { | ||||
|       result->error = tr("Failed to init OCR engine: %1").arg(engine->error()); | ||||
|       emit finished(result); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     engines_.emplace(task->sourceLanguage, std::move(engine)); | ||||
|   } | ||||
| 
 | ||||
|   auto &engine = engines_[task->sourceLanguage]; | ||||
|   SOFT_ASSERT(engine->isValid(), return ); | ||||
| 
 | ||||
|   result->recognized = engine->recognize(task->captured); | ||||
|   if (result->recognized.isEmpty()) | ||||
|     result->error = engine->error(); | ||||
| 
 | ||||
|   emit finished(result); | ||||
| } | ||||
| 
 | ||||
| void RecognizeWorker::reset(const QString &tessdataPath) | ||||
| { | ||||
|   if (tessdataPath_ == tessdataPath) | ||||
|     return; | ||||
| 
 | ||||
|   tessdataPath_ = tessdataPath; | ||||
|   engines_.clear(); | ||||
| } | ||||
							
								
								
									
										24
									
								
								src/ocr/recognizerworker.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,24 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include "stfwd.h" | ||||
| 
 | ||||
| #include <QObject> | ||||
| 
 | ||||
| class Tesseract; | ||||
| 
 | ||||
| class RecognizeWorker : public QObject | ||||
| { | ||||
|   Q_OBJECT | ||||
| public: | ||||
|   ~RecognizeWorker(); | ||||
| 
 | ||||
|   void handle(const TaskPtr &task); | ||||
|   void reset(const QString &tessdataPath); | ||||
| 
 | ||||
| signals: | ||||
|   void finished(const TaskPtr &task); | ||||
| 
 | ||||
| private: | ||||
|   std::map<QString, std::unique_ptr<Tesseract>> engines_; | ||||
|   QString tessdataPath_; | ||||
| }; | ||||
							
								
								
									
										220
									
								
								src/ocr/tesseract.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,220 @@ | ||||
| #include "tesseract.h" | ||||
| #include "debug.h" | ||||
| #include "languagecodes.h" | ||||
| #include "task.h" | ||||
| 
 | ||||
| #include <leptonica/allheaders.h> | ||||
| #include <tesseract/baseapi.h> | ||||
| 
 | ||||
| #if defined(Q_OS_LINUX) | ||||
| #include <fstream> | ||||
| static qint64 getFreeMemory() | ||||
| { | ||||
|   std::string token; | ||||
|   std::ifstream file("/proc/meminfo"); | ||||
|   qint64 freeMem = 0; | ||||
|   while (file >> token) { | ||||
|     if (token == "MemFree:" || token == "Buffers:" || token == "Cached:") { | ||||
|       unsigned long mem = 0; | ||||
|       freeMem += (file >> mem) ? mem : 0; | ||||
|     } | ||||
|   } | ||||
|   return freeMem * 1024; | ||||
| } | ||||
| #elif defined(Q_OS_WIN) | ||||
| #include <windows.h> | ||||
| #undef min | ||||
| #undef max | ||||
| static qint64 getFreeMemory() | ||||
| { | ||||
|   MEMORYSTATUSEX statex; | ||||
|   statex.dwLength = sizeof(statex); | ||||
|   if (GlobalMemoryStatusEx(&statex)) { | ||||
|     return statex.ullAvailPhys; | ||||
|   } | ||||
|   return -1; | ||||
| } | ||||
| #endif | ||||
| 
 | ||||
| static Pix *convertImage(const QImage &image) | ||||
| { | ||||
|   PIX *pix; | ||||
| 
 | ||||
|   int width = image.width(); | ||||
|   int height = image.height(); | ||||
|   int depth = image.depth(); | ||||
|   int bytesPerLine = image.bytesPerLine(); | ||||
|   int wpl = bytesPerLine / 4; | ||||
| 
 | ||||
|   pix = pixCreate(width, height, depth); | ||||
|   pixSetWpl(pix, wpl); | ||||
|   pixSetColormap(pix, nullptr); | ||||
|   memmove(pix->data, image.bits(), bytesPerLine * height); | ||||
| 
 | ||||
|   const qreal toDPM = 1.0 / 0.0254; | ||||
|   int resolutionX = image.dotsPerMeterX() / toDPM; | ||||
|   int resolutionY = image.dotsPerMeterY() / toDPM; | ||||
|   pixSetResolution(pix, resolutionX, resolutionY); | ||||
|   return pix; | ||||
| } | ||||
| 
 | ||||
| static QImage convertImage(Pix &image) | ||||
| { | ||||
|   int width = pixGetWidth(&image); | ||||
|   int height = pixGetHeight(&image); | ||||
|   int depth = pixGetDepth(&image); | ||||
|   int bytesPerLine = pixGetWpl(&image) * 4; | ||||
|   l_uint32 *datas = pixGetData(&image); | ||||
| 
 | ||||
|   QImage::Format format; | ||||
|   if (depth == 1) { | ||||
|     format = QImage::Format_Mono; | ||||
|   } else if (depth == 8) { | ||||
|     format = QImage::Format_Indexed8; | ||||
|   } else { | ||||
|     format = QImage::Format_RGB32; | ||||
|   } | ||||
| 
 | ||||
|   QImage result((uchar *)datas, width, height, bytesPerLine, format); | ||||
| 
 | ||||
|   // Set resolution
 | ||||
|   l_int32 xres, yres; | ||||
|   pixGetResolution(&image, &xres, &yres); | ||||
|   const qreal toDPM = 1.0 / 0.0254; | ||||
|   result.setDotsPerMeterX(xres * toDPM); | ||||
|   result.setDotsPerMeterY(yres * toDPM); | ||||
| 
 | ||||
|   // Handle palette
 | ||||
|   QVector<QRgb> _bwCT; | ||||
|   _bwCT.append(qRgb(255, 255, 255)); | ||||
|   _bwCT.append(qRgb(0, 0, 0)); | ||||
| 
 | ||||
|   QVector<QRgb> _grayscaleCT(256); | ||||
|   for (int i = 0; i < 256; i++) { | ||||
|     _grayscaleCT.append(qRgb(i, i, i)); | ||||
|   } | ||||
|   switch (depth) { | ||||
|     case 1: result.setColorTable(_bwCT); break; | ||||
|     case 8: result.setColorTable(_grayscaleCT); break; | ||||
|     default: result.setColorTable(_grayscaleCT); | ||||
|   } | ||||
| 
 | ||||
|   if (result.isNull()) { | ||||
|     static QImage none(0, 0, QImage::Format_Invalid); | ||||
|     qDebug("Invalid format!!!\n"); | ||||
|     return none; | ||||
|   } | ||||
| 
 | ||||
|   return result; | ||||
| } | ||||
| 
 | ||||
| static Pix *prepareImage(const QImage &image) | ||||
| { | ||||
|   Pix *pix = convertImage(image); | ||||
|   SOFT_ASSERT(pix != NULL, return nullptr); | ||||
| 
 | ||||
|   Pix *gray = pixConvertRGBToGray(pix, 0.0, 0.0, 0.0); | ||||
|   SOFT_ASSERT(gray != NULL, return nullptr); | ||||
|   pixDestroy(&pix); | ||||
| 
 | ||||
|   Pix *scaled = gray; | ||||
|   const auto xRes = pixGetXRes(gray); | ||||
|   const auto yRes = pixGetYRes(gray); | ||||
|   if (xRes * yRes != 0) { | ||||
|     const auto preferredScale = std::max(300.0 / std::min(xRes, yRes), 1.0); | ||||
|     if (preferredScale > 1.0) { | ||||
|       const auto MAX_INT16 = 0x7fff; | ||||
|       float maxScaleX = MAX_INT16 / double(gray->w); | ||||
|       float scaleX = std::min(float(preferredScale), maxScaleX); | ||||
|       float maxScaleY = MAX_INT16 / double(gray->h); | ||||
|       float scaleY = std::min(float(preferredScale), maxScaleY); | ||||
|       float scale = std::min(scaleX, scaleY); | ||||
| 
 | ||||
|       qint64 availableMemory = getFreeMemory() * 0.95; | ||||
|       if (availableMemory > 0) { | ||||
|         qint32 actualSize = gray->w * gray->h * gray->d / 8; | ||||
|         float maxScaleMemory = float(availableMemory) / actualSize; | ||||
|         scale = std::min(scale, maxScaleMemory); | ||||
|       } | ||||
|       scaled = pixScale(gray, scale, scale); | ||||
|       if (scaled == NULL) { | ||||
|         scaled = gray; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|   if (scaled != gray) { | ||||
|     pixDestroy(&gray); | ||||
|   } | ||||
|   return scaled; | ||||
| } | ||||
| 
 | ||||
| static void cleanupImage(Pix **image) | ||||
| { | ||||
|   pixDestroy(image); | ||||
| } | ||||
| 
 | ||||
| Tesseract::Tesseract(const LanguageId &language, const QString &tessdataPath) | ||||
| { | ||||
|   SOFT_ASSERT(!tessdataPath.isEmpty(), return ); | ||||
|   SOFT_ASSERT(!language.isEmpty(), return ); | ||||
| 
 | ||||
|   init(language, tessdataPath); | ||||
| } | ||||
| 
 | ||||
| Tesseract::~Tesseract() = default; | ||||
| 
 | ||||
| void Tesseract::init(const LanguageId &language, const QString &tessdataPath) | ||||
| { | ||||
|   SOFT_ASSERT(!engine_, return ); | ||||
| 
 | ||||
|   LanguageCodes languages; | ||||
|   auto langCodes = languages.findById(language); | ||||
|   if (!langCodes) { | ||||
|     error_ = QObject::tr("unknown recognition language: %1").arg(language); | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   engine_ = std::make_unique<tesseract::TessBaseAPI>(); | ||||
| 
 | ||||
|   auto result = | ||||
|       engine_->Init(qPrintable(tessdataPath), qPrintable(langCodes->tesseract), | ||||
|                     tesseract::OEM_DEFAULT); | ||||
|   if (result == 0) | ||||
|     return; | ||||
| 
 | ||||
|   error_ = QObject::tr("troubles with tessdata"); | ||||
|   engine_.reset(); | ||||
|   return; | ||||
| } | ||||
| 
 | ||||
| const QString &Tesseract::error() const | ||||
| { | ||||
|   return error_; | ||||
| } | ||||
| 
 | ||||
| QString Tesseract::recognize(const QPixmap &source) | ||||
| { | ||||
|   SOFT_ASSERT(engine_, return {}); | ||||
|   SOFT_ASSERT(!source.isNull(), return {}); | ||||
| 
 | ||||
|   error_.clear(); | ||||
| 
 | ||||
|   Pix *image = prepareImage(source.toImage()); | ||||
|   SOFT_ASSERT(image != NULL, return {}); | ||||
|   engine_->SetImage(image); | ||||
|   char *outText = engine_->GetUTF8Text(); | ||||
|   engine_->Clear(); | ||||
|   cleanupImage(&image); | ||||
| 
 | ||||
|   QString result = QString(outText).trimmed(); | ||||
|   delete[] outText; | ||||
| 
 | ||||
|   if (result.isEmpty()) | ||||
|     error_ = QObject::tr("Failed to recognize text"); | ||||
|   return result; | ||||
| } | ||||
| 
 | ||||
| bool Tesseract::isValid() const | ||||
| { | ||||
|   return engine_.get(); | ||||
| } | ||||
							
								
								
									
										31
									
								
								src/ocr/tesseract.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,31 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include "stfwd.h" | ||||
| 
 | ||||
| #include <QString> | ||||
| 
 | ||||
| #include <memory> | ||||
| 
 | ||||
| class QPixmap; | ||||
| namespace tesseract | ||||
| { | ||||
| class TessBaseAPI; | ||||
| } | ||||
| class Task; | ||||
| 
 | ||||
| class Tesseract | ||||
| { | ||||
| public: | ||||
|   Tesseract(const LanguageId& language, const QString& tessdataPath); | ||||
|   ~Tesseract(); | ||||
| 
 | ||||
|   QString recognize(const QPixmap& source); | ||||
|   bool isValid() const; | ||||
|   const QString& error() const; | ||||
| 
 | ||||
| private: | ||||
|   void init(const LanguageId& language, const QString& tessdataPath); | ||||
| 
 | ||||
|   std::unique_ptr<tesseract::TessBaseAPI> engine_; | ||||
|   QString error_; | ||||
| }; | ||||
| @ -1,17 +0,0 @@ | ||||
| #include "processingitem.h" | ||||
| 
 | ||||
| ProcessingItem::ProcessingItem () | ||||
|   : swapLanguages_ (false) { | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| bool ProcessingItem::isValid (bool checkOnlyInput) const { | ||||
|   bool valid = true; | ||||
|   valid &= (!screenPos.isNull ()); | ||||
|   valid &= (!source.isNull ()); | ||||
|   valid &= (!ocrLanguage.isEmpty ()); | ||||
|   if (!checkOnlyInput) { | ||||
|     valid &= (!recognized.isEmpty ()); | ||||
|   } | ||||
|   return valid; | ||||
| } | ||||
| @ -1,24 +0,0 @@ | ||||
| #ifndef PROCESSINGITEM_H | ||||
| #define PROCESSINGITEM_H | ||||
| 
 | ||||
| #include <QPixmap> | ||||
| 
 | ||||
| struct ProcessingItem { | ||||
|   ProcessingItem (); | ||||
|   QPoint screenPos; | ||||
|   QPixmap source; | ||||
|   QString recognized; | ||||
|   QString translated; | ||||
| 
 | ||||
|   QString ocrLanguage; | ||||
|   QString sourceLanguage; | ||||
|   QString translateLanguage; | ||||
| 
 | ||||
|   Qt::KeyboardModifiers modifiers; | ||||
|   bool swapLanguages_; | ||||
| 
 | ||||
|   bool isValid (bool checkOnlyInput = false) const; | ||||
| }; | ||||
| Q_DECLARE_METATYPE (ProcessingItem) | ||||
| 
 | ||||
| #endif // PROCESSINGITEM_H
 | ||||
| @ -1,90 +0,0 @@ | ||||
| #include "recognizer.h" | ||||
| 
 | ||||
| #include <tesseract/baseapi.h> | ||||
| 
 | ||||
| #include <QDebug> | ||||
| #include <QSettings> | ||||
| 
 | ||||
| #include "settings.h" | ||||
| #include "imageprocessing.h" | ||||
| #include "stassert.h" | ||||
| #include "recognizerhelper.h" | ||||
| 
 | ||||
| Recognizer::Recognizer (QObject *parent) : | ||||
|   QObject (parent), | ||||
|   engine_ (NULL), recognizerHelper_ (new RecognizerHelper), imageScale_ (0) { | ||||
|   applySettings (); | ||||
| } | ||||
| 
 | ||||
| void Recognizer::applySettings () { | ||||
|   QSettings settings; | ||||
|   settings.beginGroup (settings_names::recogntionGroup); | ||||
| 
 | ||||
|   recognizerHelper_->load (); | ||||
| 
 | ||||
|   tessDataDir_ = settings.value (settings_names::tessDataPlace, | ||||
|                                  settings_values::tessDataPlace).toString (); | ||||
|   ocrLanguage_ = settings.value (settings_names::ocrLanguage, | ||||
|                                  settings_values::ocrLanguage).toString (); | ||||
|   imageScale_ = settings.value (settings_names::imageScale, | ||||
|                                 settings_values::imageScale).toInt (); | ||||
| 
 | ||||
|   initEngine (engine_, ocrLanguage_); | ||||
| } | ||||
| 
 | ||||
| bool Recognizer::initEngine (tesseract::TessBaseAPI * &engine, const QString &language) { | ||||
|   if (tessDataDir_.isEmpty () || language.isEmpty ()) { | ||||
|     emit error (tr ("Неверные параметры для OCR")); | ||||
|     return false; | ||||
|   } | ||||
|   if (engine != NULL) { | ||||
|     delete engine; | ||||
|   } | ||||
|   engine = new tesseract::TessBaseAPI (); | ||||
|   int result = engine->Init (qPrintable (tessDataDir_), qPrintable (language), | ||||
|                              tesseract::OEM_DEFAULT); | ||||
|   if (result != 0) { | ||||
|     emit error (tr ("Ошибка инициализации OCR: %1").arg (result)); | ||||
|     delete engine; | ||||
|     engine = NULL; | ||||
|     return false; | ||||
|   } | ||||
|   return true; | ||||
| } | ||||
| 
 | ||||
| void Recognizer::recognize (ProcessingItem item) { | ||||
|   if (!item.isValid (true)) { | ||||
|     emit recognized (item); | ||||
|     return; | ||||
|   } | ||||
|   bool isCustomLanguage = (item.ocrLanguage != ocrLanguage_); | ||||
|   tesseract::TessBaseAPI *engine = (isCustomLanguage) ? NULL : engine_; | ||||
|   QString language = (isCustomLanguage) ? item.ocrLanguage : ocrLanguage_; | ||||
|   if (engine == NULL) { | ||||
|     if (!initEngine (engine, language)) { | ||||
|       emit recognized (item); | ||||
|       return; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   Pix *image = prepareImage (item.source.toImage (), imageScale_); | ||||
|   ST_ASSERT (image != NULL); | ||||
|   engine->SetImage (image); | ||||
|   char *outText = engine->GetUTF8Text (); | ||||
|   engine->Clear (); | ||||
|   cleanupImage (&image); | ||||
| 
 | ||||
|   QString result = QString (outText).trimmed (); | ||||
|   delete [] outText; | ||||
|   if (isCustomLanguage) { | ||||
|     delete engine; | ||||
|   } | ||||
| 
 | ||||
|   if (!result.isEmpty ()) { | ||||
|     item.recognized = recognizerHelper_->substitute (result, language); | ||||
|   } | ||||
|   else { | ||||
|     emit error (tr ("Текст не распознан.")); | ||||
|   } | ||||
|   emit recognized (item); | ||||
| } | ||||
| @ -1,41 +0,0 @@ | ||||
| #ifndef RECOGNIZER_H | ||||
| #define RECOGNIZER_H | ||||
| 
 | ||||
| #include <QObject> | ||||
| #include "QPixmap" | ||||
| 
 | ||||
| #include "processingitem.h" | ||||
| 
 | ||||
| namespace tesseract { | ||||
|   class TessBaseAPI; | ||||
| } | ||||
| class RecognizerHelper; | ||||
| 
 | ||||
| class Recognizer : public QObject { | ||||
|   Q_OBJECT | ||||
| 
 | ||||
|   public: | ||||
|     explicit Recognizer (QObject *parent = 0); | ||||
| 
 | ||||
|   signals: | ||||
|     void recognized (ProcessingItem item); | ||||
|     void error (QString text); | ||||
| 
 | ||||
|   public slots: | ||||
|     void recognize (ProcessingItem item); | ||||
|     void applySettings (); | ||||
| 
 | ||||
|   private: | ||||
|     bool initEngine (tesseract::TessBaseAPI * &engine, const QString &language); | ||||
| 
 | ||||
|   private: | ||||
|     tesseract::TessBaseAPI *engine_; | ||||
|     RecognizerHelper *recognizerHelper_; | ||||
| 
 | ||||
|     QString tessDataDir_; | ||||
|     QString ocrLanguage_; | ||||
|     int imageScale_; | ||||
| 
 | ||||
| }; | ||||
| 
 | ||||
| #endif // RECOGNIZER_H
 | ||||
| @ -1,88 +0,0 @@ | ||||
| #include <QDir> | ||||
| #include <QFile> | ||||
| #include <QFileInfo> | ||||
| #include <QSettings> | ||||
| #include <QApplication> | ||||
| 
 | ||||
| #include "recognizerhelper.h" | ||||
| 
 | ||||
| RecognizerHelper::RecognizerHelper () | ||||
|   : fileName_ ("st_subs.csv") { | ||||
| #ifdef Q_OS_LINUX | ||||
|   QDir settingDir = QFileInfo (QSettings ().fileName ()).dir (); | ||||
|   fileName_ = settingDir.absoluteFilePath (fileName_); | ||||
| #else | ||||
|   fileName_ = QApplication::applicationDirPath () + QDir::separator () + fileName_; | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| void RecognizerHelper::load () { | ||||
|   subs_.clear (); | ||||
|   QFile f (fileName_); | ||||
|   if (!f.open (QFile::ReadOnly)) { | ||||
|     return; | ||||
|   } | ||||
|   QByteArray data = f.readAll (); | ||||
|   f.close (); | ||||
|   QStringList lines = QString::fromUtf8 (data).split ('\n', QString::SkipEmptyParts); | ||||
|   for (const QString &line: lines) { | ||||
|     QStringList parts = line.mid (1, line.size () - 2).split ("\",\""); // remove "
 | ||||
|     if (parts.size () < 3) { | ||||
|       continue; | ||||
|     } | ||||
|     subs_.append (Sub (parts[0], parts[1], parts[2])); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void RecognizerHelper::save () { | ||||
|   QFile f (fileName_); | ||||
|   if (!f.open (QFile::WriteOnly)) { | ||||
|     return; | ||||
|   } | ||||
|   for (const Sub &sub: subs_) { | ||||
|     QStringList parts = QStringList () << sub.language << sub.source << sub.target; | ||||
|     QString line = "\"" + parts.join ("\",\"") + "\"\n"; | ||||
|     f.write (line.toUtf8 ()); | ||||
|   } | ||||
|   f.close (); | ||||
| } | ||||
| 
 | ||||
| QString RecognizerHelper::substitute (const QString &source, const QString &language) const { | ||||
|   QString result = source; | ||||
|   while (true) { | ||||
|     int bestMatchIndex = -1; | ||||
|     int bestMatchLen = 0; | ||||
|     int index = -1; | ||||
|     for (const Sub &sub: subs_) { | ||||
|       ++index; | ||||
|       if (sub.language != language || !result.contains (sub.source)) { | ||||
|         continue; | ||||
|       } | ||||
|       int len = sub.source.length (); | ||||
|       if (len > bestMatchLen) { | ||||
|         bestMatchLen = len; | ||||
|         bestMatchIndex = index; | ||||
|       } | ||||
|     } | ||||
|     if (bestMatchIndex > -1) { | ||||
|       const Sub &sub = subs_.at (bestMatchIndex); | ||||
|       result.replace (sub.source, sub.target); | ||||
|       continue; | ||||
|     } | ||||
|     break; | ||||
|   } | ||||
| 
 | ||||
|   return result; | ||||
| } | ||||
| 
 | ||||
| const RecognizerHelper::Subs &RecognizerHelper::subs () const { | ||||
|   return subs_; | ||||
| } | ||||
| 
 | ||||
| void RecognizerHelper::setSubs (const Subs &subs) { | ||||
|   subs_ = subs; | ||||
| } | ||||
| 
 | ||||
| RecognizerHelper::Sub::Sub (const QString &language, const QString &source, const QString &target) | ||||
|   : language (language), source (source), target (target) { | ||||
| } | ||||
| @ -1,33 +0,0 @@ | ||||
| #ifndef RECOGNIZERHELPER_H | ||||
| #define RECOGNIZERHELPER_H | ||||
| 
 | ||||
| #include <QString> | ||||
| 
 | ||||
| class RecognizerHelper { | ||||
|   public: | ||||
|     struct Sub { | ||||
|       Sub (const QString &language = QString (), const QString &source = QString (), | ||||
|            const QString &target = QString ()); | ||||
|       QString language; | ||||
|       QString source; | ||||
|       QString target; | ||||
|     }; | ||||
|     typedef QList<Sub> Subs; | ||||
| 
 | ||||
|   public: | ||||
|     RecognizerHelper (); | ||||
| 
 | ||||
|     void load (); | ||||
|     void save (); | ||||
| 
 | ||||
|     QString substitute (const QString &source, const QString& language) const; | ||||
| 
 | ||||
|     const Subs &subs () const; | ||||
|     void setSubs (const Subs &subs); | ||||
| 
 | ||||
|   private: | ||||
|     QString fileName_; | ||||
|     Subs subs_; | ||||
| }; | ||||
| 
 | ||||
| #endif // RECOGNIZERHELPER_H
 | ||||
							
								
								
									
										42
									
								
								src/represent/representer.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,42 @@ | ||||
| #include "representer.h" | ||||
| #include "manager.h" | ||||
| #include "resultwidget.h" | ||||
| #include "settings.h" | ||||
| #include "task.h" | ||||
| #include "trayicon.h" | ||||
| 
 | ||||
| Representer::Representer(Manager &manager, TrayIcon &tray) | ||||
|   : manager_(manager) | ||||
|   , tray_(tray) | ||||
|   , mode_{ResultMode::Widget} | ||||
| { | ||||
| } | ||||
| 
 | ||||
| Representer::~Representer() = default; | ||||
| 
 | ||||
| void Representer::represent(const TaskPtr &task) | ||||
| { | ||||
|   if (mode_ == ResultMode::Tooltip) | ||||
|     showTooltip(task); | ||||
|   else | ||||
|     showWidget(task); | ||||
| } | ||||
| 
 | ||||
| void Representer::updateSettings(const Settings &settings) | ||||
| { | ||||
|   mode_ = settings.resultShowType; | ||||
| } | ||||
| 
 | ||||
| void Representer::showTooltip(const TaskPtr &task) | ||||
| { | ||||
|   auto message = task->recognized + " - " + task->translated; | ||||
|   tray_.showInformation(message); | ||||
| } | ||||
| 
 | ||||
| void Representer::showWidget(const TaskPtr &task) | ||||
| { | ||||
|   if (!widget_) | ||||
|     widget_ = std::make_unique<ResultWidget>(); | ||||
| 
 | ||||
|   widget_->show(task); | ||||
| } | ||||
							
								
								
									
										25
									
								
								src/represent/representer.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,25 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include "stfwd.h" | ||||
| 
 | ||||
| enum class ResultMode; | ||||
| class ResultWidget; | ||||
| 
 | ||||
| class Representer | ||||
| { | ||||
| public: | ||||
|   Representer(Manager &manager, TrayIcon &tray); | ||||
|   ~Representer(); | ||||
| 
 | ||||
|   void represent(const TaskPtr &task); | ||||
|   void updateSettings(const Settings &settings); | ||||
| 
 | ||||
| private: | ||||
|   void showTooltip(const TaskPtr &task); | ||||
|   void showWidget(const TaskPtr &task); | ||||
| 
 | ||||
|   Manager &manager_; | ||||
|   TrayIcon &tray_; | ||||
|   std::unique_ptr<ResultWidget> widget_; | ||||
|   ResultMode mode_; | ||||
| }; | ||||
							
								
								
									
										116
									
								
								src/represent/resultwidget.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,116 @@ | ||||
| #include "resultwidget.h" | ||||
| #include "debug.h" | ||||
| #include "task.h" | ||||
| 
 | ||||
| #include <QApplication> | ||||
| #include <QBoxLayout> | ||||
| #include <QDesktopWidget> | ||||
| #include <QLabel> | ||||
| #include <QMouseEvent> | ||||
| 
 | ||||
| ResultWidget::ResultWidget(QWidget *parent) | ||||
|   : QFrame(parent) | ||||
|   , image_(new QLabel(this)) | ||||
|   , recognized_(new QLabel(this)) | ||||
|   , translated_(new QLabel(this)) | ||||
| { | ||||
|   setWindowFlags(Qt::FramelessWindowHint | Qt::NoDropShadowWindowHint | | ||||
|                  Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint); | ||||
| 
 | ||||
|   setLineWidth(1); | ||||
|   setFrameShape(QFrame::StyledPanel); | ||||
|   setFrameShadow(QFrame::Plain); | ||||
| 
 | ||||
|   image_->setAlignment(Qt::AlignCenter); | ||||
| 
 | ||||
|   recognized_->setObjectName("recognizeLabel"); | ||||
|   recognized_->setAlignment(Qt::AlignCenter); | ||||
|   recognized_->setWordWrap(true); | ||||
| 
 | ||||
|   translated_->setObjectName("translateLabel"); | ||||
|   translated_->setAlignment(Qt::AlignCenter); | ||||
|   translated_->setWordWrap(true); | ||||
| 
 | ||||
|   const auto styleSheet = | ||||
|       "#recognizeLabel, #translateLabel {" | ||||
|       "color: black;" | ||||
|       "background: qlineargradient(x1:0, y1:0, x2:1, y2:1," | ||||
|       "stop:0 darkGray, stop: 0.5 lightGray, stop:1 darkGray);" | ||||
|       "}"; | ||||
|   setStyleSheet(styleSheet); | ||||
| 
 | ||||
|   installEventFilter(this); | ||||
| 
 | ||||
|   auto layout = new QVBoxLayout(this); | ||||
|   layout->addWidget(image_); | ||||
|   layout->addWidget(recognized_); | ||||
|   layout->addWidget(translated_); | ||||
| 
 | ||||
|   layout->setMargin(0); | ||||
|   layout->setSpacing(1); | ||||
| } | ||||
| 
 | ||||
| void ResultWidget::show(const TaskPtr &task) | ||||
| { | ||||
|   SOFT_ASSERT(task->isValid(), return ); | ||||
|   image_->setPixmap(task->captured); | ||||
|   recognized_->setText(task->recognized); | ||||
|   translated_->setText(task->translated); | ||||
| 
 | ||||
|   const auto gotTranslation = !task->translated.isEmpty(); | ||||
|   translated_->setVisible(gotTranslation); | ||||
| 
 | ||||
|   show(); | ||||
|   adjustSize(); | ||||
| 
 | ||||
|   QDesktopWidget *desktop = QApplication::desktop(); | ||||
|   Q_CHECK_PTR(desktop); | ||||
|   auto correction = QPoint((width() - task->captured.width()) / 2, lineWidth()); | ||||
|   move(task->capturePoint - correction); | ||||
| 
 | ||||
|   auto screenRect = desktop->screenGeometry(this); | ||||
|   auto minY = screenRect.bottom() - height(); | ||||
|   if (y() > minY) { | ||||
|     move(x(), minY); | ||||
|   } | ||||
| 
 | ||||
|   activateWindow(); | ||||
| } | ||||
| 
 | ||||
| bool ResultWidget::eventFilter(QObject *watched, QEvent *event) | ||||
| { | ||||
|   if (event->type() == QEvent::MouseButtonPress) { | ||||
|     const auto button = static_cast<QMouseEvent *>(event)->button(); | ||||
|     if (button == Qt::LeftButton) { | ||||
|       hide(); | ||||
|     } | ||||
|     //    else if (button == Qt::RightButton) {
 | ||||
|     //      QAction *action = contextMenu_->exec(QCursor::pos());
 | ||||
|     //      if (recognizeSubMenu_->findChildren<QAction *>().contains(action)) {
 | ||||
|     //        ProcessingItem item = item_;
 | ||||
|     //        task->translated = task->recognized = QString();
 | ||||
|     //        task->ocrLanguage = dictionary_.ocrUiToCode(action->text());
 | ||||
|     //        emit requestRecognize(item);
 | ||||
|     //      } else if (translateSubMenu_->findChildren<QAction *>().contains(
 | ||||
|     //                     action)) {
 | ||||
|     //        ProcessingItem item = item_;
 | ||||
|     //        task->translated.clear();
 | ||||
|     //        task->translateLanguage =
 | ||||
|     //        dictionary_.translateUiToCode(action->text()); emit
 | ||||
|     //        requestTranslate(item);
 | ||||
|     //      } else if (action == clipboardAction_) {
 | ||||
|     //        emit requestClipboard();
 | ||||
|     //      } else if (action == imageClipboardAction_) {
 | ||||
|     //        emit requestImageClipboard();
 | ||||
|     //      } else if (action == correctAction_) {
 | ||||
|     //        emit requestEdition(item_);
 | ||||
|     //        // Return because Manager calls showResult() before hide()
 | ||||
|     //        otherwise.return QWidget::eventFilter(watched, event);
 | ||||
|     //      }
 | ||||
|     //    }
 | ||||
|     //    hide();
 | ||||
|   } else if (event->type() == QEvent::WindowDeactivate) { | ||||
|     hide(); | ||||
|   } | ||||
|   return QWidget::eventFilter(watched, event); | ||||
| } | ||||
							
								
								
									
										24
									
								
								src/represent/resultwidget.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,24 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include "stfwd.h" | ||||
| 
 | ||||
| #include <QFrame> | ||||
| 
 | ||||
| class QLabel; | ||||
| 
 | ||||
| class ResultWidget : public QFrame | ||||
| { | ||||
|   Q_OBJECT | ||||
| public: | ||||
|   ResultWidget(QWidget* parent = nullptr); | ||||
| 
 | ||||
|   void show(const TaskPtr& task); | ||||
|   using QWidget::show; | ||||
| 
 | ||||
|   bool eventFilter(QObject* watched, QEvent* event) override; | ||||
| 
 | ||||
| private: | ||||
|   QLabel* image_; | ||||
|   QLabel* recognized_; | ||||
|   QLabel* translated_; | ||||
| }; | ||||
| @ -1,121 +0,0 @@ | ||||
| #include "resultdialog.h" | ||||
| #include "ui_resultdialog.h" | ||||
| #include "stassert.h" | ||||
| #include "languagehelper.h" | ||||
| 
 | ||||
| #include <QDesktopWidget> | ||||
| #include <QMouseEvent> | ||||
| #include <QMenu> | ||||
| 
 | ||||
| ResultDialog::ResultDialog (const LanguageHelper &dictionary, QWidget *parent) : | ||||
|   QDialog (parent), | ||||
|   ui (new Ui::ResultDialog), | ||||
|   dictionary_ (dictionary), | ||||
|   contextMenu_ (NULL), recognizeSubMenu_ (NULL),  translateSubMenu_ (NULL), | ||||
|   clipboardAction_ (NULL), imageClipboardAction_ (NULL), correctAction_ (NULL) { | ||||
|   ui->setupUi (this); | ||||
|   setWindowFlags (Qt::FramelessWindowHint | Qt::NoDropShadowWindowHint | | ||||
|                   Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint); | ||||
| 
 | ||||
|   QString styleSheet = | ||||
|     "#recognizeLabel, #translateLabel {" | ||||
|     "color: black;" | ||||
|     "background: qlineargradient(x1:0, y1:0, x2:1, y2:1," | ||||
|     "stop:0 darkGray, stop: 0.5 lightGray, stop:1 darkGray);" | ||||
|     "}"; | ||||
|   setStyleSheet (styleSheet); | ||||
| 
 | ||||
|   installEventFilter (this); | ||||
|   createContextMenu (); | ||||
|   applySettings (); | ||||
| } | ||||
| 
 | ||||
| ResultDialog::~ResultDialog () { | ||||
|   delete contextMenu_; | ||||
|   delete ui; | ||||
| } | ||||
| 
 | ||||
| const ProcessingItem &ResultDialog::item () const { | ||||
|   return item_; | ||||
| } | ||||
| 
 | ||||
| void ResultDialog::applySettings () { | ||||
|   dictionary_.updateMenu (recognizeSubMenu_, dictionary_.availableOcrLanguagesUi ()); | ||||
|   dictionary_.updateMenu (translateSubMenu_, dictionary_.translateLanguagesUi ()); | ||||
| } | ||||
| 
 | ||||
| void ResultDialog::createContextMenu () { | ||||
|   contextMenu_ = new QMenu (); | ||||
|   recognizeSubMenu_ = contextMenu_->addMenu (tr ("Распознать другой язык")); | ||||
|   translateSubMenu_ = contextMenu_->addMenu (tr ("Перевести на другой язык")); | ||||
|   clipboardAction_ = contextMenu_->addAction (tr ("Скопировать в буфер")); | ||||
|   imageClipboardAction_ = contextMenu_->addAction (tr ("Скопировать рисунок в буфер")); | ||||
|   correctAction_ = contextMenu_->addAction (tr ("Исправить распознанный текст")); | ||||
| } | ||||
| 
 | ||||
| bool ResultDialog::eventFilter (QObject *object, QEvent *event) { | ||||
|   Q_UNUSED (object); | ||||
|   if (event->type () == QEvent::MouseButtonPress) { | ||||
|     Qt::MouseButton button = static_cast<QMouseEvent *>(event)->button (); | ||||
|     if (button == Qt::RightButton) { | ||||
|       QAction *action = contextMenu_->exec (QCursor::pos ()); | ||||
|       if (recognizeSubMenu_->findChildren<QAction *> ().contains (action)) { | ||||
|         ProcessingItem item = item_; | ||||
|         item.translated = item.recognized = QString (); | ||||
|         item.ocrLanguage = dictionary_.ocrUiToCode (action->text ()); | ||||
|         emit requestRecognize (item); | ||||
|       } | ||||
|       else if (translateSubMenu_->findChildren<QAction *> ().contains (action)) { | ||||
|         ProcessingItem item = item_; | ||||
|         item.translated.clear (); | ||||
|         item.translateLanguage = dictionary_.translateUiToCode (action->text ()); | ||||
|         emit requestTranslate (item); | ||||
|       } | ||||
|       else if (action == clipboardAction_) { | ||||
|         emit requestClipboard (); | ||||
|       } | ||||
|       else if (action == imageClipboardAction_) { | ||||
|         emit requestImageClipboard (); | ||||
|       } | ||||
|       else if (action == correctAction_) { | ||||
|         emit requestEdition (item_); | ||||
|         // Return because Manager calls showResult() before hide() otherwise.
 | ||||
|         return QDialog::eventFilter (object, event); | ||||
|       } | ||||
|     } | ||||
|     hide (); | ||||
|   } | ||||
|   else if (event->type () == QEvent::WindowDeactivate) { | ||||
|     hide (); | ||||
|   } | ||||
|   return QDialog::eventFilter (object, event); | ||||
| } | ||||
| 
 | ||||
| void ResultDialog::showResult (ProcessingItem item) { | ||||
|   ST_ASSERT (item.isValid ()); | ||||
|   item_ = item; | ||||
|   ui->sourceLabel->setPixmap (item.source); | ||||
|   ui->recognizeLabel->setText (item.recognized); | ||||
|   ui->translateLabel->setText (item.translated); | ||||
|   bool gotTranslation = !item.translated.isEmpty (); | ||||
|   ui->translateLabel->setVisible (gotTranslation); | ||||
|   ui->translateLine->setVisible (gotTranslation); | ||||
| 
 | ||||
|   show (); | ||||
|   adjustSize (); | ||||
| #ifdef Q_OS_LINUX | ||||
|   hide (); // buggy otherwise (on some systems)
 | ||||
|   show (); | ||||
| #endif | ||||
| 
 | ||||
|   QDesktopWidget *desktop = QApplication::desktop (); | ||||
|   Q_CHECK_PTR (desktop); | ||||
|   QPoint correction = QPoint ((width () - item.source.width ()) / 2, ui->frame->lineWidth ()); | ||||
|   move (item.screenPos - correction); | ||||
|   QRect screenRect = desktop->screenGeometry (this); | ||||
|   int minY = screenRect.bottom () - height (); | ||||
|   if (y () > minY) { | ||||
|     move (x (), minY); | ||||
|   } | ||||
|   activateWindow (); | ||||
| } | ||||
| @ -1,51 +0,0 @@ | ||||
| #ifndef RESULTDIALOG_H | ||||
| #define RESULTDIALOG_H | ||||
| 
 | ||||
| #include <QDialog> | ||||
| #include <QMenu> | ||||
| 
 | ||||
| #include "processingitem.h" | ||||
| 
 | ||||
| namespace Ui { | ||||
|   class ResultDialog; | ||||
| } | ||||
| class LanguageHelper; | ||||
| 
 | ||||
| class ResultDialog : public QDialog { | ||||
|   Q_OBJECT | ||||
| 
 | ||||
|   public: | ||||
|     explicit ResultDialog (const LanguageHelper &dictionary, QWidget *parent = 0); | ||||
|     ~ResultDialog (); | ||||
| 
 | ||||
|   signals: | ||||
|     void requestRecognize (ProcessingItem item); | ||||
|     void requestTranslate (ProcessingItem item); | ||||
|     void requestClipboard (); // Assume that slot will be called immediately.
 | ||||
|     void requestImageClipboard (); // Assume that slot will be called immediately.
 | ||||
|     void requestEdition (ProcessingItem item); | ||||
| 
 | ||||
|   public: | ||||
|     const ProcessingItem &item () const; | ||||
|     bool eventFilter (QObject *object, QEvent *event); | ||||
| 
 | ||||
|   public slots: | ||||
|     void showResult (ProcessingItem item); | ||||
|     void applySettings (); | ||||
| 
 | ||||
|   private: | ||||
|     void createContextMenu (); | ||||
| 
 | ||||
|   private: | ||||
|     Ui::ResultDialog *ui; | ||||
|     const LanguageHelper &dictionary_; | ||||
|     QMenu *contextMenu_; | ||||
|     QMenu *recognizeSubMenu_; | ||||
|     QMenu *translateSubMenu_; | ||||
|     QAction *clipboardAction_; | ||||
|     QAction *imageClipboardAction_; | ||||
|     QAction *correctAction_; | ||||
|     ProcessingItem item_; | ||||
| }; | ||||
| 
 | ||||
| #endif // RESULTDIALOG_H
 | ||||
| @ -1,116 +0,0 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <ui version="4.0"> | ||||
|  <class>ResultDialog</class> | ||||
|  <widget class="QDialog" name="ResultDialog"> | ||||
|   <property name="geometry"> | ||||
|    <rect> | ||||
|     <x>0</x> | ||||
|     <y>0</y> | ||||
|     <width>260</width> | ||||
|     <height>222</height> | ||||
|    </rect> | ||||
|   </property> | ||||
|   <property name="windowTitle"> | ||||
|    <string>Результат</string> | ||||
|   </property> | ||||
|   <layout class="QGridLayout" name="gridLayout"> | ||||
|    <property name="leftMargin"> | ||||
|     <number>0</number> | ||||
|    </property> | ||||
|    <property name="topMargin"> | ||||
|     <number>0</number> | ||||
|    </property> | ||||
|    <property name="rightMargin"> | ||||
|     <number>0</number> | ||||
|    </property> | ||||
|    <property name="bottomMargin"> | ||||
|     <number>0</number> | ||||
|    </property> | ||||
|    <property name="spacing"> | ||||
|     <number>0</number> | ||||
|    </property> | ||||
|    <item row="0" column="0"> | ||||
|     <widget class="QFrame" name="frame"> | ||||
|      <property name="frameShape"> | ||||
|       <enum>QFrame::StyledPanel</enum> | ||||
|      </property> | ||||
|      <property name="frameShadow"> | ||||
|       <enum>QFrame::Plain</enum> | ||||
|      </property> | ||||
|      <layout class="QGridLayout" name="gridLayout_2"> | ||||
|       <property name="leftMargin"> | ||||
|        <number>0</number> | ||||
|       </property> | ||||
|       <property name="topMargin"> | ||||
|        <number>0</number> | ||||
|       </property> | ||||
|       <property name="rightMargin"> | ||||
|        <number>0</number> | ||||
|       </property> | ||||
|       <property name="bottomMargin"> | ||||
|        <number>0</number> | ||||
|       </property> | ||||
|       <property name="spacing"> | ||||
|        <number>0</number> | ||||
|       </property> | ||||
|       <item row="0" column="0"> | ||||
|        <widget class="QLabel" name="sourceLabel"> | ||||
|         <property name="text"> | ||||
|          <string/> | ||||
|         </property> | ||||
|         <property name="alignment"> | ||||
|          <set>Qt::AlignCenter</set> | ||||
|         </property> | ||||
|         <property name="wordWrap"> | ||||
|          <bool>true</bool> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item row="1" column="0"> | ||||
|        <widget class="Line" name="recognizeLine"> | ||||
|         <property name="orientation"> | ||||
|          <enum>Qt::Horizontal</enum> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item row="2" column="0"> | ||||
|        <widget class="QLabel" name="recognizeLabel"> | ||||
|         <property name="text"> | ||||
|          <string/> | ||||
|         </property> | ||||
|         <property name="alignment"> | ||||
|          <set>Qt::AlignCenter</set> | ||||
|         </property> | ||||
|         <property name="wordWrap"> | ||||
|          <bool>true</bool> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item row="3" column="0"> | ||||
|        <widget class="Line" name="translateLine"> | ||||
|         <property name="orientation"> | ||||
|          <enum>Qt::Horizontal</enum> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item row="4" column="0"> | ||||
|        <widget class="QLabel" name="translateLabel"> | ||||
|         <property name="text"> | ||||
|          <string/> | ||||
|         </property> | ||||
|         <property name="alignment"> | ||||
|          <set>Qt::AlignCenter</set> | ||||
|         </property> | ||||
|         <property name="wordWrap"> | ||||
|          <bool>true</bool> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|      </layout> | ||||
|     </widget> | ||||
|    </item> | ||||
|   </layout> | ||||
|  </widget> | ||||
|  <resources/> | ||||
|  <connections/> | ||||
| </ui> | ||||
| @ -1,122 +0,0 @@ | ||||
| #include "selectiondialog.h" | ||||
| #include "ui_selectiondialog.h" | ||||
| #include "languagehelper.h" | ||||
| #include "stassert.h" | ||||
| 
 | ||||
| #include <QMouseEvent> | ||||
| #include <QPainter> | ||||
| #include <QDebug> | ||||
| #include <QMenu> | ||||
| 
 | ||||
| SelectionDialog::SelectionDialog (const LanguageHelper &dictionary, QWidget *parent) : | ||||
|   QDialog (parent), | ||||
|   ui (new Ui::SelectionDialog), dictionary_ (dictionary), | ||||
|   languageMenu_ (new QMenu), swapLanguagesAction_ (NULL) { | ||||
|   ui->setupUi (this); | ||||
|   setWindowFlags (Qt::FramelessWindowHint | Qt::NoDropShadowWindowHint | | ||||
|                   Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint); | ||||
| 
 | ||||
|   ui->label->setAutoFillBackground (false); | ||||
|   ui->label->installEventFilter (this); | ||||
| 
 | ||||
|   applySettings (); | ||||
| } | ||||
| 
 | ||||
| SelectionDialog::~SelectionDialog () { | ||||
|   delete languageMenu_; | ||||
|   delete ui; | ||||
| } | ||||
| 
 | ||||
| void SelectionDialog::applySettings () { | ||||
|   dictionary_.updateMenu (languageMenu_, dictionary_.availableOcrLanguagesUi ()); | ||||
|   if (!languageMenu_->isEmpty ()) { | ||||
|     swapLanguagesAction_ = languageMenu_->addAction (tr ("Поменять язык текста и перевода")); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| bool SelectionDialog::eventFilter (QObject *object, QEvent *event) { | ||||
|   if (object != ui->label) { | ||||
|     return QDialog::eventFilter (object, event); | ||||
|   } | ||||
| 
 | ||||
|   if (event->type () == QEvent::Show) { | ||||
|     startSelectPos_ = currentSelectPos_ = QPoint (); | ||||
|   } | ||||
|   else if (event->type () == QEvent::MouseButtonPress) { | ||||
|     QMouseEvent *mouseEvent = static_cast <QMouseEvent *> (event); | ||||
|     if ((mouseEvent->button () == Qt::LeftButton || | ||||
|          mouseEvent->button () == Qt::RightButton) && startSelectPos_.isNull ()) { | ||||
|       startSelectPos_ = mouseEvent->pos (); | ||||
|     } | ||||
|   } | ||||
|   else if (event->type () == QEvent::MouseMove) { | ||||
|     QMouseEvent *mouseEvent = static_cast <QMouseEvent *> (event); | ||||
|     if ((mouseEvent->buttons () & Qt::LeftButton || | ||||
|          mouseEvent->buttons () & Qt::RightButton) && !startSelectPos_.isNull ()) { | ||||
|       currentSelectPos_ = mouseEvent->pos (); | ||||
|       ui->label->repaint (); | ||||
|     } | ||||
|   } | ||||
|   else if (event->type () == QEvent::Paint) { | ||||
|     QRect selection = QRect (startSelectPos_, currentSelectPos_).normalized (); | ||||
|     if (selection.isValid ()) { | ||||
|       QPainter painter (ui->label); | ||||
|       painter.setPen (Qt::red); | ||||
|       painter.drawRect (selection); | ||||
|     } | ||||
|   } | ||||
|   else if (event->type () == QEvent::MouseButtonRelease) { | ||||
|     QMouseEvent *mouseEvent = static_cast <QMouseEvent *> (event); | ||||
|     if (mouseEvent->button () == Qt::LeftButton || | ||||
|         mouseEvent->button () == Qt::RightButton) { | ||||
|       if (startSelectPos_.isNull () || currentPixmap_.isNull ()) { | ||||
|         return QDialog::eventFilter (object, event); | ||||
|       } | ||||
|       QPoint endPos = mouseEvent->pos (); | ||||
|       QRect selection = QRect (startSelectPos_, endPos).normalized (); | ||||
|       startSelectPos_ = currentSelectPos_ = QPoint (); | ||||
|       QPixmap selectedPixmap = currentPixmap_.copy (selection); | ||||
|       if (selectedPixmap.width () < 3 || selectedPixmap.height () < 3) { | ||||
|         reject (); | ||||
|         return QDialog::eventFilter (object, event); | ||||
|       } | ||||
|       ProcessingItem item; | ||||
|       item.source = selectedPixmap; | ||||
|       item.screenPos = pos () + selection.topLeft (); | ||||
|       item.modifiers = mouseEvent->modifiers (); | ||||
| 
 | ||||
|       if (mouseEvent->button () == Qt::RightButton && | ||||
|           !languageMenu_->children ().isEmpty ()) { | ||||
|         QAction *action = languageMenu_->exec (QCursor::pos ()); | ||||
|         if (action == NULL) { | ||||
|           reject (); | ||||
|           return QDialog::eventFilter (object, event); | ||||
|         } | ||||
|         if (action == swapLanguagesAction_) { | ||||
|           item.swapLanguages_ = true; | ||||
|         } | ||||
|         else { | ||||
|           item.ocrLanguage = dictionary_.ocrUiToCode (action->text ()); | ||||
|           ST_ASSERT (!item.ocrLanguage.isEmpty ()); | ||||
|           item.sourceLanguage = dictionary_.ocrToTranslateCodes (item.ocrLanguage); | ||||
|           ST_ASSERT (!item.sourceLanguage.isEmpty ()); | ||||
|         } | ||||
|       } | ||||
|       emit selected (item); | ||||
|     } | ||||
|   } | ||||
|   return QDialog::eventFilter (object, event); | ||||
| } | ||||
| 
 | ||||
| void SelectionDialog::setPixmap (QPixmap pixmap, const QRect &showGeometry) { | ||||
|   ST_ASSERT (!pixmap.isNull ()); | ||||
|   ST_ASSERT (!showGeometry.isEmpty ()); | ||||
|   currentPixmap_ = pixmap; | ||||
|   QPalette palette = this->palette (); | ||||
|   palette.setBrush (this->backgroundRole (), pixmap); | ||||
|   this->setPalette (palette); | ||||
|   this->setGeometry (showGeometry); | ||||
| 
 | ||||
|   show (); | ||||
|   activateWindow (); | ||||
| } | ||||
| @ -1,43 +0,0 @@ | ||||
| #ifndef SELECTIONDIALOG_H | ||||
| #define SELECTIONDIALOG_H | ||||
| 
 | ||||
| #include <QDialog> | ||||
| #include <QPixmap> | ||||
| #include <QMenu> | ||||
| 
 | ||||
| #include "processingitem.h" | ||||
| 
 | ||||
| namespace Ui { | ||||
|   class SelectionDialog; | ||||
| } | ||||
| class LanguageHelper; | ||||
| 
 | ||||
| class SelectionDialog : public QDialog { | ||||
|   Q_OBJECT | ||||
| 
 | ||||
|   public: | ||||
|     explicit SelectionDialog (const LanguageHelper &dictionary, QWidget *parent = 0); | ||||
|     ~SelectionDialog (); | ||||
| 
 | ||||
|     bool eventFilter (QObject *object, QEvent *event); | ||||
| 
 | ||||
|   signals: | ||||
|     void selected (ProcessingItem pixmap); | ||||
|     void nothingSelected (); | ||||
| 
 | ||||
|   public slots: | ||||
|     //! Show pixmap with given geometry.
 | ||||
|     void setPixmap (QPixmap pixmap, const QRect &showGeometry); | ||||
|     void applySettings (); | ||||
| 
 | ||||
|   private: | ||||
|     Ui::SelectionDialog *ui; | ||||
|     const LanguageHelper &dictionary_; | ||||
|     QPoint startSelectPos_; | ||||
|     QPoint currentSelectPos_; | ||||
|     QPixmap currentPixmap_; | ||||
|     QMenu *languageMenu_; | ||||
|     QAction *swapLanguagesAction_; | ||||
| }; | ||||
| 
 | ||||
| #endif // SELECTIONDIALOG_H
 | ||||
| @ -1,43 +0,0 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <ui version="4.0"> | ||||
|  <class>SelectionDialog</class> | ||||
|  <widget class="QDialog" name="SelectionDialog"> | ||||
|   <property name="geometry"> | ||||
|    <rect> | ||||
|     <x>0</x> | ||||
|     <y>0</y> | ||||
|     <width>400</width> | ||||
|     <height>300</height> | ||||
|    </rect> | ||||
|   </property> | ||||
|   <property name="windowTitle"> | ||||
|    <string>Dialog</string> | ||||
|   </property> | ||||
|   <layout class="QGridLayout" name="gridLayout"> | ||||
|    <property name="leftMargin"> | ||||
|     <number>0</number> | ||||
|    </property> | ||||
|    <property name="topMargin"> | ||||
|     <number>0</number> | ||||
|    </property> | ||||
|    <property name="rightMargin"> | ||||
|     <number>0</number> | ||||
|    </property> | ||||
|    <property name="bottomMargin"> | ||||
|     <number>0</number> | ||||
|    </property> | ||||
|    <item row="0" column="0"> | ||||
|     <widget class="QLabel" name="label"> | ||||
|      <property name="cursor"> | ||||
|       <cursorShape>CrossCursor</cursorShape> | ||||
|      </property> | ||||
|      <property name="text"> | ||||
|       <string/> | ||||
|      </property> | ||||
|     </widget> | ||||
|    </item> | ||||
|   </layout> | ||||
|  </widget> | ||||
|  <resources/> | ||||
|  <connections/> | ||||
| </ui> | ||||
							
								
								
									
										131
									
								
								src/service/apptranslator.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,131 @@ | ||||
| #include "apptranslator.h" | ||||
| #include "debug.h" | ||||
| 
 | ||||
| #include <QApplication> | ||||
| #include <QDir> | ||||
| #include <QLibraryInfo> | ||||
| #include <QTranslator> | ||||
| 
 | ||||
| namespace | ||||
| { | ||||
| // const auto builtin = QLocale::English;
 | ||||
| }  // namespace
 | ||||
| 
 | ||||
| AppTranslator::AppTranslator(const QStringList &translationFiles) | ||||
|   : translationFiles_(translationFiles) | ||||
| { | ||||
| } | ||||
| 
 | ||||
| void AppTranslator::retranslate() | ||||
| { | ||||
|   auto app = QCoreApplication::instance(); | ||||
|   const auto oldTranslators = app->findChildren<QTranslator *>(); | ||||
|   for (const auto &old : oldTranslators) { | ||||
|     old->deleteLater(); | ||||
|   } | ||||
| 
 | ||||
|   //  QLocale locale(translation());
 | ||||
|   QLocale locale; | ||||
|   const auto files = | ||||
|       QStringList{QStringLiteral("qt"), QStringLiteral("qtbase")} + | ||||
|       translationFiles_; | ||||
|   const auto paths = searchPaths(); | ||||
| 
 | ||||
|   auto last = new QTranslator(app); | ||||
|   for (const auto &name : files) { | ||||
|     for (const auto &path : paths) { | ||||
|       if (!last->load(locale, name, QLatin1String("_"), path)) | ||||
|         continue; | ||||
|       app->installTranslator(last); | ||||
|       last = new QTranslator(app); | ||||
|       break; | ||||
|     } | ||||
|   } | ||||
|   last->deleteLater(); | ||||
| } | ||||
| 
 | ||||
| // QStringList TranslationLoader::availableLanguages()
 | ||||
| //{
 | ||||
| //  QStringList result{QLocale(builtin).nativeLanguageName()};
 | ||||
| //  auto checker = new QTranslator(QApplication::instance());
 | ||||
| 
 | ||||
| //  for (const auto &dir : searchPaths()) {
 | ||||
| //    for (const auto &file :
 | ||||
| //         QDir(dir).entryInfoList({appTranslation + '*'}, QDir::Files)) {
 | ||||
| //      if (checker->load(file.absoluteFilePath())) {
 | ||||
| //        const auto name = file.baseName();
 | ||||
| //        const auto suffixIndex = name.indexOf(QLatin1Char('_'));
 | ||||
| //        if (suffixIndex < 0) {
 | ||||
| //          continue;
 | ||||
| //        }
 | ||||
| //        const auto suffix = name.mid(suffixIndex + 1);
 | ||||
| //        const auto locale = QLocale(suffix);
 | ||||
| //        const auto language = locale.nativeLanguageName();
 | ||||
| //        if (!result.contains(language)) {
 | ||||
| //          result.append(language);
 | ||||
| //        }
 | ||||
| //      }
 | ||||
| //    }
 | ||||
| //  }
 | ||||
| //  return result;
 | ||||
| //}
 | ||||
| 
 | ||||
| // QString TranslationLoader::language()
 | ||||
| //{
 | ||||
| //  return toLanguage(translation());
 | ||||
| //}
 | ||||
| 
 | ||||
| // void TranslationLoader::setLanguage(const QString &language)
 | ||||
| //{
 | ||||
| //  setTranslation(toTranslation(language));
 | ||||
| //}
 | ||||
| 
 | ||||
| // QString TranslationLoader::translation()
 | ||||
| //{
 | ||||
| //  SettingsManager settings;
 | ||||
| //  auto name = settings.get(SettingsManager::Translation).toString();
 | ||||
| //  if (name.isEmpty()) {
 | ||||
| //    const QLocale locale;
 | ||||
| //    if (locale.language() == QLocale::Language::C) {
 | ||||
| //      name = QLocale(builtin).name();
 | ||||
| //    } else {
 | ||||
| //      name = locale.name();
 | ||||
| //    }
 | ||||
| //  }
 | ||||
| //  return name;
 | ||||
| //}
 | ||||
| 
 | ||||
| // void TranslationLoader::setTranslation(const QString &translation)
 | ||||
| //{
 | ||||
| //  SettingsManager settings;
 | ||||
| //  settings.set(SettingsManager::Translation, translation);
 | ||||
| //}
 | ||||
| 
 | ||||
| QStringList AppTranslator::searchPaths() const | ||||
| { | ||||
|   return QStringList{ | ||||
|       QLibraryInfo::location(QLibraryInfo::TranslationsPath), | ||||
| #ifdef Q_OS_LINUX | ||||
|       qgetenv("APPDIR") + | ||||
|           QLibraryInfo::location(QLibraryInfo::TranslationsPath),  // appimage
 | ||||
| #endif  // ifdef Q_OS_LINUX
 | ||||
|       {}, | ||||
|       QLatin1String("translations"), | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| // QString TranslationLoader::toLanguage(const QString &translation)
 | ||||
| //{
 | ||||
| //  return QLocale(translation).nativeLanguageName();
 | ||||
| //}
 | ||||
| 
 | ||||
| // QString TranslationLoader::toTranslation(const QString &language)
 | ||||
| //{
 | ||||
| //  for (auto i = 0; i < QLocale::Language::LastLanguage; ++i) {
 | ||||
| //    const auto locale = QLocale(QLocale::Language(i));
 | ||||
| //    if (locale.nativeLanguageName() == language) {
 | ||||
| //      return locale.name();
 | ||||
| //    }
 | ||||
| //  }
 | ||||
| //  return QLocale().name();
 | ||||
| //}
 | ||||
							
								
								
									
										25
									
								
								src/service/apptranslator.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,25 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <QStringList> | ||||
| 
 | ||||
| class AppTranslator | ||||
| { | ||||
| public: | ||||
|   explicit AppTranslator(const QStringList &translationFiles); | ||||
| 
 | ||||
|   void retranslate(); | ||||
| 
 | ||||
|   //  static QStringList availableLanguages();
 | ||||
|   //  static QString language();
 | ||||
|   //  static void setLanguage(const QString &language);
 | ||||
| 
 | ||||
| private: | ||||
|   //  static QString translation();
 | ||||
|   //  static void setTranslation(const QString &translation);
 | ||||
|   QStringList searchPaths() const; | ||||
| 
 | ||||
|   //  static QString toTranslation(const QString &language);
 | ||||
|   //  static QString toLanguage(const QString &translation);
 | ||||
| 
 | ||||
|   QStringList translationFiles_; | ||||
| }; | ||||
							
								
								
									
										6
									
								
								src/service/debug.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,6 @@ | ||||
| #include "debug.h" | ||||
| 
 | ||||
| namespace debug | ||||
| { | ||||
| std::atomic_bool isTrace; | ||||
| } | ||||
							
								
								
									
										71
									
								
								src/service/debug.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,71 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <QDebug> | ||||
| 
 | ||||
| #define SOFT_ASSERT(XXX, WORKAROUND)                                         \ | ||||
|   if (!(XXX)) {                                                              \ | ||||
|     qCritical() << "Soft assertion failed at" << __FILE__ << __LINE__ << ":" \ | ||||
|                 << #XXX;                                                     \ | ||||
|     WORKAROUND;                                                              \ | ||||
|   } | ||||
| 
 | ||||
| #define ASSERT(XXX)                                                     \ | ||||
|   if (!(XXX)) {                                                         \ | ||||
|     qCritical() << "Assertion failed at" << __FILE__ << __LINE__ << ":" \ | ||||
|                 << #XXX;                                                \ | ||||
|     Q_ASSERT(XXX);                                                      \ | ||||
|   } | ||||
| 
 | ||||
| #define ASSERT_X(XXX, CONTEXT)                                          \ | ||||
|   if (!(XXX)) {                                                         \ | ||||
|     qCritical() << "Assertion failed at" << __FILE__ << __LINE__ << ":" \ | ||||
|                 << #XXX << "Context (" << #CONTEXT << ")" << CONTEXT;   \ | ||||
|     Q_ASSERT(XXX);                                                      \ | ||||
|   } | ||||
| 
 | ||||
| namespace debug | ||||
| { | ||||
| extern std::atomic_bool isTrace; | ||||
| }; | ||||
| 
 | ||||
| #define LTRACE()      \ | ||||
|   if (debug::isTrace) \ | ||||
|   qDebug() | ||||
| 
 | ||||
| #define LTRACE_IF(XXX)       \ | ||||
|   if (debug::isTrace && XXX) \ | ||||
|   qDebug() | ||||
| 
 | ||||
| #define LDEBUG() qDebug() | ||||
| 
 | ||||
| #define LDEBUG_IF(XXX) \ | ||||
|   if (XXX)             \ | ||||
|   qDebug() | ||||
| 
 | ||||
| #define LWARNING() qWarning() | ||||
| 
 | ||||
| #define LWARNING_IF(XXX) \ | ||||
|   if (XXX)               \ | ||||
|   qWarning() | ||||
| 
 | ||||
| #define LERROR() qCritical() | ||||
| 
 | ||||
| #define LERROR_IF(XXX) \ | ||||
|   if (XXX)             \ | ||||
|   qCritical() | ||||
| 
 | ||||
| #define LINFO() qInfo() | ||||
| 
 | ||||
| #define LINFO_IF(XXX) \ | ||||
|   if (XXX)            \ | ||||
|   qInfo() | ||||
| 
 | ||||
| #define LFATAL() qFatal | ||||
| 
 | ||||
| #define LFATAL_IF(XXX) \ | ||||
|   if (XXX)             \ | ||||
|   qFatal | ||||
| 
 | ||||
| #define LARG(XXX) #XXX "=" << XXX | ||||
| 
 | ||||
| #define LARG_N(NAME, XXX) NAME << '=' << XXX | ||||
							
								
								
									
										481
									
								
								src/service/globalaction.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,481 @@ | ||||
| #include "globalaction.h" | ||||
| #include "debug.h" | ||||
| 
 | ||||
| #include <QApplication> | ||||
| 
 | ||||
| QHash<QPair<quint32, quint32>, QAction *> GlobalAction::actions_; | ||||
| 
 | ||||
| void GlobalAction::init() | ||||
| { | ||||
|   qApp->installNativeEventFilter(new GlobalAction); | ||||
| } | ||||
| 
 | ||||
| bool GlobalAction::makeGlobal(QAction *action) | ||||
| { | ||||
|   QKeySequence hotKey = action->shortcut(); | ||||
|   if (hotKey.isEmpty()) | ||||
|     return true; | ||||
|   Qt::KeyboardModifiers allMods = Qt::ShiftModifier | Qt::ControlModifier | | ||||
|                                   Qt::AltModifier | Qt::MetaModifier; | ||||
|   Qt::Key key = hotKey.isEmpty() ? Qt::Key(0) | ||||
|                                  : Qt::Key((hotKey[0] ^ allMods) & hotKey[0]); | ||||
|   Qt::KeyboardModifiers mods = hotKey.isEmpty() | ||||
|                                    ? Qt::KeyboardModifiers(0) | ||||
|                                    : Qt::KeyboardModifiers(hotKey[0] & allMods); | ||||
|   const quint32 nativeKey = nativeKeycode(key); | ||||
|   const quint32 nativeMods = nativeModifiers(mods); | ||||
|   const bool res = registerHotKey(nativeKey, nativeMods); | ||||
|   if (res) | ||||
|     actions_.insert(qMakePair(nativeKey, nativeMods), action); | ||||
|   else | ||||
|     LERROR() << "Failed to register global hotkey:" << LARG(hotKey.toString()); | ||||
|   return res; | ||||
| } | ||||
| 
 | ||||
| bool GlobalAction::removeGlobal(QAction *action) | ||||
| { | ||||
|   QKeySequence hotKey = action->shortcut(); | ||||
|   if (hotKey.isEmpty()) | ||||
|     return true; | ||||
|   Qt::KeyboardModifiers allMods = Qt::ShiftModifier | Qt::ControlModifier | | ||||
|                                   Qt::AltModifier | Qt::MetaModifier; | ||||
|   Qt::Key key = hotKey.isEmpty() ? Qt::Key(0) | ||||
|                                  : Qt::Key((hotKey[0] ^ allMods) & hotKey[0]); | ||||
|   Qt::KeyboardModifiers mods = hotKey.isEmpty() | ||||
|                                    ? Qt::KeyboardModifiers(0) | ||||
|                                    : Qt::KeyboardModifiers(hotKey[0] & allMods); | ||||
|   const quint32 nativeKey = nativeKeycode(key); | ||||
|   const quint32 nativeMods = nativeModifiers(mods); | ||||
|   if (!actions_.contains(qMakePair(nativeKey, nativeMods))) | ||||
|     return true; | ||||
|   const bool res = unregisterHotKey(nativeKey, nativeMods); | ||||
|   if (res) | ||||
|     actions_.remove(qMakePair(nativeKey, nativeMods)); | ||||
|   else | ||||
|     LERROR() << "Failed to unregister global hotkey:" << (hotKey.toString()); | ||||
|   return res; | ||||
| } | ||||
| 
 | ||||
| bool GlobalAction::update(QAction *action, const QKeySequence &newShortcut) | ||||
| { | ||||
|   if (!action->shortcut().isEmpty()) | ||||
|     removeGlobal(action); | ||||
|   action->setShortcut(newShortcut); | ||||
|   return newShortcut.isEmpty() ? true : makeGlobal(action); | ||||
| } | ||||
| 
 | ||||
| void GlobalAction::triggerHotKey(quint32 nativeKey, quint32 nativeMods) | ||||
| { | ||||
|   QAction *action = actions_.value(qMakePair(nativeKey, nativeMods)); | ||||
|   if (action && action->isEnabled()) | ||||
|     action->activate(QAction::Trigger); | ||||
| } | ||||
| 
 | ||||
| #ifdef Q_OS_LINUX | ||||
| #include <X11/Xlib.h> | ||||
| #include <xcb/xcb_event.h> | ||||
| #include <QX11Info> | ||||
| 
 | ||||
| static bool error = false; | ||||
| 
 | ||||
| static int customHandler(Display *display, XErrorEvent *event) | ||||
| { | ||||
|   Q_UNUSED(display); | ||||
|   switch (event->error_code) { | ||||
|     case BadAccess: | ||||
|     case BadValue: | ||||
|     case BadWindow: | ||||
|       if (event->request_code == 33 /* X_GrabKey */ || | ||||
|           event->request_code == 34 /* X_UngrabKey */) { | ||||
|         error = true; | ||||
|       } | ||||
|     default: return 0; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| bool GlobalAction::registerHotKey(quint32 nativeKey, quint32 nativeMods) | ||||
| { | ||||
|   Display *display = QX11Info::display(); | ||||
|   Window window = QX11Info::appRootWindow(); | ||||
|   Bool owner = True; | ||||
|   int pointer = GrabModeAsync; | ||||
|   int keyboard = GrabModeAsync; | ||||
|   error = false; | ||||
|   int (*handler)(Display * display, XErrorEvent * event) = | ||||
|       XSetErrorHandler(customHandler); | ||||
|   XGrabKey(display, nativeKey, nativeMods, window, owner, pointer, keyboard); | ||||
|   // allow numlock
 | ||||
|   XGrabKey(display, nativeKey, nativeMods | Mod2Mask, window, owner, pointer, | ||||
|            keyboard); | ||||
|   XSync(display, False); | ||||
|   XSetErrorHandler(handler); | ||||
|   return !error; | ||||
| } | ||||
| 
 | ||||
| bool GlobalAction::unregisterHotKey(quint32 nativeKey, quint32 nativeMods) | ||||
| { | ||||
|   Display *display = QX11Info::display(); | ||||
|   Window window = QX11Info::appRootWindow(); | ||||
|   error = false; | ||||
|   int (*handler)(Display * display, XErrorEvent * event) = | ||||
|       XSetErrorHandler(customHandler); | ||||
|   XUngrabKey(display, nativeKey, nativeMods, window); | ||||
|   // allow numlock
 | ||||
|   XUngrabKey(display, nativeKey, nativeMods | Mod2Mask, window); | ||||
|   XSync(display, False); | ||||
|   XSetErrorHandler(handler); | ||||
|   return !error; | ||||
| } | ||||
| 
 | ||||
| bool GlobalAction::nativeEventFilter(const QByteArray &eventType, void *message, | ||||
|                                      long *result) | ||||
| { | ||||
|   Q_UNUSED(eventType); | ||||
|   Q_UNUSED(result); | ||||
|   xcb_generic_event_t *event = static_cast<xcb_generic_event_t *>(message); | ||||
|   if (event->response_type == XCB_KEY_PRESS) { | ||||
|     xcb_key_press_event_t *keyEvent = | ||||
|         static_cast<xcb_key_press_event_t *>(message); | ||||
|     const quint32 keycode = keyEvent->detail; | ||||
|     const quint32 modifiers = keyEvent->state & ~XCB_MOD_MASK_2; | ||||
|     triggerHotKey(keycode, modifiers); | ||||
|   } | ||||
|   return false; | ||||
| } | ||||
| 
 | ||||
| quint32 GlobalAction::nativeKeycode(Qt::Key key) | ||||
| { | ||||
|   Display *display = QX11Info::display(); | ||||
|   KeySym keySym = XStringToKeysym(qPrintable(QKeySequence(key).toString())); | ||||
|   return XKeysymToKeycode(display, keySym); | ||||
| } | ||||
| 
 | ||||
| quint32 GlobalAction::nativeModifiers(Qt::KeyboardModifiers modifiers) | ||||
| { | ||||
|   quint32 native = 0; | ||||
|   if (modifiers & Qt::ShiftModifier) | ||||
|     native |= ShiftMask; | ||||
|   if (modifiers & Qt::ControlModifier) | ||||
|     native |= ControlMask; | ||||
|   if (modifiers & Qt::AltModifier) | ||||
|     native |= Mod1Mask; | ||||
|   if (modifiers & Qt::MetaModifier) | ||||
|     native |= Mod4Mask; | ||||
|   return native; | ||||
| } | ||||
| 
 | ||||
| #endif  // ifdef Q_OS_LINUX
 | ||||
| 
 | ||||
| #ifdef Q_OS_WIN | ||||
| #include <qt_windows.h> | ||||
| 
 | ||||
| bool GlobalAction::registerHotKey(quint32 nativeKey, quint32 nativeMods) | ||||
| { | ||||
|   return RegisterHotKey(0, nativeMods ^ nativeKey, nativeMods, nativeKey); | ||||
| } | ||||
| 
 | ||||
| bool GlobalAction::unregisterHotKey(quint32 nativeKey, quint32 nativeMods) | ||||
| { | ||||
|   return UnregisterHotKey(0, nativeMods ^ nativeKey); | ||||
| } | ||||
| 
 | ||||
| bool GlobalAction::nativeEventFilter(const QByteArray &eventType, void *message, | ||||
|                                      long *result) | ||||
| { | ||||
|   Q_UNUSED(eventType); | ||||
|   Q_UNUSED(result); | ||||
|   MSG *msg = static_cast<MSG *>(message); | ||||
|   if (msg->message == WM_HOTKEY) { | ||||
|     const quint32 keycode = HIWORD(msg->lParam); | ||||
|     const quint32 modifiers = LOWORD(msg->lParam); | ||||
|     triggerHotKey(keycode, modifiers); | ||||
|   } | ||||
|   return false; | ||||
| } | ||||
| 
 | ||||
| quint32 GlobalAction::nativeKeycode(Qt::Key key) | ||||
| { | ||||
|   switch (key) { | ||||
|     case Qt::Key_Escape: return VK_ESCAPE; | ||||
|     case Qt::Key_Tab: | ||||
|     case Qt::Key_Backtab: return VK_TAB; | ||||
|     case Qt::Key_Backspace: return VK_BACK; | ||||
|     case Qt::Key_Return: | ||||
|     case Qt::Key_Enter: return VK_RETURN; | ||||
|     case Qt::Key_Insert: return VK_INSERT; | ||||
|     case Qt::Key_Delete: return VK_DELETE; | ||||
|     case Qt::Key_Pause: return VK_PAUSE; | ||||
|     case Qt::Key_Print: return VK_PRINT; | ||||
|     case Qt::Key_Clear: return VK_CLEAR; | ||||
|     case Qt::Key_Home: return VK_HOME; | ||||
|     case Qt::Key_End: return VK_END; | ||||
|     case Qt::Key_Left: return VK_LEFT; | ||||
|     case Qt::Key_Up: return VK_UP; | ||||
|     case Qt::Key_Right: return VK_RIGHT; | ||||
|     case Qt::Key_Down: return VK_DOWN; | ||||
|     case Qt::Key_PageUp: return VK_PRIOR; | ||||
|     case Qt::Key_PageDown: return VK_NEXT; | ||||
|     case Qt::Key_F1: return VK_F1; | ||||
|     case Qt::Key_F2: return VK_F2; | ||||
|     case Qt::Key_F3: return VK_F3; | ||||
|     case Qt::Key_F4: return VK_F4; | ||||
|     case Qt::Key_F5: return VK_F5; | ||||
|     case Qt::Key_F6: return VK_F6; | ||||
|     case Qt::Key_F7: return VK_F7; | ||||
|     case Qt::Key_F8: return VK_F8; | ||||
|     case Qt::Key_F9: return VK_F9; | ||||
|     case Qt::Key_F10: return VK_F10; | ||||
|     case Qt::Key_F11: return VK_F11; | ||||
|     case Qt::Key_F12: return VK_F12; | ||||
|     case Qt::Key_F13: return VK_F13; | ||||
|     case Qt::Key_F14: return VK_F14; | ||||
|     case Qt::Key_F15: return VK_F15; | ||||
|     case Qt::Key_F16: return VK_F16; | ||||
|     case Qt::Key_F17: return VK_F17; | ||||
|     case Qt::Key_F18: return VK_F18; | ||||
|     case Qt::Key_F19: return VK_F19; | ||||
|     case Qt::Key_F20: return VK_F20; | ||||
|     case Qt::Key_F21: return VK_F21; | ||||
|     case Qt::Key_F22: return VK_F22; | ||||
|     case Qt::Key_F23: return VK_F23; | ||||
|     case Qt::Key_F24: return VK_F24; | ||||
|     case Qt::Key_Space: return VK_SPACE; | ||||
|     case Qt::Key_Asterisk: return VK_MULTIPLY; | ||||
|     case Qt::Key_Plus: return VK_ADD; | ||||
|     case Qt::Key_Comma: return VK_SEPARATOR; | ||||
|     case Qt::Key_Minus: return VK_SUBTRACT; | ||||
|     case Qt::Key_Slash: return VK_DIVIDE; | ||||
|     case Qt::Key_MediaNext: return VK_MEDIA_NEXT_TRACK; | ||||
|     case Qt::Key_MediaPrevious: return VK_MEDIA_PREV_TRACK; | ||||
|     case Qt::Key_MediaPlay: return VK_MEDIA_PLAY_PAUSE; | ||||
|     case Qt::Key_MediaStop: return VK_MEDIA_STOP; | ||||
|     case Qt::Key_VolumeDown: return VK_VOLUME_DOWN; | ||||
|     case Qt::Key_VolumeUp: return VK_VOLUME_UP; | ||||
|     case Qt::Key_VolumeMute: return VK_VOLUME_MUTE; | ||||
| 
 | ||||
|     // numbers
 | ||||
|     case Qt::Key_0: | ||||
|     case Qt::Key_1: | ||||
|     case Qt::Key_2: | ||||
|     case Qt::Key_3: | ||||
|     case Qt::Key_4: | ||||
|     case Qt::Key_5: | ||||
|     case Qt::Key_6: | ||||
|     case Qt::Key_7: | ||||
|     case Qt::Key_8: | ||||
|     case Qt::Key_9: return key; | ||||
| 
 | ||||
|     // letters
 | ||||
|     case Qt::Key_A: | ||||
|     case Qt::Key_B: | ||||
|     case Qt::Key_C: | ||||
|     case Qt::Key_D: | ||||
|     case Qt::Key_E: | ||||
|     case Qt::Key_F: | ||||
|     case Qt::Key_G: | ||||
|     case Qt::Key_H: | ||||
|     case Qt::Key_I: | ||||
|     case Qt::Key_J: | ||||
|     case Qt::Key_K: | ||||
|     case Qt::Key_L: | ||||
|     case Qt::Key_M: | ||||
|     case Qt::Key_N: | ||||
|     case Qt::Key_O: | ||||
|     case Qt::Key_P: | ||||
|     case Qt::Key_Q: | ||||
|     case Qt::Key_R: | ||||
|     case Qt::Key_S: | ||||
|     case Qt::Key_T: | ||||
|     case Qt::Key_U: | ||||
|     case Qt::Key_V: | ||||
|     case Qt::Key_W: | ||||
|     case Qt::Key_X: | ||||
|     case Qt::Key_Y: | ||||
|     case Qt::Key_Z: return key; | ||||
| 
 | ||||
|     default: return 0; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| quint32 GlobalAction::nativeModifiers(Qt::KeyboardModifiers modifiers) | ||||
| { | ||||
|   // MOD_ALT, MOD_CONTROL, (MOD_KEYUP), MOD_SHIFT, MOD_WIN
 | ||||
|   quint32 native = 0; | ||||
|   if (modifiers & Qt::ShiftModifier) | ||||
|     native |= MOD_SHIFT; | ||||
|   if (modifiers & Qt::ControlModifier) | ||||
|     native |= MOD_CONTROL; | ||||
|   if (modifiers & Qt::AltModifier) | ||||
|     native |= MOD_ALT; | ||||
|   if (modifiers & Qt::MetaModifier) | ||||
|     native |= MOD_WIN; | ||||
|   // if (modifiers & Qt::KeypadModifier)
 | ||||
|   // if (modifiers & Qt::GroupSwitchModifier)
 | ||||
|   return native; | ||||
| } | ||||
| 
 | ||||
| #endif  // ifdef Q_OS_WIN
 | ||||
| 
 | ||||
| #ifdef Q_OS_MAC | ||||
| #include <Carbon/Carbon.h> | ||||
| 
 | ||||
| static bool isInited = false; | ||||
| static QHash<QPair<quint32, quint32>, EventHotKeyRef> hotkeyRefs; | ||||
| 
 | ||||
| struct ActionAdapter { | ||||
|   static OSStatus macHandler(EventHandlerCallRef /*nextHandler*/, | ||||
|                              EventRef event, void * /*userData*/) | ||||
|   { | ||||
|     EventHotKeyID id; | ||||
|     GetEventParameter(event, kEventParamDirectObject, typeEventHotKeyID, NULL, | ||||
|                       sizeof(id), NULL, &id); | ||||
|     GlobalAction::triggerHotKey(quint32(id.signature), quint32(id.id)); | ||||
|     return noErr; | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| bool GlobalAction::registerHotKey(quint32 nativeKey, quint32 nativeMods) | ||||
| { | ||||
|   if (!isInited) { | ||||
|     EventTypeSpec spec; | ||||
|     spec.eventClass = kEventClassKeyboard; | ||||
|     spec.eventKind = kEventHotKeyPressed; | ||||
|     InstallApplicationEventHandler(&ActionAdapter::macHandler, 1, &spec, NULL, | ||||
|                                    NULL); | ||||
|     isInited = true; | ||||
|   } | ||||
| 
 | ||||
|   EventHotKeyID id; | ||||
|   id.signature = nativeKey; | ||||
|   id.id = nativeMods; | ||||
| 
 | ||||
|   EventHotKeyRef ref = NULL; | ||||
|   OSStatus status = RegisterEventHotKey(nativeKey, nativeMods, id, | ||||
|                                         GetApplicationEventTarget(), 0, &ref); | ||||
|   if (status != noErr) { | ||||
|     LERROR() << "RegisterEventHotKey error:" << LARG(status); | ||||
|     return false; | ||||
|   } else { | ||||
|     hotkeyRefs.insert(qMakePair(nativeKey, nativeMods), ref); | ||||
|     return true; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| bool GlobalAction::unregisterHotKey(quint32 nativeKey, quint32 nativeMods) | ||||
| { | ||||
|   EventHotKeyRef ref = hotkeyRefs.value(qMakePair(nativeKey, nativeMods)); | ||||
|   ASSERT(ref); | ||||
|   OSStatus status = UnregisterEventHotKey(ref); | ||||
|   if (status != noErr) { | ||||
|     LERROR() << "UnregisterEventHotKey error:" << LARG(status); | ||||
|     return false; | ||||
|   } else { | ||||
|     hotkeyRefs.remove(qMakePair(nativeKey, nativeMods)); | ||||
|     return true; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| bool GlobalAction::nativeEventFilter(const QByteArray & /*eventType*/, | ||||
|                                      void * /*message*/, long * /*result*/) | ||||
| { | ||||
|   return false; | ||||
| } | ||||
| 
 | ||||
| quint32 GlobalAction::nativeKeycode(Qt::Key key) | ||||
| { | ||||
|   switch (key) { | ||||
|     case Qt::Key_A: return kVK_ANSI_A; | ||||
|     case Qt::Key_B: return kVK_ANSI_B; | ||||
|     case Qt::Key_C: return kVK_ANSI_C; | ||||
|     case Qt::Key_D: return kVK_ANSI_D; | ||||
|     case Qt::Key_E: return kVK_ANSI_E; | ||||
|     case Qt::Key_F: return kVK_ANSI_F; | ||||
|     case Qt::Key_G: return kVK_ANSI_G; | ||||
|     case Qt::Key_H: return kVK_ANSI_H; | ||||
|     case Qt::Key_I: return kVK_ANSI_I; | ||||
|     case Qt::Key_J: return kVK_ANSI_J; | ||||
|     case Qt::Key_K: return kVK_ANSI_K; | ||||
|     case Qt::Key_L: return kVK_ANSI_L; | ||||
|     case Qt::Key_M: return kVK_ANSI_M; | ||||
|     case Qt::Key_N: return kVK_ANSI_N; | ||||
|     case Qt::Key_O: return kVK_ANSI_O; | ||||
|     case Qt::Key_P: return kVK_ANSI_P; | ||||
|     case Qt::Key_Q: return kVK_ANSI_Q; | ||||
|     case Qt::Key_R: return kVK_ANSI_R; | ||||
|     case Qt::Key_S: return kVK_ANSI_S; | ||||
|     case Qt::Key_T: return kVK_ANSI_T; | ||||
|     case Qt::Key_U: return kVK_ANSI_U; | ||||
|     case Qt::Key_V: return kVK_ANSI_V; | ||||
|     case Qt::Key_W: return kVK_ANSI_W; | ||||
|     case Qt::Key_X: return kVK_ANSI_X; | ||||
|     case Qt::Key_Y: return kVK_ANSI_Y; | ||||
|     case Qt::Key_Z: return kVK_ANSI_Z; | ||||
|     case Qt::Key_0: return kVK_ANSI_0; | ||||
|     case Qt::Key_1: return kVK_ANSI_1; | ||||
|     case Qt::Key_2: return kVK_ANSI_2; | ||||
|     case Qt::Key_3: return kVK_ANSI_3; | ||||
|     case Qt::Key_4: return kVK_ANSI_4; | ||||
|     case Qt::Key_5: return kVK_ANSI_5; | ||||
|     case Qt::Key_6: return kVK_ANSI_6; | ||||
|     case Qt::Key_7: return kVK_ANSI_7; | ||||
|     case Qt::Key_8: return kVK_ANSI_8; | ||||
|     case Qt::Key_9: return kVK_ANSI_9; | ||||
|     case Qt::Key_F1: return kVK_F1; | ||||
|     case Qt::Key_F2: return kVK_F2; | ||||
|     case Qt::Key_F3: return kVK_F3; | ||||
|     case Qt::Key_F4: return kVK_F4; | ||||
|     case Qt::Key_F5: return kVK_F5; | ||||
|     case Qt::Key_F6: return kVK_F6; | ||||
|     case Qt::Key_F7: return kVK_F7; | ||||
|     case Qt::Key_F8: return kVK_F8; | ||||
|     case Qt::Key_F9: return kVK_F9; | ||||
|     case Qt::Key_F10: return kVK_F10; | ||||
|     case Qt::Key_F11: return kVK_F11; | ||||
|     case Qt::Key_F12: return kVK_F12; | ||||
|     case Qt::Key_F13: return kVK_F13; | ||||
|     case Qt::Key_F14: return kVK_F14; | ||||
|     case Qt::Key_F15: return kVK_F15; | ||||
|     case Qt::Key_F16: return kVK_F16; | ||||
|     case Qt::Key_F17: return kVK_F17; | ||||
|     case Qt::Key_F18: return kVK_F18; | ||||
|     case Qt::Key_F19: return kVK_F19; | ||||
|     case Qt::Key_F20: return kVK_F10; | ||||
|     case Qt::Key_Return: return kVK_Return; | ||||
|     case Qt::Key_Enter: return kVK_ANSI_KeypadEnter; | ||||
|     case Qt::Key_Tab: return kVK_Tab; | ||||
|     case Qt::Key_Space: return kVK_Space; | ||||
|     case Qt::Key_Backspace: return kVK_Delete; | ||||
|     case Qt::Key_Escape: return kVK_Escape; | ||||
|     case Qt::Key_CapsLock: return kVK_CapsLock; | ||||
|     case Qt::Key_Option: return kVK_Option; | ||||
|     case Qt::Key_VolumeUp: return kVK_VolumeUp; | ||||
|     case Qt::Key_VolumeDown: return kVK_VolumeDown; | ||||
|     case Qt::Key_Help: return kVK_Help; | ||||
|     case Qt::Key_Home: return kVK_Home; | ||||
|     case Qt::Key_PageUp: return kVK_PageUp; | ||||
|     case Qt::Key_Delete: return kVK_ForwardDelete; | ||||
|     case Qt::Key_End: return kVK_End; | ||||
|     case Qt::Key_PageDown: return kVK_PageDown; | ||||
|     case Qt::Key_Left: return kVK_LeftArrow; | ||||
|     case Qt::Key_Right: return kVK_RightArrow; | ||||
|     case Qt::Key_Down: return kVK_DownArrow; | ||||
|     case Qt::Key_Up: return kVK_UpArrow; | ||||
|     default: return 0; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| quint32 GlobalAction::nativeModifiers(Qt::KeyboardModifiers modifiers) | ||||
| { | ||||
|   quint32 native = 0; | ||||
|   if (modifiers & Qt::ShiftModifier) | ||||
|     native |= shiftKey; | ||||
|   if (modifiers & Qt::ControlModifier) | ||||
|     native |= cmdKey; | ||||
|   if (modifiers & Qt::AltModifier) | ||||
|     native |= optionKey; | ||||
|   if (modifiers & Qt::MetaModifier) | ||||
|     native |= controlKey; | ||||
|   return native; | ||||
| } | ||||
| 
 | ||||
| #endif  // ifdef Q_OS_MAC
 | ||||
							
								
								
									
										29
									
								
								src/service/globalaction.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,29 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| // Some functions copied from QXT lib
 | ||||
| 
 | ||||
| #include <QAbstractNativeEventFilter> | ||||
| #include <QAction> | ||||
| 
 | ||||
| class GlobalAction : public QAbstractNativeEventFilter | ||||
| { | ||||
| public: | ||||
|   bool nativeEventFilter(const QByteArray &eventType, void *message, | ||||
|                          long *result); | ||||
| 
 | ||||
|   static void init(); | ||||
|   static bool makeGlobal(QAction *action); | ||||
|   static bool removeGlobal(QAction *action); | ||||
|   static bool update(QAction *action, const QKeySequence &newShortcut); | ||||
| 
 | ||||
| private: | ||||
|   static QHash<QPair<quint32, quint32>, QAction *> actions_; | ||||
| 
 | ||||
|   static quint32 nativeKeycode(Qt::Key key); | ||||
|   static quint32 nativeModifiers(Qt::KeyboardModifiers modifiers); | ||||
|   static bool registerHotKey(quint32 nativeKey, quint32 nativeMods); | ||||
|   static bool unregisterHotKey(quint32 nativeKey, quint32 nativeMods); | ||||
|   static void triggerHotKey(quint32 nativeKey, quint32 nativeMods); | ||||
| 
 | ||||
|   friend struct ActionAdapter; | ||||
| }; | ||||
							
								
								
									
										31
									
								
								src/service/singleapplication.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,31 @@ | ||||
| #include "singleapplication.h" | ||||
| #include "debug.h" | ||||
| 
 | ||||
| #include <QCoreApplication> | ||||
| #include <QDir> | ||||
| #include <QStandardPaths> | ||||
| 
 | ||||
| static QString fileName(const QString &baseName) | ||||
| { | ||||
|   const auto name = !baseName.isEmpty() | ||||
|                         ? baseName | ||||
|                         : QCoreApplication::applicationName().toLower(); | ||||
|   SOFT_ASSERT(!name.isEmpty(), return QStringLiteral("./dummy.lock")); | ||||
|   return QStandardPaths::writableLocation(QStandardPaths::TempLocation) + | ||||
|          QDir::separator() + name + QLatin1String(".lock"); | ||||
| } | ||||
| 
 | ||||
| SingleApplication::SingleApplication(const QString &baseName) | ||||
|   : lockFile_(fileName(baseName)) | ||||
| { | ||||
|   if (!lockFile_.tryLock()) { | ||||
|     const auto lockName = fileName(baseName); | ||||
|     LERROR() << QObject::tr("Another instance is running. Lock file is busy.") | ||||
|              << LARG(lockName); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| bool SingleApplication::isValid() const | ||||
| { | ||||
|   return lockFile_.isLocked(); | ||||
| } | ||||
							
								
								
									
										14
									
								
								src/service/singleapplication.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,14 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <QLockFile> | ||||
| 
 | ||||
| class SingleApplication | ||||
| { | ||||
| public: | ||||
|   explicit SingleApplication(const QString &baseName = {}); | ||||
| 
 | ||||
|   bool isValid() const; | ||||
| 
 | ||||
| private: | ||||
|   QLockFile lockFile_; | ||||
| }; | ||||
							
								
								
									
										118
									
								
								src/service/widgetstate.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,118 @@ | ||||
| #include "widgetstate.h" | ||||
| #include "debug.h" | ||||
| 
 | ||||
| #include <QCoreApplication> | ||||
| #include <QHeaderView> | ||||
| #include <QMainWindow> | ||||
| #include <QSettings> | ||||
| #include <QSplitter> | ||||
| #include <QTableView> | ||||
| 
 | ||||
| namespace | ||||
| { | ||||
| enum class Action { Save, Restore }; | ||||
| 
 | ||||
| void handleGeometry(QSettings *settings, QWidget *widget, Action action) | ||||
| { | ||||
|   SOFT_ASSERT(widget, return ); | ||||
|   if (widget->parent()) | ||||
|     return; | ||||
| 
 | ||||
|   const auto name = widget->objectName() + QLatin1String("_geometry"); | ||||
|   if (action == Action::Save) { | ||||
|     settings->setValue(name, widget->geometry()); | ||||
|   } else { | ||||
|     if (settings->contains(name)) | ||||
|       widget->setGeometry(settings->value(name).toRect()); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| template <class T> | ||||
| void handleState(QSettings *settings, QWidget *widget, Action action) | ||||
| { | ||||
|   auto instance = qobject_cast<T *>(widget); | ||||
|   if (!instance) | ||||
|     return; | ||||
| 
 | ||||
|   const auto name = widget->objectName() + QLatin1Char('_') + typeid(T).name(); | ||||
|   if (action == Action::Save) | ||||
|     settings->setValue(name, instance->saveState()); | ||||
|   else | ||||
|     instance->restoreState(settings->value(name).toByteArray()); | ||||
| } | ||||
| 
 | ||||
| void handleWidget(QSettings *settings, QWidget *widget, Action action) | ||||
| { | ||||
|   SOFT_ASSERT(widget, return ); | ||||
|   if (!widget->objectName().isEmpty()) { | ||||
|     handleGeometry(settings, widget, action); | ||||
|     handleState<QSplitter>(settings, widget, action); | ||||
|     handleState<QHeaderView>(settings, widget, action); | ||||
|     handleState<QMainWindow>(settings, widget, action); | ||||
|   } | ||||
| 
 | ||||
|   settings->beginGroup(widget->objectName()); | ||||
|   const auto children = | ||||
|       widget->findChildren<QWidget *>(QString(), Qt::FindDirectChildrenOnly); | ||||
|   for (auto *child : children) { | ||||
|     handleWidget(settings, child, action); | ||||
|   } | ||||
|   settings->endGroup(); | ||||
| } | ||||
| 
 | ||||
| void apply(QWidget *widget, Action action) | ||||
| { | ||||
|   QSettings settings; | ||||
|   settings.beginGroup(QStringLiteral("GUI")); | ||||
| 
 | ||||
|   handleWidget(&settings, widget, action); | ||||
| 
 | ||||
|   settings.endGroup(); | ||||
| } | ||||
| }  // namespace
 | ||||
| 
 | ||||
| WidgetState::WidgetState(QWidget *parent) | ||||
|   : QObject(parent) | ||||
| { | ||||
|   add(parent); | ||||
| } | ||||
| 
 | ||||
| void WidgetState::add(QWidget *watched) | ||||
| { | ||||
|   if (!watched) | ||||
|     return; | ||||
|   watched->installEventFilter(this); | ||||
| } | ||||
| 
 | ||||
| bool WidgetState::eventFilter(QObject *watched, QEvent *event) | ||||
| { | ||||
|   if (event->type() != QEvent::Show && event->type() != QEvent::Hide) | ||||
|     return QObject::eventFilter(watched, event); | ||||
| 
 | ||||
|   auto widget = qobject_cast<QWidget *>(watched); | ||||
|   if (!widget) | ||||
|     return QObject::eventFilter(watched, event); | ||||
| 
 | ||||
|   if (event->type() == QEvent::Show) | ||||
|     restore(widget); | ||||
|   else if (event->type() == QEvent::Hide) | ||||
|     save(widget); | ||||
| 
 | ||||
|   return QObject::eventFilter(watched, event); | ||||
| } | ||||
| 
 | ||||
| void WidgetState::save(QWidget *widget) | ||||
| { | ||||
|   SOFT_ASSERT(widget, return ); | ||||
|   SOFT_ASSERT(!widget->objectName().isEmpty(), return ); | ||||
|   apply(widget, Action::Save); | ||||
| } | ||||
| 
 | ||||
| void WidgetState::restore(QWidget *widget) | ||||
| { | ||||
|   if (QCoreApplication::arguments().contains(QLatin1String("--reset-gui"))) | ||||
|     return; | ||||
|   SOFT_ASSERT(widget, return ); | ||||
|   SOFT_ASSERT(!widget->objectName().isEmpty(), return ); | ||||
|   apply(widget, Action::Restore); | ||||
| } | ||||
							
								
								
									
										14
									
								
								src/service/widgetstate.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,14 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <QObject> | ||||
| 
 | ||||
| class WidgetState : public QObject | ||||
| { | ||||
| public: | ||||
|   WidgetState(QWidget *parent = nullptr); | ||||
|   void add(QWidget *watched); | ||||
|   bool eventFilter(QObject *watched, QEvent *event) override; | ||||
| 
 | ||||
|   static void save(QWidget *widget); | ||||
|   static void restore(QWidget *widget); | ||||
| }; | ||||
							
								
								
									
										152
									
								
								src/settings.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,152 @@ | ||||
| #include "settings.h" | ||||
| 
 | ||||
| #include <QSettings> | ||||
| 
 | ||||
| namespace | ||||
| { | ||||
| const QString qs_guiGroup = "GUI"; | ||||
| const QString qs_captureHotkey = "captureHotkey"; | ||||
| const QString qs_repeatCaptureHotkey = "repeatCaptureHotkey"; | ||||
| const QString qs_repeatHotkey = "repeatHotkey"; | ||||
| const QString qs_clipboardHotkey = "clipboardHotkey"; | ||||
| const QString qs_resultShowType = "resultShowType"; | ||||
| const QString qs_proxyType = "proxyType"; | ||||
| const QString qs_proxyHostName = "proxyHostName"; | ||||
| const QString qs_proxyPort = "proxyPort"; | ||||
| const QString qs_proxyUser = "proxyUser"; | ||||
| const QString qs_proxyPassword = "proxyPassword"; | ||||
| const QString qs_proxySavePassword = "proxySavePassword"; | ||||
| const QString qs_autoUpdateType = "autoUpdateType"; | ||||
| const QString qs_lastUpdateCheck = "lastUpdateCheck"; | ||||
| 
 | ||||
| const QString qs_recogntionGroup = "Recognition"; | ||||
| const QString qs_tessDataPlace = "tessdata_dir"; | ||||
| const QString qs_ocrLanguage = "language"; | ||||
| const QString qs_imageScale = "image_scale"; | ||||
| 
 | ||||
| const QString qs_translationGroup = "Translation"; | ||||
| const QString qs_doTranslation = "doTranslation"; | ||||
| const QString qs_ignoreSslErrors = "ignoreSslErrors"; | ||||
| const QString qs_forceRotateTranslators = "forceRotateTranslators"; | ||||
| const QString qs_sourceLanguage = "source_language"; | ||||
| const QString qs_translationLanguage = "translation_language"; | ||||
| const QString qs_translationTimeout = "translation_timeout"; | ||||
| const QString qs_debugMode = "translation_debug"; | ||||
| const QString qs_translators = "translators"; | ||||
| 
 | ||||
| QString shuffle(const QString &source) | ||||
| { | ||||
|   if (source.isEmpty()) { | ||||
|     return source; | ||||
|   } | ||||
|   char encKeys[] = {14, 26, 99, 43}; | ||||
|   std::string result = source.toStdString(); | ||||
|   for (size_t i = 0, end = result.size(); i < end; ++i) { | ||||
|     result[i] = result[i] ^ encKeys[i % sizeof(encKeys)]; | ||||
|   } | ||||
|   return QString::fromUtf8(result.data()); | ||||
| } | ||||
| 
 | ||||
| }  // namespace
 | ||||
| 
 | ||||
| void Settings::save() | ||||
| { | ||||
|   QSettings settings; | ||||
| 
 | ||||
|   settings.beginGroup(qs_guiGroup); | ||||
| 
 | ||||
|   settings.setValue(qs_captureHotkey, captureHotkey); | ||||
|   settings.setValue(qs_repeatCaptureHotkey, repeatCaptureHotkey); | ||||
|   settings.setValue(qs_repeatHotkey, showLastHotkey); | ||||
|   settings.setValue(qs_clipboardHotkey, clipboardHotkey); | ||||
| 
 | ||||
|   settings.setValue(qs_resultShowType, int(resultShowType)); | ||||
| 
 | ||||
|   settings.setValue(qs_proxyType, proxyType); | ||||
|   settings.setValue(qs_proxyHostName, proxyHostName); | ||||
|   settings.setValue(qs_proxyPort, proxyPort); | ||||
|   settings.setValue(qs_proxyUser, proxyUser); | ||||
|   settings.setValue(qs_proxySavePassword, proxySavePassword); | ||||
|   if (proxySavePassword) { | ||||
|     settings.setValue(qs_proxyPassword, shuffle(proxyPassword)); | ||||
|   } else { | ||||
|     settings.remove(qs_proxyPassword); | ||||
|   } | ||||
| 
 | ||||
|   settings.setValue(qs_autoUpdateType, autoUpdateType); | ||||
| 
 | ||||
|   settings.endGroup(); | ||||
| 
 | ||||
|   settings.beginGroup(qs_recogntionGroup); | ||||
| 
 | ||||
|   settings.setValue(qs_tessDataPlace, tessdataPath); | ||||
|   settings.setValue(qs_ocrLanguage, sourceLanguage); | ||||
| 
 | ||||
|   // TODO corrector
 | ||||
| 
 | ||||
|   settings.endGroup(); | ||||
| 
 | ||||
|   settings.beginGroup(qs_translationGroup); | ||||
| 
 | ||||
|   settings.setValue(qs_doTranslation, doTranslation); | ||||
|   settings.setValue(qs_ignoreSslErrors, ignoreSslErrors); | ||||
|   settings.setValue(qs_debugMode, debugMode); | ||||
|   settings.setValue(qs_translationLanguage, targetLanguage); | ||||
|   settings.setValue(qs_translationTimeout, int(translationTimeout.count())); | ||||
|   settings.setValue(qs_translators, translators); | ||||
| 
 | ||||
|   settings.endGroup(); | ||||
| } | ||||
| 
 | ||||
| void Settings::load() | ||||
| { | ||||
|   QSettings settings; | ||||
| 
 | ||||
|   settings.beginGroup(qs_guiGroup); | ||||
| 
 | ||||
|   captureHotkey = settings.value(qs_captureHotkey, captureHotkey).toString(); | ||||
|   repeatCaptureHotkey = | ||||
|       settings.value(qs_repeatCaptureHotkey, repeatCaptureHotkey).toString(); | ||||
|   showLastHotkey = settings.value(qs_repeatHotkey, showLastHotkey).toString(); | ||||
|   clipboardHotkey = | ||||
|       settings.value(qs_clipboardHotkey, clipboardHotkey).toString(); | ||||
| 
 | ||||
|   resultShowType = ResultMode(std::clamp( | ||||
|       settings.value(qs_resultShowType, int(resultShowType)).toInt(), 0, 1)); | ||||
| 
 | ||||
|   proxyType = settings.value(qs_proxyType, proxyType).toInt(); | ||||
|   proxyHostName = settings.value(qs_proxyHostName, proxyHostName).toString(); | ||||
|   proxyPort = settings.value(qs_proxyPort, proxyPort).toInt(); | ||||
|   proxyUser = settings.value(qs_proxyUser, proxyUser).toString(); | ||||
|   proxySavePassword = | ||||
|       settings.value(qs_proxySavePassword, proxySavePassword).toBool(); | ||||
|   proxyPassword = shuffle(settings.value(qs_proxyPassword).toString()); | ||||
| 
 | ||||
|   autoUpdateType = settings.value(qs_autoUpdateType, autoUpdateType).toInt(); | ||||
| 
 | ||||
|   settings.endGroup(); | ||||
| 
 | ||||
|   settings.beginGroup(qs_recogntionGroup); | ||||
| 
 | ||||
|   tessdataPath = settings.value(qs_tessDataPlace, tessdataPath).toString(); | ||||
|   sourceLanguage = settings.value(qs_ocrLanguage, sourceLanguage).toString(); | ||||
| 
 | ||||
|   settings.endGroup(); | ||||
| 
 | ||||
|   settings.beginGroup(qs_translationGroup); | ||||
| 
 | ||||
|   doTranslation = settings.value(qs_doTranslation, doTranslation).toBool(); | ||||
|   ignoreSslErrors = | ||||
|       settings.value(qs_ignoreSslErrors, ignoreSslErrors).toBool(); | ||||
|   debugMode = settings.value(qs_debugMode, debugMode).toBool(); | ||||
|   targetLanguage = | ||||
|       settings.value(qs_translationLanguage, targetLanguage).toString(); | ||||
|   translationTimeout = std::chrono::seconds( | ||||
|       settings.value(qs_translationTimeout, int(translationTimeout.count())) | ||||
|           .toInt()); | ||||
|   translators = settings.value(qs_translators, translators).toStringList(); | ||||
|   if (translators.size() == 1 && translators.first().contains('|'))  // legacy
 | ||||
|     translators = translators.first().split('|'); | ||||
| 
 | ||||
|   settings.endGroup(); | ||||
| } | ||||
							
								
								
									
										117
									
								
								src/settings.h
									
									
									
									
									
								
							
							
						
						| @ -1,81 +1,54 @@ | ||||
| #ifndef SETTINGS_H | ||||
| #define SETTINGS_H | ||||
| #pragma once | ||||
| 
 | ||||
| #include <QString> | ||||
| #include "stfwd.h" | ||||
| 
 | ||||
| namespace settings_names { | ||||
|   //! UI
 | ||||
|   const QString guiGroup = "GUI"; | ||||
|   const QString geometry = "geometry"; | ||||
|   const QString captureHotkey = "captureHotkey"; | ||||
|   const QString repeatCaptureHotkey = "repeatCaptureHotkey"; | ||||
|   const QString repeatHotkey = "repeatHotkey"; | ||||
|   const QString clipboardHotkey = "clipboardHotkey"; | ||||
|   const QString resultShowType = "resultShowType"; | ||||
|   const QString proxyType = "proxyType"; | ||||
|   const QString proxyHostName = "proxyHostName"; | ||||
|   const QString proxyPort = "proxyPort"; | ||||
|   const QString proxyUser = "proxyUser"; | ||||
|   const QString proxyPassword = "proxyPassword"; | ||||
|   const QString proxySavePassword = "proxySavePassword"; | ||||
|   const QString autoUpdateType = "autoUpdateType"; | ||||
|   const QString lastUpdateCheck = "lastUpdateCheck"; | ||||
| #include <QStringList> | ||||
| 
 | ||||
|   //! Recognition
 | ||||
|   const QString recogntionGroup = "Recognition"; | ||||
|   const QString tessDataPlace = "tessdata_dir"; | ||||
|   const QString ocrLanguage = "language"; | ||||
|   const QString imageScale = "image_scale"; | ||||
| #include <chrono> | ||||
| 
 | ||||
|   //! Translation
 | ||||
|   const QString translationGroup = "Translation"; | ||||
|   const QString doTranslation = "doTranslation"; | ||||
|   const QString ignoreSslErrors = "ignoreSslErrors"; | ||||
|   const QString forceRotateTranslators = "forceRotateTranslators"; | ||||
|   const QString sourceLanguage = "source_language"; | ||||
|   const QString translationLanguage = "translation_language"; | ||||
|   const QString translationTimeout = "translation_timeout"; | ||||
|   const QString translationDebugMode = "translation_debug"; | ||||
|   const QString translators = "translators"; | ||||
| } | ||||
| enum class ResultMode { Widget, Tooltip }; | ||||
| 
 | ||||
| namespace settings_values { | ||||
|   const QString appName = "ScreenTranslator"; | ||||
|   const QString companyName = "Gres"; | ||||
| struct Substitution { | ||||
|   QString source; | ||||
|   QString target; | ||||
| }; | ||||
| using Substitutions = std::unordered_multimap<LanguageId, Substitution>; | ||||
| 
 | ||||
|   //! UI
 | ||||
|   const QString captureHotkey = "Ctrl+Alt+Z"; | ||||
|   const QString repeatCaptureHotkey = "Ctrl+Alt+S"; | ||||
|   const QString repeatHotkey = "Ctrl+Alt+X"; | ||||
|   const QString clipboardHotkey = "Ctrl+Alt+C"; | ||||
|   const QString resultShowType = "1";//dialog
 | ||||
|   const int proxyType = 0; | ||||
|   const QString proxyHostName = ""; | ||||
|   const int proxyPort = 8080; | ||||
|   const QString proxyUser = ""; | ||||
|   const QString proxyPassword = ""; | ||||
|   const bool proxySavePassword = false; | ||||
|   const int autoUpdateType = 0; //Never
 | ||||
|   const QString lastUpdateCheck = ""; | ||||
| class Settings | ||||
| { | ||||
| public: | ||||
|   void save(); | ||||
|   void load(); | ||||
| 
 | ||||
|   //! Recognition
 | ||||
| #if defined(Q_OS_LINUX) | ||||
|   const QString tessDataPlace = "/usr/share/tesseract-ocr/"; | ||||
| #else | ||||
|   const QString tessDataPlace = "./"; | ||||
| #endif | ||||
|   const QString ocrLanguage = "eng"; | ||||
|   const int imageScale = 5; | ||||
|   QString captureHotkey{"Ctrl+Alt+Z"}; | ||||
|   QString repeatCaptureHotkey{"Ctrl+Alt+S"}; | ||||
|   QString showLastHotkey{"Ctrl+Alt+X"}; | ||||
|   QString clipboardHotkey{"Ctrl+Alt+C"}; | ||||
| 
 | ||||
|   //! Translation
 | ||||
|   const bool doTranslation = true; | ||||
|   const bool ignoreSslErrors = false; | ||||
|   const bool forceRotateTranslators = false; | ||||
|   const QString sourceLanguage = "auto"; | ||||
|   const QString translationLanguage = "ru"; | ||||
|   const int translationTimeout = 15; // secs
 | ||||
|   const bool translationDebugMode = false; | ||||
|   const QString translators = ""; | ||||
| } | ||||
|   int proxyType{0}; | ||||
|   QString proxyHostName{""}; | ||||
|   int proxyPort{8080}; | ||||
|   QString proxyUser{""}; | ||||
|   QString proxyPassword{""}; | ||||
|   bool proxySavePassword{false}; | ||||
| 
 | ||||
| #endif // SETTINGS_H
 | ||||
|   int autoUpdateType{0};  // Never
 | ||||
|   QString lastUpdateCheck{""}; | ||||
|   Substitutions userSubstitutions; | ||||
| 
 | ||||
|   bool debugMode{false}; | ||||
| 
 | ||||
|   QString tessdataPath{"tessdata"}; | ||||
|   QString sourceLanguage{"eng"}; | ||||
|   LanguageIds availableOcrLanguages_; | ||||
| 
 | ||||
|   bool doTranslation{true}; | ||||
|   bool ignoreSslErrors{false}; | ||||
|   bool forceRotateTranslators{false}; | ||||
|   LanguageId targetLanguage{"rus"}; | ||||
|   std::chrono::seconds translationTimeout{15}; | ||||
|   QString translatorsDir{"translators"}; | ||||
|   QStringList translators{"google.js"}; | ||||
| 
 | ||||
|   ResultMode resultShowType{ResultMode::Widget};  // dialog
 | ||||
| }; | ||||
|  | ||||
| @ -1,288 +1,204 @@ | ||||
| #include "settingseditor.h" | ||||
| #include "ui_settingseditor.h" | ||||
| #include "languagehelper.h" | ||||
| #include "translatorhelper.h" | ||||
| #include "recognizerhelper.h" | ||||
| #include "stassert.h" | ||||
| #include "utils.h" | ||||
| 
 | ||||
| #include <QSettings> | ||||
| #include <QFileDialog> | ||||
| #include <QDir> | ||||
| #include <QRegExpValidator> | ||||
| #include <QNetworkProxy> | ||||
| 
 | ||||
| #include "languagecodes.h" | ||||
| #include "settings.h" | ||||
| #include "ui_settingseditor.h" | ||||
| #include "widgetstate.h" | ||||
| 
 | ||||
| SettingsEditor::SettingsEditor (const LanguageHelper &dictionary, QWidget *parent) : | ||||
|   QDialog (parent), | ||||
|   ui (new Ui::SettingsEditor), translatorHelper_ (new TranslatorHelper), | ||||
|   recognizerHelper_ (new RecognizerHelper), dictionary_ (dictionary), | ||||
|   buttonGroup_ (new QButtonGroup (this)) { | ||||
|   ui->setupUi (this); | ||||
| #include <QDir> | ||||
| #include <QFileDialog> | ||||
| #include <QNetworkProxy> | ||||
| #include <QRegExpValidator> | ||||
| #include <QSettings> | ||||
| #include <QStringListModel> | ||||
| 
 | ||||
|   buttonGroup_->addButton (ui->trayRadio, 0); | ||||
|   buttonGroup_->addButton (ui->dialogRadio, 1); | ||||
|   connect (ui->updateButton, SIGNAL (clicked (bool)), SIGNAL (updateCheckRequested ())); | ||||
|   QStringList updateTypes = QStringList () << tr ("Никогда") << tr ("Ежедневно") | ||||
|                                            << tr ("Еженедельно") << tr ("Ежемесячно"); | ||||
|   ui->updateCombo->addItems (updateTypes); | ||||
| SettingsEditor::SettingsEditor() | ||||
|   : ui(new Ui::SettingsEditor) | ||||
| { | ||||
|   ui->setupUi(this); | ||||
| 
 | ||||
|   connect (ui->tessdataButton, SIGNAL (clicked ()), SLOT (openTessdataDialog ())); | ||||
|   connect (ui->tessdataEdit, SIGNAL (textChanged (const QString&)), | ||||
|            SLOT (initOcrLangCombo (const QString&))); | ||||
| 
 | ||||
|   connect (ui->recognizerFixTable, SIGNAL (itemChanged (QTableWidgetItem*)), | ||||
|            SLOT (recognizerFixTableItemChanged (QTableWidgetItem*))); | ||||
| 
 | ||||
|   ui->translateLangCombo->addItems (dictionary_.translateLanguagesUi ()); | ||||
| 
 | ||||
|   typedef QNetworkProxy::ProxyType ProxyType; | ||||
|   QMap<ProxyType, QString> proxyTypeNames; | ||||
|   proxyTypeNames.insert (QNetworkProxy::NoProxy, tr ("Нет")); | ||||
|   proxyTypeNames.insert (QNetworkProxy::Socks5Proxy, tr ("SOCKS 5")); | ||||
|   proxyTypeNames.insert (QNetworkProxy::HttpProxy, tr ("HTTP")); | ||||
|   QList<int> proxyOrder = proxyTypeOrder (); | ||||
|   for (int type: proxyOrder) { | ||||
|     ui->proxyTypeCombo->addItem (proxyTypeNames.value (QNetworkProxy::ProxyType (type))); | ||||
|   { | ||||
|     auto model = new QStringListModel(this); | ||||
|     model->setStringList({tr("General"), tr("Recognition"), tr("Correction"), | ||||
|                           tr("Translation"), tr("Representation"), | ||||
|                           tr("Update")}); | ||||
|     ui->pagesList->setModel(model); | ||||
|     auto selection = ui->pagesList->selectionModel(); | ||||
|     connect(selection, &QItemSelectionModel::currentRowChanged,  //
 | ||||
|             this, &SettingsEditor::updateCurrentPage); | ||||
|   } | ||||
| 
 | ||||
|   QRegExp urlRegexp (R"(^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$)"); | ||||
|   ui->proxyHostEdit->setValidator (new QRegExpValidator (urlRegexp, ui->proxyHostEdit)); | ||||
|   ui->proxyPassEdit->setEchoMode (QLineEdit::PasswordEchoOnEdit); | ||||
|   // general
 | ||||
|   //  QMap<QNetworkProxy::ProxyType, QString> proxyTypeNames;
 | ||||
|   //  proxyTypeNames.insert(QNetworkProxy::NoProxy, tr("No"));
 | ||||
|   //  proxyTypeNames.insert(QNetworkProxy::DefaultProxy, tr("System"));
 | ||||
|   //  proxyTypeNames.insert(QNetworkProxy::Socks5Proxy, tr("SOCKS 5"));
 | ||||
|   //  proxyTypeNames.insert(QNetworkProxy::HttpProxy, tr("HTTP"));
 | ||||
|   //  QList<int> proxyOrder = proxyTypeOrder();
 | ||||
|   //  for (int type : proxyOrder) {
 | ||||
|   //    ui->proxyTypeCombo->addItem(
 | ||||
|   //        proxyTypeNames.value(QNetworkProxy::ProxyType(type)));
 | ||||
|   //  }
 | ||||
| 
 | ||||
|   loadSettings (); | ||||
|   loadState (); | ||||
|   //  QRegExp urlRegexp(
 | ||||
|   //      R"(^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$)");
 | ||||
|   //  ui->proxyHostEdit->setValidator(
 | ||||
|   //      new QRegExpValidator(urlRegexp, ui->proxyHostEdit));
 | ||||
|   //  ui->proxyPassEdit->setEchoMode(QLineEdit::PasswordEchoOnEdit);
 | ||||
| 
 | ||||
|   // recognition
 | ||||
|   connect(ui->tessdataButton, &QPushButton::clicked,  //
 | ||||
|           this, &SettingsEditor::openTessdataDialog); | ||||
|   connect(ui->tessdataEdit, &QLineEdit::textChanged,  //
 | ||||
|           this, &SettingsEditor::updateTesseractLanguages); | ||||
| 
 | ||||
|   //  connect(ui->recognizerFixTable, SIGNAL(itemChanged(QTableWidgetItem *)),
 | ||||
|   //          SLOT(recognizerFixTableItemChanged(QTableWidgetItem *)));
 | ||||
| 
 | ||||
|   //  //  ui->translateLangCombo->addItems(dictionary_.translateLanguagesUi());
 | ||||
| 
 | ||||
|   // translation
 | ||||
| 
 | ||||
|   updateTranslationLanguages(); | ||||
| 
 | ||||
|   // updates
 | ||||
|   ui->updateCombo->addItems( | ||||
|       {tr("Never"), tr("Daily"), tr("Weekly"), tr("Monthly")}); | ||||
| 
 | ||||
|   new WidgetState(this); | ||||
| } | ||||
| 
 | ||||
| SettingsEditor::~SettingsEditor () { | ||||
|   saveState (); | ||||
|   delete recognizerHelper_; | ||||
|   delete translatorHelper_; | ||||
| SettingsEditor::~SettingsEditor() | ||||
| { | ||||
|   delete ui; | ||||
| } | ||||
| 
 | ||||
| void SettingsEditor::done (int result) { | ||||
|   if (result == QDialog::Accepted) { | ||||
|     saveSettings (); | ||||
|     emit settingsEdited (); | ||||
| Settings SettingsEditor::settings() const | ||||
| { | ||||
|   Settings settings; | ||||
|   settings.captureHotkey = ui->captureEdit->keySequence().toString(); | ||||
|   settings.repeatCaptureHotkey = | ||||
|       ui->repeatCaptureEdit->keySequence().toString(); | ||||
|   settings.showLastHotkey = ui->repeatEdit->keySequence().toString(); | ||||
|   settings.clipboardHotkey = ui->clipboardEdit->keySequence().toString(); | ||||
| 
 | ||||
|   settings.tessdataPath = ui->tessdataEdit->text(); | ||||
| 
 | ||||
|   settings.doTranslation = ui->doTranslationCheck->isChecked(); | ||||
|   settings.ignoreSslErrors = ui->ignoreSslCheck->isChecked(); | ||||
|   settings.debugMode = ui->translatorDebugCheck->isChecked(); | ||||
|   settings.translationTimeout = | ||||
|       std::chrono::seconds(ui->translateTimeoutSpin->value()); | ||||
| 
 | ||||
|   settings.translators.clear(); | ||||
|   for (auto i = 0, end = ui->translatorList->count(); i < end; ++i) { | ||||
|     auto item = ui->translatorList->item(i); | ||||
|     if (item->checkState() == Qt::Checked) | ||||
|       settings.translators.append(item->text()); | ||||
|   } | ||||
|   QDialog::done (result); | ||||
| 
 | ||||
|   settings.resultShowType = | ||||
|       ui->trayRadio->isChecked() ? ResultMode::Tooltip : ResultMode::Widget; | ||||
|   return settings; | ||||
| } | ||||
| 
 | ||||
| void SettingsEditor::saveSettings () const { | ||||
|   using namespace settings_names; | ||||
|   QSettings settings; | ||||
|   settings.beginGroup (guiGroup); | ||||
|   settings.setValue (captureHotkey, ui->captureEdit->keySequence ().toString ()); | ||||
|   settings.setValue (repeatCaptureHotkey, ui->repeatCaptureEdit->keySequence ().toString ()); | ||||
|   settings.setValue (repeatHotkey, ui->repeatEdit->keySequence ().toString ()); | ||||
|   settings.setValue (clipboardHotkey, ui->clipboardEdit->keySequence ().toString ()); | ||||
|   settings.setValue (resultShowType, buttonGroup_->checkedId ()); | ||||
|   settings.setValue (proxyType, ui->proxyTypeCombo->currentIndex ()); | ||||
|   settings.setValue (proxyHostName, ui->proxyHostEdit->text ()); | ||||
|   settings.setValue (proxyPort, ui->proxyPortSpin->value ()); | ||||
|   settings.setValue (proxyUser, ui->proxyUserEdit->text ()); | ||||
|   if (ui->proxySaveCheck->isChecked ()) { | ||||
|     settings.setValue (proxyPassword, encode (ui->proxyPassEdit->text ())); | ||||
|   } | ||||
|   else { | ||||
|     settings.remove (proxyPassword); | ||||
|     QNetworkProxy proxy = QNetworkProxy::applicationProxy (); | ||||
|     proxy.setPassword (ui->proxyPassEdit->text ()); | ||||
|     QNetworkProxy::setApplicationProxy (proxy); | ||||
|   } | ||||
|   settings.setValue (proxySavePassword, ui->proxySaveCheck->isChecked ()); | ||||
|   settings.setValue (autoUpdateType, ui->updateCombo->currentIndex ()); | ||||
|   settings.endGroup (); | ||||
| void SettingsEditor::setSettings(const Settings &settings) | ||||
| { | ||||
|   ui->captureEdit->setKeySequence(settings.captureHotkey); | ||||
|   ui->repeatCaptureEdit->setKeySequence(settings.repeatCaptureHotkey); | ||||
|   ui->repeatEdit->setKeySequence(settings.showLastHotkey); | ||||
|   ui->clipboardEdit->setKeySequence(settings.clipboardHotkey); | ||||
| 
 | ||||
|   ui->tessdataEdit->setText(settings.tessdataPath); | ||||
|   updateTesseractLanguages(); | ||||
| 
 | ||||
|   settings.beginGroup (recogntionGroup); | ||||
|   settings.setValue (tessDataPlace, ui->tessdataEdit->text ()); | ||||
|   QString ocrLanguageVal = dictionary_.ocrUiToCode (ui->ocrLangCombo->currentText ()); | ||||
|   settings.setValue (ocrLanguage, ocrLanguageVal); | ||||
|   settings.setValue (imageScale, ui->imageScaleSpin->value ()); | ||||
|   ui->doTranslationCheck->setChecked(settings.doTranslation); | ||||
|   ui->ignoreSslCheck->setChecked(settings.ignoreSslErrors); | ||||
|   ui->translatorDebugCheck->setChecked(settings.debugMode); | ||||
|   ui->translateTimeoutSpin->setValue(settings.translationTimeout.count()); | ||||
|   translatorsDir_ = settings.translatorsDir; | ||||
|   updateTranslators(settings.translators); | ||||
| 
 | ||||
|   {  //Recognizer substitutions
 | ||||
|     RecognizerHelper::Subs subs; | ||||
|     QTableWidget *t = ui->recognizerFixTable; // Shortcut
 | ||||
|     for (int i = 0, end = t->rowCount () - 1; i < end; ++i) { | ||||
|       QComboBox *combo = static_cast<QComboBox *>(t->cellWidget (i, SubsColLanguage)); | ||||
|       QString langUi = combo->currentText (); | ||||
|       RecognizerHelper::Sub sub; | ||||
|       sub.language = dictionary_.ocrUiToCode (langUi); | ||||
| #define GET(COL) (t->item (i, COL) ? t->item (i, COL)->text () : QString ()) | ||||
|       sub.source = GET (SubsColSource); | ||||
|       sub.target = GET (SubsColTarget); | ||||
| #undef GET | ||||
|       if (langUi.isEmpty () || sub.language == langUi || sub.source.isEmpty ()) { | ||||
|         continue; | ||||
|       } | ||||
|       subs.append (sub); | ||||
|     } | ||||
|     recognizerHelper_->setSubs (subs); | ||||
|     recognizerHelper_->save (); | ||||
|   } | ||||
| 
 | ||||
|   settings.endGroup (); | ||||
| 
 | ||||
|   settings.beginGroup (translationGroup); | ||||
|   settings.setValue (doTranslation, ui->doTranslationCheck->isChecked ()); | ||||
|   settings.setValue (ignoreSslErrors, ui->ignoreSslCheck->isChecked ()); | ||||
|   settings.setValue (forceRotateTranslators, ui->forceRotateCheck->isChecked ()); | ||||
|   settings.setValue (translationDebugMode, ui->translatorDebugCheck->isChecked ()); | ||||
|   QString trLanguage = dictionary_.translateUiToCode (ui->translateLangCombo->currentText ()); | ||||
|   settings.setValue (translationLanguage, trLanguage); | ||||
|   QString sourceLanguageVal = dictionary_.ocrToTranslateCodes (ocrLanguage); | ||||
|   settings.setValue (sourceLanguage, sourceLanguageVal); | ||||
|   settings.setValue (translationTimeout, ui->translateTimeoutSpin->value ()); | ||||
| 
 | ||||
|   {//Translators
 | ||||
|     QStringList enabled; | ||||
|     for (int i = 0, end = ui->translatorList->count (); i < end; ++i) { | ||||
|       QListWidgetItem *item = ui->translatorList->item (i); | ||||
|       if (item->checkState () == Qt::Checked) { | ||||
|         enabled << item->text (); | ||||
|       } | ||||
|     } | ||||
|     translatorHelper_->setEnabledTranslators (enabled); | ||||
|   } | ||||
| 
 | ||||
|   settings.endGroup (); | ||||
|   ui->trayRadio->setChecked(settings.resultShowType == ResultMode::Tooltip); | ||||
|   ui->dialogRadio->setChecked(settings.resultShowType == ResultMode::Widget); | ||||
| } | ||||
| 
 | ||||
| void SettingsEditor::openTessdataDialog () { | ||||
|   QString path = QFileDialog::getExistingDirectory (this, tr ("Path to tessdata")); | ||||
|   if (path.isEmpty ()) { | ||||
| void SettingsEditor::updateCurrentPage() | ||||
| { | ||||
|   ui->pagesView->setCurrentIndex(ui->pagesList->currentIndex().row()); | ||||
| } | ||||
| 
 | ||||
| void SettingsEditor::openTessdataDialog() | ||||
| { | ||||
|   const auto path = | ||||
|       QFileDialog::getExistingDirectory(this, tr("Path to tessdata")); | ||||
| 
 | ||||
|   if (path.isEmpty()) | ||||
|     return; | ||||
|   } | ||||
|   ui->tessdataEdit->setText (path); | ||||
| 
 | ||||
|   ui->tessdataEdit->setText(path); | ||||
| } | ||||
| 
 | ||||
| void SettingsEditor::loadSettings () { | ||||
| #define GET(FIELD) settings.value (settings_names::FIELD, settings_values::FIELD) | ||||
|   QSettings settings; | ||||
| void SettingsEditor::updateTesseractLanguages() | ||||
| { | ||||
|   ui->tesseractLangCombo->clear(); | ||||
|   ui->correctLangCombo->clear(); | ||||
| 
 | ||||
|   settings.beginGroup (settings_names::guiGroup); | ||||
|   ui->captureEdit->setKeySequence (QKeySequence (GET (captureHotkey).toString ())); | ||||
|   ui->repeatCaptureEdit->setKeySequence (QKeySequence (GET (repeatCaptureHotkey).toString ())); | ||||
|   ui->repeatEdit->setKeySequence (QKeySequence (GET (repeatHotkey).toString ())); | ||||
|   ui->clipboardEdit->setKeySequence (QKeySequence (GET (clipboardHotkey).toString ())); | ||||
|   QAbstractButton *button = buttonGroup_->button (GET (resultShowType).toInt ()); | ||||
|   Q_CHECK_PTR (button); | ||||
|   button->setChecked (true); | ||||
|   ui->proxyTypeCombo->setCurrentIndex (GET (proxyType).toInt ()); | ||||
|   ui->proxyHostEdit->setText (GET (proxyHostName).toString ()); | ||||
|   ui->proxyPortSpin->setValue (GET (proxyPort).toInt ()); | ||||
|   ui->proxyUserEdit->setText (GET (proxyUser).toString ()); | ||||
|   ui->proxySaveCheck->setChecked (GET (proxySavePassword).toBool ()); | ||||
|   if (ui->proxySaveCheck->isChecked ()) { | ||||
|     ui->proxyPassEdit->setText (encode (GET (proxyPassword).toString ())); | ||||
|   } | ||||
|   else { | ||||
|     ui->proxyPassEdit->setText (QNetworkProxy::applicationProxy ().password ()); | ||||
|   } | ||||
|   ui->updateCombo->setCurrentIndex (GET (autoUpdateType).toInt ()); | ||||
|   settings.endGroup (); | ||||
|   QDir dir(ui->tessdataEdit->text()); | ||||
|   if (!dir.exists()) | ||||
|     return; | ||||
| 
 | ||||
|   settings.beginGroup (settings_names::recogntionGroup); | ||||
|   ui->tessdataEdit->setText (GET (tessDataPlace).toString ()); | ||||
|   QString ocrLanguage = dictionary_.ocrCodeToUi (GET (ocrLanguage).toString ()); | ||||
|   ui->ocrLangCombo->setCurrentText (ocrLanguage); | ||||
|   ui->imageScaleSpin->setValue (GET (imageScale).toInt ()); | ||||
|   LanguageIds names; | ||||
|   LanguageCodes languages; | ||||
| 
 | ||||
|   {//Recognizer substitutions
 | ||||
|     recognizerHelper_->load (); | ||||
|     RecognizerHelper::Subs subs = recognizerHelper_->subs (); | ||||
|     ui->recognizerFixTable->setRowCount (subs.size ()); | ||||
|     int row = 0; | ||||
|     for (const RecognizerHelper::Sub &sub: subs) { | ||||
|       if (!initSubsTableRow (row, sub.language)) { | ||||
|         continue; | ||||
|       } | ||||
|       ui->recognizerFixTable->setItem (row, SubsColSource, new QTableWidgetItem (sub.source)); | ||||
|       ui->recognizerFixTable->setItem (row, SubsColTarget, new QTableWidgetItem (sub.target)); | ||||
|       ++row; | ||||
|     } | ||||
|     ui->recognizerFixTable->setRowCount (row + 1); | ||||
|     initSubsTableRow (row); | ||||
|     ui->recognizerFixTable->resizeColumnsToContents (); | ||||
|   const auto files = dir.entryList({"*.traineddata"}, QDir::Files); | ||||
|   for (const auto &file : files) { | ||||
|     const auto lang = file.left(file.indexOf(".")); | ||||
|     if (const auto bundle = languages.findByTesseract(lang)) | ||||
|       names.append(bundle->name); | ||||
|   } | ||||
| 
 | ||||
|   settings.endGroup (); | ||||
|   if (names.isEmpty()) | ||||
|     return; | ||||
| 
 | ||||
| 
 | ||||
|   settings.beginGroup (settings_names::translationGroup); | ||||
|   ui->doTranslationCheck->setChecked (GET (doTranslation).toBool ()); | ||||
|   ui->ignoreSslCheck->setChecked (GET (ignoreSslErrors).toBool ()); | ||||
|   ui->forceRotateCheck->setChecked (GET (forceRotateTranslators).toBool ()); | ||||
|   ui->translatorDebugCheck->setChecked (GET (translationDebugMode).toBool ()); | ||||
|   QString trLanguage = dictionary_.translateCodeToUi (GET (translationLanguage).toString ()); | ||||
|   ui->translateLangCombo->setCurrentText (trLanguage); | ||||
|   ui->translateTimeoutSpin->setValue (GET (translationTimeout).toInt ()); | ||||
| 
 | ||||
|   {// Translators
 | ||||
|     QStringList enabled; | ||||
|     ui->translatorList->addItems (translatorHelper_->possibleTranslators (enabled)); | ||||
|     for (int i = 0, end = ui->translatorList->count (); i < end; ++i) { | ||||
|       QListWidgetItem *item = ui->translatorList->item (i); | ||||
|       Qt::CheckState state = enabled.contains (item->text ()) ? Qt::Checked : Qt::Unchecked; | ||||
|       item->setCheckState (state); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   settings.endGroup (); | ||||
| #undef GET | ||||
|   std::sort(names.begin(), names.end()); | ||||
|   ui->tesseractLangCombo->addItems(names); | ||||
|   ui->correctLangCombo->addItems(names); | ||||
| } | ||||
| 
 | ||||
| bool SettingsEditor::initSubsTableRow (int row, const QString &languageCode) { | ||||
|   QString lang = dictionary_.ocrCodeToUi (languageCode); | ||||
|   if (!languageCode.isEmpty () && lang == languageCode) { | ||||
|     return false; | ||||
|   } | ||||
|   QComboBox *langCombo = new QComboBox (ui->recognizerFixTable); | ||||
|   langCombo->setModel (ui->ocrLangCombo->model ()); | ||||
|   if (!languageCode.isEmpty ()) { | ||||
|     langCombo->setCurrentText (lang); | ||||
|   } | ||||
|   else { | ||||
|     langCombo->setCurrentIndex (ui->ocrLangCombo->currentIndex ()); | ||||
|   } | ||||
|   ui->recognizerFixTable->setCellWidget (row, SubsColLanguage, langCombo); | ||||
|   return true; | ||||
| void SettingsEditor::updateCorrectionsTable() | ||||
| { | ||||
| } | ||||
| 
 | ||||
| void SettingsEditor::saveState () const { | ||||
|   QSettings settings; | ||||
|   settings.beginGroup (settings_names::guiGroup); | ||||
|   settings.setValue (objectName () + "_" + settings_names::geometry, saveGeometry ()); | ||||
| } | ||||
| void SettingsEditor::updateTranslators(const QStringList &enabled) | ||||
| { | ||||
|   ui->translatorList->clear(); | ||||
| 
 | ||||
| void SettingsEditor::loadState () { | ||||
|   QSettings settings; | ||||
|   settings.beginGroup (settings_names::guiGroup); | ||||
|   restoreGeometry (settings.value (objectName () + "_" + settings_names::geometry).toByteArray ()); | ||||
| } | ||||
|   QDir dir(translatorsDir_); | ||||
|   if (!dir.exists()) | ||||
|     return; | ||||
| 
 | ||||
| void SettingsEditor::initOcrLangCombo (const QString &path) { | ||||
|   ui->ocrLangCombo->clear (); | ||||
|   ui->ocrLangCombo->addItems (dictionary_.availableOcrLanguagesUi (path)); | ||||
| } | ||||
|   auto files = dir.entryList({"*.js"}, QDir::Files); | ||||
|   std::sort(files.begin(), files.end()); | ||||
|   ui->translatorList->addItems(files); | ||||
| 
 | ||||
| void SettingsEditor::recognizerFixTableItemChanged (QTableWidgetItem *item) { | ||||
|   ST_ASSERT (item->column () < 3); | ||||
|   int row = item->row (); | ||||
|   QTableWidget *t = ui->recognizerFixTable; | ||||
| #define CHECK(COL) (!t->item (row, COL) || t->item (row, COL)->text ().isEmpty ()) | ||||
|   bool isRowEmpty = CHECK (SubsColSource) && CHECK (SubsColTarget); | ||||
| #undef CHECK | ||||
|   int lastRow = ui->recognizerFixTable->rowCount () - 1; | ||||
|   if (isRowEmpty && row != lastRow) { | ||||
|     ui->recognizerFixTable->removeRow (row); | ||||
|   } | ||||
|   else if (!isRowEmpty && row == lastRow) { | ||||
|     int newRow = lastRow + 1; | ||||
|     ui->recognizerFixTable->insertRow (newRow); | ||||
|     initSubsTableRow (newRow); | ||||
|   for (auto i = 0, end = ui->translatorList->count(); i < end; ++i) { | ||||
|     auto item = ui->translatorList->item(i); | ||||
|     item->setCheckState(enabled.contains(item->text()) ? Qt::Checked | ||||
|                                                        : Qt::Unchecked); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void SettingsEditor::updateTranslationLanguages() | ||||
| { | ||||
|   LanguageIds names; | ||||
|   LanguageCodes languages; | ||||
| 
 | ||||
|   for (const auto &bundle : languages.all()) { | ||||
|     if (!bundle.second.iso639_1.isEmpty()) | ||||
|       names.append(bundle.second.name); | ||||
|   } | ||||
| 
 | ||||
|   ui->translateLangCombo->clear(); | ||||
|   std::sort(names.begin(), names.end()); | ||||
|   ui->translateLangCombo->addItems(names); | ||||
| } | ||||
|  | ||||
| @ -1,54 +1,33 @@ | ||||
| #ifndef SETTINGSEDITOR_H | ||||
| #define SETTINGSEDITOR_H | ||||
| #pragma once | ||||
| 
 | ||||
| #include <QDialog> | ||||
| #include <QButtonGroup> | ||||
| #include <QMap> | ||||
| 
 | ||||
| class QTableWidgetItem; | ||||
| namespace Ui { | ||||
|   class SettingsEditor; | ||||
| #include "settings.h" | ||||
| 
 | ||||
| namespace Ui | ||||
| { | ||||
| class SettingsEditor; | ||||
| } | ||||
| class LanguageHelper; | ||||
| class TranslatorHelper; | ||||
| class RecognizerHelper; | ||||
| 
 | ||||
| class SettingsEditor : public QDialog { | ||||
| class SettingsEditor : public QDialog | ||||
| { | ||||
|   Q_OBJECT | ||||
| 
 | ||||
|   enum SubsCol { | ||||
|     SubsColLanguage = 0, SubsColSource, SubsColTarget | ||||
|   }; | ||||
| public: | ||||
|   explicit SettingsEditor(); | ||||
|   ~SettingsEditor(); | ||||
| 
 | ||||
|   public: | ||||
|     explicit SettingsEditor (const LanguageHelper &dictionary, QWidget *parent = 0); | ||||
|     ~SettingsEditor (); | ||||
|   Settings settings() const; | ||||
|   void setSettings(const Settings &settings); | ||||
| 
 | ||||
|   signals: | ||||
|     void settingsEdited (); | ||||
|     void updateCheckRequested (); | ||||
| private: | ||||
|   void updateCurrentPage(); | ||||
|   void openTessdataDialog(); | ||||
|   void updateTesseractLanguages(); | ||||
|   void updateCorrectionsTable(); | ||||
|   void updateTranslators(const QStringList &enabled); | ||||
|   void updateTranslationLanguages(); | ||||
| 
 | ||||
|   public slots: | ||||
|     void done (int result); | ||||
| 
 | ||||
|   private slots: | ||||
|     void saveSettings () const; | ||||
|     void openTessdataDialog (); | ||||
|     void initOcrLangCombo (const QString &path); | ||||
|     void recognizerFixTableItemChanged (QTableWidgetItem *item); | ||||
| 
 | ||||
|   private: | ||||
|     void loadSettings (); | ||||
|     void saveState () const; | ||||
|     void loadState (); | ||||
|     bool initSubsTableRow (int row, const QString &languageCode = QString ()); | ||||
| 
 | ||||
|   private: | ||||
|     Ui::SettingsEditor *ui; | ||||
|     TranslatorHelper *translatorHelper_; | ||||
|     RecognizerHelper *recognizerHelper_; | ||||
|     const LanguageHelper &dictionary_; | ||||
|     QButtonGroup *buttonGroup_; | ||||
|   Ui::SettingsEditor *ui; | ||||
|   QString translatorsDir_; | ||||
| }; | ||||
| 
 | ||||
| #endif // SETTINGSEDITOR_H
 | ||||
|  | ||||
| @ -6,15 +6,25 @@ | ||||
|    <rect> | ||||
|     <x>0</x> | ||||
|     <y>0</y> | ||||
|     <width>553</width> | ||||
|     <height>483</height> | ||||
|     <width>889</width> | ||||
|     <height>549</height> | ||||
|    </rect> | ||||
|   </property> | ||||
|   <property name="windowTitle"> | ||||
|    <string>Настройки</string> | ||||
|   </property> | ||||
|   <layout class="QGridLayout" name="gridLayout_6"> | ||||
|    <item row="1" column="0" colspan="2"> | ||||
|    <item row="0" column="0"> | ||||
|     <widget class="QListView" name="pagesList"> | ||||
|      <property name="sizePolicy"> | ||||
|       <sizepolicy hsizetype="Maximum" vsizetype="Expanding"> | ||||
|        <horstretch>0</horstretch> | ||||
|        <verstretch>0</verstretch> | ||||
|       </sizepolicy> | ||||
|      </property> | ||||
|     </widget> | ||||
|    </item> | ||||
|    <item row="1" column="0" colspan="4"> | ||||
|     <widget class="QDialogButtonBox" name="buttonBox"> | ||||
|      <property name="orientation"> | ||||
|       <enum>Qt::Horizontal</enum> | ||||
| @ -24,20 +34,23 @@ | ||||
|      </property> | ||||
|     </widget> | ||||
|    </item> | ||||
|    <item row="0" column="0" colspan="2"> | ||||
|     <widget class="QTabWidget" name="tabWidget"> | ||||
|    <item row="0" column="1" colspan="3"> | ||||
|     <widget class="QStackedWidget" name="pagesView"> | ||||
|      <property name="sizePolicy"> | ||||
|       <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> | ||||
|        <horstretch>0</horstretch> | ||||
|        <verstretch>0</verstretch> | ||||
|       </sizepolicy> | ||||
|      </property> | ||||
|      <property name="currentIndex"> | ||||
|       <number>0</number> | ||||
|      </property> | ||||
|      <widget class="QWidget" name="commonTab"> | ||||
|       <attribute name="title"> | ||||
|        <string>Общее</string> | ||||
|       </attribute> | ||||
|       <layout class="QGridLayout" name="gridLayout_8"> | ||||
|        <item row="0" column="0"> | ||||
|      <widget class="QWidget" name="pageGeneral"> | ||||
|       <layout class="QVBoxLayout" name="verticalLayout"> | ||||
|        <item> | ||||
|         <widget class="QGroupBox" name="groupBox"> | ||||
|          <property name="title"> | ||||
|           <string>Горячие клавиши</string> | ||||
|           <string>Shortcuts</string> | ||||
|          </property> | ||||
|          <layout class="QGridLayout" name="gridLayout"> | ||||
|           <item row="0" column="0"> | ||||
| @ -46,7 +59,7 @@ | ||||
|              <string><html><head/><body><p>Сочетание клавиш для перехода в режим захвата.</p></body></html></string> | ||||
|             </property> | ||||
|             <property name="text"> | ||||
|              <string>Захватить</string> | ||||
|              <string>Capture</string> | ||||
|             </property> | ||||
|            </widget> | ||||
|           </item> | ||||
| @ -59,7 +72,7 @@ | ||||
|              <string><html><head/><body><p>Сочетание клавиш для перехода в режим захвата, но с использованием последнего использованного, а не текущего, изображения.</p></body></html></string> | ||||
|             </property> | ||||
|             <property name="text"> | ||||
|              <string>Захватить повторно</string> | ||||
|              <string>Repeat capture</string> | ||||
|             </property> | ||||
|            </widget> | ||||
|           </item> | ||||
| @ -72,7 +85,7 @@ | ||||
|              <string><html><head/><body><p>Сочетание клавиш для повторного отображения последнего результата.</p></body></html></string> | ||||
|             </property> | ||||
|             <property name="text"> | ||||
|              <string>Показать</string> | ||||
|              <string>Show last result</string> | ||||
|             </property> | ||||
|            </widget> | ||||
|           </item> | ||||
| @ -85,7 +98,7 @@ | ||||
|              <string><html><head/><body><p>Сочетание клавиш для копирования последнего результата в буфер обмена.</p></body></html></string> | ||||
|             </property> | ||||
|             <property name="text"> | ||||
|              <string>Скопировать</string> | ||||
|              <string>Copy result to clipboard</string> | ||||
|             </property> | ||||
|            </widget> | ||||
|           </item> | ||||
| @ -95,16 +108,16 @@ | ||||
|          </layout> | ||||
|         </widget> | ||||
|        </item> | ||||
|        <item row="1" column="0"> | ||||
|        <item> | ||||
|         <widget class="QGroupBox" name="groupBox_2"> | ||||
|          <property name="title"> | ||||
|           <string>Прокси</string> | ||||
|           <string>Proxy</string> | ||||
|          </property> | ||||
|          <layout class="QGridLayout" name="gridLayout_3"> | ||||
|           <item row="0" column="0"> | ||||
|            <widget class="QLabel" name="label_12"> | ||||
|             <property name="text"> | ||||
|              <string>Тип:</string> | ||||
|              <string>Type:</string> | ||||
|             </property> | ||||
|            </widget> | ||||
|           </item> | ||||
| @ -114,7 +127,7 @@ | ||||
|           <item row="0" column="2"> | ||||
|            <widget class="QLabel" name="label_15"> | ||||
|             <property name="text"> | ||||
|              <string>Пользователь:</string> | ||||
|              <string>User:</string> | ||||
|             </property> | ||||
|            </widget> | ||||
|           </item> | ||||
| @ -124,7 +137,7 @@ | ||||
|           <item row="1" column="0"> | ||||
|            <widget class="QLabel" name="label_13"> | ||||
|             <property name="text"> | ||||
|              <string>Адрес:</string> | ||||
|              <string>Address:</string> | ||||
|             </property> | ||||
|            </widget> | ||||
|           </item> | ||||
| @ -134,7 +147,7 @@ | ||||
|           <item row="1" column="2"> | ||||
|            <widget class="QLabel" name="label_16"> | ||||
|             <property name="text"> | ||||
|              <string>Пароль:</string> | ||||
|              <string>Password:</string> | ||||
|             </property> | ||||
|            </widget> | ||||
|           </item> | ||||
| @ -144,7 +157,7 @@ | ||||
|           <item row="2" column="0"> | ||||
|            <widget class="QLabel" name="label_14"> | ||||
|             <property name="text"> | ||||
|              <string>Порт:</string> | ||||
|              <string>Port:</string> | ||||
|             </property> | ||||
|            </widget> | ||||
|           </item> | ||||
| @ -161,113 +174,63 @@ | ||||
|           <item row="2" column="2" colspan="2"> | ||||
|            <widget class="QCheckBox" name="proxySaveCheck"> | ||||
|             <property name="text"> | ||||
|              <string>Сохранять пароль (небезопасно)</string> | ||||
|              <string>save password (unsafe)</string> | ||||
|             </property> | ||||
|            </widget> | ||||
|           </item> | ||||
|          </layout> | ||||
|         </widget> | ||||
|        </item> | ||||
|        <item row="2" column="0"> | ||||
|         <layout class="QHBoxLayout" name="horizontalLayout"> | ||||
|          <item> | ||||
|           <widget class="QGroupBox" name="resultGroup"> | ||||
|            <property name="sizePolicy"> | ||||
|             <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> | ||||
|              <horstretch>0</horstretch> | ||||
|              <verstretch>0</verstretch> | ||||
|             </sizepolicy> | ||||
|            </property> | ||||
|            <property name="title"> | ||||
|             <string>Вывод результата</string> | ||||
|            </property> | ||||
|            <layout class="QGridLayout" name="gridLayout_4"> | ||||
|             <item row="0" column="0"> | ||||
|              <widget class="QRadioButton" name="trayRadio"> | ||||
|               <property name="sizePolicy"> | ||||
|                <sizepolicy hsizetype="Maximum" vsizetype="Fixed"> | ||||
|                 <horstretch>0</horstretch> | ||||
|                 <verstretch>0</verstretch> | ||||
|                </sizepolicy> | ||||
|               </property> | ||||
|               <property name="text"> | ||||
|                <string>Трей</string> | ||||
|               </property> | ||||
|              </widget> | ||||
|             </item> | ||||
|             <item row="0" column="1"> | ||||
|              <widget class="QRadioButton" name="dialogRadio"> | ||||
|               <property name="sizePolicy"> | ||||
|                <sizepolicy hsizetype="Maximum" vsizetype="Fixed"> | ||||
|                 <horstretch>0</horstretch> | ||||
|                 <verstretch>0</verstretch> | ||||
|                </sizepolicy> | ||||
|               </property> | ||||
|               <property name="text"> | ||||
|                <string>Окно</string> | ||||
|               </property> | ||||
|               <property name="checked"> | ||||
|                <bool>true</bool> | ||||
|               </property> | ||||
|              </widget> | ||||
|             </item> | ||||
|            </layout> | ||||
|           </widget> | ||||
|          </item> | ||||
|          <item> | ||||
|           <widget class="QGroupBox" name="groupBox_3"> | ||||
|            <property name="title"> | ||||
|             <string>Обновление</string> | ||||
|            </property> | ||||
|            <layout class="QGridLayout" name="gridLayout_5"> | ||||
|             <item row="0" column="0"> | ||||
|              <widget class="QLabel" name="label_17"> | ||||
|               <property name="text"> | ||||
|                <string>Проверять обновления:</string> | ||||
|               </property> | ||||
|              </widget> | ||||
|             </item> | ||||
|             <item row="0" column="1"> | ||||
|              <widget class="QComboBox" name="updateCombo"/> | ||||
|             </item> | ||||
|             <item row="0" column="2"> | ||||
|              <widget class="QPushButton" name="updateButton"> | ||||
|               <property name="sizePolicy"> | ||||
|                <sizepolicy hsizetype="Maximum" vsizetype="Fixed"> | ||||
|                 <horstretch>0</horstretch> | ||||
|                 <verstretch>0</verstretch> | ||||
|                </sizepolicy> | ||||
|               </property> | ||||
|               <property name="text"> | ||||
|                <string>Проверить</string> | ||||
|               </property> | ||||
|              </widget> | ||||
|             </item> | ||||
|            </layout> | ||||
|           </widget> | ||||
|          </item> | ||||
|         </layout> | ||||
|        </item> | ||||
|        <item row="3" column="0"> | ||||
|         <spacer name="verticalSpacer"> | ||||
|        <item> | ||||
|         <spacer name="verticalSpacer_4"> | ||||
|          <property name="orientation"> | ||||
|           <enum>Qt::Vertical</enum> | ||||
|          </property> | ||||
|          <property name="sizeHint" stdset="0"> | ||||
|           <size> | ||||
|            <width>20</width> | ||||
|            <height>270</height> | ||||
|            <height>40</height> | ||||
|           </size> | ||||
|          </property> | ||||
|         </spacer> | ||||
|        </item> | ||||
|       </layout> | ||||
|      </widget> | ||||
|      <widget class="QWidget" name="recognizeTab"> | ||||
|       <attribute name="title"> | ||||
|        <string>Распознавание</string> | ||||
|       </attribute> | ||||
|      <widget class="QWidget" name="pageRecognize"> | ||||
|       <layout class="QGridLayout" name="gridLayout_2"> | ||||
|        <item row="1" column="0"> | ||||
|         <widget class="QLabel" name="label_4"> | ||||
|          <property name="toolTip"> | ||||
|           <string><html><head/><body><p>Заполняется на основании содержания tessdata</p></body></html></string> | ||||
|          </property> | ||||
|          <property name="text"> | ||||
|           <string>Language</string> | ||||
|          </property> | ||||
|          <property name="buddy"> | ||||
|           <cstring>tesseractLangCombo</cstring> | ||||
|          </property> | ||||
|         </widget> | ||||
|        </item> | ||||
|        <item row="0" column="3"> | ||||
|         <widget class="QToolButton" name="tessdataButton"> | ||||
|          <property name="text"> | ||||
|           <string>...</string> | ||||
|          </property> | ||||
|         </widget> | ||||
|        </item> | ||||
|        <item row="2" column="1"> | ||||
|         <spacer name="verticalSpacer_2"> | ||||
|          <property name="orientation"> | ||||
|           <enum>Qt::Vertical</enum> | ||||
|          </property> | ||||
|          <property name="sizeHint" stdset="0"> | ||||
|           <size> | ||||
|            <width>20</width> | ||||
|            <height>40</height> | ||||
|           </size> | ||||
|          </property> | ||||
|         </spacer> | ||||
|        </item> | ||||
|        <item row="0" column="0"> | ||||
|         <widget class="QLabel" name="label_2"> | ||||
|          <property name="text"> | ||||
| @ -278,66 +241,50 @@ | ||||
|          </property> | ||||
|         </widget> | ||||
|        </item> | ||||
|        <item row="0" column="1"> | ||||
|        <item row="0" column="1" colspan="2"> | ||||
|         <widget class="QLineEdit" name="tessdataEdit"> | ||||
|          <property name="placeholderText"> | ||||
|           <string>Enter path</string> | ||||
|          </property> | ||||
|         </widget> | ||||
|        </item> | ||||
|        <item row="0" column="2"> | ||||
|         <widget class="QToolButton" name="tessdataButton"> | ||||
|          <property name="text"> | ||||
|           <string>...</string> | ||||
|          </property> | ||||
|         </widget> | ||||
|        <item row="1" column="1" colspan="3"> | ||||
|         <widget class="QComboBox" name="tesseractLangCombo"/> | ||||
|        </item> | ||||
|        <item row="1" column="0"> | ||||
|         <widget class="QLabel" name="label_4"> | ||||
|       </layout> | ||||
|      </widget> | ||||
|      <widget class="QWidget" name="pageCorrect"> | ||||
|       <layout class="QGridLayout" name="gridLayout_10"> | ||||
|        <item row="0" column="0"> | ||||
|         <widget class="QLabel" name="label_18"> | ||||
|          <property name="toolTip"> | ||||
|           <string><html><head/><body><p>Заполняется на основании содержания tessdata</p></body></html></string> | ||||
|          </property> | ||||
|          <property name="text"> | ||||
|           <string>Язык распознавания</string> | ||||
|           <string>Language:</string> | ||||
|          </property> | ||||
|          <property name="buddy"> | ||||
|           <cstring>ocrLangCombo</cstring> | ||||
|           <cstring>tesseractLangCombo</cstring> | ||||
|          </property> | ||||
|         </widget> | ||||
|        </item> | ||||
|        <item row="1" column="1" colspan="2"> | ||||
|         <widget class="QComboBox" name="ocrLangCombo"/> | ||||
|        <item row="0" column="1"> | ||||
|         <widget class="QComboBox" name="correctLangCombo"/> | ||||
|        </item> | ||||
|        <item row="2" column="0"> | ||||
|         <widget class="QLabel" name="label_5"> | ||||
|          <property name="toolTip"> | ||||
|           <string><html><head/><body><p>Масштабирование изображения для улучшения распознания. Больше - лучше (до определенных пределов), но медленнее и потребляет больше памяти.</p><p>Рекомендуемые значения от 5 до 10.</p></body></html></string> | ||||
|          </property> | ||||
|          <property name="text"> | ||||
|           <string>Увеличение масштаба</string> | ||||
|          </property> | ||||
|          <property name="buddy"> | ||||
|           <cstring>imageScaleSpin</cstring> | ||||
|          </property> | ||||
|         </widget> | ||||
|        </item> | ||||
|        <item row="2" column="1" colspan="2"> | ||||
|         <widget class="QSpinBox" name="imageScaleSpin"/> | ||||
|        </item> | ||||
|        <item row="3" column="0" colspan="3"> | ||||
|        <item row="1" column="0" colspan="2"> | ||||
|         <widget class="QLabel" name="label_11"> | ||||
|          <property name="toolTip"> | ||||
|           <string><html><head/><body><p>Символы, регулярно распознаваемые с ошибками. При обнаружении будут заменены на указанные.</p></body></html></string> | ||||
|          </property> | ||||
|          <property name="text"> | ||||
|           <string>Исправления:</string> | ||||
|           <string>Corrections</string> | ||||
|          </property> | ||||
|          <property name="alignment"> | ||||
|           <set>Qt::AlignCenter</set> | ||||
|          </property> | ||||
|         </widget> | ||||
|        </item> | ||||
|        <item row="4" column="0" colspan="3"> | ||||
|        <item row="2" column="0" colspan="2"> | ||||
|         <widget class="QTableWidget" name="recognizerFixTable"> | ||||
|          <property name="selectionBehavior"> | ||||
|           <enum>QAbstractItemView::SelectRows</enum> | ||||
| @ -345,59 +292,45 @@ | ||||
|          <property name="sortingEnabled"> | ||||
|           <bool>true</bool> | ||||
|          </property> | ||||
|          <column> | ||||
|           <property name="text"> | ||||
|            <string>Язык</string> | ||||
|           </property> | ||||
|          </column> | ||||
|          <column> | ||||
|           <property name="text"> | ||||
|            <string>Исходный текст</string> | ||||
|           </property> | ||||
|          </column> | ||||
|          <column> | ||||
|           <property name="text"> | ||||
|            <string>Исправление</string> | ||||
|           </property> | ||||
|          </column> | ||||
|         </widget> | ||||
|        </item> | ||||
|       </layout> | ||||
|      </widget> | ||||
|      <widget class="QWidget" name="translateTab"> | ||||
|       <attribute name="title"> | ||||
|        <string>Перевод</string> | ||||
|       </attribute> | ||||
|       <layout class="QGridLayout" name="gridLayout_7"> | ||||
|      <widget class="QWidget" name="pageTranslate"> | ||||
|       <layout class="QGridLayout" name="gridLayout_9"> | ||||
|        <item row="0" column="0"> | ||||
|         <widget class="QCheckBox" name="doTranslationCheck"> | ||||
|         <widget class="QCheckBox" name="translatorDebugCheck"> | ||||
|          <property name="toolTip"> | ||||
|           <string><html><head/><body><p>Необходимо ли переводить (вкл) распознанный текст.</p></body></html></string> | ||||
|           <string><html><head/><body><p>Отображает окно переводчика. Следует использовать только для разработки переводчиков.</p></body></html></string> | ||||
|          </property> | ||||
|          <property name="text"> | ||||
|           <string>Переводить текст</string> | ||||
|           <string>Debug mode</string> | ||||
|          </property> | ||||
|         </widget> | ||||
|        </item> | ||||
|        <item row="3" column="0"> | ||||
|         <widget class="QLabel" name="label_9"> | ||||
|          <property name="toolTip"> | ||||
|           <string><html><head/><body><p>Максимальное время, которое может быть затрачено на перевод, чтобы он не считался &quot;зависшим&quot;.</p></body></html></string> | ||||
|          </property> | ||||
|          <property name="text"> | ||||
|           <string>Максимальное время перевода:</string> | ||||
|          </property> | ||||
|         </widget> | ||||
|        </item> | ||||
|        <item row="3" column="1" colspan="2"> | ||||
|        <item row="3" column="2"> | ||||
|         <widget class="QSpinBox" name="translateTimeoutSpin"> | ||||
|          <property name="suffix"> | ||||
|           <string> сек.</string> | ||||
|           <string> secs</string> | ||||
|          </property> | ||||
|         </widget> | ||||
|        </item> | ||||
|        <item row="4" column="1" colspan="2"> | ||||
|         <widget class="QComboBox" name="translateLangCombo"/> | ||||
|        <item row="2" column="0" colspan="2"> | ||||
|         <widget class="QCheckBox" name="ignoreSslCheck"> | ||||
|          <property name="text"> | ||||
|           <string>Ignore SSL errors</string> | ||||
|          </property> | ||||
|         </widget> | ||||
|        </item> | ||||
|        <item row="5" column="0" colspan="3"> | ||||
|         <widget class="QLabel" name="label_10"> | ||||
|          <property name="text"> | ||||
|           <string>Translators</string> | ||||
|          </property> | ||||
|          <property name="alignment"> | ||||
|           <set>Qt::AlignCenter</set> | ||||
|          </property> | ||||
|         </widget> | ||||
|        </item> | ||||
|        <item row="4" column="0"> | ||||
|         <widget class="QLabel" name="label_6"> | ||||
| @ -405,23 +338,13 @@ | ||||
|           <string><html><head/><body><p>Язык, на который осуществляется перевод.</p></body></html></string> | ||||
|          </property> | ||||
|          <property name="text"> | ||||
|           <string>Язык результата:</string> | ||||
|           <string>Language:</string> | ||||
|          </property> | ||||
|          <property name="buddy"> | ||||
|           <cstring>translateLangCombo</cstring> | ||||
|          </property> | ||||
|         </widget> | ||||
|        </item> | ||||
|        <item row="5" column="0" colspan="3"> | ||||
|         <widget class="QLabel" name="label_10"> | ||||
|          <property name="text"> | ||||
|           <string>Переводчики:</string> | ||||
|          </property> | ||||
|          <property name="alignment"> | ||||
|           <set>Qt::AlignCenter</set> | ||||
|          </property> | ||||
|         </widget> | ||||
|        </item> | ||||
|        <item row="6" column="0" colspan="3"> | ||||
|         <widget class="QListWidget" name="translatorList"> | ||||
|          <property name="toolTip"> | ||||
| @ -438,29 +361,148 @@ | ||||
|          </property> | ||||
|         </widget> | ||||
|        </item> | ||||
|        <item row="0" column="1" colspan="2"> | ||||
|         <widget class="QCheckBox" name="translatorDebugCheck"> | ||||
|        <item row="1" column="0" colspan="2"> | ||||
|         <widget class="QCheckBox" name="doTranslationCheck"> | ||||
|          <property name="toolTip"> | ||||
|           <string><html><head/><body><p>Отображает окно переводчика. Следует использовать только для разработки переводчиков.</p></body></html></string> | ||||
|           <string><html><head/><body><p>Необходимо ли переводить (вкл) распознанный текст.</p></body></html></string> | ||||
|          </property> | ||||
|          <property name="text"> | ||||
|           <string>Режим отладки</string> | ||||
|           <string>Translate text</string> | ||||
|          </property> | ||||
|         </widget> | ||||
|        </item> | ||||
|        <item row="4" column="1" colspan="2"> | ||||
|         <widget class="QComboBox" name="translateLangCombo"/> | ||||
|        </item> | ||||
|        <item row="3" column="0" colspan="2"> | ||||
|         <widget class="QLabel" name="label_9"> | ||||
|          <property name="toolTip"> | ||||
|           <string><html><head/><body><p>Максимальное время, которое может быть затрачено на перевод, чтобы он не считался &quot;зависшим&quot;.</p></body></html></string> | ||||
|          </property> | ||||
|          <property name="text"> | ||||
|           <string>Single translator timeout:</string> | ||||
|          </property> | ||||
|         </widget> | ||||
|        </item> | ||||
|       </layout> | ||||
|      </widget> | ||||
|      <widget class="QWidget" name="pageRepresent"> | ||||
|       <layout class="QGridLayout" name="gridLayout_7"> | ||||
|        <item row="0" column="0"> | ||||
|         <widget class="QGroupBox" name="resultGroup"> | ||||
|          <property name="sizePolicy"> | ||||
|           <sizepolicy hsizetype="Maximum" vsizetype="Maximum"> | ||||
|            <horstretch>0</horstretch> | ||||
|            <verstretch>0</verstretch> | ||||
|           </sizepolicy> | ||||
|          </property> | ||||
|          <property name="title"> | ||||
|           <string>Result type</string> | ||||
|          </property> | ||||
|          <layout class="QGridLayout" name="gridLayout_4"> | ||||
|           <item row="0" column="0"> | ||||
|            <widget class="QRadioButton" name="trayRadio"> | ||||
|             <property name="sizePolicy"> | ||||
|              <sizepolicy hsizetype="Maximum" vsizetype="Fixed"> | ||||
|               <horstretch>0</horstretch> | ||||
|               <verstretch>0</verstretch> | ||||
|              </sizepolicy> | ||||
|             </property> | ||||
|             <property name="text"> | ||||
|              <string>Tray</string> | ||||
|             </property> | ||||
|            </widget> | ||||
|           </item> | ||||
|           <item row="0" column="1"> | ||||
|            <widget class="QRadioButton" name="dialogRadio"> | ||||
|             <property name="sizePolicy"> | ||||
|              <sizepolicy hsizetype="Maximum" vsizetype="Fixed"> | ||||
|               <horstretch>0</horstretch> | ||||
|               <verstretch>0</verstretch> | ||||
|              </sizepolicy> | ||||
|             </property> | ||||
|             <property name="text"> | ||||
|              <string>Window</string> | ||||
|             </property> | ||||
|             <property name="checked"> | ||||
|              <bool>true</bool> | ||||
|             </property> | ||||
|            </widget> | ||||
|           </item> | ||||
|          </layout> | ||||
|         </widget> | ||||
|        </item> | ||||
|        <item row="1" column="0"> | ||||
|         <widget class="QCheckBox" name="ignoreSslCheck"> | ||||
|          <property name="text"> | ||||
|           <string>Игнорировать ошибки SSL</string> | ||||
|         <spacer name="verticalSpacer_3"> | ||||
|          <property name="orientation"> | ||||
|           <enum>Qt::Vertical</enum> | ||||
|          </property> | ||||
|          <property name="sizeHint" stdset="0"> | ||||
|           <size> | ||||
|            <width>20</width> | ||||
|            <height>40</height> | ||||
|           </size> | ||||
|          </property> | ||||
|         </spacer> | ||||
|        </item> | ||||
|        <item row="0" column="1"> | ||||
|         <spacer name="horizontalSpacer"> | ||||
|          <property name="orientation"> | ||||
|           <enum>Qt::Horizontal</enum> | ||||
|          </property> | ||||
|          <property name="sizeHint" stdset="0"> | ||||
|           <size> | ||||
|            <width>40</width> | ||||
|            <height>20</height> | ||||
|           </size> | ||||
|          </property> | ||||
|         </spacer> | ||||
|        </item> | ||||
|       </layout> | ||||
|      </widget> | ||||
|      <widget class="QWidget" name="pageUpdate"> | ||||
|       <layout class="QVBoxLayout" name="verticalLayout_2"> | ||||
|        <item> | ||||
|         <widget class="QWidget" name="widget_3" native="true"> | ||||
|          <layout class="QGridLayout" name="gridLayout_5"> | ||||
|           <item row="0" column="0"> | ||||
|            <widget class="QLabel" name="label_17"> | ||||
|             <property name="text"> | ||||
|              <string>Check for updates:</string> | ||||
|             </property> | ||||
|            </widget> | ||||
|           </item> | ||||
|           <item row="0" column="1"> | ||||
|            <widget class="QComboBox" name="updateCombo"/> | ||||
|           </item> | ||||
|           <item row="0" column="2"> | ||||
|            <widget class="QPushButton" name="updateButton"> | ||||
|             <property name="sizePolicy"> | ||||
|              <sizepolicy hsizetype="Maximum" vsizetype="Fixed"> | ||||
|               <horstretch>0</horstretch> | ||||
|               <verstretch>0</verstretch> | ||||
|              </sizepolicy> | ||||
|             </property> | ||||
|             <property name="text"> | ||||
|              <string>Check now</string> | ||||
|             </property> | ||||
|            </widget> | ||||
|           </item> | ||||
|          </layout> | ||||
|         </widget> | ||||
|        </item> | ||||
|        <item row="1" column="1" colspan="2"> | ||||
|         <widget class="QCheckBox" name="forceRotateCheck"> | ||||
|          <property name="text"> | ||||
|           <string>Принудительно менять переводчики</string> | ||||
|        <item> | ||||
|         <spacer name="verticalSpacer"> | ||||
|          <property name="orientation"> | ||||
|           <enum>Qt::Vertical</enum> | ||||
|          </property> | ||||
|         </widget> | ||||
|          <property name="sizeHint" stdset="0"> | ||||
|           <size> | ||||
|            <width>20</width> | ||||
|            <height>389</height> | ||||
|           </size> | ||||
|          </property> | ||||
|         </spacer> | ||||
|        </item> | ||||
|       </layout> | ||||
|      </widget> | ||||
| @ -469,29 +511,16 @@ | ||||
|   </layout> | ||||
|  </widget> | ||||
|  <tabstops> | ||||
|   <tabstop>tabWidget</tabstop> | ||||
|   <tabstop>captureEdit</tabstop> | ||||
|   <tabstop>repeatCaptureEdit</tabstop> | ||||
|   <tabstop>repeatEdit</tabstop> | ||||
|   <tabstop>clipboardEdit</tabstop> | ||||
|   <tabstop>trayRadio</tabstop> | ||||
|   <tabstop>dialogRadio</tabstop> | ||||
|   <tabstop>proxyTypeCombo</tabstop> | ||||
|   <tabstop>proxyHostEdit</tabstop> | ||||
|   <tabstop>proxyPortSpin</tabstop> | ||||
|   <tabstop>proxyUserEdit</tabstop> | ||||
|   <tabstop>proxyPassEdit</tabstop> | ||||
|   <tabstop>proxySaveCheck</tabstop> | ||||
|   <tabstop>tessdataEdit</tabstop> | ||||
|   <tabstop>tessdataButton</tabstop> | ||||
|   <tabstop>ocrLangCombo</tabstop> | ||||
|   <tabstop>imageScaleSpin</tabstop> | ||||
|   <tabstop>recognizerFixTable</tabstop> | ||||
|   <tabstop>doTranslationCheck</tabstop> | ||||
|   <tabstop>translatorDebugCheck</tabstop> | ||||
|   <tabstop>translateTimeoutSpin</tabstop> | ||||
|   <tabstop>translateLangCombo</tabstop> | ||||
|   <tabstop>translatorList</tabstop> | ||||
|  </tabstops> | ||||
|  <resources/> | ||||
|  <connections> | ||||
|  | ||||
| @ -1,15 +0,0 @@ | ||||
| #ifndef ST_ASSERT_H | ||||
| #define ST_ASSERT_H | ||||
| 
 | ||||
| #include <assert.h> | ||||
| 
 | ||||
| #if !defined(ST_ASSERT) | ||||
| #  if defined(ST_NO_ASSERT) | ||||
| #    define ST_ASSERT(CONDITION) | ||||
| #  else | ||||
| #    define ST_ASSERT(CONDITION) assert (CONDITION) | ||||
| #  endif | ||||
| #endif | ||||
| 
 | ||||
| #endif // ST_ASSERT_H
 | ||||
| 
 | ||||
							
								
								
									
										21
									
								
								src/stfwd.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,21 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <memory> | ||||
| 
 | ||||
| class QString; | ||||
| class QStringList; | ||||
| 
 | ||||
| class Manager; | ||||
| class Settings; | ||||
| class Task; | ||||
| class Translator; | ||||
| class TrayIcon; | ||||
| class Capturer; | ||||
| class Representer; | ||||
| class Translator; | ||||
| class Corrector; | ||||
| class Recognizer; | ||||
| 
 | ||||
| using TaskPtr = std::shared_ptr<Task>; | ||||
| using LanguageId = QString; | ||||
| using LanguageIds = QStringList; | ||||
							
								
								
									
										32
									
								
								src/task.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,32 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include "stfwd.h" | ||||
| 
 | ||||
| #include <QPixmap> | ||||
| 
 | ||||
| class Task | ||||
| { | ||||
| public: | ||||
|   bool isNull() const { return captured.isNull() && !sourceLanguage.isEmpty(); } | ||||
|   bool isValid() const { return error.isEmpty(); } | ||||
|   //  void trace(const QString& message);
 | ||||
| 
 | ||||
|   QPoint capturePoint; | ||||
|   QPixmap captured; | ||||
|   QString recognized; | ||||
|   QString translated{"sample"}; | ||||
| 
 | ||||
|   LanguageId sourceLanguage{"eng"}; | ||||
|   LanguageId targetLanguage;  //{"ru"};
 | ||||
| 
 | ||||
|   QStringList translators{"google.js"}; | ||||
| 
 | ||||
|   QString error; | ||||
|   //  QStringList traceLog;
 | ||||
| 
 | ||||
|   //  bool swapLanguages;
 | ||||
| }; | ||||
| 
 | ||||
| using TaskPtr = std::shared_ptr<Task>; | ||||
| 
 | ||||
| Q_DECLARE_METATYPE(TaskPtr); | ||||
							
								
								
									
										210
									
								
								src/translate/translator.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,210 @@ | ||||
| #include "translator.h" | ||||
| #include "debug.h" | ||||
| #include "manager.h" | ||||
| #include "settings.h" | ||||
| #include "task.h" | ||||
| #include "webpage.h" | ||||
| #include "webpageproxy.h" | ||||
| #include "widgetstate.h" | ||||
| 
 | ||||
| #include <QBoxLayout> | ||||
| #include <QCloseEvent> | ||||
| #include <QFile> | ||||
| #include <QSplitter> | ||||
| #include <QTabWidget> | ||||
| #include <QTcpSocket> | ||||
| #include <QTextEdit> | ||||
| #include <QWebChannel> | ||||
| #include <QWebEngineView> | ||||
| 
 | ||||
| #include <unordered_set> | ||||
| 
 | ||||
| static std::map<QString, QString> loadScripts(const QString &dir, | ||||
|                                               const QStringList &scriptNames) | ||||
| { | ||||
|   std::map<QString, QString> result; | ||||
|   for (const auto &name : scriptNames) { | ||||
|     QFile f(dir + QLatin1Char('/') + name); | ||||
|     if (!f.open(QFile::ReadOnly)) | ||||
|       continue; | ||||
|     const auto script = QString::fromUtf8(f.readAll()); | ||||
|     if (!script.isEmpty()) | ||||
|       result.emplace(name, script); | ||||
|   } | ||||
|   return result; | ||||
| } | ||||
| 
 | ||||
| Translator::Translator(Manager &manager) | ||||
|   : manager_(manager) | ||||
|   , view_(nullptr) | ||||
|   , tabs_(new QTabWidget(this)) | ||||
| { | ||||
| #ifdef DEVELOP | ||||
|   { | ||||
|     QTcpSocket socket; | ||||
|     if (socket.bind()) { | ||||
|       quint16 port = socket.localPort(); | ||||
|       LTRACE() << "debug port" << port; | ||||
|       qputenv("QTWEBENGINE_REMOTE_DEBUGGING", QString::number(port).toUtf8()); | ||||
|       socket.close(); | ||||
|     } | ||||
|   } | ||||
| #endif | ||||
| 
 | ||||
|   setObjectName("Translator"); | ||||
| 
 | ||||
|   view_ = new QWebEngineView(this); | ||||
| 
 | ||||
|   auto splitter = new QSplitter(Qt::Vertical, this); | ||||
|   splitter->addWidget(view_); | ||||
|   splitter->addWidget(tabs_); | ||||
| 
 | ||||
|   auto layout = new QVBoxLayout(this); | ||||
|   layout->addWidget(splitter); | ||||
| 
 | ||||
|   startTimer(1000); | ||||
| 
 | ||||
|   connect(tabs_, &QTabWidget::currentChanged,  //
 | ||||
|           this, &Translator::changeCurrentPage); | ||||
| 
 | ||||
|   view_->setMinimumSize(200, 200); | ||||
| 
 | ||||
|   new WidgetState(this); | ||||
| } | ||||
| 
 | ||||
| Translator::~Translator() = default; | ||||
| 
 | ||||
| void Translator::translate(const TaskPtr &task) | ||||
| { | ||||
|   SOFT_ASSERT(task, return ); | ||||
|   queue_.push_back(task); | ||||
|   processQueue(); | ||||
| } | ||||
| 
 | ||||
| void Translator::updateSettings(const Settings &settings) | ||||
| { | ||||
|   view_->setPage(nullptr); | ||||
|   pages_.clear(); | ||||
| 
 | ||||
|   tabs_->blockSignals(true); | ||||
|   for (auto i = 0, end = tabs_->count(); i < end; ++i) { | ||||
|     auto tab = tabs_->widget(0); | ||||
|     tabs_->removeTab(0); | ||||
|     tab->deleteLater(); | ||||
|   } | ||||
|   tabs_->blockSignals(false); | ||||
| 
 | ||||
|   const auto loaded = | ||||
|       loadScripts(settings.translatorsDir, settings.translators); | ||||
|   if (loaded.empty()) { | ||||
|     manager_.fatalError( | ||||
|         tr("No translators loaded from %1 (named %2)") | ||||
|             .arg(settings.translatorsDir, settings.translators.join(", "))); | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   for (const auto &script : loaded) { | ||||
|     const auto &scriptName = script.first; | ||||
|     const auto &scriptText = script.second; | ||||
|     const auto pageIt = pages_.emplace( | ||||
|         scriptName, std::make_unique<WebPage>(*this, scriptText, scriptName)); | ||||
|     SOFT_ASSERT(pageIt.second, continue); | ||||
| 
 | ||||
|     const auto &page = pageIt.first->second; | ||||
|     page->setIgnoreSslErrors(settings.ignoreSslErrors); | ||||
|     page->setTimeout(settings.translationTimeout); | ||||
| 
 | ||||
|     auto log = new QTextEdit(tabs_); | ||||
|     tabs_->addTab(log, scriptName); | ||||
| 
 | ||||
|     connect(page.get(), &WebPage::log,  //
 | ||||
|             log, &QTextEdit::append); | ||||
| 
 | ||||
|     SOFT_ASSERT(log->document(), continue) | ||||
|     log->document()->setMaximumBlockCount(1000); | ||||
|   } | ||||
| 
 | ||||
|   if (settings.debugMode) { | ||||
|     show(); | ||||
|   } else { | ||||
|     hide(); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void Translator::changeCurrentPage(int tabIndex) | ||||
| { | ||||
|   const auto name = tabs_->tabText(tabIndex); | ||||
|   SOFT_ASSERT(pages_.count(name), return ); | ||||
|   view_->setPage(pages_[name].get()); | ||||
| } | ||||
| 
 | ||||
| void Translator::processQueue() | ||||
| { | ||||
|   if (queue_.empty()) | ||||
|     return; | ||||
| 
 | ||||
|   std::unordered_set<QString> idlePages; | ||||
|   std::unordered_set<Task *> busyTasks; | ||||
| 
 | ||||
|   for (auto &i : pages_) { | ||||
|     if (i.second->isBusy()) | ||||
|       busyTasks.insert(i.second->task().get()); | ||||
|     else | ||||
|       idlePages.insert(i.first); | ||||
|   } | ||||
| 
 | ||||
|   if (idlePages.empty()) | ||||
|     return; | ||||
| 
 | ||||
|   std::vector<TaskPtr> finishedTasks; | ||||
|   for (const auto &task : queue_) { | ||||
|     if (idlePages.empty()) | ||||
|       break; | ||||
| 
 | ||||
|     if (busyTasks.count(task.get())) | ||||
|       continue; | ||||
| 
 | ||||
|     if (task->translators.isEmpty()) { | ||||
|       task->error = tr("All translators failed"); | ||||
|       finishedTasks.push_back(task); | ||||
|       continue; | ||||
|     } | ||||
| 
 | ||||
|     for (auto &translator : task->translators) { | ||||
|       if (!idlePages.count(translator)) | ||||
|         continue; | ||||
| 
 | ||||
|       SOFT_ASSERT(pages_.count(translator), continue); | ||||
|       pages_[translator]->start(task); | ||||
|       task->translators.removeOne(translator); | ||||
|       idlePages.erase(translator); | ||||
|       break; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   if (!finishedTasks.empty()) { | ||||
|     for (const auto &task : finishedTasks) markTranslated(task); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void Translator::markTranslated(const TaskPtr &task) | ||||
| { | ||||
|   manager_.translated(task); | ||||
|   queue_.erase(std::remove(queue_.begin(), queue_.end(), task), queue_.end()); | ||||
| } | ||||
| 
 | ||||
| void Translator::finish(const TaskPtr &task) | ||||
| { | ||||
|   markTranslated(task); | ||||
|   processQueue(); | ||||
| } | ||||
| 
 | ||||
| void Translator::timerEvent(QTimerEvent * /*event*/) | ||||
| { | ||||
|   processQueue(); | ||||
| } | ||||
| 
 | ||||
| void Translator::closeEvent(QCloseEvent *event) | ||||
| { | ||||
|   event->ignore(); | ||||
| } | ||||
							
								
								
									
										37
									
								
								src/translate/translator.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,37 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include "stfwd.h" | ||||
| 
 | ||||
| #include <QWidget> | ||||
| 
 | ||||
| class QWebEngineView; | ||||
| class QTabWidget; | ||||
| 
 | ||||
| class WebPage; | ||||
| 
 | ||||
| class Translator : public QWidget | ||||
| { | ||||
|   Q_OBJECT | ||||
| public: | ||||
|   explicit Translator(Manager &manager); | ||||
|   ~Translator(); | ||||
| 
 | ||||
|   void translate(const TaskPtr &task); | ||||
|   void updateSettings(const Settings &settings); | ||||
|   void finish(const TaskPtr &task); | ||||
| 
 | ||||
| protected: | ||||
|   void timerEvent(QTimerEvent *event) override; | ||||
|   void closeEvent(QCloseEvent *event) override; | ||||
| 
 | ||||
| private: | ||||
|   void changeCurrentPage(int tabIndex); | ||||
|   void processQueue(); | ||||
|   void markTranslated(const TaskPtr &task); | ||||
| 
 | ||||
|   Manager &manager_; | ||||
|   QWebEngineView *view_; | ||||
|   QTabWidget *tabs_; | ||||
|   std::vector<TaskPtr> queue_; | ||||
|   std::map<QString, std::unique_ptr<WebPage>> pages_; | ||||
| }; | ||||
							
								
								
									
										166
									
								
								src/translate/webpage.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,166 @@ | ||||
| #include "webpage.h" | ||||
| #include "debug.h" | ||||
| #include "languagecodes.h" | ||||
| #include "task.h" | ||||
| #include "translator.h" | ||||
| #include "webpageproxy.h" | ||||
| 
 | ||||
| #include <QWebEngineProfile> | ||||
| #include <QWebEngineScriptCollection> | ||||
| #include <QWebEngineSettings> | ||||
| #include <QtWebChannel> | ||||
| 
 | ||||
| WebPage::WebPage(Translator &translator, const QString &script, | ||||
|                  const QString &scriptName) | ||||
|   : QWebEnginePage(new QWebEngineProfile) | ||||
|   , translator_(translator) | ||||
|   , proxy_(new WebPageProxy(*this)) | ||||
| { | ||||
|   profile()->setParent(this); | ||||
| 
 | ||||
|   connect(this, &WebPage::proxyAuthenticationRequired, this, | ||||
|           &WebPage::authenticateProxy); | ||||
| 
 | ||||
|   scheduleWebchannelInitScript(); | ||||
|   scheduleTranslatorScript(script, scriptName); | ||||
| 
 | ||||
|   settings()->setAttribute(QWebEngineSettings::AutoLoadImages, false); | ||||
|   //  settings()->setAttribute(QWebEngineSettings::LocalStorageEnabled, true);
 | ||||
| 
 | ||||
|   auto channel = new QWebChannel(this); | ||||
|   channel->registerObject("proxy", proxy_.get()); | ||||
|   setWebChannel(channel, QWebEngineScript::ScriptWorldId::UserWorld); | ||||
| 
 | ||||
|   // to load scripts
 | ||||
|   setUrl(QUrl::fromUserInput("about:blank")); | ||||
| } | ||||
| 
 | ||||
| WebPage::~WebPage() = default; | ||||
| 
 | ||||
| void WebPage::scheduleWebchannelInitScript() | ||||
| { | ||||
|   QFile f(":/qtwebchannel/qwebchannel.js"); | ||||
|   if (!f.open(QFile::ReadOnly)) { | ||||
|     LERROR() << "Failed to open bundled file" << f.fileName(); | ||||
|     return; | ||||
|   } | ||||
|   const auto data = | ||||
|       QString::fromUtf8(f.readAll()) + | ||||
|       R"(new QWebChannel(qt.webChannelTransport, function(channel){ | ||||
| window.proxy = channel.objects.proxy; | ||||
| if (typeof init === "function") init (); | ||||
|       });)"; | ||||
| 
 | ||||
|   QWebEngineScript js; | ||||
| 
 | ||||
|   js.setSourceCode(data); | ||||
|   js.setName("qwebchannel.js"); | ||||
|   js.setWorldId(QWebEngineScript::UserWorld); | ||||
|   js.setInjectionPoint(QWebEngineScript::Deferred); | ||||
|   js.setRunsOnSubFrames(false); | ||||
| 
 | ||||
|   SOFT_ASSERT(profile(), return ); | ||||
|   profile()->scripts()->insert(js); | ||||
| } | ||||
| 
 | ||||
| void WebPage::scheduleTranslatorScript(const QString &script, | ||||
|                                        const QString &scriptName) | ||||
| { | ||||
|   QWebEngineScript js; | ||||
| 
 | ||||
|   js.setSourceCode(script); | ||||
|   js.setName(scriptName); | ||||
|   js.setWorldId(QWebEngineScript::UserWorld); | ||||
|   js.setInjectionPoint(QWebEngineScript::Deferred); | ||||
|   js.setRunsOnSubFrames(false); | ||||
| 
 | ||||
|   SOFT_ASSERT(profile(), return ); | ||||
|   profile()->scripts()->insert(js); | ||||
| } | ||||
| 
 | ||||
| void WebPage::setIgnoreSslErrors(bool ignoreSslErrors) | ||||
| { | ||||
|   ignoreSslErrors_ = ignoreSslErrors; | ||||
| } | ||||
| 
 | ||||
| void WebPage::setTimeout(std::chrono::seconds timeout) | ||||
| { | ||||
|   timeout_ = timeout; | ||||
| } | ||||
| 
 | ||||
| void WebPage::start(const TaskPtr &task) | ||||
| { | ||||
|   LanguageCodes languages; | ||||
|   auto langCodes = languages.findById(task->targetLanguage); | ||||
|   if (!langCodes) { | ||||
|     task->error = QObject::tr("unknown translation language: %1") | ||||
|                       .arg(task->targetLanguage); | ||||
|     translator_.finish(task); | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   task_ = task; | ||||
|   isBusy_ = true; | ||||
|   nextIdleTime_ = QDateTime::currentDateTime().addSecs(timeout_.count()); | ||||
| 
 | ||||
|   proxy_->translate(task->recognized, task->sourceLanguage, | ||||
|                     langCodes->iso639_1); | ||||
| } | ||||
| 
 | ||||
| bool WebPage::isBusy() const | ||||
| { | ||||
|   return task_ && isBusy_ && QDateTime::currentDateTime() < nextIdleTime_; | ||||
| } | ||||
| 
 | ||||
| void WebPage::setTranslated(const QString &text) | ||||
| { | ||||
|   if (!isBusy()) | ||||
|     return; | ||||
| 
 | ||||
|   isBusy_ = false; | ||||
| 
 | ||||
|   SOFT_ASSERT(task_, return ) | ||||
|   task_->translated = text; | ||||
|   translator_.finish(task_); | ||||
| } | ||||
| 
 | ||||
| void WebPage::setFailed(const QString &error) | ||||
| { | ||||
|   if (!isBusy()) | ||||
|     return; | ||||
| 
 | ||||
|   isBusy_ = false; | ||||
| 
 | ||||
|   SOFT_ASSERT(task_, return ) | ||||
|   //  task_->error = error;
 | ||||
|   translator_.finish(task_); | ||||
| } | ||||
| 
 | ||||
| TaskPtr WebPage::task() const | ||||
| { | ||||
|   return task_; | ||||
| } | ||||
| 
 | ||||
| void WebPage::javaScriptConsoleMessage( | ||||
|     QWebEnginePage::JavaScriptConsoleMessageLevel /*level*/, | ||||
|     const QString &message, int lineNumber, const QString &sourceID) | ||||
| { | ||||
|   qDebug() << sourceID << ":" << lineNumber << message; | ||||
|   emit log(QString("%1: %2 %3").arg(sourceID).arg(lineNumber).arg(message)); | ||||
| } | ||||
| 
 | ||||
| bool WebPage::certificateError(const QWebEngineCertificateError &error) | ||||
| { | ||||
|   qDebug() << "certificateError" << error.url() << error.error() | ||||
|            << error.errorDescription(); | ||||
|   return ignoreSslErrors_; | ||||
| } | ||||
| 
 | ||||
| void WebPage::authenticateProxy(const QUrl & /*requestUrl*/, | ||||
|                                 QAuthenticator *authenticator, | ||||
|                                 const QString & /*proxyHost*/) | ||||
| { | ||||
|   const auto proxy = QNetworkProxy::applicationProxy(); | ||||
|   authenticator->setUser(proxy.user()); | ||||
|   authenticator->setPassword(proxy.password()); | ||||
| } | ||||
							
								
								
									
										50
									
								
								src/translate/webpage.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,50 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include "stfwd.h" | ||||
| 
 | ||||
| #include <QWebEngineCertificateError> | ||||
| #include <QWebEngineView> | ||||
| 
 | ||||
| class WebPageProxy; | ||||
| 
 | ||||
| class WebPage : public QWebEnginePage | ||||
| { | ||||
|   Q_OBJECT | ||||
| public: | ||||
|   WebPage(Translator &translator, const QString &script, | ||||
|           const QString &scriptName); | ||||
|   ~WebPage(); | ||||
| 
 | ||||
|   void setIgnoreSslErrors(bool ignoreSslErrors); | ||||
|   void setTimeout(std::chrono::seconds timeout); | ||||
| 
 | ||||
|   void start(const TaskPtr &task); | ||||
|   void setTranslated(const QString &text); | ||||
|   void setFailed(const QString &error); | ||||
|   bool isBusy() const; | ||||
|   TaskPtr task() const; | ||||
| 
 | ||||
| signals: | ||||
|   void log(const QString &text); | ||||
| 
 | ||||
| protected: | ||||
|   void javaScriptConsoleMessage(JavaScriptConsoleMessageLevel level, | ||||
|                                 const QString &message, int lineNumber, | ||||
|                                 const QString &sourceID) override; | ||||
|   bool certificateError(const QWebEngineCertificateError &error) override; | ||||
| 
 | ||||
| private: | ||||
|   void authenticateProxy(const QUrl &requestUrl, QAuthenticator *authenticator, | ||||
|                          const QString &proxyHost); | ||||
|   void scheduleWebchannelInitScript(); | ||||
|   void scheduleTranslatorScript(const QString &script, | ||||
|                                 const QString &scriptName); | ||||
| 
 | ||||
|   Translator &translator_; | ||||
|   std::unique_ptr<WebPageProxy> proxy_; | ||||
|   TaskPtr task_; | ||||
|   bool ignoreSslErrors_{false}; | ||||
|   bool isBusy_{false}; | ||||
|   QDateTime nextIdleTime_; | ||||
|   std::chrono::seconds timeout_{15}; | ||||
| }; | ||||
							
								
								
									
										17
									
								
								src/translate/webpageproxy.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,17 @@ | ||||
| #include "webpageproxy.h" | ||||
| #include "webpage.h" | ||||
| 
 | ||||
| WebPageProxy::WebPageProxy(WebPage &page) | ||||
|   : page_(page) | ||||
| { | ||||
| } | ||||
| 
 | ||||
| void WebPageProxy::setTranslated(const QString &result) | ||||
| { | ||||
|   page_.setTranslated(result); | ||||
| } | ||||
| 
 | ||||
| void WebPageProxy::setFailed(const QString &error) | ||||
| { | ||||
|   page_.setFailed(error); | ||||
| } | ||||
							
								
								
									
										23
									
								
								src/translate/webpageproxy.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,23 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <QObject> | ||||
| 
 | ||||
| class WebPage; | ||||
| 
 | ||||
| class WebPageProxy : public QObject | ||||
| { | ||||
|   Q_OBJECT | ||||
| public: | ||||
|   explicit WebPageProxy(WebPage& page); | ||||
| 
 | ||||
| signals: | ||||
|   void terminated(); | ||||
|   void translate(const QString& text, const QString& from, const QString& to); | ||||
| 
 | ||||
| public slots: | ||||
|   void setTranslated(const QString& result); | ||||
|   void setFailed(const QString& error); | ||||
| 
 | ||||
| private: | ||||
|   WebPage& page_; | ||||
| }; | ||||
| @ -1,88 +0,0 @@ | ||||
| #include <QSettings> | ||||
| #include <QDir> | ||||
| #include <QFile> | ||||
| #include <QApplication> | ||||
| 
 | ||||
| #include "translatorhelper.h" | ||||
| #include "settings.h" | ||||
| 
 | ||||
| TranslatorHelper::TranslatorHelper () | ||||
|   : translatorsDir_ ("translators"), currentIndex_ (0), triesLeft_ (0) { | ||||
|   translatorsDir_ = QApplication::applicationDirPath () + QDir::separator () + translatorsDir_; | ||||
| } | ||||
| 
 | ||||
| void TranslatorHelper::setEnabledTranslators (const QStringList &enabled) const { | ||||
|   QSettings settings; | ||||
|   settings.beginGroup (settings_names::translationGroup); | ||||
|   settings.setValue (settings_names::translators, enabled.join ("|")); | ||||
| } | ||||
| 
 | ||||
| QStringList TranslatorHelper::possibleTranslators (QStringList &enabled) const { | ||||
| #define GET(FIELD) settings.value (settings_names::FIELD, settings_values::FIELD) | ||||
|   QSettings settings; | ||||
|   settings.beginGroup (settings_names::translationGroup); | ||||
|   QStringList exist = QDir (translatorsDir_).entryList (QStringList () << "*.js", QDir::Files); | ||||
|   QStringList saved = GET (translators).toString ().split ("|", QString::SkipEmptyParts); | ||||
|   QStringList on, off; | ||||
|   std::copy_if (saved.begin (), saved.end (), std::back_inserter (on), [&](const QString &i) { | ||||
|     return exist.contains (i); | ||||
|   }); | ||||
|   on = on.isEmpty () ? exist : on; | ||||
| 
 | ||||
|   std::copy_if (exist.begin (), exist.end (), std::back_inserter (off), [&](const QString &i) { | ||||
|     return !on.contains (i); | ||||
|   }); | ||||
| 
 | ||||
|   enabled = on; | ||||
|   return (on + off); | ||||
| #undef GET | ||||
| } | ||||
| 
 | ||||
| QStringList TranslatorHelper::enabledTranslatorScripts () const { | ||||
|   QStringList enabled; | ||||
|   possibleTranslators (enabled); | ||||
|   QStringList scripts; | ||||
|   for (const QString &name: enabled) { | ||||
|     QFile f (translatorsDir_ + QDir::separator () + name); | ||||
|     if (f.open (QFile::ReadOnly)) { | ||||
|       QString script = QString::fromUtf8 (f.readAll ()); | ||||
|       if (!script.isEmpty ()) { | ||||
|         scripts << script; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|   return scripts; | ||||
| } | ||||
| 
 | ||||
| void TranslatorHelper::loadScripts () { | ||||
|   scripts_ = enabledTranslatorScripts (); | ||||
| } | ||||
| 
 | ||||
| void TranslatorHelper::newItem (bool forceRotate) { | ||||
|   triesLeft_ = scripts_.size (); | ||||
|   currentIndex_ = forceRotate ? currentIndex_ + 1 : 0; | ||||
|   if (currentIndex_ >= scripts_.size ()) { | ||||
|     currentIndex_ = 0; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| QString TranslatorHelper::nextScript () { | ||||
|   --triesLeft_; | ||||
| 
 | ||||
|   if (++currentIndex_ >= scripts_.size ()) { | ||||
|     currentIndex_ = 0; | ||||
|   } | ||||
| 
 | ||||
|   return currentScript (); | ||||
| } | ||||
| 
 | ||||
| QString TranslatorHelper::currentScript () const { | ||||
|   if (triesLeft_ > 0 && currentIndex_ < scripts_.size ()) { | ||||
|     return scripts_.at (currentIndex_); | ||||
|   } | ||||
|   return QString (); | ||||
| } | ||||
| 
 | ||||
| bool TranslatorHelper::gotScripts () const { | ||||
|   return !scripts_.isEmpty (); | ||||
| } | ||||
| @ -1,28 +0,0 @@ | ||||
| #ifndef TRANSLATORHELPER_H | ||||
| #define TRANSLATORHELPER_H | ||||
| 
 | ||||
| #include <QStringList> | ||||
| 
 | ||||
| class TranslatorHelper { | ||||
|   public: | ||||
|     TranslatorHelper (); | ||||
| 
 | ||||
|     QStringList possibleTranslators (QStringList &enabled) const; | ||||
|     QStringList enabledTranslatorScripts () const; | ||||
| 
 | ||||
|     void setEnabledTranslators (const QStringList &enabled) const; | ||||
| 
 | ||||
|     void loadScripts (); | ||||
|     void newItem (bool forceRotate); | ||||
|     QString nextScript (); | ||||
|     QString currentScript () const; | ||||
|     bool gotScripts () const; | ||||
| 
 | ||||
|   private: | ||||
|     QString translatorsDir_; | ||||
|     QStringList scripts_; | ||||
|     int currentIndex_; | ||||
|     int triesLeft_; | ||||
| }; | ||||
| 
 | ||||
| #endif // TRANSLATORHELPER_H
 | ||||
							
								
								
									
										221
									
								
								src/trayicon.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,221 @@ | ||||
| #include "trayicon.h" | ||||
| #include "debug.h" | ||||
| #include "globalaction.h" | ||||
| #include "manager.h" | ||||
| #include "settings.h" | ||||
| 
 | ||||
| #include <QMenu> | ||||
| #include <QTimer> | ||||
| 
 | ||||
| TrayIcon::TrayIcon(Manager &manager) | ||||
|   : manager_(manager) | ||||
|   , tray_(std::make_unique<QSystemTrayIcon>()) | ||||
|   , iconUpdateTimer_(std::make_unique<QTimer>()) | ||||
| { | ||||
|   GlobalAction::init(); | ||||
| 
 | ||||
|   connect(tray_.get(), &QSystemTrayIcon::activated,  //
 | ||||
|           this, &TrayIcon::handleIconClick); | ||||
| 
 | ||||
|   iconUpdateTimer_->setSingleShot(true); | ||||
|   connect(iconUpdateTimer_.get(), &QTimer::timeout,  //
 | ||||
|           this, &TrayIcon::updateIcon); | ||||
| 
 | ||||
|   tray_->setContextMenu(contextMenu()); | ||||
|   setIcon(Icon::Idle, Duration::Permanent); | ||||
|   updateActions(); | ||||
|   tray_->show(); | ||||
| } | ||||
| 
 | ||||
| TrayIcon::~TrayIcon() = default; | ||||
| 
 | ||||
| void TrayIcon::updateSettings(const Settings &settings) | ||||
| { | ||||
|   QStringList failedActions; | ||||
|   if (!GlobalAction::update(captureAction_, settings.captureHotkey)) | ||||
|     failedActions << settings.captureHotkey; | ||||
|   if (!GlobalAction::update(repeatCaptureAction_, settings.repeatCaptureHotkey)) | ||||
|     failedActions << settings.repeatCaptureHotkey; | ||||
|   if (!GlobalAction::update(showLastAction_, settings.showLastHotkey)) | ||||
|     failedActions << settings.showLastHotkey; | ||||
|   if (!GlobalAction::update(clipboardAction_, settings.clipboardHotkey)) | ||||
|     failedActions << settings.clipboardHotkey; | ||||
| 
 | ||||
|   if (!failedActions.isEmpty()) { | ||||
|     showError(tr("Failed to register global shortcuts:\n%1") | ||||
|                   .arg(failedActions.join('\n'))); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void TrayIcon::blockActions(bool block) | ||||
| { | ||||
|   isActionsBlocked_ = block; | ||||
|   updateActions(); | ||||
| } | ||||
| 
 | ||||
| void TrayIcon::setTaskActionsEnabled(bool isEnabled) | ||||
| { | ||||
|   gotTask_ = isEnabled; | ||||
|   updateActions(); | ||||
| } | ||||
| 
 | ||||
| void TrayIcon::setRepeatCaptureEnabled(bool isEnabled) | ||||
| { | ||||
|   canRepeatCapture_ = isEnabled; | ||||
|   updateActions(); | ||||
| } | ||||
| 
 | ||||
| void TrayIcon::updateActions() | ||||
| { | ||||
|   if (isActionsBlocked_) { | ||||
|     QVector<QAction *> blockable{captureAction_, repeatCaptureAction_, | ||||
|                                  showLastAction_, settingsAction_}; | ||||
|     for (auto &action : blockable) action->setEnabled(false); | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   captureAction_->setEnabled(true); | ||||
|   settingsAction_->setEnabled(true); | ||||
| 
 | ||||
|   QVector<QAction *> taskActions{showLastAction_, clipboardAction_}; | ||||
|   for (auto &action : taskActions) action->setEnabled(gotTask_); | ||||
| 
 | ||||
|   repeatCaptureAction_->setEnabled(canRepeatCapture_); | ||||
| } | ||||
| 
 | ||||
| void TrayIcon::setIcon(TrayIcon::Icon icon, Duration duration) | ||||
| { | ||||
|   QMap<Icon, QString> icons{ | ||||
|       {Icon::Idle, QStringLiteral(":icons/app.png")}, | ||||
|       {Icon::Success, QStringLiteral(":icons/st_success.png")}, | ||||
|       {Icon::Busy, QStringLiteral(":icons/st_busy.png")}, | ||||
|       {Icon::Error, QStringLiteral(":icons/st_error.png")}, | ||||
|   }; | ||||
| 
 | ||||
|   tray_->setIcon(QIcon(icons.value(icon))); | ||||
| 
 | ||||
|   if (duration == Duration::Permanent) { | ||||
|     permanentIcon_ = icon; | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   const auto durationMsec = 3000; | ||||
|   iconUpdateTimer_->start(durationMsec); | ||||
| } | ||||
| 
 | ||||
| void TrayIcon::setActiveTaskCount(int count) | ||||
| { | ||||
|   activeTaskCount_ = count; | ||||
|   updateIcon(); | ||||
| } | ||||
| 
 | ||||
| void TrayIcon::resetFatalError() | ||||
| { | ||||
|   isFatalError_ = false; | ||||
|   updateIcon(); | ||||
| } | ||||
| 
 | ||||
| void TrayIcon::updateIcon() | ||||
| { | ||||
|   if (iconUpdateTimer_->isActive()) | ||||
|     return; | ||||
| 
 | ||||
|   if (isFatalError_) { | ||||
|     setIcon(Icon::Error, Duration::Permanent); | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   setIcon(activeTaskCount_ > 0 ? Icon::Busy : Icon::Idle, Duration::Permanent); | ||||
| } | ||||
| 
 | ||||
| void TrayIcon::showInformation(const QString &text) | ||||
| { | ||||
|   tray_->showMessage({}, text, QSystemTrayIcon::Information); | ||||
| } | ||||
| 
 | ||||
| void TrayIcon::showError(const QString &text) | ||||
| { | ||||
|   LERROR() << text; | ||||
|   setIcon(Icon::Error, Duration::Temporal); | ||||
|   tray_->showMessage(tr("Error"), text, QSystemTrayIcon::Warning); | ||||
| } | ||||
| 
 | ||||
| void TrayIcon::showFatalError(const QString &text) | ||||
| { | ||||
|   LERROR() << text; | ||||
|   isFatalError_ = true; | ||||
|   tray_->showMessage(tr("Error"), text, QSystemTrayIcon::Critical); | ||||
|   updateIcon(); | ||||
| } | ||||
| 
 | ||||
| void TrayIcon::showSuccess() | ||||
| { | ||||
|   setIcon(Icon::Success, Duration::Temporal); | ||||
| } | ||||
| 
 | ||||
| void TrayIcon::handleIconClick(QSystemTrayIcon::ActivationReason reason) | ||||
| { | ||||
|   if (reason == QSystemTrayIcon::Trigger && showLastAction_->isEnabled()) { | ||||
|     manager_.showLast(); | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   if (reason == QSystemTrayIcon::MiddleClick && clipboardAction_->isEnabled()) { | ||||
|     manager_.copyLastToClipboard(); | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   if (reason == QSystemTrayIcon::DoubleClick && | ||||
|       repeatCaptureAction_->isEnabled()) { | ||||
|     manager_.repeatCapture(); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| QMenu *TrayIcon::contextMenu() | ||||
| { | ||||
|   QMenu *menu = new QMenu(); | ||||
|   { | ||||
|     captureAction_ = menu->addAction(tr("Capture")); | ||||
|     connect(captureAction_, &QAction::triggered,  //
 | ||||
|             this, [this] { manager_.capture(); }); | ||||
|   } | ||||
|   { | ||||
|     repeatCaptureAction_ = menu->addAction(tr("Repeat capture")); | ||||
|     connect(repeatCaptureAction_, &QAction::triggered,  //
 | ||||
|             this, [this] { manager_.repeatCapture(); }); | ||||
|   } | ||||
| 
 | ||||
|   { | ||||
|     QMenu *translateMenu = menu->addMenu(tr("Result")); | ||||
|     { | ||||
|       showLastAction_ = translateMenu->addAction(tr("Show")); | ||||
|       connect(showLastAction_, &QAction::triggered,  //
 | ||||
|               this, [this] { manager_.showLast(); }); | ||||
|     } | ||||
|     { | ||||
|       clipboardAction_ = translateMenu->addAction(tr("To clipboard")); | ||||
|       connect(clipboardAction_, &QAction::triggered,  //
 | ||||
|               this, [this] { manager_.copyLastToClipboard(); }); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   { | ||||
|     settingsAction_ = menu->addAction(tr("Settings")); | ||||
|     connect(settingsAction_, &QAction::triggered,  //
 | ||||
|             this, [this] { manager_.settings(); }); | ||||
|   } | ||||
| 
 | ||||
|   { | ||||
|     auto action = menu->addAction(tr("About")); | ||||
|     connect(action, &QAction::triggered,  //
 | ||||
|             this, [this] { manager_.about(); }); | ||||
|   } | ||||
| 
 | ||||
|   { | ||||
|     auto action = menu->addAction(tr("Quit")); | ||||
|     connect(action, &QAction::triggered,  //
 | ||||
|             this, [this] { manager_.quit(); }); | ||||
|   } | ||||
| 
 | ||||
|   return menu; | ||||
| } | ||||
							
								
								
									
										55
									
								
								src/trayicon.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,55 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include "stfwd.h" | ||||
| 
 | ||||
| #include <QSystemTrayIcon> | ||||
| 
 | ||||
| class QAction; | ||||
| 
 | ||||
| class TrayIcon : public QObject | ||||
| { | ||||
|   Q_OBJECT | ||||
| public: | ||||
|   explicit TrayIcon(Manager &manager); | ||||
|   ~TrayIcon(); | ||||
| 
 | ||||
|   void updateSettings(const Settings &settings); | ||||
| 
 | ||||
|   void blockActions(bool block); | ||||
|   void setTaskActionsEnabled(bool isEnabled); | ||||
|   void setRepeatCaptureEnabled(bool isEnabled); | ||||
|   void setActiveTaskCount(int count); | ||||
|   void resetFatalError(); | ||||
| 
 | ||||
|   void showInformation(const QString &text); | ||||
|   void showError(const QString &text); | ||||
|   void showFatalError(const QString &text); | ||||
|   void showSuccess(); | ||||
| 
 | ||||
| private: | ||||
|   enum class Icon { Idle, Success, Busy, Error }; | ||||
|   enum Duration { Permanent, Temporal }; | ||||
|   void setIcon(TrayIcon::Icon icon, Duration duration); | ||||
|   void handleIconClick(QSystemTrayIcon::ActivationReason reason); | ||||
|   QMenu *contextMenu(); | ||||
|   void updateIcon(); | ||||
|   void updateActions(); | ||||
| 
 | ||||
|   Manager &manager_; | ||||
|   std::unique_ptr<QSystemTrayIcon> tray_; | ||||
| 
 | ||||
|   QAction *captureAction_{nullptr}; | ||||
|   QAction *repeatCaptureAction_{nullptr}; | ||||
|   QAction *showLastAction_{nullptr}; | ||||
|   QAction *clipboardAction_{nullptr}; | ||||
|   QAction *settingsAction_{nullptr}; | ||||
| 
 | ||||
|   std::unique_ptr<QTimer> iconUpdateTimer_; | ||||
|   int activeTaskCount_{0}; | ||||
|   bool isFatalError_{false}; | ||||
|   Icon permanentIcon_{Icon::Idle}; | ||||
| 
 | ||||
|   bool gotTask_{false}; | ||||
|   bool canRepeatCapture_{false}; | ||||
|   bool isActionsBlocked_{false}; | ||||
| }; | ||||
							
								
								
									
										264
									
								
								src/updater.cpp
									
									
									
									
									
								
							
							
						
						| @ -1,264 +0,0 @@ | ||||
| #include <QDir> | ||||
| #include <QFile> | ||||
| #include <QJsonDocument> | ||||
| #include <QJsonArray> | ||||
| #include <QNetworkRequest> | ||||
| #include <QNetworkReply> | ||||
| #include <QMessageBox> | ||||
| #include <QApplication> | ||||
| 
 | ||||
| #include "updater.h" | ||||
| #include "stassert.h" | ||||
| 
 | ||||
| namespace { | ||||
| #define FIELD(NAME) const QString _ ## NAME = #NAME | ||||
|   FIELD (Application); | ||||
| 
 | ||||
|   FIELD (name); | ||||
|   FIELD (version); | ||||
|   FIELD (compatibleVersion); | ||||
|   FIELD (built_in); | ||||
|   FIELD (versionString); | ||||
|   FIELD (permissions); | ||||
|   FIELD (url); | ||||
|   FIELD (path); | ||||
| #undef FIELD | ||||
| 
 | ||||
| #if defined(Q_OS_WIN) | ||||
|   const QString _platform = "_win"; | ||||
| #elif defined(Q_OS_LINUX) | ||||
|   const QString _platform = "_linux"; | ||||
| #endif | ||||
| 
 | ||||
|   QString versionField (const QJsonObject &component, const QString &field) { | ||||
|     return component.contains (field + _platform) | ||||
|            ? component[field + _platform].toVariant ().toString () | ||||
|            : component[field].toVariant ().toString (); | ||||
|   } | ||||
| 
 | ||||
|   QFileInfo fileDir (const QString &fileName) { | ||||
|     return QFileInfo (fileName).absolutePath (); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| Updater::Updater (QObject *parent) | ||||
|   : QObject (parent), | ||||
|   network_ (new QNetworkAccessManager (this)), | ||||
|   componentsUpdating_ (0) { | ||||
|   updatesFileName_ = QApplication::applicationDirPath () + QDir::separator () + "updates.json"; | ||||
|   backupSuffix_ = "_backup"; | ||||
|   connect (network_, SIGNAL (finished (QNetworkReply*)), | ||||
|            SLOT (replyFinished (QNetworkReply*))); | ||||
| 
 | ||||
|   getCurrentVersion (); | ||||
|   updateCurrentVersion (); | ||||
| } | ||||
| 
 | ||||
| QDateTime Updater::nextCheckTime (const QDateTime &lastCheckTime, int updateType) const { | ||||
|   QDateTime nextTime; | ||||
|   switch (updateType) { | ||||
|     case UpdateTypeDaily: | ||||
|       nextTime = lastCheckTime.addDays (1); | ||||
|       break; | ||||
|     case UpdateTypeWeekly: | ||||
|       nextTime =  lastCheckTime.addDays (7); | ||||
|       break; | ||||
|     case UpdateTypeMonthly: | ||||
|       nextTime =  lastCheckTime.addDays (30); | ||||
|       break; | ||||
|     case UpdateTypeNever: | ||||
|     default: | ||||
|       return QDateTime (); | ||||
|   } | ||||
|   if (nextTime < QDateTime::currentDateTime ()) { | ||||
|     return QDateTime::currentDateTime ().addSecs (5); | ||||
|   } | ||||
|   return nextTime; | ||||
| } | ||||
| 
 | ||||
| void Updater::getCurrentVersion () { | ||||
|   QFile f (":/version.json"); | ||||
|   if (f.open (QFile::ReadOnly)) { | ||||
|     currentVersion_ = QJsonDocument::fromJson (f.readAll ()).object (); | ||||
|     f.close (); | ||||
|   } | ||||
|   else { | ||||
|     emit error (tr ("Ошибка определения текущей версии. Обновление недоступно.")); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void Updater::updateCurrentVersion () { | ||||
|   QFile f (updatesFileName_); | ||||
|   if (!f.open (QFile::ReadOnly)) { | ||||
|     return; | ||||
|   } | ||||
|   QJsonObject updated = QJsonDocument::fromJson (f.readAll ()).object (); | ||||
|   f.close (); | ||||
|   for (const QString &component: updated.keys ()) { | ||||
|     QJsonObject current = currentVersion_[component].toObject (); | ||||
|     int updatedVersion = updated[component].toInt (); | ||||
|     if (current[_built_in].toBool () || current[_version].toInt () >= updatedVersion) { | ||||
|       continue; | ||||
|     } | ||||
|     current[_version] = updatedVersion; | ||||
|     currentVersion_[component] = current; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| QString Updater::currentAppVersion () const { | ||||
|   return currentVersion_[_Application].toObject ()[_versionString].toString (); | ||||
| } | ||||
| 
 | ||||
| void Updater::checkForUpdates () { | ||||
|   getAvailableVersion (); | ||||
| } | ||||
| 
 | ||||
| void Updater::getAvailableVersion () { | ||||
|   QNetworkRequest request (versionField (currentVersion_, _url)); | ||||
|   request.setAttribute (QNetworkRequest::User, _version); | ||||
|   network_->get (request); | ||||
| } | ||||
| 
 | ||||
| void Updater::replyFinished (QNetworkReply *reply) { | ||||
|   if (reply->error () != QNetworkReply::NoError) { | ||||
|     emit tr ("Ошибка загрузки информации для обновления."); | ||||
|     return; | ||||
|   } | ||||
|   QByteArray content = reply->readAll (); | ||||
|   QString component = reply->request ().attribute (QNetworkRequest::User).toString (); | ||||
|   if (component == _version) { | ||||
|     availableVersion_ = QJsonDocument::fromJson (content).object (); | ||||
|     parseAvailableVersion (); | ||||
|   } | ||||
|   else if (availableVersion_.contains (component) && !content.isEmpty ()) { | ||||
|     installComponent (component, content); | ||||
|   } | ||||
|   reply->deleteLater (); | ||||
| } | ||||
| 
 | ||||
| void Updater::parseAvailableVersion () { | ||||
|   QStringList inaccessible, incompatible; | ||||
|   QStringList updateList; | ||||
|   QDir currentDir; | ||||
|   for (const QString &component: availableVersion_.keys ()) { | ||||
|     QJsonObject available = availableVersion_[component].toObject (); | ||||
|     QJsonObject current = currentVersion_[component].toObject (); | ||||
|     QString path = versionField (available, _path); | ||||
|     if (path.isEmpty ()) { | ||||
|       continue; | ||||
|     } | ||||
| 
 | ||||
|     QFileInfo installDir = fileDir (path); | ||||
|     if (!installDir.exists ()) { | ||||
|       currentDir.mkpath (installDir.absoluteFilePath ()); | ||||
|     } | ||||
|     if (!installDir.isWritable ()) { // check dir because install = remove + make new
 | ||||
|       inaccessible << installDir.absoluteFilePath (); | ||||
|     } | ||||
|     if (current[_version].toInt () < versionField (available, _compatibleVersion).toInt ()) { | ||||
|       incompatible << component; | ||||
|     } | ||||
|     if (!QFile::exists (path) || current[_version].toInt () < available[_version].toInt ()) { | ||||
|       updateList << component; | ||||
|     } | ||||
|   } | ||||
|   if (updateList.isEmpty ()) { | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   QFileInfo updateFileDir = fileDir (updatesFileName_); | ||||
|   if (!updateFileDir.isWritable ()) { | ||||
|     inaccessible << updateFileDir.absoluteFilePath (); | ||||
|   } | ||||
|   inaccessible.removeDuplicates (); | ||||
| 
 | ||||
|   QString message = tr ("Доступно обновлений: %1.\n").arg (updateList.size ()); | ||||
|   QMessageBox::StandardButtons buttons = QMessageBox::Ok; | ||||
|   if (!incompatible.isEmpty ()) { | ||||
|     message += tr ("Несовместимых обновлений: %1.\nВыполните обновление вручную.") | ||||
|                .arg (incompatible.size ()); | ||||
|   } | ||||
|   else if (!inaccessible.isEmpty ()) { | ||||
|     message += tr ("Недоступных для записи директорий: %1.\n%2\nИзмените права доступа и " | ||||
|                    "повторите попытку или выполните обновление вручную.") | ||||
|                .arg (inaccessible.size ()).arg (inaccessible.join ("\n")); | ||||
|   } | ||||
|   else { | ||||
|     message += tr ("Обновить?"); | ||||
|     buttons = QMessageBox::Yes | QMessageBox::No; | ||||
|   } | ||||
|   int result = QMessageBox::question (NULL, tr ("Обновление"), message, buttons); | ||||
|   if (result == QMessageBox::Yes) { | ||||
|     componentsUpdating_ = updateList.size (); | ||||
|     for (const QString &component: updateList) { | ||||
|       getComponent (component); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void Updater::getComponent (const QString &component) { | ||||
|   QJsonObject available = availableVersion_[component].toObject (); | ||||
|   QString path = versionField (available, _path); | ||||
|   if (path.isEmpty ()) { | ||||
|     --componentsUpdating_; | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   QString url = versionField (available, _url); | ||||
|   if (url.isEmpty ()) { // just remove component
 | ||||
|     installComponent (component, QByteArray ()); | ||||
|   } | ||||
|   else { | ||||
|     QNetworkRequest request (url); | ||||
|     request.setAttribute (QNetworkRequest::User, component); | ||||
|     network_->get (request); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void Updater::installComponent (const QString &component, const QByteArray &newContent) { | ||||
|   --componentsUpdating_; | ||||
|   ST_ASSERT (availableVersion_.contains (component)); | ||||
|   QJsonObject available = availableVersion_[component].toObject (); | ||||
|   QString path = versionField (available, _path); | ||||
|   ST_ASSERT (!path.isEmpty ()); | ||||
| 
 | ||||
|   QString backup = path + backupSuffix_; | ||||
|   QFile::remove (backup); | ||||
|   QFile::rename (path, backup); | ||||
| 
 | ||||
|   if (!newContent.isEmpty ()) { | ||||
|     QFile f (path); | ||||
|     if (!f.open (QFile::WriteOnly)) { | ||||
|       emit error (tr ("Ошибка обновления файла (%1).").arg (path)); | ||||
|       return; | ||||
|     } | ||||
|     f.write (newContent); | ||||
|     f.close (); | ||||
|     bool ok; | ||||
|     QFileDevice::Permissions perm (available[_permissions].toString ().toUInt (&ok, 16)); | ||||
|     if (ok) { | ||||
|       f.setPermissions (perm); | ||||
|     } | ||||
|   } | ||||
|   updateVersionInfo (component, available[_version].toInt ()); | ||||
| 
 | ||||
|   if (componentsUpdating_ == 0) { | ||||
|     emit updated (); | ||||
|     QString message = tr ("Обновление завершено. Для активации некоторых компонентов " | ||||
|                           "может потребоваться перезапуск."); | ||||
|     QMessageBox::information (NULL, tr ("Обновление"), message, QMessageBox::Ok); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void Updater::updateVersionInfo (const QString &component, int version) { | ||||
|   QFile f (updatesFileName_); | ||||
|   if (!f.open (QFile::ReadWrite)) { | ||||
|     emit error (tr ("Ошибка обновления файла с текущей версией.")); | ||||
|     return; | ||||
|   } | ||||
|   QJsonObject updated = QJsonDocument::fromJson (f.readAll ()).object (); | ||||
|   updated[component] = version; | ||||
|   f.seek (0); | ||||
|   f.write (QJsonDocument (updated).toJson ()); | ||||
|   f.close (); | ||||
| } | ||||
| @ -1,65 +0,0 @@ | ||||
| #ifndef UPDATER_H | ||||
| #define UPDATER_H | ||||
| 
 | ||||
| #include <QObject> | ||||
| #include <QJsonObject> | ||||
| #include <QNetworkAccessManager> | ||||
| 
 | ||||
| /*!
 | ||||
|  * \brief The Updater class. | ||||
|  * | ||||
|  * Allows to download and copy files from remote source to local machine. | ||||
|  */ | ||||
| class Updater : public QObject { | ||||
|   Q_OBJECT | ||||
| 
 | ||||
|   public: | ||||
|     enum UpdateType { | ||||
|       UpdateTypeNever, UpdateTypeDaily, UpdateTypeWeekly, UpdateTypeMonthly | ||||
|     }; | ||||
| 
 | ||||
|     explicit Updater (QObject *parent = 0); | ||||
| 
 | ||||
|     QString currentAppVersion () const; | ||||
| 
 | ||||
|     //! Initiate updates check.
 | ||||
|     void checkForUpdates (); | ||||
| 
 | ||||
|     //! Get nearest update check time based on given settings.
 | ||||
|     QDateTime nextCheckTime (const QDateTime &lastCheckTime, int updateType) const; | ||||
| 
 | ||||
|   signals: | ||||
|     void error (const QString &message); | ||||
|     //! Emited after all components updated.
 | ||||
|     void updated (); | ||||
| 
 | ||||
|   private slots: | ||||
|     //! Handle remote downloads finish.
 | ||||
|     void replyFinished (QNetworkReply *reply); | ||||
| 
 | ||||
|   private: | ||||
|     //! Load current version info (built-in).
 | ||||
|     void getCurrentVersion (); | ||||
|     //! Update current version info with information about preformed updates.
 | ||||
|     void updateCurrentVersion (); | ||||
|     //! Load latest available version info from remote source.
 | ||||
|     void getAvailableVersion (); | ||||
|     //! Check is updates available, prompt user and start update.
 | ||||
|     void parseAvailableVersion (); | ||||
|     //! Start update of given component.
 | ||||
|     void getComponent (const QString &component); | ||||
|     //! Finalize update of given component with given new content.
 | ||||
|     void installComponent (const QString &component, const QByteArray &newContent); | ||||
|     //! Save information about component update on disk (for updateCurrentVersion()).
 | ||||
|     void updateVersionInfo (const QString &component, int version); | ||||
| 
 | ||||
|   private: | ||||
|     QNetworkAccessManager *network_; | ||||
|     QJsonObject availableVersion_; | ||||
|     QJsonObject currentVersion_; | ||||
|     QString updatesFileName_; | ||||
|     int componentsUpdating_; | ||||
|     QString backupSuffix_; | ||||
| }; | ||||
| 
 | ||||
| #endif // UPDATER_H
 | ||||
| @ -1,21 +0,0 @@ | ||||
| #include <QNetworkProxy> | ||||
| 
 | ||||
| #include "utils.h" | ||||
| 
 | ||||
| QString encode (const QString &source) { | ||||
|   if (source.isEmpty ()) { | ||||
|     return source; | ||||
|   } | ||||
|   char encKeys[] = {14, 26, 99, 43}; | ||||
|   std::string result = source.toStdString (); | ||||
|   for (int i = 0, end = result.size (); i < end; ++i) { | ||||
|     result [i] = result[i] ^ encKeys[ i % sizeof(encKeys)]; | ||||
|   } | ||||
|   return QString::fromUtf8 (result.data ()); | ||||
| } | ||||
| 
 | ||||
| QList<int> proxyTypeOrder () { | ||||
|   QList<int> proxyOrder; | ||||
|   proxyOrder << QNetworkProxy::NoProxy << QNetworkProxy::Socks5Proxy << QNetworkProxy::HttpProxy; | ||||
|   return proxyOrder; | ||||
| } | ||||
							
								
								
									
										10
									
								
								src/utils.h
									
									
									
									
									
								
							
							
						
						| @ -1,10 +0,0 @@ | ||||
| #ifndef UTILS_H | ||||
| #define UTILS_H | ||||
| 
 | ||||
| #include <QString> | ||||
| 
 | ||||
| QString encode (const QString &source); | ||||
| 
 | ||||
| QList<int> proxyTypeOrder (); | ||||
| 
 | ||||
| #endif // UTILS_H
 | ||||
| @ -1,156 +0,0 @@ | ||||
| #include <QWebView> | ||||
| #include <QWebFrame> | ||||
| #include <QSettings> | ||||
| #include <QNetworkReply> | ||||
| #include <QFile> | ||||
| 
 | ||||
| #include "webtranslator.h" | ||||
| #include "processingitem.h" | ||||
| #include "settings.h" | ||||
| #include "stassert.h" | ||||
| #include "webtranslatorproxy.h" | ||||
| #include "translatorhelper.h" | ||||
| 
 | ||||
| WebTranslator::WebTranslator () | ||||
|   : QObject (), | ||||
|   proxy_ (new WebTranslatorProxy (this)), view_ (new QWebView), | ||||
|   translatorHelper_ (new TranslatorHelper), isReady_ (true), | ||||
|   ignoreSslErrors_ (settings_values::ignoreSslErrors), | ||||
|   forceRotateTranslators_ (settings_values::forceRotateTranslators) { | ||||
| 
 | ||||
|   view_->settings ()->setAttribute (QWebSettings::AutoLoadImages, false); | ||||
|   view_->settings ()->setAttribute (QWebSettings::DeveloperExtrasEnabled, true); | ||||
|   view_->settings ()->setAttribute (QWebSettings::LocalStorageEnabled, true); | ||||
| 
 | ||||
|   connect (view_, SIGNAL (loadFinished (bool)), SLOT (loadFinished (bool))); | ||||
|   connect (view_->page ()->mainFrame (), SIGNAL (javaScriptWindowObjectCleared ()), | ||||
|            this, SLOT (addProxyToView ())); | ||||
|   connect (view_->page ()->networkAccessManager (), SIGNAL (finished (QNetworkReply*)), | ||||
|            this, SLOT (replyFinished (QNetworkReply*))); | ||||
|   connect (view_->page ()->networkAccessManager (), | ||||
|            SIGNAL (sslErrors (QNetworkReply*,QList<QSslError>)), | ||||
|            this, SLOT (handleSslErrors (QNetworkReply*,QList<QSslError>))); | ||||
| 
 | ||||
|   translationTimeout_.setSingleShot (true); | ||||
|   connect (&translationTimeout_, SIGNAL (timeout ()), SLOT (abortTranslation ())); | ||||
| 
 | ||||
|   connect (proxy_, SIGNAL (translated (QString)), SLOT (proxyTranslated (QString))); | ||||
| 
 | ||||
|   // Delay because it can emit signal that is not connected yet.
 | ||||
|   QTimer::singleShot (500, this, SLOT (applySettings ())); | ||||
| } | ||||
| 
 | ||||
| WebTranslator::~WebTranslator () { | ||||
|   delete translatorHelper_; | ||||
|   delete view_; | ||||
| } | ||||
| 
 | ||||
| void WebTranslator::addProxyToView () { | ||||
|   view_->page ()->mainFrame ()->addToJavaScriptWindowObject ("st_wtp", proxy_); | ||||
|   view_->page ()->mainFrame ()->evaluateJavaScript (translatorHelper_->currentScript ()); | ||||
| } | ||||
| 
 | ||||
| void WebTranslator::translate (ProcessingItem item) { | ||||
|   if (!item.isValid () || item.translateLanguage.isEmpty ()) { | ||||
|     emit translated (item); | ||||
|     return; | ||||
|   } | ||||
|   queue_.push_back (item); | ||||
|   translateQueued (); | ||||
| } | ||||
| 
 | ||||
| void WebTranslator::translateQueued () { | ||||
|   if (isReady_ && !queue_.isEmpty ()) { | ||||
|     translatorHelper_->newItem (forceRotateTranslators_); | ||||
|     proxy_->setItem (queue_.first ()); | ||||
|     if (!tryNextTranslator (true)) { | ||||
|       return; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void WebTranslator::proxyTranslated (const QString &text) { | ||||
|   if (!queue_.isEmpty () && queue_.first ().recognized == proxy_->sourceText ()) { | ||||
|     if (text.isEmpty () && tryNextTranslator ()) { | ||||
|       return; | ||||
|     } | ||||
|     ProcessingItem &item = queue_.first (); | ||||
|     item.translated = text; | ||||
|     emit translated (item); | ||||
|   } | ||||
|   finishTranslation (false); | ||||
| } | ||||
| 
 | ||||
| void WebTranslator::handleSslErrors (QNetworkReply *reply, const QList<QSslError> &) { | ||||
|   if (ignoreSslErrors_) { | ||||
|     reply->ignoreSslErrors (); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void WebTranslator::abortTranslation () { | ||||
|   if (!tryNextTranslator ()) { | ||||
|     emit error (tr ("Перевод отменен по таймауту.")); | ||||
|     finishTranslation (); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void WebTranslator::loadFinished (bool ok) { | ||||
|   if (!ok && !tryNextTranslator ()) { | ||||
|     QString url = view_->url ().toString (); | ||||
|     emit error (tr ("Ошибка загрузки страницы (%1) для перевода.").arg (url)); | ||||
|     finishTranslation (); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void WebTranslator::finishTranslation (bool markAsTranslated) { | ||||
|   translationTimeout_.stop (); | ||||
|   view_->stop (); | ||||
|   if (!queue_.isEmpty ()) { | ||||
|     if (markAsTranslated) { | ||||
|       emit translated (queue_.first ()); | ||||
|     } | ||||
|     queue_.pop_front (); | ||||
|   } | ||||
|   isReady_ = true; | ||||
|   translateQueued (); | ||||
| } | ||||
| 
 | ||||
| bool WebTranslator::tryNextTranslator (bool firstTime) { | ||||
|   QString script = firstTime ? translatorHelper_->currentScript () | ||||
|                    : translatorHelper_->nextScript (); | ||||
|   if (script.isEmpty ()) { | ||||
|     return false; | ||||
|   } | ||||
|   translationTimeout_.stop (); | ||||
|   view_->stop (); | ||||
|   addProxyToView (); | ||||
|   view_->page ()->mainFrame ()->evaluateJavaScript ("translate();"); | ||||
|   isReady_ = false; | ||||
|   translationTimeout_.start (); | ||||
|   return true; | ||||
| } | ||||
| 
 | ||||
| void WebTranslator::replyFinished (QNetworkReply *reply) { | ||||
|   emit proxy_->resourceLoaded (reply->url ().toString ()); | ||||
| } | ||||
| 
 | ||||
| void WebTranslator::applySettings () { | ||||
|   QSettings settings; | ||||
|   settings.beginGroup (settings_names::translationGroup); | ||||
| #define GET(NAME) settings.value (settings_names::NAME, settings_values::NAME) | ||||
|   translationTimeout_.setInterval (GET (translationTimeout).toInt () * 1000); | ||||
|   translatorHelper_->loadScripts (); | ||||
|   if (!translatorHelper_->gotScripts ()) { | ||||
|     emit error (tr ("Нет сценариев для перевода. Измените настройки.")); | ||||
|   } | ||||
|   bool debugMode = GET (translationDebugMode).toBool (); | ||||
|   setDebugMode (debugMode); | ||||
| 
 | ||||
|   ignoreSslErrors_ = GET (ignoreSslErrors).toBool (); | ||||
|   forceRotateTranslators_ = GET (forceRotateTranslators).toBool (); | ||||
| #undef GET | ||||
| } | ||||
| 
 | ||||
| void WebTranslator::setDebugMode (bool isOn) { | ||||
|   view_->setVisible (isOn); | ||||
| } | ||||
| @ -1,57 +0,0 @@ | ||||
| #ifndef WEBTRANSLATOR_H | ||||
| #define WEBTRANSLATOR_H | ||||
| 
 | ||||
| #include <QObject> | ||||
| #include <QMap> | ||||
| #include <QTimer> | ||||
| 
 | ||||
| #include "processingitem.h" | ||||
| 
 | ||||
| class QWebView; | ||||
| class QNetworkReply; | ||||
| class QSslError; | ||||
| 
 | ||||
| class WebTranslatorProxy; | ||||
| class TranslatorHelper; | ||||
| 
 | ||||
| class WebTranslator : public QObject { | ||||
|   Q_OBJECT | ||||
| 
 | ||||
|   public: | ||||
|     explicit WebTranslator (); | ||||
|     ~WebTranslator (); | ||||
| 
 | ||||
|   signals: | ||||
|     void translated (ProcessingItem item); | ||||
|     void error (QString text); | ||||
| 
 | ||||
|   public slots: | ||||
|     void translate (ProcessingItem item); | ||||
|     void applySettings (); | ||||
|     void setDebugMode (bool isOn); | ||||
| 
 | ||||
|   private slots: | ||||
|     void loadFinished (bool ok); | ||||
|     void replyFinished (QNetworkReply *reply); | ||||
|     void addProxyToView (); | ||||
|     void abortTranslation (); | ||||
|     void proxyTranslated (const QString &text); | ||||
|     void handleSslErrors (QNetworkReply *reply, const QList<QSslError> &errors); | ||||
| 
 | ||||
|   private: | ||||
|     void translateQueued (); | ||||
|     void finishTranslation (bool markAsTranslated = true); | ||||
|     bool tryNextTranslator (bool firstTime = false); | ||||
| 
 | ||||
|   private: | ||||
|     WebTranslatorProxy *proxy_; | ||||
|     QWebView *view_; | ||||
|     TranslatorHelper *translatorHelper_; | ||||
|     QVector<ProcessingItem> queue_; | ||||
|     bool isReady_; | ||||
|     bool ignoreSslErrors_; | ||||
|     bool forceRotateTranslators_; | ||||
|     QTimer translationTimeout_; | ||||
| }; | ||||
| 
 | ||||
| #endif // WEBTRANSLATOR_H
 | ||||
| @ -1,25 +0,0 @@ | ||||
| #include "webtranslatorproxy.h" | ||||
| #include "processingitem.h" | ||||
| 
 | ||||
| WebTranslatorProxy::WebTranslatorProxy (QObject *parent) | ||||
|   : QObject (parent) { | ||||
| } | ||||
| 
 | ||||
| void WebTranslatorProxy::setItem (const ProcessingItem &item) { | ||||
|   sourceText_ = item.recognized; | ||||
|   sourceLanguage_ = item.ocrLanguage; | ||||
|   resultLanguage_ = item.translateLanguage; | ||||
| } | ||||
| 
 | ||||
| const QString &WebTranslatorProxy::sourceText () const { | ||||
|   return sourceText_; | ||||
| } | ||||
| 
 | ||||
| const QString &WebTranslatorProxy::sourceLanguage () const { | ||||
|   return sourceLanguage_; | ||||
| } | ||||
| 
 | ||||
| const QString &WebTranslatorProxy::resultLanguage () const { | ||||
|   return resultLanguage_; | ||||
| } | ||||
| 
 | ||||
| @ -1,37 +0,0 @@ | ||||
| #ifndef WEBTRANSLATORPROXY_H | ||||
| #define WEBTRANSLATORPROXY_H | ||||
| 
 | ||||
| #include <QObject> | ||||
| 
 | ||||
| class ProcessingItem; | ||||
| 
 | ||||
| /*!
 | ||||
|  * \brief Proxy class between WebTranslator and QWebView. | ||||
|  */ | ||||
| class WebTranslatorProxy : public QObject { | ||||
|   Q_OBJECT | ||||
|   Q_PROPERTY (QString sourceText READ sourceText) | ||||
|   Q_PROPERTY (QString sourceLanguage READ sourceLanguage) | ||||
|   Q_PROPERTY (QString resultLanguage READ resultLanguage) | ||||
| 
 | ||||
|   public: | ||||
|     explicit WebTranslatorProxy (QObject *parent = 0); | ||||
| 
 | ||||
|     void setItem (const ProcessingItem &item); | ||||
| 
 | ||||
|     const QString &sourceText () const; | ||||
|     const QString &sourceLanguage () const; | ||||
|     const QString &resultLanguage () const; | ||||
| 
 | ||||
|   signals: | ||||
|     void translated (const QString &text); | ||||
| 
 | ||||
|     void resourceLoaded (const QString &url); | ||||
| 
 | ||||
|   private: | ||||
|     QString sourceText_; | ||||
|     QString sourceLanguage_; | ||||
|     QString resultLanguage_; | ||||
| }; | ||||
| 
 | ||||
| #endif // WEBTRANSLATORPROXY_H
 | ||||
| @ -1,50 +1,41 @@ | ||||
| var isPageLoaded = false; | ||||
| var isTranslationFinished = false; // async translation request
 | ||||
| var isScheduled = false; | ||||
| var lastText = ''; | ||||
| var active = window.location.href !== "about:blank"; | ||||
| 
 | ||||
| function checkFinished () { | ||||
|     if (!isPageLoaded || !isTranslationFinished || isScheduled) return; | ||||
|     isScheduled = true; | ||||
|     setTimeout(function () { | ||||
|         var spans = [].slice.call (document.querySelectorAll ( | ||||
|                                        'span.translation > span, #result_box > span')); | ||||
|         var text = spans.reduce (function (res, i) { | ||||
|             return res + ' ' + i.innerText; | ||||
|         }, ''); | ||||
|         console.log (text); | ||||
|         st_wtp.translated (text); | ||||
|         isTranslationFinished = isScheduled = false; | ||||
|     }, 2000); // wait for gui fill
 | ||||
|     if (!active) return; | ||||
| 
 | ||||
|     let spans = [].slice.call (document.querySelectorAll ('span.translation > span, #result_box > span')); | ||||
|     let text = spans.reduce (function (res, i) { | ||||
|         return res + ' ' + i.innerText; | ||||
|     }, ''); | ||||
| 
 | ||||
|     if (text === lastText || text === '') | ||||
|         return; | ||||
| 
 | ||||
|     console.log ('translated text', text, 'old', lastText, 'size', text.length, lastText.length); | ||||
|     lastText = text; | ||||
|     active = false; | ||||
|     proxy.setTranslated (text); | ||||
| } | ||||
| function onResourceLoad (url) { | ||||
|     if (url.indexOf ('/translate_a/single') > -1) { | ||||
|         isTranslationFinished = true; | ||||
|         if (isPageLoaded) { | ||||
|             checkFinished (); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| st_wtp.resourceLoaded.connect (onResourceLoad); | ||||
| function onPageLoad () { | ||||
|     if (window.location.href.indexOf('about:blank') === 0) { | ||||
|         translate (); | ||||
| 
 | ||||
| function translate (text, from, to){ | ||||
|     console.log('start translate', text, from, to) | ||||
|     active = true; | ||||
| 
 | ||||
|     if (window.location.href.indexOf('//translate.google') !== -1 | ||||
|             && window.location.href.indexOf('&tl='+to+'&') !== -1) { | ||||
|         document.querySelector('textarea#source').value=text; | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     isPageLoaded = true; | ||||
|     if (isTranslationFinished) { | ||||
|         checkFinished (); | ||||
|     } | ||||
| } | ||||
| window.onload = onPageLoad(); | ||||
| 
 | ||||
| function translate (){ | ||||
|     if (window.location.href.indexOf('https://translate.google') === 0) { | ||||
|         window.location = 'about:blank'; | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     var url = 'https://translate.google.com/#auto/' + | ||||
|             st_wtp.resultLanguage + '/' + st_wtp.sourceText; | ||||
|     //    var url = 'https://translate.google.com/#auto/' + to + '/' + text;
 | ||||
|     let url = 'https://translate.google.com/#view=home&op=translate&sl=auto&tl=' + to + '&text=' + text; | ||||
|     console.log("setting url", url); | ||||
|     window.location = encodeURI (url); | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| function init() { | ||||
|     proxy.translate.connect (translate); | ||||
|     setInterval(checkFinished, 300); | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -1,36 +1,42 @@ | ||||
| var isPageLoaded = false; | ||||
| var isTranslationFinished = true; // async translation request
 | ||||
| var isScheduled = false; | ||||
| var lastText = ''; | ||||
| var active = window.location.href !== "about:blank"; | ||||
| 
 | ||||
| function checkFinished () { | ||||
|     if (!isPageLoaded || !isTranslationFinished || isScheduled) return; | ||||
|     isScheduled = true; | ||||
|     setTimeout(function () { | ||||
|         var spans = [].slice.call (document.querySelectorAll ('span.translation-chunk')); | ||||
|         var text = spans.reduce (function (res, i) { | ||||
|             return res + i.innerText + ' '; | ||||
|         }, ''); | ||||
|         console.log (text); | ||||
|         st_wtp.translated (text); | ||||
|         isTranslationFinished = isScheduled = false; | ||||
|     }, 2000); // wait for gui fill
 | ||||
| } | ||||
| function onResourceLoad (url) { | ||||
|     if (url.indexOf ('/tr.json/translate?') > -1) { | ||||
|         isTranslationFinished = true; | ||||
|         checkFinished (); | ||||
|     } | ||||
| } | ||||
| st_wtp.resourceLoaded.connect (onResourceLoad); | ||||
| function onPageLoad () { | ||||
|     isPageLoaded = true; | ||||
|     checkFinished (); | ||||
| } | ||||
| window.onload = onPageLoad(); | ||||
|     if (!active) return; | ||||
| 
 | ||||
| function translate (){ | ||||
|     var url = 'https://translate.yandex.ru/?lang=' + st_wtp.sourceLanguage + '-' + | ||||
|             st_wtp.resultLanguage + '&text=' + st_wtp.sourceText ; | ||||
|     url = url.replace(new RegExp(' ','g') , '%20') | ||||
|     window.location = (url); | ||||
|     var spans = [].slice.call (document.querySelectorAll ('span.translation-chunk')); | ||||
|     let text = spans.reduce (function (res, i) { | ||||
|         return res + ' ' + i.innerText; | ||||
|     }, ''); | ||||
| 
 | ||||
|     if (text === lastText || text === '') | ||||
|         return; | ||||
| 
 | ||||
|     console.log ('translated text', text, 'old', lastText, 'size', text.length, lastText.length); | ||||
|     lastText = text; | ||||
|     active = false; | ||||
|     proxy.setTranslated (text); | ||||
| } | ||||
| 
 | ||||
| function translate (text, from, to){ | ||||
|     console.log('start translate', text, from, to) | ||||
|     active = true; | ||||
| 
 | ||||
|     var langs = 'lang=' + from + '-' + to; | ||||
|     if (window.location.href.indexOf('//translate.yandex') !== -1 | ||||
|             && window.location.href.indexOf(langs) !== -1) { | ||||
|         document.querySelector('textarea#textarea').value=text | ||||
|         document.querySelector('div#textbox').dispatchEvent( | ||||
|                     new Event("input", {bubbles: true, cancelable: true})); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     var url = 'https://translate.yandex.ru/?' + langs + '&text=' + text; | ||||
|     url = url.replace(new RegExp(' ','g') , '%20') | ||||
|     window.location = url; | ||||
| } | ||||
| 
 | ||||
| function init() { | ||||
|     proxy.translate.connect (translate); | ||||
|     setInterval(checkFinished, 300); | ||||
| } | ||||
|  | ||||
 Gres
						Gres