1 #include "browser.h"
2 #include "browserwindow.h"
3 #include "downloadmanagerwidget.h"
4 #include "tabwidget.h"
5 #include "webview.h"
6 #include <QApplication>
7 #include <QCloseEvent>
8 #include <QDesktopWidget>
9 #include <QEvent>
10 #include <QFileDialog>
11 #include <QInputDialog>
12 #include <QMenuBar>
13 #include <QMessageBox>
14 #include <QProgressBar>
15 #include <QStatusBar>
16 #include <QToolBar>
17 #include <QVBoxLayout>
18 #include <QWebEngineProfile>
19
BrowserWindow(Browser * browser,QWebEngineProfile * profile)20 BrowserWindow::BrowserWindow(Browser *browser, QWebEngineProfile *profile)
21 : m_browser(browser)
22 , m_profile(profile)
23 , m_tabWidget(new TabWidget(profile, this))
24 , m_progressBar(new QProgressBar(this))
25 , m_historyBackAction(nullptr)
26 , m_historyForwardAction(nullptr)
27 , m_stopAction(nullptr)
28 , m_reloadAction(nullptr)
29 , m_stopReloadAction(nullptr)
30 , m_urlLineEdit(nullptr)
31 , m_favAction(nullptr)
32 {
33 setAttribute(Qt::WA_DeleteOnClose, true);
34 setFocusPolicy(Qt::ClickFocus);
35
36 QToolBar *toolbar = createToolBar();
37 addToolBar(toolbar);
38 menuBar()->addMenu(createFileMenu(m_tabWidget));
39 menuBar()->addMenu(createEditMenu());
40 menuBar()->addMenu(createViewMenu(toolbar));
41 menuBar()->addMenu(createWindowMenu(m_tabWidget));
42 menuBar()->addMenu(createHelpMenu());
43
44 QWidget *centralWidget = new QWidget(this);
45 QVBoxLayout *layout = new QVBoxLayout;
46 layout->setSpacing(0);
47 layout->setMargin(0);
48 addToolBarBreak();
49
50 m_progressBar->setMaximumHeight(1);
51 m_progressBar->setTextVisible(false);
52 m_progressBar->setStyleSheet(QStringLiteral("QProgressBar {border: 0px} QProgressBar::chunk {background-color: #da4453}"));
53
54 layout->addWidget(m_progressBar);
55 layout->addWidget(m_tabWidget);
56 centralWidget->setLayout(layout);
57
58 QHBoxLayout *hLayout = new QHBoxLayout(this);
59 hLayout->addWidget(centralWidget);
60
61 connect(m_tabWidget, &TabWidget::titleChanged, this, &BrowserWindow::handleWebViewTitleChanged);
62 connect(m_tabWidget, &TabWidget::linkHovered, [this](const QString& url) {
63 statusBar()->showMessage(url);
64 });
65 connect(m_tabWidget, &TabWidget::loadProgress, this, &BrowserWindow::handleWebViewLoadProgress);
66 connect(m_tabWidget, &TabWidget::webActionEnabledChanged, this, &BrowserWindow::handleWebActionEnabledChanged);
67 connect(m_tabWidget, &TabWidget::urlChanged, [this](const QUrl &url) {
68 m_urlLineEdit->setText(url.toDisplayString());
69 });
70 connect(m_tabWidget, &TabWidget::favIconChanged, m_favAction, &QAction::setIcon);
71 connect(m_urlLineEdit, &QLineEdit::returnPressed, [this]() {
72 m_tabWidget->setUrl(QUrl::fromUserInput(m_urlLineEdit->text()));
73 });
74
75 QAction *focusUrlLineEditAction = new QAction(this);
76 addAction(focusUrlLineEditAction);
77 focusUrlLineEditAction->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L));
78 connect(focusUrlLineEditAction, &QAction::triggered, this, [this] () {
79 m_urlLineEdit->setFocus(Qt::ShortcutFocusReason);
80 });
81
82 handleWebViewTitleChanged(QString());
83 m_tabWidget->createTab();
84 }
85
sizeHint() const86 QSize BrowserWindow::sizeHint() const
87 {
88 QRect desktopRect = QApplication::desktop()->screenGeometry();
89 QSize size = desktopRect.size() * qreal(0.9);
90 return size;
91 }
92
createFileMenu(TabWidget * tabWidget)93 QMenu *BrowserWindow::createFileMenu(TabWidget *tabWidget)
94 {
95 QMenu *fileMenu = new QMenu(tr("&File"));
96 fileMenu->addAction(tr("&New Window"), this, &BrowserWindow::handleNewWindowTriggered, QKeySequence::New);
97 fileMenu->addAction(tr("New &Incognito Window"), this, &BrowserWindow::handleNewIncognitoWindowTriggered);
98
99 QAction *newTabAction = new QAction(tr("New &Tab"), this);
100 newTabAction->setShortcuts(QKeySequence::AddTab);
101 connect(newTabAction, &QAction::triggered, tabWidget, &TabWidget::createTab);
102 fileMenu->addAction(newTabAction);
103
104 fileMenu->addAction(tr("&Open File..."), this, &BrowserWindow::handleFileOpenTriggered, QKeySequence::Open);
105 fileMenu->addSeparator();
106
107 QAction *closeTabAction = new QAction(tr("&Close Tab"), this);
108 closeTabAction->setShortcuts(QKeySequence::Close);
109 connect(closeTabAction, &QAction::triggered, [tabWidget]() {
110 tabWidget->closeTab(tabWidget->currentIndex());
111 });
112 fileMenu->addAction(closeTabAction);
113
114 QAction *closeAction = new QAction(tr("&Quit"),this);
115 closeAction->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_Q));
116 connect(closeAction, &QAction::triggered, this, &QWidget::close);
117 fileMenu->addAction(closeAction);
118
119 connect(fileMenu, &QMenu::aboutToShow, [this, closeAction]() {
120 if (m_browser->windows().count() == 1)
121 closeAction->setText(tr("&Quit"));
122 else
123 closeAction->setText(tr("&Close Window"));
124 });
125 return fileMenu;
126 }
127
createEditMenu()128 QMenu *BrowserWindow::createEditMenu()
129 {
130 QMenu *editMenu = new QMenu(tr("&Edit"));
131 QAction *findAction = editMenu->addAction(tr("&Find"));
132 findAction->setShortcuts(QKeySequence::Find);
133 connect(findAction, &QAction::triggered, this, &BrowserWindow::handleFindActionTriggered);
134
135 QAction *findNextAction = editMenu->addAction(tr("Find &Next"));
136 findNextAction->setShortcut(QKeySequence::FindNext);
137 connect(findNextAction, &QAction::triggered, [this]() {
138 if (!currentTab() || m_lastSearch.isEmpty())
139 return;
140 currentTab()->findText(m_lastSearch);
141 });
142
143 QAction *findPreviousAction = editMenu->addAction(tr("Find &Previous"));
144 findPreviousAction->setShortcut(QKeySequence::FindPrevious);
145 connect(findPreviousAction, &QAction::triggered, [this]() {
146 if (!currentTab() || m_lastSearch.isEmpty())
147 return;
148 currentTab()->findText(m_lastSearch, QWebEnginePage::FindBackward);
149 });
150
151 return editMenu;
152 }
153
createViewMenu(QToolBar * toolbar)154 QMenu *BrowserWindow::createViewMenu(QToolBar *toolbar)
155 {
156 QMenu *viewMenu = new QMenu(tr("&View"));
157 m_stopAction = viewMenu->addAction(tr("&Stop"));
158 QList<QKeySequence> shortcuts;
159 shortcuts.append(QKeySequence(Qt::CTRL | Qt::Key_Period));
160 shortcuts.append(Qt::Key_Escape);
161 m_stopAction->setShortcuts(shortcuts);
162 connect(m_stopAction, &QAction::triggered, [this]() {
163 m_tabWidget->triggerWebPageAction(QWebEnginePage::Stop);
164 });
165
166 m_reloadAction = viewMenu->addAction(tr("Reload Page"));
167 m_reloadAction->setShortcuts(QKeySequence::Refresh);
168 connect(m_reloadAction, &QAction::triggered, [this]() {
169 m_tabWidget->triggerWebPageAction(QWebEnginePage::Reload);
170 });
171
172 QAction *zoomIn = viewMenu->addAction(tr("Zoom &In"));
173 zoomIn->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_Plus));
174 connect(zoomIn, &QAction::triggered, [this]() {
175 if (currentTab())
176 currentTab()->setZoomFactor(currentTab()->zoomFactor() + 0.1);
177 });
178
179 QAction *zoomOut = viewMenu->addAction(tr("Zoom &Out"));
180 zoomOut->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_Minus));
181 connect(zoomOut, &QAction::triggered, [this]() {
182 if (currentTab())
183 currentTab()->setZoomFactor(currentTab()->zoomFactor() - 0.1);
184 });
185
186 QAction *resetZoom = viewMenu->addAction(tr("Reset &Zoom"));
187 resetZoom->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_0));
188 connect(resetZoom, &QAction::triggered, [this]() {
189 if (currentTab())
190 currentTab()->setZoomFactor(1.0);
191 });
192
193
194 viewMenu->addSeparator();
195 QAction *viewToolbarAction = new QAction(tr("Hide Toolbar"),this);
196 viewToolbarAction->setShortcut(tr("Ctrl+|"));
197 connect(viewToolbarAction, &QAction::triggered, [toolbar,viewToolbarAction]() {
198 if (toolbar->isVisible()) {
199 viewToolbarAction->setText(tr("Show Toolbar"));
200 toolbar->close();
201 } else {
202 viewToolbarAction->setText(tr("Hide Toolbar"));
203 toolbar->show();
204 }
205 });
206 viewMenu->addAction(viewToolbarAction);
207
208 QAction *viewStatusbarAction = new QAction(tr("Hide Status Bar"), this);
209 viewStatusbarAction->setShortcut(tr("Ctrl+/"));
210 connect(viewStatusbarAction, &QAction::triggered, [this, viewStatusbarAction]() {
211 if (statusBar()->isVisible()) {
212 viewStatusbarAction->setText(tr("Show Status Bar"));
213 statusBar()->close();
214 } else {
215 viewStatusbarAction->setText(tr("Hide Status Bar"));
216 statusBar()->show();
217 }
218 });
219 viewMenu->addAction(viewStatusbarAction);
220 return viewMenu;
221 }
222
createWindowMenu(TabWidget * tabWidget)223 QMenu *BrowserWindow::createWindowMenu(TabWidget *tabWidget)
224 {
225 QMenu *menu = new QMenu(tr("&Window"));
226
227 QAction *nextTabAction = new QAction(tr("Show Next Tab"), this);
228 QList<QKeySequence> shortcuts;
229 shortcuts.append(QKeySequence(Qt::CTRL | Qt::Key_BraceRight));
230 shortcuts.append(QKeySequence(Qt::CTRL | Qt::Key_PageDown));
231 shortcuts.append(QKeySequence(Qt::CTRL | Qt::Key_BracketRight));
232 shortcuts.append(QKeySequence(Qt::CTRL | Qt::Key_Less));
233 nextTabAction->setShortcuts(shortcuts);
234 connect(nextTabAction, &QAction::triggered, tabWidget, &TabWidget::nextTab);
235
236 QAction *previousTabAction = new QAction(tr("Show Previous Tab"), this);
237 shortcuts.clear();
238 shortcuts.append(QKeySequence(Qt::CTRL | Qt::Key_BraceLeft));
239 shortcuts.append(QKeySequence(Qt::CTRL | Qt::Key_PageUp));
240 shortcuts.append(QKeySequence(Qt::CTRL | Qt::Key_BracketLeft));
241 shortcuts.append(QKeySequence(Qt::CTRL | Qt::Key_Greater));
242 previousTabAction->setShortcuts(shortcuts);
243 connect(previousTabAction, &QAction::triggered, tabWidget, &TabWidget::previousTab);
244
245 connect(menu, &QMenu::aboutToShow, [this, menu, nextTabAction, previousTabAction]() {
246 menu->clear();
247 menu->addAction(nextTabAction);
248 menu->addAction(previousTabAction);
249 menu->addSeparator();
250
251 QVector<BrowserWindow*> windows = m_browser->windows();
252 int index(-1);
253 for (auto window : windows) {
254 QAction *action = menu->addAction(window->windowTitle(), this, &BrowserWindow::handleShowWindowTriggered);
255 action->setData(++index);
256 action->setCheckable(true);
257 if (window == this)
258 action->setChecked(true);
259 }
260 });
261 return menu;
262 }
263
createHelpMenu()264 QMenu *BrowserWindow::createHelpMenu()
265 {
266 QMenu *helpMenu = new QMenu(tr("&Help"));
267 helpMenu->addAction(tr("About &Qt"), qApp, QApplication::aboutQt);
268 return helpMenu;
269 }
270
createToolBar()271 QToolBar *BrowserWindow::createToolBar()
272 {
273 QToolBar *navigationBar = new QToolBar(tr("Navigation"));
274 navigationBar->setMovable(false);
275 navigationBar->toggleViewAction()->setEnabled(false);
276
277 m_historyBackAction = new QAction(this);
278 QList<QKeySequence> backShortcuts = QKeySequence::keyBindings(QKeySequence::Back);
279 for (auto it = backShortcuts.begin(); it != backShortcuts.end();) {
280 // Chromium already handles navigate on backspace when appropriate.
281 if ((*it)[0] == Qt::Key_Backspace)
282 it = backShortcuts.erase(it);
283 else
284 ++it;
285 }
286 // For some reason Qt doesn't bind the dedicated Back key to Back.
287 backShortcuts.append(QKeySequence(Qt::Key_Back));
288 m_historyBackAction->setShortcuts(backShortcuts);
289 m_historyBackAction->setIconVisibleInMenu(false);
290 m_historyBackAction->setIcon(QIcon(QStringLiteral(":go-previous.png")));
291 m_historyBackAction->setToolTip(tr("Go back in history"));
292 connect(m_historyBackAction, &QAction::triggered, [this]() {
293 m_tabWidget->triggerWebPageAction(QWebEnginePage::Back);
294 });
295 navigationBar->addAction(m_historyBackAction);
296
297 m_historyForwardAction = new QAction(this);
298 QList<QKeySequence> fwdShortcuts = QKeySequence::keyBindings(QKeySequence::Forward);
299 for (auto it = fwdShortcuts.begin(); it != fwdShortcuts.end();) {
300 if (((*it)[0] & Qt::Key_unknown) == Qt::Key_Backspace)
301 it = fwdShortcuts.erase(it);
302 else
303 ++it;
304 }
305 fwdShortcuts.append(QKeySequence(Qt::Key_Forward));
306 m_historyForwardAction->setShortcuts(fwdShortcuts);
307 m_historyForwardAction->setIconVisibleInMenu(false);
308 m_historyForwardAction->setIcon(QIcon(QStringLiteral(":go-next.png")));
309 m_historyForwardAction->setToolTip(tr("Go forward in history"));
310 connect(m_historyForwardAction, &QAction::triggered, [this]() {
311 m_tabWidget->triggerWebPageAction(QWebEnginePage::Forward);
312 });
313 navigationBar->addAction(m_historyForwardAction);
314
315 m_stopReloadAction = new QAction(this);
316 connect(m_stopReloadAction, &QAction::triggered, [this]() {
317 m_tabWidget->triggerWebPageAction(QWebEnginePage::WebAction(m_stopReloadAction->data().toInt()));
318 });
319 navigationBar->addAction(m_stopReloadAction);
320
321 m_urlLineEdit = new QLineEdit(this);
322 m_favAction = new QAction(this);
323 m_urlLineEdit->addAction(m_favAction, QLineEdit::LeadingPosition);
324 m_urlLineEdit->setClearButtonEnabled(true);
325 navigationBar->addWidget(m_urlLineEdit);
326
327 auto downloadsAction = new QAction(this);
328 downloadsAction->setIcon(QIcon(QStringLiteral(":go-bottom.png")));
329 downloadsAction->setToolTip(tr("Show downloads"));
330 navigationBar->addAction(downloadsAction);
331 connect(downloadsAction, &QAction::triggered, [this]() {
332 m_browser->downloadManagerWidget().show();
333 });
334
335 return navigationBar;
336 }
337
handleWebActionEnabledChanged(QWebEnginePage::WebAction action,bool enabled)338 void BrowserWindow::handleWebActionEnabledChanged(QWebEnginePage::WebAction action, bool enabled)
339 {
340 switch (action) {
341 case QWebEnginePage::Back:
342 m_historyBackAction->setEnabled(enabled);
343 break;
344 case QWebEnginePage::Forward:
345 m_historyForwardAction->setEnabled(enabled);
346 break;
347 case QWebEnginePage::Reload:
348 m_reloadAction->setEnabled(enabled);
349 break;
350 case QWebEnginePage::Stop:
351 m_stopAction->setEnabled(enabled);
352 break;
353 default:
354 qWarning("Unhandled webActionChanged signal");
355 }
356 }
357
handleWebViewTitleChanged(const QString & title)358 void BrowserWindow::handleWebViewTitleChanged(const QString &title)
359 {
360 QString suffix = m_profile->isOffTheRecord()
361 ? tr("Qt Simple Browser (Incognito)")
362 : tr("Qt Simple Browser");
363
364 if (title.isEmpty())
365 setWindowTitle(suffix);
366 else
367 setWindowTitle(title + " - " + suffix);
368 }
369
handleNewWindowTriggered()370 void BrowserWindow::handleNewWindowTriggered()
371 {
372 m_browser->createWindow();
373 }
374
handleNewIncognitoWindowTriggered()375 void BrowserWindow::handleNewIncognitoWindowTriggered()
376 {
377 m_browser->createWindow(/* offTheRecord: */ true);
378 }
379
handleFileOpenTriggered()380 void BrowserWindow::handleFileOpenTriggered()
381 {
382 QUrl url = QFileDialog::getOpenFileUrl(this, tr("Open Web Resource"), QString(),
383 tr("Web Resources (*.html *.htm *.svg *.png *.gif *.svgz);;All files (*.*)"));
384 if (url.isEmpty())
385 return;
386 currentTab()->setUrl(url);
387 }
388
handleFindActionTriggered()389 void BrowserWindow::handleFindActionTriggered()
390 {
391 if (!currentTab())
392 return;
393 bool ok = false;
394 QString search = QInputDialog::getText(this, tr("Find"),
395 tr("Find:"), QLineEdit::Normal,
396 m_lastSearch, &ok);
397 if (ok && !search.isEmpty()) {
398 m_lastSearch = search;
399 currentTab()->findText(m_lastSearch, 0, [this](bool found) {
400 if (!found)
401 statusBar()->showMessage(tr("\"%1\" not found.").arg(m_lastSearch));
402 });
403 }
404 }
405
closeEvent(QCloseEvent * event)406 void BrowserWindow::closeEvent(QCloseEvent *event)
407 {
408 if (m_tabWidget->count() > 1) {
409 int ret = QMessageBox::warning(this, tr("Confirm close"),
410 tr("Are you sure you want to close the window ?\n"
411 "There are %1 tabs open.").arg(m_tabWidget->count()),
412 QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
413 if (ret == QMessageBox::No) {
414 event->ignore();
415 return;
416 }
417 }
418 event->accept();
419 deleteLater();
420 }
421
tabWidget() const422 TabWidget *BrowserWindow::tabWidget() const
423 {
424 return m_tabWidget;
425 }
426
currentTab() const427 WebView *BrowserWindow::currentTab() const
428 {
429 return m_tabWidget->currentWebView();
430 }
431
id()432 QString BrowserWindow::id()
433 {
434 return "OKSIMPLEBROSSER";
435 }
436
handleWebViewLoadProgress(int progress)437 void BrowserWindow::handleWebViewLoadProgress(int progress)
438 {
439 static QIcon stopIcon(QStringLiteral(":process-stop.png"));
440 static QIcon reloadIcon(QStringLiteral(":view-refresh.png"));
441
442 if (0 < progress && progress < 100) {
443 m_stopReloadAction->setData(QWebEnginePage::Stop);
444 m_stopReloadAction->setIcon(stopIcon);
445 m_stopReloadAction->setToolTip(tr("Stop loading the current page"));
446 m_progressBar->setValue(progress);
447 } else {
448 m_stopReloadAction->setData(QWebEnginePage::Reload);
449 m_stopReloadAction->setIcon(reloadIcon);
450 m_stopReloadAction->setToolTip(tr("Reload the current page"));
451 m_progressBar->setValue(0);
452 }
453 }
454
handleShowWindowTriggered()455 void BrowserWindow::handleShowWindowTriggered()
456 {
457 if (QAction *action = qobject_cast<QAction*>(sender())) {
458 int offset = action->data().toInt();
459 QVector<BrowserWindow*> windows = m_browser->windows();
460 windows.at(offset)->activateWindow();
461 windows.at(offset)->currentTab()->setFocus();
462 }
463 }
464