Merge pull request #35 from ngn999/bugfix/Macbook_scroll_issue

disable macOS trackpad zoom; fix a deadlock
This commit is contained in:
xiaoyifang 2022-03-31 00:10:37 +08:00 committed by GitHub
commit 228d7001e5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 226 additions and 236 deletions

View file

@ -2,6 +2,7 @@
* Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */
#include "articleview.hh"
#include "QtCore/qvariant.h"
#include "folding.hh"
#include "fulltextsearch.hh"
#include "gddebug.hh"
@ -179,27 +180,6 @@ public:
void ArticleView::emitJavascriptFinished(){
emit notifyJavascriptFinished();
}
//in webengine,javascript has been executed in async mode ,for simpility,use EventLoop to simulate sync execution.
//a better solution would be to replace it with callback etc.
QString ArticleView::runJavaScriptSync(QWebEnginePage* frame, const QString& variable)
{
qDebug("%s", QString("runJavascriptScriptSync:%1").arg(variable).toLatin1().data());
QString result;
QSharedPointer<QEventLoop> loop = QSharedPointer<QEventLoop>(new QEventLoop());
QTimer::singleShot(1000, loop.data(), &QEventLoop::quit);
frame->runJavaScript(variable, [=,&result](const QVariant &v)
{
if(loop->isRunning()){
if(v.isValid())
result = v.toString();
loop->quit();
}
});
loop->exec();
return result;
}
namespace {
@ -700,13 +680,13 @@ void ArticleView::selectCurrentArticle()
QString( "gdSelectArticle( '%1' );var elem=document.getElementById('%2'); if(elem!=undefined){elem.scrollIntoView(true);}" ).arg( getActiveArticleId() ,getCurrentArticle()) );
}
bool ArticleView::isFramedArticle( QString const & ca )
void ArticleView::isFramedArticle( QString const & ca, const std::function< void( bool ) > & callback )
{
if ( ca.isEmpty() )
return false;
if( ca.isEmpty() )
callback( false );
QString result=runJavaScriptSync( ui.definition->page(), QString( "!!document.getElementById('gdexpandframe-%1');" ).arg( ca.mid( 7 ) ) );
return result=="true";
ui.definition->page()->runJavaScript( QString( "!!document.getElementById('gdexpandframe-%1');" ).arg( ca.mid( 7 ) ),
[ callback ]( const QVariant & res ) { callback( res.toBool() ); } );
}
bool ArticleView::isExternalLink( QUrl const & url )
@ -718,39 +698,41 @@ void ArticleView::tryMangleWebsiteClickedUrl( QUrl & url, Contexts & contexts )
{
// Don't try mangling audio urls, even if they are from the framed websites
if( ( url.scheme() == "http" || url.scheme() == "https" )
&& ! Dictionary::WebMultimediaDownload::isAudioUrl( url ) )
if( ( url.scheme() == "http" || url.scheme() == "https" ) && !Dictionary::WebMultimediaDownload::isAudioUrl( url ) )
{
// Maybe a link inside a website was clicked?
QString ca = getCurrentArticle();
isFramedArticle( ca,
[ =, &url ]( bool framed )
{
if( framed )
{
// QVariant result = runJavaScriptSync( ui.definition->page(), "gdLastUrlText" );
QVariant result;
if ( isFramedArticle( ca ) )
{
//QVariant result = runJavaScriptSync( ui.definition->page(), "gdLastUrlText" );
QVariant result ;
if( result.type() == QVariant::String )
{
// Looks this way
contexts[ dictionaryIdFromScrollTo( ca ) ] = QString::fromLatin1( url.toEncoded() );
if ( result.type() == QVariant::String )
{
// Looks this way
contexts[ dictionaryIdFromScrollTo( ca ) ] = QString::fromLatin1( url.toEncoded() );
QUrl target;
QUrl target;
QString queryWord = result.toString();
QString queryWord = result.toString();
// Empty requests are treated as no request, so we work this around by
// adding a space.
if( queryWord.isEmpty() )
queryWord = " ";
// Empty requests are treated as no request, so we work this around by
// adding a space.
if ( queryWord.isEmpty() )
queryWord = " ";
target.setScheme( "gdlookup" );
target.setHost( "localhost" );
target.setPath( "/" + queryWord );
target.setScheme( "gdlookup" );
target.setHost( "localhost" );
target.setPath( "/" + queryWord );
url = target;
}
}
url = target;
}
}
} );
}
}
@ -829,6 +811,17 @@ bool ArticleView::handleF3( QObject * /*obj*/, QEvent * ev )
bool ArticleView::eventFilter( QObject * obj, QEvent * ev )
{
#ifdef Q_OS_MAC
if( ev->type() == QEvent::NativeGesture )
{
qDebug() << "it's a Native Gesture!";
// handle Qt::ZoomNativeGesture Qt::SmartZoomNativeGesture here
// ignore swipe left/right.
// QWebEngine can handle Qt::SmartZoomNativeGesture.
}
#else
if( ev->type() == QEvent::Gesture )
{
Gestures::GestureResult result;
@ -873,6 +866,7 @@ bool ArticleView::eventFilter( QObject * obj, QEvent * ev )
return handled;
}
#endif
if( ev->type() == QEvent::MouseMove )
{
@ -1644,12 +1638,16 @@ void ArticleView::forward()
ui.definition->forward();
}
bool ArticleView::hasSound()
void ArticleView::hasSound( const std::function< void( bool ) > & callback )
{
QVariant v = runJavaScriptSync( ui.definition->page(),"gdAudioLinks.first" );
if ( v.type() == QVariant::String )
return !v.toString().isEmpty();
return false;
ui.definition->page()->runJavaScript( "gdAudioLinks.first",
[ callback ]( const QVariant & v )
{
bool has = false;
if( v.type() == QVariant::String )
has = !v.toString().isEmpty();
callback( has );
} );
}
//use webengine javascript to playsound
@ -1661,26 +1659,24 @@ void ArticleView::playSound()
" } "
" return link;})(); ";
QString soundScript = runJavaScriptSync(ui.definition->page(), variable);
if (!soundScript.isEmpty())
openLink(QUrl::fromEncoded(soundScript.toUtf8()), ui.definition->url());
ui.definition->page()->runJavaScript(variable,[this](const QVariant & result){
if (result.type() == QVariant::String) {
QString soundScript = result.toString();
if (!soundScript.isEmpty())
openLink(QUrl::fromEncoded(soundScript.toUtf8()), ui.definition->url());
}
});
}
// use eventloop to turn the async callback to sync execution.
QString ArticleView::toHtml()
void ArticleView::toHtml( const std::function< void( QString & ) > & callback )
{
QString result;
QSharedPointer<QEventLoop> loop = QSharedPointer<QEventLoop>(new QEventLoop());
QTimer::singleShot(1000, loop.data(), &QEventLoop::quit);
ui.definition->page()->toHtml([loop, &result](const QString &content) {
if (loop->isRunning()) {
result = content;
loop->quit();
}
});
loop->exec();
return result;
ui.definition->page()->toHtml(
[ = ]( const QString & content )
{
QString html = content;
callback( html );
} );
}
void ArticleView::setHtml(const QString& content,const QUrl& baseUrl){
@ -2507,10 +2503,9 @@ void ArticleView::highlightFTSResults()
else
regexp.setPattern( regString );
QRegularExpression::PatternOptions patternOptions = QRegularExpression::DotMatchesEverythingOption
| QRegularExpression::UseUnicodePropertiesOption
| QRegularExpression::MultilineOption
| QRegularExpression::InvertedGreedinessOption;
QRegularExpression::PatternOptions patternOptions =
QRegularExpression::DotMatchesEverythingOption | QRegularExpression::UseUnicodePropertiesOption |
QRegularExpression::MultilineOption | QRegularExpression::InvertedGreedinessOption;
if( !Utils::Url::hasQueryItem( url, "matchcase" ) )
patternOptions |= QRegularExpression::CaseInsensitiveOption;
regexp.setPatternOptions( patternOptions );
@ -2518,84 +2513,69 @@ void ArticleView::highlightFTSResults()
if( regexp.pattern().isEmpty() || !regexp.isValid() )
return;
sptr< AccentMarkHandler > marksHandler = ignoreDiacritics ?
new DiacriticsHandler : new AccentMarkHandler;
sptr< AccentMarkHandler > marksHandler = ignoreDiacritics ? new DiacriticsHandler : new AccentMarkHandler;
// Clear any current selection
if ( ui.definition->selectedText().size() )
if( ui.definition->selectedText().size() )
{
ui.definition->page()->
runJavaScript( "window.getSelection().removeAllRanges();_=0;" );
ui.definition->page()->runJavaScript( "window.getSelection().removeAllRanges();_=0;" );
}
QString pageText = getWebPageTextSync(ui.definition->page());
marksHandler->setText( pageText );
QRegularExpressionMatchIterator it = regexp.globalMatch( marksHandler->normalizedText() );
while( it.hasNext() )
{
QRegularExpressionMatch match = it.next();
// Mirror pos and matched length to original string
int pos = match.capturedStart();
int spos = marksHandler->mirrorPosition( pos );
int matched = marksHandler->mirrorPosition( pos + match.capturedLength() ) - spos;
// Add mark pos (if presented)
while( spos + matched < pageText.length()
&& pageText[ spos + matched ].category() == QChar::Mark_NonSpacing )
matched++;
if( matched > FTS::MaxMatchLengthForHighlightResults )
ui.definition->page()->toPlainText(
[ & ]( const QString pageText )
{
gdWarning( "ArticleView::highlightFTSResults(): Too long match - skipped (matched length %i, allowed %i)",
match.capturedLength(), FTS::MaxMatchLengthForHighlightResults );
}
else
allMatches.append( pageText.mid( spos, matched ) );
}
marksHandler->setText( pageText );
ftsSearchMatchCase = Utils::Url::hasQueryItem( url, "matchcase" );
QRegularExpressionMatchIterator it = regexp.globalMatch( marksHandler->normalizedText() );
while( it.hasNext() )
{
QRegularExpressionMatch match = it.next();
QWebEnginePage::FindFlags flags ( 0 );
// Mirror pos and matched length to original string
int pos = match.capturedStart();
int spos = marksHandler->mirrorPosition( pos );
int matched = marksHandler->mirrorPosition( pos + match.capturedLength() ) - spos;
if( ftsSearchMatchCase )
flags |= QWebEnginePage::FindCaseSensitively;
// Add mark pos (if presented)
while( spos + matched < pageText.length() && pageText[ spos + matched ].category() == QChar::Mark_NonSpacing )
matched++;
for( int x = 0; x < allMatches.size(); x++ )
ui.definition->findText( allMatches.at( x ), flags );
if( matched > FTS::MaxMatchLengthForHighlightResults )
{
gdWarning( "ArticleView::highlightFTSResults(): Too long match - skipped (matched length %i, allowed %i)",
match.capturedLength(),
FTS::MaxMatchLengthForHighlightResults );
}
else
allMatches.append( pageText.mid( spos, matched ) );
}
if( !allMatches.isEmpty() )
{
ui.definition->findText( allMatches.at( 0 ), flags );
//if( ui.definition->findText( allMatches.at( 0 ), flags ) )
{
ui.definition->page()->
runJavaScript( QString( "%1=window.getSelection().getRangeAt(0);_=0;" )
.arg( rangeVarName ) );
}
}
ftsSearchMatchCase = Utils::Url::hasQueryItem( url, "matchcase" );
ui.ftsSearchFrame->show();
ui.ftsSearchPrevious->setEnabled( false );
ui.ftsSearchNext->setEnabled( allMatches.size()>1 );
QWebEnginePage::FindFlags flags( 0 );
ftsSearchIsOpened = true;
}
if( ftsSearchMatchCase )
flags |= QWebEnginePage::FindCaseSensitively;
QString ArticleView::getWebPageTextSync(QWebEnginePage * page){
QString planText;
QSharedPointer<QEventLoop> loop = QSharedPointer<QEventLoop>(new QEventLoop());
QTimer::singleShot(1000, loop.data(), &QEventLoop::quit);
page->toPlainText([&](const QString &result)
{
if(loop->isRunning()){
planText = result;
loop->quit();
} });
loop->exec();
return planText;
for( int x = 0; x < allMatches.size(); x++ )
ui.definition->findText( allMatches.at( x ), flags );
if( !allMatches.isEmpty() )
{
ui.definition->findText( allMatches.at( 0 ), flags );
// if( ui.definition->findText( allMatches.at( 0 ), flags ) )
{
ui.definition->page()->runJavaScript(
QString( "%1=window.getSelection().getRangeAt(0);_=0;" ).arg( rangeVarName ) );
}
}
ui.ftsSearchFrame->show();
ui.ftsSearchPrevious->setEnabled( false );
ui.ftsSearchNext->setEnabled( allMatches.size() > 1 );
ftsSearchIsOpened = true;
} );
}
void ArticleView::setActiveDictIds(ActiveDictIds ad) {

View file

@ -112,8 +112,6 @@ public:
/// Returns "gdfrom-" + dictionaryId.
static QString scrollToFromDictionaryId( QString const & dictionaryId );
QString runJavaScriptSync(QWebEnginePage* frame, const QString& variable);
void emitJavascriptFinished();
/// Shows the definition of the given word with the given group.
@ -158,8 +156,6 @@ public:
/// Called when preference changes
void setSelectionBySingleClick( bool set );
QString getWebPageTextSync(QWebEnginePage * page);
public slots:
/// Goes back in history
@ -179,7 +175,7 @@ public:
{ ui.definition->reload(); }
/// Returns true if there's an audio reference on the page, false otherwise.
bool hasSound();
void hasSound( const std::function< void( bool has ) > & callback );
/// Plays the first audio reference on the page, if any.
void playSound();
@ -195,7 +191,7 @@ public:
}
/// Returns current article's text in .html format
QString toHtml();
void toHtml( const std::function< void( QString & ) > & callback );
void setHtml(const QString& content, const QUrl& baseUrl);
void setContent(const QByteArray &data, const QString &mimeType = QString(), const QUrl &baseUrl = QUrl());
@ -369,7 +365,7 @@ private:
/// Checks if the given article in form of "gdfrom-xxx" is inside a "website"
/// frame.
bool isFramedArticle( QString const & );
void isFramedArticle( QString const & article, const std::function< void( bool framed ) > & callback );
/// Checks if the given link is to be opened externally, as opposed to opening
/// it in-place.

