Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/goldendict/goldendict int…
Browse files Browse the repository at this point in the history
…o add-avoid-auto-scrolling-out-of-top-dictionary-option
  • Loading branch information
vedgy committed Jun 7, 2022
2 parents 036df2b + 99982a1 commit f4f0ae8
Show file tree
Hide file tree
Showing 31 changed files with 642 additions and 161 deletions.
3 changes: 0 additions & 3 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +0,0 @@
[submodule "winlibs/lib/msvc"]
path = winlibs/lib/msvc
url = git://github.com/Tvangeste/goldendict-winlibs-prebuilt.git
140 changes: 89 additions & 51 deletions articleview.cc
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,24 @@
using std::map;
using std::list;

/// This class exposes only slim, minimal API to JavaScript clients in order to
/// reduce attack surface available to potentionally malicious external scripts.
class ArticleViewJsProxy: public QObject
{
Q_OBJECT
public:
/// Note: view becomes the parent of this proxy object.
explicit ArticleViewJsProxy( ArticleView & view ):
QObject( &view ), articleView( view )
{}

Q_INVOKABLE void onJsActiveArticleChanged( QString const & id )
{ articleView.onJsActiveArticleChanged( id ); }

private:
ArticleView & articleView;
};

/// AccentMarkHandler class
///
/// Remove accent marks from text
Expand Down Expand Up @@ -230,6 +248,7 @@ ArticleView::ArticleView( QWidget * parent, ArticleNetworkAccessManager & nm,
groups( groups_ ),
popupView( popupView_ ),
cfg( cfg_ ),
jsProxy( new ArticleViewJsProxy( *this ) ),
pasteAction( this ),
articleUpAction( this ),
articleDownAction( this ),
Expand Down Expand Up @@ -558,61 +577,64 @@ void ArticleView::loadFinished( bool )
qApp->sendEvent( ui.definition, &ev );
}

QVariant userDataVariant = ui.definition->history()->currentItem().userData();
// Expand collapsed article if only one loaded
ui.definition->page()->mainFrame()->evaluateJavaScript( "gdCheckArticlesNumber();" );

if ( userDataVariant.type() == QVariant::Map )
bool jumpedToCurrentArticle = false;
// Jump to current article after page reloading
if( !articleToJump.isEmpty() )
{
QMap< QString, QVariant > userData = userDataVariant.toMap();
jumpedToCurrentArticle = setCurrentArticle( articleToJump, true );
articleToJump.clear();
}

QString currentArticle = userData.value( "currentArticle" ).toString();
if( !jumpedToCurrentArticle )
{
QVariant userDataVariant = ui.definition->history()->currentItem().userData();

if ( currentArticle.size() )
if ( userDataVariant.type() == QVariant::Map )
{
// There's an active article saved, so set it to be active.
setCurrentArticle( currentArticle );
}
QMap< QString, QVariant > userData = userDataVariant.toMap();

double sx = 0, sy = 0;
QString currentArticle = userData.value( "currentArticle" ).toString();

if ( userData.value( "sx" ).type() == QVariant::Double )
sx = userData.value( "sx" ).toDouble();
if ( currentArticle.size() )
{
// There's an active article saved, so set it to be active.
setCurrentArticle( currentArticle );
}

if ( userData.value( "sy" ).type() == QVariant::Double )
sy = userData.value( "sy" ).toDouble();
double sx = 0, sy = 0;

if ( sx != 0 || sy != 0 )
{
// Restore scroll position
ui.definition->page()->mainFrame()->evaluateJavaScript(
QString( "window.scroll( %1, %2 );" ).arg( sx ).arg( sy ) );
if ( userData.value( "sx" ).type() == QVariant::Double )
sx = userData.value( "sx" ).toDouble();

if ( userData.value( "sy" ).type() == QVariant::Double )
sy = userData.value( "sy" ).toDouble();

if ( sx != 0 || sy != 0 )
{
// Restore scroll position
ui.definition->page()->mainFrame()->evaluateJavaScript(
QString( "window.scroll( %1, %2 );" ).arg( sx ).arg( sy ) );
}
}
}
else
if( cfg.preferences.autoScrollToTargetArticle )
{
QString const scrollTo = scrollToFromUrl( url );
if( !scrollTo.isEmpty() )
else
if( cfg.preferences.autoScrollToTargetArticle )
{
// There is no active article saved in history, but we have it as a parameter.
// setCurrentArticle will save it and scroll there.
setCurrentArticle( scrollTo, true );
QString const scrollTo = scrollToFromUrl( url );
if( !scrollTo.isEmpty() )
{
// There is no active article saved in history, but we have it as a parameter.
// setCurrentArticle will save it and scroll there.
setCurrentArticle( scrollTo, true );
}
}
}


