Compare commits

...

18 commits

Author SHA1 Message Date
xiaoyifang 071e9389e1
Merge 6c63ba45b6 into 453948155a 2024-11-12 19:00:54 -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
YiFang Xiao 6c63ba45b6 1 2024-11-09 20:40:55 +08:00
YiFang Xiao a9478cdc34 1 2024-11-09 20:17:29 +08:00
xiaoyifang c0697ecdda 1 2024-11-04 11:42:04 +08:00
autofix-ci[bot] 701a4effb3
[autofix.ci] apply automated fixes 2024-11-04 03:29:03 +00:00
xiaoyifang f9a3705942 1 2024-11-04 11:27:46 +08:00
YiFang Xiao 2c91b78e13 1 2024-11-03 17:42:51 +08:00
YiFang Xiao c6387df392 1 2024-11-03 17:42:50 +08:00
autofix-ci[bot] 43c982cf8a
[autofix.ci] apply automated fixes 2024-11-03 07:26:21 +00:00
YiFang Xiao e07e730a09 1 2024-11-03 15:19:32 +08:00
YiFang Xiao ab214cfb05 1 2024-11-03 14:20:10 +08:00
autofix-ci[bot] c00239a1b1 [autofix.ci] apply automated fixes 2024-11-03 14:20:03 +08:00
xiaoyifang 469896bbf1 1 2024-11-03 14:19:19 +08:00
xiaoyifang 0d89c4ab56 1 2024-11-03 14:19:19 +08:00
xiaoyifang 9e99389cc3 opt: website dictionary render html 2024-11-03 14:19:12 +08:00
xiaoyifang 6edfd15962 opt: add option about open website in seperate tab 2024-11-03 14:17:29 +08:00
16 changed files with 786 additions and 683 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