View file

@ -174,8 +174,9 @@ MainWindow::MainWindow( Config::Class & cfg_ ):
articleMaker.setCollapseParameters( cfg.preferences.collapseBigArticles, cfg.preferences.articleSizeLimit );
// Set own gesture recognizers
#ifndef Q_OS_MAC
Gestures::registerRecognizers();
#endif
// use our own, custom statusbar
setStatusBar(0);
mainStatusBar = new MainStatusBar( this );
@ -2033,10 +2034,14 @@ void MainWindow::updateBackForwardButtons()
void MainWindow::updatePronounceAvailability()
{
bool pronounceEnabled = ui.tabWidget->count() > 0 &&
getCurrentArticleView()->hasSound();
navPronounce->setEnabled( pronounceEnabled );
if (ui.tabWidget->count() > 0) {
getCurrentArticleView()->hasSound([this](bool has) {
navPronounce->setEnabled( has );
});
}
else {
navPronounce->setEnabled( false );
}
}
void MainWindow::editDictionaries( unsigned editDictionaryGroup )
@ -3405,7 +3410,7 @@ static void filterAndCollectResources( QString & html, QRegExp & rx, const QStri
void MainWindow::on_saveArticle_triggered()
{
ArticleView *view = getCurrentArticleView();
ArticleView * view = getCurrentArticleView();
QString fileName = view->getTitle().simplified();
@ -3416,12 +3421,12 @@ void MainWindow::on_saveArticle_triggered()
fileName += ".html";
QString savePath;
if ( cfg.articleSavePath.isEmpty() )
if( cfg.articleSavePath.isEmpty() )
savePath = QDir::homePath();
else
{
savePath = QDir::fromNativeSeparators( cfg.articleSavePath );
if ( !QDir( savePath ).exists() )
if( !QDir( savePath ).exists() )
savePath = QDir::homePath();
}
@ -3432,97 +3437,102 @@ void MainWindow::on_saveArticle_triggered()
filters.push_back( tr( "Article, HTML Only (*.html)" ) );
fileName = savePath + "/" + fileName;
fileName = QFileDialog::getSaveFileName( this, tr( "Save Article As" ),
fileName = QFileDialog::getSaveFileName( this,
tr( "Save Article As" ),
fileName,
filters.join( ";;" ),
&selectedFilter, options );
&selectedFilter,
options );
bool complete = ( selectedFilter == filters[ 0 ] );
if ( !fileName.isEmpty() )
{
if( fileName.isEmpty() )
return;
QFile file( fileName );
if ( !file.open( QIODevice::WriteOnly ) )
view->toHtml(
[ = ]( QString & html ) mutable
{
QMessageBox::critical( this, tr( "Error" ),
tr( "Can't save article: %1" ).arg( file.errorString() ) );
}
else
{
QString html = view->toHtml();
QFileInfo fi( fileName );
cfg.articleSavePath = QDir::toNativeSeparators( fi.absoluteDir().absolutePath() );
// Convert internal links
QRegExp rx3( "href=\"(bword:|gdlookup://localhost/)([^\"]+)\"" );
int pos = 0;
QRegularExpression anchorRx( "(g[0-9a-f]{32}_)[0-9a-f]+_" );
while ( ( pos = rx3.indexIn( html, pos ) ) != -1 )
QFile file( fileName );
if( !file.open( QIODevice::WriteOnly ) )
{
QString name = QUrl::fromPercentEncoding( rx3.cap( 2 ).simplified().toLatin1() );
QString anchor;
name.replace( "?gdanchor=", "#" );
int n = name.indexOf( '#' );
if( n > 0 )
{
anchor = name.mid( n );
name.truncate( n );
anchor.replace( anchorRx, "\\1" ); // MDict anchors
}
name.replace( rxName, "_" );
name = QString( "href=\"" ) + QUrl::toPercentEncoding( name ) + ".html" + anchor + "\"";
html.replace( pos, rx3.cap().length(), name );
pos += name.length();
}
// MDict anchors
QRegularExpression anchorLinkRe( "(<\\s*a\\s+[^>]*\\b(?:name|id)\\b\\s*=\\s*[\"']*g[0-9a-f]{32}_)([0-9a-f]+_)(?=[^\"'])", QRegularExpression::PatternOption::CaseInsensitiveOption );
html.replace( anchorLinkRe, "\\1" );
if ( complete )
{
QString folder = fi.absoluteDir().absolutePath() + "/" + fi.baseName() + "_files";
QRegExp rx1( "\"((?:bres|gico|gdau|qrcx|gdvideo)://[^\"]+)\"" );
QRegExp rx2( "'((?:bres|gico|gdau|qrcx|gdvideo)://[^']+)'" );
set< QByteArray > resourceIncluded;
vector< pair< QUrl, QString > > downloadResources;
filterAndCollectResources( html, rx1, "\"", folder, resourceIncluded, downloadResources );
filterAndCollectResources( html, rx2, "'", folder, resourceIncluded, downloadResources );
ArticleSaveProgressDialog * progressDialog = new ArticleSaveProgressDialog( this );
// reserve '1' for saving main html file
int maxVal = 1;
// Pull and save resources to files
for ( vector< pair< QUrl, QString > >::const_iterator i = downloadResources.begin();
i != downloadResources.end(); ++i )
{
ResourceToSaveHandler * handler = view->saveResource( i->first, i->second );
if( !handler->isEmpty() )
{
maxVal += 1;
connect( handler, SIGNAL( done() ), progressDialog, SLOT( perform() ) );
}
}
progressDialog->setLabelText( tr("Saving article...") );
progressDialog->setRange( 0, maxVal );
progressDialog->setValue( 0 );
progressDialog->show();
file.write( html.toUtf8() );
progressDialog->setValue( 1 );
QMessageBox::critical( this, tr( "Error" ), tr( "Can't save article: %1" ).arg( file.errorString() ) );
}
else
{
file.write( html.toUtf8() );
QFileInfo fi( fileName );
cfg.articleSavePath = QDir::toNativeSeparators( fi.absoluteDir().absolutePath() );
// Convert internal links
QRegExp rx3( "href=\"(bword:|gdlookup://localhost/)([^\"]+)\"" );
int pos = 0;
QRegularExpression anchorRx( "(g[0-9a-f]{32}_)[0-9a-f]+_" );
while( ( pos = rx3.indexIn( html, pos ) ) != -1 )
{
QString name = QUrl::fromPercentEncoding( rx3.cap( 2 ).simplified().toLatin1() );
QString anchor;
name.replace( "?gdanchor=", "#" );
int n = name.indexOf( '#' );
if( n > 0 )
{
anchor = name.mid( n );
name.truncate( n );
anchor.replace( anchorRx, "\\1" ); // MDict anchors
}
name.replace( rxName, "_" );
name = QString( "href=\"" ) + QUrl::toPercentEncoding( name ) + ".html" + anchor + "\"";
html.replace( pos, rx3.cap().length(), name );
pos += name.length();
}
// MDict anchors
QRegularExpression anchorLinkRe(
"(<\\s*a\\s+[^>]*\\b(?:name|id)\\b\\s*=\\s*[\"']*g[0-9a-f]{32}_)([0-9a-f]+_)(?=[^\"'])",
QRegularExpression::PatternOption::CaseInsensitiveOption );
html.replace( anchorLinkRe, "\\1" );
if( complete )
{
QString folder = fi.absoluteDir().absolutePath() + "/" + fi.baseName() + "_files";
QRegExp rx1( "\"((?:bres|gico|gdau|qrcx|gdvideo)://[^\"]+)\"" );
QRegExp rx2( "'((?:bres|gico|gdau|qrcx|gdvideo)://[^']+)'" );
set< QByteArray > resourceIncluded;
vector< pair< QUrl, QString > > downloadResources;
filterAndCollectResources( html, rx1, "\"", folder, resourceIncluded, downloadResources );
filterAndCollectResources( html, rx2, "'", folder, resourceIncluded, downloadResources );
ArticleSaveProgressDialog * progressDialog = new ArticleSaveProgressDialog( this );
// reserve '1' for saving main html file
int maxVal = 1;
// Pull and save resources to files
for( vector< pair< QUrl, QString > >::const_iterator i = downloadResources.begin();
i != downloadResources.end();
++i )
{
ResourceToSaveHandler * handler = view->saveResource( i->first, i->second );
if( !handler->isEmpty() )
{
maxVal += 1;
connect( handler, SIGNAL( done() ), progressDialog, SLOT( perform() ) );
}
}
progressDialog->setLabelText( tr( "Saving article..." ) );
progressDialog->setRange( 0, maxVal );
progressDialog->setValue( 0 );
progressDialog->show();
file.write( html.toUtf8() );
progressDialog->perform();
}
else
{
file.write( html.toUtf8() );
}
}
}
}
} );
}
void MainWindow::on_rescanFiles_triggered()

View file

@ -4,16 +4,16 @@
#ifndef __MUTEX_HH_INCLUDED__
#define __MUTEX_HH_INCLUDED__
#include <QMutex>
#include <QRecursiveMutex>
/// This provides a mutex class. As you can see, it's just a Qt one, but it
/// does provide the Lock class which doesn't seem to exist in Qt, and it does
/// provide some abstraction for dictionaries in case they are to be ported
/// away from Qt.
class Mutex: public QMutex
class Mutex : public QRecursiveMutex
{
public:
Mutex() : QMutex( )
Mutex() : QRecursiveMutex()
{}
~Mutex()
{}

View file

@ -1141,7 +1141,11 @@ void ScanPopup::altModePoll()
void ScanPopup::pageLoaded( ArticleView * )
{
ui.pronounceButton->setVisible( definition->hasSound() );
definition->hasSound([this](bool has){
ui.pronounceButton->setVisible( has );
});
updateBackForwardButtons();