ui.definition->unsetCursor();
//QApplication::restoreOverrideCursor();

// Expand collapsed article if only one loaded
ui.definition->page()->mainFrame()->evaluateJavaScript( QString( "gdCheckArticlesNumber();" ) );

// Jump to current article after page reloading
if( !articleToJump.isEmpty() )
{
setCurrentArticle( articleToJump, true );
articleToJump.clear();
}

#if QT_VERSION >= QT_VERSION_CHECK(4, 6, 0)
if( !Qt4x5::Url::queryItemValue( url, "gdanchor" ).isEmpty() )
{
Expand Down Expand Up @@ -744,13 +766,13 @@ void ArticleView::jumpToDictionary( QString const & id, bool force )
}
}

void ArticleView::setCurrentArticle( QString const & id, bool moveToIt )
bool ArticleView::setCurrentArticle( QString const & id, bool moveToIt )
{
if ( !isScrollTo( id ) )
return; // Incorrect id
return false; // Incorrect id

if ( !ui.definition->isVisible() )
return; // No action on background page, scrollIntoView there don't work
return false; // No action on background page, scrollIntoView there don't work

QString const dictionaryId = dictionaryIdFromScrollTo( id );
if ( getArticlesList().contains( dictionaryId ) )
Expand All @@ -766,6 +788,7 @@ void ArticleView::setCurrentArticle( QString const & id, bool moveToIt )
ui.definition->page()->mainFrame()->evaluateJavaScript(
QString( "gdMakeArticleActive( '%1' );" ).arg( dictionaryId ) );
}
return true;
}

void ArticleView::selectCurrentArticle()
Expand Down Expand Up @@ -1151,7 +1174,7 @@ void ArticleView::linkHovered ( const QString & link, const QString & , const QS

void ArticleView::attachToJavaScript()
{
ui.definition->page()->mainFrame()->addToJavaScriptWindowObject( QString( "articleview" ), this );
ui.definition->page()->mainFrame()->addToJavaScriptWindowObject( "articleview", jsProxy );
}

void ArticleView::linkClicked( QUrl const & url_ )
Expand Down Expand Up @@ -2672,17 +2695,9 @@ void ArticleView::highlightFTSResults()
if( ftsSearchMatchCase )
flags |= QWebPage::FindCaseSensitively;

#if QT_VERSION >= 0x040600
flags |= QWebPage::HighlightAllOccurrences;

for( int x = 0; x < allMatches.size(); x++ )
ui.definition->findText( allMatches.at( x ), flags );

flags &= ~QWebPage::HighlightAllOccurrences;
#endif

if( !allMatches.isEmpty() )
{
highlightAllFtsOccurences( flags );
if( ui.definition->findText( allMatches.at( 0 ), flags ) )
{
ui.definition->page()->currentFrame()->
Expand All @@ -2698,6 +2713,27 @@ void ArticleView::highlightFTSResults()
ftsSearchIsOpened = true;
}

void ArticleView::highlightAllFtsOccurences( QWebPage::FindFlags flags )
{
flags |= QWebPage::HighlightAllOccurrences;

// Usually allMatches contains mostly duplicates. Thus searching for each element of
// allMatches to highlight them takes a long time => collect unique elements into a
// set and search for them instead.
// Don't use QList::toSet() or QSet's range constructor because they reserve space
// for QList::size() elements, whereas the final QSet size is likely 1 or 2.
QSet< QString > uniqueMatches;
for( int x = 0; x < allMatches.size(); ++x )
{
QString const & match = allMatches.at( x );
// Consider words that differ only in case equal if the search is case-insensitive.
uniqueMatches.insert( ftsSearchMatchCase ? match : match.toLower() );
}

for( QSet< QString >::const_iterator it = uniqueMatches.constBegin(); it != uniqueMatches.constEnd(); ++it )
ui.definition->findText( *it, flags );
}

void ArticleView::performFtsFindOperation( bool backwards )
{
if( !ftsSearchIsOpened )
Expand Down Expand Up @@ -3102,3 +3138,5 @@ void ResourceToSaveHandler::downloadFinished()
deleteLater();
}
}

#include "articleview.moc"
8 changes: 6 additions & 2 deletions articleview.hh
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "groupcombobox.hh"
#include "ui_articleview.h"

class ArticleViewJsProxy;
class ResourceToSaveHandler;

/// A widget with the web view tailored to view and handle articles -- it
Expand All @@ -32,6 +33,8 @@ class ArticleView: public QFrame

Ui::ArticleView ui;

ArticleViewJsProxy * const jsProxy;

QAction pasteAction, articleUpAction, articleDownAction,
goBackAction, goForwardAction, selectCurrentArticleAction,
copyAsTextAction, jumpToTargetArticleAction, inspectAction;
Expand Down Expand Up @@ -61,8 +64,8 @@ class ArticleView: public QFrame
int ftsPosition;

void highlightFTSResults();
void highlightAllFtsOccurences( QWebPage::FindFlags flags );
void performFtsFindOperation( bool backwards );
void showFindButtons();

public:
/// The popupView flag influences contents of the context menus to be
Expand Down Expand Up @@ -321,7 +324,8 @@ private:

/// Sets the current article by executing a javascript code.
/// If moveToIt is true, it moves the focus to it as well.
void setCurrentArticle( QString const &, bool moveToIt = false );
/// Returns true in case of success, false otherwise.
bool setCurrentArticle( QString const &, bool moveToIt = false );

/// Checks if the given article in form of "gdfrom-xxx" is inside a "website"
/// frame.
Expand Down
55 changes: 54 additions & 1 deletion config.cc
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,31 @@
#include <QDesktopServices>
#endif

#if defined( HAVE_X11 ) && QT_VERSION >= QT_VERSION_CHECK( 5, 0, 0 )
// Whether XDG Base Directory specification might be followed.
// Only Qt5 builds are supported, as Qt4 doesn't provide all functions needed
// to get XDG Base Directory compliant locations.
#define XDG_BASE_DIRECTORY_COMPLIANCE
#endif

namespace Config {

namespace
{
#ifdef XDG_BASE_DIRECTORY_COMPLIANCE
const char xdgSubdirName[] = "goldendict";

QDir getDataDir()
{
QDir dir = QStandardPaths::writableLocation( QStandardPaths::GenericDataLocation );
dir.mkpath( xdgSubdirName );
if ( !dir.cd( xdgSubdirName ) )
throw exCantUseDataDir();

return dir;
}
#endif

QString portableHomeDirPath()
{
return QCoreApplication::applicationDirPath() + "/portable";
Expand All @@ -52,6 +73,14 @@ namespace
result = QDir::fromNativeSeparators( QString::fromWCharArray( _wgetenv( L"APPDATA" ) ) );
#else
char const * pathInHome = ".goldendict";
#ifdef XDG_BASE_DIRECTORY_COMPLIANCE
// check if an old config dir is present, otherwise use standards-compliant location
if ( !result.exists( pathInHome ) )
{
result.setPath( QStandardPaths::writableLocation( QStandardPaths::ConfigLocation ) );
pathInHome = xdgSubdirName;
}
#endif
#endif

result.mkpath( pathInHome );
Expand Down Expand Up @@ -2214,6 +2243,15 @@ QString getIndexDir() THROW_SPEC( exError )
{
QDir result = getHomeDir();

#ifdef XDG_BASE_DIRECTORY_COMPLIANCE
// store index in XDG_CACHE_HOME in non-portable version
// *and* when an old index directory in GoldenDict home doesn't exist
if ( !isPortableVersion() && !result.exists( "index" ) )
{
result.setPath( getCacheDir() );
}
#endif

result.mkpath( "index" );

if ( !result.cd( "index" ) )
Expand All @@ -2229,7 +2267,18 @@ QString getPidFileName() THROW_SPEC( exError )

QString getHistoryFileName() THROW_SPEC( exError )
{
return getHomeDir().filePath( "history" );
QString homeHistoryPath = getHomeDir().filePath( "history" );

#ifdef XDG_BASE_DIRECTORY_COMPLIANCE
// use separate data dir for history, if it is not already stored alongside
// configuration in non-portable mode
if ( !isPortableVersion() && !QFile::exists( homeHistoryPath ) )
{
return getDataDir().filePath( "history" );
}
#endif

return homeHistoryPath;
}

QString getFavoritiesFileName() THROW_SPEC( exError )
Expand Down Expand Up @@ -2347,7 +2396,11 @@ QString getCacheDir() throw()
{
return isPortableVersion() ? portableHomeDirPath() + "/cache"
#if QT_VERSION >= QT_VERSION_CHECK( 5, 0, 0 )
#ifdef HAVE_X11
: QStandardPaths::writableLocation( QStandardPaths::GenericCacheLocation ) + "/goldendict";
#else
: QStandardPaths::writableLocation( QStandardPaths::CacheLocation );
#endif
#else
: QDesktopServices::storageLocation( QDesktopServices::CacheLocation );
#endif
Expand Down
1 change: 1 addition & 0 deletions config.hh
Original file line number Diff line number Diff line change
Expand Up @@ -759,6 +759,7 @@ private:
};

DEF_EX( exError, "Error with the program's configuration", std::exception )
DEF_EX( exCantUseDataDir, "Can't use XDG_DATA_HOME directory to store GoldenDict data", exError )
DEF_EX( exCantUseHomeDir, "Can't use home directory to store GoldenDict preferences", exError )
DEF_EX( exCantUseIndexDir, "Can't use index directory to store GoldenDict index files", exError )
DEF_EX( exCantReadConfigFile, "Can't read the configuration file", exError )
Expand Down
10 changes: 6 additions & 4 deletions dictzip.c
Original file line number Diff line number Diff line change
Expand Up @@ -349,11 +349,13 @@ static enum DZ_ERRORS dict_read_header( const char *filename,
}
header->chunks = xmalloc( sizeof( header->chunks[0] )
* header->chunkCount );
if( header->chunks == 0 ) {
return DZ_ERR_NOMEMORY;
}
if( header->chunks == 0 )
{
fclose( str );
return DZ_ERR_NOMEMORY;
}

for (i = 0; i < header->chunkCount; i++) {
for (i = 0; i < header->chunkCount; i++) {
header->chunks[i] = getc( str ) << 0;
header->chunks[i] |= getc( str ) << 8;
}
Expand Down
4 changes: 4 additions & 0 deletions dsl_details.cc
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,11 @@ ArticleDom::ArticleDom( wstring const & str, string const & dictName,
if( ch == L'\n' )
break;
if( ch != L'\r' )
{
if( escaped && ( ch == L'(' || ch == ')' ) )
linkTo.push_back( L'\\' );
linkTo.push_back( ch );
}
}
linkTo = Folding::trimWhitespace( linkTo );

Expand Down

0 comments on commit f4f0ae8

Please sign in to comment.