@ -529,6 +529,26 @@ void ArticleRequest::altSearchFinished()
for ( const auto & activeDict : activeDicts ) { for ( const auto & activeDict : activeDicts ) {
try { try {
// if the dictionary is website dictionary and openinNewTab is enabled, emit a signal.
if ( GlobalBroadcaster::instance()->getPreference()->openWebsiteInNewTab ) {
if ( ( activeDict->getFeatures() | Dictionary::WebSite ) == Dictionary::WebSite ) {
//replace the word,and get the actual requested url
string url = activeDict->getProperties()[ Dictionary::Property::Url ];
if ( url.empty() ) {
continue;
}
QString requestUrl = Utils::WebSite::urlReplaceWord( QString::fromStdString( url ), word );
emit GlobalBroadcaster::instance()
-> websiteDictionarySignal( QString::fromStdString( activeDict->getName() ), requestUrl );
QStringList dictIds;
dictIds << QString::fromStdString( activeDict->getId() );
ActiveDictIds hittedWord{ group.id, word, dictIds };
emit GlobalBroadcaster::instance() -> dictionaryChanges( hittedWord );
continue;
}
}
sptr< Dictionary::DataRequest > r = activeDict->getArticle( sptr< Dictionary::DataRequest > r = activeDict->getArticle(
wordStd, wordStd,
altsVector, altsVector,

View file

@ -45,4 +45,6 @@ signals:
void dictionaryClear( ActiveDictIds ad ); void dictionaryClear( ActiveDictIds ad );
void indexingDictionary( QString ); void indexingDictionary( QString );
void websiteDictionarySignal( QString, QString );
}; };

View file

@ -1071,6 +1071,10 @@ Class load()
( preferences.namedItem( "removeInvalidIndexOnExit" ).toElement().text() == "1" ); ( preferences.namedItem( "removeInvalidIndexOnExit" ).toElement().text() == "1" );
} }
if ( !preferences.namedItem( "openWebsiteInNewTab" ).isNull() ) {
c.preferences.openWebsiteInNewTab = ( preferences.namedItem( "openWebsiteInNewTab" ).toElement().text() == "1" );
}
if ( !preferences.namedItem( "maxStringsInHistory" ).isNull() ) { if ( !preferences.namedItem( "maxStringsInHistory" ).isNull() ) {
c.preferences.maxStringsInHistory = preferences.namedItem( "maxStringsInHistory" ).toElement().text().toUInt(); c.preferences.maxStringsInHistory = preferences.namedItem( "maxStringsInHistory" ).toElement().text().toUInt();
} }
@ -2100,6 +2104,10 @@ void save( Class const & c )
opt.appendChild( dd.createTextNode( c.preferences.removeInvalidIndexOnExit ? "1" : "0" ) ); opt.appendChild( dd.createTextNode( c.preferences.removeInvalidIndexOnExit ? "1" : "0" ) );
preferences.appendChild( opt ); preferences.appendChild( opt );
opt = dd.createElement( "openWebsiteInNewTab" );
opt.appendChild( dd.createTextNode( c.preferences.openWebsiteInNewTab ? "1" : "0" ) );
preferences.appendChild( opt );
opt = dd.createElement( "maxStringsInHistory" ); opt = dd.createElement( "maxStringsInHistory" );
opt.appendChild( dd.createTextNode( QString::number( c.preferences.maxStringsInHistory ) ) ); opt.appendChild( dd.createTextNode( QString::number( c.preferences.maxStringsInHistory ) ) );
preferences.appendChild( opt ); preferences.appendChild( opt );

View file

@ -400,6 +400,7 @@ struct Preferences
int maxNetworkCacheSize; int maxNetworkCacheSize;
bool clearNetworkCacheOnExit; bool clearNetworkCacheOnExit;
bool removeInvalidIndexOnExit = false; bool removeInvalidIndexOnExit = false;
bool openWebsiteInNewTab = false;
qreal zoomFactor; qreal zoomFactor;
qreal helpZoomFactor; qreal helpZoomFactor;

View file

@ -34,7 +34,8 @@ enum Property {
Author, Author,
Copyright, Copyright,
Description, Description,
Email Email,
Url,
}; };
DEF_EX( Ex, "Dictionary error", std::exception ) DEF_EX( Ex, "Dictionary error", std::exception )

View file

@ -24,7 +24,6 @@ class WebSiteDictionary: public Dictionary::Class
{ {
string name; string name;
QByteArray urlTemplate; QByteArray urlTemplate;
bool experimentalIframe;
QString iconFilename; QString iconFilename;
bool inside_iframe; bool inside_iframe;
QNetworkAccessManager & netMgr; QNetworkAccessManager & netMgr;
@ -41,14 +40,8 @@ public:
name( name_ ), name( name_ ),
iconFilename( iconFilename_ ), iconFilename( iconFilename_ ),
inside_iframe( inside_iframe_ ), inside_iframe( inside_iframe_ ),
netMgr( netMgr_ ), netMgr( netMgr_ )
experimentalIframe( false )
{ {
if ( urlTemplate_.startsWith( "http://" ) || urlTemplate_.startsWith( "https://" ) ) {
experimentalIframe = true;
}
//else file:/// local dictionary file path
urlTemplate = QUrl( urlTemplate_ ).toEncoded(); urlTemplate = QUrl( urlTemplate_ ).toEncoded();
dictionaryDescription = urlTemplate_; dictionaryDescription = urlTemplate_;
} }
@ -60,7 +53,9 @@ public:
map< Property, string > getProperties() noexcept override map< Property, string > getProperties() noexcept override
{ {
return map< Property, string >(); map< Property, string > properties;
properties.insert( { Property::Url, urlTemplate.toStdString() } );
return properties;
} }
unsigned long getArticleCount() noexcept override unsigned long getArticleCount() noexcept override
@ -321,7 +316,7 @@ void WebSiteArticleRequest::requestFinished( QNetworkReply * r )
sptr< DataRequest > WebSiteDictionary::getArticle( wstring const & str, sptr< DataRequest > WebSiteDictionary::getArticle( wstring const & str,
vector< wstring > const & /*alts*/, vector< wstring > const & /*alts*/,
wstring const & context, wstring const & /*context*/,
bool /*ignoreDiacritics*/ ) bool /*ignoreDiacritics*/ )
{ {
QString urlString = Utils::WebSite::urlReplaceWord( QString( urlTemplate ), QString::fromStdU32String( str ) ); QString urlString = Utils::WebSite::urlReplaceWord( QString( urlTemplate ), QString::fromStdU32String( str ) );
@ -335,24 +330,22 @@ sptr< DataRequest > WebSiteDictionary::getArticle( wstring const & str,
QUrl url( urlString ); QUrl url( urlString );
GlobalBroadcaster::instance()->addWhitelist( url.host() ); GlobalBroadcaster::instance()->addWhitelist( url.host() );
QString encodeUrl; const QString & encodeUrl = urlString;
if ( experimentalIframe ) {
encodeUrl = "ifr://localhost?url=" + QUrl::toPercentEncoding( urlString ); if ( GlobalBroadcaster::instance()->getPreference()->openWebsiteInNewTab ) {
result += string( "<div><span>this website dictionary is opened in the new tab</span></div>" );
} }
else { else {
encodeUrl = urlString; fmt::format_to( std::back_inserter( result ),
} R"(<iframe id="gdexpandframe-{}" src="{}"
fmt::format_to( std::back_inserter( result ),
R"(<iframe id="gdexpandframe-{}" src="{}"
onmouseover="processIframeMouseOver('gdexpandframe-{}');" onmouseover="processIframeMouseOver('gdexpandframe-{}');"
onmouseout="processIframeMouseOut();" scrolling="no" onmouseout="processIframeMouseOut();" scrolling="no"
style="overflow:visible; width:100%; display:block; border:none;" style="overflow:visible; width:100%; display:block; border:none;"
sandbox="allow-same-origin allow-scripts allow-popups"></iframe>)", sandbox="allow-same-origin allow-scripts allow-popups"></iframe>)",
getId(), getId(),
encodeUrl.toStdString(), encodeUrl.toStdString(),
getId() ); getId() );
}
auto dr = std::make_shared< DataRequestInstant >( true ); auto dr = std::make_shared< DataRequestInstant >( true );
dr->appendString( result ); dr->appendString( result );
return dr; return dr;

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

@ -85,6 +85,162 @@ QString ArticleView::scrollToFromDictionaryId( QString const & dictionaryId )
Q_ASSERT( !isScrollTo( dictionaryId ) ); Q_ASSERT( !isScrollTo( dictionaryId ) );
return scrollToPrefix + dictionaryId; return scrollToPrefix + dictionaryId;
} }
void ArticleView::setupWebview()
{ // setup GUI
this->webview = new ArticleWebView( this );
this->ftsSearchPanel = new FtsSearchPanel( this );
this->searchPanel = new SearchPanel( this );
this->searchPanel->hide();
this->ftsSearchPanel->hide();
auto * baseLayout = new QVBoxLayout( this );
this->tabWidget = new QTabWidget( this );
baseLayout->setContentsMargins( 0, 0, 0, 0 );
baseLayout->addWidget( this->tabWidget );
auto * tab1 = new QWidget( tabWidget );
// Layout
auto * mainLayout = new QVBoxLayout( tab1 );
mainLayout->addWidget( this->webview );
mainLayout->addWidget( this->ftsSearchPanel );
mainLayout->addWidget( this->searchPanel );
this->webview->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding );
this->ftsSearchPanel->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Minimum );
this->searchPanel->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Minimum );
mainLayout->setContentsMargins( 0, 0, 0, 0 );
this->tabWidget->addTab( tab1, "Dictionaries" );
this->tabWidget->setTabBarAutoHide( true );
// end UI setup
connect( this->searchPanel->previous, &QPushButton::clicked, this, &ArticleView::on_searchPrevious_clicked );
connect( this->searchPanel->next, &QPushButton::clicked, this, &ArticleView::on_searchNext_clicked );
connect( this->searchPanel->close, &QPushButton::clicked, this, &ArticleView::on_searchCloseButton_clicked );
connect( this->searchPanel->caseSensitive, &QCheckBox::toggled, this, &ArticleView::on_searchCaseSensitive_clicked );
connect( this->searchPanel->lineEdit, &QLineEdit::textEdited, this, &ArticleView::on_searchText_textEdited );
connect( this->searchPanel->lineEdit, &QLineEdit::returnPressed, this, &ArticleView::on_searchText_returnPressed );
connect( this->ftsSearchPanel->next, &QPushButton::clicked, this, &ArticleView::on_ftsSearchNext_clicked );
connect( this->ftsSearchPanel->previous, &QPushButton::clicked, this, &ArticleView::on_ftsSearchPrevious_clicked );
//
this->webview->setUp( const_cast< Config::Class * >( &this->cfg ) );
this->syncBackgroundColorWithCfgDarkReader();
this->goBackAction.setShortcut( QKeySequence( "Alt+Left" ) );
this->webview->addAction( &this->goBackAction );
connect( &this->goBackAction, &QAction::triggered, this, &ArticleView::back );
this->goForwardAction.setShortcut( QKeySequence( "Alt+Right" ) );
this->webview->addAction( &this->goForwardAction );
connect( &this->goForwardAction, &QAction::triggered, this, &ArticleView::forward );
this->webview->pageAction( QWebEnginePage::Copy )->setShortcut( QKeySequence::Copy );
this->webview->addAction( this->webview->pageAction( QWebEnginePage::Copy ) );
QAction * selectAll = this->webview->pageAction( QWebEnginePage::SelectAll );
selectAll->setShortcut( QKeySequence::SelectAll );
selectAll->setShortcutContext( Qt::WidgetWithChildrenShortcut );
this->webview->addAction( selectAll );
this->webview->setContextMenuPolicy( Qt::CustomContextMenu );
connect( this->webview, &QWebEngineView::loadFinished, this, &ArticleView::loadFinished );
connect( this->webview, &ArticleWebView::linkClicked, this, &ArticleView::linkClicked );
connect( this->webview->page(), &QWebEnginePage::titleChanged, this, &ArticleView::handleTitleChanged );
connect( this->webview, &QWidget::customContextMenuRequested, this, &ArticleView::contextMenuRequested );
connect( this->webview->page(), &QWebEnginePage::linkHovered, this, &ArticleView::linkHovered );
connect( this->webview, &ArticleWebView::doubleClicked, this, &ArticleView::doubleClicked );
this->pasteAction.setShortcut( QKeySequence::Paste );
this->webview->addAction( &this->pasteAction );
connect( &this->pasteAction, &QAction::triggered, this, &ArticleView::pasteTriggered );
this->articleUpAction.setShortcut( QKeySequence( "Alt+Up" ) );
this->webview->addAction( &this->articleUpAction );
connect( &this->articleUpAction, &QAction::triggered, this, &ArticleView::moveOneArticleUp );
this->articleDownAction.setShortcut( QKeySequence( "Alt+Down" ) );
this->webview->addAction( &this->articleDownAction );
connect( &this->articleDownAction, &QAction::triggered, this, &ArticleView::moveOneArticleDown );
this->selectCurrentArticleAction.setShortcut( QKeySequence( "Ctrl+Shift+A" ) );
this->selectCurrentArticleAction.setText( tr( "Select Current Article" ) );
this->webview->addAction( &this->selectCurrentArticleAction );
connect( &this->selectCurrentArticleAction, &QAction::triggered, this, &ArticleView::selectCurrentArticle );
this->copyAsTextAction.setShortcut( QKeySequence( "Ctrl+Shift+C" ) );
this->copyAsTextAction.setText( tr( "Copy as text" ) );
this->webview->addAction( &this->copyAsTextAction );
connect( &this->copyAsTextAction, &QAction::triggered, this, &ArticleView::copyAsText );
this->inspectAction.setShortcut( QKeySequence( Qt::Key_F12 ) );
this->inspectAction.setText( tr( "Inspect" ) );
this->webview->addAction( &this->inspectAction );
connect( &this->inspectAction, &QAction::triggered, this, &ArticleView::inspectElement );
this->webview->installEventFilter( this );
this->searchPanel->installEventFilter( this );
this->ftsSearchPanel->installEventFilter( this );
QWebEngineSettings * settings = this->webview->settings();
settings->setUnknownUrlSchemePolicy( QWebEngineSettings::UnknownUrlSchemePolicy::DisallowUnknownUrlSchemes );
#if ( QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 ) )
settings->defaultSettings()->setAttribute( QWebEngineSettings::LocalContentCanAccessRemoteUrls, true );
settings->defaultSettings()->setAttribute( QWebEngineSettings::LocalContentCanAccessFileUrls, true );
settings->defaultSettings()->setAttribute( QWebEngineSettings::ErrorPageEnabled, false );
settings->defaultSettings()->setAttribute( QWebEngineSettings::LinksIncludedInFocusChain, false );
settings->defaultSettings()->setAttribute( QWebEngineSettings::PlaybackRequiresUserGesture, false );
settings->defaultSettings()->setAttribute( QWebEngineSettings::JavascriptCanAccessClipboard, true );
settings->defaultSettings()->setAttribute( QWebEngineSettings::PrintElementBackgrounds, false );
#else
settings->setAttribute( QWebEngineSettings::LocalContentCanAccessRemoteUrls, true );
settings->setAttribute( QWebEngineSettings::LocalContentCanAccessFileUrls, true );
settings->setAttribute( QWebEngineSettings::ErrorPageEnabled, false );
settings->setAttribute( QWebEngineSettings::LinksIncludedInFocusChain, false );
settings->setAttribute( QWebEngineSettings::PlaybackRequiresUserGesture, false );
settings->setAttribute( QWebEngineSettings::JavascriptCanAccessClipboard, true );
settings->setAttribute( QWebEngineSettings::PrintElementBackgrounds, false );
#endif
this->expandOptionalParts = this->cfg.preferences.alwaysExpandOptionalParts;
#ifndef Q_OS_MACOS
this->webview->grabGesture( Gestures::GDPinchGestureType );
this->webview->grabGesture( Gestures::GDSwipeGestureType );
#endif
}
void ArticleView::addWebsiteTab( QString name, QString url )
{
auto * view = new QWebEngineView( this );
view->load( url );
view->setZoomFactor( this->cfg.preferences.zoomFactor );
int index = tabWidget->count();
QString escaped = Utils::escapeAmps( name );
tabWidget->insertTab( index, view, escaped );
}
void ArticleView::clearWebsiteTabs()
{
int index = tabWidget->count();
// keep the first tab
while ( index-- > 1 ) {
tabWidget->removeTab( index );
}
}
ArticleView::ArticleView( QWidget * parent, ArticleView::ArticleView( QWidget * parent,
ArticleNetworkAccessManager & nm, ArticleNetworkAccessManager & nm,
@ -115,133 +271,11 @@ ArticleView::ArticleView( QWidget * parent,
translateLine( translateLine_ ) translateLine( translateLine_ )
{ {
// setup GUI // setup GUI
webview = new ArticleWebView( this ); setupWebview();
ftsSearchPanel = new FtsSearchPanel( this );
searchPanel = new SearchPanel( this );
searchPanel->hide();
ftsSearchPanel->hide();
// Layout
auto * mainLayout = new QVBoxLayout( this );
mainLayout->addWidget( webview );
mainLayout->addWidget( ftsSearchPanel );
mainLayout->addWidget( searchPanel );
webview->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding ); auto html = this->articleNetMgr.getHtml( ResourceType::UNTITLE );
ftsSearchPanel->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Minimum );
searchPanel->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Minimum );
mainLayout->setContentsMargins( 0, 0, 0, 0 );
// end UI setup
connect( searchPanel->previous, &QPushButton::clicked, this, &ArticleView::on_searchPrevious_clicked );
connect( searchPanel->next, &QPushButton::clicked, this, &ArticleView::on_searchNext_clicked );
connect( searchPanel->close, &QPushButton::clicked, this, &ArticleView::on_searchCloseButton_clicked );
connect( searchPanel->caseSensitive, &QCheckBox::toggled, this, &ArticleView::on_searchCaseSensitive_clicked );
connect( searchPanel->lineEdit, &QLineEdit::textEdited, this, &ArticleView::on_searchText_textEdited );
connect( searchPanel->lineEdit, &QLineEdit::returnPressed, this, &ArticleView::on_searchText_returnPressed );
connect( ftsSearchPanel->next, &QPushButton::clicked, this, &ArticleView::on_ftsSearchNext_clicked );
connect( ftsSearchPanel->previous, &QPushButton::clicked, this, &ArticleView::on_ftsSearchPrevious_clicked );
//
webview->setUp( const_cast< Config::Class * >( &cfg ) );
syncBackgroundColorWithCfgDarkReader();
goBackAction.setShortcut( QKeySequence( "Alt+Left" ) );
webview->addAction( &goBackAction );
connect( &goBackAction, &QAction::triggered, this, &ArticleView::back );
goForwardAction.setShortcut( QKeySequence( "Alt+Right" ) );
webview->addAction( &goForwardAction );
connect( &goForwardAction, &QAction::triggered, this, &ArticleView::forward );
webview->pageAction( QWebEnginePage::Copy )->setShortcut( QKeySequence::Copy );
webview->addAction( webview->pageAction( QWebEnginePage::Copy ) );
QAction * selectAll = webview->pageAction( QWebEnginePage::SelectAll );
selectAll->setShortcut( QKeySequence::SelectAll );
selectAll->setShortcutContext( Qt::WidgetWithChildrenShortcut );
webview->addAction( selectAll );
webview->setContextMenuPolicy( Qt::CustomContextMenu );
connect( webview, &QWebEngineView::loadFinished, this, &ArticleView::loadFinished );
connect( webview, &ArticleWebView::linkClicked, this, &ArticleView::linkClicked );
connect( webview->page(), &QWebEnginePage::titleChanged, this, &ArticleView::handleTitleChanged );
connect( webview, &QWidget::customContextMenuRequested, this, &ArticleView::contextMenuRequested );
connect( webview->page(), &QWebEnginePage::linkHovered, this, &ArticleView::linkHovered );
connect( webview, &ArticleWebView::doubleClicked, this, &ArticleView::doubleClicked );
pasteAction.setShortcut( QKeySequence::Paste );
webview->addAction( &pasteAction );
connect( &pasteAction, &QAction::triggered, this, &ArticleView::pasteTriggered );
articleUpAction.setShortcut( QKeySequence( "Alt+Up" ) );
webview->addAction( &articleUpAction );
connect( &articleUpAction, &QAction::triggered, this, &ArticleView::moveOneArticleUp );
articleDownAction.setShortcut( QKeySequence( "Alt+Down" ) );
webview->addAction( &articleDownAction );
connect( &articleDownAction, &QAction::triggered, this, &ArticleView::moveOneArticleDown );
selectCurrentArticleAction.setShortcut( QKeySequence( "Ctrl+Shift+A" ) );
selectCurrentArticleAction.setText( tr( "Select Current Article" ) );
webview->addAction( &selectCurrentArticleAction );
connect( &selectCurrentArticleAction, &QAction::triggered, this, &ArticleView::selectCurrentArticle );
copyAsTextAction.setShortcut( QKeySequence( "Ctrl+Shift+C" ) );
copyAsTextAction.setText( tr( "Copy as text" ) );
webview->addAction( &copyAsTextAction );
connect( &copyAsTextAction, &QAction::triggered, this, &ArticleView::copyAsText );
inspectAction.setShortcut( QKeySequence( Qt::Key_F12 ) );
inspectAction.setText( tr( "Inspect" ) );
webview->addAction( &inspectAction );
connect( &inspectAction, &QAction::triggered, this, &ArticleView::inspectElement );
webview->installEventFilter( this );
searchPanel->installEventFilter( this );
ftsSearchPanel->installEventFilter( this );
QWebEngineSettings * settings = webview->settings();
settings->setUnknownUrlSchemePolicy( QWebEngineSettings::UnknownUrlSchemePolicy::DisallowUnknownUrlSchemes );
#if ( QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 ) )
settings->defaultSettings()->setAttribute( QWebEngineSettings::LocalContentCanAccessRemoteUrls, true );
settings->defaultSettings()->setAttribute( QWebEngineSettings::LocalContentCanAccessFileUrls, true );
settings->defaultSettings()->setAttribute( QWebEngineSettings::ErrorPageEnabled, false );
settings->defaultSettings()->setAttribute( QWebEngineSettings::LinksIncludedInFocusChain, false );
settings->defaultSettings()->setAttribute( QWebEngineSettings::PlaybackRequiresUserGesture, false );
settings->defaultSettings()->setAttribute( QWebEngineSettings::JavascriptCanAccessClipboard, true );
settings->defaultSettings()->setAttribute( QWebEngineSettings::PrintElementBackgrounds, false );
#else
settings->setAttribute( QWebEngineSettings::LocalContentCanAccessRemoteUrls, true );
settings->setAttribute( QWebEngineSettings::LocalContentCanAccessFileUrls, true );
settings->setAttribute( QWebEngineSettings::ErrorPageEnabled, false );
settings->setAttribute( QWebEngineSettings::LinksIncludedInFocusChain, false );
settings->setAttribute( QWebEngineSettings::PlaybackRequiresUserGesture, false );
settings->setAttribute( QWebEngineSettings::JavascriptCanAccessClipboard, true );
settings->setAttribute( QWebEngineSettings::PrintElementBackgrounds, false );
#endif
auto html = articleNetMgr.getHtml( ResourceType::UNTITLE );
webview->setHtml( QString::fromStdString( html ) );
expandOptionalParts = cfg.preferences.alwaysExpandOptionalParts;
#ifndef Q_OS_MACOS
webview->grabGesture( Gestures::GDPinchGestureType );
webview->grabGesture( Gestures::GDSwipeGestureType );
#endif
this->webview->setHtml( QString::fromStdString( html ) );
connect( GlobalBroadcaster::instance(), &GlobalBroadcaster::dictionaryChanges, this, &ArticleView::setActiveDictIds ); connect( GlobalBroadcaster::instance(), &GlobalBroadcaster::dictionaryChanges, this, &ArticleView::setActiveDictIds );
connect( GlobalBroadcaster::instance(), &GlobalBroadcaster::dictionaryClear, this, &ArticleView::dictionaryClear ); connect( GlobalBroadcaster::instance(), &GlobalBroadcaster::dictionaryClear, this, &ArticleView::dictionaryClear );
@ -304,6 +338,7 @@ void ArticleView::showDefinition( QString const & word,
Contexts const & contexts_ ) Contexts const & contexts_ )
{ {
GlobalBroadcaster::instance()->pronounce_engine.reset(); GlobalBroadcaster::instance()->pronounce_engine.reset();
clearWebsiteTabs();
currentWord = word.trimmed(); currentWord = word.trimmed();
if ( currentWord.isEmpty() ) { if ( currentWord.isEmpty() ) {
return; return;
@ -351,7 +386,7 @@ void ArticleView::showDefinition( QString const & word,
QString mutedDicts = getMutedForGroup( group ); QString mutedDicts = getMutedForGroup( group );
if ( mutedDicts.size() ) { if ( !mutedDicts.isEmpty() ) {
Utils::Url::addQueryItem( req, "muted", mutedDicts ); Utils::Url::addQueryItem( req, "muted", mutedDicts );
} }
@ -1235,6 +1270,17 @@ void ArticleView::syncBackgroundColorWithCfgDarkReader() const
#endif #endif
} }
void ArticleView::openWebsiteInNewTab( QString name, QString url )
{
QString escaped = Utils::escapeAmps( name );
//found existed QWebEngineView.
auto * view = new QWebEngineView( this );
view->load( QUrl( url ) );
tabWidget->addTab( view, escaped );
}
void ArticleView::back() void ArticleView::back()
{ {
@ -2162,6 +2208,11 @@ void ArticleView::clearContent()
webview->setHtml( QString::fromStdString( html ) ); webview->setHtml( QString::fromStdString( html ) );
} }
void ArticleView::load( QString url )
{
webview->load( QUrl( url ) );
}
ResourceToSaveHandler::ResourceToSaveHandler( ArticleView * view, QString fileName ): ResourceToSaveHandler::ResourceToSaveHandler( ArticleView * view, QString fileName ):
QObject( view ), QObject( view ),

View file

@ -96,7 +96,7 @@ public:
QAction * dictionaryBarToggled = nullptr, QAction * dictionaryBarToggled = nullptr,
unsigned currentGroupId = 0 ); unsigned currentGroupId = 0 );
void openWebsiteInNewTab( QString name, QString url );
void setCurrentGroupId( unsigned currengGrgId ); void setCurrentGroupId( unsigned currengGrgId );
unsigned getCurrentGroupId(); unsigned getCurrentGroupId();
@ -108,7 +108,7 @@ public:
~ArticleView(); ~ArticleView();
void load( QString url );
/// Returns "gdfrom-" + dictionaryId. /// Returns "gdfrom-" + dictionaryId.
static QString scrollToFromDictionaryId( QString const & dictionaryId ); static QString scrollToFromDictionaryId( QString const & dictionaryId );
@ -167,9 +167,14 @@ public:
/// \brief Set background as black if darkreader mode is enabled. /// \brief Set background as black if darkreader mode is enabled.
void syncBackgroundColorWithCfgDarkReader() const; void syncBackgroundColorWithCfgDarkReader() const;
void addWebsiteTab( QString name, QString url );
void clearWebsiteTabs();
private: private:
// widgets // widgets
ArticleWebView * webview; ArticleWebView * webview;
QTabWidget * tabWidget;
SearchPanel * searchPanel; SearchPanel * searchPanel;
FtsSearchPanel * ftsSearchPanel; FtsSearchPanel * ftsSearchPanel;
@ -411,6 +416,7 @@ private:
QString getMutedForGroup( unsigned group ); QString getMutedForGroup( unsigned group );
QStringList getMutedDictionaries( unsigned group ); QStringList getMutedDictionaries( unsigned group );
void setupWebview();
}; };
class ResourceToSaveHandler: public QObject class ResourceToSaveHandler: public QObject

