Compare commits

...

8 commits

Author SHA1 Message Date
xiaoyifang 2fc85c98ff
Merge 15b918eb6a into 453948155a 2024-11-12 19:00:48 -05:00
shenleban tongying 453948155a
clean: simplify macOS hotkey mapping code
Some checks are pending
SonarCloud / Build and analyze (push) Waiting to run
2024-11-12 12:33:28 +00:00
shenleban tongying 8fc71c9586
dev: .mm / obj-c files -> clang-format and simplify includes
Some checks are pending
SonarCloud / Build and analyze (push) Waiting to run
2024-11-12 10:19:59 +00:00
shenleban tongying 8fd5b37335 clean: remove unused DEF_EX
Some checks are pending
SonarCloud / Build and analyze (push) Waiting to run
2024-11-12 01:29:12 -05:00
shenleban tongying 4758f9e972 refactor: change DEF_EX macro's underlying implementation to template 2024-11-12 00:30:02 -05:00
autofix-ci[bot] 15b918eb6a
[autofix.ci] apply automated fixes 2024-11-08 01:47:29 +00:00
xiaoyifang 27cbb7351b opt: add option about 2024-11-06 13:35:22 +08:00
xiaoyifang c787a08d2f opt: add option about 2024-11-06 12:07:23 +08:00
15 changed files with 581 additions and 568 deletions

View file

@ -142,4 +142,7 @@ StatementMacros:
- QT_REQUIRE_VERSION - QT_REQUIRE_VERSION
UseCRLF: false UseCRLF: false
UseTab: Never UseTab: Never
---
Language: ObjC
BasedOnStyle: WebKit
... ...

View file

@ -17,7 +17,6 @@ Checks: >
portability-*, portability-*,
readability-*, readability-*,
-bugprone-easily-swappable-parameters, -bugprone-easily-swappable-parameters,
-bugprone-reserved-identifier,
-cppcoreguidelines-owning-memory, -cppcoreguidelines-owning-memory,
-cppcoreguidelines-prefer-member-initializer, -cppcoreguidelines-prefer-member-initializer,
-cppcoreguidelines-pro-bounds-array-to-pointer-decay, -cppcoreguidelines-pro-bounds-array-to-pointer-decay,
@ -44,5 +43,11 @@ CheckOptions:
value: 1 value: 1
- key: cppcoreguidelines-explicit-virtual-functions.IgnoreDestructors - key: cppcoreguidelines-explicit-virtual-functions.IgnoreDestructors
value: 1 value: 1
- key: modernize-avoid-c-arrays.AllowStringArrays
value: 1
- key: cppcoreguidelines-avoid-c-arrays.AllowStringArrays
value: 1
- key: hicpp-avoid-c-arrays.AllowStringArrays
value: 1
... ...

View file

@ -4,22 +4,17 @@
#pragma once #pragma once
#include <string> #include <string>
#include <fmt/format.h>
// clang-format off
/// A way to declare an exception class fast /// A way to declare an exception class fast
/// Do like this: /// Do like this:
/// DEF_EX( exErrorInFoo, "An error in foo encountered", std::exception ) /// DEF_EX( exErrorInFoo, "An error in foo encountered", std::exception )
/// DEF_EX( exFooNotFound, "Foo was not found", exErrorInFoo ) /// DEF_EX( exFooNotFound, "Foo was not found", exErrorInFoo )
#define DEF_EX( exName, exDescription, exParent ) \
#define DEF_EX( exName, exDescription, exParent ) \ constexpr static char ExStr_## exName[] = exDescription; \
class exName: public exParent \ using exName = defineEx< exParent, ExStr_## exName >;
{ \
public: \
virtual const char * what() const noexcept \
{ \
return ( exDescription ); \
} \
virtual ~exName() noexcept {} \
};
/// Same as DEF_EX, but takes a runtime string argument, which gets concatenated /// Same as DEF_EX, but takes a runtime string argument, which gets concatenated
/// with the description. /// with the description.
@ -29,20 +24,36 @@
/// throw exCantOpen( "example.txt" ); /// throw exCantOpen( "example.txt" );
/// ///
/// what() would return "can't open file example.txt" /// what() would return "can't open file example.txt"
///
#define DEF_EX_STR( exName, exDescription, exParent ) \
constexpr static char ExStr_## exName[] = exDescription; \
using exName = defineExStr< exParent, ExStr_## exName >;
#define DEF_EX_STR( exName, exDescription, exParent ) \ // clang-format on
class exName: public exParent \
{ \ template< typename ParentEx, const char * description >
std::string value; \ class defineEx: public ParentEx
\ {
public: \ public:
explicit exName( std::string const & value_ ): \ virtual const char * what() const noexcept
value( std::string( exDescription ) + " " + value_ ) \ {
{ \ return description;
} \ }
virtual const char * what() const noexcept \ };
{ \
return value.c_str(); \ template< typename ParentEx, const char * description >
} \ class defineExStr: public ParentEx
virtual ~exName() noexcept {} \ {
}; public:
explicit defineExStr( std::string const & message_ ):
message( fmt::format( "{} {}", description, message_ ) )
{
}
virtual const char * what() const noexcept
{
return message.c_str();
}
private:
std::string message;
};

View file

