goldendict-ng/src/ui/articleview.hh

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

471 lines
15 KiB
C++
Raw Normal View History

2012-02-20 21:47:14 +00:00
/* This file is (c) 2008-2012 Konstantin Isakov <ikm@goldendict.org>
* Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */
#ifndef GOLDENDICT_ARTICLEVIEW_H
#define GOLDENDICT_ARTICLEVIEW_H
#include <QAction>
#include <QMap>
#include <QSet>
#include <QUrl>
#include <QWebEngineView>
#include <list>
#include "article_netmgr.hh"
#include "audioplayerinterface.hh"
#include "instances.hh"
#include "groupcombobox.hh"
#include "globalbroadcaster.hh"
#include "article_inspect.hh"
#if ( QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 ) )
#include <QtCore5Compat/QRegExp>
2022-02-27 05:17:37 +00:00
#endif
#include "ankiconnector.hh"
#include "webmultimediadownload.hh"
#include "base_type.hh"
#include "articlewebview.hh"
#include "ui/searchpanel.hh"
#include "ui/ftssearchpanel.hh"
class ResourceToSaveHandler;
class ArticleViewAgent;
/// A widget with the web view tailored to view and handle articles -- it
/// uses the appropriate netmgr, handles link clicks, rmb clicks etc
class ArticleView: public QWidget
{
Q_OBJECT
ArticleNetworkAccessManager & articleNetMgr;
AudioPlayerPtr const & audioPlayer;
std::vector< sptr< Dictionary::Class > > const & allDictionaries;
Instances::Groups const & groups;
bool popupView;
Config::Class const & cfg;
QWebChannel * channel;
ArticleViewAgent * agent;
AnkiConnector * ankiConnector;
2014-04-16 16:18:28 +00:00
QAction pasteAction, articleUpAction, articleDownAction, goBackAction, goForwardAction, selectCurrentArticleAction,
copyAsTextAction, inspectAction;
2014-04-16 16:18:28 +00:00
QAction & openSearchAction;
2009-05-16 11:14:43 +00:00
bool searchIsOpened;
bool expandOptionalParts;
QString rangeVarName;
/// An action used to create Anki notes.
QAction sendToAnkiAction{ tr( "&Create Anki note" ), this };
/// Any resource we've decided to download off the dictionary gets stored here.
/// Full vector capacity is used for search requests, where we have to make
/// a multitude of requests.
std::list< sptr< Dictionary::DataRequest > > resourceDownloadRequests;
/// Url of the resourceDownloadRequests
QUrl resourceDownloadUrl;
/// For resources opened via desktop services
QSet< QString > desktopOpenedTempFiles;
QAction * dictionaryBarToggled;
GroupComboBox const * groupComboBox;
unsigned currentGroupId;
QLineEdit const * translateLine;
/// current searching word.
QString currentWord;
/// current active dict id list;
QStringList currentActiveDictIds;
bool historyMode = false;
//current active dictionary id;
QString activeDictId;
/// Search in results of full-text search
QString firstAvailableText;
QStringList uniqueMatches;
2023-05-13 00:44:17 +00:00
bool ftsSearchIsOpened = false;
bool ftsSearchMatchCase = false;
QString delayedHighlightText;
void highlightFTSResults();
void performFtsFindOperation( bool backwards );
public:
/// The popupView flag influences contents of the context menus to be
/// appropriate to the context of the view.
/// The groups aren't copied -- rather than that, the reference is kept
ArticleView( QWidget * parent,
ArticleNetworkAccessManager &,
AudioPlayerPtr const &,
std::vector< sptr< Dictionary::Class > > const & allDictionaries,
Instances::Groups const &,
bool popupView,
Config::Class const & cfg,
2014-04-16 16:18:28 +00:00
QAction & openSearchAction_,
QLineEdit const * translateLine,
QAction * dictionaryBarToggled = nullptr,
GroupComboBox const * groupComboBox = nullptr );
void setCurrentGroupId( unsigned currengGrgId );
unsigned getCurrentGroupId();
virtual QSize minimumSizeHint() const;
void clearContent();
~ArticleView();
2023-01-02 14:00:42 +00:00
/// Returns "gdfrom-" + dictionaryId.
static QString scrollToFromDictionaryId( QString const & dictionaryId );
/// Shows the definition of the given word with the given group.
/// scrollTo can be optionally set to a "gdfrom-xxxx" identifier to position
/// the page to that article on load.
/// contexts is an optional map of context values to be passed for dictionaries.
/// The only values to pass here are ones obtained from showDefinitionInNewTab()
/// signal or none at all.
void showDefinition( QString const & word,
unsigned group,
QString const & scrollTo = QString(),
Contexts const & contexts = Contexts() );
void showDefinition( QString const & word,
QStringList const & dictIDs,
QRegExp const & searchRegExp,
unsigned group,
bool ignoreDiacritics );
2014-04-16 16:18:28 +00:00
void sendToAnki( QString const & word, QString const & text, QString const & sentence );
/// Clears the view and sets the application-global waiting cursor,
/// which will be restored when some article loads eventually.
void showAnticipation();
2009-04-30 19:57:25 +00:00
/// Create a new Anki card from a currently displayed article with the provided id.
/// This function will call QWebEnginePage::runJavaScript() to fetch the corresponding HTML.
void makeAnkiCardFromArticle( QString const & article_id );
/// Opens the given link. Supposed to be used in response to
/// openLinkInNewTab() signal. The link scheme is therefore supposed to be
/// one of the internal ones.
/// contexts is an optional map of context values to be passed for dictionaries.
/// The only values to pass here are ones obtained from showDefinitionInNewTab()
/// signal or none at all.
void openLink( QUrl const & url,
QUrl const & referrer,
QString const & scrollTo = QString(),
Contexts const & contexts = Contexts() );
2009-04-30 19:57:25 +00:00
/// Called when the state of dictionary bar changes and the view is active.
/// The function reloads content if the change affects it.
void updateMutedContents();
bool canGoBack();
bool canGoForward();
/// Called when preference changes
void setSelectionBySingleClick( bool set );
void setDelayedHighlightText( QString const & text );
private:
// widgets
ArticleWebView * webview;
SearchPanel * searchPanel;
FtsSearchPanel * ftsSearchPanel;
public slots:
/// Goes back in history
void back();
/// Goes forward in history
void forward();
/// Takes the focus to the view
void focus()
{
webview->setFocus();
}
/// Sends *word* to Anki.
void handleAnkiAction();
public:
/// Reloads the view
void reload();
void stopSound();
/// Returns true if there's an audio reference on the page, false otherwise.
2022-03-30 15:08:24 +00:00
void hasSound( const std::function< void( bool has ) > & callback );
/// Plays the first audio reference on the page, if any.
void playSound();
2009-04-30 19:57:25 +00:00
void setZoomFactor( qreal factor )
{
qreal existedFactor = webview->zoomFactor();
if ( !qFuzzyCompare( existedFactor, factor ) ) {
qDebug() << "zoom factor ,existed:" << existedFactor << "set:" << factor;
webview->setZoomFactor( factor );
//webview->page()->setZoomFactor(factor);
}
}
2009-04-30 19:57:25 +00:00
2009-05-01 11:17:29 +00:00
/// Returns current article's text in .html format
2022-03-30 15:08:24 +00:00
void toHtml( const std::function< void( QString & ) > & callback );
2009-05-01 11:17:29 +00:00
2021-08-15 03:05:38 +00:00
void setHtml( const QString & content, const QUrl & baseUrl );
2021-08-21 01:41:40 +00:00
void setContent( const QByteArray & data, const QString & mimeType = QString(), const QUrl & baseUrl = QUrl() );
2021-08-14 07:25:10 +00:00
2009-05-01 11:17:29 +00:00
/// Returns current article's title
QString getTitle();
2009-05-01 12:20:33 +00:00
/// Returns the phrase translated by the current article.
QString getWord() const;
2009-05-01 12:20:33 +00:00
/// Prints current article
void print( QPrinter * ) const;
2009-05-16 11:14:43 +00:00
/// Closes search if it's open and returns true. Returns false if it
/// wasn't open.
bool closeSearch();
bool isSearchOpened();
/// Jumps to the article specified by the dictionary id,
/// by executing a javascript code.
void jumpToDictionary( QString const &, bool force );
/// Returns all articles currently present in view, as a list of dictionary
/// string ids.
QStringList getArticlesList();
/// Returns the dictionary id of the currently active article in the view.
QString getActiveArticleId();
void setActiveArticleId( QString const & );
2017-04-27 20:55:53 +00:00
ResourceToSaveHandler * saveResource( const QUrl & url, const QString & fileName );
ResourceToSaveHandler * saveResource( const QUrl & url, const QUrl & ref, const QString & fileName );
void findText( QString & text,
const QWebEnginePage::FindFlags & f,
const std::function< void( bool match ) > & callback = nullptr );
signals:
void iconChanged( ArticleView *, QIcon const & icon );
void titleChanged( ArticleView *, QString const & title );
void pageLoaded( ArticleView * );
/// Signals that the following link was requested to be opened in new tab
void openLinkInNewTab( QUrl const &, QUrl const & referrer, QString const & fromArticle, Contexts const & contexts );
/// Signals that the following definition was requested to be showed in new tab
void showDefinitionInNewTab( QString const & word,
unsigned group,
QString const & fromArticle,
2023-01-02 14:00:42 +00:00
Contexts const & contexts );
/// Put translated word into history
void sendWordToHistory( QString const & word );
/// Emitted when user types a text key. This should typically be used to
/// switch focus to word input.
void typingEvent( QString const & text );
void statusBarMessage( QString const & message, int timeout = 0, QPixmap const & pixmap = QPixmap() );
/// Signals that the dictionaries pane was requested to be showed
void showDictsPane();
2022-01-09 04:54:50 +00:00
/// Signals that the founded dictionaries ready to be showed
void updateFoundInDictsList();
/// Emitted when an article becomes active,
/// typically in response to user actions
/// (clicking on the article or using shortcuts).
/// id - the dictionary id of the active article.
void activeArticleChanged( ArticleView const *, QString const & id );
/// Signal to add word to history even if history is disabled
void forceAddWordToHistory( const QString & word );
/// Signal to close popup menu
void closePopupMenu();
void sendWordToInputLine( QString const & word );
void storeResourceSavePath( QString const & );
void zoomIn();
void zoomOut();
2021-11-24 14:38:37 +00:00
/// signal finished javascript;
void notifyJavascriptFinished();
2022-08-08 12:48:46 +00:00
void inspectSignal( QWebEnginePage * page );
void saveBookmarkSignal( const QString & bookmark );
public slots:
void on_searchPrevious_clicked();
void on_searchNext_clicked();
void onJsActiveArticleChanged( QString const & id );
/// Handles F3 and Shift+F3 for search navigation
bool handleF3( QObject * obj, QEvent * ev );
/// Selects an entire text of the current article
void selectCurrentArticle();
//receive signal from weburlinterceptor.
void linkClicked( QUrl const & );
//aim to receive signal from html. the fragment url click to navigation through page wil not be intecepted by weburlinteceptor
Q_INVOKABLE void linkClickedInHtml( QUrl const & );
private slots:
void inspectElement();
void loadFinished( bool ok );
void handleTitleChanged( QString const & title );
void handleUrlChanged( QUrl const & url );
void attachWebChannelToHtml();
2021-07-06 13:01:50 +00:00
void linkHovered( const QString & link );
void contextMenuRequested( QPoint const & );
bool isAudioLink( QUrl & targetUrl )
{
return ( targetUrl.scheme() == "gdau" || Utils::Url::isAudioUrl( targetUrl ) );
}
void resourceDownloadFinished();
/// We handle pasting by attempting to define the word in clipboard.
void pasteTriggered();
unsigned getCurrentGroup();
/// Nagivates to the previous article relative to the active one.
void moveOneArticleUp();
/// Nagivates to the next article relative to the active one.
void moveOneArticleDown();
2009-05-16 11:14:43 +00:00
/// Opens the search area
void openSearch();
void on_searchText_textEdited();
void on_searchText_returnPressed();
void on_searchCloseButton_clicked();
void on_searchCaseSensitive_clicked();
void on_highlightAllButton_clicked();
2009-05-16 11:14:43 +00:00
void on_ftsSearchPrevious_clicked();
void on_ftsSearchNext_clicked();
/// Handles the double-click from the definition.
void doubleClicked( QPoint pos );
/// Handles audio player error message
void audioPlayerError( QString const & message );
/// Copy current selection as plain text
void copyAsText();
void setActiveDictIds( const ActiveDictIds & ad );
void dictionaryClear( const ActiveDictIds & ad );
private:
/// Deduces group from the url. If there doesn't seem to be any group,
/// returns 0.
unsigned getGroup( QUrl const & );
/// Returns current article in the view, in the form of "gdfrom-xxx" id.
QString getCurrentArticle();
/// Sets the current article by executing a javascript code.
/// If moveToIt is true, it moves the focus to it as well.
/// 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.
2022-03-30 15:08:24 +00:00
void isFramedArticle( QString const & article, const std::function< void( bool framed ) > & callback );
/// Sees if the last clicked link is from a website frame. If so, changes url
/// to point to url text translation instead, and saves the original
/// url to the appropriate "contexts" entry.
void tryMangleWebsiteClickedUrl( QUrl & url, Contexts & contexts );
/// Loads a page at @p url into view.
void load( QUrl const & url );
/// Attempts removing last temporary file created.
void cleanupTemp();
2009-04-30 19:57:25 +00:00
bool eventFilter( QObject * obj, QEvent * ev ) override;
void performFindOperation( bool restart, bool backwards, bool checkHighlight = false );
2009-05-16 11:14:43 +00:00
/// Returns the comma-separated list of dictionary ids which should be muted
/// for the given group. If there are none, returns empty string.
QString getMutedForGroup( unsigned group );
2021-10-05 01:23:30 +00:00
QStringList getMutedDictionaries( unsigned group );
2021-12-12 05:35:32 +00:00
protected:
// We need this to hide the search bar when we're showed
void showEvent( QShowEvent * ) override;
};
class ResourceToSaveHandler: public QObject
{
Q_OBJECT
public:
explicit ResourceToSaveHandler( ArticleView * view, QString fileName );
void addRequest( const sptr< Dictionary::DataRequest > & req );
2017-04-27 20:55:53 +00:00
bool isEmpty()
{
return downloadRequests.empty();
}
signals:
void done();
void statusBarMessage( QString const & message, int timeout = 0, QPixmap const & pixmap = QPixmap() );
2017-04-27 20:55:53 +00:00
public slots:
void downloadFinished();
private:
2017-04-27 20:55:53 +00:00
std::list< sptr< Dictionary::DataRequest > > downloadRequests;
QString fileName;
2017-04-27 20:55:53 +00:00
bool alreadyDone;
};
class ArticleViewAgent: public QObject
{
Q_OBJECT
ArticleView * articleView;
public:
explicit ArticleViewAgent( ArticleView * articleView );
public slots:
Q_INVOKABLE void onJsActiveArticleChanged( QString const & id );
Q_INVOKABLE void linkClickedInHtml( QUrl const & );
2023-04-15 11:10:15 +00:00
Q_INVOKABLE void collapseInHtml( QString const & dictId, bool on = true ) const;
};
#endif