From d6696b0800760e05a999c30ff539672c010793af Mon Sep 17 00:00:00 2001 From: Igor Kushnir Date: Sat, 5 Nov 2022 10:52:19 +0200 Subject: [PATCH 1/7] Expand unclosed tag DSL warning message The dictionary name, article and the tag name help to locate the issue. GD_FDPRINTF() => gdWarning() to improve flexibility and user options. --- dsl_details.cc | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/dsl_details.cc b/dsl_details.cc index 682ef0a9..f4f61733 100644 --- a/dsl_details.cc +++ b/dsl_details.cc @@ -630,7 +630,19 @@ ArticleDom::ArticleDom( wstring const & str, string const & dictName, if ( stack.size() ) { - GD_FDPRINTF( stderr, "Warning: %u tags were unclosed.\n", (unsigned) stack.size() ); + unsigned const unclosedTagCount = stack.size(); + QByteArray const firstTagName = gd::toQString( stack.front()->tagName ).toUtf8(); + if( dictName.empty() ) + { + gdWarning( "Warning: %u tag(s) were unclosed, first tag name \"%s\".\n", + unclosedTagCount, firstTagName.constData() ); + } + else + { + gdWarning( "Warning: %u tag(s) were unclosed in \"%s\", article \"%s\", first tag name \"%s\".\n", + unclosedTagCount, dictName.c_str(), gd::toQString( headword ).toUtf8().constData(), + firstTagName.constData() ); + } } } From 7c0c586418d743a7f5c37b4bd1120200a78da4f8 Mon Sep 17 00:00:00 2001 From: Igor Kushnir Date: Sat, 5 Nov 2022 11:09:14 +0200 Subject: [PATCH 2/7] Extract is_mN() and isAnyM() --- dsl_details.cc | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/dsl_details.cc b/dsl_details.cc index f4f61733..9ddf9c9a 100644 --- a/dsl_details.cc +++ b/dsl_details.cc @@ -162,13 +162,26 @@ wstring ArticleDom::Node::renderAsText( bool stripTrsTag ) const return result; } -// Returns true if src == 'm' and dest is 'mX', where X is a digit -static inline bool checkM( wstring const & dest, wstring const & src ) +namespace { + +/// @return true if @p tagName equals "mN" where N is a digit +bool is_mN( wstring const & tagName ) { - return ( src == GD_NATIVE_TO_WS( L"m" ) && dest.size() == 2 && - dest[ 0 ] == L'm' && iswdigit( dest[ 1 ] ) ); + return tagName.size() == 2 && tagName[ 0 ] == L'm' && iswdigit( tagName[ 1 ] ); } +bool isAnyM( wstring const & tagName ) +{ + return tagName == GD_NATIVE_TO_WS( L"m" ) || is_mN( tagName ); +} + +bool checkM( wstring const & dest, wstring const & src ) +{ + return src == GD_NATIVE_TO_WS( L"m" ) && is_mN( dest ); +} + +} // unnamed namespace + ArticleDom::ArticleDom( wstring const & str, string const & dictName, wstring const & headword_): root( Node::Tag(), wstring(), wstring() ), stringPos( str.c_str() ), @@ -374,8 +387,7 @@ ArticleDom::ArticleDom( wstring const & str, string const & dictName, if ( !isClosing ) { - if ( name == GD_NATIVE_TO_WS( L"m" ) || - ( name.size() == 2 && name[ 0 ] == L'm' && iswdigit( name[ 1 ] ) ) ) + if( isAnyM( name ) ) { // Opening an 'mX' or 'm' tag closes any previous 'm' tag closeTag( GD_NATIVE_TO_WS( L"m" ), stack, false ); @@ -652,7 +664,7 @@ void ArticleDom::openTag( wstring const & name, { list< Node > nodesToReopen; - if( name == GD_NATIVE_TO_WS( L"m" ) || checkM( name, GD_NATIVE_TO_WS( L"m" ) ) ) + if( isAnyM( name ) ) { // All tags above [m] tag will be closed and reopened after // to avoid break this tag by closing some other tag. From 38f5fa6c908621b88e0a2ec329384e91704ee22c Mon Sep 17 00:00:00 2001 From: Igor Kushnir Date: Sat, 5 Nov 2022 11:51:21 +0200 Subject: [PATCH 3/7] Don't warn about unclosed DSL [mN] tags According to DSL documentation, closing the [mN] tags is optional: unclosed [mN] tags affect formatting until the end of a card. As many dictionaries don't close the [mN] tags, GoldenDict printed multiple unclosed-tag warnings during each word lookup. --- dsl_details.cc | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/dsl_details.cc b/dsl_details.cc index 9ddf9c9a..ac0b1d91 100644 --- a/dsl_details.cc +++ b/dsl_details.cc @@ -13,6 +13,8 @@ #include #include +#include + namespace Dsl { namespace Details { @@ -180,6 +182,17 @@ bool checkM( wstring const & dest, wstring const & src ) return src == GD_NATIVE_TO_WS( L"m" ) && is_mN( dest ); } +/// Closing the [mN] tags is optional. Quote from https://documentation.help/ABBYY-Lingvo8/paragraph_form.htm: +/// Any paragraph from this tag until the end of card or until system meets an «[/m]» (margin shift toggle off) tag +struct MustTagBeClosed +{ + bool operator()( ArticleDom::Node const * tag ) const + { + Q_ASSERT( tag->isTag ); + return !isAnyM( tag->tagName ); + } +}; + } // unnamed namespace ArticleDom::ArticleDom( wstring const & str, string const & dictName, @@ -642,8 +655,13 @@ ArticleDom::ArticleDom( wstring const & str, string const & dictName, if ( stack.size() ) { - unsigned const unclosedTagCount = stack.size(); - QByteArray const firstTagName = gd::toQString( stack.front()->tagName ).toUtf8(); + list< Node * >::iterator it = std::find_if( stack.begin(), stack.end(), MustTagBeClosed() ); + if( it == stack.end() ) + return; // no unclosed tags that must be closed => nothing to warn about + QByteArray const firstTagName = gd::toQString( ( *it )->tagName ).toUtf8(); + ++it; + unsigned const unclosedTagCount = 1 + std::count_if( it, stack.end(), MustTagBeClosed() ); + if( dictName.empty() ) { gdWarning( "Warning: %u tag(s) were unclosed, first tag name \"%s\".\n", From fdcd6a377351a2ac7bb5519df279868adf601fa0 Mon Sep 17 00:00:00 2001 From: Igor Kushnir Date: Sat, 5 Nov 2022 16:10:24 +0200 Subject: [PATCH 4/7] Demote input phrase limit message from warning to debug Limiting input phrase length is disabled by default. The default length limit value is 1000. The user has to enable the option and lower the length limit in order to be surprised by missing translation. By making such configuration changes, the user should become aware of this feature and its effect. As the person who has implemented this feature and uses 100 as the length limit, I am never surprised or disappointed by missing translation. The only annoyance is this warning that floods my systemd journal. Apparently I accidentally select overly long phrases way too often. --- config.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config.cc b/config.cc index 092a040d..fdc0ce1a 100644 --- a/config.cc +++ b/config.cc @@ -188,8 +188,8 @@ InputPhrase Preferences::sanitizeInputPhrase( QString const & inputPhrase ) cons if( limitInputPhraseLength && inputPhrase.size() > inputPhraseLengthLimit ) { - gdWarning( "Ignoring an input phrase %d symbols long. The configured maximum input phrase length is %d symbols.", - inputPhrase.size(), inputPhraseLengthLimit ); + gdDebug( "Ignoring an input phrase %d symbols long. The configured maximum input phrase length is %d symbols.", + inputPhrase.size(), inputPhraseLengthLimit ); return result; } From a321593ed1797774923354f0fd3e2432d9bbd83c Mon Sep 17 00:00:00 2001 From: Igor Kushnir Date: Sat, 5 Nov 2022 18:05:11 +0200 Subject: [PATCH 5/7] Linux-specific: check correct X11 window ID translateLine->internalWinId() always equals 0. When the show/hide main window hotkey is triggered right after GoldenDict starts to system tray, `wh` equals MainWindow::internalWinId(). A few more experiments confirm that XGetInputFocus()'s output parameter `focus_return` is an ID of a top-level window, not of an embedded widget child. --- mainwindow.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mainwindow.cc b/mainwindow.cc index b41d6f57..f3bbff3d 100644 --- a/mainwindow.cc +++ b/mainwindow.cc @@ -3076,11 +3076,12 @@ void MainWindow::toggleMainWindow( bool onlyShow ) ftsDlg->show(); focusTranslateLine(); + #ifdef X11_MAIN_WINDOW_FOCUS_WORKAROUNDS Window wh = 0; int rev = 0; XGetInputFocus( QX11Info::display(), &wh, &rev ); - if( wh != translateLine->internalWinId() && !byIconClick ) + if( wh != internalWinId() && !byIconClick ) { QPoint const pointRelativeToRoot = mapToGlobal( QPoint( 0, 0 ) ); XEvent event; From 5f96f1f26ee2389b29d8059a74f3e0e61a52c6c2 Mon Sep 17 00:00:00 2001 From: Igor Kushnir Date: Sat, 5 Nov 2022 18:21:28 +0200 Subject: [PATCH 6/7] Linux-specific: don't force X11 focus unnecessarily Focus is already transferred to GoldenDict in toggleMainWindow() only the first time the main window is shown. At all subsequent requests to show the main window, focus has to be forced with the workaround. Checking focus asynchronously allows to resort to the workaround less often. Under Xfce: the timeout of 0 ms is almost always sufficient in the Qt 5 version, but is never enough in the Qt 4 version. The timeout of 4 ms is always sufficient in both versions. Under KDE Plasma: the timeout of 0 ms is rarely sufficient in the Qt 5 version. Unfortunately, with any timeout other than 0 ms, the Qt 5 version does not always get focus, which would be a serious regression, so no other timeout can be used. The Qt 4 version does not always get focus both with and without the timeout. --- mainwindow.cc | 60 +++++++++++++++++++++++++++++---------------------- mainwindow.hh | 4 ++++ 2 files changed, 38 insertions(+), 26 deletions(-) diff --git a/mainwindow.cc b/mainwindow.cc index f3bbff3d..a11c83c2 100644 --- a/mainwindow.cc +++ b/mainwindow.cc @@ -3078,36 +3078,44 @@ void MainWindow::toggleMainWindow( bool onlyShow ) focusTranslateLine(); #ifdef X11_MAIN_WINDOW_FOCUS_WORKAROUNDS - Window wh = 0; - int rev = 0; - XGetInputFocus( QX11Info::display(), &wh, &rev ); - if( wh != internalWinId() && !byIconClick ) - { - QPoint const pointRelativeToRoot = mapToGlobal( QPoint( 0, 0 ) ); - XEvent event; - memset( &event, 0, sizeof( event) ); - event.type = ButtonPress; - event.xbutton.x = 0; - event.xbutton.y = 0; - event.xbutton.x_root = pointRelativeToRoot.x(); - event.xbutton.y_root = pointRelativeToRoot.y(); - event.xbutton.window = internalWinId(); - event.xbutton.root = QX11Info::appRootWindow( QX11Info::appScreen() ); - event.xbutton.state = Button1Mask; - event.xbutton.button = Button1; - event.xbutton.same_screen = true; - event.xbutton.time = CurrentTime; - - XSendEvent( QX11Info::display(), internalWinId(), true, 0xfff, &event ); - XFlush( QX11Info::display() ); - event.type = ButtonRelease; - XSendEvent( QX11Info::display(), internalWinId(), true, 0xfff, &event ); - XFlush( QX11Info::display() ); - } + if( !byIconClick ) + QTimer::singleShot( 0, this, SLOT( forceX11Focus() ) ); #endif } } +#ifdef X11_MAIN_WINDOW_FOCUS_WORKAROUNDS +void MainWindow::forceX11Focus() +{ + Window wh = 0; + int rev = 0; + XGetInputFocus( QX11Info::display(), &wh, &rev ); + if( wh != internalWinId() ) + { + QPoint const pointRelativeToRoot = mapToGlobal( QPoint( 0, 0 ) ); + XEvent event; + memset( &event, 0, sizeof( event) ); + event.type = ButtonPress; + event.xbutton.x = 0; + event.xbutton.y = 0; + event.xbutton.x_root = pointRelativeToRoot.x(); + event.xbutton.y_root = pointRelativeToRoot.y(); + event.xbutton.window = internalWinId(); + event.xbutton.root = QX11Info::appRootWindow( QX11Info::appScreen() ); + event.xbutton.state = Button1Mask; + event.xbutton.button = Button1; + event.xbutton.same_screen = true; + event.xbutton.time = CurrentTime; + + XSendEvent( QX11Info::display(), internalWinId(), true, 0xfff, &event ); + XFlush( QX11Info::display() ); + event.type = ButtonRelease; + XSendEvent( QX11Info::display(), internalWinId(), true, 0xfff, &event ); + XFlush( QX11Info::display() ); + } +} +#endif + void MainWindow::installHotKeys() { hotkeyWrapper.reset(); // Remove the old one diff --git a/mainwindow.hh b/mainwindow.hh index b98cc85b..2fa3cd05 100644 --- a/mainwindow.hh +++ b/mainwindow.hh @@ -280,6 +280,10 @@ private: private slots: +#ifdef X11_MAIN_WINDOW_FOCUS_WORKAROUNDS + void forceX11Focus(); +#endif + void hotKeyActivated( int ); /// If new release checks are on, santizies the next check time and starts From 570949b9f94d2932ac274c472a6b6a3008448171 Mon Sep 17 00:00:00 2001 From: Xiao YiFang Date: Sun, 6 Nov 2022 10:09:09 +0800 Subject: [PATCH 7/7] fix conflict with upstream as codebase has changed a lot --- dsl_details.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dsl_details.cc b/dsl_details.cc index 4424cd0d..569a2d55 100644 --- a/dsl_details.cc +++ b/dsl_details.cc @@ -170,17 +170,17 @@ namespace { /// @return true if @p tagName equals "mN" where N is a digit bool is_mN( wstring const & tagName ) { - return tagName.size() == 2 && tagName[ 0 ] == L'm' && iswdigit( tagName[ 1 ] ); + return tagName.size() == 2 && tagName[ 0 ] == U'm' && iswdigit( tagName[ 1 ] ); } bool isAnyM( wstring const & tagName ) { - return tagName == GD_NATIVE_TO_WS( L"m" ) || is_mN( tagName ); + return tagName == U"m" || is_mN( tagName ); } bool checkM( wstring const & dest, wstring const & src ) { - return src == GD_NATIVE_TO_WS( L"m" ) && is_mN( dest ); + return src == U"m" && is_mN( dest ); } /// Closing the [mN] tags is optional. Quote from https://documentation.help/ABBYY-Lingvo8/paragraph_form.htm: