1
2#############################################################################
3##
4## Copyright (C) 2018 The Qt Company Ltd.
5## Contact: http://www.qt.io/licensing/
6##
7## This file is part of the Qt for Python examples of the Qt Toolkit.
8##
9## $QT_BEGIN_LICENSE:BSD$
10## You may use this file under the terms of the BSD license as follows:
11##
12## "Redistribution and use in source and binary forms, with or without
13## modification, are permitted provided that the following conditions are
14## met:
15##   * Redistributions of source code must retain the above copyright
16##     notice, this list of conditions and the following disclaimer.
17##   * Redistributions in binary form must reproduce the above copyright
18##     notice, this list of conditions and the following disclaimer in
19##     the documentation and/or other materials provided with the
20##     distribution.
21##   * Neither the name of The Qt Company Ltd nor the names of its
22##     contributors may be used to endorse or promote products derived
23##     from this software without specific prior written permission.
24##
25##
26## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
27## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
28## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
29## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
30## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
31## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
32## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
33## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
34## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
35## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
36## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
37##
38## $QT_END_LICENSE$
39##
40#############################################################################
41
42"""PySide2 WebEngineWidgets Example"""
43
44import sys
45from bookmarkwidget import BookmarkWidget
46from browsertabwidget import BrowserTabWidget
47from downloadwidget import DownloadWidget
48from findtoolbar import FindToolBar
49from webengineview import WebEngineView
50from PySide2 import QtCore
51from PySide2.QtCore import Qt, QUrl
52from PySide2.QtGui import QKeySequence, QIcon
53from PySide2.QtWidgets import (QAction, QApplication, QDockWidget, QLabel,
54                               QLineEdit, QMainWindow, QToolBar)
55from PySide2.QtWebEngineWidgets import QWebEngineDownloadItem, QWebEnginePage
56
57main_windows = []
58
59
60def create_main_window():
61    """Creates a MainWindow using 75% of the available screen resolution."""
62    main_win = MainWindow()
63    main_windows.append(main_win)
64    available_geometry = app.desktop().availableGeometry(main_win)
65    main_win.resize(available_geometry.width() * 2 / 3,
66                    available_geometry.height() * 2 / 3)
67    main_win.show()
68    return main_win
69
70
71def create_main_window_with_browser():
72    """Creates a MainWindow with a BrowserTabWidget."""
73    main_win = create_main_window()
74    return main_win.add_browser_tab()
75
76
77class MainWindow(QMainWindow):
78    """Provides the parent window that includes the BookmarkWidget,
79    BrowserTabWidget, and a DownloadWidget, to offer the complete
80    web browsing experience."""
81
82    def __init__(self):
83        super(MainWindow, self).__init__()
84
85        self.setWindowTitle('PySide2 tabbed browser Example')
86
87        self._tab_widget = BrowserTabWidget(create_main_window_with_browser)
88        self._tab_widget.enabled_changed.connect(self._enabled_changed)
89        self._tab_widget.download_requested.connect(self._download_requested)
90        self.setCentralWidget(self._tab_widget)
91        self.connect(self._tab_widget, QtCore.SIGNAL("url_changed(QUrl)"),
92                     self.url_changed)
93
94        self._bookmark_dock = QDockWidget()
95        self._bookmark_dock.setWindowTitle('Bookmarks')
96        self._bookmark_widget = BookmarkWidget()
97        self._bookmark_widget.open_bookmark.connect(self.load_url)
98        self._bookmark_widget.open_bookmark_in_new_tab.connect(self.load_url_in_new_tab)
99        self._bookmark_dock.setWidget(self._bookmark_widget)
100        self.addDockWidget(Qt.LeftDockWidgetArea, self._bookmark_dock)
101
102        self._find_tool_bar = None
103
104        self._actions = {}
105        self._create_menu()
106
107        self._tool_bar = QToolBar()
108        self.addToolBar(self._tool_bar)
109        for action in self._actions.values():
110            if not action.icon().isNull():
111                self._tool_bar.addAction(action)
112
113        self._addres_line_edit = QLineEdit()
114        self._addres_line_edit.setClearButtonEnabled(True)
115        self._addres_line_edit.returnPressed.connect(self.load)
116        self._tool_bar.addWidget(self._addres_line_edit)
117        self._zoom_label = QLabel()
118        self.statusBar().addPermanentWidget(self._zoom_label)
119        self._update_zoom_label()
120
121        self._bookmarksToolBar = QToolBar()
122        self.addToolBar(Qt.TopToolBarArea, self._bookmarksToolBar)
123        self.insertToolBarBreak(self._bookmarksToolBar)
124        self._bookmark_widget.changed.connect(self._update_bookmarks)
125        self._update_bookmarks()
126
127    def _update_bookmarks(self):
128        self._bookmark_widget.populate_tool_bar(self._bookmarksToolBar)
129        self._bookmark_widget.populate_other(self._bookmark_menu, 3)
130
131    def _create_menu(self):
132        file_menu = self.menuBar().addMenu("&File")
133        exit_action = QAction(QIcon.fromTheme("application-exit"), "E&xit",
134                              self, shortcut="Ctrl+Q", triggered=qApp.quit)
135        file_menu.addAction(exit_action)
136
137        navigation_menu = self.menuBar().addMenu("&Navigation")
138
139        style_icons = ':/qt-project.org/styles/commonstyle/images/'
140        back_action = QAction(QIcon.fromTheme("go-previous",
141                                              QIcon(style_icons + 'left-32.png')),
142                              "Back", self,
143                              shortcut=QKeySequence(QKeySequence.Back),
144                              triggered=self._tab_widget.back)
145        self._actions[QWebEnginePage.Back] = back_action
146        back_action.setEnabled(False)
147        navigation_menu.addAction(back_action)
148        forward_action = QAction(QIcon.fromTheme("go-next",
149                                                 QIcon(style_icons + 'right-32.png')),
150                                 "Forward", self,
151                                 shortcut=QKeySequence(QKeySequence.Forward),
152                                 triggered=self._tab_widget.forward)
153        forward_action.setEnabled(False)
154        self._actions[QWebEnginePage.Forward] = forward_action
155
156        navigation_menu.addAction(forward_action)
157        reload_action = QAction(QIcon(style_icons + 'refresh-32.png'),
158                                "Reload", self,
159                                shortcut=QKeySequence(QKeySequence.Refresh),
160                                triggered=self._tab_widget.reload)
161        self._actions[QWebEnginePage.Reload] = reload_action
162        reload_action.setEnabled(False)
163        navigation_menu.addAction(reload_action)
164
165        navigation_menu.addSeparator()
166
167        new_tab_action = QAction("New Tab", self,
168                                 shortcut='Ctrl+T',
169                                 triggered=self.add_browser_tab)
170        navigation_menu.addAction(new_tab_action)
171
172        close_tab_action = QAction("Close Current Tab", self,
173                                   shortcut="Ctrl+W",
174                                   triggered=self._close_current_tab)
175        navigation_menu.addAction(close_tab_action)
176
177        navigation_menu.addSeparator()
178
179        history_action = QAction("History...", self,
180                                 triggered=self._tab_widget.show_history)
181        navigation_menu.addAction(history_action)
182
183        edit_menu = self.menuBar().addMenu("&Edit")
184
185        find_action = QAction("Find", self,
186                              shortcut=QKeySequence(QKeySequence.Find),
187                              triggered=self._show_find)
188        edit_menu.addAction(find_action)
189
190        edit_menu.addSeparator()
191        undo_action = QAction("Undo", self,
192                              shortcut=QKeySequence(QKeySequence.Undo),
193                              triggered=self._tab_widget.undo)
194        self._actions[QWebEnginePage.Undo] = undo_action
195        undo_action.setEnabled(False)
196        edit_menu.addAction(undo_action)
197
198        redo_action = QAction("Redo", self,
199                              shortcut=QKeySequence(QKeySequence.Redo),
200                              triggered=self._tab_widget.redo)
201        self._actions[QWebEnginePage.Redo] = redo_action
202        redo_action.setEnabled(False)
203        edit_menu.addAction(redo_action)
204
205        edit_menu.addSeparator()
206
207        cut_action = QAction("Cut", self,
208                             shortcut=QKeySequence(QKeySequence.Cut),
209                             triggered=self._tab_widget.cut)
210        self._actions[QWebEnginePage.Cut] = cut_action
211        cut_action.setEnabled(False)
212        edit_menu.addAction(cut_action)
213
214        copy_action = QAction("Copy", self,
215                              shortcut=QKeySequence(QKeySequence.Copy),
216                              triggered=self._tab_widget.copy)
217        self._actions[QWebEnginePage.Copy] = copy_action
218        copy_action.setEnabled(False)
219        edit_menu.addAction(copy_action)
220
221        paste_action = QAction("Paste", self,
222                               shortcut=QKeySequence(QKeySequence.Paste),
223                               triggered=self._tab_widget.paste)
224        self._actions[QWebEnginePage.Paste] = paste_action
225        paste_action.setEnabled(False)
226        edit_menu.addAction(paste_action)
227
228        edit_menu.addSeparator()
229
230        select_all_action = QAction("Select All", self,
231                                    shortcut=QKeySequence(QKeySequence.SelectAll),
232                                    triggered=self._tab_widget.select_all)
233        self._actions[QWebEnginePage.SelectAll] = select_all_action
234        select_all_action.setEnabled(False)
235        edit_menu.addAction(select_all_action)
236
237        self._bookmark_menu = self.menuBar().addMenu("&Bookmarks")
238        add_bookmark_action = QAction("&Add Bookmark", self,
239                                      triggered=self._add_bookmark)
240        self._bookmark_menu.addAction(add_bookmark_action)
241        add_tool_bar_bookmark_action = QAction("&Add Bookmark to Tool Bar", self,
242                                               triggered=self._add_tool_bar_bookmark)
243        self._bookmark_menu.addAction(add_tool_bar_bookmark_action)
244        self._bookmark_menu.addSeparator()
245
246        tools_menu = self.menuBar().addMenu("&Tools")
247        download_action = QAction("Open Downloads", self,
248                                  triggered=DownloadWidget.open_download_directory)
249        tools_menu.addAction(download_action)
250
251        window_menu = self.menuBar().addMenu("&Window")
252
253        window_menu.addAction(self._bookmark_dock.toggleViewAction())
254
255        window_menu.addSeparator()
256
257        zoom_in_action = QAction(QIcon.fromTheme("zoom-in"),
258                                 "Zoom In", self,
259                                 shortcut=QKeySequence(QKeySequence.ZoomIn),
260                                 triggered=self._zoom_in)
261        window_menu.addAction(zoom_in_action)
262        zoom_out_action = QAction(QIcon.fromTheme("zoom-out"),
263                                  "Zoom Out", self,
264                                  shortcut=QKeySequence(QKeySequence.ZoomOut),
265                                  triggered=self._zoom_out)
266        window_menu.addAction(zoom_out_action)
267
268        reset_zoom_action = QAction(QIcon.fromTheme("zoom-original"),
269                                    "Reset Zoom", self,
270                                    shortcut="Ctrl+0",
271                                    triggered=self._reset_zoom)
272        window_menu.addAction(reset_zoom_action)
273
274        about_menu = self.menuBar().addMenu("&About")
275        about_action = QAction("About Qt", self,
276                               shortcut=QKeySequence(QKeySequence.HelpContents),
277                               triggered=qApp.aboutQt)
278        about_menu.addAction(about_action)
279
280    def add_browser_tab(self):
281        return self._tab_widget.add_browser_tab()
282
283    def _close_current_tab(self):
284        if self._tab_widget.count() > 1:
285            self._tab_widget.close_current_tab()
286        else:
287            self.close()
288
289    def close_event(self, event):
290        main_windows.remove(self)
291        event.accept()
292
293    def load(self):
294        url_string = self._addres_line_edit.text().strip()
295        if url_string:
296            self.load_url_string(url_string)
297
298    def load_url_string(self, url_s):
299        url = QUrl.fromUserInput(url_s)
300        if (url.isValid()):
301            self.load_url(url)
302
303    def load_url(self, url):
304        self._tab_widget.load(url)
305
306    def load_url_in_new_tab(self, url):
307        self.add_browser_tab().load(url)
308
309    def url_changed(self, url):
310        self._addres_line_edit.setText(url.toString())
311
312    def _enabled_changed(self, web_action, enabled):
313        action = self._actions[web_action]
314        if action:
315            action.setEnabled(enabled)
316
317    def _add_bookmark(self):
318        index = self._tab_widget.currentIndex()
319        if index >= 0:
320            url = self._tab_widget.url()
321            title = self._tab_widget.tabText(index)
322            icon = self._tab_widget.tabIcon(index)
323            self._bookmark_widget.add_bookmark(url, title, icon)
324
325    def _add_tool_bar_bookmark(self):
326        index = self._tab_widget.currentIndex()
327        if index >= 0:
328            url = self._tab_widget.url()
329            title = self._tab_widget.tabText(index)
330            icon = self._tab_widget.tabIcon(index)
331            self._bookmark_widget.add_tool_bar_bookmark(url, title, icon)
332
333    def _zoom_in(self):
334        new_zoom = self._tab_widget.zoom_factor() * 1.5
335        if (new_zoom <= WebEngineView.maximum_zoom_factor()):
336            self._tab_widget.set_zoom_factor(new_zoom)
337            self._update_zoom_label()
338
339    def _zoom_out(self):
340        new_zoom = self._tab_widget.zoom_factor() / 1.5
341        if (new_zoom >= WebEngineView.minimum_zoom_factor()):
342            self._tab_widget.set_zoom_factor(new_zoom)
343            self._update_zoom_label()
344
345    def _reset_zoom(self):
346        self._tab_widget.set_zoom_factor(1)
347        self._update_zoom_label()
348
349    def _update_zoom_label(self):
350        percent = int(self._tab_widget.zoom_factor() * 100)
351        self._zoom_label.setText("{}%".format(percent))
352
353    def _download_requested(self, item):
354        # Remove old downloads before opening a new one
355        for old_download in self.statusBar().children():
356            if (type(old_download).__name__ == 'DownloadWidget' and
357                old_download.state() != QWebEngineDownloadItem.DownloadInProgress):
358                self.statusBar().removeWidget(old_download)
359                del old_download
360
361        item.accept()
362        download_widget = DownloadWidget(item)
363        download_widget.remove_requested.connect(self._remove_download_requested,
364                                                 Qt.QueuedConnection)
365        self.statusBar().addWidget(download_widget)
366
367    def _remove_download_requested(self):
368            download_widget = self.sender()
369            self.statusBar().removeWidget(download_widget)
370            del download_widget
371
372    def _show_find(self):
373        if self._find_tool_bar is None:
374            self._find_tool_bar = FindToolBar()
375            self._find_tool_bar.find.connect(self._tab_widget.find)
376            self.addToolBar(Qt.BottomToolBarArea, self._find_tool_bar)
377        else:
378            self._find_tool_bar.show()
379        self._find_tool_bar.focus_find()
380
381    def write_bookmarks(self):
382        self._bookmark_widget.write_bookmarks()
383
384
385if __name__ == '__main__':
386    app = QApplication(sys.argv)
387    main_win = create_main_window()
388    initial_urls = sys.argv[1:]
389    if not initial_urls:
390        initial_urls.append('http://qt.io')
391    for url in initial_urls:
392        main_win.load_url_in_new_tab(QUrl.fromUserInput(url))
393    exit_code = app.exec_()
394    main_win.write_bookmarks()
395    sys.exit(exit_code)
396