View file

@ -66,6 +66,7 @@
#include <QGuiApplication> #include <QGuiApplication>
#include <QWebEngineSettings> #include <QWebEngineSettings>
#include <QProxyStyle> #include <QProxyStyle>
#include <utility>
#ifdef HAVE_X11 #ifdef HAVE_X11
#if ( QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 ) ) #if ( QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 ) )
@ -719,6 +720,10 @@ MainWindow::MainWindow( Config::Class & cfg_ ):
&GlobalBroadcaster::indexingDictionary, &GlobalBroadcaster::indexingDictionary,
this, this,
&MainWindow::showFTSIndexingName ); &MainWindow::showFTSIndexingName );
connect( GlobalBroadcaster::instance(),
&GlobalBroadcaster::websiteDictionarySignal,
this,
&MainWindow::openWebsiteInNewTab );
connect( &GlobalBroadcaster::instance()->pronounce_engine, connect( &GlobalBroadcaster::instance()->pronounce_engine,
&PronounceEngine::emitAudio, &PronounceEngine::emitAudio,
@ -2156,7 +2161,7 @@ void MainWindow::updateFoundInDictsList()
if ( dictionaries[ x ]->getId() == i->toUtf8().data() ) { if ( dictionaries[ x ]->getId() == i->toUtf8().data() ) {
QString dictName = QString::fromUtf8( dictionaries[ x ]->getName().c_str() ); QString dictName = QString::fromUtf8( dictionaries[ x ]->getName().c_str() );
QString dictId = QString::fromUtf8( dictionaries[ x ]->getId().c_str() ); QString dictId = QString::fromUtf8( dictionaries[ x ]->getId().c_str() );
QListWidgetItem * item = auto * item =
new QListWidgetItem( dictionaries[ x ]->getIcon(), dictName, ui.dictsList, QListWidgetItem::Type ); new QListWidgetItem( dictionaries[ x ]->getIcon(), dictName, ui.dictsList, QListWidgetItem::Type );
item->setData( Qt::UserRole, QVariant( dictId ) ); item->setData( Qt::UserRole, QVariant( dictId ) );
item->setToolTip( dictName ); item->setToolTip( dictName );
@ -2170,7 +2175,7 @@ void MainWindow::updateFoundInDictsList()
} }
} }
//if no item in dict List panel has been choose ,select first one. //if no item in dict List panel has been choosen ,select first one.
if ( ui.dictsList->count() > 0 && ui.dictsList->selectedItems().empty() ) { if ( ui.dictsList->count() > 0 && ui.dictsList->selectedItems().empty() ) {
ui.dictsList->setCurrentRow( 0 ); ui.dictsList->setCurrentRow( 0 );
} }
@ -2181,7 +2186,7 @@ void MainWindow::updateBackForwardButtons()
{ {
ArticleView * view = getCurrentArticleView(); ArticleView * view = getCurrentArticleView();
if ( view ) { if ( view != nullptr ) {
navBack->setEnabled( view->canGoBack() ); navBack->setEnabled( view->canGoBack() );
navForward->setEnabled( view->canGoForward() ); navForward->setEnabled( view->canGoForward() );
} }
@ -2190,7 +2195,12 @@ void MainWindow::updateBackForwardButtons()
void MainWindow::updatePronounceAvailability() void MainWindow::updatePronounceAvailability()
{ {
if ( ui.tabWidget->count() > 0 ) { if ( ui.tabWidget->count() > 0 ) {
getCurrentArticleView()->hasSound( [ this ]( bool has ) { ArticleView * pView = getCurrentArticleView();
if ( pView == nullptr ) {
return;
}
pView->hasSound( [ this ]( bool has ) {
navPronounce->setEnabled( has ); navPronounce->setEnabled( has );
} ); } );
} }
@ -3723,7 +3733,8 @@ void MainWindow::messageFromAnotherInstanceReceived( QString const & message )
ArticleView * MainWindow::getCurrentArticleView() ArticleView * MainWindow::getCurrentArticleView()
{ {
if ( QWidget * cw = ui.tabWidget->currentWidget() ) { if ( QWidget * cw = ui.tabWidget->currentWidget() ) {
return dynamic_cast< ArticleView * >( cw ); auto * pView = dynamic_cast< ArticleView * >( cw );
return pView;
} }
return nullptr; return nullptr;
} }
@ -4378,6 +4389,19 @@ void MainWindow::showFTSIndexingName( QString const & name )
} }
} }
void MainWindow::openWebsiteInNewTab( QString name, QString url )
{
// QString escaped = Utils::escapeAmps( name );
// auto * view = new ArticleView( this, articleNetMgr, audioPlayerFactory.player(), cfg );
// view->load( url );
// int index = cfg.preferences.newTabsOpenAfterCurrentOne ? ui.tabWidget->currentIndex() + 1 : ui.tabWidget->count();
// ui.tabWidget->insertTab( index, view, escaped );
// mruList.append( dynamic_cast< QWidget * >( view ) );
getCurrentArticleView()->addWebsiteTab( std::move( name ), url );
}
QString MainWindow::unescapeTabHeader( QString const & header ) QString MainWindow::unescapeTabHeader( QString const & header )
{ {
// Reset table header to original headword // Reset table header to original headword

View file

@ -290,6 +290,7 @@ private slots:
void openDictionaryFolder( QString const & id ); void openDictionaryFolder( QString const & id );
void showFTSIndexingName( QString const & name ); void showFTSIndexingName( QString const & name );
void openWebsiteInNewTab( QString name, QString url );
void handleAddToFavoritesButton(); void handleAddToFavoritesButton();

View file

@ -366,6 +366,7 @@ Preferences::Preferences( QWidget * parent, Config::Class & cfg_ ):
//Misc //Misc
ui.removeInvalidIndexOnExit->setChecked( p.removeInvalidIndexOnExit ); ui.removeInvalidIndexOnExit->setChecked( p.removeInvalidIndexOnExit );
ui.openWebsiteInNewTab->setChecked( p.openWebsiteInNewTab );
// Add-on styles // Add-on styles
ui.addonStylesLabel->setVisible( ui.addonStyles->count() > 1 ); ui.addonStylesLabel->setVisible( ui.addonStyles->count() > 1 );
@ -525,6 +526,7 @@ Config::Preferences Preferences::getPreferences()
p.clearNetworkCacheOnExit = ui.clearNetworkCacheOnExit->isChecked(); p.clearNetworkCacheOnExit = ui.clearNetworkCacheOnExit->isChecked();
p.removeInvalidIndexOnExit = ui.removeInvalidIndexOnExit->isChecked(); p.removeInvalidIndexOnExit = ui.removeInvalidIndexOnExit->isChecked();
p.openWebsiteInNewTab = ui.openWebsiteInNewTab->isChecked();
p.addonStyle = ui.addonStyles->getCurrentStyle(); p.addonStyle = ui.addonStyles->getCurrentStyle();

View file

@ -1938,6 +1938,13 @@ from Stardict, Babylon and GLS dictionaries</string>
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QCheckBox" name="openWebsiteInNewTab">
<property name="text">
<string>Open website dictionary in seperate tab</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>