mirror of
https://github.com/xiaoyifang/goldendict-ng.git
synced 2024-11-23 20:14:05 +00:00
Compare commits
18 commits
677745ba09
...
071e9389e1
Author | SHA1 | Date | |
---|---|---|---|
071e9389e1 | |||
453948155a | |||
8fc71c9586 | |||
6c63ba45b6 | |||
a9478cdc34 | |||
c0697ecdda | |||
701a4effb3 | |||
f9a3705942 | |||
2c91b78e13 | |||
c6387df392 | |||
43c982cf8a | |||
e07e730a09 | |||
ab214cfb05 | |||
c00239a1b1 | |||
469896bbf1 | |||
0d89c4ab56 | |||
9e99389cc3 | |||
6edfd15962 |
|
@ -142,4 +142,7 @@ StatementMacros:
|
||||||
- QT_REQUIRE_VERSION
|
- QT_REQUIRE_VERSION
|
||||||
UseCRLF: false
|
UseCRLF: false
|
||||||
UseTab: Never
|
UseTab: Never
|
||||||
|
---
|
||||||
|
Language: ObjC
|
||||||
|
BasedOnStyle: WebKit
|
||||||
...
|
...
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -45,4 +45,6 @@ signals:
|
||||||
void dictionaryClear( ActiveDictIds ad );
|
void dictionaryClear( ActiveDictIds ad );
|
||||||
|
|
||||||
void indexingDictionary( QString );
|
void indexingDictionary( QString );
|
||||||
|
|
||||||
|
void websiteDictionarySignal( QString, QString );
|
||||||
};
|
};
|
||||||
|
|
|
@ -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 );
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 )
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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( ©AsTextAction );
|
|
||||||
connect( ©AsTextAction, &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 ),
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in a new issue