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