@ -201,6 +201,7 @@ Preferences::Preferences():
doubleClickTranslates( true ), doubleClickTranslates( true ),
selectWordBySingleClick( false ), selectWordBySingleClick( false ),
autoScrollToTargetArticle( true ), autoScrollToTargetArticle( true ),
targetArticleAtFirst( false ),
escKeyHidesMainWindow( false ), escKeyHidesMainWindow( false ),
alwaysOnTop( false ), alwaysOnTop( false ),
searchInDock( false ), searchInDock( false ),
@ -929,6 +930,11 @@ Class load()
( preferences.namedItem( "autoScrollToTargetArticle" ).toElement().text() == "1" ); ( preferences.namedItem( "autoScrollToTargetArticle" ).toElement().text() == "1" );
} }
if ( !preferences.namedItem( "targetArticleAtFirst" ).isNull() ) {
c.preferences.targetArticleAtFirst =
( preferences.namedItem( "targetArticleAtFirst" ).toElement().text() == "1" );
}
if ( !preferences.namedItem( "escKeyHidesMainWindow" ).isNull() ) { if ( !preferences.namedItem( "escKeyHidesMainWindow" ).isNull() ) {
c.preferences.escKeyHidesMainWindow = c.preferences.escKeyHidesMainWindow =
( preferences.namedItem( "escKeyHidesMainWindow" ).toElement().text() == "1" ); ( preferences.namedItem( "escKeyHidesMainWindow" ).toElement().text() == "1" );
@ -1866,6 +1872,10 @@ void save( Class const & c )
opt.appendChild( dd.createTextNode( c.preferences.autoScrollToTargetArticle ? "1" : "0" ) ); opt.appendChild( dd.createTextNode( c.preferences.autoScrollToTargetArticle ? "1" : "0" ) );
preferences.appendChild( opt ); preferences.appendChild( opt );
opt = dd.createElement( "targetArticleAtFirst" );
opt.appendChild( dd.createTextNode( c.preferences.targetArticleAtFirst ? "1" : "0" ) );
preferences.appendChild( opt );
opt = dd.createElement( "escKeyHidesMainWindow" ); opt = dd.createElement( "escKeyHidesMainWindow" );
opt.appendChild( dd.createTextNode( c.preferences.escKeyHidesMainWindow ? "1" : "0" ) ); opt.appendChild( dd.createTextNode( c.preferences.escKeyHidesMainWindow ? "1" : "0" ) );
preferences.appendChild( opt ); preferences.appendChild( opt );

View file

@ -356,6 +356,7 @@ struct Preferences
bool doubleClickTranslates; bool doubleClickTranslates;
bool selectWordBySingleClick; bool selectWordBySingleClick;
bool autoScrollToTargetArticle; bool autoScrollToTargetArticle;
bool targetArticleAtFirst;
bool escKeyHidesMainWindow; bool escKeyHidesMainWindow;
bool alwaysOnTop; bool alwaysOnTop;

View file

@ -46,10 +46,6 @@ using BtreeIndexing::IndexInfo;
namespace { namespace {
DEF_EX_STR( exNotAardFile, "Not an AARD file", Dictionary::Ex )
DEF_EX_STR( exWordIsTooLarge, "Enountered a word that is too large:", Dictionary::Ex )
DEF_EX_STR( exSuddenEndOfFile, "Sudden end of file", Dictionary::Ex )
#pragma pack( push, 1 ) #pragma pack( push, 1 )
/// AAR file header /// AAR file header

View file

@ -169,10 +169,6 @@ void addEntryToIndex( string & word,
indexedWords.addWord( Utf8::decode( word ), articleOffset ); indexedWords.addWord( Utf8::decode( word ), articleOffset );
} }
DEF_EX( exFailedToDecompressArticle, "Failed to decompress article's body", Dictionary::Ex )
DEF_EX( exChunkIndexOutOfRange, "Chunk index is out of range", Dictionary::Ex )
class BglDictionary: public BtreeIndexing::BtreeDictionary class BglDictionary: public BtreeIndexing::BtreeDictionary
{ {
QMutex idxMutex; QMutex idxMutex;

View file

@ -301,7 +301,6 @@ namespace {
////////////////// GLS Dictionary ////////////////// GLS Dictionary
using Dictionary::exCantReadFile; using Dictionary::exCantReadFile;
DEF_EX( exUserAbort, "User abort", Dictionary::Ex )
DEF_EX_STR( exDictzipError, "DICTZIP error", Dictionary::Ex ) DEF_EX_STR( exDictzipError, "DICTZIP error", Dictionary::Ex )
enum { enum {

View file

@ -64,14 +64,11 @@ DEF_EX( exNotAnIfoFile, "Not an .ifo file", Dictionary::Ex )
DEF_EX_STR( exBadFieldInIfo, "Bad field in .ifo file encountered:", Dictionary::Ex ) DEF_EX_STR( exBadFieldInIfo, "Bad field in .ifo file encountered:", Dictionary::Ex )
DEF_EX_STR( exNoIdxFile, "No corresponding .idx file was found for", Dictionary::Ex ) DEF_EX_STR( exNoIdxFile, "No corresponding .idx file was found for", Dictionary::Ex )
DEF_EX_STR( exNoDictFile, "No corresponding .dict file was found for", Dictionary::Ex ) DEF_EX_STR( exNoDictFile, "No corresponding .dict file was found for", Dictionary::Ex )
DEF_EX_STR( exNoSynFile, "No corresponding .syn file was found for", Dictionary::Ex )
DEF_EX( ex64BitsNotSupported, "64-bit indices are not presently supported, sorry", Dictionary::Ex ) DEF_EX( ex64BitsNotSupported, "64-bit indices are not presently supported, sorry", Dictionary::Ex )
DEF_EX( exDicttypeNotSupported, "Dictionaries with dicttypes are not supported, sorry", Dictionary::Ex ) DEF_EX( exDicttypeNotSupported, "Dictionaries with dicttypes are not supported, sorry", Dictionary::Ex )
using Dictionary::exCantReadFile; using Dictionary::exCantReadFile;
DEF_EX_STR( exWordIsTooLarge, "Enountered a word that is too large:", Dictionary::Ex )
DEF_EX_STR( exSuddenEndOfFile, "Sudden end of file", Dictionary::Ex )
DEF_EX_STR( exDictzipError, "DICTZIP error", Dictionary::Ex ) DEF_EX_STR( exDictzipError, "DICTZIP error", Dictionary::Ex )
DEF_EX_STR( exIncorrectOffset, "Incorrect offset encountered in file", Dictionary::Ex ) DEF_EX_STR( exIncorrectOffset, "Incorrect offset encountered in file", Dictionary::Ex )

View file

@ -81,7 +81,6 @@ namespace {
using Dictionary::exCantReadFile; using Dictionary::exCantReadFile;
DEF_EX_STR( exNotXdxfFile, "The file is not an XDXF file:", Dictionary::Ex ) DEF_EX_STR( exNotXdxfFile, "The file is not an XDXF file:", Dictionary::Ex )
DEF_EX( exCorruptedIndex, "The index file is corrupted", Dictionary::Ex )
DEF_EX_STR( exDictzipError, "DICTZIP error", Dictionary::Ex ) DEF_EX_STR( exDictzipError, "DICTZIP error", Dictionary::Ex )
enum { enum {

View file

@ -30,8 +30,7 @@
#endif #endif
#ifdef Q_OS_MAC #ifdef Q_OS_MAC
#define __SECURITYHI__ #import <Carbon/Carbon.h>
#include <Carbon/Carbon.h>
#endif #endif
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////

View file

@ -2,11 +2,13 @@
* Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */ * Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */
#include "hotkeywrapper.hh" #include "hotkeywrapper.hh"
#include <QTimer>
#include <QMessageBox> #include <QMessageBox>
#include <QPushButton>
#include <QObject> #include <QObject>
#include <QPushButton>
#include <QTimer>
#include <memory> #include <memory>
#include <vector>
#import <Appkit/Appkit.h> #import <Appkit/Appkit.h>
@ -23,120 +25,107 @@
/// https://github.com/sindresorhus/KeyboardShortcuts/blob/9369a045a72a5296150879781321aecd228171db/readme.md?plain=1#L207 /// https://github.com/sindresorhus/KeyboardShortcuts/blob/9369a045a72a5296150879781321aecd228171db/readme.md?plain=1#L207
/// ///
namespace MacKeyMapping namespace MacKeyMapping {
{
// Convert Qt key codes to Mac OS X native codes // Convert Qt key codes to Mac OS X native codes
struct ReverseMapEntry struct ReverseMapEntry {
{ UniChar character;
UniChar character; UInt16 keyCode;
UInt16 keyCode;
}; };
static struct ReverseMapEntry * mapping; static std::vector<ReverseMapEntry> mapping;
static int mapEntries = 0;
/// References:
/// * https://github.com/libsdl-org/SDL/blob/fc12cc6dfd859a4e01376162a58f12208e539ac6/src/video/cocoa/SDL_cocoakeyboard.m#L345
/// * https://github.com/qt/qtbase/blob/922369844fcb75386237bca3eef59edd5093f58d/src/gui/platform/darwin/qapplekeymapper.mm#L449
///
/// Known possible flaws 1) UCKeyTranslate doesn't handle modifiers at all 2) Handling keyboard switching
void createMapping() void createMapping()
{ {
if( mapping == NULL ) if (mapping.empty()) {
{ mapping.reserve(128);
TISInputSourceRef inputSourceRef = TISCopyInputSourceForLanguage( CFSTR( "en" ) );
if ( !inputSourceRef ) {
inputSourceRef = TISCopyCurrentKeyboardInputSource();
}
if ( !inputSourceRef ) {
return;
}
CFDataRef dataRef = ( CFDataRef )TISGetInputSourceProperty( inputSourceRef, TISInputSourceRef inputSourceRef = TISCopyCurrentKeyboardLayoutInputSource();
kTISPropertyUnicodeKeyLayoutData ); if (!inputSourceRef) {
// this method returns null under macos Japanese input method(and also Chinese), which causes cmd+C+C not to be registered as a hotkey return;
if( !dataRef )
{
// solve the null value under Japanese keyboard
inputSourceRef = TISCopyCurrentKeyboardLayoutInputSource();
dataRef = static_cast<CFDataRef>((TISGetInputSourceProperty(inputSourceRef, kTISPropertyUnicodeKeyLayoutData)));
if (!dataRef) {
return;
} }
}
const UCKeyboardLayout * keyboardLayoutPtr = ( const UCKeyboardLayout * )CFDataGetBytePtr( dataRef ); CFDataRef uchrDataRef = (CFDataRef)TISGetInputSourceProperty(inputSourceRef, kTISPropertyUnicodeKeyLayoutData);
if( !keyboardLayoutPtr ) {
return;
}
mapping = ( struct ReverseMapEntry * )calloc( 128 , sizeof(struct ReverseMapEntry) ); const UCKeyboardLayout* UCKeyboardLayoutPtr;
if( !mapping ) {
return;
}
mapEntries = 0; if (uchrDataRef) {
UCKeyboardLayoutPtr = (const UCKeyboardLayout*)CFDataGetBytePtr(uchrDataRef);
for( int i = 0; i < 128; i++ )
{
UInt32 theDeadKeyState = 0;
UniCharCount theLength = 0;
if( UCKeyTranslate( keyboardLayoutPtr, i, kUCKeyActionDisplay, 0, LMGetKbdType(),
kUCKeyTranslateNoDeadKeysBit, &theDeadKeyState, 1, &theLength,
&mapping[ mapEntries ].character ) == noErr && theLength > 0 )
{
if( isprint( mapping[ mapEntries ].character ) )
{
mapping[ mapEntries++ ].keyCode = i;
} }
}
if (!UCKeyboardLayoutPtr) {
return;
}
for (UInt16 i = 0; i < 128; i++) {
UInt32 theDeadKeyState = 0;
UniCharCount theLength = 0;
UniChar temp_char_buf;
if (UCKeyTranslate(UCKeyboardLayoutPtr, i, kUCKeyActionDown, 0, LMGetKbdType(),
kUCKeyTranslateNoDeadKeysBit, &theDeadKeyState, 1, &theLength,
&temp_char_buf)
== noErr
&& theLength > 0) {
if (isprint(temp_char_buf)) {
mapping.emplace_back(ReverseMapEntry { temp_char_buf, i });
}
}
}
mapping.shrink_to_fit();
} }
}
} }
quint32 qtKeyToNativeKey( quint32 key ) quint32 qtKeyToNativeKey(UniChar key)
{ {
createMapping(); createMapping();
if( mapping == NULL ) { if (mapping.empty()) {
return 0;
}
for (auto& m : mapping) {
if (m.character == key) {
return m.keyCode;
}
}
return 0; return 0;
} }
for( int i = 0; i < mapEntries; i++ )
{
if( mapping[ i ].character == key ) {
return mapping[ i ].keyCode;
}
}
return 0;
}
} // namespace MacKeyMapping } // namespace MacKeyMapping
static pascal OSStatus hotKeyHandler( EventHandlerCallRef /* nextHandler */, EventRef theEvent, void * userData ) static pascal OSStatus hotKeyHandler(EventHandlerCallRef /* nextHandler */, EventRef theEvent, void* userData)
{ {
EventHotKeyID hkID; EventHotKeyID hkID;
GetEventParameter( theEvent, kEventParamDirectObject, typeEventHotKeyID, NULL, sizeof(EventHotKeyID), NULL, &hkID ); GetEventParameter(theEvent, kEventParamDirectObject, typeEventHotKeyID, NULL, sizeof(EventHotKeyID), NULL, &hkID);
static_cast< HotkeyWrapper * >( userData )->activated( hkID.id ); static_cast<HotkeyWrapper*>(userData)->activated(hkID.id);
return noErr; return noErr;
} }
HotkeyWrapper::HotkeyWrapper( QObject *parent ) HotkeyWrapper::HotkeyWrapper(QObject* parent)
{ {
(void) parent; (void)parent;
hotKeyFunction = NewEventHandlerUPP( hotKeyHandler ); hotKeyFunction = NewEventHandlerUPP(hotKeyHandler);
EventTypeSpec type; EventTypeSpec type;
type.eventClass = kEventClassKeyboard; type.eventClass = kEventClassKeyboard;
type.eventKind = kEventHotKeyPressed; type.eventKind = kEventHotKeyPressed;
InstallApplicationEventHandler( hotKeyFunction, 1, &type, this, &handlerRef ); InstallApplicationEventHandler(hotKeyFunction, 1, &type, this, &handlerRef);
keyC = nativeKey( 'c' ); keyC = nativeKey('c');
} }
HotkeyWrapper::~HotkeyWrapper() HotkeyWrapper::~HotkeyWrapper()
{ {
unregister(); unregister();
RemoveEventHandler( handlerRef ); RemoveEventHandler(handlerRef);
} }
void HotkeyWrapper::waitKey2() void HotkeyWrapper::waitKey2()
{ {
state2 = false; state2 = false;
} }
void checkAndRequestAccessibilityPermission() void checkAndRequestAccessibilityPermission()
{ {
@ -159,196 +148,220 @@ void checkAndRequestAccessibilityPermission()
} }
} }
void HotkeyWrapper::activated( int hkId ) void HotkeyWrapper::activated(int hkId)
{ {
if ( state2 ) if (state2) { // wait for 2nd key
{ // wait for 2nd key
waitKey2(); // Cancel the 2nd-key wait stage waitKey2(); // Cancel the 2nd-key wait stage
if ( hkId == state2waiter.id + 1 || if (hkId == state2waiter.id + 1 || (hkId == state2waiter.id && state2waiter.key == state2waiter.key2)) {
( hkId == state2waiter.id && state2waiter.key == state2waiter.key2 ) ) emit hotkeyActivated(state2waiter.handle);
{ return;
emit hotkeyActivated( state2waiter.handle ); }
return;
} }
}
for ( int i = 0; i < hotkeys.count(); i++ ) for (int i = 0; i < hotkeys.count(); i++) {
{ HotkeyStruct& hs = hotkeys[i];
HotkeyStruct &hs = hotkeys[ i ]; if (hkId == hs.id) {
if( hkId == hs.id ) if (hs.key == keyC && hs.modifier == cmdKey) {
{ checkAndRequestAccessibilityPermission();
if( hs.key == keyC && hs.modifier == cmdKey )
{
checkAndRequestAccessibilityPermission();
// If that was a copy-to-clipboard shortcut, re-emit it back so it could
// reach its original destination so it could be acted upon.
UnregisterEventHotKey( hs.hkRef );
sendCmdC(); // If that was a copy-to-clipboard shortcut, re-emit it back so it could
// reach its original destination so it could be acted upon.
UnregisterEventHotKey(hs.hkRef);
EventHotKeyID hotKeyID; sendCmdC();
hotKeyID.signature = 'GDHK';
hotKeyID.id = hs.id;
RegisterEventHotKey( hs.key, hs.modifier, hotKeyID, GetApplicationEventTarget(), 0, &hs.hkRef ); EventHotKeyID hotKeyID;
} hotKeyID.signature = 'GDHK';
hotKeyID.id = hs.id;
if ( hs.key2 == 0 ) { RegisterEventHotKey(hs.key, hs.modifier, hotKeyID, GetApplicationEventTarget(), 0, &hs.hkRef);
emit hotkeyActivated( hs.handle ); }
return;
}
state2 = true; if (hs.key2 == 0) {
state2waiter = hs; emit hotkeyActivated(hs.handle);
QTimer::singleShot( 500, this, SLOT( waitKey2() ) ); return;
return; }
state2 = true;
state2waiter = hs;
QTimer::singleShot(500, this, SLOT(waitKey2()));
return;
}
} }
}
state2 = false; state2 = false;
return; return;
} }
void HotkeyWrapper::unregister() void HotkeyWrapper::unregister()
{ {
for ( int i = 0; i < hotkeys.count(); i++ ) for (int i = 0; i < hotkeys.count(); i++) {
{ HotkeyStruct const& hk = hotkeys.at(i);
HotkeyStruct const & hk = hotkeys.at( i );
UnregisterEventHotKey( hk.hkRef ); UnregisterEventHotKey(hk.hkRef);
if ( hk.key2 && hk.key2 != hk.key ) { if (hk.key2 && hk.key2 != hk.key) {
UnregisterEventHotKey( hk.hkRef2 ); UnregisterEventHotKey(hk.hkRef2);
} }
} }
(static_cast< QHotkeyApplication * >( qApp ))->unregisterWrapper( this ); (static_cast<QHotkeyApplication*>(qApp))->unregisterWrapper(this);
} }
bool HotkeyWrapper::setGlobalKey( QKeySequence const & seq, int handle ) bool HotkeyWrapper::setGlobalKey(QKeySequence const& seq, int handle)
{ {
Config::HotKey hk(seq); Config::HotKey hk(seq);
return setGlobalKey(hk.key1,hk.key2,hk.modifiers,handle); return setGlobalKey(hk.key1, hk.key2, hk.modifiers, handle);
} }
bool HotkeyWrapper::setGlobalKey( int key, int key2, Qt::KeyboardModifiers modifier, int handle ) bool HotkeyWrapper::setGlobalKey(int key, int key2, Qt::KeyboardModifiers modifier, int handle)
{ {
if ( !key ) { if (!key) {
return false; // We don't monitor empty combinations return false; // We don't monitor empty combinations
}
quint32 vk = nativeKey(key);
if (vk == 0) {
return false;
}
quint32 vk2 = key2 ? nativeKey(key2) : 0;
static int nextId = 1;
if (nextId > 0xBFFF - 1) {
nextId = 1;
}
quint32 mod = 0;
if (modifier & Qt::CTRL) {
mod |= cmdKey;
}
if (modifier & Qt::ALT) {
mod |= optionKey;
}
if (modifier & Qt::SHIFT) {
mod |= shiftKey;
}
if (modifier & Qt::META) {
mod |= controlKey;
}
hotkeys.append(HotkeyStruct(vk, vk2, mod, handle, nextId));
HotkeyStruct& hk = hotkeys.last();
EventHotKeyID hotKeyID;
hotKeyID.signature = 'GDHK';
hotKeyID.id = nextId;
OSStatus ret = RegisterEventHotKey(vk, mod, hotKeyID, GetApplicationEventTarget(), 0, &hk.hkRef);
if (ret != 0) {
return false;
}
if (vk2 && vk2 != vk) {
hotKeyID.id = nextId + 1;
ret = RegisterEventHotKey(vk2, mod, hotKeyID, GetApplicationEventTarget(), 0, &hk.hkRef2);
}
nextId += 2;
return ret == 0;
} }
quint32 vk = nativeKey( key ); quint32 HotkeyWrapper::nativeKey(int key)
if( vk == 0 ) {
return false;
}
quint32 vk2 = key2 ? nativeKey( key2 ) : 0;
static int nextId = 1;
if( nextId > 0xBFFF - 1 ) {
nextId = 1;
}
quint32 mod = 0;
if( modifier & Qt::CTRL ) {
mod |= cmdKey;
}
if( modifier & Qt::ALT ) {
mod |= optionKey;
}
if( modifier & Qt::SHIFT ) {
mod |= shiftKey;
}
if( modifier & Qt::META ) {
mod |= controlKey;
}
hotkeys.append( HotkeyStruct( vk, vk2, mod, handle, nextId ) );
HotkeyStruct &hk = hotkeys.last();
EventHotKeyID hotKeyID;
hotKeyID.signature = 'GDHK';
hotKeyID.id = nextId;
OSStatus ret = RegisterEventHotKey( vk, mod, hotKeyID, GetApplicationEventTarget(), 0, &hk.hkRef );
if ( ret != 0 ) {
return false;
}
if ( vk2 && vk2 != vk )
{
hotKeyID.id = nextId + 1;
ret = RegisterEventHotKey( vk2, mod, hotKeyID, GetApplicationEventTarget(), 0, &hk.hkRef2 );
}
nextId += 2;
return ret == 0;
}
quint32 HotkeyWrapper::nativeKey( int key )
{ {
switch( key ) { switch (key) {
case Qt::Key_Escape: return 0x35; case Qt::Key_Escape:
case Qt::Key_Tab: return 0x30; return 0x35;
case Qt::Key_Backspace: return 0x33; case Qt::Key_Tab:
case Qt::Key_Return: return 0x24; return 0x30;
case Qt::Key_Enter: return 0x4c; case Qt::Key_Backspace:
case Qt::Key_Delete: return 0x75; return 0x33;
case Qt::Key_Clear: return 0x47; case Qt::Key_Return:
case Qt::Key_Home: return 0x73; return 0x24;
case Qt::Key_End: return 0x77; case Qt::Key_Enter:
case Qt::Key_Left: return 0x7b; return 0x4c;
case Qt::Key_Up: return 0x7e; case Qt::Key_Delete:
case Qt::Key_Right: return 0x7c; return 0x75;
case Qt::Key_Down: return 0x7d; case Qt::Key_Clear:
case Qt::Key_PageUp: return 0x74; return 0x47;
case Qt::Key_PageDown: return 0x79; case Qt::Key_Home:
case Qt::Key_CapsLock: return 0x57; return 0x73;
case Qt::Key_F1: return 0x7a; case Qt::Key_End:
case Qt::Key_F2: return 0x78; return 0x77;
case Qt::Key_F3: return 0x63; case Qt::Key_Left:
case Qt::Key_F4: return 0x76; return 0x7b;
case Qt::Key_F5: return 0x60; case Qt::Key_Up:
case Qt::Key_F6: return 0x61; return 0x7e;
case Qt::Key_F7: return 0x62; case Qt::Key_Right:
case Qt::Key_F8: return 0x64; return 0x7c;
case Qt::Key_F9: return 0x65; case Qt::Key_Down:
case Qt::Key_F10: return 0x6d; return 0x7d;
case Qt::Key_F11: return 0x67; case Qt::Key_PageUp:
case Qt::Key_F12: return 0x6f; return 0x74;
case Qt::Key_F13: return 0x69; case Qt::Key_PageDown:
case Qt::Key_F14: return 0x6b; return 0x79;
case Qt::Key_F15: return 0x71; case Qt::Key_CapsLock:
case Qt::Key_Help: return 0x72; return 0x57;
case Qt::Key_F1:
return 0x7a;
case Qt::Key_F2:
return 0x78;
case Qt::Key_F3:
return 0x63;
case Qt::Key_F4:
return 0x76;
case Qt::Key_F5:
return 0x60;
case Qt::Key_F6:
return 0x61;
case Qt::Key_F7:
return 0x62;
case Qt::Key_F8:
return 0x64;
case Qt::Key_F9:
return 0x65;
case Qt::Key_F10:
return 0x6d;
case Qt::Key_F11:
return 0x67;
case Qt::Key_F12:
return 0x6f;
case Qt::Key_F13:
return 0x69;
case Qt::Key_F14:
return 0x6b;
case Qt::Key_F15:
return 0x71;
case Qt::Key_Help:
return 0x72;
default:; default:;
} }
return MacKeyMapping::qtKeyToNativeKey( QChar( key ).toLower().toLatin1() ); return MacKeyMapping::qtKeyToNativeKey(QChar(key).toLower().unicode());
} }
void HotkeyWrapper::sendCmdC() void HotkeyWrapper::sendCmdC()
{ {
CGEventFlags flags = kCGEventFlagMaskCommand; CGEventFlags flags = kCGEventFlagMaskCommand;
CGEventRef ev; CGEventRef ev;
CGEventSourceRef source = CGEventSourceCreate( kCGEventSourceStateCombinedSessionState ); CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStateCombinedSessionState);
//press down // press down
ev = CGEventCreateKeyboardEvent( source, keyC, true ); ev = CGEventCreateKeyboardEvent(source, keyC, true);
CGEventSetFlags( ev, CGEventFlags( flags | CGEventGetFlags( ev ) ) ); //combine flags CGEventSetFlags(ev, CGEventFlags(flags | CGEventGetFlags(ev))); // combine flags
CGEventPost( kCGAnnotatedSessionEventTap, ev ); CGEventPost(kCGAnnotatedSessionEventTap, ev);
CFRelease( ev ); CFRelease(ev);
//press up // press up
ev = CGEventCreateKeyboardEvent( source, keyC, false ); ev = CGEventCreateKeyboardEvent(source, keyC, false);
CGEventSetFlags( ev, CGEventFlags( flags | CGEventGetFlags( ev ) ) ); //combine flags CGEventSetFlags(ev, CGEventFlags(flags | CGEventGetFlags(ev))); // combine flags
CGEventPost( kCGAnnotatedSessionEventTap, ev ); CGEventPost(kCGAnnotatedSessionEventTap, ev);
CFRelease( ev ); CFRelease(ev);
CFRelease( source ); CFRelease(source);
} }
EventHandlerUPP HotkeyWrapper::hotKeyFunction = NULL; EventHandlerUPP HotkeyWrapper::hotKeyFunction = NULL;

View file

@ -1,372 +1,344 @@
#include "macmouseover.hh" #include "macmouseover.hh"
#include <AppKit/NSTouch.h> #import <AppKit/AppKit.h>
#include <AppKit/NSEvent.h>
#include <AppKit/NSScreen.h>
#include <Foundation/NSAutoreleasePool.h>
#include <Foundation/Foundation.h>
#ifndef AVAILABLE_MAC_OS_X_VERSION_10_11_AND_LATER
#define kAXValueTypeCGPoint kAXValueCGPointType
#define kAXValueTypeCFRange kAXValueCFRangeType
#endif
const int mouseOverInterval = 300; const int mouseOverInterval = 300;
CGEventRef eventCallback( CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon ) CGEventRef eventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void* refcon)
{ {
(void) proxy; (void)proxy;
if( type != kCGEventMouseMoved ) { if (type != kCGEventMouseMoved) {
return event;
}
static_cast<MacMouseOver*>(refcon)->mouseMoved();
return event; return event;
} }
static_cast< MacMouseOver * >( refcon )->mouseMoved();
return event;
}
static CGPoint carbonScreenPointFromCocoaScreenPoint( NSPoint cocoaPoint ) static CGPoint carbonScreenPointFromCocoaScreenPoint(NSPoint cocoaPoint)
{ {
NSScreen *foundScreen = nil; NSScreen* foundScreen = nil;
CGPoint thePoint; CGPoint thePoint;
for (NSScreen *screen in [NSScreen screens]) { for (NSScreen* screen in [NSScreen screens]) {
if (NSPointInRect(cocoaPoint, [screen frame])) { if (NSPointInRect(cocoaPoint, [screen frame])) {
foundScreen = screen; foundScreen = screen;
}
} }
}
if (foundScreen) { if (foundScreen) {
CGFloat screenHeight = [foundScreen frame].size.height; CGFloat screenHeight = [foundScreen frame].size.height;
thePoint = CGPointMake(cocoaPoint.x, screenHeight - cocoaPoint.y - 1); thePoint = CGPointMake(cocoaPoint.x, screenHeight - cocoaPoint.y - 1);
} } else {
else { thePoint = CGPointMake(0.0, 0.0);
thePoint = CGPointMake(0.0, 0.0); }
return thePoint;
} }
return thePoint; MacMouseOver& MacMouseOver::instance()
}
MacMouseOver & MacMouseOver::instance()
{ {
static MacMouseOver m; static MacMouseOver m;
return m; return m;
} }
MacMouseOver::MacMouseOver() : MacMouseOver::MacMouseOver()
pPref(NULL) : pPref(NULL)
, tapRef( 0 ) , tapRef(0)
, loop( 0 ) , loop(0)
{ {
mouseTimer.setSingleShot( true ); mouseTimer.setSingleShot(true);
connect( &mouseTimer, SIGNAL( timeout() ), this, SLOT( timerShot() ) ); connect(&mouseTimer, SIGNAL(timeout()), this, SLOT(timerShot()));
elementSystemWide = AXUIElementCreateSystemWide(); elementSystemWide = AXUIElementCreateSystemWide();
} }
MacMouseOver::~MacMouseOver() MacMouseOver::~MacMouseOver()
{ {
disableMouseOver(); disableMouseOver();
if( tapRef ) { if (tapRef) {
CFRelease( tapRef ); CFRelease(tapRef);
}
if (loop) {
CFRelease(loop);
}
if (elementSystemWide) {
CFRelease(elementSystemWide);
}
} }
if( loop ) { QString MacMouseOver::CFStringRefToQString(CFStringRef str)
CFRelease( loop );
}
if( elementSystemWide ) {
CFRelease( elementSystemWide );
}
}
QString MacMouseOver::CFStringRefToQString( CFStringRef str )
{ {
int length = CFStringGetLength( str ); int length = CFStringGetLength(str);
if( length == 0 ) { if (length == 0) {
return QString(); return QString();
} }
UniChar *chars = new UniChar[ length ]; UniChar* chars = new UniChar[length];
CFStringGetCharacters( str, CFRangeMake( 0, length ), chars ); CFStringGetCharacters(str, CFRangeMake(0, length), chars);
QString result = QString::fromUtf16( (char16_t*)chars, length );
delete[] chars; QString result = QString::fromUtf16((char16_t*)chars, length);
return result;
delete[] chars;
return result;
} }
void MacMouseOver::mouseMoved() void MacMouseOver::mouseMoved()
{ {
mouseTimer.start( mouseOverInterval ); mouseTimer.start(mouseOverInterval);
} }
void MacMouseOver::enableMouseOver() void MacMouseOver::enableMouseOver()
{ {
mouseTimer.stop(); mouseTimer.stop();
if( !isAXAPIEnabled() ) { if (!isAXAPIEnabled()) {
return; return;
} }
if( !tapRef ) { if (!tapRef) {
tapRef = CGEventTapCreate( kCGAnnotatedSessionEventTap, kCGHeadInsertEventTap, tapRef = CGEventTapCreate(kCGAnnotatedSessionEventTap, kCGHeadInsertEventTap,
kCGEventTapOptionListenOnly, kCGEventTapOptionListenOnly,
CGEventMaskBit( kCGEventMouseMoved ), CGEventMaskBit(kCGEventMouseMoved),
eventCallback, this ); eventCallback, this);
} }
if( !tapRef ) { if (!tapRef) {
return; return;
} }
if( !loop ) { if (!loop) {
loop = CFMachPortCreateRunLoopSource( kCFAllocatorDefault, tapRef, 0 ); loop = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, tapRef, 0);
} }
if( loop ) { if (loop) {
CFRunLoopAddSource( CFRunLoopGetMain(), loop, kCFRunLoopCommonModes ); CFRunLoopAddSource(CFRunLoopGetMain(), loop, kCFRunLoopCommonModes);
} }
} }
void MacMouseOver::disableMouseOver() void MacMouseOver::disableMouseOver()
{ {
mouseTimer.stop(); mouseTimer.stop();
if( loop ) { if (loop) {
CFRunLoopRemoveSource( CFRunLoopGetMain(), loop, kCFRunLoopCommonModes ); CFRunLoopRemoveSource(CFRunLoopGetMain(), loop, kCFRunLoopCommonModes);
} }
} }
void MacMouseOver::timerShot() void MacMouseOver::timerShot()
{ {
if( mouseMutex.tryLock( 0 ) ) { if (mouseMutex.tryLock(0)) {
mouseMutex.unlock(); mouseMutex.unlock();
} else { } else {
return; return;
} }
if( !pPref ) { if (!pPref) {
return; return;
} }
if( !pPref->enableScanPopupModifiers || checkModifiersPressed( pPref->scanPopupModifiers ) ) { if (!pPref->enableScanPopupModifiers || checkModifiersPressed(pPref->scanPopupModifiers)) {
handlePosition(); handlePosition();
} }
} }
void MacMouseOver::handlePosition() void MacMouseOver::handlePosition()
{ {
QMutexLocker _( &mouseMutex ); QMutexLocker _(&mouseMutex);
QString strToTranslate; QString strToTranslate;
NSAutoreleasePool * pool = [ [ NSAutoreleasePool alloc ] init ]; NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
CGPoint pt = carbonScreenPointFromCocoaScreenPoint( [NSEvent mouseLocation] ); CGPoint pt = carbonScreenPointFromCocoaScreenPoint([NSEvent mouseLocation]);
[ pool drain ]; [pool drain];
CFArrayRef names = 0; CFArrayRef names = 0;
AXUIElementRef elem = 0; AXUIElementRef elem = 0;
AXError err = AXUIElementCopyElementAtPosition( elementSystemWide, pt.x, pt.y, &elem ); AXError err = AXUIElementCopyElementAtPosition(elementSystemWide, pt.x, pt.y, &elem);
if( err != kAXErrorSuccess ) { if (err != kAXErrorSuccess) {
return; return;
} }
for( ; ; ) for (;;) {
{ CFTypeRef parameter = AXValueCreate(kAXValueTypeCGPoint, &pt);
CFTypeRef parameter = AXValueCreate( kAXValueTypeCGPoint, &pt ); CFTypeRef rangeValue;
CFTypeRef rangeValue; err = AXUIElementCopyParameterizedAttributeNames(elem, &names);
err = AXUIElementCopyParameterizedAttributeNames( elem, &names ); if (err != kAXErrorSuccess) {
if( err != kAXErrorSuccess ) {
break;
}
int numOfAttributes = CFArrayGetCount( names );
if( CFArrayContainsValue( names, CFRangeMake( 0, numOfAttributes ), CFSTR( "AXRangeForPosition" ) ) )
{
// Standard interface
err = AXUIElementCopyParameterizedAttributeValue( elem, kAXRangeForPositionParameterizedAttribute,
parameter, ( CFTypeRef * )&rangeValue );
CFRelease( parameter );
if( err != kAXErrorSuccess ) {
break;
}
CFStringRef stringValue;
CFRange decodedRange = CFRangeMake( 0, 0 );
bool b = AXValueGetValue( (AXValueRef)rangeValue, kAXValueTypeCFRange, &decodedRange );
CFRelease( rangeValue );
if( b )
{
int fromPos = decodedRange.location - 127;
if( fromPos < 0 ) {
fromPos = 0;
}
int wordPos = decodedRange.location - fromPos; // Cursor position in result string
CFRange range = CFRangeMake( fromPos, wordPos + 1 );
parameter = AXValueCreate( kAXValueTypeCFRange, &range );
err = AXUIElementCopyParameterizedAttributeValue( elem, kAXStringForRangeParameterizedAttribute,
parameter, (CFTypeRef *)&stringValue );
CFRelease( parameter );
if( err != kAXErrorSuccess ) {
break;
}
strToTranslate = CFStringRefToQString( stringValue );
CFRelease( stringValue );
// Read string further
for( int i = 1; i < 128; i++ )
{
range = CFRangeMake( decodedRange.location + i, 1 );
parameter = AXValueCreate( kAXValueTypeCFRange, &range );
err = AXUIElementCopyParameterizedAttributeValue( elem, kAXStringForRangeParameterizedAttribute,
parameter, (CFTypeRef *)&stringValue );
CFRelease( parameter );
if( err != kAXErrorSuccess ) {
break; break;
}
QString s = CFStringRefToQString( stringValue );
CFRelease( stringValue );
if( s[ 0 ].isLetterOrNumber() || s[ 0 ] == '-' ) {
strToTranslate += s;
} else {
break;
}
} }
handleRetrievedString( strToTranslate, wordPos ); int numOfAttributes = CFArrayGetCount(names);
} if (CFArrayContainsValue(names, CFRangeMake(0, numOfAttributes), CFSTR("AXRangeForPosition"))) {
} // Standard interface
else if( CFArrayContainsValue( names, CFRangeMake( 0, numOfAttributes ), CFSTR( "AXTextMarkerForPosition" ) ) ) err = AXUIElementCopyParameterizedAttributeValue(elem, kAXRangeForPositionParameterizedAttribute,
{ parameter, (CFTypeRef*)&rangeValue);
// Safari interface CFRelease(parameter);
CFTypeRef marker, range; if (err != kAXErrorSuccess) {
CFStringRef str; break;
err = AXUIElementCopyParameterizedAttributeValue( elem, CFSTR( "AXTextMarkerForPosition" ), }
parameter, ( CFTypeRef * )&marker );
CFRelease( parameter ); CFStringRef stringValue;
if( err != kAXErrorSuccess ) {
CFRange decodedRange = CFRangeMake(0, 0);
bool b = AXValueGetValue((AXValueRef)rangeValue, kAXValueTypeCFRange, &decodedRange);
CFRelease(rangeValue);
if (b) {
int fromPos = decodedRange.location - 127;
if (fromPos < 0) {
fromPos = 0;
}
int wordPos = decodedRange.location - fromPos; // Cursor position in result string
CFRange range = CFRangeMake(fromPos, wordPos + 1);
parameter = AXValueCreate(kAXValueTypeCFRange, &range);
err = AXUIElementCopyParameterizedAttributeValue(elem, kAXStringForRangeParameterizedAttribute,
parameter, (CFTypeRef*)&stringValue);
CFRelease(parameter);
if (err != kAXErrorSuccess) {
break;
}
strToTranslate = CFStringRefToQString(stringValue);
CFRelease(stringValue);
// Read string further
for (int i = 1; i < 128; i++) {
range = CFRangeMake(decodedRange.location + i, 1);
parameter = AXValueCreate(kAXValueTypeCFRange, &range);
err = AXUIElementCopyParameterizedAttributeValue(elem, kAXStringForRangeParameterizedAttribute,
parameter, (CFTypeRef*)&stringValue);
CFRelease(parameter);
if (err != kAXErrorSuccess) {
break;
}
QString s = CFStringRefToQString(stringValue);
CFRelease(stringValue);
if (s[0].isLetterOrNumber() || s[0] == '-') {
strToTranslate += s;
} else {
break;
}
}
handleRetrievedString(strToTranslate, wordPos);
}
} else if (CFArrayContainsValue(names, CFRangeMake(0, numOfAttributes), CFSTR("AXTextMarkerForPosition"))) {
// Safari interface
CFTypeRef marker, range;
CFStringRef str;
err = AXUIElementCopyParameterizedAttributeValue(elem, CFSTR("AXTextMarkerForPosition"),
parameter, (CFTypeRef*)&marker);
CFRelease(parameter);
if (err != kAXErrorSuccess) {
break;
}
err = AXUIElementCopyParameterizedAttributeValue(elem, CFSTR("AXLeftWordTextMarkerRangeForTextMarker"),
marker, (CFTypeRef*)&range);
CFRelease(marker);
if (err != kAXErrorSuccess) {
break;
}
err = AXUIElementCopyParameterizedAttributeValue(elem, CFSTR("AXStringForTextMarkerRange"),
range, (CFTypeRef*)&str);
CFRelease(range);
if (err == kAXErrorSuccess) {
strToTranslate = CFStringRefToQString(str);
CFRelease(str);
handleRetrievedString(strToTranslate, 0);
}
}
break; break;
}
err = AXUIElementCopyParameterizedAttributeValue( elem, CFSTR( "AXLeftWordTextMarkerRangeForTextMarker" ),
marker, ( CFTypeRef * )&range );
CFRelease( marker );
if( err != kAXErrorSuccess ) {
break;
}
err = AXUIElementCopyParameterizedAttributeValue( elem, CFSTR( "AXStringForTextMarkerRange" ),
range, ( CFTypeRef * )&str );
CFRelease( range );
if( err == kAXErrorSuccess )
{
strToTranslate = CFStringRefToQString( str );
CFRelease( str );
handleRetrievedString( strToTranslate, 0 );
}
} }
break; if (elem) {
} CFRelease(elem);
if( elem ) { }
CFRelease( elem ); if (names) {
} CFRelease(names);
if( names ) { }
CFRelease( names );
}
} }
void MacMouseOver::handleRetrievedString( QString & wordSeq, int wordSeqPos ) void MacMouseOver::handleRetrievedString(QString& wordSeq, int wordSeqPos)
{ {
if( wordSeq.isEmpty() ) { if (wordSeq.isEmpty()) {
return; return;
}
// locate the word inside the sequence
QString word;
if ( wordSeq[ wordSeqPos ].isSpace() )
{
// Currently we ignore such cases
return;
}
else
if ( !wordSeq[ wordSeqPos ].isLetterOrNumber() )
{
// Special case: the cursor points to something which doesn't look like a
// middle of the word -- assume that it's something that joins two words
// together.
int begin = wordSeqPos;
for( ; begin; --begin ) {
if ( !wordSeq[ begin - 1 ].isLetterOrNumber() ) {
break;
}
}
int end = wordSeqPos;
while( ++end < wordSeq.size() ) {
if ( !wordSeq[ end ].isLetterOrNumber() ) {
break;
}
}
if ( end - begin == 1 )
{
// Well, turns out it was just a single non-letter char, discard it
return;
} }
word = wordSeq.mid( begin, end - begin ); // locate the word inside the sequence
}
else
{
// Cursor points to a letter -- cut the word it points to
int begin = wordSeqPos; QString word;
for( ; begin; --begin ) { if (wordSeq[wordSeqPos].isSpace()) {
if ( !wordSeq[ begin - 1 ].isLetterOrNumber() ) { // Currently we ignore such cases
break; return;
} } else if (!wordSeq[wordSeqPos].isLetterOrNumber()) {
} // Special case: the cursor points to something which doesn't look like a
// middle of the word -- assume that it's something that joins two words
// together.
int end = wordSeqPos; int begin = wordSeqPos;
while( ++end < wordSeq.size() ) for (; begin; --begin) {
{ if (!wordSeq[begin - 1].isLetterOrNumber()) {
if ( !wordSeq[ end ].isLetterOrNumber() ) { break;
break; }
} }
int end = wordSeqPos;
while (++end < wordSeq.size()) {
if (!wordSeq[end].isLetterOrNumber()) {
break;
}
}
if (end - begin == 1) {
// Well, turns out it was just a single non-letter char, discard it
return;
}
word = wordSeq.mid(begin, end - begin);
} else {
// Cursor points to a letter -- cut the word it points to
int begin = wordSeqPos;
for (; begin; --begin) {
if (!wordSeq[begin - 1].isLetterOrNumber()) {
break;
}
}
int end = wordSeqPos;
while (++end < wordSeq.size()) {
if (!wordSeq[end].isLetterOrNumber()) {
break;
}
}
word = wordSeq.mid(begin, end - begin);
} }
word = wordSeq.mid( begin, end - begin );
}
// See if we have an RTL char. Reverse the whole string if we do. // See if we have an RTL char. Reverse the whole string if we do.
for( int x = 0; x < word.size(); ++x ) for (int x = 0; x < word.size(); ++x) {
{ QChar::Direction d = word[x].direction();
QChar::Direction d = word[ x ].direction();
if ( d == QChar::DirR || d == QChar::DirAL || if (d == QChar::DirR || d == QChar::DirAL || d == QChar::DirRLE || d == QChar::DirRLO) {
d == QChar::DirRLE || d == QChar::DirRLO ) std::reverse(word.begin(), word.end());
{ break;
std::reverse( word.begin(), word.end() ); }
break;
} }
}
emit instance().hovered( word, false ); emit instance().hovered(word, false);
} }
bool MacMouseOver::isAXAPIEnabled() bool MacMouseOver::isAXAPIEnabled()
{ {
if( NSFoundationVersionNumber >= 1000 ) { // MacOS 10.9+ if (NSFoundationVersionNumber >= 1000) { // MacOS 10.9+
return AXIsProcessTrusted(); return AXIsProcessTrusted();
} }
return AXAPIEnabled(); return AXAPIEnabled();
} }

View file

@ -185,6 +185,7 @@ Preferences::Preferences( QWidget * parent, Config::Class & cfg_ ):
ui.doubleClickTranslates->setChecked( p.doubleClickTranslates ); ui.doubleClickTranslates->setChecked( p.doubleClickTranslates );
ui.selectBySingleClick->setChecked( p.selectWordBySingleClick ); ui.selectBySingleClick->setChecked( p.selectWordBySingleClick );
ui.autoScrollToTargetArticle->setChecked( p.autoScrollToTargetArticle ); ui.autoScrollToTargetArticle->setChecked( p.autoScrollToTargetArticle );
ui.targetArticleAtFirst->setChecked( p.targetArticleAtFirst );
ui.escKeyHidesMainWindow->setChecked( p.escKeyHidesMainWindow ); ui.escKeyHidesMainWindow->setChecked( p.escKeyHidesMainWindow );
ui.darkMode->addItem( tr( "On" ), QVariant::fromValue( Config::Dark::On ) ); ui.darkMode->addItem( tr( "On" ), QVariant::fromValue( Config::Dark::On ) );
@ -441,6 +442,7 @@ Config::Preferences Preferences::getPreferences()
p.doubleClickTranslates = ui.doubleClickTranslates->isChecked(); p.doubleClickTranslates = ui.doubleClickTranslates->isChecked();
p.selectWordBySingleClick = ui.selectBySingleClick->isChecked(); p.selectWordBySingleClick = ui.selectBySingleClick->isChecked();
p.autoScrollToTargetArticle = ui.autoScrollToTargetArticle->isChecked(); p.autoScrollToTargetArticle = ui.autoScrollToTargetArticle->isChecked();
p.targetArticleAtFirst = ui.targetArticleAtFirst->isChecked();
p.escKeyHidesMainWindow = ui.escKeyHidesMainWindow->isChecked(); p.escKeyHidesMainWindow = ui.escKeyHidesMainWindow->isChecked();
p.darkMode = ui.darkMode->currentData().value< Config::Dark >(); p.darkMode = ui.darkMode->currentData().value< Config::Dark >();

View file

@ -169,6 +169,16 @@ however, the article from the topmost dictionary is shown.</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="1">
<widget class="QCheckBox" name="targetArticleAtFirst">
<property name="text">
<string>Place the target article at the first place.</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0"> <item row="1" column="0">
<widget class="QGroupBox" name="enableTrayIcon"> <widget class="QGroupBox" name="enableTrayIcon">
<property name="toolTip"> <property name="toolTip">