1 // For license of this file, see <project-root-folder>/LICENSE.md.
2 
3 #include "gui/webbrowser.h"
4 
5 #include "database/databasequeries.h"
6 #include "gui/messagebox.h"
7 #include "gui/reusable/discoverfeedsbutton.h"
8 #include "gui/reusable/locationlineedit.h"
9 #include "gui/reusable/searchtextwidget.h"
10 #include "gui/webviewer.h"
11 #include "miscellaneous/application.h"
12 #include "miscellaneous/iconfactory.h"
13 #include "network-web/networkfactory.h"
14 #include "network-web/webfactory.h"
15 #include "services/abstract/serviceroot.h"
16 
17 #include <QKeyEvent>
18 #include <QScrollBar>
19 #include <QTimer>
20 #include <QToolBar>
21 #include <QToolTip>
22 #include <QWebEngineSettings>
23 #include <QWidgetAction>
24 
WebBrowser(QWidget * parent)25 WebBrowser::WebBrowser(QWidget* parent) : TabContent(parent),
26   m_layout(new QVBoxLayout(this)),
27   m_toolBar(new QToolBar(tr("Navigation panel"), this)),
28   m_webView(new WebViewer(this)),
29   m_searchWidget(new SearchTextWidget(this)),
30   m_txtLocation(new LocationLineEdit(this)),
31   m_btnDiscoverFeeds(new DiscoverFeedsButton(this)),
32   m_actionBack(m_webView->pageAction(QWebEnginePage::WebAction::Back)),
33   m_actionForward(m_webView->pageAction(QWebEnginePage::WebAction::Forward)),
34   m_actionReload(m_webView->pageAction(QWebEnginePage::WebAction::Reload)),
35   m_actionStop(m_webView->pageAction(QWebEnginePage::WebAction::Stop)),
36   m_actionOpenInSystemBrowser(new QAction(qApp->icons()->fromTheme(QSL("document-open")),
37                                           tr("Open this website in system web browser"),
38                                           this)) {
39   // Initialize the components and layout.
40   initializeLayout();
41   setFocusProxy(m_txtLocation);
42   setTabOrder(m_txtLocation, m_toolBar);
43   setTabOrder(m_toolBar, m_webView);
44   createConnections();
45   reloadFontSettings();
46 }
47 
createConnections()48 void WebBrowser::createConnections() {
49   installEventFilter(this);
50 
51   connect(m_searchWidget, &SearchTextWidget::searchCancelled, this, [this]() {
52     m_webView->findText(QString());
53   });
54   connect(m_searchWidget, &SearchTextWidget::searchForText, this, [this](const QString& text, bool backwards) {
55     if (backwards) {
56       m_webView->findText(text, QWebEnginePage::FindFlag::FindBackward);
57     }
58     else {
59       m_webView->findText(text);
60     }
61 
62     m_searchWidget->setFocus();
63   });
64 
65   connect(m_actionOpenInSystemBrowser, &QAction::triggered, this, &WebBrowser::openCurrentSiteInSystemBrowser);
66 
67   connect(m_txtLocation, &LocationLineEdit::submitted,
68           this, static_cast<void (WebBrowser::*)(const QString&)>(&WebBrowser::loadUrl));
69   connect(m_webView, &WebViewer::urlChanged, this, &WebBrowser::updateUrl);
70 
71   // Change location textbox status according to webpage status.
72   connect(m_webView, &WebViewer::loadStarted, this, &WebBrowser::onLoadingStarted);
73   connect(m_webView, &WebViewer::loadProgress, this, &WebBrowser::onLoadingProgress);
74   connect(m_webView, &WebViewer::loadFinished, this, &WebBrowser::onLoadingFinished);
75 
76   // Forward title/icon changes.
77   connect(m_webView, &WebViewer::titleChanged, this, &WebBrowser::onTitleChanged);
78   connect(m_webView, &WebViewer::iconChanged, this, &WebBrowser::onIconChanged);
79 
80   connect(m_webView->page(), &WebPage::windowCloseRequested, this, &WebBrowser::closeRequested);
81 }
82 
updateUrl(const QUrl & url)83 void WebBrowser::updateUrl(const QUrl& url) {
84   m_txtLocation->setText(url.toString());
85 }
86 
loadUrl(const QUrl & url)87 void WebBrowser::loadUrl(const QUrl& url) {
88   if (url.isValid()) {
89     m_webView->load(url);
90   }
91 }
92 
~WebBrowser()93 WebBrowser::~WebBrowser() {
94   // Delete members. Do not use scoped pointers here.
95   delete m_layout;
96 }
97 
verticalScrollBarPosition() const98 double WebBrowser::verticalScrollBarPosition() const {
99   double position;
100   QEventLoop loop;
101 
102   viewer()->page()->runJavaScript(QSL("window.pageYOffset;"), [&position, &loop](const QVariant& val) {
103     position = val.toDouble();
104     loop.exit();
105   });
106   loop.exec();
107 
108   return position;
109 }
110 
setVerticalScrollBarPosition(double pos)111 void WebBrowser::setVerticalScrollBarPosition(double pos) {
112   viewer()->page()->runJavaScript(QSL("window.scrollTo(0, %1);").arg(pos));
113 }
114 
reloadFontSettings()115 void WebBrowser::reloadFontSettings() {
116   QFont fon;
117 
118   fon.fromString(qApp->settings()->value(GROUP(Messages),
119                                          SETTING(Messages::PreviewerFontStandard)).toString());
120 
121   QWebEngineSettings::defaultSettings()->setFontFamily(QWebEngineSettings::FontFamily::StandardFont, fon.family());
122   QWebEngineSettings::defaultSettings()->setFontFamily(QWebEngineSettings::FontFamily::SerifFont, fon.family());
123   QWebEngineSettings::defaultSettings()->setFontFamily(QWebEngineSettings::FontFamily::SansSerifFont, fon.family());
124   QWebEngineSettings::defaultSettings()->setFontSize(QWebEngineSettings::DefaultFontSize, fon.pointSize());
125 }
126 
increaseZoom()127 void WebBrowser::increaseZoom() {
128   m_webView->increaseWebPageZoom();
129 }
130 
decreaseZoom()131 void WebBrowser::decreaseZoom() {
132   m_webView->decreaseWebPageZoom();
133 }
134 
resetZoom()135 void WebBrowser::resetZoom() {
136   m_webView->resetWebPageZoom(true);
137 }
138 
clear(bool also_hide)139 void WebBrowser::clear(bool also_hide) {
140   m_webView->clear();
141   m_messages.clear();
142 
143   if (also_hide) {
144     hide();
145   }
146 }
147 
loadUrl(const QString & url)148 void WebBrowser::loadUrl(const QString& url) {
149   return loadUrl(QUrl::fromUserInput(url));
150 }
151 
loadMessages(const QList<Message> & messages,RootItem * root)152 void WebBrowser::loadMessages(const QList<Message>& messages, RootItem* root) {
153   m_messages = messages;
154   m_root = root;
155 
156   if (!m_root.isNull()) {
157     m_searchWidget->hide();
158     m_webView->loadMessages(messages, root);
159     show();
160   }
161 }
162 
loadMessage(const Message & message,RootItem * root)163 void WebBrowser::loadMessage(const Message& message, RootItem* root) {
164   loadMessages({ message }, root);
165 }
166 
eventFilter(QObject * watched,QEvent * event)167 bool WebBrowser::eventFilter(QObject* watched, QEvent* event) {
168   Q_UNUSED(watched)
169 
170   if (event->type() == QEvent::KeyPress) {
171     QKeyEvent* key_event = static_cast<QKeyEvent*>(event);
172 
173     if (key_event->matches(QKeySequence::StandardKey::Find)) {
174       m_searchWidget->clear();
175       m_searchWidget->show();
176       m_searchWidget->setFocus();
177       return true;
178     }
179   }
180 
181   return false;
182 }
183 
openCurrentSiteInSystemBrowser()184 void WebBrowser::openCurrentSiteInSystemBrowser() {
185   auto url = m_webView->url();
186 
187   if (!url.isValid() || url.host().contains(QSL(APP_LOW_NAME))) {
188     return;
189   }
190 
191   qApp->web()->openUrlInExternalBrowser(url.toString());
192 }
193 
onTitleChanged(const QString & new_title)194 void WebBrowser::onTitleChanged(const QString& new_title) {
195   if (new_title.isEmpty()) {
196     //: Webbrowser tab title when no title is available.
197     emit titleChanged(m_index, tr("No title"));
198   }
199   else {
200     emit titleChanged(m_index, new_title);
201   }
202 }
203 
onIconChanged(const QIcon & icon)204 void WebBrowser::onIconChanged(const QIcon& icon) {
205   emit iconChanged(m_index, icon);
206 }
207 
initializeLayout()208 void WebBrowser::initializeLayout() {
209   m_toolBar->setFloatable(false);
210   m_toolBar->setMovable(false);
211   m_toolBar->setAllowedAreas(Qt::ToolBarArea::TopToolBarArea);
212 
213   // Modify action texts.
214   m_actionBack->setText(tr("Back"));
215   m_actionForward->setText(tr("Forward"));
216   m_actionReload->setText(tr("Reload"));
217   m_actionStop->setText(tr("Stop"));
218   QWidgetAction* act_discover = new QWidgetAction(this);
219 
220   m_actionOpenInSystemBrowser->setEnabled(false);
221   act_discover->setDefaultWidget(m_btnDiscoverFeeds);
222 
223   // Add needed actions into toolbar.
224   m_toolBar->addAction(m_actionBack);
225   m_toolBar->addAction(m_actionForward);
226   m_toolBar->addAction(m_actionReload);
227   m_toolBar->addAction(m_actionStop);
228   m_toolBar->addAction(m_actionOpenInSystemBrowser);
229   m_toolBar->addAction(act_discover);
230   m_toolBar->addWidget(m_txtLocation);
231   m_loadingProgress = new QProgressBar(this);
232   m_loadingProgress->setFixedHeight(5);
233   m_loadingProgress->setMinimum(0);
234   m_loadingProgress->setTextVisible(false);
235   m_loadingProgress->setMaximum(100);
236   m_loadingProgress->setAttribute(Qt::WidgetAttribute::WA_TranslucentBackground);
237 
238   // Setup layout.
239   m_layout->addWidget(m_toolBar);
240   m_layout->addWidget(m_webView);
241   m_layout->addWidget(m_loadingProgress);
242   m_layout->addWidget(m_searchWidget);
243   m_layout->setMargin(0);
244   m_layout->setSpacing(0);
245 
246   m_searchWidget->hide();
247 }
248 
onLoadingStarted()249 void WebBrowser::onLoadingStarted() {
250   m_btnDiscoverFeeds->clearFeedAddresses();
251   m_loadingProgress->show();
252   m_actionOpenInSystemBrowser->setEnabled(false);
253 }
254 
onLoadingProgress(int progress)255 void WebBrowser::onLoadingProgress(int progress) {
256   m_loadingProgress->setValue(progress);
257 }
258 
onLoadingFinished(bool success)259 void WebBrowser::onLoadingFinished(bool success) {
260   if (success) {
261     auto url = m_webView->url();
262 
263     if (url.isValid() && !url.host().contains(QSL(APP_LOW_NAME))) {
264       m_actionOpenInSystemBrowser->setEnabled(true);
265     }
266 
267     // Let's check if there are any feeds defined on the web and eventually
268     // display "Add feeds" button.
269     m_webView->page()->toHtml([this](const QString& result) {
270       this->m_btnDiscoverFeeds->setFeedAddresses(NetworkFactory::extractFeedLinksFromHtmlPage(m_webView->url(), result));
271     });
272   }
273   else {
274     m_btnDiscoverFeeds->clearFeedAddresses();
275   }
276 
277   m_loadingProgress->hide();
278   m_loadingProgress->setValue(0);
279 }
280 
findMessage(int id)281 Message* WebBrowser::findMessage(int id) {
282   for (int i = 0; i < m_messages.size(); i++) {
283     if (m_messages.at(i).m_id == id) {
284       return &m_messages[i];
285     }
286   }
287 
288   return nullptr;
289 }
290