summaryrefslogtreecommitdiffstats
path: root/examples
diff options
context:
space:
mode:
authorJocelyn Turcotte <jocelyn.turcotte@digia.com>2013-08-05 16:37:31 +0200
committerJocelyn Turcotte <jocelyn.turcotte@digia.com>2013-08-20 18:15:09 +0200
commitccfeac7d1e474408e2cae169714326f6198eb17c (patch)
tree56d245e3277c2a455589f7d30b3bff9e5a8fb5b7 /examples
parent0498dc995ddac96009c5ef496545cdd6f49f367f (diff)
Import the demo browser into widget examples.
Import the sources as-is, without adding it to the build, to allow performing diffs later on the changes that were needed to port it to use QtWebEngine and manage source compatibility issues. Change-Id: Icf8a284881ce2153e9b5a1ba97dbe77096f1b88d Reviewed-by: Andras Becsi <andras.becsi@digia.com>
Diffstat (limited to 'examples')
-rw-r--r--examples/widgets/browser/Info_mac.plist43
-rw-r--r--examples/widgets/browser/addbookmarkdialog.ui98
-rw-r--r--examples/widgets/browser/autosaver.cpp93
-rw-r--r--examples/widgets/browser/autosaver.h75
-rw-r--r--examples/widgets/browser/bookmarks.cpp986
-rw-r--r--examples/widgets/browser/bookmarks.h309
-rw-r--r--examples/widgets/browser/bookmarks.ui106
-rw-r--r--examples/widgets/browser/browser.icnsbin0 -> 50218 bytes
-rw-r--r--examples/widgets/browser/browser.icobin0 -> 15374 bytes
-rw-r--r--examples/widgets/browser/browser.pro100
-rw-r--r--examples/widgets/browser/browser.rc1
-rw-r--r--examples/widgets/browser/browserapplication.cpp458
-rw-r--r--examples/widgets/browser/browserapplication.h118
-rw-r--r--examples/widgets/browser/browsermainwindow.cpp948
-rw-r--r--examples/widgets/browser/browsermainwindow.h167
-rw-r--r--examples/widgets/browser/chasewidget.cpp141
-rw-r--r--examples/widgets/browser/chasewidget.h85
-rw-r--r--examples/widgets/browser/cookiejar.cpp737
-rw-r--r--examples/widgets/browser/cookiejar.h203
-rw-r--r--examples/widgets/browser/cookies.ui106
-rw-r--r--examples/widgets/browser/cookiesexceptions.ui184
-rw-r--r--examples/widgets/browser/data/addtab.pngbin0 -> 469 bytes
-rw-r--r--examples/widgets/browser/data/browser.svg411
-rw-r--r--examples/widgets/browser/data/closetab.pngbin0 -> 516 bytes
-rw-r--r--examples/widgets/browser/data/data.qrc11
-rw-r--r--examples/widgets/browser/data/defaultbookmarks.xbel43
-rw-r--r--examples/widgets/browser/data/defaulticon.pngbin0 -> 1473 bytes
-rw-r--r--examples/widgets/browser/data/history.pngbin0 -> 1527 bytes
-rw-r--r--examples/widgets/browser/data/loading.gifbin0 -> 847 bytes
-rw-r--r--examples/widgets/browser/doc/images/browser-demo.pngbin0 -> 156342 bytes
-rw-r--r--examples/widgets/browser/doc/src/browser.qdoc41
-rw-r--r--examples/widgets/browser/downloaditem.ui134
-rw-r--r--examples/widgets/browser/downloadmanager.cpp578
-rw-r--r--examples/widgets/browser/downloadmanager.h161
-rw-r--r--examples/widgets/browser/downloads.ui83
-rw-r--r--examples/widgets/browser/edittableview.cpp77
-rw-r--r--examples/widgets/browser/edittableview.h60
-rw-r--r--examples/widgets/browser/edittreeview.cpp76
-rw-r--r--examples/widgets/browser/edittreeview.h60
-rw-r--r--examples/widgets/browser/history.cpp1291
-rw-r--r--examples/widgets/browser/history.h349
-rw-r--r--examples/widgets/browser/history.ui106
-rw-r--r--examples/widgets/browser/htmls/htmls.qrc5
-rw-r--r--examples/widgets/browser/htmls/notfound.html63
-rw-r--r--examples/widgets/browser/main.cpp52
-rw-r--r--examples/widgets/browser/modelmenu.cpp226
-rw-r--r--examples/widgets/browser/modelmenu.h104
-rw-r--r--examples/widgets/browser/networkaccessmanager.cpp214
-rw-r--r--examples/widgets/browser/networkaccessmanager.h77
-rw-r--r--examples/widgets/browser/passworddialog.ui111
-rw-r--r--examples/widgets/browser/proxy.ui104
-rw-r--r--examples/widgets/browser/searchlineedit.cpp240
-rw-r--r--examples/widgets/browser/searchlineedit.h102
-rw-r--r--examples/widgets/browser/settings.cpp321
-rw-r--r--examples/widgets/browser/settings.h73
-rw-r--r--examples/widgets/browser/settings.ui614
-rw-r--r--examples/widgets/browser/squeezelabel.cpp60
-rw-r--r--examples/widgets/browser/squeezelabel.h59
-rw-r--r--examples/widgets/browser/tabwidget.cpp834
-rw-r--r--examples/widgets/browser/tabwidget.h223
-rw-r--r--examples/widgets/browser/toolbarsearch.cpp163
-rw-r--r--examples/widgets/browser/toolbarsearch.h83
-rw-r--r--examples/widgets/browser/urllineedit.cpp342
-rw-r--r--examples/widgets/browser/urllineedit.h114
-rw-r--r--examples/widgets/browser/webview.cpp315
-rw-r--r--examples/widgets/browser/webview.h119
-rw-r--r--examples/widgets/browser/xbel.cpp282
-rw-r--r--examples/widgets/browser/xbel.h111
68 files changed, 13450 insertions, 0 deletions
diff --git a/examples/widgets/browser/Info_mac.plist b/examples/widgets/browser/Info_mac.plist
new file mode 100644
index 000000000..87435b2b6
--- /dev/null
+++ b/examples/widgets/browser/Info_mac.plist
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist SYSTEM "file://localhost/System/Library/DTDs/PropertyList.dtd">
+<plist version="0.9">
+<dict>
+ <key>CFBundleIconFile</key>
+ <string>@ICON@</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleGetInfoString</key>
+ <string>Created by Qt/QMake</string>
+ <key>CFBundleIdentifier</key>
+ <string>com.trolltech.DemoBrowser</string>
+ <key>CFBundleSignature</key>
+ <string>ttxt</string>
+ <key>CFBundleExecutable</key>
+ <string>@EXECUTABLE@</string>
+ <key>CFBundleDocumentTypes</key>
+ <array>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>html</string>
+ <string>htm</string>
+ <string>shtml</string>
+ <string>xht</string>
+ <string>xhtml</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>@ICON@</string>
+ <key>CFBundleTypeName</key>
+ <string>HTML Document</string>
+ <key>CFBundleTypeOSTypes</key>
+ <array>
+ <string>HTML</string>
+ </array>
+ <key>CFBundleTypeRole</key>
+ <string>Viewer</string>
+ </dict>
+ </array>
+ <key>NOTE</key>
+ <string>DemoBrowser by Digia Plc and/or its subsidiary(-ies)</string>
+</dict>
+</plist>
diff --git a/examples/widgets/browser/addbookmarkdialog.ui b/examples/widgets/browser/addbookmarkdialog.ui
new file mode 100644
index 000000000..3460d7bb8
--- /dev/null
+++ b/examples/widgets/browser/addbookmarkdialog.ui
@@ -0,0 +1,98 @@
+<ui version="4.0" >
+ <class>AddBookmarkDialog</class>
+ <widget class="QDialog" name="AddBookmarkDialog" >
+ <property name="geometry" >
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>240</width>
+ <height>168</height>
+ </rect>
+ </property>
+ <property name="windowTitle" >
+ <string>Add Bookmark</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout" >
+ <item>
+ <widget class="QLabel" name="label" >
+ <property name="text" >
+ <string>Type a name for the bookmark, and choose where to keep it.</string>
+ </property>
+ <property name="textFormat" >
+ <enum>Qt::PlainText</enum>
+ </property>
+ <property name="wordWrap" >
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="name" />
+ </item>
+ <item>
+ <widget class="QComboBox" name="location" />
+ </item>
+ <item>
+ <spacer name="verticalSpacer" >
+ <property name="orientation" >
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0" >
+ <size>
+ <width>20</width>
+ <height>2</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox" >
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons" >
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ <property name="centerButtons" >
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>AddBookmarkDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>AddBookmarkDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/examples/widgets/browser/autosaver.cpp b/examples/widgets/browser/autosaver.cpp
new file mode 100644
index 000000000..a6efa4ca1
--- /dev/null
+++ b/examples/widgets/browser/autosaver.cpp
@@ -0,0 +1,93 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the demonstration applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "autosaver.h"
+
+#include <QtCore/QDir>
+#include <QtCore/QCoreApplication>
+#include <QtCore/QMetaObject>
+#include <QtDebug>
+
+#define AUTOSAVE_IN 1000 * 3 // seconds
+#define MAXWAIT 1000 * 15 // seconds
+
+AutoSaver::AutoSaver(QObject *parent) : QObject(parent)
+{
+ Q_ASSERT(parent);
+}
+
+AutoSaver::~AutoSaver()
+{
+ if (m_timer.isActive())
+ qWarning() << "AutoSaver: still active when destroyed, changes not saved.";
+}
+
+void AutoSaver::changeOccurred()
+{
+ if (m_firstChange.isNull())
+ m_firstChange.start();
+
+ if (m_firstChange.elapsed() > MAXWAIT) {
+ saveIfNeccessary();
+ } else {
+ m_timer.start(AUTOSAVE_IN, this);
+ }
+}
+
+void AutoSaver::timerEvent(QTimerEvent *event)
+{
+ if (event->timerId() == m_timer.timerId()) {
+ saveIfNeccessary();
+ } else {
+ QObject::timerEvent(event);
+ }
+}
+
+void AutoSaver::saveIfNeccessary()
+{
+ if (!m_timer.isActive())
+ return;
+ m_timer.stop();
+ m_firstChange = QTime();
+ if (!QMetaObject::invokeMethod(parent(), "save", Qt::DirectConnection)) {
+ qWarning() << "AutoSaver: error invoking slot save() on parent";
+ }
+}
diff --git a/examples/widgets/browser/autosaver.h b/examples/widgets/browser/autosaver.h
new file mode 100644
index 000000000..cf499b066
--- /dev/null
+++ b/examples/widgets/browser/autosaver.h
@@ -0,0 +1,75 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the demonstration applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef AUTOSAVER_H
+#define AUTOSAVER_H
+
+#include <QtCore/QObject>
+#include <QtCore/QBasicTimer>
+#include <QtCore/QTime>
+
+/*
+ This class will call the save() slot on the parent object when the parent changes.
+ It will wait several seconds after changed() to combining multiple changes and
+ prevent continuous writing to disk.
+ */
+class AutoSaver : public QObject {
+
+Q_OBJECT
+
+public:
+ AutoSaver(QObject *parent);
+ ~AutoSaver();
+ void saveIfNeccessary();
+
+public slots:
+ void changeOccurred();
+
+protected:
+ void timerEvent(QTimerEvent *event);
+
+private:
+ QBasicTimer m_timer;
+ QTime m_firstChange;
+
+};
+
+#endif // AUTOSAVER_H
diff --git a/examples/widgets/browser/bookmarks.cpp b/examples/widgets/browser/bookmarks.cpp
new file mode 100644
index 000000000..be4127b03
--- /dev/null
+++ b/examples/widgets/browser/bookmarks.cpp
@@ -0,0 +1,986 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the demonstration applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "bookmarks.h"
+
+#include "autosaver.h"
+#include "browserapplication.h"
+#include "history.h"
+#include "xbel.h"
+
+#include <QtCore/QBuffer>
+#include <QtCore/QFile>
+#include <QtCore/QMimeData>
+
+#include <QtGui/QDesktopServices>
+#include <QtGui/QDragEnterEvent>
+#include <QtGui/QIcon>
+#include <QtWidgets/QFileDialog>
+#include <QtWidgets/QHeaderView>
+#include <QtWidgets/QMessageBox>
+#include <QtWidgets/QToolButton>
+
+#include <QWebSettings>
+
+#include <QtCore/QDebug>
+
+#define BOOKMARKBAR "Bookmarks Bar"
+#define BOOKMARKMENU "Bookmarks Menu"
+
+BookmarksManager::BookmarksManager(QObject *parent)
+ : QObject(parent)
+ , m_loaded(false)
+ , m_saveTimer(new AutoSaver(this))
+ , m_bookmarkRootNode(0)
+ , m_bookmarkModel(0)
+{
+ connect(this, SIGNAL(entryAdded(BookmarkNode*)),
+ m_saveTimer, SLOT(changeOccurred()));
+ connect(this, SIGNAL(entryRemoved(BookmarkNode*,int,BookmarkNode*)),
+ m_saveTimer, SLOT(changeOccurred()));
+ connect(this, SIGNAL(entryChanged(BookmarkNode*)),
+ m_saveTimer, SLOT(changeOccurred()));
+}
+
+BookmarksManager::~BookmarksManager()
+{
+ m_saveTimer->saveIfNeccessary();
+}
+
+void BookmarksManager::changeExpanded()
+{
+ m_saveTimer->changeOccurred();
+}
+
+void BookmarksManager::load()
+{
+ if (m_loaded)
+ return;
+ m_loaded = true;
+
+ QString dir = QStandardPaths::writableLocation(QStandardPaths::DataLocation);
+ QString bookmarkFile = dir + QLatin1String("/bookmarks.xbel");
+ if (!QFile::exists(bookmarkFile))
+ bookmarkFile = QLatin1String(":defaultbookmarks.xbel");
+
+ XbelReader reader;
+ m_bookmarkRootNode = reader.read(bookmarkFile);
+ if (reader.error() != QXmlStreamReader::NoError) {
+ QMessageBox::warning(0, QLatin1String("Loading Bookmark"),
+ tr("Error when loading bookmarks on line %1, column %2:\n"
+ "%3").arg(reader.lineNumber()).arg(reader.columnNumber()).arg(reader.errorString()));
+ }
+
+ BookmarkNode *toolbar = 0;
+ BookmarkNode *menu = 0;
+ QList<BookmarkNode*> others;
+ for (int i = m_bookmarkRootNode->children().count() - 1; i >= 0; --i) {
+ BookmarkNode *node = m_bookmarkRootNode->children().at(i);
+ if (node->type() == BookmarkNode::Folder) {
+ // Automatically convert
+ if (node->title == tr("Toolbar Bookmarks") && !toolbar) {
+ node->title = tr(BOOKMARKBAR);
+ }
+ if (node->title == tr(BOOKMARKBAR) && !toolbar) {
+ toolbar = node;
+ }
+
+ // Automatically convert
+ if (node->title == tr("Menu") && !menu) {
+ node->title = tr(BOOKMARKMENU);
+ }
+ if (node->title == tr(BOOKMARKMENU) && !menu) {
+ menu = node;
+ }
+ } else {
+ others.append(node);
+ }
+ m_bookmarkRootNode->remove(node);
+ }
+ Q_ASSERT(m_bookmarkRootNode->children().count() == 0);
+ if (!toolbar) {
+ toolbar = new BookmarkNode(BookmarkNode::Folder, m_bookmarkRootNode);
+ toolbar->title = tr(BOOKMARKBAR);
+ } else {
+ m_bookmarkRootNode->add(toolbar);
+ }
+
+ if (!menu) {
+ menu = new BookmarkNode(BookmarkNode::Folder, m_bookmarkRootNode);
+ menu->title = tr(BOOKMARKMENU);
+ } else {
+ m_bookmarkRootNode->add(menu);
+ }
+
+ for (int i = 0; i < others.count(); ++i)
+ menu->add(others.at(i));
+}
+
+void BookmarksManager::save() const
+{
+ if (!m_loaded)
+ return;
+
+ XbelWriter writer;
+ QString dir = QStandardPaths::writableLocation(QStandardPaths::DataLocation);
+ QString bookmarkFile = dir + QLatin1String("/bookmarks.xbel");
+ if (!writer.write(bookmarkFile, m_bookmarkRootNode))
+ qWarning() << "BookmarkManager: error saving to" << bookmarkFile;
+}
+
+void BookmarksManager::addBookmark(BookmarkNode *parent, BookmarkNode *node, int row)
+{
+ if (!m_loaded)
+ return;
+ Q_ASSERT(parent);
+ InsertBookmarksCommand *command = new InsertBookmarksCommand(this, parent, node, row);
+ m_commands.push(command);
+}
+
+void BookmarksManager::removeBookmark(BookmarkNode *node)
+{
+ if (!m_loaded)
+ return;
+
+ Q_ASSERT(node);
+ BookmarkNode *parent = node->parent();
+ int row = parent->children().indexOf(node);
+ RemoveBookmarksCommand *command = new RemoveBookmarksCommand(this, parent, row);
+ m_commands.push(command);
+}
+
+void BookmarksManager::setTitle(BookmarkNode *node, const QString &newTitle)
+{
+ if (!m_loaded)
+ return;
+
+ Q_ASSERT(node);
+ ChangeBookmarkCommand *command = new ChangeBookmarkCommand(this, node, newTitle, true);
+ m_commands.push(command);
+}
+
+void BookmarksManager::setUrl(BookmarkNode *node, const QString &newUrl)
+{
+ if (!m_loaded)
+ return;
+
+ Q_ASSERT(node);
+ ChangeBookmarkCommand *command = new ChangeBookmarkCommand(this, node, newUrl, false);
+ m_commands.push(command);
+}
+
+BookmarkNode *BookmarksManager::bookmarks()
+{
+ if (!m_loaded)
+ load();
+ return m_bookmarkRootNode;
+}
+
+BookmarkNode *BookmarksManager::menu()
+{
+ if (!m_loaded)
+ load();
+
+ for (int i = m_bookmarkRootNode->children().count() - 1; i >= 0; --i) {
+ BookmarkNode *node = m_bookmarkRootNode->children().at(i);
+ if (node->title == tr(BOOKMARKMENU))
+ return node;
+ }
+ Q_ASSERT(false);
+ return 0;
+}
+
+BookmarkNode *BookmarksManager::toolbar()
+{
+ if (!m_loaded)
+ load();
+
+ for (int i = m_bookmarkRootNode->children().count() - 1; i >= 0; --i) {
+ BookmarkNode *node = m_bookmarkRootNode->children().at(i);
+ if (node->title == tr(BOOKMARKBAR))
+ return node;
+ }
+ Q_ASSERT(false);
+ return 0;
+}
+
+BookmarksModel *BookmarksManager::bookmarksModel()
+{
+ if (!m_bookmarkModel)
+ m_bookmarkModel = new BookmarksModel(this, this);
+ return m_bookmarkModel;
+}
+
+void BookmarksManager::importBookmarks()
+{
+ QString fileName = QFileDialog::getOpenFileName(0, tr("Open File"),
+ QString(),
+ tr("XBEL (*.xbel *.xml)"));
+ if (fileName.isEmpty())
+ return;
+
+ XbelReader reader;
+ BookmarkNode *importRootNode = reader.read(fileName);
+ if (reader.error() != QXmlStreamReader::NoError) {
+ QMessageBox::warning(0, QLatin1String("Loading Bookmark"),
+ tr("Error when loading bookmarks on line %1, column %2:\n"
+ "%3").arg(reader.lineNumber()).arg(reader.columnNumber()).arg(reader.errorString()));
+ }
+
+ importRootNode->setType(BookmarkNode::Folder);
+ importRootNode->title = (tr("Imported %1").arg(QDate::currentDate().toString(Qt::SystemLocaleShortDate)));
+ addBookmark(menu(), importRootNode);
+}
+
+void BookmarksManager::exportBookmarks()
+{
+ QString fileName = QFileDialog::getSaveFileName(0, tr("Save File"),
+ tr("%1 Bookmarks.xbel").arg(QCoreApplication::applicationName()),
+ tr("XBEL (*.xbel *.xml)"));
+ if (fileName.isEmpty())
+ return;
+
+ XbelWriter writer;
+ if (!writer.write(fileName, m_bookmarkRootNode))
+ QMessageBox::critical(0, tr("Export error"), tr("error saving bookmarks"));
+}
+
+RemoveBookmarksCommand::RemoveBookmarksCommand(BookmarksManager *m_bookmarkManagaer, BookmarkNode *parent, int row)
+ : QUndoCommand(BookmarksManager::tr("Remove Bookmark"))
+ , m_row(row)
+ , m_bookmarkManagaer(m_bookmarkManagaer)
+ , m_node(parent->children().value(row))
+ , m_parent(parent)
+ , m_done(false)
+{
+}
+
+RemoveBookmarksCommand::~RemoveBookmarksCommand()
+{
+ if (m_done && !m_node->parent()) {
+ delete m_node;
+ }
+}
+
+void RemoveBookmarksCommand::undo()
+{
+ m_parent->add(m_node, m_row);
+ emit m_bookmarkManagaer->entryAdded(m_node);
+ m_done = false;
+}
+
+void RemoveBookmarksCommand::redo()
+{
+ m_parent->remove(m_node);
+ emit m_bookmarkManagaer->entryRemoved(m_parent, m_row, m_node);
+ m_done = true;
+}
+
+InsertBookmarksCommand::InsertBookmarksCommand(BookmarksManager *m_bookmarkManagaer,
+ BookmarkNode *parent, BookmarkNode *node, int row)
+ : RemoveBookmarksCommand(m_bookmarkManagaer, parent, row)
+{
+ setText(BookmarksManager::tr("Insert Bookmark"));
+ m_node = node;
+}
+
+ChangeBookmarkCommand::ChangeBookmarkCommand(BookmarksManager *m_bookmarkManagaer, BookmarkNode *node,
+ const QString &newValue, bool title)
+ : QUndoCommand()
+ , m_bookmarkManagaer(m_bookmarkManagaer)
+ , m_title(title)
+ , m_newValue(newValue)
+ , m_node(node)
+{
+ if (m_title) {
+ m_oldValue = m_node->title;
+ setText(BookmarksManager::tr("Name Change"));
+ } else {
+ m_oldValue = m_node->url;
+ setText(BookmarksManager::tr("Address Change"));
+ }
+}
+
+void ChangeBookmarkCommand::undo()
+{
+ if (m_title)
+ m_node->title = m_oldValue;
+ else
+ m_node->url = m_oldValue;
+ emit m_bookmarkManagaer->entryChanged(m_node);
+}
+
+void ChangeBookmarkCommand::redo()
+{
+ if (m_title)
+ m_node->title = m_newValue;
+ else
+ m_node->url = m_newValue;
+ emit m_bookmarkManagaer->entryChanged(m_node);
+}
+
+BookmarksModel::BookmarksModel(BookmarksManager *bookmarkManager, QObject *parent)
+ : QAbstractItemModel(parent)
+ , m_endMacro(false)
+ , m_bookmarksManager(bookmarkManager)
+{
+ connect(bookmarkManager, SIGNAL(entryAdded(BookmarkNode*)),
+ this, SLOT(entryAdded(BookmarkNode*)));
+ connect(bookmarkManager, SIGNAL(entryRemoved(BookmarkNode*,int,BookmarkNode*)),
+ this, SLOT(entryRemoved(BookmarkNode*,int,BookmarkNode*)));
+ connect(bookmarkManager, SIGNAL(entryChanged(BookmarkNode*)),
+ this, SLOT(entryChanged(BookmarkNode*)));
+}
+
+QModelIndex BookmarksModel::index(BookmarkNode *node) const
+{
+ BookmarkNode *parent = node->parent();
+ if (!parent)
+ return QModelIndex();
+ return createIndex(parent->children().indexOf(node), 0, node);
+}
+
+void BookmarksModel::entryAdded(BookmarkNode *item)
+{
+ Q_ASSERT(item && item->parent());
+ int row = item->parent()->children().indexOf(item);
+ BookmarkNode *parent = item->parent();
+ // item was already added so remove beore beginInsertRows is called
+ parent->remove(item);
+ beginInsertRows(index(parent), row, row);
+ parent->add(item, row);
+ endInsertRows();
+}
+
+void BookmarksModel::entryRemoved(BookmarkNode *parent, int row, BookmarkNode *item)
+{
+ // item was already removed, re-add so beginRemoveRows works
+ parent->add(item, row);
+ beginRemoveRows(index(parent), row, row);
+ parent->remove(item);
+ endRemoveRows();
+}
+
+void BookmarksModel::entryChanged(BookmarkNode *item)
+{
+ QModelIndex idx = index(item);
+ emit dataChanged(idx, idx);
+}
+
+bool BookmarksModel::removeRows(int row, int count, const QModelIndex &parent)
+{
+ if (row < 0 || count <= 0 || row + count > rowCount(parent))
+ return false;
+
+ BookmarkNode *bookmarkNode = node(parent);
+ for (int i = row + count - 1; i >= row; --i) {
+ BookmarkNode *node = bookmarkNode->children().at(i);
+ if (node == m_bookmarksManager->menu()
+ || node == m_bookmarksManager->toolbar())
+ continue;
+
+ m_bookmarksManager->removeBookmark(node);
+ }
+ if (m_endMacro) {
+ m_bookmarksManager->undoRedoStack()->endMacro();
+ m_endMacro = false;
+ }
+ return true;
+}
+
+QVariant BookmarksModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
+ switch (section) {
+ case 0: return tr("Title");
+ case 1: return tr("Address");
+ }
+ }
+ return QAbstractItemModel::headerData(section, orientation, role);
+}
+
+QVariant BookmarksModel::data(const QModelIndex &index, int role) const
+{
+ if (!index.isValid() || index.model() != this)
+ return QVariant();
+
+ const BookmarkNode *bookmarkNode = node(index);
+ switch (role) {
+ case Qt::EditRole:
+ case Qt::DisplayRole:
+ if (bookmarkNode->type() == BookmarkNode::Separator) {
+ switch (index.column()) {
+ case 0: return QString(50, 0xB7);
+ case 1: return QString();
+ }
+ }
+
+ switch (index.column()) {
+ case 0: return bookmarkNode->title;
+ case 1: return bookmarkNode->url;
+ }
+ break;
+ case BookmarksModel::UrlRole:
+ return QUrl(bookmarkNode->url);
+ break;
+ case BookmarksModel::UrlStringRole:
+ return bookmarkNode->url;
+ break;
+ case BookmarksModel::TypeRole:
+ return bookmarkNode->type();
+ break;
+ case BookmarksModel::SeparatorRole:
+ return (bookmarkNode->type() == BookmarkNode::Separator);
+ break;
+ case Qt::DecorationRole:
+ if (index.column() == 0) {
+ if (bookmarkNode->type() == BookmarkNode::Folder)
+ return QApplication::style()->standardIcon(QStyle::SP_DirIcon);
+ return BrowserApplication::instance()->icon(bookmarkNode->url);
+ }
+ }
+
+ return QVariant();
+}
+
+int BookmarksModel::columnCount(const QModelIndex &parent) const
+{
+ return (parent.column() > 0) ? 0 : 2;
+}
+
+int BookmarksModel::rowCount(const QModelIndex &parent) const
+{
+ if (parent.column() > 0)
+ return 0;
+
+ if (!parent.isValid())
+ return m_bookmarksManager->bookmarks()->children().count();
+
+ const BookmarkNode *item = static_cast<BookmarkNode*>(parent.internalPointer());
+ return item->children().count();
+}
+
+QModelIndex BookmarksModel::index(int row, int column, const QModelIndex &parent) const
+{
+ if (row < 0 || column < 0 || row >= rowCount(parent) || column >= columnCount(parent))
+ return QModelIndex();
+
+ // get the parent node
+ BookmarkNode *parentNode = node(parent);
+ return createIndex(row, column, parentNode->children().at(row));
+}
+
+QModelIndex BookmarksModel::parent(const QModelIndex &index) const
+{
+ if (!index.isValid())
+ return QModelIndex();
+
+ BookmarkNode *itemNode = node(index);
+ BookmarkNode *parentNode = (itemNode ? itemNode->parent() : 0);
+ if (!parentNode || parentNode == m_bookmarksManager->bookmarks())
+ return QModelIndex();
+
+ // get the parent's row
+ BookmarkNode *grandParentNode = parentNode->parent();
+ int parentRow = grandParentNode->children().indexOf(parentNode);
+ Q_ASSERT(parentRow >= 0);
+ return createIndex(parentRow, 0, parentNode);
+}
+
+bool BookmarksModel::hasChildren(const QModelIndex &parent) const
+{
+ if (!parent.isValid())
+ return true;
+ const BookmarkNode *parentNode = node(parent);
+ return (parentNode->type() == BookmarkNode::Folder);
+}
+
+Qt::ItemFlags BookmarksModel::flags(const QModelIndex &index) const
+{
+ if (!index.isValid())
+ return Qt::NoItemFlags;
+
+ Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
+
+ BookmarkNode *bookmarkNode = node(index);
+
+ if (bookmarkNode != m_bookmarksManager->menu()
+ && bookmarkNode != m_bookmarksManager->toolbar()) {
+ flags |= Qt::ItemIsDragEnabled;
+ if (bookmarkNode->type() != BookmarkNode::Separator)
+ flags |= Qt::ItemIsEditable;
+ }
+ if (hasChildren(index))
+ flags |= Qt::ItemIsDropEnabled;
+ return flags;
+}
+
+Qt::DropActions BookmarksModel::supportedDropActions () const
+{
+ return Qt::CopyAction | Qt::MoveAction;
+}
+
+#define MIMETYPE QLatin1String("application/bookmarks.xbel")
+
+QStringList BookmarksModel::mimeTypes() const
+{
+ QStringList types;
+ types << MIMETYPE;
+ return types;
+}
+
+QMimeData *BookmarksModel::mimeData(const QModelIndexList &indexes) const
+{
+ QMimeData *mimeData = new QMimeData();
+ QByteArray data;
+ QDataStream stream(&data, QIODevice::WriteOnly);
+ foreach (QModelIndex index, indexes) {
+ if (index.column() != 0 || !index.isValid())
+ continue;
+ QByteArray encodedData;
+ QBuffer buffer(&encodedData);
+ buffer.open(QBuffer::ReadWrite);
+ XbelWriter writer;
+ const BookmarkNode *parentNode = node(index);
+ writer.write(&buffer, parentNode);
+ stream << encodedData;
+ }
+ mimeData->setData(MIMETYPE, data);
+ return mimeData;
+}
+
+bool BookmarksModel::dropMimeData(const QMimeData *data,
+ Qt::DropAction action, int row, int column, const QModelIndex &parent)
+{
+ if (action == Qt::IgnoreAction)
+ return true;
+
+ if (!data->hasFormat(MIMETYPE)
+ || column > 0)
+ return false;
+
+ QByteArray ba = data->data(MIMETYPE);
+ QDataStream stream(&ba, QIODevice::ReadOnly);
+ if (stream.atEnd())
+ return false;
+
+ QUndoStack *undoStack = m_bookmarksManager->undoRedoStack();
+ undoStack->beginMacro(QLatin1String("Move Bookmarks"));
+
+ while (!stream.atEnd()) {
+ QByteArray encodedData;
+ stream >> encodedData;
+ QBuffer buffer(&encodedData);
+ buffer.open(QBuffer::ReadOnly);
+
+ XbelReader reader;
+ BookmarkNode *rootNode = reader.read(&buffer);
+ QList<BookmarkNode*> children = rootNode->children();
+ for (int i = 0; i < children.count(); ++i) {
+ BookmarkNode *bookmarkNode = children.at(i);
+ rootNode->remove(bookmarkNode);
+ row = qMax(0, row);
+ BookmarkNode *parentNode = node(parent);
+ m_bookmarksManager->addBookmark(parentNode, bookmarkNode, row);
+ m_endMacro = true;
+ }
+ delete rootNode;
+ }
+ return true;
+}
+
+bool BookmarksModel::setData(const QModelIndex &index, const QVariant &value, int role)
+{
+ if (!index.isValid() || (flags(index) & Qt::ItemIsEditable) == 0)
+ return false;
+
+ BookmarkNode *item = node(index);
+
+ switch (role) {
+ case Qt::EditRole:
+ case Qt::DisplayRole:
+ if (index.column() == 0) {
+ m_bookmarksManager->setTitle(item, value.toString());
+ break;
+ }
+ if (index.column() == 1) {
+ m_bookmarksManager->setUrl(item, value.toString());
+ break;
+ }
+ return false;
+ case BookmarksModel::UrlRole:
+ m_bookmarksManager->setUrl(item, value.toUrl().toString());
+ break;
+ case BookmarksModel::UrlStringRole:
+ m_bookmarksManager->setUrl(item, value.toString());
+ break;
+ default:
+ break;
+ return false;
+ }
+
+ return true;
+}
+
+BookmarkNode *BookmarksModel::node(const QModelIndex &index) const
+{
+ BookmarkNode *itemNode = static_cast<BookmarkNode*>(index.internalPointer());
+ if (!itemNode)
+ return m_bookmarksManager->bookmarks();
+ return itemNode;
+}
+
+
+AddBookmarkProxyModel::AddBookmarkProxyModel(QObject *parent)
+ : QSortFilterProxyModel(parent)
+{
+}
+
+int AddBookmarkProxyModel::columnCount(const QModelIndex &parent) const
+{
+ return qMin(1, QSortFilterProxyModel::columnCount(parent));
+}
+
+bool AddBookmarkProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
+{
+ QModelIndex idx = sourceModel()->index(source_row, 0, source_parent);
+ return sourceModel()->hasChildren(idx);
+}
+
+AddBookmarkDialog::AddBookmarkDialog(const QString &url, const QString &title, QWidget *parent, BookmarksManager *bookmarkManager)
+ : QDialog(parent)
+ , m_url(url)
+ , m_bookmarksManager(bookmarkManager)
+{
+ setWindowFlags(Qt::Sheet);
+ if (!m_bookmarksManager)
+ m_bookmarksManager = BrowserApplication::bookmarksManager();
+ setupUi(this);
+ QTreeView *view = new QTreeView(this);
+ m_proxyModel = new AddBookmarkProxyModel(this);
+ BookmarksModel *model = m_bookmarksManager->bookmarksModel();
+ m_proxyModel->setSourceModel(model);
+ view->setModel(m_proxyModel);
+ view->expandAll();
+ view->header()->setStretchLastSection(true);
+ view->header()->hide();
+ view->setItemsExpandable(false);
+ view->setRootIsDecorated(false);
+ view->setIndentation(10);
+ location->setModel(m_proxyModel);
+ view->show();
+ location->setView(view);
+ BookmarkNode *menu = m_bookmarksManager->menu();
+ QModelIndex idx = m_proxyModel->mapFromSource(model->index(menu));
+ view->setCurrentIndex(idx);
+ location->setCurrentIndex(idx.row());
+ name->setText(title);
+}
+
+void AddBookmarkDialog::accept()
+{
+ QModelIndex index = location->view()->currentIndex();
+ index = m_proxyModel->mapToSource(index);
+ if (!index.isValid())
+ index = m_bookmarksManager->bookmarksModel()->index(0, 0);
+ BookmarkNode *parent = m_bookmarksManager->bookmarksModel()->node(index);
+ BookmarkNode *bookmark = new BookmarkNode(BookmarkNode::Bookmark);
+ bookmark->url = m_url;
+ bookmark->title = name->text();
+ m_bookmarksManager->addBookmark(parent, bookmark);
+ QDialog::accept();
+}
+
+BookmarksMenu::BookmarksMenu(QWidget *parent)
+ : ModelMenu(parent)
+ , m_bookmarksManager(0)
+{
+ connect(this, SIGNAL(activated(QModelIndex)),
+ this, SLOT(activated(QModelIndex)));
+ setMaxRows(-1);
+ setHoverRole(BookmarksModel::UrlStringRole);
+ setSeparatorRole(BookmarksModel::SeparatorRole);
+}
+
+void BookmarksMenu::activated(const QModelIndex &index)
+{
+ emit openUrl(index.data(BookmarksModel::UrlRole).toUrl());
+}
+
+bool BookmarksMenu::prePopulated()
+{
+ m_bookmarksManager = BrowserApplication::bookmarksManager();
+ setModel(m_bookmarksManager->bookmarksModel());
+ setRootIndex(m_bookmarksManager->bookmarksModel()->index(1, 0));
+ // initial actions
+ for (int i = 0; i < m_initialActions.count(); ++i)
+ addAction(m_initialActions.at(i));
+ if (!m_initialActions.isEmpty())
+ addSeparator();
+ createMenu(model()->index(0, 0), 1, this);
+ return true;
+}
+
+void BookmarksMenu::setInitialActions(QList<QAction*> actions)
+{
+ m_initialActions = actions;
+ for (int i = 0; i < m_initialActions.count(); ++i)
+ addAction(m_initialActions.at(i));
+}
+
+BookmarksDialog::BookmarksDialog(QWidget *parent, BookmarksManager *manager)
+ : QDialog(parent)
+{
+ m_bookmarksManager = manager;
+ if (!m_bookmarksManager)
+ m_bookmarksManager = BrowserApplication::bookmarksManager();
+ setupUi(this);
+
+ tree->setUniformRowHeights(true);
+ tree->setSelectionBehavior(QAbstractItemView::SelectRows);
+ tree->setSelectionMode(QAbstractItemView::ContiguousSelection);
+ tree->setTextElideMode(Qt::ElideMiddle);
+ m_bookmarksModel = m_bookmarksManager->bookmarksModel();
+ m_proxyModel = new TreeProxyModel(this);
+ connect(search, SIGNAL(textChanged(QString)),
+ m_proxyModel, SLOT(setFilterFixedString(QString)));
+ connect(removeButton, SIGNAL(clicked()), tree, SLOT(removeOne()));
+ m_proxyModel->setSourceModel(m_bookmarksModel);
+ tree->setModel(m_proxyModel);
+ tree->setDragDropMode(QAbstractItemView::InternalMove);
+ tree->setExpanded(m_proxyModel->index(0, 0), true);
+ tree->setAlternatingRowColors(true);
+ QFontMetrics fm(font());
+ int header = fm.width(QLatin1Char('m')) * 40;
+ tree->header()->resizeSection(0, header);
+ tree->header()->setStretchLastSection(true);
+ connect(tree, SIGNAL(activated(QModelIndex)),
+ this, SLOT(open()));
+ tree->setContextMenuPolicy(Qt::CustomContextMenu);
+ connect(tree, SIGNAL(customContextMenuRequested(QPoint)),
+ this, SLOT(customContextMenuRequested(QPoint)));
+ connect(addFolderButton, SIGNAL(clicked()),
+ this, SLOT(newFolder()));
+ expandNodes(m_bookmarksManager->bookmarks());
+ setAttribute(Qt::WA_DeleteOnClose);
+}
+
+BookmarksDialog::~BookmarksDialog()
+{
+ if (saveExpandedNodes(tree->rootIndex()))
+ m_bookmarksManager->changeExpanded();
+}
+
+bool BookmarksDialog::saveExpandedNodes(const QModelIndex &parent)
+{
+ bool changed = false;
+ for (int i = 0; i < m_proxyModel->rowCount(parent); ++i) {
+ QModelIndex child = m_proxyModel->index(i, 0, parent);
+ QModelIndex sourceIndex = m_proxyModel->mapToSource(child);
+ BookmarkNode *childNode = m_bookmarksModel->node(sourceIndex);
+ bool wasExpanded = childNode->expanded;
+ if (tree->isExpanded(child)) {
+ childNode->expanded = true;
+ changed |= saveExpandedNodes(child);
+ } else {
+ childNode->expanded = false;
+ }
+ changed |= (wasExpanded != childNode->expanded);
+ }
+ return changed;
+}
+
+void BookmarksDialog::expandNodes(BookmarkNode *node)
+{
+ for (int i = 0; i < node->children().count(); ++i) {
+ BookmarkNode *childNode = node->children()[i];
+ if (childNode->expanded) {
+ QModelIndex idx = m_bookmarksModel->index(childNode);
+ idx = m_proxyModel->mapFromSource(idx);
+ tree->setExpanded(idx, true);
+ expandNodes(childNode);
+ }
+ }
+}
+
+void BookmarksDialog::customContextMenuRequested(const QPoint &pos)
+{
+ QMenu menu;
+ QModelIndex index = tree->indexAt(pos);
+ index = index.sibling(index.row(), 0);
+ if (index.isValid() && !tree->model()->hasChildren(index)) {
+ menu.addAction(tr("Open"), this, SLOT(open()));
+ menu.addSeparator();
+ }
+ menu.addAction(tr("Delete"), tree, SLOT(removeOne()));
+ menu.exec(QCursor::pos());
+}
+
+void BookmarksDialog::open()
+{
+ QModelIndex index = tree->currentIndex();
+ if (!index.parent().isValid())
+ return;
+ emit openUrl(index.sibling(index.row(), 1).data(BookmarksModel::UrlRole).toUrl());
+}
+
+void BookmarksDialog::newFolder()
+{
+ QModelIndex currentIndex = tree->currentIndex();
+ QModelIndex idx = currentIndex;
+ if (idx.isValid() && !idx.model()->hasChildren(idx))
+ idx = idx.parent();
+ if (!idx.isValid())
+ idx = tree->rootIndex();
+ idx = m_proxyModel->mapToSource(idx);
+ BookmarkNode *parent = m_bookmarksManager->bookmarksModel()->node(idx);
+ BookmarkNode *node = new BookmarkNode(BookmarkNode::Folder);
+ node->title = tr("New Folder");
+ m_bookmarksManager->addBookmark(parent, node, currentIndex.row() + 1);
+}
+
+BookmarksToolBar::BookmarksToolBar(BookmarksModel *model, QWidget *parent)
+ : QToolBar(tr("Bookmark"), parent)
+ , m_bookmarksModel(model)
+{
+ connect(this, SIGNAL(actionTriggered(QAction*)), this, SLOT(triggered(QAction*)));
+ setRootIndex(model->index(0, 0));
+ connect(m_bookmarksModel, SIGNAL(modelReset()), this, SLOT(build()));
+ connect(m_bookmarksModel, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(build()));
+ connect(m_bookmarksModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(build()));
+ connect(m_bookmarksModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(build()));
+ setAcceptDrops(true);
+}
+
+void BookmarksToolBar::dragEnterEvent(QDragEnterEvent *event)
+{
+ const QMimeData *mimeData = event->mimeData();
+ if (mimeData->hasUrls())
+ event->acceptProposedAction();
+ QToolBar::dragEnterEvent(event);
+}
+
+void BookmarksToolBar::dropEvent(QDropEvent *event)
+{
+ const QMimeData *mimeData = event->mimeData();
+ if (mimeData->hasUrls() && mimeData->hasText()) {
+ QList<QUrl> urls = mimeData->urls();
+ QAction *action = actionAt(event->pos());
+ QString dropText;
+ if (action)
+ dropText = action->text();
+ int row = -1;
+ QModelIndex parentIndex = m_root;
+ for (int i = 0; i < m_bookmarksModel->rowCount(m_root); ++i) {
+ QModelIndex idx = m_bookmarksModel->index(i, 0, m_root);
+ QString title = idx.data().toString();
+ if (title == dropText) {
+ row = i;
+ if (m_bookmarksModel->hasChildren(idx)) {
+ parentIndex = idx;
+ row = -1;
+ }
+ break;
+ }
+ }
+ BookmarkNode *bookmark = new BookmarkNode(BookmarkNode::Bookmark);
+ bookmark->url = urls.at(0).toString();
+ bookmark->title = mimeData->text();
+
+ BookmarkNode *parent = m_bookmarksModel->node(parentIndex);
+ BookmarksManager *bookmarksManager = m_bookmarksModel->bookmarksManager();
+ bookmarksManager->addBookmark(parent, bookmark, row);
+ event->acceptProposedAction();
+ }
+ QToolBar::dropEvent(event);
+}
+
+
+void BookmarksToolBar::setRootIndex(const QModelIndex &index)
+{
+ m_root = index;
+ build();
+}
+
+QModelIndex BookmarksToolBar::rootIndex() const
+{
+ return m_root;
+}
+
+void BookmarksToolBar::build()
+{
+ clear();
+ for (int i = 0; i < m_bookmarksModel->rowCount(m_root); ++i) {
+ QModelIndex idx = m_bookmarksModel->index(i, 0, m_root);
+ if (m_bookmarksModel->hasChildren(idx)) {
+ QToolButton *button = new QToolButton(this);
+ button->setPopupMode(QToolButton::InstantPopup);
+ button->setArrowType(Qt::DownArrow);
+ button->setText(idx.data().toString());
+ ModelMenu *menu = new ModelMenu(this);
+ connect(menu, SIGNAL(activated(QModelIndex)),
+ this, SLOT(activated(QModelIndex)));
+ menu->setModel(m_bookmarksModel);
+ menu->setRootIndex(idx);
+ menu->addAction(new QAction(menu));
+ button->setMenu(menu);
+ button->setToolButtonStyle(Qt::ToolButtonTextOnly);
+ QAction *a = addWidget(button);
+ a->setText(idx.data().toString());
+ } else {
+ QAction *action = addAction(idx.data().toString());
+ action->setData(idx.data(BookmarksModel::UrlRole));
+ }
+ }
+}
+
+void BookmarksToolBar::triggered(QAction *action)
+{
+ QVariant v = action->data();
+ if (v.canConvert<QUrl>()) {
+ emit openUrl(v.toUrl());
+ }
+}
+
+void BookmarksToolBar::activated(const QModelIndex &index)
+{
+ emit openUrl(index.data(BookmarksModel::UrlRole).toUrl());
+}
diff --git a/examples/widgets/browser/bookmarks.h b/examples/widgets/browser/bookmarks.h
new file mode 100644
index 000000000..be29666df
--- /dev/null
+++ b/examples/widgets/browser/bookmarks.h
@@ -0,0 +1,309 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the demonstration applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef BOOKMARKS_H
+#define BOOKMARKS_H
+
+#include <QtCore/QObject>
+#include <QtCore/QAbstractItemModel>
+
+#include <QtWidgets/QUndoCommand>
+
+/*!
+ Bookmark manager, owner of the bookmarks, loads, saves and basic tasks
+ */
+class AutoSaver;
+class BookmarkNode;
+class BookmarksModel;
+class BookmarksManager : public QObject
+{
+ Q_OBJECT
+
+signals:
+ void entryAdded(BookmarkNode *item);
+ void entryRemoved(BookmarkNode *parent, int row, BookmarkNode *item);
+ void entryChanged(BookmarkNode *item);
+
+public:
+ BookmarksManager(QObject *parent = 0);
+ ~BookmarksManager();
+
+ void addBookmark(BookmarkNode *parent, BookmarkNode *node, int row = -1);
+ void removeBookmark(BookmarkNode *node);
+ void setTitle(BookmarkNode *node, const QString &newTitle);
+ void setUrl(BookmarkNode *node, const QString &newUrl);
+ void changeExpanded();
+
+ BookmarkNode *bookmarks();
+ BookmarkNode *menu();
+ BookmarkNode *toolbar();
+
+ BookmarksModel *bookmarksModel();
+ QUndoStack *undoRedoStack() { return &m_commands; };
+
+public slots:
+ void importBookmarks();
+ void exportBookmarks();
+
+private slots:
+ void save() const;
+
+private:
+ void load();
+
+ bool m_loaded;
+ AutoSaver *m_saveTimer;
+ BookmarkNode *m_bookmarkRootNode;
+ BookmarksModel *m_bookmarkModel;
+ QUndoStack m_commands;
+
+ friend class RemoveBookmarksCommand;
+ friend class ChangeBookmarkCommand;
+};
+
+class RemoveBookmarksCommand : public QUndoCommand
+{
+
+public:
+ RemoveBookmarksCommand(BookmarksManager *m_bookmarkManagaer, BookmarkNode *parent, int row);
+ ~RemoveBookmarksCommand();
+ void undo();
+ void redo();
+
+protected:
+ int m_row;
+ BookmarksManager *m_bookmarkManagaer;
+ BookmarkNode *m_node;
+ BookmarkNode *m_parent;
+ bool m_done;
+};
+
+class InsertBookmarksCommand : public RemoveBookmarksCommand
+{
+
+public:
+ InsertBookmarksCommand(BookmarksManager *m_bookmarkManagaer,
+ BookmarkNode *parent, BookmarkNode *node, int row);
+ void undo() { RemoveBookmarksCommand::redo(); }
+ void redo() { RemoveBookmarksCommand::undo(); }
+
+};
+
+class ChangeBookmarkCommand : public QUndoCommand
+{
+
+public:
+ ChangeBookmarkCommand(BookmarksManager *m_bookmarkManagaer,
+ BookmarkNode *node, const QString &newValue, bool title);
+ void undo();
+ void redo();
+
+private:
+ BookmarksManager *m_bookmarkManagaer;
+ bool m_title;
+ QString m_oldValue;
+ QString m_newValue;
+ BookmarkNode *m_node;
+};
+
+/*!
+ BookmarksModel is a QAbstractItemModel wrapper around the BookmarkManager
+ */
+class BookmarksModel : public QAbstractItemModel
+{
+ Q_OBJECT
+
+public slots:
+ void entryAdded(BookmarkNode *item);
+ void entryRemoved(BookmarkNode *parent, int row, BookmarkNode *item);
+ void entryChanged(BookmarkNode *item);
+
+public:
+ enum Roles {
+ TypeRole = Qt::UserRole + 1,
+ UrlRole = Qt::UserRole + 2,
+ UrlStringRole = Qt::UserRole + 3,
+ SeparatorRole = Qt::UserRole + 4
+ };
+
+ BookmarksModel(BookmarksManager *bookmarkManager, QObject *parent = 0);
+ inline BookmarksManager *bookmarksManager() const { return m_bookmarksManager; }
+
+ QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
+ int columnCount(const QModelIndex &parent = QModelIndex()) const;
+ int rowCount(const QModelIndex &parent = QModelIndex()) const;
+ QModelIndex index(int, int, const QModelIndex& = QModelIndex()) const;
+ QModelIndex parent(const QModelIndex& index= QModelIndex()) const;
+ Qt::ItemFlags flags(const QModelIndex &index) const;
+ Qt::DropActions supportedDropActions () const;
+ bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex());
+ bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);
+ QMimeData *mimeData(const QModelIndexList &indexes) const;
+ QStringList mimeTypes() const;
+ bool dropMimeData(const QMimeData *data,
+ Qt::DropAction action, int row, int column, const QModelIndex &parent);
+ bool hasChildren(const QModelIndex &parent = QModelIndex()) const;
+
+ BookmarkNode *node(const QModelIndex &index) const;
+ QModelIndex index(BookmarkNode *node) const;
+
+private:
+
+ bool m_endMacro;
+ BookmarksManager *m_bookmarksManager;
+};
+
+// Menu that is dynamically populated from the bookmarks
+#include "modelmenu.h"
+class BookmarksMenu : public ModelMenu
+{
+ Q_OBJECT
+
+signals:
+ void openUrl(const QUrl &url);
+
+public:
+ BookmarksMenu(QWidget *parent = 0);
+ void setInitialActions(QList<QAction*> actions);
+
+protected:
+ bool prePopulated();
+
+private slots:
+ void activated(const QModelIndex &index);
+
+private:
+ BookmarksManager *m_bookmarksManager;
+ QList<QAction*> m_initialActions;
+};
+
+/*
+ Proxy model that filters out the bookmarks so only the folders
+ are left behind. Used in the add bookmark dialog combobox.
+ */
+#include <QtCore/QSortFilterProxyModel>
+class AddBookmarkProxyModel : public QSortFilterProxyModel
+{
+ Q_OBJECT
+public:
+ AddBookmarkProxyModel(QObject * parent = 0);
+ int columnCount(const QModelIndex & parent = QModelIndex()) const;
+
+protected:
+ bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const;
+};
+
+/*!
+ Add bookmark dialog
+ */
+#include "ui_addbookmarkdialog.h"
+class AddBookmarkDialog : public QDialog, public Ui_AddBookmarkDialog
+{
+ Q_OBJECT
+
+public:
+ AddBookmarkDialog(const QString &url, const QString &title, QWidget *parent = 0, BookmarksManager *bookmarkManager = 0);
+
+private slots:
+ void accept();
+
+private:
+ QString m_url;
+ BookmarksManager *m_bookmarksManager;
+ AddBookmarkProxyModel *m_proxyModel;
+};
+
+#include "ui_bookmarks.h"
+class TreeProxyModel;
+class BookmarksDialog : public QDialog, public Ui_BookmarksDialog
+{
+ Q_OBJECT
+
+signals:
+ void openUrl(const QUrl &url);
+
+public:
+ BookmarksDialog(QWidget *parent = 0, BookmarksManager *manager = 0);
+ ~BookmarksDialog();
+
+private slots:
+ void customContextMenuRequested(const QPoint &pos);
+ void open();
+ void newFolder();
+
+private:
+ void expandNodes(BookmarkNode *node);
+ bool saveExpandedNodes(const QModelIndex &parent);
+
+ BookmarksManager *m_bookmarksManager;
+ BookmarksModel *m_bookmarksModel;
+ TreeProxyModel *m_proxyModel;
+};
+
+#include <QtWidgets/QToolBar>
+class BookmarksToolBar : public QToolBar
+{
+ Q_OBJECT
+
+signals:
+ void openUrl(const QUrl &url);
+
+public:
+ BookmarksToolBar(BookmarksModel *model, QWidget *parent = 0);
+ void setRootIndex(const QModelIndex &index);
+ QModelIndex rootIndex() const;
+
+protected:
+ void dragEnterEvent(QDragEnterEvent *event);
+ void dropEvent(QDropEvent *event);
+
+private slots:
+ void triggered(QAction *action);
+ void activated(const QModelIndex &index);
+ void build();
+
+private:
+ BookmarksModel *m_bookmarksModel;
+ QPersistentModelIndex m_root;
+};
+
+#endif // BOOKMARKS_H
diff --git a/examples/widgets/browser/bookmarks.ui b/examples/widgets/browser/bookmarks.ui
new file mode 100644
index 000000000..c893e941d
--- /dev/null
+++ b/examples/widgets/browser/bookmarks.ui
@@ -0,0 +1,106 @@
+<ui version="4.0" >
+ <class>BookmarksDialog</class>
+ <widget class="QDialog" name="BookmarksDialog" >
+ <property name="geometry" >
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>758</width>
+ <height>450</height>
+ </rect>
+ </property>
+ <property name="windowTitle" >
+ <string>Bookmarks</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout" >
+ <item row="0" column="0" >
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0" >
+ <size>
+ <width>252</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="0" column="1" >
+ <widget class="SearchLineEdit" name="search" />
+ </item>
+ <item row="1" column="0" colspan="2" >
+ <widget class="EditTreeView" name="tree" />
+ </item>
+ <item row="2" column="0" colspan="2" >
+ <layout class="QHBoxLayout" >
+ <item>
+ <widget class="QPushButton" name="removeButton" >
+ <property name="text" >
+ <string>&amp;Remove</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="addFolderButton" >
+ <property name="text" >
+ <string>Add Folder</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0" >
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox" >
+ <property name="standardButtons" >
+ <set>QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>SearchLineEdit</class>
+ <extends>QLineEdit</extends>
+ <header>searchlineedit.h</header>
+ </customwidget>
+ <customwidget>
+ <class>EditTreeView</class>
+ <extends>QTreeView</extends>
+ <header>edittreeview.h</header>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>BookmarksDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>472</x>
+ <y>329</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>461</x>
+ <y>356</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/examples/widgets/browser/browser.icns b/examples/widgets/browser/browser.icns
new file mode 100644
index 000000000..f591ae48a
--- /dev/null
+++ b/examples/widgets/browser/browser.icns
Binary files differ
diff --git a/examples/widgets/browser/browser.ico b/examples/widgets/browser/browser.ico
new file mode 100644
index 000000000..7f9be934d
--- /dev/null
+++ b/examples/widgets/browser/browser.ico
Binary files differ
diff --git a/examples/widgets/browser/browser.pro b/examples/widgets/browser/browser.pro
new file mode 100644
index 000000000..97559ed3d
--- /dev/null
+++ b/examples/widgets/browser/browser.pro
@@ -0,0 +1,100 @@
+TEMPLATE = app
+TARGET = browser
+QT += webkitwidgets network widgets printsupport
+
+qtHaveModule(uitools):!embedded: QT += uitools
+else: DEFINES += QT_NO_UITOOLS
+
+FORMS += \
+ addbookmarkdialog.ui \
+ bookmarks.ui \
+ cookies.ui \
+ cookiesexceptions.ui \
+ downloaditem.ui \
+ downloads.ui \
+ history.ui \
+ passworddialog.ui \
+ proxy.ui \
+ settings.ui
+
+HEADERS += \
+ autosaver.h \
+ bookmarks.h \
+ browserapplication.h \
+ browsermainwindow.h \
+ chasewidget.h \
+ cookiejar.h \
+ downloadmanager.h \
+ edittableview.h \
+ edittreeview.h \
+ history.h \
+ modelmenu.h \
+ networkaccessmanager.h \
+ searchlineedit.h \
+ settings.h \
+ squeezelabel.h \
+ tabwidget.h \
+ toolbarsearch.h \
+ urllineedit.h \
+ webview.h \
+ xbel.h
+
+SOURCES += \
+ autosaver.cpp \
+ bookmarks.cpp \
+ browserapplication.cpp \
+ browsermainwindow.cpp \
+ chasewidget.cpp \
+ cookiejar.cpp \
+ downloadmanager.cpp \
+ edittableview.cpp \
+ edittreeview.cpp \
+ history.cpp \
+ modelmenu.cpp \
+ networkaccessmanager.cpp \
+ searchlineedit.cpp \
+ settings.cpp \
+ squeezelabel.cpp \
+ tabwidget.cpp \
+ toolbarsearch.cpp \
+ urllineedit.cpp \
+ webview.cpp \
+ xbel.cpp \
+ main.cpp
+
+RESOURCES += data/data.qrc htmls/htmls.qrc
+
+build_all:!build_pass {
+ CONFIG -= build_all
+ CONFIG += release
+}
+
+win32 {
+ RC_FILE = browser.rc
+}
+
+mac {
+ ICON = browser.icns
+ QMAKE_INFO_PLIST = Info_mac.plist
+ TARGET = Browser
+
+ # No 64-bit Flash on Mac, so build the browser 32-bit
+ contains(QT_CONFIG, x86) {
+ CONFIG -= x86_64
+ CONFIG += x86
+ }
+ contains(QT_CONFIG, ppc) {
+ CONFIG -= ppc64
+ CONFIG += ppc
+ }
+}
+
+wince*: {
+ DEPLOYMENT_PLUGIN += qjpeg qgif
+}
+
+EXAMPLE_FILES = Info_mac.plist browser.icns browser.ico browser.rc
+
+# install
+target.path = $$[QT_INSTALL_EXAMPLES]/webkitwidgets/browser
+INSTALLS += target
diff --git a/examples/widgets/browser/browser.rc b/examples/widgets/browser/browser.rc
new file mode 100644
index 000000000..39e17e973
--- /dev/null
+++ b/examples/widgets/browser/browser.rc
@@ -0,0 +1 @@
+IDI_ICON1 ICON DISCARDABLE "browser.ico"
diff --git a/examples/widgets/browser/browserapplication.cpp b/examples/widgets/browser/browserapplication.cpp
new file mode 100644
index 000000000..5230e58f9
--- /dev/null
+++ b/examples/widgets/browser/browserapplication.cpp
@@ -0,0 +1,458 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the demonstration applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "browserapplication.h"
+
+#include "bookmarks.h"
+#include "browsermainwindow.h"
+#include "cookiejar.h"
+#include "downloadmanager.h"
+#include "history.h"
+#include "networkaccessmanager.h"
+#include "tabwidget.h"
+#include "webview.h"
+
+#include <QtCore/QBuffer>
+#include <QtCore/QDir>
+#include <QtCore/QLibraryInfo>
+#include <QtCore/QSettings>
+#include <QtCore/QTextStream>
+#include <QtCore/QTranslator>
+
+#include <QtGui/QDesktopServices>
+#include <QtGui/QFileOpenEvent>
+#include <QtWidgets/QMessageBox>
+
+#include <QtNetwork/QLocalServer>
+#include <QtNetwork/QLocalSocket>
+#include <QtNetwork/QNetworkProxy>
+#include <QtNetwork/QSslSocket>
+
+#include <QWebSettings>
+
+#include <QtCore/QDebug>
+
+DownloadManager *BrowserApplication::s_downloadManager = 0;
+HistoryManager *BrowserApplication::s_historyManager = 0;
+NetworkAccessManager *BrowserApplication::s_networkAccessManager = 0;
+BookmarksManager *BrowserApplication::s_bookmarksManager = 0;
+
+BrowserApplication::BrowserApplication(int &argc, char **argv)
+ : QApplication(argc, argv)
+ , m_localServer(0)
+{
+ QCoreApplication::setOrganizationName(QLatin1String("Qt"));
+ QCoreApplication::setApplicationName(QLatin1String("demobrowser"));
+ QCoreApplication::setApplicationVersion(QLatin1String("0.1"));
+#ifdef Q_WS_QWS
+ // Use a different server name for QWS so we can run an X11
+ // browser and a QWS browser in parallel on the same machine for
+ // debugging
+ QString serverName = QCoreApplication::applicationName() + QLatin1String("_qws");
+#else
+ QString serverName = QCoreApplication::applicationName();
+#endif
+ QLocalSocket socket;
+ socket.connectToServer(serverName);
+ if (socket.waitForConnected(500)) {
+ QTextStream stream(&socket);
+ QStringList args = QCoreApplication::arguments();
+ if (args.count() > 1)
+ stream << args.last();
+ else
+ stream << QString();
+ stream.flush();
+ socket.waitForBytesWritten();
+ return;
+ }
+
+#if defined(Q_WS_MAC)
+ QApplication::setQuitOnLastWindowClosed(false);
+#else
+ QApplication::setQuitOnLastWindowClosed(true);
+#endif
+
+ m_localServer = new QLocalServer(this);
+ connect(m_localServer, SIGNAL(newConnection()),
+ this, SLOT(newLocalSocketConnection()));
+ if (!m_localServer->listen(serverName)) {
+ if (m_localServer->serverError() == QAbstractSocket::AddressInUseError
+ && QFile::exists(m_localServer->serverName())) {
+ QFile::remove(m_localServer->serverName());
+ m_localServer->listen(serverName);
+ }
+ }
+
+#ifndef QT_NO_OPENSSL
+ if (!QSslSocket::supportsSsl()) {
+ QMessageBox::information(0, "Demo Browser",
+ "This system does not support OpenSSL. SSL websites will not be available.");
+ }
+#endif
+
+ QDesktopServices::setUrlHandler(QLatin1String("http"), this, "openUrl");
+ QString localSysName = QLocale::system().name();
+
+ installTranslator(QLatin1String("qt_") + localSysName);
+
+ QSettings settings;
+ settings.beginGroup(QLatin1String("sessions"));
+ m_lastSession = settings.value(QLatin1String("lastSession")).toByteArray();
+ settings.endGroup();
+
+#if defined(Q_WS_MAC)
+ connect(this, SIGNAL(lastWindowClosed()),
+ this, SLOT(lastWindowClosed()));
+#endif
+
+ QTimer::singleShot(0, this, SLOT(postLaunch()));
+}
+
+BrowserApplication::~BrowserApplication()
+{
+ delete s_downloadManager;
+ for (int i = 0; i < m_mainWindows.size(); ++i) {
+ BrowserMainWindow *window = m_mainWindows.at(i);
+ delete window;
+ }
+ delete s_networkAccessManager;
+ delete s_bookmarksManager;
+}
+
+#if defined(Q_WS_MAC)
+void BrowserApplication::lastWindowClosed()
+{
+ clean();
+ BrowserMainWindow *mw = new BrowserMainWindow;
+ mw->slotHome();
+ m_mainWindows.prepend(mw);
+}
+#endif
+
+BrowserApplication *BrowserApplication::instance()
+{
+ return (static_cast<BrowserApplication *>(QCoreApplication::instance()));
+}
+
+#if defined(Q_WS_MAC)
+#include <QtWidgets/QMessageBox>
+void BrowserApplication::quitBrowser()
+{
+ clean();
+ int tabCount = 0;
+ for (int i = 0; i < m_mainWindows.count(); ++i) {
+ tabCount =+ m_mainWindows.at(i)->tabWidget()->count();
+ }
+
+ if (tabCount > 1) {
+ int ret = QMessageBox::warning(mainWindow(), QString(),
+ tr("There are %1 windows and %2 tabs open\n"
+ "Do you want to quit anyway?").arg(m_mainWindows.count()).arg(tabCount),
+ QMessageBox::Yes | QMessageBox::No,
+ QMessageBox::No);
+ if (ret == QMessageBox::No)
+ return;
+ }
+
+ exit(0);
+}
+#endif
+
+/*!
+ Any actions that can be delayed until the window is visible
+ */
+void BrowserApplication::postLaunch()
+{
+ QString directory = QStandardPaths::writableLocation(QStandardPaths::DataLocation);
+ if (directory.isEmpty())
+ directory = QDir::homePath() + QLatin1String("/.") + QCoreApplication::applicationName();
+ QWebSettings::setIconDatabasePath(directory);
+ QWebSettings::setOfflineStoragePath(directory);
+
+ setWindowIcon(QIcon(QLatin1String(":browser.svg")));
+
+ loadSettings();
+
+ // newMainWindow() needs to be called in main() for this to happen
+ if (m_mainWindows.count() > 0) {
+ QStringList args = QCoreApplication::arguments();
+ if (args.count() > 1)
+ mainWindow()->loadPage(args.last());
+ else
+ mainWindow()->slotHome();
+ }
+ BrowserApplication::historyManager();
+}
+
+void BrowserApplication::loadSettings()
+{
+ QSettings settings;
+ settings.beginGroup(QLatin1String("websettings"));
+
+ QWebSettings *defaultSettings = QWebSettings::globalSettings();
+ QString standardFontFamily = defaultSettings->fontFamily(QWebSettings::StandardFont);
+ int standardFontSize = defaultSettings->fontSize(QWebSettings::DefaultFontSize);
+ QFont standardFont = QFont(standardFontFamily, standardFontSize);
+ standardFont = qvariant_cast<QFont>(settings.value(QLatin1String("standardFont"), standardFont));
+ defaultSettings->setFontFamily(QWebSettings::StandardFont, standardFont.family());
+ defaultSettings->setFontSize(QWebSettings::DefaultFontSize, standardFont.pointSize());
+
+ QString fixedFontFamily = defaultSettings->fontFamily(QWebSettings::FixedFont);
+ int fixedFontSize = defaultSettings->fontSize(QWebSettings::DefaultFixedFontSize);
+ QFont fixedFont = QFont(fixedFontFamily, fixedFontSize);
+ fixedFont = qvariant_cast<QFont>(settings.value(QLatin1String("fixedFont"), fixedFont));
+ defaultSettings->setFontFamily(QWebSettings::FixedFont, fixedFont.family());
+ defaultSettings->setFontSize(QWebSettings::DefaultFixedFontSize, fixedFont.pointSize());
+
+ defaultSettings->setAttribute(QWebSettings::JavascriptEnabled, settings.value(QLatin1String("enableJavascript"), true).toBool());
+ defaultSettings->setAttribute(QWebSettings::PluginsEnabled, settings.value(QLatin1String("enablePlugins"), true).toBool());
+
+ QUrl url = settings.value(QLatin1String("userStyleSheet")).toUrl();
+ defaultSettings->setUserStyleSheetUrl(url);
+
+ defaultSettings->setAttribute(QWebSettings::DnsPrefetchEnabled, true);
+
+ settings.endGroup();
+}
+
+QList<BrowserMainWindow*> BrowserApplication::mainWindows()
+{
+ clean();
+ QList<BrowserMainWindow*> list;
+ for (int i = 0; i < m_mainWindows.count(); ++i)
+ list.append(m_mainWindows.at(i));
+ return list;
+}
+
+void BrowserApplication::clean()
+{
+ // cleanup any deleted main windows first
+ for (int i = m_mainWindows.count() - 1; i >= 0; --i)
+ if (m_mainWindows.at(i).isNull())
+ m_mainWindows.removeAt(i);
+}
+
+void BrowserApplication::saveSession()
+{
+ QWebSettings *globalSettings = QWebSettings::globalSettings();
+ if (globalSettings->testAttribute(QWebSettings::PrivateBrowsingEnabled))
+ return;
+
+ clean();
+
+ QSettings settings;
+ settings.beginGroup(QLatin1String("sessions"));
+
+ QByteArray data;
+ QBuffer buffer(&data);
+ QDataStream stream(&buffer);
+ buffer.open(QIODevice::ReadWrite);
+
+ stream << m_mainWindows.count();
+ for (int i = 0; i < m_mainWindows.count(); ++i)
+ stream << m_mainWindows.at(i)->saveState();
+ settings.setValue(QLatin1String("lastSession"), data);
+ settings.endGroup();
+}
+
+bool BrowserApplication::canRestoreSession() const
+{
+ return !m_lastSession.isEmpty();
+}
+
+void BrowserApplication::restoreLastSession()
+{
+ QList<QByteArray> windows;
+ QBuffer buffer(&m_lastSession);
+ QDataStream stream(&buffer);
+ buffer.open(QIODevice::ReadOnly);
+ int windowCount;
+ stream >> windowCount;
+ for (int i = 0; i < windowCount; ++i) {
+ QByteArray windowState;
+ stream >> windowState;
+ windows.append(windowState);
+ }
+ for (int i = 0; i < windows.count(); ++i) {
+ BrowserMainWindow *newWindow = 0;
+ if (m_mainWindows.count() == 1
+ && mainWindow()->tabWidget()->count() == 1
+ && mainWindow()->currentTab()->url() == QUrl()) {
+ newWindow = mainWindow();
+ } else {
+ newWindow = newMainWindow();
+ }
+ newWindow->restoreState(windows.at(i));
+ }
+}
+
+bool BrowserApplication::isTheOnlyBrowser() const
+{
+ return (m_localServer != 0);
+}
+
+void BrowserApplication::installTranslator(const QString &name)
+{
+ QTranslator *translator = new QTranslator(this);
+ translator->load(name, QLibraryInfo::location(QLibraryInfo::TranslationsPath));
+ QApplication::installTranslator(translator);
+}
+
+#if defined(Q_WS_MAC)
+bool BrowserApplication::event(QEvent* event)
+{
+ switch (event->type()) {
+ case QEvent::ApplicationActivate: {
+ clean();
+ if (!m_mainWindows.isEmpty()) {
+ BrowserMainWindow *mw = mainWindow();
+ if (mw && !mw->isMinimized()) {
+ mainWindow()->show();
+ }
+ return true;
+ }
+ }
+ case QEvent::FileOpen:
+ if (!m_mainWindows.isEmpty()) {
+ mainWindow()->loadPage(static_cast<QFileOpenEvent *>(event)->file());
+ return true;
+ }
+ default:
+ break;
+ }
+ return QApplication::event(event);
+}
+#endif
+
+void BrowserApplication::openUrl(const QUrl &url)
+{
+ mainWindow()->loadPage(url.toString());
+}
+
+BrowserMainWindow *BrowserApplication::newMainWindow()
+{
+ BrowserMainWindow *browser = new BrowserMainWindow();
+ m_mainWindows.prepend(browser);
+ browser->show();
+ return browser;
+}
+
+BrowserMainWindow *BrowserApplication::mainWindow()
+{
+ clean();
+ if (m_mainWindows.isEmpty())
+ newMainWindow();
+ return m_mainWindows[0];
+}
+
+void BrowserApplication::newLocalSocketConnection()
+{
+ QLocalSocket *socket = m_localServer->nextPendingConnection();
+ if (!socket)
+ return;
+ socket->waitForReadyRead(1000);
+ QTextStream stream(socket);
+ QString url;
+ stream >> url;
+ if (!url.isEmpty()) {
+ QSettings settings;
+ settings.beginGroup(QLatin1String("general"));
+ int openLinksIn = settings.value(QLatin1String("openLinksIn"), 0).toInt();
+ settings.endGroup();
+ if (openLinksIn == 1)
+ newMainWindow();
+ else
+ mainWindow()->tabWidget()->newTab();
+ openUrl(url);
+ }
+ delete socket;
+ mainWindow()->raise();
+ mainWindow()->activateWindow();
+}
+
+CookieJar *BrowserApplication::cookieJar()
+{
+ return (CookieJar*)networkAccessManager()->cookieJar();
+}
+
+DownloadManager *BrowserApplication::downloadManager()
+{
+ if (!s_downloadManager) {
+ s_downloadManager = new DownloadManager();
+ }
+ return s_downloadManager;
+}
+
+NetworkAccessManager *BrowserApplication::networkAccessManager()
+{
+ if (!s_networkAccessManager) {
+ s_networkAccessManager = new NetworkAccessManager();
+ s_networkAccessManager->setCookieJar(new CookieJar);
+ }
+ return s_networkAccessManager;
+}
+
+HistoryManager *BrowserApplication::historyManager()
+{
+ if (!s_historyManager) {
+ s_historyManager = new HistoryManager();
+ QWebHistoryInterface::setDefaultInterface(s_historyManager);
+ }
+ return s_historyManager;
+}
+
+BookmarksManager *BrowserApplication::bookmarksManager()
+{
+ if (!s_bookmarksManager) {
+ s_bookmarksManager = new BookmarksManager;
+ }
+ return s_bookmarksManager;
+}
+
+QIcon BrowserApplication::icon(const QUrl &url) const
+{
+ QIcon icon = QWebSettings::iconForUrl(url);
+ if (!icon.isNull())
+ return icon.pixmap(16, 16);
+ if (m_defaultIcon.isNull())
+ m_defaultIcon = QIcon(QLatin1String(":defaulticon.png"));
+ return m_defaultIcon.pixmap(16, 16);
+}
diff --git a/examples/widgets/browser/browserapplication.h b/examples/widgets/browser/browserapplication.h
new file mode 100644
index 000000000..b17f1cea5
--- /dev/null
+++ b/examples/widgets/browser/browserapplication.h
@@ -0,0 +1,118 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the demonstration applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef BROWSERAPPLICATION_H
+#define BROWSERAPPLICATION_H
+
+#include <QtWidgets/QApplication>
+
+#include <QtCore/QUrl>
+#include <QtCore/QPointer>
+
+#include <QtGui/QIcon>
+
+QT_BEGIN_NAMESPACE
+class QLocalServer;
+QT_END_NAMESPACE
+
+class BookmarksManager;
+class BrowserMainWindow;
+class CookieJar;
+class DownloadManager;
+class HistoryManager;
+class NetworkAccessManager;
+class BrowserApplication : public QApplication
+{
+ Q_OBJECT
+
+public:
+ BrowserApplication(int &argc, char **argv);
+ ~BrowserApplication();
+ static BrowserApplication *instance();
+ void loadSettings();
+
+ bool isTheOnlyBrowser() const;
+ BrowserMainWindow *mainWindow();
+ QList<BrowserMainWindow*> mainWindows();
+ QIcon icon(const QUrl &url) const;
+
+ void saveSession();
+ bool canRestoreSession() const;
+
+ static HistoryManager *historyManager();
+ static CookieJar *cookieJar();
+ static DownloadManager *downloadManager();
+ static NetworkAccessManager *networkAccessManager();
+ static BookmarksManager *bookmarksManager();
+
+#if defined(Q_WS_MAC)
+ bool event(QEvent *event);
+#endif
+
+public slots:
+ BrowserMainWindow *newMainWindow();
+ void restoreLastSession();
+#if defined(Q_WS_MAC)
+ void lastWindowClosed();
+ void quitBrowser();
+#endif
+
+private slots:
+ void postLaunch();
+ void openUrl(const QUrl &url);
+ void newLocalSocketConnection();
+
+private:
+ void clean();
+ void installTranslator(const QString &name);
+
+ static HistoryManager *s_historyManager;
+ static DownloadManager *s_downloadManager;
+ static NetworkAccessManager *s_networkAccessManager;
+ static BookmarksManager *s_bookmarksManager;
+
+ QList<QPointer<BrowserMainWindow> > m_mainWindows;
+ QLocalServer *m_localServer;
+ QByteArray m_lastSession;
+ mutable QIcon m_defaultIcon;
+};
+
+#endif // BROWSERAPPLICATION_H
diff --git a/examples/widgets/browser/browsermainwindow.cpp b/examples/widgets/browser/browsermainwindow.cpp
new file mode 100644
index 000000000..ae9896c67
--- /dev/null
+++ b/examples/widgets/browser/browsermainwindow.cpp
@@ -0,0 +1,948 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the demonstration applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "browsermainwindow.h"
+
+#include "autosaver.h"
+#include "bookmarks.h"
+#include "browserapplication.h"
+#include "chasewidget.h"
+#include "downloadmanager.h"
+#include "history.h"
+#include "settings.h"
+#include "tabwidget.h"
+#include "toolbarsearch.h"
+#include "ui_passworddialog.h"
+#include "webview.h"
+
+#include <QtCore/QSettings>
+
+#include <QtWidgets/QDesktopWidget>
+#include <QtWidgets/QFileDialog>
+#include <QtWidgets/QPlainTextEdit>
+#include <QtPrintSupport/QPrintDialog>
+#include <QtPrintSupport/QPrintPreviewDialog>
+#include <QtPrintSupport/QPrinter>
+#include <QtWidgets/QMenuBar>
+#include <QtWidgets/QMessageBox>
+#include <QtWidgets/QStatusBar>
+#include <QtWidgets/QToolBar>
+#include <QtWidgets/QInputDialog>
+
+#include <QWebFrame>
+#include <QWebHistory>
+
+#include <QtCore/QDebug>
+
+BrowserMainWindow::BrowserMainWindow(QWidget *parent, Qt::WindowFlags flags)
+ : QMainWindow(parent, flags)
+ , m_tabWidget(new TabWidget(this))
+ , m_autoSaver(new AutoSaver(this))
+ , m_historyBack(0)
+ , m_historyForward(0)
+ , m_stop(0)
+ , m_reload(0)
+{
+ setToolButtonStyle(Qt::ToolButtonFollowStyle);
+ setAttribute(Qt::WA_DeleteOnClose, true);
+ statusBar()->setSizeGripEnabled(true);
+ setupMenu();
+ setupToolBar();
+
+ QWidget *centralWidget = new QWidget(this);
+ BookmarksModel *boomarksModel = BrowserApplication::bookmarksManager()->bookmarksModel();
+ m_bookmarksToolbar = new BookmarksToolBar(boomarksModel, this);
+ connect(m_bookmarksToolbar, SIGNAL(openUrl(QUrl)),
+ m_tabWidget, SLOT(loadUrlInCurrentTab(QUrl)));
+ connect(m_bookmarksToolbar->toggleViewAction(), SIGNAL(toggled(bool)),
+ this, SLOT(updateBookmarksToolbarActionText(bool)));
+
+ QVBoxLayout *layout = new QVBoxLayout;
+ layout->setSpacing(0);
+ layout->setMargin(0);
+#if defined(Q_WS_MAC)
+ layout->addWidget(m_bookmarksToolbar);
+ layout->addWidget(new QWidget); // <- OS X tab widget style bug
+#else
+ addToolBarBreak();
+ addToolBar(m_bookmarksToolbar);
+#endif
+ layout->addWidget(m_tabWidget);
+ centralWidget->setLayout(layout);
+ setCentralWidget(centralWidget);
+
+ connect(m_tabWidget, SIGNAL(loadPage(QString)),
+ this, SLOT(loadPage(QString)));
+ connect(m_tabWidget, SIGNAL(setCurrentTitle(QString)),
+ this, SLOT(slotUpdateWindowTitle(QString)));
+ connect(m_tabWidget, SIGNAL(showStatusBarMessage(QString)),
+ statusBar(), SLOT(showMessage(QString)));
+ connect(m_tabWidget, SIGNAL(linkHovered(QString)),
+ statusBar(), SLOT(showMessage(QString)));
+ connect(m_tabWidget, SIGNAL(loadProgress(int)),
+ this, SLOT(slotLoadProgress(int)));
+ connect(m_tabWidget, SIGNAL(tabsChanged()),
+ m_autoSaver, SLOT(changeOccurred()));
+ connect(m_tabWidget, SIGNAL(geometryChangeRequested(QRect)),
+ this, SLOT(geometryChangeRequested(QRect)));
+ connect(m_tabWidget, SIGNAL(printRequested(QWebFrame*)),
+ this, SLOT(printRequested(QWebFrame*)));
+ connect(m_tabWidget, SIGNAL(menuBarVisibilityChangeRequested(bool)),
+ menuBar(), SLOT(setVisible(bool)));
+ connect(m_tabWidget, SIGNAL(statusBarVisibilityChangeRequested(bool)),
+ statusBar(), SLOT(setVisible(bool)));
+ connect(m_tabWidget, SIGNAL(toolBarVisibilityChangeRequested(bool)),
+ m_navigationBar, SLOT(setVisible(bool)));
+ connect(m_tabWidget, SIGNAL(toolBarVisibilityChangeRequested(bool)),
+ m_bookmarksToolbar, SLOT(setVisible(bool)));
+#if defined(Q_WS_MAC)
+ connect(m_tabWidget, SIGNAL(lastTabClosed()),
+ this, SLOT(close()));
+#else
+ connect(m_tabWidget, SIGNAL(lastTabClosed()),
+ m_tabWidget, SLOT(newTab()));
+#endif
+
+ slotUpdateWindowTitle();
+ loadDefaultState();
+ m_tabWidget->newTab();
+
+ int size = m_tabWidget->lineEditStack()->sizeHint().height();
+ m_navigationBar->setIconSize(QSize(size, size));
+
+}
+
+BrowserMainWindow::~BrowserMainWindow()
+{
+ m_autoSaver->changeOccurred();
+ m_autoSaver->saveIfNeccessary();
+}
+
+void BrowserMainWindow::loadDefaultState()
+{
+ QSettings settings;
+ settings.beginGroup(QLatin1String("BrowserMainWindow"));
+ QByteArray data = settings.value(QLatin1String("defaultState")).toByteArray();
+ restoreState(data);
+ settings.endGroup();
+}
+
+QSize BrowserMainWindow::sizeHint() const
+{
+ QRect desktopRect = QApplication::desktop()->screenGeometry();
+ QSize size = desktopRect.size() * qreal(0.9);
+ return size;
+}
+
+void BrowserMainWindow::save()
+{
+ BrowserApplication::instance()->saveSession();
+
+ QSettings settings;
+ settings.beginGroup(QLatin1String("BrowserMainWindow"));
+ QByteArray data = saveState(false);
+ settings.setValue(QLatin1String("defaultState"), data);
+ settings.endGroup();
+}
+
+static const qint32 BrowserMainWindowMagic = 0xba;
+
+QByteArray BrowserMainWindow::saveState(bool withTabs) const
+{
+ int version = 2;
+ QByteArray data;
+ QDataStream stream(&data, QIODevice::WriteOnly);
+
+ stream << qint32(BrowserMainWindowMagic);
+ stream << qint32(version);
+
+ stream << size();
+ stream << !m_navigationBar->isHidden();
+ stream << !m_bookmarksToolbar->isHidden();
+ stream << !statusBar()->isHidden();
+ if (withTabs)
+ stream << tabWidget()->saveState();
+ else
+ stream << QByteArray();
+ return data;
+}
+
+bool BrowserMainWindow::restoreState(const QByteArray &state)
+{
+ int version = 2;
+ QByteArray sd = state;
+ QDataStream stream(&sd, QIODevice::ReadOnly);
+ if (stream.atEnd())
+ return false;
+
+ qint32 marker;
+ qint32 v;
+ stream >> marker;
+ stream >> v;
+ if (marker != BrowserMainWindowMagic || v != version)
+ return false;
+
+ QSize size;
+ bool showToolbar;
+ bool showBookmarksBar;
+ bool showStatusbar;
+ QByteArray tabState;
+
+ stream >> size;
+ stream >> showToolbar;
+ stream >> showBookmarksBar;
+ stream >> showStatusbar;
+ stream >> tabState;
+
+ resize(size);
+
+ m_navigationBar->setVisible(showToolbar);
+ updateToolbarActionText(showToolbar);
+
+ m_bookmarksToolbar->setVisible(showBookmarksBar);
+ updateBookmarksToolbarActionText(showBookmarksBar);
+
+ statusBar()->setVisible(showStatusbar);
+ updateStatusbarActionText(showStatusbar);
+
+ if (!tabWidget()->restoreState(tabState))
+ return false;
+
+ return true;
+}
+
+void BrowserMainWindow::setupMenu()
+{
+ new QShortcut(QKeySequence(Qt::Key_F6), this, SLOT(slotSwapFocus()));
+
+ // File
+ QMenu *fileMenu = menuBar()->addMenu(tr("&File"));
+
+ fileMenu->addAction(tr("&New Window"), this, SLOT(slotFileNew()), QKeySequence::New);
+ fileMenu->addAction(m_tabWidget->newTabAction());
+ fileMenu->addAction(tr("&Open File..."), this, SLOT(slotFileOpen()), QKeySequence::Open);
+ fileMenu->addAction(tr("Open &Location..."), this,
+ SLOT(slotSelectLineEdit()), QKeySequence(Qt::ControlModifier + Qt::Key_L));
+ fileMenu->addSeparator();
+ fileMenu->addAction(m_tabWidget->closeTabAction());
+ fileMenu->addSeparator();
+ fileMenu->addAction(tr("&Save As..."), this,
+ SLOT(slotFileSaveAs()), QKeySequence(QKeySequence::Save));
+ fileMenu->addSeparator();
+ BookmarksManager *bookmarksManager = BrowserApplication::bookmarksManager();
+ fileMenu->addAction(tr("&Import Bookmarks..."), bookmarksManager, SLOT(importBookmarks()));
+ fileMenu->addAction(tr("&Export Bookmarks..."), bookmarksManager, SLOT(exportBookmarks()));
+ fileMenu->addSeparator();
+ fileMenu->addAction(tr("P&rint Preview..."), this, SLOT(slotFilePrintPreview()));
+ fileMenu->addAction(tr("&Print..."), this, SLOT(slotFilePrint()), QKeySequence::Print);
+ fileMenu->addSeparator();
+ QAction *action = fileMenu->addAction(tr("Private &Browsing..."), this, SLOT(slotPrivateBrowsing()));
+ action->setCheckable(true);
+ fileMenu->addSeparator();
+
+#if defined(Q_WS_MAC)
+ fileMenu->addAction(tr("&Quit"), BrowserApplication::instance(), SLOT(quitBrowser()), QKeySequence(Qt::CTRL | Qt::Key_Q));
+#else
+ fileMenu->addAction(tr("&Quit"), this, SLOT(close()), QKeySequence(Qt::CTRL | Qt::Key_Q));
+#endif
+
+ // Edit
+ QMenu *editMenu = menuBar()->addMenu(tr("&Edit"));
+ QAction *m_undo = editMenu->addAction(tr("&Undo"));
+ m_undo->setShortcuts(QKeySequence::Undo);
+ m_tabWidget->addWebAction(m_undo, QWebPage::Undo);
+ QAction *m_redo = editMenu->addAction(tr("&Redo"));
+ m_redo->setShortcuts(QKeySequence::Redo);
+ m_tabWidget->addWebAction(m_redo, QWebPage::Redo);
+ editMenu->addSeparator();
+ QAction *m_cut = editMenu->addAction(tr("Cu&t"));
+ m_cut->setShortcuts(QKeySequence::Cut);
+ m_tabWidget->addWebAction(m_cut, QWebPage::Cut);
+ QAction *m_copy = editMenu->addAction(tr("&Copy"));
+ m_copy->setShortcuts(QKeySequence::Copy);
+ m_tabWidget->addWebAction(m_copy, QWebPage::Copy);
+ QAction *m_paste = editMenu->addAction(tr("&Paste"));
+ m_paste->setShortcuts(QKeySequence::Paste);
+ m_tabWidget->addWebAction(m_paste, QWebPage::Paste);
+ editMenu->addSeparator();
+
+ QAction *m_find = editMenu->addAction(tr("&Find"));
+ m_find->setShortcuts(QKeySequence::Find);
+ connect(m_find, SIGNAL(triggered()), this, SLOT(slotEditFind()));
+ new QShortcut(QKeySequence(Qt::Key_Slash), this, SLOT(slotEditFind()));
+
+ QAction *m_findNext = editMenu->addAction(tr("&Find Next"));
+ m_findNext->setShortcuts(QKeySequence::FindNext);
+ connect(m_findNext, SIGNAL(triggered()), this, SLOT(slotEditFindNext()));
+
+ QAction *m_findPrevious = editMenu->addAction(tr("&Find Previous"));
+ m_findPrevious->setShortcuts(QKeySequence::FindPrevious);
+ connect(m_findPrevious, SIGNAL(triggered()), this, SLOT(slotEditFindPrevious()));
+
+ editMenu->addSeparator();
+ editMenu->addAction(tr("&Preferences"), this, SLOT(slotPreferences()), tr("Ctrl+,"));
+
+ // View
+ QMenu *viewMenu = menuBar()->addMenu(tr("&View"));
+
+ m_viewBookmarkBar = new QAction(this);
+ updateBookmarksToolbarActionText(true);
+ m_viewBookmarkBar->setShortcut(tr("Shift+Ctrl+B"));
+ connect(m_viewBookmarkBar, SIGNAL(triggered()), this, SLOT(slotViewBookmarksBar()));
+ viewMenu->addAction(m_viewBookmarkBar);
+
+ m_viewToolbar = new QAction(this);
+ updateToolbarActionText(true);
+ m_viewToolbar->setShortcut(tr("Ctrl+|"));
+ connect(m_viewToolbar, SIGNAL(triggered()), this, SLOT(slotViewToolbar()));
+ viewMenu->addAction(m_viewToolbar);
+
+ m_viewStatusbar = new QAction(this);
+ updateStatusbarActionText(true);
+ m_viewStatusbar->setShortcut(tr("Ctrl+/"));
+ connect(m_viewStatusbar, SIGNAL(triggered()), this, SLOT(slotViewStatusbar()));
+ viewMenu->addAction(m_viewStatusbar);
+
+ viewMenu->addSeparator();
+
+ m_stop = viewMenu->addAction(tr("&Stop"));
+ QList<QKeySequence> shortcuts;
+ shortcuts.append(QKeySequence(Qt::CTRL | Qt::Key_Period));
+ shortcuts.append(Qt::Key_Escape);
+ m_stop->setShortcuts(shortcuts);
+ m_tabWidget->addWebAction(m_stop, QWebPage::Stop);
+
+ m_reload = viewMenu->addAction(tr("Reload Page"));
+ m_reload->setShortcuts(QKeySequence::Refresh);
+ m_tabWidget->addWebAction(m_reload, QWebPage::Reload);
+
+ viewMenu->addAction(tr("Zoom &In"), this, SLOT(slotViewZoomIn()), QKeySequence(Qt::CTRL | Qt::Key_Plus));
+ viewMenu->addAction(tr("Zoom &Out"), this, SLOT(slotViewZoomOut()), QKeySequence(Qt::CTRL | Qt::Key_Minus));
+ viewMenu->addAction(tr("Reset &Zoom"), this, SLOT(slotViewResetZoom()), QKeySequence(Qt::CTRL | Qt::Key_0));
+ QAction *zoomTextOnlyAction = viewMenu->addAction(tr("Zoom &Text Only"));
+ connect(zoomTextOnlyAction, SIGNAL(toggled(bool)), this, SLOT(slotViewZoomTextOnly(bool)));
+ zoomTextOnlyAction->setCheckable(true);
+ zoomTextOnlyAction->setChecked(false);
+
+ viewMenu->addSeparator();
+ viewMenu->addAction(tr("Page S&ource"), this, SLOT(slotViewPageSource()), tr("Ctrl+Alt+U"));
+ QAction *a = viewMenu->addAction(tr("&Full Screen"), this, SLOT(slotViewFullScreen(bool)), Qt::Key_F11);
+ a->setCheckable(true);
+
+ // History
+ HistoryMenu *historyMenu = new HistoryMenu(this);
+ connect(historyMenu, SIGNAL(openUrl(QUrl)),
+ m_tabWidget, SLOT(loadUrlInCurrentTab(QUrl)));
+ connect(historyMenu, SIGNAL(hovered(QString)), this,
+ SLOT(slotUpdateStatusbar(QString)));
+ historyMenu->setTitle(tr("Hi&story"));
+ menuBar()->addMenu(historyMenu);
+ QList<QAction*> historyActions;
+
+ m_historyBack = new QAction(tr("Back"), this);
+ m_tabWidget->addWebAction(m_historyBack, QWebPage::Back);
+ m_historyBack->setShortcuts(QKeySequence::Back);
+ m_historyBack->setIconVisibleInMenu(false);
+
+ m_historyForward = new QAction(tr("Forward"), this);
+ m_tabWidget->addWebAction(m_historyForward, QWebPage::Forward);
+ m_historyForward->setShortcuts(QKeySequence::Forward);
+ m_historyForward->setIconVisibleInMenu(false);
+
+ QAction *m_historyHome = new QAction(tr("Home"), this);
+ connect(m_historyHome, SIGNAL(triggered()), this, SLOT(slotHome()));
+ m_historyHome->setShortcut(QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_H));
+
+ m_restoreLastSession = new QAction(tr("Restore Last Session"), this);
+ connect(m_restoreLastSession, SIGNAL(triggered()), BrowserApplication::instance(), SLOT(restoreLastSession()));
+ m_restoreLastSession->setEnabled(BrowserApplication::instance()->canRestoreSession());
+
+ historyActions.append(m_historyBack);
+ historyActions.append(m_historyForward);
+ historyActions.append(m_historyHome);
+ historyActions.append(m_tabWidget->recentlyClosedTabsAction());
+ historyActions.append(m_restoreLastSession);
+ historyMenu->setInitialActions(historyActions);
+
+ // Bookmarks
+ BookmarksMenu *bookmarksMenu = new BookmarksMenu(this);
+ connect(bookmarksMenu, SIGNAL(openUrl(QUrl)),
+ m_tabWidget, SLOT(loadUrlInCurrentTab(QUrl)));
+ connect(bookmarksMenu, SIGNAL(hovered(QString)),
+ this, SLOT(slotUpdateStatusbar(QString)));
+ bookmarksMenu->setTitle(tr("&Bookmarks"));
+ menuBar()->addMenu(bookmarksMenu);
+
+ QList<QAction*> bookmarksActions;
+
+ QAction *showAllBookmarksAction = new QAction(tr("Show All Bookmarks"), this);
+ connect(showAllBookmarksAction, SIGNAL(triggered()), this, SLOT(slotShowBookmarksDialog()));
+ m_addBookmark = new QAction(QIcon(QLatin1String(":addbookmark.png")), tr("Add Bookmark..."), this);
+ m_addBookmark->setIconVisibleInMenu(false);
+
+ connect(m_addBookmark, SIGNAL(triggered()), this, SLOT(slotAddBookmark()));
+ m_addBookmark->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_D));
+
+ bookmarksActions.append(showAllBookmarksAction);
+ bookmarksActions.append(m_addBookmark);
+ bookmarksMenu->setInitialActions(bookmarksActions);
+
+ // Window
+ m_windowMenu = menuBar()->addMenu(tr("&Window"));
+ connect(m_windowMenu, SIGNAL(aboutToShow()),
+ this, SLOT(slotAboutToShowWindowMenu()));
+ slotAboutToShowWindowMenu();
+
+ QMenu *toolsMenu = menuBar()->addMenu(tr("&Tools"));
+ toolsMenu->addAction(tr("Web &Search"), this, SLOT(slotWebSearch()), QKeySequence(tr("Ctrl+K", "Web Search")));
+ a = toolsMenu->addAction(tr("Enable Web &Inspector"), this, SLOT(slotToggleInspector(bool)));
+ a->setCheckable(true);
+
+ QMenu *helpMenu = menuBar()->addMenu(tr("&Help"));
+ helpMenu->addAction(tr("About &Qt"), qApp, SLOT(aboutQt()));
+ helpMenu->addAction(tr("About &Demo Browser"), this, SLOT(slotAboutApplication()));
+}
+
+void BrowserMainWindow::setupToolBar()
+{
+ setUnifiedTitleAndToolBarOnMac(true);
+ m_navigationBar = addToolBar(tr("Navigation"));
+ connect(m_navigationBar->toggleViewAction(), SIGNAL(toggled(bool)),
+ this, SLOT(updateToolbarActionText(bool)));
+
+ m_historyBack->setIcon(style()->standardIcon(QStyle::SP_ArrowBack, 0, this));
+ m_historyBackMenu = new QMenu(this);
+ m_historyBack->setMenu(m_historyBackMenu);
+ connect(m_historyBackMenu, SIGNAL(aboutToShow()),
+ this, SLOT(slotAboutToShowBackMenu()));
+ connect(m_historyBackMenu, SIGNAL(triggered(QAction*)),
+ this, SLOT(slotOpenActionUrl(QAction*)));
+ m_navigationBar->addAction(m_historyBack);
+
+ m_historyForward->setIcon(style()->standardIcon(QStyle::SP_ArrowForward, 0, this));
+ m_historyForwardMenu = new QMenu(this);
+ connect(m_historyForwardMenu, SIGNAL(aboutToShow()),
+ this, SLOT(slotAboutToShowForwardMenu()));
+ connect(m_historyForwardMenu, SIGNAL(triggered(QAction*)),
+ this, SLOT(slotOpenActionUrl(QAction*)));
+ m_historyForward->setMenu(m_historyForwardMenu);
+ m_navigationBar->addAction(m_historyForward);
+
+ m_stopReload = new QAction(this);
+ m_reloadIcon = style()->standardIcon(QStyle::SP_BrowserReload);
+ m_stopReload->setIcon(m_reloadIcon);
+
+ m_navigationBar->addAction(m_stopReload);
+
+ m_navigationBar->addWidget(m_tabWidget->lineEditStack());
+
+ m_toolbarSearch = new ToolbarSearch(m_navigationBar);
+ m_navigationBar->addWidget(m_toolbarSearch);
+ connect(m_toolbarSearch, SIGNAL(search(QUrl)), SLOT(loadUrl(QUrl)));
+
+ m_chaseWidget = new ChaseWidget(this);
+ m_navigationBar->addWidget(m_chaseWidget);
+}
+
+void BrowserMainWindow::slotShowBookmarksDialog()
+{
+ BookmarksDialog *dialog = new BookmarksDialog(this);
+ connect(dialog, SIGNAL(openUrl(QUrl)),
+ m_tabWidget, SLOT(loadUrlInCurrentTab(QUrl)));
+ dialog->show();
+}
+
+void BrowserMainWindow::slotAddBookmark()
+{
+ WebView *webView = currentTab();
+ QString url = webView->url().toString();
+ QString title = webView->title();
+ AddBookmarkDialog dialog(url, title);
+ dialog.exec();
+}
+
+void BrowserMainWindow::slotViewToolbar()
+{
+ if (m_navigationBar->isVisible()) {
+ updateToolbarActionText(false);
+ m_navigationBar->close();
+ } else {
+ updateToolbarActionText(true);
+ m_navigationBar->show();
+ }
+ m_autoSaver->changeOccurred();
+}
+
+void BrowserMainWindow::slotViewBookmarksBar()
+{
+ if (m_bookmarksToolbar->isVisible()) {
+ updateBookmarksToolbarActionText(false);
+ m_bookmarksToolbar->close();
+ } else {
+ updateBookmarksToolbarActionText(true);
+ m_bookmarksToolbar->show();
+ }
+ m_autoSaver->changeOccurred();
+}
+
+void BrowserMainWindow::updateStatusbarActionText(bool visible)
+{
+ m_viewStatusbar->setText(!visible ? tr("Show Status Bar") : tr("Hide Status Bar"));
+}
+
+void BrowserMainWindow::updateToolbarActionText(bool visible)
+{
+ m_viewToolbar->setText(!visible ? tr("Show Toolbar") : tr("Hide Toolbar"));
+}
+
+void BrowserMainWindow::updateBookmarksToolbarActionText(bool visible)
+{
+ m_viewBookmarkBar->setText(!visible ? tr("Show Bookmarks bar") : tr("Hide Bookmarks bar"));
+}
+
+void BrowserMainWindow::slotViewStatusbar()
+{
+ if (statusBar()->isVisible()) {
+ updateStatusbarActionText(false);
+ statusBar()->close();
+ } else {
+ updateStatusbarActionText(true);
+ statusBar()->show();
+ }
+ m_autoSaver->changeOccurred();
+}
+
+void BrowserMainWindow::loadUrl(const QUrl &url)
+{
+ if (!currentTab() || !url.isValid())
+ return;
+
+ m_tabWidget->currentLineEdit()->setText(QString::fromUtf8(url.toEncoded()));
+ m_tabWidget->loadUrlInCurrentTab(url);
+}
+
+void BrowserMainWindow::slotDownloadManager()
+{
+ BrowserApplication::downloadManager()->show();
+}
+
+void BrowserMainWindow::slotSelectLineEdit()
+{
+ m_tabWidget->currentLineEdit()->selectAll();
+ m_tabWidget->currentLineEdit()->setFocus();
+}
+
+void BrowserMainWindow::slotFileSaveAs()
+{
+ BrowserApplication::downloadManager()->download(currentTab()->url(), true);
+}
+
+void BrowserMainWindow::slotPreferences()
+{
+ SettingsDialog *s = new SettingsDialog(this);
+ s->show();
+}
+
+void BrowserMainWindow::slotUpdateStatusbar(const QString &string)
+{
+ statusBar()->showMessage(string, 2000);
+}
+
+void BrowserMainWindow::slotUpdateWindowTitle(const QString &title)
+{
+ if (title.isEmpty()) {
+ setWindowTitle(tr("Qt Demo Browser"));
+ } else {
+#if defined(Q_WS_MAC)
+ setWindowTitle(title);
+#else
+ setWindowTitle(tr("%1 - Qt Demo Browser", "Page title and Browser name").arg(title));
+#endif
+ }
+}
+
+void BrowserMainWindow::slotAboutApplication()
+{
+ QMessageBox::about(this, tr("About"), tr(
+ "Version %1"
+ "<p>This demo demonstrates Qt's "
+ "webkit facilities in action, providing an example "
+ "browser for you to experiment with.<p>"
+ "<p>QtWebKit is based on the Open Source WebKit Project developed at <a href=\"http://webkit.org/\">http://webkit.org/</a>."
+ ).arg(QCoreApplication::applicationVersion()));
+}
+
+void BrowserMainWindow::slotFileNew()
+{
+ BrowserApplication::instance()->newMainWindow();
+ BrowserMainWindow *mw = BrowserApplication::instance()->mainWindow();
+ mw->slotHome();
+}
+
+void BrowserMainWindow::slotFileOpen()
+{
+ QString file = QFileDialog::getOpenFileName(this, tr("Open Web Resource"), QString(),
+ tr("Web Resources (*.html *.htm *.svg *.png *.gif *.svgz);;All files (*.*)"));
+
+ if (file.isEmpty())
+ return;
+
+ loadPage(file);
+}
+
+void BrowserMainWindow::slotFilePrintPreview()
+{
+#ifndef QT_NO_PRINTPREVIEWDIALOG
+ if (!currentTab())
+ return;
+ QPrintPreviewDialog *dialog = new QPrintPreviewDialog(this);
+ connect(dialog, SIGNAL(paintRequested(QPrinter*)),
+ currentTab(), SLOT(print(QPrinter*)));
+ dialog->exec();
+#endif
+}
+
+void BrowserMainWindow::slotFilePrint()
+{
+ if (!currentTab())
+ return;
+ printRequested(currentTab()->page()->mainFrame());
+}
+
+void BrowserMainWindow::printRequested(QWebFrame *frame)
+{
+#ifndef QT_NO_PRINTDIALOG
+ QPrinter printer;
+ QPrintDialog *dialog = new QPrintDialog(&printer, this);
+ dialog->setWindowTitle(tr("Print Document"));
+ if (dialog->exec() != QDialog::Accepted)
+ return;
+ frame->print(&printer);
+#endif
+}
+
+void BrowserMainWindow::slotPrivateBrowsing()
+{
+ QWebSettings *settings = QWebSettings::globalSettings();
+ bool pb = settings->testAttribute(QWebSettings::PrivateBrowsingEnabled);
+ if (!pb) {
+ QString title = tr("Are you sure you want to turn on private browsing?");
+ QString text = tr("<b>%1</b><br><br>When private browsing in turned on,"
+ " webpages are not added to the history,"
+ " items are automatically removed from the Downloads window," \
+ " new cookies are not stored, current cookies can't be accessed," \
+ " site icons wont be stored, session wont be saved, " \
+ " and searches are not added to the pop-up menu in the Google search box." \
+ " Until you close the window, you can still click the Back and Forward buttons" \
+ " to return to the webpages you have opened.").arg(title);
+
+ QMessageBox::StandardButton button = QMessageBox::question(this, QString(), text,
+ QMessageBox::Ok | QMessageBox::Cancel,
+ QMessageBox::Ok);
+ if (button == QMessageBox::Ok) {
+ settings->setAttribute(QWebSettings::PrivateBrowsingEnabled, true);
+ }
+ } else {
+ settings->setAttribute(QWebSettings::PrivateBrowsingEnabled, false);
+
+ QList<BrowserMainWindow*> windows = BrowserApplication::instance()->mainWindows();
+ for (int i = 0; i < windows.count(); ++i) {
+ BrowserMainWindow *window = windows.at(i);
+ window->m_lastSearch = QString::null;
+ window->tabWidget()->clear();
+ }
+ }
+}
+
+void BrowserMainWindow::closeEvent(QCloseEvent *event)
+{
+ if (m_tabWidget->count() > 1) {
+ int ret = QMessageBox::warning(this, QString(),
+ tr("Are you sure you want to close the window?"
+ " There are %1 tabs open").arg(m_tabWidget->count()),
+ QMessageBox::Yes | QMessageBox::No,
+ QMessageBox::No);
+ if (ret == QMessageBox::No) {
+ event->ignore();
+ return;
+ }
+ }
+ event->accept();
+ deleteLater();
+}
+
+void BrowserMainWindow::slotEditFind()
+{
+ if (!currentTab())
+ return;
+ bool ok;
+ QString search = QInputDialog::getText(this, tr("Find"),
+ tr("Text:"), QLineEdit::Normal,
+ m_lastSearch, &ok);
+ if (ok && !search.isEmpty()) {
+ m_lastSearch = search;
+ if (!currentTab()->findText(m_lastSearch))
+ slotUpdateStatusbar(tr("\"%1\" not found.").arg(m_lastSearch));
+ }
+}
+
+void BrowserMainWindow::slotEditFindNext()
+{
+ if (!currentTab() && !m_lastSearch.isEmpty())
+ return;
+ currentTab()->findText(m_lastSearch);
+}
+
+void BrowserMainWindow::slotEditFindPrevious()
+{
+ if (!currentTab() && !m_lastSearch.isEmpty())
+ return;
+ currentTab()->findText(m_lastSearch, QWebPage::FindBackward);
+}
+
+void BrowserMainWindow::slotViewZoomIn()
+{
+ if (!currentTab())
+ return;
+ currentTab()->setZoomFactor(currentTab()->zoomFactor() + 0.1);
+}
+
+void BrowserMainWindow::slotViewZoomOut()
+{
+ if (!currentTab())
+ return;
+ currentTab()->setZoomFactor(currentTab()->zoomFactor() - 0.1);
+}
+
+void BrowserMainWindow::slotViewResetZoom()
+{
+ if (!currentTab())
+ return;
+ currentTab()->setZoomFactor(1.0);
+}
+
+void BrowserMainWindow::slotViewZoomTextOnly(bool enable)
+{
+ if (!currentTab())
+ return;
+ currentTab()->page()->settings()->setAttribute(QWebSettings::ZoomTextOnly, enable);
+}
+
+void BrowserMainWindow::slotViewFullScreen(bool makeFullScreen)
+{
+ if (makeFullScreen) {
+ showFullScreen();
+ } else {
+ if (isMinimized())
+ showMinimized();
+ else if (isMaximized())
+ showMaximized();
+ else showNormal();
+ }
+}
+
+void BrowserMainWindow::slotViewPageSource()
+{
+ if (!currentTab())
+ return;
+
+ QString markup = currentTab()->page()->mainFrame()->toHtml();
+ QPlainTextEdit *view = new QPlainTextEdit(markup);
+ view->setWindowTitle(tr("Page Source of %1").arg(currentTab()->title()));
+ view->setMinimumWidth(640);
+ view->setAttribute(Qt::WA_DeleteOnClose);
+ view->show();
+}
+
+void BrowserMainWindow::slotHome()
+{
+ QSettings settings;
+ settings.beginGroup(QLatin1String("MainWindow"));
+ QString home = settings.value(QLatin1String("home"), QLatin1String("http://qt-project.org/")).toString();
+ loadPage(home);
+}
+
+void BrowserMainWindow::slotWebSearch()
+{
+ m_toolbarSearch->lineEdit()->selectAll();
+ m_toolbarSearch->lineEdit()->setFocus();
+}
+
+void BrowserMainWindow::slotToggleInspector(bool enable)
+{
+ QWebSettings::globalSettings()->setAttribute(QWebSettings::DeveloperExtrasEnabled, enable);
+ if (enable) {
+ int result = QMessageBox::question(this, tr("Web Inspector"),
+ tr("The web inspector will only work correctly for pages that were loaded after enabling.\n"
+ "Do you want to reload all pages?"),
+ QMessageBox::Yes | QMessageBox::No);
+ if (result == QMessageBox::Yes) {
+ m_tabWidget->reloadAllTabs();
+ }
+ }
+}
+
+void BrowserMainWindow::slotSwapFocus()
+{
+ if (currentTab()->hasFocus())
+ m_tabWidget->currentLineEdit()->setFocus();
+ else
+ currentTab()->setFocus();
+}
+
+void BrowserMainWindow::loadPage(const QString &page)
+{
+ QUrl url = QUrl::fromUserInput(page);
+ loadUrl(url);
+}
+
+TabWidget *BrowserMainWindow::tabWidget() const
+{
+ return m_tabWidget;
+}
+
+WebView *BrowserMainWindow::currentTab() const
+{
+ return m_tabWidget->currentWebView();
+}
+
+void BrowserMainWindow::slotLoadProgress(int progress)
+{
+ if (progress < 100 && progress > 0) {
+ m_chaseWidget->setAnimated(true);
+ disconnect(m_stopReload, SIGNAL(triggered()), m_reload, SLOT(trigger()));
+ if (m_stopIcon.isNull())
+ m_stopIcon = style()->standardIcon(QStyle::SP_BrowserStop);
+ m_stopReload->setIcon(m_stopIcon);
+ connect(m_stopReload, SIGNAL(triggered()), m_stop, SLOT(trigger()));
+ m_stopReload->setToolTip(tr("Stop loading the current page"));
+ } else {
+ m_chaseWidget->setAnimated(false);
+ disconnect(m_stopReload, SIGNAL(triggered()), m_stop, SLOT(trigger()));
+ m_stopReload->setIcon(m_reloadIcon);
+ connect(m_stopReload, SIGNAL(triggered()), m_reload, SLOT(trigger()));
+ m_stopReload->setToolTip(tr("Reload the current page"));
+ }
+}
+
+void BrowserMainWindow::slotAboutToShowBackMenu()
+{
+ m_historyBackMenu->clear();
+ if (!currentTab())
+ return;
+ QWebHistory *history = currentTab()->history();
+ int historyCount = history->count();
+ for (int i = history->backItems(historyCount).count() - 1; i >= 0; --i) {
+ QWebHistoryItem item = history->backItems(history->count()).at(i);
+ QAction *action = new QAction(this);
+ action->setData(-1*(historyCount-i-1));
+ QIcon icon = BrowserApplication::instance()->icon(item.url());
+ action->setIcon(icon);
+ action->setText(item.title());
+ m_historyBackMenu->addAction(action);
+ }
+}
+
+void BrowserMainWindow::slotAboutToShowForwardMenu()
+{
+ m_historyForwardMenu->clear();
+ if (!currentTab())
+ return;
+ QWebHistory *history = currentTab()->history();
+ int historyCount = history->count();
+ for (int i = 0; i < history->forwardItems(history->count()).count(); ++i) {
+ QWebHistoryItem item = history->forwardItems(historyCount).at(i);
+ QAction *action = new QAction(this);
+ action->setData(historyCount-i);
+ QIcon icon = BrowserApplication::instance()->icon(item.url());
+ action->setIcon(icon);
+ action->setText(item.title());
+ m_historyForwardMenu->addAction(action);
+ }
+}
+
+void BrowserMainWindow::slotAboutToShowWindowMenu()
+{
+ m_windowMenu->clear();
+ m_windowMenu->addAction(m_tabWidget->nextTabAction());
+ m_windowMenu->addAction(m_tabWidget->previousTabAction());
+ m_windowMenu->addSeparator();
+ m_windowMenu->addAction(tr("Downloads"), this, SLOT(slotDownloadManager()), QKeySequence(tr("Alt+Ctrl+L", "Download Manager")));
+
+ m_windowMenu->addSeparator();
+ QList<BrowserMainWindow*> windows = BrowserApplication::instance()->mainWindows();
+ for (int i = 0; i < windows.count(); ++i) {
+ BrowserMainWindow *window = windows.at(i);
+ QAction *action = m_windowMenu->addAction(window->windowTitle(), this, SLOT(slotShowWindow()));
+ action->setData(i);
+ action->setCheckable(true);
+ if (window == this)
+ action->setChecked(true);
+ }
+}
+
+void BrowserMainWindow::slotShowWindow()
+{
+ if (QAction *action = qobject_cast<QAction*>(sender())) {
+ QVariant v = action->data();
+ if (v.canConvert<int>()) {
+ int offset = qvariant_cast<int>(v);
+ QList<BrowserMainWindow*> windows = BrowserApplication::instance()->mainWindows();
+ windows.at(offset)->activateWindow();
+ windows.at(offset)->currentTab()->setFocus();
+ }
+ }
+}
+
+void BrowserMainWindow::slotOpenActionUrl(QAction *action)
+{
+ int offset = action->data().toInt();
+ QWebHistory *history = currentTab()->history();
+ if (offset < 0)
+ history->goToItem(history->backItems(-1*offset).first()); // back
+ else if (offset > 0)
+ history->goToItem(history->forwardItems(history->count() - offset + 1).back()); // forward
+ }
+
+void BrowserMainWindow::geometryChangeRequested(const QRect &geometry)
+{
+ setGeometry(geometry);
+}
diff --git a/examples/widgets/browser/browsermainwindow.h b/examples/widgets/browser/browsermainwindow.h
new file mode 100644
index 000000000..a6905e0bc
--- /dev/null
+++ b/examples/widgets/browser/browsermainwindow.h
@@ -0,0 +1,167 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the demonstration applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef BROWSERMAINWINDOW_H
+#define BROWSERMAINWINDOW_H
+
+#include <QtWidgets/QMainWindow>
+#include <QtGui/QIcon>
+#include <QtCore/QUrl>
+
+class AutoSaver;
+class BookmarksToolBar;
+class ChaseWidget;
+class QWebFrame;
+class TabWidget;
+class ToolbarSearch;
+class WebView;
+
+/*!
+ The MainWindow of the Browser Application.
+
+ Handles the tab widget and all the actions
+ */
+class BrowserMainWindow : public QMainWindow {
+ Q_OBJECT
+
+public:
+ BrowserMainWindow(QWidget *parent = 0, Qt::WindowFlags flags = 0);
+ ~BrowserMainWindow();
+ QSize sizeHint() const;
+
+public:
+ TabWidget *tabWidget() const;
+ WebView *currentTab() const;
+ QByteArray saveState(bool withTabs = true) const;
+ bool restoreState(const QByteArray &state);
+
+public slots:
+ void loadPage(const QString &url);
+ void slotHome();
+
+protected:
+ void closeEvent(QCloseEvent *event);
+
+private slots:
+ void save();
+
+ void slotLoadProgress(int);
+ void slotUpdateStatusbar(const QString &string);
+ void slotUpdateWindowTitle(const QString &title = QString());
+
+ void loadUrl(const QUrl &url);
+ void slotPreferences();
+
+ void slotFileNew();
+ void slotFileOpen();
+ void slotFilePrintPreview();
+ void slotFilePrint();
+ void slotPrivateBrowsing();
+ void slotFileSaveAs();
+ void slotEditFind();
+ void slotEditFindNext();
+ void slotEditFindPrevious();
+ void slotShowBookmarksDialog();
+ void slotAddBookmark();
+ void slotViewZoomIn();
+ void slotViewZoomOut();
+ void slotViewResetZoom();
+ void slotViewZoomTextOnly(bool enable);
+ void slotViewToolbar();
+ void slotViewBookmarksBar();
+ void slotViewStatusbar();
+ void slotViewPageSource();
+ void slotViewFullScreen(bool enable);
+
+ void slotWebSearch();
+ void slotToggleInspector(bool enable);
+ void slotAboutApplication();
+ void slotDownloadManager();
+ void slotSelectLineEdit();
+
+ void slotAboutToShowBackMenu();
+ void slotAboutToShowForwardMenu();
+ void slotAboutToShowWindowMenu();
+ void slotOpenActionUrl(QAction *action);
+ void slotShowWindow();
+ void slotSwapFocus();
+
+ void printRequested(QWebFrame *frame);
+ void geometryChangeRequested(const QRect &geometry);
+ void updateToolbarActionText(bool visible);
+ void updateBookmarksToolbarActionText(bool visible);
+
+private:
+ void loadDefaultState();
+ void setupMenu();
+ void setupToolBar();
+ void updateStatusbarActionText(bool visible);
+
+private:
+ QToolBar *m_navigationBar;
+ ToolbarSearch *m_toolbarSearch;
+ BookmarksToolBar *m_bookmarksToolbar;
+ ChaseWidget *m_chaseWidget;
+ TabWidget *m_tabWidget;
+ AutoSaver *m_autoSaver;
+
+ QAction *m_historyBack;
+ QMenu *m_historyBackMenu;
+ QAction *m_historyForward;
+ QMenu *m_historyForwardMenu;
+ QMenu *m_windowMenu;
+
+ QAction *m_stop;
+ QAction *m_reload;
+ QAction *m_stopReload;
+ QAction *m_viewToolbar;
+ QAction *m_viewBookmarkBar;
+ QAction *m_viewStatusbar;
+ QAction *m_restoreLastSession;
+ QAction *m_addBookmark;
+
+ QIcon m_reloadIcon;
+ QIcon m_stopIcon;
+
+ QString m_lastSearch;
+};
+
+#endif // BROWSERMAINWINDOW_H
diff --git a/examples/widgets/browser/chasewidget.cpp b/examples/widgets/browser/chasewidget.cpp
new file mode 100644
index 000000000..c34dde739
--- /dev/null
+++ b/examples/widgets/browser/chasewidget.cpp
@@ -0,0 +1,141 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the demonstration applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "chasewidget.h"
+
+#include <QtCore/QPoint>
+
+#include <QtWidgets/QApplication>
+#include <QtGui/QHideEvent>
+#include <QtGui/QPainter>
+#include <QtGui/QPaintEvent>
+#include <QtGui/QShowEvent>
+
+ChaseWidget::ChaseWidget(QWidget *parent, QPixmap pixmap, bool pixmapEnabled)
+ : QWidget(parent)
+ , m_segment(0)
+ , m_delay(100)
+ , m_step(40)
+ , m_timerId(-1)
+ , m_animated(false)
+ , m_pixmap(pixmap)
+ , m_pixmapEnabled(pixmapEnabled)
+{
+}
+
+void ChaseWidget::setAnimated(bool value)
+{
+ if (m_animated == value)
+ return;
+ m_animated = value;
+ if (m_timerId != -1) {
+ killTimer(m_timerId);
+ m_timerId = -1;
+ }
+ if (m_animated) {
+ m_segment = 0;
+ m_timerId = startTimer(m_delay);
+ }
+ update();
+}
+
+void ChaseWidget::paintEvent(QPaintEvent *event)
+{
+ Q_UNUSED(event);
+ QPainter p(this);
+ if (m_pixmapEnabled && !m_pixmap.isNull()) {
+ p.drawPixmap(0, 0, m_pixmap);
+ return;
+ }
+
+ const int extent = qMin(width() - 8, height() - 8);
+ const int displ = extent / 4;
+ const int ext = extent / 4 - 1;
+
+ p.setRenderHint(QPainter::Antialiasing, true);
+
+ if (m_animated)
+ p.setPen(Qt::gray);
+ else
+ p.setPen(QPen(palette().dark().color()));
+
+ p.translate(width() / 2, height() / 2); // center
+
+ for (int segment = 0; segment < segmentCount(); ++segment) {
+ p.rotate(QApplication::isRightToLeft() ? m_step : -m_step);
+ if (m_animated)
+ p.setBrush(colorForSegment(segment));
+ else
+ p.setBrush(palette().background());
+ p.drawEllipse(QRect(displ, -ext / 2, ext, ext));
+ }
+}
+
+QSize ChaseWidget::sizeHint() const
+{
+ return QSize(32, 32);
+}
+
+void ChaseWidget::timerEvent(QTimerEvent *event)
+{
+ if (event->timerId() == m_timerId) {
+ ++m_segment;
+ update();
+ }
+ QWidget::timerEvent(event);
+}
+
+QColor ChaseWidget::colorForSegment(int seg) const
+{
+ int index = ((seg + m_segment) % segmentCount());
+ int comp = qMax(0, 255 - (index * (255 / segmentCount())));
+ return QColor(comp, comp, comp, 255);
+}
+
+int ChaseWidget::segmentCount() const
+{
+ return 360 / m_step;
+}
+
+void ChaseWidget::setPixmapEnabled(bool enable)
+{
+ m_pixmapEnabled = enable;
+}
diff --git a/examples/widgets/browser/chasewidget.h b/examples/widgets/browser/chasewidget.h
new file mode 100644
index 000000000..2c57d08ac
--- /dev/null
+++ b/examples/widgets/browser/chasewidget.h
@@ -0,0 +1,85 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the demonstration applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef CHASEWIDGET_H
+#define CHASEWIDGET_H
+
+#include <QtWidgets/QWidget>
+
+#include <QtCore/QSize>
+#include <QtGui/QColor>
+#include <QtGui/QPixmap>
+
+QT_BEGIN_NAMESPACE
+class QHideEvent;
+class QShowEvent;
+class QPaintEvent;
+class QTimerEvent;
+QT_END_NAMESPACE
+
+class ChaseWidget : public QWidget
+{
+ Q_OBJECT
+public:
+ ChaseWidget(QWidget *parent = 0, QPixmap pixmap = QPixmap(), bool pixmapEnabled = false);
+
+ void setAnimated(bool value);
+ void setPixmapEnabled(bool enable);
+ QSize sizeHint() const;
+
+protected:
+ void paintEvent(QPaintEvent *event);
+ void timerEvent(QTimerEvent *event);
+
+private:
+ int segmentCount() const;
+ QColor colorForSegment(int segment) const;
+
+ int m_segment;
+ int m_delay;
+ int m_step;
+ int m_timerId;
+ bool m_animated;
+ QPixmap m_pixmap;
+ bool m_pixmapEnabled;
+};
+
+#endif
diff --git a/examples/widgets/browser/cookiejar.cpp b/examples/widgets/browser/cookiejar.cpp
new file mode 100644
index 000000000..28eae7c89
--- /dev/null
+++ b/examples/widgets/browser/cookiejar.cpp
@@ -0,0 +1,737 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the demonstration applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "cookiejar.h"
+
+#include "autosaver.h"
+
+#include <QtCore/QDateTime>
+#include <QtCore/QDir>
+#include <QtCore/QFile>
+#include <QtCore/QMetaEnum>
+#include <QtCore/QSettings>
+#include <QtCore/QUrl>
+
+#include <QtWidgets/QCompleter>
+#include <QtGui/QDesktopServices>
+#include <QtGui/QFont>
+#include <QtGui/QFontMetrics>
+#include <QtWidgets/QHeaderView>
+#include <QtGui/QKeyEvent>
+#include <QtCore/QSortFilterProxyModel>
+#include <QtNetwork/QNetworkCookie>
+
+#include <QWebSettings>
+
+#include <QtCore/QDebug>
+
+static const unsigned int JAR_VERSION = 23;
+
+QT_BEGIN_NAMESPACE
+QDataStream &operator<<(QDataStream &stream, const QList<QNetworkCookie> &list)
+{
+ stream << JAR_VERSION;
+ stream << quint32(list.size());
+ for (int i = 0; i < list.size(); ++i)
+ stream << list.at(i).toRawForm();
+ return stream;
+}
+
+QDataStream &operator>>(QDataStream &stream, QList<QNetworkCookie> &list)
+{
+ list.clear();
+
+ quint32 version;
+ stream >> version;
+
+ if (version != JAR_VERSION)
+ return stream;
+
+ quint32 count;
+ stream >> count;
+ for (quint32 i = 0; i < count; ++i)
+ {
+ QByteArray value;
+ stream >> value;
+ QList<QNetworkCookie> newCookies = QNetworkCookie::parseCookies(value);
+ if (newCookies.count() == 0 && value.length() != 0) {
+ qWarning() << "CookieJar: Unable to parse saved cookie:" << value;
+ }
+ for (int j = 0; j < newCookies.count(); ++j)
+ list.append(newCookies.at(j));
+ if (stream.atEnd())
+ break;
+ }
+ return stream;
+}
+QT_END_NAMESPACE
+
+CookieJar::CookieJar(QObject *parent)
+ : QNetworkCookieJar(parent)
+ , m_loaded(false)
+ , m_saveTimer(new AutoSaver(this))
+ , m_acceptCookies(AcceptOnlyFromSitesNavigatedTo)
+{
+}
+
+CookieJar::~CookieJar()
+{
+ if (m_keepCookies == KeepUntilExit)
+ clear();
+ m_saveTimer->saveIfNeccessary();
+}
+
+void CookieJar::clear()
+{
+ setAllCookies(QList<QNetworkCookie>());
+ m_saveTimer->changeOccurred();
+ emit cookiesChanged();
+}
+
+void CookieJar::load()
+{
+ if (m_loaded)
+ return;
+ // load cookies and exceptions
+ qRegisterMetaTypeStreamOperators<QList<QNetworkCookie> >("QList<QNetworkCookie>");
+ QSettings cookieSettings(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1String("/cookies.ini"), QSettings::IniFormat);
+ setAllCookies(qvariant_cast<QList<QNetworkCookie> >(cookieSettings.value(QLatin1String("cookies"))));
+ cookieSettings.beginGroup(QLatin1String("Exceptions"));
+ m_exceptions_block = cookieSettings.value(QLatin1String("block")).toStringList();
+ m_exceptions_allow = cookieSettings.value(QLatin1String("allow")).toStringList();
+ m_exceptions_allowForSession = cookieSettings.value(QLatin1String("allowForSession")).toStringList();
+ qSort(m_exceptions_block.begin(), m_exceptions_block.end());
+ qSort(m_exceptions_allow.begin(), m_exceptions_allow.end());
+ qSort(m_exceptions_allowForSession.begin(), m_exceptions_allowForSession.end());
+
+ loadSettings();
+}
+
+void CookieJar::loadSettings()
+{
+ QSettings settings;
+ settings.beginGroup(QLatin1String("cookies"));
+ QByteArray value = settings.value(QLatin1String("acceptCookies"),
+ QLatin1String("AcceptOnlyFromSitesNavigatedTo")).toByteArray();
+ QMetaEnum acceptPolicyEnum = staticMetaObject.enumerator(staticMetaObject.indexOfEnumerator("AcceptPolicy"));
+ m_acceptCookies = acceptPolicyEnum.keyToValue(value) == -1 ?
+ AcceptOnlyFromSitesNavigatedTo :
+ static_cast<AcceptPolicy>(acceptPolicyEnum.keyToValue(value));
+
+ value = settings.value(QLatin1String("keepCookiesUntil"), QLatin1String("KeepUntilExpire")).toByteArray();
+ QMetaEnum keepPolicyEnum = staticMetaObject.enumerator(staticMetaObject.indexOfEnumerator("KeepPolicy"));
+ m_keepCookies = keepPolicyEnum.keyToValue(value) == -1 ?
+ KeepUntilExpire :
+ static_cast<KeepPolicy>(keepPolicyEnum.keyToValue(value));
+
+ if (m_keepCookies == KeepUntilExit)
+ setAllCookies(QList<QNetworkCookie>());
+
+ m_loaded = true;
+ emit cookiesChanged();
+}
+
+void CookieJar::save()
+{
+ if (!m_loaded)
+ return;
+ purgeOldCookies();
+ QString directory = QStandardPaths::writableLocation(QStandardPaths::DataLocation);
+ if (directory.isEmpty())
+ directory = QDir::homePath() + QLatin1String("/.") + QCoreApplication::applicationName();
+ if (!QFile::exists(directory)) {
+ QDir dir;
+ dir.mkpath(directory);
+ }
+ QSettings cookieSettings(directory + QLatin1String("/cookies.ini"), QSettings::IniFormat);
+ QList<QNetworkCookie> cookies = allCookies();
+ for (int i = cookies.count() - 1; i >= 0; --i) {
+ if (cookies.at(i).isSessionCookie())
+ cookies.removeAt(i);
+ }
+ cookieSettings.setValue(QLatin1String("cookies"), QVariant::fromValue<QList<QNetworkCookie> >(cookies));
+ cookieSettings.beginGroup(QLatin1String("Exceptions"));
+ cookieSettings.setValue(QLatin1String("block"), m_exceptions_block);
+ cookieSettings.setValue(QLatin1String("allow"), m_exceptions_allow);
+ cookieSettings.setValue(QLatin1String("allowForSession"), m_exceptions_allowForSession);
+
+ // save cookie settings
+ QSettings settings;
+ settings.beginGroup(QLatin1String("cookies"));
+ QMetaEnum acceptPolicyEnum = staticMetaObject.enumerator(staticMetaObject.indexOfEnumerator("AcceptPolicy"));
+ settings.setValue(QLatin1String("acceptCookies"), QLatin1String(acceptPolicyEnum.valueToKey(m_acceptCookies)));
+
+ QMetaEnum keepPolicyEnum = staticMetaObject.enumerator(staticMetaObject.indexOfEnumerator("KeepPolicy"));
+ settings.setValue(QLatin1String("keepCookiesUntil"), QLatin1String(keepPolicyEnum.valueToKey(m_keepCookies)));
+}
+
+void CookieJar::purgeOldCookies()
+{
+ QList<QNetworkCookie> cookies = allCookies();
+ if (cookies.isEmpty())
+ return;
+ int oldCount = cookies.count();
+ QDateTime now = QDateTime::currentDateTime();
+ for (int i = cookies.count() - 1; i >= 0; --i) {
+ if (!cookies.at(i).isSessionCookie() && cookies.at(i).expirationDate() < now)
+ cookies.removeAt(i);
+ }
+ if (oldCount == cookies.count())
+ return;
+ setAllCookies(cookies);
+ emit cookiesChanged();
+}
+
+QList<QNetworkCookie> CookieJar::cookiesForUrl(const QUrl &url) const
+{
+ CookieJar *that = const_cast<CookieJar*>(this);
+ if (!m_loaded)
+ that->load();
+
+ QWebSettings *globalSettings = QWebSettings::globalSettings();
+ if (globalSettings->testAttribute(QWebSettings::PrivateBrowsingEnabled)) {
+ QList<QNetworkCookie> noCookies;
+ return noCookies;
+ }
+
+ return QNetworkCookieJar::cookiesForUrl(url);
+}
+
+bool CookieJar::setCookiesFromUrl(const QList<QNetworkCookie> &cookieList, const QUrl &url)
+{
+ if (!m_loaded)
+ load();
+
+ QWebSettings *globalSettings = QWebSettings::globalSettings();
+ if (globalSettings->testAttribute(QWebSettings::PrivateBrowsingEnabled))
+ return false;
+
+ QString host = url.host();
+ bool eBlock = qBinaryFind(m_exceptions_block.begin(), m_exceptions_block.end(), host) != m_exceptions_block.end();
+ bool eAllow = qBinaryFind(m_exceptions_allow.begin(), m_exceptions_allow.end(), host) != m_exceptions_allow.end();
+ bool eAllowSession = qBinaryFind(m_exceptions_allowForSession.begin(), m_exceptions_allowForSession.end(), host) != m_exceptions_allowForSession.end();
+
+ bool addedCookies = false;
+ // pass exceptions
+ bool acceptInitially = (m_acceptCookies != AcceptNever);
+ if ((acceptInitially && !eBlock)
+ || (!acceptInitially && (eAllow || eAllowSession))) {
+ // pass url domain == cookie domain
+ QDateTime soon = QDateTime::currentDateTime();
+ soon = soon.addDays(90);
+ foreach (QNetworkCookie cookie, cookieList) {
+ QList<QNetworkCookie> lst;
+ if (m_keepCookies == KeepUntilTimeLimit
+ && !cookie.isSessionCookie()
+ && cookie.expirationDate() > soon) {
+ cookie.setExpirationDate(soon);
+ }
+ lst += cookie;
+ if (QNetworkCookieJar::setCookiesFromUrl(lst, url)) {
+ addedCookies = true;
+ } else {
+ // finally force it in if wanted
+ if (m_acceptCookies == AcceptAlways) {
+ QList<QNetworkCookie> cookies = allCookies();
+ cookies += cookie;
+ setAllCookies(cookies);
+ addedCookies = true;
+ }
+#if 0
+ else
+ qWarning() << "setCookiesFromUrl failed" << url << cookieList.value(0).toRawForm();
+#endif
+ }
+ }
+ }
+
+ if (addedCookies) {
+ m_saveTimer->changeOccurred();
+ emit cookiesChanged();
+ }
+ return addedCookies;
+}
+
+CookieJar::AcceptPolicy CookieJar::acceptPolicy() const
+{
+ if (!m_loaded)
+ (const_cast<CookieJar*>(this))->load();
+ return m_acceptCookies;
+}
+
+void CookieJar::setAcceptPolicy(AcceptPolicy policy)
+{
+ if (!m_loaded)
+ load();
+ if (policy == m_acceptCookies)
+ return;
+ m_acceptCookies = policy;
+ m_saveTimer->changeOccurred();
+}
+
+CookieJar::KeepPolicy CookieJar::keepPolicy() const
+{
+ if (!m_loaded)
+ (const_cast<CookieJar*>(this))->load();
+ return m_keepCookies;
+}
+
+void CookieJar::setKeepPolicy(KeepPolicy policy)
+{
+ if (!m_loaded)
+ load();
+ if (policy == m_keepCookies)
+ return;
+ m_keepCookies = policy;
+ m_saveTimer->changeOccurred();
+}
+
+QStringList CookieJar::blockedCookies() const
+{
+ if (!m_loaded)
+ (const_cast<CookieJar*>(this))->load();
+ return m_exceptions_block;
+}
+
+QStringList CookieJar::allowedCookies() const
+{
+ if (!m_loaded)
+ (const_cast<CookieJar*>(this))->load();
+ return m_exceptions_allow;
+}
+
+QStringList CookieJar::allowForSessionCookies() const
+{
+ if (!m_loaded)
+ (const_cast<CookieJar*>(this))->load();
+ return m_exceptions_allowForSession;
+}
+
+void CookieJar::setBlockedCookies(const QStringList &list)
+{
+ if (!m_loaded)
+ load();
+ m_exceptions_block = list;
+ qSort(m_exceptions_block.begin(), m_exceptions_block.end());
+ m_saveTimer->changeOccurred();
+}
+
+void CookieJar::setAllowedCookies(const QStringList &list)
+{
+ if (!m_loaded)
+ load();
+ m_exceptions_allow = list;
+ qSort(m_exceptions_allow.begin(), m_exceptions_allow.end());
+ m_saveTimer->changeOccurred();
+}
+
+void CookieJar::setAllowForSessionCookies(const QStringList &list)
+{
+ if (!m_loaded)
+ load();
+ m_exceptions_allowForSession = list;
+ qSort(m_exceptions_allowForSession.begin(), m_exceptions_allowForSession.end());
+ m_saveTimer->changeOccurred();
+}
+
+CookieModel::CookieModel(CookieJar *cookieJar, QObject *parent)
+ : QAbstractTableModel(parent)
+ , m_cookieJar(cookieJar)
+{
+ connect(m_cookieJar, SIGNAL(cookiesChanged()), this, SLOT(cookiesChanged()));
+ m_cookieJar->load();
+}
+
+QVariant CookieModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ if (role == Qt::SizeHintRole) {
+ QFont font;
+ font.setPointSize(10);
+ QFontMetrics fm(font);
+ int height = fm.height() + fm.height()/3;
+ int width = fm.width(headerData(section, orientation, Qt::DisplayRole).toString());
+ return QSize(width, height);
+ }
+
+ if (orientation == Qt::Horizontal) {
+ if (role != Qt::DisplayRole)
+ return QVariant();
+
+ switch (section) {
+ case 0:
+ return tr("Website");
+ case 1:
+ return tr("Name");
+ case 2:
+ return tr("Path");
+ case 3:
+ return tr("Secure");
+ case 4:
+ return tr("Expires");
+ case 5:
+ return tr("Contents");
+ default:
+ return QVariant();
+ }
+ }
+ return QAbstractTableModel::headerData(section, orientation, role);
+}
+
+QVariant CookieModel::data(const QModelIndex &index, int role) const
+{
+ QList<QNetworkCookie> lst;
+ if (m_cookieJar)
+ lst = m_cookieJar->allCookies();
+ if (index.row() < 0 || index.row() >= lst.size())
+ return QVariant();
+
+ switch (role) {
+ case Qt::DisplayRole:
+ case Qt::EditRole: {
+ QNetworkCookie cookie = lst.at(index.row());
+ switch (index.column()) {
+ case 0:
+ return cookie.domain();
+ case 1:
+ return cookie.name();
+ case 2:
+ return cookie.path();
+ case 3:
+ return cookie.isSecure();
+ case 4:
+ return cookie.expirationDate();
+ case 5:
+ return cookie.value();
+ }
+ }
+ case Qt::FontRole:{
+ QFont font;
+ font.setPointSize(10);
+ return font;
+ }
+ }
+
+ return QVariant();
+}
+
+int CookieModel::columnCount(const QModelIndex &parent) const
+{
+ return (parent.isValid()) ? 0 : 6;
+}
+
+int CookieModel::rowCount(const QModelIndex &parent) const
+{
+ return (parent.isValid() || !m_cookieJar) ? 0 : m_cookieJar->allCookies().count();
+}
+
+bool CookieModel::removeRows(int row, int count, const QModelIndex &parent)
+{
+ if (parent.isValid() || !m_cookieJar)
+ return false;
+ int lastRow = row + count - 1;
+ beginRemoveRows(parent, row, lastRow);
+ QList<QNetworkCookie> lst = m_cookieJar->allCookies();
+ for (int i = lastRow; i >= row; --i) {
+ lst.removeAt(i);
+ }
+ m_cookieJar->setAllCookies(lst);
+ endRemoveRows();
+ return true;
+}
+
+void CookieModel::cookiesChanged()
+{
+ beginResetModel();
+ endResetModel();
+}
+
+CookiesDialog::CookiesDialog(CookieJar *cookieJar, QWidget *parent) : QDialog(parent)
+{
+ setupUi(this);
+ setWindowFlags(Qt::Sheet);
+ CookieModel *model = new CookieModel(cookieJar, this);
+ m_proxyModel = new QSortFilterProxyModel(this);
+ connect(search, SIGNAL(textChanged(QString)),
+ m_proxyModel, SLOT(setFilterFixedString(QString)));
+ connect(removeButton, SIGNAL(clicked()), cookiesTable, SLOT(removeOne()));
+ connect(removeAllButton, SIGNAL(clicked()), cookiesTable, SLOT(removeAll()));
+ m_proxyModel->setSourceModel(model);
+ cookiesTable->verticalHeader()->hide();
+ cookiesTable->setSelectionBehavior(QAbstractItemView::SelectRows);
+ cookiesTable->setModel(m_proxyModel);
+ cookiesTable->setAlternatingRowColors(true);
+ cookiesTable->setTextElideMode(Qt::ElideMiddle);
+ cookiesTable->setShowGrid(false);
+ cookiesTable->setSortingEnabled(true);
+ QFont f = font();
+ f.setPointSize(10);
+ QFontMetrics fm(f);
+ int height = fm.height() + fm.height()/3;
+ cookiesTable->verticalHeader()->setDefaultSectionSize(height);
+ cookiesTable->verticalHeader()->setMinimumSectionSize(-1);
+ for (int i = 0; i < model->columnCount(); ++i){
+ int header = cookiesTable->horizontalHeader()->sectionSizeHint(i);
+ switch (i) {
+ case 0:
+ header = fm.width(QLatin1String("averagehost.domain.com"));
+ break;
+ case 1:
+ header = fm.width(QLatin1String("_session_id"));
+ break;
+ case 4:
+ header = fm.width(QDateTime::currentDateTime().toString(Qt::LocalDate));
+ break;
+ }
+ int buffer = fm.width(QLatin1String("xx"));
+ header += buffer;
+ cookiesTable->horizontalHeader()->resizeSection(i, header);
+ }
+ cookiesTable->horizontalHeader()->setStretchLastSection(true);
+}
+
+
+
+CookieExceptionsModel::CookieExceptionsModel(CookieJar *cookiejar, QObject *parent)
+ : QAbstractTableModel(parent)
+ , m_cookieJar(cookiejar)
+{
+ m_allowedCookies = m_cookieJar->allowedCookies();
+ m_blockedCookies = m_cookieJar->blockedCookies();
+ m_sessionCookies = m_cookieJar->allowForSessionCookies();
+}
+
+QVariant CookieExceptionsModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ if (role == Qt::SizeHintRole) {
+ QFont font;
+ font.setPointSize(10);
+ QFontMetrics fm(font);
+ int height = fm.height() + fm.height()/3;
+ int width = fm.width(headerData(section, orientation, Qt::DisplayRole).toString());
+ return QSize(width, height);
+ }
+
+ if (orientation == Qt::Horizontal
+ && role == Qt::DisplayRole) {
+ switch (section) {
+ case 0:
+ return tr("Website");
+ case 1:
+ return tr("Status");
+ }
+ }
+ return QAbstractTableModel::headerData(section, orientation, role);
+}
+
+QVariant CookieExceptionsModel::data(const QModelIndex &index, int role) const
+{
+ if (index.row() < 0 || index.row() >= rowCount())
+ return QVariant();
+
+ switch (role) {
+ case Qt::DisplayRole:
+ case Qt::EditRole: {
+ int row = index.row();
+ if (row < m_allowedCookies.count()) {
+ switch (index.column()) {
+ case 0:
+ return m_allowedCookies.at(row);
+ case 1:
+ return tr("Allow");
+ }
+ }
+ row = row - m_allowedCookies.count();
+ if (row < m_blockedCookies.count()) {
+ switch (index.column()) {
+ case 0:
+ return m_blockedCookies.at(row);
+ case 1:
+ return tr("Block");
+ }
+ }
+ row = row - m_blockedCookies.count();
+ if (row < m_sessionCookies.count()) {
+ switch (index.column()) {
+ case 0:
+ return m_sessionCookies.at(row);
+ case 1:
+ return tr("Allow For Session");
+ }
+ }
+ }
+ case Qt::FontRole:{
+ QFont font;
+ font.setPointSize(10);
+ return font;
+ }
+ }
+ return QVariant();
+}
+
+int CookieExceptionsModel::columnCount(const QModelIndex &parent) const
+{
+ return (parent.isValid()) ? 0 : 2;
+}
+
+int CookieExceptionsModel::rowCount(const QModelIndex &parent) const
+{
+ return (parent.isValid() || !m_cookieJar) ? 0 : m_allowedCookies.count() + m_blockedCookies.count() + m_sessionCookies.count();
+}
+
+bool CookieExceptionsModel::removeRows(int row, int count, const QModelIndex &parent)
+{
+ if (parent.isValid() || !m_cookieJar)
+ return false;
+
+ int lastRow = row + count - 1;
+ beginRemoveRows(parent, row, lastRow);
+ for (int i = lastRow; i >= row; --i) {
+ if (i < m_allowedCookies.count()) {
+ m_allowedCookies.removeAt(row);
+ continue;
+ }
+ i = i - m_allowedCookies.count();
+ if (i < m_blockedCookies.count()) {
+ m_blockedCookies.removeAt(row);
+ continue;
+ }
+ i = i - m_blockedCookies.count();
+ if (i < m_sessionCookies.count()) {
+ m_sessionCookies.removeAt(row);
+ continue;
+ }
+ }
+ m_cookieJar->setAllowedCookies(m_allowedCookies);
+ m_cookieJar->setBlockedCookies(m_blockedCookies);
+ m_cookieJar->setAllowForSessionCookies(m_sessionCookies);
+ endRemoveRows();
+ return true;
+}
+
+CookiesExceptionsDialog::CookiesExceptionsDialog(CookieJar *cookieJar, QWidget *parent)
+ : QDialog(parent)
+ , m_cookieJar(cookieJar)
+{
+ setupUi(this);
+ setWindowFlags(Qt::Sheet);
+ connect(removeButton, SIGNAL(clicked()), exceptionTable, SLOT(removeOne()));
+ connect(removeAllButton, SIGNAL(clicked()), exceptionTable, SLOT(removeAll()));
+ exceptionTable->verticalHeader()->hide();
+ exceptionTable->setSelectionBehavior(QAbstractItemView::SelectRows);
+ exceptionTable->setAlternatingRowColors(true);
+ exceptionTable->setTextElideMode(Qt::ElideMiddle);
+ exceptionTable->setShowGrid(false);
+ exceptionTable->setSortingEnabled(true);
+ m_exceptionsModel = new CookieExceptionsModel(cookieJar, this);
+ m_proxyModel = new QSortFilterProxyModel(this);
+ m_proxyModel->setSourceModel(m_exceptionsModel);
+ connect(search, SIGNAL(textChanged(QString)),
+ m_proxyModel, SLOT(setFilterFixedString(QString)));
+ exceptionTable->setModel(m_proxyModel);
+
+ CookieModel *cookieModel = new CookieModel(cookieJar, this);
+ domainLineEdit->setCompleter(new QCompleter(cookieModel, domainLineEdit));
+
+ connect(domainLineEdit, SIGNAL(textChanged(QString)),
+ this, SLOT(textChanged(QString)));
+ connect(blockButton, SIGNAL(clicked()), this, SLOT(block()));
+ connect(allowButton, SIGNAL(clicked()), this, SLOT(allow()));
+ connect(allowForSessionButton, SIGNAL(clicked()), this, SLOT(allowForSession()));
+
+ QFont f = font();
+ f.setPointSize(10);
+ QFontMetrics fm(f);
+ int height = fm.height() + fm.height()/3;
+ exceptionTable->verticalHeader()->setDefaultSectionSize(height);
+ exceptionTable->verticalHeader()->setMinimumSectionSize(-1);
+ for (int i = 0; i < m_exceptionsModel->columnCount(); ++i){
+ int header = exceptionTable->horizontalHeader()->sectionSizeHint(i);
+ switch (i) {
+ case 0:
+ header = fm.width(QLatin1String("averagebiglonghost.domain.com"));
+ break;
+ case 1:
+ header = fm.width(QLatin1String("Allow For Session"));
+ break;
+ }
+ int buffer = fm.width(QLatin1String("xx"));
+ header += buffer;
+ exceptionTable->horizontalHeader()->resizeSection(i, header);
+ }
+}
+
+void CookiesExceptionsDialog::textChanged(const QString &text)
+{
+ bool enabled = !text.isEmpty();
+ blockButton->setEnabled(enabled);
+ allowButton->setEnabled(enabled);
+ allowForSessionButton->setEnabled(enabled);
+}
+
+void CookiesExceptionsDialog::block()
+{
+ if (domainLineEdit->text().isEmpty())
+ return;
+ m_exceptionsModel->m_blockedCookies.append(domainLineEdit->text());
+ m_cookieJar->setBlockedCookies(m_exceptionsModel->m_blockedCookies);
+ m_exceptionsModel->beginResetModel();
+ m_exceptionsModel->endResetModel();
+}
+
+void CookiesExceptionsDialog::allow()
+{
+ if (domainLineEdit->text().isEmpty())
+ return;
+ m_exceptionsModel->m_allowedCookies.append(domainLineEdit->text());
+ m_cookieJar->setAllowedCookies(m_exceptionsModel->m_allowedCookies);
+ m_exceptionsModel->beginResetModel();
+ m_exceptionsModel->endResetModel();
+}
+
+void CookiesExceptionsDialog::allowForSession()
+{
+ if (domainLineEdit->text().isEmpty())
+ return;
+ m_exceptionsModel->m_sessionCookies.append(domainLineEdit->text());
+ m_cookieJar->setAllowForSessionCookies(m_exceptionsModel->m_sessionCookies);
+ m_exceptionsModel->beginResetModel();
+ m_exceptionsModel->endResetModel();
+}
diff --git a/examples/widgets/browser/cookiejar.h b/examples/widgets/browser/cookiejar.h
new file mode 100644
index 000000000..1cc7800e1
--- /dev/null
+++ b/examples/widgets/browser/cookiejar.h
@@ -0,0 +1,203 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the demonstration applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef COOKIEJAR_H
+#define COOKIEJAR_H
+
+#include <QtNetwork/QNetworkCookieJar>
+
+#include <QtCore/QAbstractItemModel>
+#include <QtCore/QStringList>
+
+#include <QtWidgets/QDialog>
+#include <QtWidgets/QTableView>
+
+QT_BEGIN_NAMESPACE
+class QSortFilterProxyModel;
+class QKeyEvent;
+QT_END_NAMESPACE
+
+class AutoSaver;
+
+class CookieJar : public QNetworkCookieJar
+{
+ friend class CookieModel;
+ Q_OBJECT
+ Q_PROPERTY(AcceptPolicy acceptPolicy READ acceptPolicy WRITE setAcceptPolicy)
+ Q_PROPERTY(KeepPolicy keepPolicy READ keepPolicy WRITE setKeepPolicy)
+ Q_PROPERTY(QStringList blockedCookies READ blockedCookies WRITE setBlockedCookies)
+ Q_PROPERTY(QStringList allowedCookies READ allowedCookies WRITE setAllowedCookies)
+ Q_PROPERTY(QStringList allowForSessionCookies READ allowForSessionCookies WRITE setAllowForSessionCookies)
+ Q_ENUMS(KeepPolicy)
+ Q_ENUMS(AcceptPolicy)
+
+signals:
+ void cookiesChanged();
+
+public:
+ enum AcceptPolicy {
+ AcceptAlways,
+ AcceptNever,
+ AcceptOnlyFromSitesNavigatedTo
+ };
+
+ enum KeepPolicy {
+ KeepUntilExpire,
+ KeepUntilExit,
+ KeepUntilTimeLimit
+ };
+
+ CookieJar(QObject *parent = 0);
+ ~CookieJar();
+
+ QList<QNetworkCookie> cookiesForUrl(const QUrl &url) const;
+ bool setCookiesFromUrl(const QList<QNetworkCookie> &cookieList, const QUrl &url);
+
+ AcceptPolicy acceptPolicy() const;
+ void setAcceptPolicy(AcceptPolicy policy);
+
+ KeepPolicy keepPolicy() const;
+ void setKeepPolicy(KeepPolicy policy);
+
+ QStringList blockedCookies() const;
+ QStringList allowedCookies() const;
+ QStringList allowForSessionCookies() const;
+
+ void setBlockedCookies(const QStringList &list);
+ void setAllowedCookies(const QStringList &list);
+ void setAllowForSessionCookies(const QStringList &list);
+
+public slots:
+ void clear();
+ void loadSettings();
+
+private slots:
+ void save();
+
+private:
+ void purgeOldCookies();
+ void load();
+ bool m_loaded;
+ AutoSaver *m_saveTimer;
+
+ AcceptPolicy m_acceptCookies;
+ KeepPolicy m_keepCookies;
+
+ QStringList m_exceptions_block;
+ QStringList m_exceptions_allow;
+ QStringList m_exceptions_allowForSession;
+};
+
+class CookieModel : public QAbstractTableModel
+{
+ Q_OBJECT
+
+public:
+ CookieModel(CookieJar *jar, QObject *parent = 0);
+ QVariant headerData(int section, Qt::Orientation orientation, int role) const;
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
+ int columnCount(const QModelIndex &parent = QModelIndex()) const;
+ int rowCount(const QModelIndex &parent = QModelIndex()) const;
+ bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex());
+
+private slots:
+ void cookiesChanged();
+
+private:
+ CookieJar *m_cookieJar;
+};
+
+#include "ui_cookies.h"
+#include "ui_cookiesexceptions.h"
+
+class CookiesDialog : public QDialog, public Ui_CookiesDialog
+{
+ Q_OBJECT
+
+public:
+ CookiesDialog(CookieJar *cookieJar, QWidget *parent = 0);
+
+private:
+ QSortFilterProxyModel *m_proxyModel;
+};
+
+class CookieExceptionsModel : public QAbstractTableModel
+{
+ Q_OBJECT
+ friend class CookiesExceptionsDialog;
+
+public:
+ CookieExceptionsModel(CookieJar *cookieJar, QObject *parent = 0);
+ QVariant headerData(int section, Qt::Orientation orientation, int role) const;
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
+ int columnCount(const QModelIndex &parent = QModelIndex()) const;
+ int rowCount(const QModelIndex &parent = QModelIndex()) const;
+ bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex());
+
+private:
+ CookieJar *m_cookieJar;
+
+ // Domains we allow, Domains we block, Domains we allow for this session
+ QStringList m_allowedCookies;
+ QStringList m_blockedCookies;
+ QStringList m_sessionCookies;
+};
+
+class CookiesExceptionsDialog : public QDialog, public Ui_CookiesExceptionsDialog
+{
+ Q_OBJECT
+
+public:
+ CookiesExceptionsDialog(CookieJar *cookieJar, QWidget *parent = 0);
+
+private slots:
+ void block();
+ void allow();
+ void allowForSession();
+ void textChanged(const QString &text);
+
+private:
+ CookieExceptionsModel *m_exceptionsModel;
+ QSortFilterProxyModel *m_proxyModel;
+ CookieJar *m_cookieJar;
+};
+
+#endif // COOKIEJAR_H
diff --git a/examples/widgets/browser/cookies.ui b/examples/widgets/browser/cookies.ui
new file mode 100644
index 000000000..c4bccc548
--- /dev/null
+++ b/examples/widgets/browser/cookies.ui
@@ -0,0 +1,106 @@
+<ui version="4.0" >
+ <class>CookiesDialog</class>
+ <widget class="QDialog" name="CookiesDialog" >
+ <property name="geometry" >
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>550</width>
+ <height>370</height>
+ </rect>
+ </property>
+ <property name="windowTitle" >
+ <string>Cookies</string>
+ </property>
+ <layout class="QGridLayout" >
+ <item row="0" column="0" >
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0" >
+ <size>
+ <width>252</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="0" column="1" >
+ <widget class="SearchLineEdit" name="search" />
+ </item>
+ <item row="1" column="0" colspan="2" >
+ <widget class="EditTableView" name="cookiesTable" />
+ </item>
+ <item row="2" column="0" colspan="2" >
+ <layout class="QHBoxLayout" >
+ <item>
+ <widget class="QPushButton" name="removeButton" >
+ <property name="text" >
+ <string>&amp;Remove</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="removeAllButton" >
+ <property name="text" >
+ <string>Remove &amp;All Cookies</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0" >
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox" >
+ <property name="standardButtons" >
+ <set>QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>SearchLineEdit</class>
+ <extends>QLineEdit</extends>
+ <header>searchlineedit.h</header>
+ </customwidget>
+ <customwidget>
+ <class>EditTableView</class>
+ <extends>QTableView</extends>
+ <header>edittableview.h</header>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>CookiesDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>472</x>
+ <y>329</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>461</x>
+ <y>356</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/examples/widgets/browser/cookiesexceptions.ui b/examples/widgets/browser/cookiesexceptions.ui
new file mode 100644
index 000000000..3d9ef6241
--- /dev/null
+++ b/examples/widgets/browser/cookiesexceptions.ui
@@ -0,0 +1,184 @@
+<ui version="4.0" >
+ <class>CookiesExceptionsDialog</class>
+ <widget class="QDialog" name="CookiesExceptionsDialog" >
+ <property name="geometry" >
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>466</width>
+ <height>446</height>
+ </rect>
+ </property>
+ <property name="windowTitle" >
+ <string>Cookie Exceptions</string>
+ </property>
+ <layout class="QVBoxLayout" >
+ <item>
+ <widget class="QGroupBox" name="newExceptionGroupBox" >
+ <property name="title" >
+ <string>New Exception</string>
+ </property>
+ <layout class="QGridLayout" >
+ <item row="0" column="0" >
+ <layout class="QHBoxLayout" >
+ <item>
+ <widget class="QLabel" name="label" >
+ <property name="text" >
+ <string>Domain:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="domainLineEdit" />
+ </item>
+ </layout>
+ </item>
+ <item row="1" column="0" >
+ <layout class="QHBoxLayout" >
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0" >
+ <size>
+ <width>81</width>
+ <height>25</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="blockButton" >
+ <property name="enabled" >
+ <bool>false</bool>
+ </property>
+ <property name="text" >
+ <string>Block</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="allowForSessionButton" >
+ <property name="enabled" >
+ <bool>false</bool>
+ </property>
+ <property name="text" >
+ <string>Allow For Session</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="allowButton" >
+ <property name="enabled" >
+ <bool>false</bool>
+ </property>
+ <property name="text" >
+ <string>Allow</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="ExceptionsGroupBox" >
+ <property name="title" >
+ <string>Exceptions</string>
+ </property>
+ <layout class="QGridLayout" >
+ <item row="0" column="0" colspan="3" >
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0" >
+ <size>
+ <width>252</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="0" column="3" >
+ <widget class="SearchLineEdit" name="search" />
+ </item>
+ <item row="1" column="0" colspan="4" >
+ <widget class="EditTableView" name="exceptionTable" />
+ </item>
+ <item row="2" column="0" >
+ <widget class="QPushButton" name="removeButton" >
+ <property name="text" >
+ <string>&amp;Remove</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1" >
+ <widget class="QPushButton" name="removeAllButton" >
+ <property name="text" >
+ <string>Remove &amp;All</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="2" colspan="2" >
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0" >
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox" >
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons" >
+ <set>QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>SearchLineEdit</class>
+ <extends>QLineEdit</extends>
+ <header>searchlineedit.h</header>
+ </customwidget>
+ <customwidget>
+ <class>EditTableView</class>
+ <extends>QTableView</extends>
+ <header>edittableview.h</header>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>CookiesExceptionsDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>381</x>
+ <y>428</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>336</x>
+ <y>443</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/examples/widgets/browser/data/addtab.png b/examples/widgets/browser/data/addtab.png
new file mode 100644
index 000000000..20928fb40
--- /dev/null
+++ b/examples/widgets/browser/data/addtab.png
Binary files differ
diff --git a/examples/widgets/browser/data/browser.svg b/examples/widgets/browser/data/browser.svg
new file mode 100644
index 000000000..8795187b1
--- /dev/null
+++ b/examples/widgets/browser/data/browser.svg
@@ -0,0 +1,411 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="48px"
+ height="48px"
+ id="svg2160"
+ sodipodi:version="0.32"
+ inkscape:version="0.46"
+ inkscape:export-filename="c:\icons\qtbrowser48.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90"
+ sodipodi:docbase="C:\icons"
+ sodipodi:docname="browser.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape">
+ <defs
+ id="defs2162"><linearGradient
+ id="linearGradient3808">
+ <stop
+ id="stop3810"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:0.54263568;" />
+ <stop
+ id="stop3812"
+ offset="1"
+ style="stop-color:#000000;stop-opacity:0;" />
+</linearGradient>
+<inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 24 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="48 : 24 : 1"
+ inkscape:persp3d-origin="24 : 16 : 1"
+ id="perspective63" />
+<linearGradient
+ id="linearGradient3326">
+ <stop
+ style="stop-color:#000000;stop-opacity:0.3137255;"
+ offset="0"
+ id="stop3328" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop3330" />
+</linearGradient>
+<linearGradient
+ id="linearGradient3318">
+ <stop
+ style="stop-color:#000000;stop-opacity:0.3137255;"
+ offset="0"
+ id="stop3320" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop3322" />
+</linearGradient>
+<linearGradient
+ id="linearGradient3302">
+ <stop
+ style="stop-color:#000000;stop-opacity:0.3137255;"
+ offset="0"
+ id="stop3304" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop3306" />
+</linearGradient>
+<linearGradient
+ id="linearGradient3267">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop3269" />
+ <stop
+ id="stop3275"
+ offset="0.79661018"
+ style="stop-color:#000000;stop-opacity:1;" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop3271" />
+</linearGradient>
+<linearGradient
+ id="linearGradient3745">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0.19587629;"
+ offset="0"
+ id="stop3747" />
+ <stop
+ style="stop-color:#7cb2ff;stop-opacity:0.07216495;"
+ offset="1"
+ id="stop3749" />
+</linearGradient>
+<linearGradient
+ inkscape:collect="always"
+ id="linearGradient3561">
+ <stop
+ style="stop-color:#b1d0ff;stop-opacity:1;"
+ offset="0"
+ id="stop3563" />
+ <stop
+ style="stop-color:#b1d0ff;stop-opacity:0;"
+ offset="1"
+ id="stop3565" />
+</linearGradient>
+<linearGradient
+ id="linearGradient3181">
+ <stop
+ style="stop-color:#4f7a33;stop-opacity:1;"
+ offset="0"
+ id="stop3183" />
+ <stop
+ style="stop-color:#204712;stop-opacity:1;"
+ offset="1"
+ id="stop3185" />
+</linearGradient>
+<linearGradient
+ id="linearGradient3143">
+ <stop
+ style="stop-color:#c1dbff;stop-opacity:1;"
+ offset="0"
+ id="stop3145" />
+ <stop
+ style="stop-color:#004e92;stop-opacity:1;"
+ offset="1"
+ id="stop3147" />
+</linearGradient>
+<radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3143"
+ id="radialGradient3149"
+ cx="9.1428566"
+ cy="15.142858"
+ fx="9.1428566"
+ fy="15.142858"
+ r="20.121096"
+ gradientUnits="userSpaceOnUse" />
+<radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3181"
+ id="radialGradient3187"
+ cx="10.739879"
+ cy="18.250999"
+ fx="10.739879"
+ fy="18.250999"
+ r="7.4191086"
+ gradientTransform="matrix(1.0504709,0,0,1.5077925,-0.3797113,-9.2677171)"
+ gradientUnits="userSpaceOnUse" />
+<radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3181"
+ id="radialGradient3195"
+ cx="14.947268"
+ cy="35.920116"
+ fx="14.947268"
+ fy="35.920116"
+ r="6.0472684"
+ gradientTransform="matrix(1,0,0,0.7248478,0,9.8834985)"
+ gradientUnits="userSpaceOnUse" />
+<radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3181"
+ id="radialGradient3203"
+ cx="34.227203"
+ cy="24.681196"
+ fx="34.227203"
+ fy="24.681196"
+ r="6.7517419"
+ gradientTransform="matrix(0.9941509,-0.1079997,0.2962199,2.7267411,-7.1108629,-38.921508)"
+ gradientUnits="userSpaceOnUse" />
+<radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3561"
+ id="radialGradient3567"
+ cx="22.714285"
+ cy="23.571428"
+ fx="22.714285"
+ fy="23.571428"
+ r="19.828572"
+ gradientUnits="userSpaceOnUse" />
+<linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3745"
+ id="linearGradient3751"
+ x1="0.84126461"
+ y1="13.678415"
+ x2="31.397495"
+ y2="13.678415"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.8791332,0.7829527,-0.6285195,1.0951445,14.147627,-10.49311)" />
+<filter
+ inkscape:collect="always"
+ id="filter4176">
+ <feGaussianBlur
+ inkscape:collect="always"
+ stdDeviation="0.27747502"
+ id="feGaussianBlur4178" />
+</filter>
+<radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3267"
+ id="radialGradient3273"
+ cx="22.714285"
+ cy="23.571428"
+ fx="22.714285"
+ fy="23.571428"
+ r="19.428572"
+ gradientUnits="userSpaceOnUse" />
+<inkscape:perspective
+ id="perspective136"
+ inkscape:persp3d-origin="138.6795 : 92.479329 : 1"
+ inkscape:vp_z="277.35901 : 138.71899 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 138.71899 : 1"
+ sodipodi:type="inkscape:persp3d" />
+
+
+
+
+
+
+
+
+
+
+<linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3808"
+ id="linearGradient3806"
+ x1="32.829472"
+ y1="32.055603"
+ x2="34.522324"
+ y2="-1.0290829"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.8832227,0,0,1,-8.0103007,9.1923882)" />
+</defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="5.6568542"
+ inkscape:cx="30.924085"
+ inkscape:cy="24.59691"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="1299"
+ inkscape:window-height="883"
+ inkscape:window-x="373"
+ inkscape:window-y="89"
+ showguides="false" />
+ <metadata
+ id="metadata2165">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title>Qt Browser</dc:title>
+ <dc:creator>
+ <cc:Agent>
+ <dc:title>Jens Bache-Wiig</dc:title>
+ </cc:Agent>
+ </dc:creator>
+ <dc:rights>
+ <cc:Agent>
+ <dc:title>Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).</dc:title>
+ </cc:Agent>
+ </dc:rights>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer">
+ <path
+ sodipodi:type="arc"
+ style="opacity:0.78108437;fill:url(#radialGradient3273);fill-opacity:1;stroke:none;stroke-width:0.80000001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path3407"
+ sodipodi:cx="22.714285"
+ sodipodi:cy="23.571428"
+ sodipodi:rx="19.428572"
+ sodipodi:ry="19.428572"
+ d="M 42.142857,23.571428 A 19.428572,19.428572 0 1 1 3.2857132,23.571428 A 19.428572,19.428572 0 1 1 42.142857,23.571428 z"
+ transform="matrix(1.0818892,0,0,1.0409446,-2.4313375,0.4303723)" />
+ <path
+ sodipodi:type="arc"
+ style="fill:url(#radialGradient3149);fill-opacity:1;stroke:none;stroke-width:0.80000000000000004;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path2170"
+ sodipodi:cx="22.714285"
+ sodipodi:cy="23.571428"
+ sodipodi:rx="19.428572"
+ sodipodi:ry="19.428572"
+ d="M 42.142857 23.571428 A 19.428572 19.428572 0 1 1 3.2857132,23.571428 A 19.428572 19.428572 0 1 1 42.142857 23.571428 z" />
+ <path
+ d="M 26.602136,8.2160843 C 26.322653,8.1637524 26.048884,8.1512446 25.78375,8.1745351 L 25.783243,8.1743913 C 25.783243,8.1743913 23.973525,8.3138471 23.891496,8.3211793 C 22.239361,8.4705552 20.985434,10.008307 20.985434,12.131916 L 20.985434,37.174579 L 22.83515,39.126673 L 41.425135,33.998394 C 42.704203,33.746799 43.714709,33.629384 43.714709,31.78483 L 43.714709,11.392226 L 26.602136,8.2160843 z"
+ id="path2998"
+ style="fill:url(#linearGradient3806);fill-opacity:1"
+ sodipodi:nodetypes="cccsccccccc" />
+ <path
+ style="fill:url(#radialGradient3203);fill-opacity:1;fill-rule:evenodd;stroke:#1d3215;stroke-width:0.51392877000000003;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 37.535517,11.721122 C 32.782916,8.7478602 30.602351,6.3542385 32.09957,13.4346 C 32.320572,14.27055 33.291276,13.739232 33.291276,14.862228 C 33.291276,16.155819 32.607502,17.380765 31.797574,18.146663 C 30.959323,18.939344 31.011357,20.258984 31.797574,21.002459 C 33.06234,22.198469 33.942515,22.715936 35.572536,22.715936 C 36.6448,22.715936 37.003629,23.274262 37.23352,24.143834 C 37.362263,24.630808 38.410486,25.085663 38.894503,25.428942 C 38.938905,25.460433 38.139512,26.551348 38.139512,27.999158 C 38.139512,29.113512 38.405167,29.358325 38.743505,29.998215 C 38.949111,30.387072 36.418877,30.283794 36.025532,30.283794 C 35.005751,30.283794 34.181701,30.712163 33.15656,30.712163 C 32.264543,30.712163 31.099578,30.3566 31.344578,31.283323 C 31.763542,32.868074 32.552566,33.932342 32.552566,35.709806 C 32.552566,36.862272 31.047367,37.598377 30.287588,38.137232 C 29.30273,38.835721 29.133207,39.307154 28.475606,40.136289 C 28.132145,40.569341 26.990548,41.409612 28.475606,40.707448 C 29.476144,40.234375 31.192063,39.423774 32.09957,38.565601 C 33.257846,37.470293 34.527421,37.269266 35.723534,36.138176 C 36.659137,35.253436 37.512933,34.691155 38.29051,33.710749 C 39.024031,32.785889 39.498498,31.90347 39.498498,30.712163 C 39.498498,29.682482 39.308098,28.750366 39.951493,28.141948 C 40.902684,24.235856 42.225874,19.789742 39.751646,16.005086 C 38.569376,15.014407 37.717516,13.109859 37.535517,11.721122 z "
+ id="path3151"
+ sodipodi:nodetypes="ccsssssssssssssssssssccc" />
+ <path
+ style="fill:url(#radialGradient3187);fill-opacity:1;fill-rule:evenodd;stroke:#063a0a;stroke-width:0.51231807;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 14.777083,7.8630009 C 14.047432,8.4403746 12.751987,10.898939 13.27641,12.146301 C 13.709874,13.177316 14.920827,13.613143 15.827553,13.859622 C 16.568703,14.061091 17.049015,14.457271 17.478293,15.001835 C 17.832696,15.451415 17.971105,16.346745 18.078563,16.857932 C 18.298637,17.904845 18.947911,17.058563 17.62836,18.000145 C 17.234352,18.281296 14.875696,18.000145 14.476948,18.000145 C 11.976825,18.384083 14.297504,19.464893 14.92715,20.712903 C 15.204987,21.770261 15.377352,22.405336 15.377352,23.711213 C 15.377352,24.875672 15.377352,24.78389 15.377352,25.99564 C 15.377352,27.194757 15.044241,27.28063 13.876679,27.28063 C 13.023055,27.28063 12.647321,26.423969 11.625669,26.423969 C 10.400599,26.423969 11.303539,27.667106 11.475602,27.994513 C 12.006402,29.004538 11.662121,29.599737 10.875334,28.851174 C 9.855722,27.881096 8.8280305,26.760556 8.0240557,25.99564 C 2.8789379,25.807372 4.5677903,23.466499 3.9722395,18.999582 C 5.041259,16.526382 4.7558935,17.248897 7.2737194,12.574632 C 10.149914,9.5491592 13.589212,5.9532919 14.777083,7.8630009 z"
+ id="path3159"
+ sodipodi:nodetypes="csssssccsssssscccc" />
+ <path
+ style="fill:url(#radialGradient3195);fill-opacity:1;fill-rule:evenodd;stroke:#163c0c;stroke-width:0.59999999999999998;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 10.265966,34.571429 C 9.245427,35.081699 8.6225774,36.042538 9.980252,36.857143 C 10.637564,37.25153 11.478587,37.606311 12.265966,38 C 13.258976,38.496505 14.481138,39.018522 15.408823,39.714286 C 16.227572,40.328348 15.587589,39.928184 16.123109,38.857143 C 16.827927,37.447507 18.14516,38.79674 18.837395,39.142857 C 20.044787,39.746554 20.46001,38.652394 20.694537,37.714286 C 20.459863,35.791335 18.579948,34.625723 17.123109,33.285715 C 16.704922,32.588736 15.507117,31.689713 14.837395,31.857143 C 13.49505,33.304042 12.350312,33.960279 10.265966,34.571429 z "
+ id="path3161"
+ sodipodi:nodetypes="cssssscccc" />
+ <path
+ sodipodi:type="arc"
+ style="fill:none;fill-opacity:1;stroke:url(#radialGradient3567);stroke-width:0.80000001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.6502732"
+ id="path3557"
+ sodipodi:cx="22.714285"
+ sodipodi:cy="23.571428"
+ sodipodi:rx="19.428572"
+ sodipodi:ry="19.428572"
+ d="M 42.142857 23.571428 A 19.428572 19.428572 0 1 1 3.2857132,23.571428 A 19.428572 19.428572 0 1 1 42.142857 23.571428 z"
+ transform="matrix(0.95317,0,0,0.95317,0.9922816,1.1752786)" />
+ <path
+ style="fill:url(#linearGradient3751);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 39.916926,27.786316 C 44.588637,26.790847 38.225604,13.201712 32.946381,8.5000566 C 18.135275,-0.40265528 10.844456,5.6490056 3.6645529,16.333771 C 5.7478288,18.189127 14.704728,33.158645 39.916926,27.786316 z"
+ id="path3578"
+ sodipodi:nodetypes="cccs" />
+ <path
+ d="M 45.902562,20.610592 C 46.007701,20.610592 46.120332,20.603354 46.240455,20.590275 L 45.609873,20.590275 C 45.697743,20.603608 45.798946,20.610592 45.902562,20.610592 z"
+ id="path3012"
+ style="fill:#0a6333" />
+ <path
+ sodipodi:type="arc"
+ style="fill:none;fill-opacity:1;stroke:#273e5e;stroke-width:0.80000001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path3818"
+ sodipodi:cx="22.714285"
+ sodipodi:cy="23.571428"
+ sodipodi:rx="19.428572"
+ sodipodi:ry="19.428572"
+ d="M 42.142857,23.571428 A 19.428572,19.428572 0 1 1 3.2857132,23.571428 A 19.428572,19.428572 0 1 1 42.142857,23.571428 z"
+ transform="matrix(0.9754581,0,0,0.9754581,0.3821951,0.7002631)" />
+ <g
+ transform="matrix(0.1269799,0,0,0.1269799,23.283534,9.5774104)"
+ id="g236">
+ <path
+ style="fill:#024c1c"
+ id="path238"
+ d="M 44.233,0.368 C 42.032,0.004 39.876,-0.083 37.788,0.079 L 37.784,0.078 C 37.784,0.078 23.532,1.048 22.886,1.099 C 9.875,2.138 0,12.834 0,27.605 L 0,201.792 L 14.567,215.37 L 160.968,190.766 C 171.041,189.016 178.999,177.133 178.999,164.303 L 178.999,22.46 L 44.233,0.368 z" />
+
+ <path
+ style="fill:#66b036"
+ id="path240"
+ d="M 179,164.304 C 179,177.134 171.042,189.017 160.969,190.767 L 14.567,215.37 L 14.567,26.683 C 14.567,9.52 28.263,-2.264 44.231,0.368 L 179,22.462 L 179,164.304 z" />
+
+ <g
+ id="g242">
+ <path
+ style="fill:#ffffff"
+ id="path244"
+ d="M 133.897,47.137 L 145.72,48.411 L 145.72,69.158 L 159.025,70.099 L 159.025,83.113 L 145.72,82.502 L 145.72,130.066 C 145.72,134.207 146.176,136.869 147.093,138.064 C 147.919,139.158 149.195,139.697 150.907,139.697 C 151.069,139.697 151.24,139.695 151.414,139.683 C 154.031,139.533 156.878,138.728 159.98,137.314 L 159.98,149.275 C 154.707,151.591 149.532,152.966 144.452,153.398 C 143.716,153.457 143.005,153.486 142.317,153.486 C 137.716,153.486 134.199,152.152 131.797,149.451 C 128.998,146.318 127.598,141.285 127.598,134.387 L 127.598,81.661 L 121.209,81.368 L 121.209,67.424 L 129,67.985 L 133.897,47.137 z" />
+
+ </g>
+
+ <polygon
+ style="fill:#0a6333"
+ id="polygon246"
+ points="159.027,83.112 145.722,82.501 145.722,82.785 152.854,83.112 159.027,83.112 " />
+
+ <path
+ style="fill:#024c1c"
+ id="path248"
+ d="M 148.488,139.21 C 149.168,139.548 149.96,139.696 150.908,139.696 C 151.07,139.696 151.241,139.694 151.415,139.682 C 154.032,139.532 156.879,138.727 159.981,137.313 L 153.806,137.313 C 151.938,138.169 150.178,138.808 148.488,139.21 z" />
+
+ <path
+ style="fill:#024c1c"
+ id="path250"
+ d="M 133.897,47.137 L 127.723,47.137 L 122.93,67.549 L 129,67.985 L 133.897,47.137 z M 131.799,149.45 C 129,146.317 127.6,141.284 127.6,134.386 L 127.6,81.661 L 121.211,81.368 L 121.211,67.424 L 115.03,67.424 L 115.03,70.539 C 115.926,73.897 116.63,77.539 117.149,81.465 L 121.426,81.661 L 121.426,134.386 C 121.426,141.284 122.827,146.318 125.625,149.45 C 128.029,152.151 131.541,153.485 136.141,153.485 L 142.318,153.485 C 137.718,153.485 134.2,152.151 131.799,149.45 z" />
+
+ <path
+ style="fill:#0a6333"
+ id="path252"
+ d="M 102.954,170.419 C 103.782,170.419 104.669,170.362 105.615,170.259 L 100.649,170.259 C 101.341,170.364 102.138,170.419 102.954,170.419 z" />
+
+ <path
+ style="fill:#ffffff"
+ id="path254"
+ d="M 112.036,139.78 C 107.81,149.749 101.365,156.27 92.542,159.288 C 93.43,163.856 94.778,166.929 96.567,168.55 C 97.955,169.796 100.094,170.419 102.958,170.419 C 103.782,170.419 104.671,170.362 105.615,170.259 L 105.615,183.736 L 99.497,184.539 C 97.692,184.771 95.98,184.889 94.361,184.889 C 89.001,184.889 84.665,183.59 81.402,180.961 C 77.085,177.496 73.899,170.805 71.857,160.908 C 62.48,158.91 55.166,152.945 50.103,142.937 C 44.965,132.769 42.349,117.895 42.349,98.441 C 42.349,77.466 45.927,61.985 52.971,52.169 C 58.912,43.885 67.202,39.812 77.634,39.812 C 79.306,39.812 81.033,39.916 82.809,40.124 C 95.081,41.539 103.977,47.329 109.77,57.362 C 115.453,67.177 118.243,81.244 118.243,99.721 C 118.242,116.643 116.186,129.954 112.036,139.78 z M 93.582,135.933 C 95.996,129.724 97.189,117.54 97.189,99.37 C 97.189,83.054 96.007,71.837 93.608,65.682 C 91.21,59.496 87.622,56.153 82.808,55.731 C 82.441,55.7 82.075,55.681 81.724,55.681 C 77.264,55.681 73.84,58.283 71.447,63.508 C 68.863,69.201 67.555,81.003 67.555,98.866 C 67.555,116.129 68.826,128.379 71.388,135.569 C 73.804,142.419 77.423,145.813 82.174,145.813 C 82.384,145.813 82.593,145.805 82.809,145.79 C 87.566,145.489 91.148,142.202 93.582,135.933" />
+
+ <path
+ style="fill:#024c1c"
+ id="path256"
+ d="M 84.708,183.003 C 84.59,182.95 84.477,182.896 84.361,182.839 C 84.349,182.835 84.336,182.829 84.323,182.821 C 84.218,182.77 84.115,182.716 84.011,182.663 C 83.991,182.653 83.971,182.642 83.948,182.63 C 83.854,182.579 83.761,182.528 83.667,182.476 C 83.636,182.46 83.609,182.443 83.579,182.427 C 83.494,182.38 83.412,182.331 83.328,182.284 C 83.286,182.263 83.25,182.239 83.209,182.214 C 83.137,182.171 83.062,182.128 82.994,182.083 C 82.943,182.054 82.897,182.024 82.848,181.993 C 82.785,181.954 82.726,181.915 82.663,181.876 C 82.606,181.837 82.552,181.798 82.492,181.759 C 82.442,181.726 82.392,181.693 82.342,181.659 C 82.272,181.612 82.206,181.563 82.141,181.518 C 82.101,181.489 82.061,181.463 82.021,181.432 C 81.943,181.377 81.866,181.319 81.79,181.26 C 81.764,181.239 81.735,181.221 81.708,181.199 C 81.607,181.121 81.505,181.039 81.402,180.959 C 77.085,177.494 73.899,170.803 71.857,160.906 C 62.48,158.908 55.166,152.943 50.103,142.935 C 44.965,132.767 42.349,117.893 42.349,98.439 C 42.349,77.464 45.927,61.983 52.971,52.167 C 58.912,43.883 67.202,39.81 77.634,39.81 C 77.67,39.81 71.114,39.806 71.114,39.806 L 71.114,39.81 C 60.694,39.818 52.411,43.89 46.476,52.167 C 39.434,61.984 35.855,77.465 35.855,98.439 C 35.855,117.892 38.469,132.767 43.609,142.935 C 48.671,152.943 55.983,158.908 65.361,160.906 C 67.403,170.802 70.588,177.494 74.904,180.959 C 78.168,183.588 82.507,184.887 87.867,184.887 C 87.967,184.887 88.07,184.887 88.17,184.885 L 93.861,184.885 C 90.361,184.828 87.306,184.203 84.716,183.006 C 84.712,183.007 84.708,183.007 84.708,183.003 z M 87.113,65.681 C 89.511,71.837 90.69,83.054 90.69,99.369 C 90.69,117.539 89.502,129.723 87.083,135.932 C 85.142,140.942 82.439,144.047 79.013,145.248 C 79.999,145.621 81.058,145.81 82.173,145.81 C 82.383,145.81 82.592,145.802 82.808,145.787 C 87.567,145.488 91.149,142.201 93.582,135.932 C 95.996,129.723 97.189,117.539 97.189,99.369 C 97.189,83.053 96.007,71.836 93.608,65.681 C 91.21,59.495 87.622,56.152 82.808,55.73 C 82.441,55.699 82.075,55.68 81.724,55.68 C 80.601,55.68 79.549,55.845 78.556,56.173 L 78.556,56.175 L 78.556,56.175 C 82.254,57.322 85.104,60.5 87.113,65.681 z" />
+
+</g>
+ </g>
+</svg>
diff --git a/examples/widgets/browser/data/closetab.png b/examples/widgets/browser/data/closetab.png
new file mode 100644
index 000000000..ab9d669ee
--- /dev/null
+++ b/examples/widgets/browser/data/closetab.png
Binary files differ
diff --git a/examples/widgets/browser/data/data.qrc b/examples/widgets/browser/data/data.qrc
new file mode 100644
index 000000000..c7d0294c1
--- /dev/null
+++ b/examples/widgets/browser/data/data.qrc
@@ -0,0 +1,11 @@
+<!DOCTYPE RCC><RCC version="1.0">
+<qresource>
+ <file>addtab.png</file>
+ <file>closetab.png</file>
+ <file>history.png</file>
+ <file>browser.svg</file>
+ <file>defaultbookmarks.xbel</file>
+ <file>loading.gif</file>
+ <file>defaulticon.png</file>
+</qresource>
+</RCC>
diff --git a/examples/widgets/browser/data/defaultbookmarks.xbel b/examples/widgets/browser/data/defaultbookmarks.xbel
new file mode 100644
index 000000000..7a95e36b3
--- /dev/null
+++ b/examples/widgets/browser/data/defaultbookmarks.xbel
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE xbel>
+<xbel version="1.0">
+ <folder folded="yes">
+ <title>Bookmarks Bar</title>
+ <bookmark href="http://qt-project.org/">
+ <title>Qt Home Page</title>
+ </bookmark>
+ <bookmark href="http://webkit.org/">
+ <title>WebKit.org</title>
+ </bookmark>
+ <bookmark href="http://qt-project.org/doc/">
+ <title>Qt Documentation</title>
+ </bookmark>
+ <bookmark href="http://qt-project.org/quarterly/">
+ <title>Qt Quarterly</title>
+ </bookmark>
+ <bookmark href="http://planet.qt-project.org/">
+ <title>Qt Blog</title>
+ </bookmark>
+ <bookmark href="http://www.qtcentre.org/">
+ <title>Qt Centre</title>
+ </bookmark>
+ <bookmark href="http://qt-apps.org/">
+ <title>Qt-Apps.org</title>
+ </bookmark>
+ <bookmark href="http://qt-project.org/wiki/OnlineCommunities/">
+ <title>Online Communities</title>
+ </bookmark>
+ <bookmark href="http://xkcd.com/">
+ <title>xkcd</title>
+ </bookmark>
+ <bookmark href="http://twitter.com/qtproject">
+ <title>Twitter</title>
+ </bookmark>
+ </folder>
+ <folder folded="yes">
+ <title>Bookmarks Menu</title>
+ <bookmark href="http://reddit.com/">
+ <title>reddit.com: what's new online!</title>
+ </bookmark>
+ </folder>
+</xbel>
diff --git a/examples/widgets/browser/data/defaulticon.png b/examples/widgets/browser/data/defaulticon.png
new file mode 100644
index 000000000..01a0920c9
--- /dev/null
+++ b/examples/widgets/browser/data/defaulticon.png
Binary files differ
diff --git a/examples/widgets/browser/data/history.png b/examples/widgets/browser/data/history.png
new file mode 100644
index 000000000..552a1cbd8
--- /dev/null
+++ b/examples/widgets/browser/data/history.png
Binary files differ
diff --git a/examples/widgets/browser/data/loading.gif b/examples/widgets/browser/data/loading.gif
new file mode 100644
index 000000000..c1545eb0e
--- /dev/null
+++ b/examples/widgets/browser/data/loading.gif
Binary files differ
diff --git a/examples/widgets/browser/doc/images/browser-demo.png b/examples/widgets/browser/doc/images/browser-demo.png
new file mode 100644
index 000000000..09d065095
--- /dev/null
+++ b/examples/widgets/browser/doc/images/browser-demo.png
Binary files differ
diff --git a/examples/widgets/browser/doc/src/browser.qdoc b/examples/widgets/browser/doc/src/browser.qdoc
new file mode 100644
index 000000000..4e53de38f
--- /dev/null
+++ b/examples/widgets/browser/doc/src/browser.qdoc
@@ -0,0 +1,41 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the documentation of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:FDL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Free Documentation License Usage
+** Alternatively, this file may be used under the terms of the GNU Free
+** Documentation License version 1.3 as published by the Free Software
+** Foundation and appearing in the file included in the packaging of
+** this file. Please review the following information to ensure
+** the GNU Free Documentation License version 1.3 requirements
+** will be met: http://www.gnu.org/copyleft/fdl.html.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+/*!
+ \example webkitwidgets/browser
+ \title Tab Browser
+ \brief Demonstrates a complete web browsing experience
+ \ingroup webkit-widgetexamples
+
+ The Tab Browser example shows \l{Qt WebKit} module in action,
+ providing a little Web browser application with support for tabs.
+
+ \image browser-demo.png
+
+ This browser is the foundation for the \l{Arora Browser}, a simple cross-platform
+ Web browser.
+*/
diff --git a/examples/widgets/browser/downloaditem.ui b/examples/widgets/browser/downloaditem.ui
new file mode 100644
index 000000000..4a0a0fd9a
--- /dev/null
+++ b/examples/widgets/browser/downloaditem.ui
@@ -0,0 +1,134 @@
+<ui version="4.0" >
+ <class>DownloadItem</class>
+ <widget class="QWidget" name="DownloadItem" >
+ <property name="geometry" >
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>423</width>
+ <height>110</height>
+ </rect>
+ </property>
+ <property name="windowTitle" >
+ <string>Form</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="fileIcon" >
+ <property name="sizePolicy" >
+ <sizepolicy vsizetype="Minimum" hsizetype="Minimum" >
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text" >
+ <string>Ico</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout_2" >
+ <item>
+ <widget class="SqueezeLabel" native="1" name="fileNameLabel" >
+ <property name="sizePolicy" >
+ <sizepolicy vsizetype="Preferred" hsizetype="Expanding" >
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text" stdset="0" >
+ <string>Filename</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QProgressBar" name="progressBar" >
+ <property name="value" >
+ <number>0</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="SqueezeLabel" native="1" name="downloadInfoLabel" >
+ <property name="sizePolicy" >
+ <sizepolicy vsizetype="Preferred" hsizetype="Minimum" >
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text" stdset="0" >
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout" >
+ <item>
+ <spacer name="verticalSpacer" >
+ <property name="orientation" >
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0" >
+ <size>
+ <width>17</width>
+ <height>1</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="tryAgainButton" >
+ <property name="enabled" >
+ <bool>false</bool>
+ </property>
+ <property name="text" >
+ <string>Try Again</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="stopButton" >
+ <property name="text" >
+ <string>Stop</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="openButton" >
+ <property name="text" >
+ <string>Open</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_2" >
+ <property name="orientation" >
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0" >
+ <size>
+ <width>17</width>
+ <height>5</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>SqueezeLabel</class>
+ <extends>QWidget</extends>
+ <header>squeezelabel.h</header>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/examples/widgets/browser/downloadmanager.cpp b/examples/widgets/browser/downloadmanager.cpp
new file mode 100644
index 000000000..04889d9e3
--- /dev/null
+++ b/examples/widgets/browser/downloadmanager.cpp
@@ -0,0 +1,578 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the demonstration applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "downloadmanager.h"
+
+#include "autosaver.h"
+#include "browserapplication.h"
+#include "networkaccessmanager.h"
+
+#include <math.h>
+
+#include <QtCore/QMetaEnum>
+#include <QtCore/QSettings>
+
+#include <QtGui/QDesktopServices>
+#include <QtWidgets/QFileDialog>
+#include <QtWidgets/QHeaderView>
+#include <QtWidgets/QFileIconProvider>
+
+#include <QtCore/QDebug>
+
+#include <QWebSettings>
+
+/*!
+ DownloadItem is a widget that is displayed in the download manager list.
+ It moves the data from the QNetworkReply into the QFile as well
+ as update the information/progressbar and report errors.
+ */
+DownloadItem::DownloadItem(QNetworkReply *reply, bool requestFileName, QWidget *parent)
+ : QWidget(parent)
+ , m_reply(reply)
+ , m_requestFileName(requestFileName)
+ , m_bytesReceived(0)
+{
+ setupUi(this);
+ QPalette p = downloadInfoLabel->palette();
+ p.setColor(QPalette::Text, Qt::darkGray);
+ downloadInfoLabel->setPalette(p);
+ progressBar->setMaximum(0);
+ tryAgainButton->hide();
+ connect(stopButton, SIGNAL(clicked()), this, SLOT(stop()));
+ connect(openButton, SIGNAL(clicked()), this, SLOT(open()));
+ connect(tryAgainButton, SIGNAL(clicked()), this, SLOT(tryAgain()));
+
+ init();
+}
+
+void DownloadItem::init()
+{
+ if (!m_reply)
+ return;
+
+ // attach to the m_reply
+ m_url = m_reply->url();
+ m_reply->setParent(this);
+ connect(m_reply, SIGNAL(readyRead()), this, SLOT(downloadReadyRead()));
+ connect(m_reply, SIGNAL(error(QNetworkReply::NetworkError)),
+ this, SLOT(error(QNetworkReply::NetworkError)));
+ connect(m_reply, SIGNAL(downloadProgress(qint64,qint64)),
+ this, SLOT(downloadProgress(qint64,qint64)));
+ connect(m_reply, SIGNAL(metaDataChanged()),
+ this, SLOT(metaDataChanged()));
+ connect(m_reply, SIGNAL(finished()),
+ this, SLOT(finished()));
+
+ // reset info
+ downloadInfoLabel->clear();
+ progressBar->setValue(0);
+ getFileName();
+
+ // start timer for the download estimation
+ m_downloadTime.start();
+
+ if (m_reply->error() != QNetworkReply::NoError) {
+ error(m_reply->error());
+ finished();
+ }
+}
+
+void DownloadItem::getFileName()
+{
+ QSettings settings;
+ settings.beginGroup(QLatin1String("downloadmanager"));
+ QString defaultLocation = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
+ QString downloadDirectory = settings.value(QLatin1String("downloadDirectory"), defaultLocation).toString();
+ if (!downloadDirectory.isEmpty())
+ downloadDirectory += QLatin1Char('/');
+
+ QString defaultFileName = saveFileName(downloadDirectory);
+ QString fileName = defaultFileName;
+ if (m_requestFileName) {
+ fileName = QFileDialog::getSaveFileName(this, tr("Save File"), defaultFileName);
+ if (fileName.isEmpty()) {
+ m_reply->close();
+ fileNameLabel->setText(tr("Download canceled: %1").arg(QFileInfo(defaultFileName).fileName()));
+ return;
+ }
+ }
+ m_output.setFileName(fileName);
+ fileNameLabel->setText(QFileInfo(m_output.fileName()).fileName());
+ if (m_requestFileName)
+ downloadReadyRead();
+}
+
+QString DownloadItem::saveFileName(const QString &directory) const
+{
+ // Move this function into QNetworkReply to also get file name sent from the server
+ QString path = m_url.path();
+ QFileInfo info(path);
+ QString baseName = info.completeBaseName();
+ QString endName = info.suffix();
+
+ if (baseName.isEmpty()) {
+ baseName = QLatin1String("unnamed_download");
+ qDebug() << "DownloadManager:: downloading unknown file:" << m_url;
+ }
+ QString name = directory + baseName + QLatin1Char('.') + endName;
+ if (QFile::exists(name)) {
+ // already exists, don't overwrite
+ int i = 1;
+ do {
+ name = directory + baseName + QLatin1Char('-') + QString::number(i++) + QLatin1Char('.') + endName;
+ } while (QFile::exists(name));
+ }
+ return name;
+}
+
+
+void DownloadItem::stop()
+{
+ setUpdatesEnabled(false);
+ stopButton->setEnabled(false);
+ stopButton->hide();
+ tryAgainButton->setEnabled(true);
+ tryAgainButton->show();
+ setUpdatesEnabled(true);
+ m_reply->abort();
+}
+
+void DownloadItem::open()
+{
+ QFileInfo info(m_output);
+ QUrl url = QUrl::fromLocalFile(info.absolutePath());
+ QDesktopServices::openUrl(url);
+}
+
+void DownloadItem::tryAgain()
+{
+ if (!tryAgainButton->isEnabled())
+ return;
+
+ tryAgainButton->setEnabled(false);
+ tryAgainButton->setVisible(false);
+ stopButton->setEnabled(true);
+ stopButton->setVisible(true);
+ progressBar->setVisible(true);
+
+ QNetworkReply *r = BrowserApplication::networkAccessManager()->get(QNetworkRequest(m_url));
+ if (m_reply)
+ m_reply->deleteLater();
+ if (m_output.exists())
+ m_output.remove();
+ m_reply = r;
+ init();
+ emit statusChanged();
+}
+
+void DownloadItem::downloadReadyRead()
+{
+ if (m_requestFileName && m_output.fileName().isEmpty())
+ return;
+ if (!m_output.isOpen()) {
+ // in case someone else has already put a file there
+ if (!m_requestFileName)
+ getFileName();
+ if (!m_output.open(QIODevice::WriteOnly)) {
+ downloadInfoLabel->setText(tr("Error opening save file: %1")
+ .arg(m_output.errorString()));
+ stopButton->click();
+ emit statusChanged();
+ return;
+ }
+ emit statusChanged();
+ }
+ if (-1 == m_output.write(m_reply->readAll())) {
+ downloadInfoLabel->setText(tr("Error saving: %1")
+ .arg(m_output.errorString()));
+ stopButton->click();
+ }
+}
+
+void DownloadItem::error(QNetworkReply::NetworkError)
+{
+ qDebug() << "DownloadItem::error" << m_reply->errorString() << m_url;
+ downloadInfoLabel->setText(tr("Network Error: %1").arg(m_reply->errorString()));
+ tryAgainButton->setEnabled(true);
+ tryAgainButton->setVisible(true);
+}
+
+void DownloadItem::metaDataChanged()
+{
+ qDebug() << "DownloadItem::metaDataChanged: not handled.";
+}
+
+void DownloadItem::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
+{
+ m_bytesReceived = bytesReceived;
+ if (bytesTotal == -1) {
+ progressBar->setValue(0);
+ progressBar->setMaximum(0);
+ } else {
+ progressBar->setValue(bytesReceived);
+ progressBar->setMaximum(bytesTotal);
+ }
+ updateInfoLabel();
+}
+
+void DownloadItem::updateInfoLabel()
+{
+ if (m_reply->error() == QNetworkReply::NoError)
+ return;
+
+ qint64 bytesTotal = progressBar->maximum();
+ bool running = !downloadedSuccessfully();
+
+ // update info label
+ double speed = m_bytesReceived * 1000.0 / m_downloadTime.elapsed();
+ double timeRemaining = ((double)(bytesTotal - m_bytesReceived)) / speed;
+ QString timeRemainingString = tr("seconds");
+ if (timeRemaining > 60) {
+ timeRemaining = timeRemaining / 60;
+ timeRemainingString = tr("minutes");
+ }
+ timeRemaining = floor(timeRemaining);
+
+ // When downloading the eta should never be 0
+ if (timeRemaining == 0)
+ timeRemaining = 1;
+
+ QString info;
+ if (running) {
+ QString remaining;
+ if (bytesTotal != 0)
+ remaining = tr("- %4 %5 remaining")
+ .arg(timeRemaining)
+ .arg(timeRemainingString);
+ info = tr("%1 of %2 (%3/sec) %4")
+ .arg(dataString(m_bytesReceived))
+ .arg(bytesTotal == 0 ? tr("?") : dataString(bytesTotal))
+ .arg(dataString((int)speed))
+ .arg(remaining);
+ } else {
+ if (m_bytesReceived == bytesTotal)
+ info = dataString(m_output.size());
+ else
+ info = tr("%1 of %2 - Stopped")
+ .arg(dataString(m_bytesReceived))
+ .arg(dataString(bytesTotal));
+ }
+ downloadInfoLabel->setText(info);
+}
+
+QString DownloadItem::dataString(int size) const
+{
+ QString unit;
+ if (size < 1024) {
+ unit = tr("bytes");
+ } else if (size < 1024*1024) {
+ size /= 1024;
+ unit = tr("kB");
+ } else {
+ size /= 1024*1024;
+ unit = tr("MB");
+ }
+ return QString(QLatin1String("%1 %2")).arg(size).arg(unit);
+}
+
+bool DownloadItem::downloading() const
+{
+ return (progressBar->isVisible());
+}
+
+bool DownloadItem::downloadedSuccessfully() const
+{
+ return (stopButton->isHidden() && tryAgainButton->isHidden());
+}
+
+void DownloadItem::finished()
+{
+ progressBar->hide();
+ stopButton->setEnabled(false);
+ stopButton->hide();
+ m_output.close();
+ updateInfoLabel();
+ emit statusChanged();
+}
+
+/*!
+ DownloadManager is a Dialog that contains a list of DownloadItems
+
+ It is a basic download manager. It only downloads the file, doesn't do BitTorrent,
+ extract zipped files or anything fancy.
+ */
+DownloadManager::DownloadManager(QWidget *parent)
+ : QDialog(parent)
+ , m_autoSaver(new AutoSaver(this))
+ , m_manager(BrowserApplication::networkAccessManager())
+ , m_iconProvider(0)
+ , m_removePolicy(Never)
+{
+ setupUi(this);
+ downloadsView->setShowGrid(false);
+ downloadsView->verticalHeader()->hide();
+ downloadsView->horizontalHeader()->hide();
+ downloadsView->setAlternatingRowColors(true);
+ downloadsView->horizontalHeader()->setStretchLastSection(true);
+ m_model = new DownloadModel(this);
+ downloadsView->setModel(m_model);
+ connect(cleanupButton, SIGNAL(clicked()), this, SLOT(cleanup()));
+ load();
+}
+
+DownloadManager::~DownloadManager()
+{
+ m_autoSaver->changeOccurred();
+ m_autoSaver->saveIfNeccessary();
+ if (m_iconProvider)
+ delete m_iconProvider;
+}
+
+int DownloadManager::activeDownloads() const
+{
+ int count = 0;
+ for (int i = 0; i < m_downloads.count(); ++i) {
+ if (m_downloads.at(i)->stopButton->isEnabled())
+ ++count;
+ }
+ return count;
+}
+
+void DownloadManager::download(const QNetworkRequest &request, bool requestFileName)
+{
+ if (request.url().isEmpty())
+ return;
+ handleUnsupportedContent(m_manager->get(request), requestFileName);
+}
+
+void DownloadManager::handleUnsupportedContent(QNetworkReply *reply, bool requestFileName)
+{
+ if (!reply || reply->url().isEmpty())
+ return;
+ QVariant header = reply->header(QNetworkRequest::ContentLengthHeader);
+ bool ok;
+ int size = header.toInt(&ok);
+ if (ok && size == 0)
+ return;
+
+ qDebug() << "DownloadManager::handleUnsupportedContent" << reply->url() << "requestFileName" << requestFileName;
+ DownloadItem *item = new DownloadItem(reply, requestFileName, this);
+ addItem(item);
+}
+
+void DownloadManager::addItem(DownloadItem *item)
+{
+ connect(item, SIGNAL(statusChanged()), this, SLOT(updateRow()));
+ int row = m_downloads.count();
+ m_model->beginInsertRows(QModelIndex(), row, row);
+ m_downloads.append(item);
+ m_model->endInsertRows();
+ updateItemCount();
+ if (row == 0)
+ show();
+ downloadsView->setIndexWidget(m_model->index(row, 0), item);
+ QIcon icon = style()->standardIcon(QStyle::SP_FileIcon);
+ item->fileIcon->setPixmap(icon.pixmap(48, 48));
+ downloadsView->setRowHeight(row, item->sizeHint().height());
+}
+
+void DownloadManager::updateRow()
+{
+ DownloadItem *item = qobject_cast<DownloadItem*>(sender());
+ int row = m_downloads.indexOf(item);
+ if (-1 == row)
+ return;
+ if (!m_iconProvider)
+ m_iconProvider = new QFileIconProvider();
+ QIcon icon = m_iconProvider->icon(item->m_output.fileName());
+ if (icon.isNull())
+ icon = style()->standardIcon(QStyle::SP_FileIcon);
+ item->fileIcon->setPixmap(icon.pixmap(48, 48));
+ downloadsView->setRowHeight(row, item->minimumSizeHint().height());
+
+ bool remove = false;
+ QWebSettings *globalSettings = QWebSettings::globalSettings();
+ if (!item->downloading()
+ && globalSettings->testAttribute(QWebSettings::PrivateBrowsingEnabled))
+ remove = true;
+
+ if (item->downloadedSuccessfully()
+ && removePolicy() == DownloadManager::SuccessFullDownload) {
+ remove = true;
+ }
+ if (remove)
+ m_model->removeRow(row);
+
+ cleanupButton->setEnabled(m_downloads.count() - activeDownloads() > 0);
+}
+
+DownloadManager::RemovePolicy DownloadManager::removePolicy() const
+{
+ return m_removePolicy;
+}
+
+void DownloadManager::setRemovePolicy(RemovePolicy policy)
+{
+ if (policy == m_removePolicy)
+ return;
+ m_removePolicy = policy;
+ m_autoSaver->changeOccurred();
+}
+
+void DownloadManager::save() const
+{
+ QSettings settings;
+ settings.beginGroup(QLatin1String("downloadmanager"));
+ QMetaEnum removePolicyEnum = staticMetaObject.enumerator(staticMetaObject.indexOfEnumerator("RemovePolicy"));
+ settings.setValue(QLatin1String("removeDownloadsPolicy"), QLatin1String(removePolicyEnum.valueToKey(m_removePolicy)));
+ settings.setValue(QLatin1String("size"), size());
+ if (m_removePolicy == Exit)
+ return;
+
+ for (int i = 0; i < m_downloads.count(); ++i) {
+ QString key = QString(QLatin1String("download_%1_")).arg(i);
+ settings.setValue(key + QLatin1String("url"), m_downloads[i]->m_url);
+ settings.setValue(key + QLatin1String("location"), QFileInfo(m_downloads[i]->m_output).filePath());
+ settings.setValue(key + QLatin1String("done"), m_downloads[i]->downloadedSuccessfully());
+ }
+ int i = m_downloads.count();
+ QString key = QString(QLatin1String("download_%1_")).arg(i);
+ while (settings.contains(key + QLatin1String("url"))) {
+ settings.remove(key + QLatin1String("url"));
+ settings.remove(key + QLatin1String("location"));
+ settings.remove(key + QLatin1String("done"));
+ key = QString(QLatin1String("download_%1_")).arg(++i);
+ }
+}
+
+void DownloadManager::load()
+{
+ QSettings settings;
+ settings.beginGroup(QLatin1String("downloadmanager"));
+ QSize size = settings.value(QLatin1String("size")).toSize();
+ if (size.isValid())
+ resize(size);
+ QByteArray value = settings.value(QLatin1String("removeDownloadsPolicy"), QLatin1String("Never")).toByteArray();
+ QMetaEnum removePolicyEnum = staticMetaObject.enumerator(staticMetaObject.indexOfEnumerator("RemovePolicy"));
+ m_removePolicy = removePolicyEnum.keyToValue(value) == -1 ?
+ Never :
+ static_cast<RemovePolicy>(removePolicyEnum.keyToValue(value));
+
+ int i = 0;
+ QString key = QString(QLatin1String("download_%1_")).arg(i);
+ while (settings.contains(key + QLatin1String("url"))) {
+ QUrl url = settings.value(key + QLatin1String("url")).toUrl();
+ QString fileName = settings.value(key + QLatin1String("location")).toString();
+ bool done = settings.value(key + QLatin1String("done"), true).toBool();
+ if (!url.isEmpty() && !fileName.isEmpty()) {
+ DownloadItem *item = new DownloadItem(0, this);
+ item->m_output.setFileName(fileName);
+ item->fileNameLabel->setText(QFileInfo(item->m_output.fileName()).fileName());
+ item->m_url = url;
+ item->stopButton->setVisible(false);
+ item->stopButton->setEnabled(false);
+ item->tryAgainButton->setVisible(!done);
+ item->tryAgainButton->setEnabled(!done);
+ item->progressBar->setVisible(!done);
+ addItem(item);
+ }
+ key = QString(QLatin1String("download_%1_")).arg(++i);
+ }
+ cleanupButton->setEnabled(m_downloads.count() - activeDownloads() > 0);
+}
+
+void DownloadManager::cleanup()
+{
+ if (m_downloads.isEmpty())
+ return;
+ m_model->removeRows(0, m_downloads.count());
+ updateItemCount();
+ if (m_downloads.isEmpty() && m_iconProvider) {
+ delete m_iconProvider;
+ m_iconProvider = 0;
+ }
+ m_autoSaver->changeOccurred();
+}
+
+void DownloadManager::updateItemCount()
+{
+ int count = m_downloads.count();
+ itemCount->setText(count == 1 ? tr("1 Download") : tr("%1 Downloads").arg(count));
+}
+
+DownloadModel::DownloadModel(DownloadManager *downloadManager, QObject *parent)
+ : QAbstractListModel(parent)
+ , m_downloadManager(downloadManager)
+{
+}
+
+QVariant DownloadModel::data(const QModelIndex &index, int role) const
+{
+ if (index.row() < 0 || index.row() >= rowCount(index.parent()))
+ return QVariant();
+ if (role == Qt::ToolTipRole)
+ if (!m_downloadManager->m_downloads.at(index.row())->downloadedSuccessfully())
+ return m_downloadManager->m_downloads.at(index.row())->downloadInfoLabel->text();
+ return QVariant();
+}
+
+int DownloadModel::rowCount(const QModelIndex &parent) const
+{
+ return (parent.isValid()) ? 0 : m_downloadManager->m_downloads.count();
+}
+
+bool DownloadModel::removeRows(int row, int count, const QModelIndex &parent)
+{
+ if (parent.isValid())
+ return false;
+
+ int lastRow = row + count - 1;
+ for (int i = lastRow; i >= row; --i) {
+ if (m_downloadManager->m_downloads.at(i)->downloadedSuccessfully()
+ || m_downloadManager->m_downloads.at(i)->tryAgainButton->isEnabled()) {
+ beginRemoveRows(parent, i, i);
+ m_downloadManager->m_downloads.takeAt(i)->deleteLater();
+ endRemoveRows();
+ }
+ }
+ m_downloadManager->m_autoSaver->changeOccurred();
+ return true;
+}
diff --git a/examples/widgets/browser/downloadmanager.h b/examples/widgets/browser/downloadmanager.h
new file mode 100644
index 000000000..072e99efd
--- /dev/null
+++ b/examples/widgets/browser/downloadmanager.h
@@ -0,0 +1,161 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the demonstration applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef DOWNLOADMANAGER_H
+#define DOWNLOADMANAGER_H
+
+#include "ui_downloads.h"
+#include "ui_downloaditem.h"
+
+#include <QtNetwork/QNetworkReply>
+
+#include <QtCore/QFile>
+#include <QtCore/QTime>
+
+class DownloadItem : public QWidget, public Ui_DownloadItem
+{
+ Q_OBJECT
+
+signals:
+ void statusChanged();
+
+public:
+ DownloadItem(QNetworkReply *reply = 0, bool requestFileName = false, QWidget *parent = 0);
+ bool downloading() const;
+ bool downloadedSuccessfully() const;
+
+ QUrl m_url;
+
+ QFile m_output;
+ QNetworkReply *m_reply;
+
+private slots:
+ void stop();
+ void tryAgain();
+ void open();
+
+ void downloadReadyRead();
+ void error(QNetworkReply::NetworkError code);
+ void downloadProgress(qint64 bytesReceived, qint64 bytesTotal);
+ void metaDataChanged();
+ void finished();
+
+private:
+ void getFileName();
+ void init();
+ void updateInfoLabel();
+ QString dataString(int size) const;
+
+ QString saveFileName(const QString &directory) const;
+
+ bool m_requestFileName;
+ qint64 m_bytesReceived;
+ QTime m_downloadTime;
+};
+
+class AutoSaver;
+class DownloadModel;
+QT_BEGIN_NAMESPACE
+class QFileIconProvider;
+QT_END_NAMESPACE
+
+class DownloadManager : public QDialog, public Ui_DownloadDialog
+{
+ Q_OBJECT
+ Q_PROPERTY(RemovePolicy removePolicy READ removePolicy WRITE setRemovePolicy)
+ Q_ENUMS(RemovePolicy)
+
+public:
+ enum RemovePolicy {
+ Never,
+ Exit,
+ SuccessFullDownload
+ };
+
+ DownloadManager(QWidget *parent = 0);
+ ~DownloadManager();
+ int activeDownloads() const;
+
+ RemovePolicy removePolicy() const;
+ void setRemovePolicy(RemovePolicy policy);
+
+public slots:
+ void download(const QNetworkRequest &request, bool requestFileName = false);
+ inline void download(const QUrl &url, bool requestFileName = false)
+ { download(QNetworkRequest(url), requestFileName); }
+ void handleUnsupportedContent(QNetworkReply *reply, bool requestFileName = false);
+ void cleanup();
+
+private slots:
+ void save() const;
+ void updateRow();
+
+private:
+ void addItem(DownloadItem *item);
+ void updateItemCount();
+ void load();
+
+ AutoSaver *m_autoSaver;
+ DownloadModel *m_model;
+ QNetworkAccessManager *m_manager;
+ QFileIconProvider *m_iconProvider;
+ QList<DownloadItem*> m_downloads;
+ RemovePolicy m_removePolicy;
+ friend class DownloadModel;
+};
+
+class DownloadModel : public QAbstractListModel
+{
+ friend class DownloadManager;
+ Q_OBJECT
+
+public:
+ DownloadModel(DownloadManager *downloadManager, QObject *parent = 0);
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
+ int rowCount(const QModelIndex &parent = QModelIndex()) const;
+ bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex());
+
+private:
+ DownloadManager *m_downloadManager;
+
+};
+
+#endif // DOWNLOADMANAGER_H
diff --git a/examples/widgets/browser/downloads.ui b/examples/widgets/browser/downloads.ui
new file mode 100644
index 000000000..a2e256935
--- /dev/null
+++ b/examples/widgets/browser/downloads.ui
@@ -0,0 +1,83 @@
+<ui version="4.0" >
+ <class>DownloadDialog</class>
+ <widget class="QDialog" name="DownloadDialog" >
+ <property name="geometry" >
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>332</width>
+ <height>252</height>
+ </rect>
+ </property>
+ <property name="windowTitle" >
+ <string>Downloads</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>0</number>
+ </property>
+ <item row="0" column="0" colspan="3" >
+ <widget class="EditTableView" name="downloadsView" />
+ </item>
+ <item row="1" column="0" >
+ <layout class="QHBoxLayout" name="horizontalLayout" >
+ <item>
+ <widget class="QPushButton" name="cleanupButton" >
+ <property name="enabled" >
+ <bool>false</bool>
+ </property>
+ <property name="text" >
+ <string>Clean up</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0" >
+ <size>
+ <width>58</width>
+ <height>24</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ <item row="1" column="1" >
+ <widget class="QLabel" name="itemCount" >
+ <property name="text" >
+ <string>0 Items</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2" >
+ <spacer name="horizontalSpacer" >
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0" >
+ <size>
+ <width>148</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>EditTableView</class>
+ <extends>QTableView</extends>
+ <header>edittableview.h</header>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/examples/widgets/browser/edittableview.cpp b/examples/widgets/browser/edittableview.cpp
new file mode 100644
index 000000000..71d9b5706
--- /dev/null
+++ b/examples/widgets/browser/edittableview.cpp
@@ -0,0 +1,77 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the demonstration applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "edittableview.h"
+#include <QtGui/QKeyEvent>
+
+EditTableView::EditTableView(QWidget *parent)
+ : QTableView(parent)
+{
+}
+
+void EditTableView::keyPressEvent(QKeyEvent *event)
+{
+ if ((event->key() == Qt::Key_Delete
+ || event->key() == Qt::Key_Backspace)
+ && model()) {
+ removeOne();
+ } else {
+ QAbstractItemView::keyPressEvent(event);
+ }
+}
+
+void EditTableView::removeOne()
+{
+ if (!model() || !selectionModel())
+ return;
+ int row = currentIndex().row();
+ model()->removeRow(row, rootIndex());
+ QModelIndex idx = model()->index(row, 0, rootIndex());
+ if (!idx.isValid())
+ idx = model()->index(row - 1, 0, rootIndex());
+ selectionModel()->select(idx, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows);
+}
+
+void EditTableView::removeAll()
+{
+ if (model())
+ model()->removeRows(0, model()->rowCount(rootIndex()), rootIndex());
+}
diff --git a/examples/widgets/browser/edittableview.h b/examples/widgets/browser/edittableview.h
new file mode 100644
index 000000000..1c865ad42
--- /dev/null
+++ b/examples/widgets/browser/edittableview.h
@@ -0,0 +1,60 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the demonstration applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef EDITTABLEVIEW_H
+#define EDITTABLEVIEW_H
+
+#include <QtWidgets/QTableView>
+
+class EditTableView : public QTableView
+{
+ Q_OBJECT
+
+public:
+ EditTableView(QWidget *parent = 0);
+ void keyPressEvent(QKeyEvent *event);
+
+public slots:
+ void removeOne();
+ void removeAll();
+};
+
+#endif // EDITTABLEVIEW_H
diff --git a/examples/widgets/browser/edittreeview.cpp b/examples/widgets/browser/edittreeview.cpp
new file mode 100644
index 000000000..5b8007856
--- /dev/null
+++ b/examples/widgets/browser/edittreeview.cpp
@@ -0,0 +1,76 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the demonstration applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "edittreeview.h"
+
+#include <QtGui/QKeyEvent>
+
+EditTreeView::EditTreeView(QWidget *parent)
+ : QTreeView(parent)
+{
+}
+
+void EditTreeView::keyPressEvent(QKeyEvent *event)
+{
+ if ((event->key() == Qt::Key_Delete
+ || event->key() == Qt::Key_Backspace)
+ && model()) {
+ removeOne();
+ } else {
+ QAbstractItemView::keyPressEvent(event);
+ }
+}
+
+void EditTreeView::removeOne()
+{
+ if (!model())
+ return;
+ QModelIndex ci = currentIndex();
+ int row = ci.row();
+ model()->removeRow(row, ci.parent());
+}
+
+void EditTreeView::removeAll()
+{
+ if (!model())
+ return;
+ model()->removeRows(0, model()->rowCount(rootIndex()), rootIndex());
+}
diff --git a/examples/widgets/browser/edittreeview.h b/examples/widgets/browser/edittreeview.h
new file mode 100644
index 000000000..4159812a6
--- /dev/null
+++ b/examples/widgets/browser/edittreeview.h
@@ -0,0 +1,60 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the demonstration applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef EDITTREEVIEW_H
+#define EDITTREEVIEW_H
+
+#include <QtWidgets/QTreeView>
+
+class EditTreeView : public QTreeView
+{
+ Q_OBJECT
+
+public:
+ EditTreeView(QWidget *parent = 0);
+ void keyPressEvent(QKeyEvent *event);
+
+public slots:
+ void removeOne();
+ void removeAll();
+};
+
+#endif // EDITTREEVIEW_H
diff --git a/examples/widgets/browser/history.cpp b/examples/widgets/browser/history.cpp
new file mode 100644
index 000000000..d5b245e34
--- /dev/null
+++ b/examples/widgets/browser/history.cpp
@@ -0,0 +1,1291 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the demonstration applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "history.h"
+
+#include "autosaver.h"
+#include "browserapplication.h"
+
+#include <QtCore/QBuffer>
+#include <QtCore/QDir>
+#include <QtCore/QFile>
+#include <QtCore/QFileInfo>
+#include <QtCore/QSettings>
+#include <QtCore/QTemporaryFile>
+#include <QtCore/QTextStream>
+
+#include <QtCore/QtAlgorithms>
+
+#include <QtGui/QClipboard>
+#include <QtGui/QDesktopServices>
+#include <QtWidgets/QHeaderView>
+#include <QtWidgets/QStyle>
+
+#include <QWebHistoryInterface>
+#include <QWebSettings>
+
+#include <QtCore/QDebug>
+
+static const unsigned int HISTORY_VERSION = 23;
+
+HistoryManager::HistoryManager(QObject *parent)
+ : QWebHistoryInterface(parent)
+ , m_saveTimer(new AutoSaver(this))
+ , m_historyLimit(30)
+ , m_historyModel(0)
+ , m_historyFilterModel(0)
+ , m_historyTreeModel(0)
+{
+ m_expiredTimer.setSingleShot(true);
+ connect(&m_expiredTimer, SIGNAL(timeout()),
+ this, SLOT(checkForExpired()));
+ connect(this, SIGNAL(entryAdded(HistoryItem)),
+ m_saveTimer, SLOT(changeOccurred()));
+ connect(this, SIGNAL(entryRemoved(HistoryItem)),
+ m_saveTimer, SLOT(changeOccurred()));
+ load();
+
+ m_historyModel = new HistoryModel(this, this);
+ m_historyFilterModel = new HistoryFilterModel(m_historyModel, this);
+ m_historyTreeModel = new HistoryTreeModel(m_historyFilterModel, this);
+
+ // QWebHistoryInterface will delete the history manager
+ QWebHistoryInterface::setDefaultInterface(this);
+}
+
+HistoryManager::~HistoryManager()
+{
+ m_saveTimer->saveIfNeccessary();
+}
+
+QList<HistoryItem> HistoryManager::history() const
+{
+ return m_history;
+}
+
+bool HistoryManager::historyContains(const QString &url) const
+{
+ return m_historyFilterModel->historyContains(url);
+}
+
+void HistoryManager::addHistoryEntry(const QString &url)
+{
+ QUrl cleanUrl(url);
+ cleanUrl.setPassword(QString());
+ cleanUrl.setHost(cleanUrl.host().toLower());
+ HistoryItem item(cleanUrl.toString(), QDateTime::currentDateTime());
+ addHistoryItem(item);
+}
+
+void HistoryManager::setHistory(const QList<HistoryItem> &history, bool loadedAndSorted)
+{
+ m_history = history;
+
+ // verify that it is sorted by date
+ if (!loadedAndSorted)
+ qSort(m_history.begin(), m_history.end());
+
+ checkForExpired();
+
+ if (loadedAndSorted) {
+ m_lastSavedUrl = m_history.value(0).url;
+ } else {
+ m_lastSavedUrl = QString();
+ m_saveTimer->changeOccurred();
+ }
+ emit historyReset();
+}
+
+HistoryModel *HistoryManager::historyModel() const
+{
+ return m_historyModel;
+}
+
+HistoryFilterModel *HistoryManager::historyFilterModel() const
+{
+ return m_historyFilterModel;
+}
+
+HistoryTreeModel *HistoryManager::historyTreeModel() const
+{
+ return m_historyTreeModel;
+}
+
+void HistoryManager::checkForExpired()
+{
+ if (m_historyLimit < 0 || m_history.isEmpty())
+ return;
+
+ QDateTime now = QDateTime::currentDateTime();
+ int nextTimeout = 0;
+
+ while (!m_history.isEmpty()) {
+ QDateTime checkForExpired = m_history.last().dateTime;
+ checkForExpired.setDate(checkForExpired.date().addDays(m_historyLimit));
+ if (now.daysTo(checkForExpired) > 7) {
+ // check at most in a week to prevent int overflows on the timer
+ nextTimeout = 7 * 86400;
+ } else {
+ nextTimeout = now.secsTo(checkForExpired);
+ }
+ if (nextTimeout > 0)
+ break;
+ HistoryItem item = m_history.takeLast();
+ // remove from saved file also
+ m_lastSavedUrl = QString();
+ emit entryRemoved(item);
+ }
+
+ if (nextTimeout > 0)
+ m_expiredTimer.start(nextTimeout * 1000);
+}
+
+void HistoryManager::addHistoryItem(const HistoryItem &item)
+{
+ QWebSettings *globalSettings = QWebSettings::globalSettings();
+ if (globalSettings->testAttribute(QWebSettings::PrivateBrowsingEnabled))
+ return;
+
+ m_history.prepend(item);
+ emit entryAdded(item);
+ if (m_history.count() == 1)
+ checkForExpired();
+}
+
+void HistoryManager::updateHistoryItem(const QUrl &url, const QString &title)
+{
+ for (int i = 0; i < m_history.count(); ++i) {
+ if (url == m_history.at(i).url) {
+ m_history[i].title = title;
+ m_saveTimer->changeOccurred();
+ if (m_lastSavedUrl.isEmpty())
+ m_lastSavedUrl = m_history.at(i).url;
+ emit entryUpdated(i);
+ break;
+ }
+ }
+}
+
+int HistoryManager::historyLimit() const
+{
+ return m_historyLimit;
+}
+
+void HistoryManager::setHistoryLimit(int limit)
+{
+ if (m_historyLimit == limit)
+ return;
+ m_historyLimit = limit;
+ checkForExpired();
+ m_saveTimer->changeOccurred();
+}
+
+void HistoryManager::clear()
+{
+ m_history.clear();
+ m_lastSavedUrl = QString();
+ m_saveTimer->changeOccurred();
+ m_saveTimer->saveIfNeccessary();
+ historyReset();
+}
+
+void HistoryManager::loadSettings()
+{
+ // load settings
+ QSettings settings;
+ settings.beginGroup(QLatin1String("history"));
+ m_historyLimit = settings.value(QLatin1String("historyLimit"), 30).toInt();
+}
+
+void HistoryManager::load()
+{
+ loadSettings();
+
+ QFile historyFile(QStandardPaths::writableLocation(QStandardPaths::DataLocation)
+ + QLatin1String("/history"));
+ if (!historyFile.exists())
+ return;
+ if (!historyFile.open(QFile::ReadOnly)) {
+ qWarning() << "Unable to open history file" << historyFile.fileName();
+ return;
+ }
+
+ QList<HistoryItem> list;
+ QDataStream in(&historyFile);
+ // Double check that the history file is sorted as it is read in
+ bool needToSort = false;
+ HistoryItem lastInsertedItem;
+ QByteArray data;
+ QDataStream stream;
+ QBuffer buffer;
+ stream.setDevice(&buffer);
+ while (!historyFile.atEnd()) {
+ in >> data;
+ buffer.close();
+ buffer.setBuffer(&data);
+ buffer.open(QIODevice::ReadOnly);
+ quint32 ver;
+ stream >> ver;
+ if (ver != HISTORY_VERSION)
+ continue;
+ HistoryItem item;
+ stream >> item.url;
+ stream >> item.dateTime;
+ stream >> item.title;
+
+ if (!item.dateTime.isValid())
+ continue;
+
+ if (item == lastInsertedItem) {
+ if (lastInsertedItem.title.isEmpty() && !list.isEmpty())
+ list[0].title = item.title;
+ continue;
+ }
+
+ if (!needToSort && !list.isEmpty() && lastInsertedItem < item)
+ needToSort = true;
+
+ list.prepend(item);
+ lastInsertedItem = item;
+ }
+ if (needToSort)
+ qSort(list.begin(), list.end());
+
+ setHistory(list, true);
+
+ // If we had to sort re-write the whole history sorted
+ if (needToSort) {
+ m_lastSavedUrl = QString();
+ m_saveTimer->changeOccurred();
+ }
+}
+
+void HistoryManager::save()
+{
+ QSettings settings;
+ settings.beginGroup(QLatin1String("history"));
+ settings.setValue(QLatin1String("historyLimit"), m_historyLimit);
+
+ bool saveAll = m_lastSavedUrl.isEmpty();
+ int first = m_history.count() - 1;
+ if (!saveAll) {
+ // find the first one to save
+ for (int i = 0; i < m_history.count(); ++i) {
+ if (m_history.at(i).url == m_lastSavedUrl) {
+ first = i - 1;
+ break;
+ }
+ }
+ }
+ if (first == m_history.count() - 1)
+ saveAll = true;
+
+ QString directory = QStandardPaths::writableLocation(QStandardPaths::DataLocation);
+ if (directory.isEmpty())
+ directory = QDir::homePath() + QLatin1String("/.") + QCoreApplication::applicationName();
+ if (!QFile::exists(directory)) {
+ QDir dir;
+ dir.mkpath(directory);
+ }
+
+ QFile historyFile(directory + QLatin1String("/history"));
+ // When saving everything use a temporary file to prevent possible data loss.
+ QTemporaryFile tempFile;
+ tempFile.setAutoRemove(false);
+ bool open = false;
+ if (saveAll) {
+ open = tempFile.open();
+ } else {
+ open = historyFile.open(QFile::Append);
+ }
+
+ if (!open) {
+ qWarning() << "Unable to open history file for saving"
+ << (saveAll ? tempFile.fileName() : historyFile.fileName());
+ return;
+ }
+
+ QDataStream out(saveAll ? &tempFile : &historyFile);
+ for (int i = first; i >= 0; --i) {
+ QByteArray data;
+ QDataStream stream(&data, QIODevice::WriteOnly);
+ HistoryItem item = m_history.at(i);
+ stream << HISTORY_VERSION << item.url << item.dateTime << item.title;
+ out << data;
+ }
+ tempFile.close();
+
+ if (saveAll) {
+ if (historyFile.exists() && !historyFile.remove())
+ qWarning() << "History: error removing old history." << historyFile.errorString();
+ if (!tempFile.rename(historyFile.fileName()))
+ qWarning() << "History: error moving new history over old." << tempFile.errorString() << historyFile.fileName();
+ }
+ m_lastSavedUrl = m_history.value(0).url;
+}
+
+HistoryModel::HistoryModel(HistoryManager *history, QObject *parent)
+ : QAbstractTableModel(parent)
+ , m_history(history)
+{
+ Q_ASSERT(m_history);
+ connect(m_history, SIGNAL(historyReset()),
+ this, SLOT(historyReset()));
+ connect(m_history, SIGNAL(entryRemoved(HistoryItem)),
+ this, SLOT(historyReset()));
+
+ connect(m_history, SIGNAL(entryAdded(HistoryItem)),
+ this, SLOT(entryAdded()));
+ connect(m_history, SIGNAL(entryUpdated(int)),
+ this, SLOT(entryUpdated(int)));
+}
+
+void HistoryModel::historyReset()
+{
+ beginResetModel();
+ endResetModel();
+}
+
+void HistoryModel::entryAdded()
+{
+ beginInsertRows(QModelIndex(), 0, 0);
+ endInsertRows();
+}
+
+void HistoryModel::entryUpdated(int offset)
+{
+ QModelIndex idx = index(offset, 0);
+ emit dataChanged(idx, idx);
+}
+
+QVariant HistoryModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ if (orientation == Qt::Horizontal
+ && role == Qt::DisplayRole) {
+ switch (section) {
+ case 0: return tr("Title");
+ case 1: return tr("Address");
+ }
+ }
+ return QAbstractTableModel::headerData(section, orientation, role);
+}
+
+QVariant HistoryModel::data(const QModelIndex &index, int role) const
+{
+ QList<HistoryItem> lst = m_history->history();
+ if (index.row() < 0 || index.row() >= lst.size())
+ return QVariant();
+
+ const HistoryItem &item = lst.at(index.row());
+ switch (role) {
+ case DateTimeRole:
+ return item.dateTime;
+ case DateRole:
+ return item.dateTime.date();
+ case UrlRole:
+ return QUrl(item.url);
+ case UrlStringRole:
+ return item.url;
+ case Qt::DisplayRole:
+ case Qt::EditRole: {
+ switch (index.column()) {
+ case 0:
+ // when there is no title try to generate one from the url
+ if (item.title.isEmpty()) {
+ QString page = QFileInfo(QUrl(item.url).path()).fileName();
+ if (!page.isEmpty())
+ return page;
+ return item.url;
+ }
+ return item.title;
+ case 1:
+ return item.url;
+ }
+ }
+ case Qt::DecorationRole:
+ if (index.column() == 0) {
+ return BrowserApplication::instance()->icon(item.url);
+ }
+ }
+ return QVariant();
+}
+
+int HistoryModel::columnCount(const QModelIndex &parent) const
+{
+ return (parent.isValid()) ? 0 : 2;
+}
+
+int HistoryModel::rowCount(const QModelIndex &parent) const
+{
+ return (parent.isValid()) ? 0 : m_history->history().count();
+}
+
+bool HistoryModel::removeRows(int row, int count, const QModelIndex &parent)
+{
+ if (parent.isValid())
+ return false;
+ int lastRow = row + count - 1;
+ beginRemoveRows(parent, row, lastRow);
+ QList<HistoryItem> lst = m_history->history();
+ for (int i = lastRow; i >= row; --i)
+ lst.removeAt(i);
+ disconnect(m_history, SIGNAL(historyReset()), this, SLOT(historyReset()));
+ m_history->setHistory(lst);
+ connect(m_history, SIGNAL(historyReset()), this, SLOT(historyReset()));
+ endRemoveRows();
+ return true;
+}
+
+#define MOVEDROWS 15
+
+/*
+ Maps the first bunch of items of the source model to the root
+*/
+HistoryMenuModel::HistoryMenuModel(HistoryTreeModel *sourceModel, QObject *parent)
+ : QAbstractProxyModel(parent)
+ , m_treeModel(sourceModel)
+{
+ setSourceModel(sourceModel);
+}
+
+int HistoryMenuModel::bumpedRows() const
+{
+ QModelIndex first = m_treeModel->index(0, 0);
+ if (!first.isValid())
+ return 0;
+ return qMin(m_treeModel->rowCount(first), MOVEDROWS);
+}
+
+int HistoryMenuModel::columnCount(const QModelIndex &parent) const
+{
+ return m_treeModel->columnCount(mapToSource(parent));
+}
+
+int HistoryMenuModel::rowCount(const QModelIndex &parent) const
+{
+ if (parent.column() > 0)
+ return 0;
+
+ if (!parent.isValid()) {
+ int folders = sourceModel()->rowCount();
+ int bumpedItems = bumpedRows();
+ if (bumpedItems <= MOVEDROWS
+ && bumpedItems == sourceModel()->rowCount(sourceModel()->index(0, 0)))
+ --folders;
+ return bumpedItems + folders;
+ }
+
+ if (parent.internalId() == quintptr(-1)) {
+ if (parent.row() < bumpedRows())
+ return 0;
+ }
+
+ QModelIndex idx = mapToSource(parent);
+ int defaultCount = sourceModel()->rowCount(idx);
+ if (idx == sourceModel()->index(0, 0))
+ return defaultCount - bumpedRows();
+ return defaultCount;
+}
+
+QModelIndex HistoryMenuModel::mapFromSource(const QModelIndex &sourceIndex) const
+{
+ // currently not used or autotested
+ Q_ASSERT(false);
+ int sr = m_treeModel->mapToSource(sourceIndex).row();
+ return createIndex(sourceIndex.row(), sourceIndex.column(), sr);
+}
+
+QModelIndex HistoryMenuModel::mapToSource(const QModelIndex &proxyIndex) const
+{
+ if (!proxyIndex.isValid())
+ return QModelIndex();
+
+ if (proxyIndex.internalId() == quintptr(-1)) {
+ int bumpedItems = bumpedRows();
+ if (proxyIndex.row() < bumpedItems)
+ return m_treeModel->index(proxyIndex.row(), proxyIndex.column(), m_treeModel->index(0, 0));
+ if (bumpedItems <= MOVEDROWS && bumpedItems == sourceModel()->rowCount(m_treeModel->index(0, 0)))
+ --bumpedItems;
+ return m_treeModel->index(proxyIndex.row() - bumpedItems, proxyIndex.column());
+ }
+
+ QModelIndex historyIndex = m_treeModel->sourceModel()->index(proxyIndex.internalId(), proxyIndex.column());
+ QModelIndex treeIndex = m_treeModel->mapFromSource(historyIndex);
+ return treeIndex;
+}
+
+QModelIndex HistoryMenuModel::index(int row, int column, const QModelIndex &parent) const
+{
+ if (row < 0
+ || column < 0 || column >= columnCount(parent)
+ || parent.column() > 0)
+ return QModelIndex();
+ if (!parent.isValid())
+ return createIndex(row, column, quintptr(-1));
+
+ QModelIndex treeIndexParent = mapToSource(parent);
+
+ int bumpedItems = 0;
+ if (treeIndexParent == m_treeModel->index(0, 0))
+ bumpedItems = bumpedRows();
+ QModelIndex treeIndex = m_treeModel->index(row + bumpedItems, column, treeIndexParent);
+ QModelIndex historyIndex = m_treeModel->mapToSource(treeIndex);
+ int historyRow = historyIndex.row();
+ if (historyRow == -1)
+ historyRow = treeIndex.row();
+ return createIndex(row, column, historyRow);
+}
+
+QModelIndex HistoryMenuModel::parent(const QModelIndex &index) const
+{
+ int offset = index.internalId();
+ if (offset == -1 || !index.isValid())
+ return QModelIndex();
+
+ QModelIndex historyIndex = m_treeModel->sourceModel()->index(index.internalId(), 0);
+ QModelIndex treeIndex = m_treeModel->mapFromSource(historyIndex);
+ QModelIndex treeIndexParent = treeIndex.parent();
+
+ int sr = m_treeModel->mapToSource(treeIndexParent).row();
+ int bumpedItems = bumpedRows();
+ if (bumpedItems <= MOVEDROWS && bumpedItems == sourceModel()->rowCount(sourceModel()->index(0, 0)))
+ --bumpedItems;
+ return createIndex(bumpedItems + treeIndexParent.row(), treeIndexParent.column(), sr);
+}
+
+
+HistoryMenu::HistoryMenu(QWidget *parent)
+ : ModelMenu(parent)
+ , m_history(0)
+{
+ connect(this, SIGNAL(activated(QModelIndex)),
+ this, SLOT(activated(QModelIndex)));
+ setHoverRole(HistoryModel::UrlStringRole);
+}
+
+void HistoryMenu::activated(const QModelIndex &index)
+{
+ emit openUrl(index.data(HistoryModel::UrlRole).toUrl());
+}
+
+bool HistoryMenu::prePopulated()
+{
+ if (!m_history) {
+ m_history = BrowserApplication::historyManager();
+ m_historyMenuModel = new HistoryMenuModel(m_history->historyTreeModel(), this);
+ setModel(m_historyMenuModel);
+ }
+ // initial actions
+ for (int i = 0; i < m_initialActions.count(); ++i)
+ addAction(m_initialActions.at(i));
+ if (!m_initialActions.isEmpty())
+ addSeparator();
+ setFirstSeparator(m_historyMenuModel->bumpedRows());
+
+ return false;
+}
+
+void HistoryMenu::postPopulated()
+{
+ if (m_history->history().count() > 0)
+ addSeparator();
+
+ QAction *showAllAction = new QAction(tr("Show All History"), this);
+ connect(showAllAction, SIGNAL(triggered()), this, SLOT(showHistoryDialog()));
+ addAction(showAllAction);
+
+ QAction *clearAction = new QAction(tr("Clear History"), this);
+ connect(clearAction, SIGNAL(triggered()), m_history, SLOT(clear()));
+ addAction(clearAction);
+}
+
+void HistoryMenu::showHistoryDialog()
+{
+ HistoryDialog *dialog = new HistoryDialog(this);
+ connect(dialog, SIGNAL(openUrl(QUrl)),
+ this, SIGNAL(openUrl(QUrl)));
+ dialog->show();
+}
+
+void HistoryMenu::setInitialActions(QList<QAction*> actions)
+{
+ m_initialActions = actions;
+ for (int i = 0; i < m_initialActions.count(); ++i)
+ addAction(m_initialActions.at(i));
+}
+
+TreeProxyModel::TreeProxyModel(QObject *parent) : QSortFilterProxyModel(parent)
+{
+ setSortRole(HistoryModel::DateTimeRole);
+ setFilterCaseSensitivity(Qt::CaseInsensitive);
+}
+
+bool TreeProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
+{
+ if (!source_parent.isValid())
+ return true;
+ return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent);
+}
+
+HistoryDialog::HistoryDialog(QWidget *parent, HistoryManager *setHistory) : QDialog(parent)
+{
+ HistoryManager *history = setHistory;
+ if (!history)
+ history = BrowserApplication::historyManager();
+ setupUi(this);
+ tree->setUniformRowHeights(true);
+ tree->setSelectionBehavior(QAbstractItemView::SelectRows);
+ tree->setTextElideMode(Qt::ElideMiddle);
+ QAbstractItemModel *model = history->historyTreeModel();
+ TreeProxyModel *proxyModel = new TreeProxyModel(this);
+ connect(search, SIGNAL(textChanged(QString)),
+ proxyModel, SLOT(setFilterFixedString(QString)));
+ connect(removeButton, SIGNAL(clicked()), tree, SLOT(removeOne()));
+ connect(removeAllButton, SIGNAL(clicked()), history, SLOT(clear()));
+ proxyModel->setSourceModel(model);
+ tree->setModel(proxyModel);
+ tree->setExpanded(proxyModel->index(0, 0), true);
+ tree->setAlternatingRowColors(true);
+ QFontMetrics fm(font());
+ int header = fm.width(QLatin1Char('m')) * 40;
+ tree->header()->resizeSection(0, header);
+ tree->header()->setStretchLastSection(true);
+ connect(tree, SIGNAL(activated(QModelIndex)),
+ this, SLOT(open()));
+ tree->setContextMenuPolicy(Qt::CustomContextMenu);
+ connect(tree, SIGNAL(customContextMenuRequested(QPoint)),
+ this, SLOT(customContextMenuRequested(QPoint)));
+}
+
+void HistoryDialog::customContextMenuRequested(const QPoint &pos)
+{
+ QMenu menu;
+ QModelIndex index = tree->indexAt(pos);
+ index = index.sibling(index.row(), 0);
+ if (index.isValid() && !tree->model()->hasChildren(index)) {
+ menu.addAction(tr("Open"), this, SLOT(open()));
+ menu.addSeparator();
+ menu.addAction(tr("Copy"), this, SLOT(copy()));
+ }
+ menu.addAction(tr("Delete"), tree, SLOT(removeOne()));
+ menu.exec(QCursor::pos());
+}
+
+void HistoryDialog::open()
+{
+ QModelIndex index = tree->currentIndex();
+ if (!index.parent().isValid())
+ return;
+ emit openUrl(index.data(HistoryModel::UrlRole).toUrl());
+}
+
+void HistoryDialog::copy()
+{
+ QModelIndex index = tree->currentIndex();
+ if (!index.parent().isValid())
+ return;
+ QString url = index.data(HistoryModel::UrlStringRole).toString();
+
+ QClipboard *clipboard = QApplication::clipboard();
+ clipboard->setText(url);
+}
+
+HistoryFilterModel::HistoryFilterModel(QAbstractItemModel *sourceModel, QObject *parent)
+ : QAbstractProxyModel(parent),
+ m_loaded(false)
+{
+ setSourceModel(sourceModel);
+}
+
+int HistoryFilterModel::historyLocation(const QString &url) const
+{
+ load();
+ if (!m_historyHash.contains(url))
+ return 0;
+ return sourceModel()->rowCount() - m_historyHash.value(url);
+}
+
+QVariant HistoryFilterModel::data(const QModelIndex &index, int role) const
+{
+ return QAbstractProxyModel::data(index, role);
+}
+
+void HistoryFilterModel::setSourceModel(QAbstractItemModel *newSourceModel)
+{
+ if (sourceModel()) {
+ disconnect(sourceModel(), SIGNAL(modelReset()), this, SLOT(sourceReset()));
+ disconnect(sourceModel(), SIGNAL(dataChanged(QModelIndex,QModelIndex)),
+ this, SLOT(dataChanged(QModelIndex,QModelIndex)));
+ disconnect(sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)),
+ this, SLOT(sourceRowsInserted(QModelIndex,int,int)));
+ disconnect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)),
+ this, SLOT(sourceRowsRemoved(QModelIndex,int,int)));
+ }
+
+ QAbstractProxyModel::setSourceModel(newSourceModel);
+
+ if (sourceModel()) {
+ m_loaded = false;
+ connect(sourceModel(), SIGNAL(modelReset()), this, SLOT(sourceReset()));
+ connect(sourceModel(), SIGNAL(dataChanged(QModelIndex,QModelIndex)),
+ this, SLOT(sourceDataChanged(QModelIndex,QModelIndex)));
+ connect(sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)),
+ this, SLOT(sourceRowsInserted(QModelIndex,int,int)));
+ connect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)),
+ this, SLOT(sourceRowsRemoved(QModelIndex,int,int)));
+ }
+}
+
+void HistoryFilterModel::sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
+{
+ emit dataChanged(mapFromSource(topLeft), mapFromSource(bottomRight));
+}
+
+QVariant HistoryFilterModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ return sourceModel()->headerData(section, orientation, role);
+}
+
+void HistoryFilterModel::sourceReset()
+{
+ m_loaded = false;
+ beginResetModel();
+ endResetModel();
+}
+
+int HistoryFilterModel::rowCount(const QModelIndex &parent) const
+{
+ load();
+ if (parent.isValid())
+ return 0;
+ return m_historyHash.count();
+}
+
+int HistoryFilterModel::columnCount(const QModelIndex &parent) const
+{
+ return (parent.isValid()) ? 0 : 2;
+}
+
+QModelIndex HistoryFilterModel::mapToSource(const QModelIndex &proxyIndex) const
+{
+ load();
+ int sourceRow = sourceModel()->rowCount() - proxyIndex.internalId();
+ return sourceModel()->index(sourceRow, proxyIndex.column());
+}
+
+QModelIndex HistoryFilterModel::mapFromSource(const QModelIndex &sourceIndex) const
+{
+ load();
+ QString url = sourceIndex.data(HistoryModel::UrlStringRole).toString();
+ if (!m_historyHash.contains(url))
+ return QModelIndex();
+
+ // This can be done in a binary search, but we can't use qBinary find
+ // because it can't take: qBinaryFind(m_sourceRow.end(), m_sourceRow.begin(), v);
+ // so if this is a performance bottlneck then convert to binary search, until then
+ // the cleaner/easier to read code wins the day.
+ int realRow = -1;
+ int sourceModelRow = sourceModel()->rowCount() - sourceIndex.row();
+
+ for (int i = 0; i < m_sourceRow.count(); ++i) {
+ if (m_sourceRow.at(i) == sourceModelRow) {
+ realRow = i;
+ break;
+ }
+ }
+ if (realRow == -1)
+ return QModelIndex();
+
+ return createIndex(realRow, sourceIndex.column(), sourceModel()->rowCount() - sourceIndex.row());
+}
+
+QModelIndex HistoryFilterModel::index(int row, int column, const QModelIndex &parent) const
+{
+ load();
+ if (row < 0 || row >= rowCount(parent)
+ || column < 0 || column >= columnCount(parent))
+ return QModelIndex();
+
+ return createIndex(row, column, m_sourceRow[row]);
+}
+
+QModelIndex HistoryFilterModel::parent(const QModelIndex &) const
+{
+ return QModelIndex();
+}
+
+void HistoryFilterModel::load() const
+{
+ if (m_loaded)
+ return;
+ m_sourceRow.clear();
+ m_historyHash.clear();
+ m_historyHash.reserve(sourceModel()->rowCount());
+ for (int i = 0; i < sourceModel()->rowCount(); ++i) {
+ QModelIndex idx = sourceModel()->index(i, 0);
+ QString url = idx.data(HistoryModel::UrlStringRole).toString();
+ if (!m_historyHash.contains(url)) {
+ m_sourceRow.append(sourceModel()->rowCount() - i);
+ m_historyHash[url] = sourceModel()->rowCount() - i;
+ }
+ }
+ m_loaded = true;
+}
+
+void HistoryFilterModel::sourceRowsInserted(const QModelIndex &parent, int start, int end)
+{
+ Q_ASSERT(start == end && start == 0);
+ Q_UNUSED(end);
+ if (!m_loaded)
+ return;
+ QModelIndex idx = sourceModel()->index(start, 0, parent);
+ QString url = idx.data(HistoryModel::UrlStringRole).toString();
+ if (m_historyHash.contains(url)) {
+ int sourceRow = sourceModel()->rowCount() - m_historyHash[url];
+ int realRow = mapFromSource(sourceModel()->index(sourceRow, 0)).row();
+ beginRemoveRows(QModelIndex(), realRow, realRow);
+ m_sourceRow.removeAt(realRow);
+ m_historyHash.remove(url);
+ endRemoveRows();
+ }
+ beginInsertRows(QModelIndex(), 0, 0);
+ m_historyHash.insert(url, sourceModel()->rowCount() - start);
+ m_sourceRow.insert(0, sourceModel()->rowCount());
+ endInsertRows();
+}
+
+void HistoryFilterModel::sourceRowsRemoved(const QModelIndex &, int start, int end)
+{
+ Q_UNUSED(start);
+ Q_UNUSED(end);
+ sourceReset();
+}
+
+/*
+ Removing a continuous block of rows will remove filtered rows too as this is
+ the users intention.
+*/
+bool HistoryFilterModel::removeRows(int row, int count, const QModelIndex &parent)
+{
+ if (row < 0 || count <= 0 || row + count > rowCount(parent) || parent.isValid())
+ return false;
+ int lastRow = row + count - 1;
+ disconnect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)),
+ this, SLOT(sourceRowsRemoved(QModelIndex,int,int)));
+ beginRemoveRows(parent, row, lastRow);
+ int oldCount = rowCount();
+ int start = sourceModel()->rowCount() - m_sourceRow.value(row);
+ int end = sourceModel()->rowCount() - m_sourceRow.value(lastRow);
+ sourceModel()->removeRows(start, end - start + 1);
+ endRemoveRows();
+ connect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)),
+ this, SLOT(sourceRowsRemoved(QModelIndex,int,int)));
+ m_loaded = false;
+ if (oldCount - count != rowCount()) {
+ beginResetModel();
+ endResetModel();
+ }
+ return true;
+}
+
+HistoryCompletionModel::HistoryCompletionModel(QObject *parent)
+ : QAbstractProxyModel(parent)
+{
+}
+
+QVariant HistoryCompletionModel::data(const QModelIndex &index, int role) const
+{
+ if (sourceModel()
+ && (role == Qt::EditRole || role == Qt::DisplayRole)
+ && index.isValid()) {
+ QModelIndex idx = mapToSource(index);
+ idx = idx.sibling(idx.row(), 1);
+ QString urlString = idx.data(HistoryModel::UrlStringRole).toString();
+ if (index.row() % 2) {
+ QUrl url = urlString;
+ QString s = url.toString(QUrl::RemoveScheme
+ | QUrl::RemoveUserInfo
+ | QUrl::StripTrailingSlash);
+ return s.mid(2); // strip // from the front
+ }
+ return urlString;
+ }
+ return QAbstractProxyModel::data(index, role);
+}
+
+int HistoryCompletionModel::rowCount(const QModelIndex &parent) const
+{
+ return (parent.isValid() || !sourceModel()) ? 0 : sourceModel()->rowCount(parent) * 2;
+}
+
+int HistoryCompletionModel::columnCount(const QModelIndex &parent) const
+{
+ return (parent.isValid()) ? 0 : 1;
+}
+
+QModelIndex HistoryCompletionModel::mapFromSource(const QModelIndex &sourceIndex) const
+{
+ int row = sourceIndex.row() * 2;
+ return index(row, sourceIndex.column());
+}
+
+QModelIndex HistoryCompletionModel::mapToSource(const QModelIndex &proxyIndex) const
+{
+ if (!sourceModel())
+ return QModelIndex();
+ int row = proxyIndex.row() / 2;
+ return sourceModel()->index(row, proxyIndex.column());
+}
+
+QModelIndex HistoryCompletionModel::index(int row, int column, const QModelIndex &parent) const
+{
+ if (row < 0 || row >= rowCount(parent)
+ || column < 0 || column >= columnCount(parent))
+ return QModelIndex();
+ return createIndex(row, column);
+}
+
+QModelIndex HistoryCompletionModel::parent(const QModelIndex &) const
+{
+ return QModelIndex();
+}
+
+void HistoryCompletionModel::setSourceModel(QAbstractItemModel *newSourceModel)
+{
+ if (sourceModel()) {
+ disconnect(sourceModel(), SIGNAL(modelReset()), this, SLOT(sourceReset()));
+ disconnect(sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)),
+ this, SLOT(sourceReset()));
+ disconnect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)),
+ this, SLOT(sourceReset()));
+ }
+
+ QAbstractProxyModel::setSourceModel(newSourceModel);
+
+ if (newSourceModel) {
+ connect(newSourceModel, SIGNAL(modelReset()), this, SLOT(sourceReset()));
+ connect(sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)),
+ this, SLOT(sourceReset()));
+ connect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)),
+ this, SLOT(sourceReset()));
+ }
+
+ beginResetModel();
+ endResetModel();
+}
+
+void HistoryCompletionModel::sourceReset()
+{
+ beginResetModel();
+ endResetModel();
+}
+
+HistoryTreeModel::HistoryTreeModel(QAbstractItemModel *sourceModel, QObject *parent)
+ : QAbstractProxyModel(parent)
+{
+ setSourceModel(sourceModel);
+}
+
+QVariant HistoryTreeModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ return sourceModel()->headerData(section, orientation, role);
+}
+
+QVariant HistoryTreeModel::data(const QModelIndex &index, int role) const
+{
+ if ((role == Qt::EditRole || role == Qt::DisplayRole)) {
+ int start = index.internalId();
+ if (start == 0) {
+ int offset = sourceDateRow(index.row());
+ if (index.column() == 0) {
+ QModelIndex idx = sourceModel()->index(offset, 0);
+ QDate date = idx.data(HistoryModel::DateRole).toDate();
+ if (date == QDate::currentDate())
+ return tr("Earlier Today");
+ return date.toString(QLatin1String("dddd, MMMM d, yyyy"));
+ }
+ if (index.column() == 1) {
+ return tr("%1 items").arg(rowCount(index.sibling(index.row(), 0)));
+ }
+ }
+ }
+ if (role == Qt::DecorationRole && index.column() == 0 && !index.parent().isValid())
+ return QIcon(QLatin1String(":history.png"));
+ if (role == HistoryModel::DateRole && index.column() == 0 && index.internalId() == 0) {
+ int offset = sourceDateRow(index.row());
+ QModelIndex idx = sourceModel()->index(offset, 0);
+ return idx.data(HistoryModel::DateRole);
+ }
+
+ return QAbstractProxyModel::data(index, role);
+}
+
+int HistoryTreeModel::columnCount(const QModelIndex &parent) const
+{
+ return sourceModel()->columnCount(mapToSource(parent));
+}
+
+int HistoryTreeModel::rowCount(const QModelIndex &parent) const
+{
+ if ( parent.internalId() != 0
+ || parent.column() > 0
+ || !sourceModel())
+ return 0;
+
+ // row count OF dates
+ if (!parent.isValid()) {
+ if (!m_sourceRowCache.isEmpty())
+ return m_sourceRowCache.count();
+ QDate currentDate;
+ int rows = 0;
+ int totalRows = sourceModel()->rowCount();
+
+ for (int i = 0; i < totalRows; ++i) {
+ QDate rowDate = sourceModel()->index(i, 0).data(HistoryModel::DateRole).toDate();
+ if (rowDate != currentDate) {
+ m_sourceRowCache.append(i);
+ currentDate = rowDate;
+ ++rows;
+ }
+ }
+ Q_ASSERT(m_sourceRowCache.count() == rows);
+ return rows;
+ }
+
+ // row count FOR a date
+ int start = sourceDateRow(parent.row());
+ int end = sourceDateRow(parent.row() + 1);
+ return (end - start);
+}
+
+// Translate the top level date row into the offset where that date starts
+int HistoryTreeModel::sourceDateRow(int row) const
+{
+ if (row <= 0)
+ return 0;
+
+ if (m_sourceRowCache.isEmpty())
+ rowCount(QModelIndex());
+
+ if (row >= m_sourceRowCache.count()) {
+ if (!sourceModel())
+ return 0;
+ return sourceModel()->rowCount();
+ }
+ return m_sourceRowCache.at(row);
+}
+
+QModelIndex HistoryTreeModel::mapToSource(const QModelIndex &proxyIndex) const
+{
+ int offset = proxyIndex.internalId();
+ if (offset == 0)
+ return QModelIndex();
+ int startDateRow = sourceDateRow(offset - 1);
+ return sourceModel()->index(startDateRow + proxyIndex.row(), proxyIndex.column());
+}
+
+QModelIndex HistoryTreeModel::index(int row, int column, const QModelIndex &parent) const
+{
+ if (row < 0
+ || column < 0 || column >= columnCount(parent)
+ || parent.column() > 0)
+ return QModelIndex();
+
+ if (!parent.isValid())
+ return createIndex(row, column);
+ return createIndex(row, column, parent.row() + 1);
+}
+
+QModelIndex HistoryTreeModel::parent(const QModelIndex &index) const
+{
+ int offset = index.internalId();
+ if (offset == 0 || !index.isValid())
+ return QModelIndex();
+ return createIndex(offset - 1, 0);
+}
+
+bool HistoryTreeModel::hasChildren(const QModelIndex &parent) const
+{
+ QModelIndex grandparent = parent.parent();
+ if (!grandparent.isValid())
+ return true;
+ return false;
+}
+
+Qt::ItemFlags HistoryTreeModel::flags(const QModelIndex &index) const
+{
+ if (!index.isValid())
+ return Qt::NoItemFlags;
+ return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled;
+}
+
+bool HistoryTreeModel::removeRows(int row, int count, const QModelIndex &parent)
+{
+ if (row < 0 || count <= 0 || row + count > rowCount(parent))
+ return false;
+
+ if (parent.isValid()) {
+ // removing pages
+ int offset = sourceDateRow(parent.row());
+ return sourceModel()->removeRows(offset + row, count);
+ } else {
+ // removing whole dates
+ for (int i = row + count - 1; i >= row; --i) {
+ QModelIndex dateParent = index(i, 0);
+ int offset = sourceDateRow(dateParent.row());
+ if (!sourceModel()->removeRows(offset, rowCount(dateParent)))
+ return false;
+ }
+ }
+ return true;
+}
+
+void HistoryTreeModel::setSourceModel(QAbstractItemModel *newSourceModel)
+{
+ if (sourceModel()) {
+ disconnect(sourceModel(), SIGNAL(modelReset()), this, SLOT(sourceReset()));
+ disconnect(sourceModel(), SIGNAL(layoutChanged()), this, SLOT(sourceReset()));
+ disconnect(sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)),
+ this, SLOT(sourceRowsInserted(QModelIndex,int,int)));
+ disconnect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)),
+ this, SLOT(sourceRowsRemoved(QModelIndex,int,int)));
+ }
+
+ QAbstractProxyModel::setSourceModel(newSourceModel);
+
+ if (newSourceModel) {
+ connect(sourceModel(), SIGNAL(modelReset()), this, SLOT(sourceReset()));
+ connect(sourceModel(), SIGNAL(layoutChanged()), this, SLOT(sourceReset()));
+ connect(sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)),
+ this, SLOT(sourceRowsInserted(QModelIndex,int,int)));
+ connect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)),
+ this, SLOT(sourceRowsRemoved(QModelIndex,int,int)));
+ }
+
+ beginResetModel();
+ endResetModel();
+}
+
+void HistoryTreeModel::sourceReset()
+{
+ beginResetModel();
+ m_sourceRowCache.clear();
+ endResetModel();
+}
+
+void HistoryTreeModel::sourceRowsInserted(const QModelIndex &parent, int start, int end)
+{
+ Q_UNUSED(parent); // Avoid warnings when compiling release
+ Q_ASSERT(!parent.isValid());
+ if (start != 0 || start != end) {
+ beginResetModel();
+ m_sourceRowCache.clear();
+ endResetModel();
+ return;
+ }
+
+ m_sourceRowCache.clear();
+ QModelIndex treeIndex = mapFromSource(sourceModel()->index(start, 0));
+ QModelIndex treeParent = treeIndex.parent();
+ if (rowCount(treeParent) == 1) {
+ beginInsertRows(QModelIndex(), 0, 0);
+ endInsertRows();
+ } else {
+ beginInsertRows(treeParent, treeIndex.row(), treeIndex.row());
+ endInsertRows();
+ }
+}
+
+QModelIndex HistoryTreeModel::mapFromSource(const QModelIndex &sourceIndex) const
+{
+ if (!sourceIndex.isValid())
+ return QModelIndex();
+
+ if (m_sourceRowCache.isEmpty())
+ rowCount(QModelIndex());
+
+ QList<int>::iterator it;
+ it = qLowerBound(m_sourceRowCache.begin(), m_sourceRowCache.end(), sourceIndex.row());
+ if (*it != sourceIndex.row())
+ --it;
+ int dateRow = qMax(0, it - m_sourceRowCache.begin());
+ int row = sourceIndex.row() - m_sourceRowCache.at(dateRow);
+ return createIndex(row, sourceIndex.column(), dateRow + 1);
+}
+
+void HistoryTreeModel::sourceRowsRemoved(const QModelIndex &parent, int start, int end)
+{
+ Q_UNUSED(parent); // Avoid warnings when compiling release
+ Q_ASSERT(!parent.isValid());
+ if (m_sourceRowCache.isEmpty())
+ return;
+ for (int i = end; i >= start;) {
+ QList<int>::iterator it;
+ it = qLowerBound(m_sourceRowCache.begin(), m_sourceRowCache.end(), i);
+ // playing it safe
+ if (it == m_sourceRowCache.end()) {
+ beginResetModel();
+ m_sourceRowCache.clear();
+ endResetModel();
+ return;
+ }
+
+ if (*it != i)
+ --it;
+ int row = qMax(0, it - m_sourceRowCache.begin());
+ int offset = m_sourceRowCache[row];
+ QModelIndex dateParent = index(row, 0);
+ // If we can remove all the rows in the date do that and skip over them
+ int rc = rowCount(dateParent);
+ if (i - rc + 1 == offset && start <= i - rc + 1) {
+ beginRemoveRows(QModelIndex(), row, row);
+ m_sourceRowCache.removeAt(row);
+ i -= rc + 1;
+ } else {
+ beginRemoveRows(dateParent, i - offset, i - offset);
+ ++row;
+ --i;
+ }
+ for (int j = row; j < m_sourceRowCache.count(); ++j)
+ --m_sourceRowCache[j];
+ endRemoveRows();
+ }
+}
diff --git a/examples/widgets/browser/history.h b/examples/widgets/browser/history.h
new file mode 100644
index 000000000..e8764f2ff
--- /dev/null
+++ b/examples/widgets/browser/history.h
@@ -0,0 +1,349 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the demonstration applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef HISTORY_H
+#define HISTORY_H
+
+#include "modelmenu.h"
+
+#include <QtCore/QDateTime>
+#include <QtCore/QHash>
+#include <QtCore/QObject>
+#include <QtCore/QTimer>
+#include <QtCore/QUrl>
+
+#include <QtCore/QSortFilterProxyModel>
+
+#include <QWebHistoryInterface>
+
+class HistoryItem
+{
+public:
+ HistoryItem() {}
+ HistoryItem(const QString &u,
+ const QDateTime &d = QDateTime(), const QString &t = QString())
+ : title(t), url(u), dateTime(d) {}
+
+ inline bool operator==(const HistoryItem &other) const
+ { return other.title == title
+ && other.url == url && other.dateTime == dateTime; }
+
+ // history is sorted in reverse
+ inline bool operator <(const HistoryItem &other) const
+ { return dateTime > other.dateTime; }
+
+ QString title;
+ QString url;
+ QDateTime dateTime;
+};
+
+class AutoSaver;
+class HistoryModel;
+class HistoryFilterModel;
+class HistoryTreeModel;
+class HistoryManager : public QWebHistoryInterface
+{
+ Q_OBJECT
+ Q_PROPERTY(int historyLimit READ historyLimit WRITE setHistoryLimit)
+
+signals:
+ void historyReset();
+ void entryAdded(const HistoryItem &item);
+ void entryRemoved(const HistoryItem &item);
+ void entryUpdated(int offset);
+
+public:
+ HistoryManager(QObject *parent = 0);
+ ~HistoryManager();
+
+ bool historyContains(const QString &url) const;
+ void addHistoryEntry(const QString &url);
+
+ void updateHistoryItem(const QUrl &url, const QString &title);
+
+ int historyLimit() const;
+ void setHistoryLimit(int limit);
+
+ QList<HistoryItem> history() const;
+ void setHistory(const QList<HistoryItem> &history, bool loadedAndSorted = false);
+
+ // History manager keeps around these models for use by the completer and other classes
+ HistoryModel *historyModel() const;
+ HistoryFilterModel *historyFilterModel() const;
+ HistoryTreeModel *historyTreeModel() const;
+
+public slots:
+ void clear();
+ void loadSettings();
+
+private slots:
+ void save();
+ void checkForExpired();
+
+protected:
+ void addHistoryItem(const HistoryItem &item);
+
+private:
+ void load();
+
+ AutoSaver *m_saveTimer;
+ int m_historyLimit;
+ QTimer m_expiredTimer;
+ QList<HistoryItem> m_history;
+ QString m_lastSavedUrl;
+
+ HistoryModel *m_historyModel;
+ HistoryFilterModel *m_historyFilterModel;
+ HistoryTreeModel *m_historyTreeModel;
+};
+
+class HistoryModel : public QAbstractTableModel
+{
+ Q_OBJECT
+
+public slots:
+ void historyReset();
+ void entryAdded();
+ void entryUpdated(int offset);
+
+public:
+ enum Roles {
+ DateRole = Qt::UserRole + 1,
+ DateTimeRole = Qt::UserRole + 2,
+ UrlRole = Qt::UserRole + 3,
+ UrlStringRole = Qt::UserRole + 4
+ };
+
+ HistoryModel(HistoryManager *history, QObject *parent = 0);
+ QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
+ int columnCount(const QModelIndex &parent = QModelIndex()) const;
+ int rowCount(const QModelIndex &parent = QModelIndex()) const;
+ bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex());
+
+private:
+ HistoryManager *m_history;
+};
+
+/*!
+ Proxy model that will remove any duplicate entries.
+ Both m_sourceRow and m_historyHash store their offsets not from
+ the front of the list, but as offsets from the back.
+ */
+class HistoryFilterModel : public QAbstractProxyModel
+{
+ Q_OBJECT
+
+public:
+ HistoryFilterModel(QAbstractItemModel *sourceModel, QObject *parent = 0);
+
+ inline bool historyContains(const QString &url) const
+ { load(); return m_historyHash.contains(url); }
+ int historyLocation(const QString &url) const;
+
+ QModelIndex mapFromSource(const QModelIndex &sourceIndex) const;
+ QModelIndex mapToSource(const QModelIndex &proxyIndex) const;
+ void setSourceModel(QAbstractItemModel *sourceModel);
+ QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
+ int rowCount(const QModelIndex &parent = QModelIndex()) const;
+ int columnCount(const QModelIndex &parent = QModelIndex()) const;
+ QModelIndex index(int, int, const QModelIndex& = QModelIndex()) const;
+ QModelIndex parent(const QModelIndex& index= QModelIndex()) const;
+ bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex());
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
+
+private slots:
+ void sourceReset();
+ void sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight);
+ void sourceRowsInserted(const QModelIndex &parent, int start, int end);
+ void sourceRowsRemoved(const QModelIndex &, int, int);
+
+private:
+ void load() const;
+
+ mutable QList<int> m_sourceRow;
+ mutable QHash<QString, int> m_historyHash;
+ mutable bool m_loaded;
+};
+
+/*
+ The history menu
+ - Removes the first twenty entries and puts them as children of the top level.
+ - If there are less then twenty entries then the first folder is also removed.
+
+ The mapping is done by knowing that HistoryTreeModel is over a table
+ We store that row offset in our index's private data.
+*/
+class HistoryMenuModel : public QAbstractProxyModel
+{
+ Q_OBJECT
+
+public:
+ HistoryMenuModel(HistoryTreeModel *sourceModel, QObject *parent = 0);
+ int columnCount(const QModelIndex &parent) const;
+ int rowCount(const QModelIndex &parent = QModelIndex()) const;
+ QModelIndex mapFromSource(const QModelIndex & sourceIndex) const;
+ QModelIndex mapToSource(const QModelIndex & proxyIndex) const;
+ QModelIndex index(int, int, const QModelIndex &parent = QModelIndex()) const;
+ QModelIndex parent(const QModelIndex &index = QModelIndex()) const;
+
+ int bumpedRows() const;
+
+private:
+ HistoryTreeModel *m_treeModel;
+};
+
+// Menu that is dynamically populated from the history
+class HistoryMenu : public ModelMenu
+{
+ Q_OBJECT
+
+signals:
+ void openUrl(const QUrl &url);
+
+public:
+ HistoryMenu(QWidget *parent = 0);
+ void setInitialActions(QList<QAction*> actions);
+
+protected:
+ bool prePopulated();
+ void postPopulated();
+
+private slots:
+ void activated(const QModelIndex &index);
+ void showHistoryDialog();
+
+private:
+ HistoryManager *m_history;
+ HistoryMenuModel *m_historyMenuModel;
+ QList<QAction*> m_initialActions;
+};
+
+// proxy model for the history model that
+// exposes each url http://www.foo.com and it url starting at the host www.foo.com
+class HistoryCompletionModel : public QAbstractProxyModel
+{
+ Q_OBJECT
+
+public:
+ HistoryCompletionModel(QObject *parent = 0);
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
+ int rowCount(const QModelIndex &parent = QModelIndex()) const;
+ int columnCount(const QModelIndex &parent = QModelIndex()) const;
+ QModelIndex mapFromSource(const QModelIndex &sourceIndex) const;
+ QModelIndex mapToSource(const QModelIndex &proxyIndex) const;
+ QModelIndex index(int, int, const QModelIndex& = QModelIndex()) const;
+ QModelIndex parent(const QModelIndex& index= QModelIndex()) const;
+ void setSourceModel(QAbstractItemModel *sourceModel);
+
+private slots:
+ void sourceReset();
+
+};
+
+// proxy model for the history model that converts the list
+// into a tree, one top level node per day.
+// Used in the HistoryDialog.
+class HistoryTreeModel : public QAbstractProxyModel
+{
+ Q_OBJECT
+
+public:
+ HistoryTreeModel(QAbstractItemModel *sourceModel, QObject *parent = 0);
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
+ int columnCount(const QModelIndex &parent) const;
+ int rowCount(const QModelIndex &parent = QModelIndex()) const;
+ QModelIndex mapFromSource(const QModelIndex &sourceIndex) const;
+ QModelIndex mapToSource(const QModelIndex &proxyIndex) const;
+ QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const;
+ QModelIndex parent(const QModelIndex &index= QModelIndex()) const;
+ bool hasChildren(const QModelIndex &parent = QModelIndex()) const;
+ Qt::ItemFlags flags(const QModelIndex &index) const;
+ bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex());
+ QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
+
+ void setSourceModel(QAbstractItemModel *sourceModel);
+
+private slots:
+ void sourceReset();
+ void sourceRowsInserted(const QModelIndex &parent, int start, int end);
+ void sourceRowsRemoved(const QModelIndex &parent, int start, int end);
+
+private:
+ int sourceDateRow(int row) const;
+ mutable QList<int> m_sourceRowCache;
+
+};
+
+// A modified QSortFilterProxyModel that always accepts the root nodes in the tree
+// so filtering is only done on the children.
+// Used in the HistoryDialog
+class TreeProxyModel : public QSortFilterProxyModel
+{
+ Q_OBJECT
+
+public:
+ TreeProxyModel(QObject *parent = 0);
+
+protected:
+ bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const;
+};
+
+#include "ui_history.h"
+
+class HistoryDialog : public QDialog, public Ui_HistoryDialog
+{
+ Q_OBJECT
+
+signals:
+ void openUrl(const QUrl &url);
+
+public:
+ HistoryDialog(QWidget *parent = 0, HistoryManager *history = 0);
+
+private slots:
+ void customContextMenuRequested(const QPoint &pos);
+ void open();
+ void copy();
+
+};
+
+#endif // HISTORY_H
diff --git a/examples/widgets/browser/history.ui b/examples/widgets/browser/history.ui
new file mode 100644
index 000000000..0944940e7
--- /dev/null
+++ b/examples/widgets/browser/history.ui
@@ -0,0 +1,106 @@
+<ui version="4.0" >
+ <class>HistoryDialog</class>
+ <widget class="QDialog" name="HistoryDialog" >
+ <property name="geometry" >
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>758</width>
+ <height>450</height>
+ </rect>
+ </property>
+ <property name="windowTitle" >
+ <string>History</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout" >
+ <item row="0" column="0" >
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0" >
+ <size>
+ <width>252</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="0" column="1" >
+ <widget class="SearchLineEdit" name="search" />
+ </item>
+ <item row="1" column="0" colspan="2" >
+ <widget class="EditTreeView" name="tree" />
+ </item>
+ <item row="2" column="0" colspan="2" >
+ <layout class="QHBoxLayout" >
+ <item>
+ <widget class="QPushButton" name="removeButton" >
+ <property name="text" >
+ <string>&amp;Remove</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="removeAllButton" >
+ <property name="text" >
+ <string>Remove &amp;All</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0" >
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox" >
+ <property name="standardButtons" >
+ <set>QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>SearchLineEdit</class>
+ <extends>QLineEdit</extends>
+ <header>searchlineedit.h</header>
+ </customwidget>
+ <customwidget>
+ <class>EditTreeView</class>
+ <extends>QTreeView</extends>
+ <header>edittreeview.h</header>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>HistoryDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>472</x>
+ <y>329</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>461</x>
+ <y>356</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/examples/widgets/browser/htmls/htmls.qrc b/examples/widgets/browser/htmls/htmls.qrc
new file mode 100644
index 000000000..03b256ccb
--- /dev/null
+++ b/examples/widgets/browser/htmls/htmls.qrc
@@ -0,0 +1,5 @@
+<!DOCTYPE RCC><RCC version="1.0">
+<qresource>
+ <file>notfound.html</file>
+</qresource>
+</RCC>
diff --git a/examples/widgets/browser/htmls/notfound.html b/examples/widgets/browser/htmls/notfound.html
new file mode 100644
index 000000000..e89845aa6
--- /dev/null
+++ b/examples/widgets/browser/htmls/notfound.html
@@ -0,0 +1,63 @@
+<html>
+<head>
+<title>%1</title>
+<style>
+body {
+ padding: 3em 0em;
+ background: #eeeeee;
+}
+hr {
+ color: lightgray;
+ width: 100%;
+}
+img {
+ float: left;
+ opacity: .8;
+}
+#box {
+ background: white;
+ border: 1px solid lightgray;
+ width: 600px;
+ padding: 60px;
+ margin: auto;
+}
+h1 {
+ font-size: 130%;
+ font-weight: bold;
+ border-bottom: 1px solid lightgray;
+ margin-left: 48px;
+}
+h2 {
+ font-size: 100%;
+ font-weight: normal;
+ border-bottom: 1px solid lightgray;
+ margin-left: 48px;
+}
+ul {
+ font-size: 80%;
+ padding-left: 48px;
+ margin: 0;
+}
+#reloadButton {
+ padding-left: 48px;
+}
+</style>
+</head>
+<body>
+ <div id="box">
+ <img src="data:image/png;base64,IMAGE_BINARY_DATA_HERE" width="32" height="32"/>
+ <h1>%2</h1>
+ <h2>When connecting to: %3.</h2>
+ <ul>
+ <li>Check the address for errors such as <b>ww</b>.example.com
+ instead of <b>www</b>.example.com</li>
+ <li>If the address is correct, try checking the network
+ connection.</li>
+ <li>If your computer or network is protected by a firewall or
+ proxy, make sure that the browser demo is permitted to access
+ the network.</li>
+ </ul>
+ <br/><br/>
+ </div>
+</body>
+</html>
diff --git a/examples/widgets/browser/main.cpp b/examples/widgets/browser/main.cpp
new file mode 100644
index 000000000..9baf01f42
--- /dev/null
+++ b/examples/widgets/browser/main.cpp
@@ -0,0 +1,52 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the demonstration applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "browserapplication.h"
+
+int main(int argc, char **argv)
+{
+ Q_INIT_RESOURCE(data);
+ BrowserApplication application(argc, argv);
+ if (!application.isTheOnlyBrowser())
+ return 0;
+ application.newMainWindow();
+ return application.exec();
+}
diff --git a/examples/widgets/browser/modelmenu.cpp b/examples/widgets/browser/modelmenu.cpp
new file mode 100644
index 000000000..56453b080
--- /dev/null
+++ b/examples/widgets/browser/modelmenu.cpp
@@ -0,0 +1,226 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the demonstration applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "modelmenu.h"
+
+#include <QtCore/QAbstractItemModel>
+#include <qdebug.h>
+
+ModelMenu::ModelMenu(QWidget * parent)
+ : QMenu(parent)
+ , m_maxRows(7)
+ , m_firstSeparator(-1)
+ , m_maxWidth(-1)
+ , m_hoverRole(0)
+ , m_separatorRole(0)
+ , m_model(0)
+{
+ connect(this, SIGNAL(aboutToShow()), this, SLOT(aboutToShow()));
+}
+
+bool ModelMenu::prePopulated()
+{
+ return false;
+}
+
+void ModelMenu::postPopulated()
+{
+}
+
+void ModelMenu::setModel(QAbstractItemModel *model)
+{
+ m_model = model;
+}
+
+QAbstractItemModel *ModelMenu::model() const
+{
+ return m_model;
+}
+
+void ModelMenu::setMaxRows(int max)
+{
+ m_maxRows = max;
+}
+
+int ModelMenu::maxRows() const
+{
+ return m_maxRows;
+}
+
+void ModelMenu::setFirstSeparator(int offset)
+{
+ m_firstSeparator = offset;
+}
+
+int ModelMenu::firstSeparator() const
+{
+ return m_firstSeparator;
+}
+
+void ModelMenu::setRootIndex(const QModelIndex &index)
+{
+ m_root = index;
+}
+
+QModelIndex ModelMenu::rootIndex() const
+{
+ return m_root;
+}
+
+void ModelMenu::setHoverRole(int role)
+{
+ m_hoverRole = role;
+}
+
+int ModelMenu::hoverRole() const
+{
+ return m_hoverRole;
+}
+
+void ModelMenu::setSeparatorRole(int role)
+{
+ m_separatorRole = role;
+}
+
+int ModelMenu::separatorRole() const
+{
+ return m_separatorRole;
+}
+
+Q_DECLARE_METATYPE(QModelIndex)
+void ModelMenu::aboutToShow()
+{
+ if (QMenu *menu = qobject_cast<QMenu*>(sender())) {
+ QVariant v = menu->menuAction()->data();
+ if (v.canConvert<QModelIndex>()) {
+ QModelIndex idx = qvariant_cast<QModelIndex>(v);
+ createMenu(idx, -1, menu, menu);
+ disconnect(menu, SIGNAL(aboutToShow()), this, SLOT(aboutToShow()));
+ return;
+ }
+ }
+
+ clear();
+ if (prePopulated())
+ addSeparator();
+ int max = m_maxRows;
+ if (max != -1)
+ max += m_firstSeparator;
+ createMenu(m_root, max, this, this);
+ postPopulated();
+}
+
+void ModelMenu::createMenu(const QModelIndex &parent, int max, QMenu *parentMenu, QMenu *menu)
+{
+ if (!menu) {
+ QString title = parent.data().toString();
+ menu = new QMenu(title, this);
+ QIcon icon = qvariant_cast<QIcon>(parent.data(Qt::DecorationRole));
+ menu->setIcon(icon);
+ parentMenu->addMenu(menu);
+ QVariant v;
+ v.setValue(parent);
+ menu->menuAction()->setData(v);
+ connect(menu, SIGNAL(aboutToShow()), this, SLOT(aboutToShow()));
+ return;
+ }
+
+ int end = m_model->rowCount(parent);
+ if (max != -1)
+ end = qMin(max, end);
+
+ connect(menu, SIGNAL(triggered(QAction*)), this, SLOT(triggered(QAction*)));
+ connect(menu, SIGNAL(hovered(QAction*)), this, SLOT(hovered(QAction*)));
+
+ for (int i = 0; i < end; ++i) {
+ QModelIndex idx = m_model->index(i, 0, parent);
+ if (m_model->hasChildren(idx)) {
+ createMenu(idx, -1, menu);
+ } else {
+ if (m_separatorRole != 0
+ && idx.data(m_separatorRole).toBool())
+ addSeparator();
+ else
+ menu->addAction(makeAction(idx));
+ }
+ if (menu == this && i == m_firstSeparator - 1)
+ addSeparator();
+ }
+}
+
+QAction *ModelMenu::makeAction(const QModelIndex &index)
+{
+ QIcon icon = qvariant_cast<QIcon>(index.data(Qt::DecorationRole));
+ QAction *action = makeAction(icon, index.data().toString(), this);
+ QVariant v;
+ v.setValue(index);
+ action->setData(v);
+ return action;
+}
+
+QAction *ModelMenu::makeAction(const QIcon &icon, const QString &text, QObject *parent)
+{
+ QFontMetrics fm(font());
+ if (-1 == m_maxWidth)
+ m_maxWidth = fm.width(QLatin1Char('m')) * 30;
+ QString smallText = fm.elidedText(text, Qt::ElideMiddle, m_maxWidth);
+ return new QAction(icon, smallText, parent);
+}
+
+void ModelMenu::triggered(QAction *action)
+{
+ QVariant v = action->data();
+ if (v.canConvert<QModelIndex>()) {
+ QModelIndex idx = qvariant_cast<QModelIndex>(v);
+ emit activated(idx);
+ }
+}
+
+void ModelMenu::hovered(QAction *action)
+{
+ QVariant v = action->data();
+ if (v.canConvert<QModelIndex>()) {
+ QModelIndex idx = qvariant_cast<QModelIndex>(v);
+ QString hoveredString = idx.data(m_hoverRole).toString();
+ if (!hoveredString.isEmpty())
+ emit hovered(hoveredString);
+ }
+}
diff --git a/examples/widgets/browser/modelmenu.h b/examples/widgets/browser/modelmenu.h
new file mode 100644
index 000000000..4045a7758
--- /dev/null
+++ b/examples/widgets/browser/modelmenu.h
@@ -0,0 +1,104 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the demonstration applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef MODELMENU_H
+#define MODELMENU_H
+
+#include <QtWidgets/QMenu>
+#include <QtCore/QAbstractItemModel>
+
+// A QMenu that is dynamically populated from a QAbstractItemModel
+class ModelMenu : public QMenu
+{
+ Q_OBJECT
+
+signals:
+ void activated(const QModelIndex &index);
+ void hovered(const QString &text);
+
+public:
+ ModelMenu(QWidget *parent = 0);
+
+ void setModel(QAbstractItemModel *model);
+ QAbstractItemModel *model() const;
+
+ void setMaxRows(int max);
+ int maxRows() const;
+
+ void setFirstSeparator(int offset);
+ int firstSeparator() const;
+
+ void setRootIndex(const QModelIndex &index);
+ QModelIndex rootIndex() const;
+
+ void setHoverRole(int role);
+ int hoverRole() const;
+
+ void setSeparatorRole(int role);
+ int separatorRole() const;
+
+ QAction *makeAction(const QIcon &icon, const QString &text, QObject *parent);
+
+protected:
+ // add any actions before the tree, return true if any actions are added.
+ virtual bool prePopulated();
+ // add any actions after the tree
+ virtual void postPopulated();
+ // put all of the children of parent into menu up to max
+ void createMenu(const QModelIndex &parent, int max, QMenu *parentMenu = 0, QMenu *menu = 0);
+
+private slots:
+ void aboutToShow();
+ void triggered(QAction *action);
+ void hovered(QAction *action);
+
+private:
+ QAction *makeAction(const QModelIndex &index);
+ int m_maxRows;
+ int m_firstSeparator;
+ int m_maxWidth;
+ int m_hoverRole;
+ int m_separatorRole;
+ QAbstractItemModel *m_model;
+ QPersistentModelIndex m_root;
+};
+
+#endif // MODELMENU_H
diff --git a/examples/widgets/browser/networkaccessmanager.cpp b/examples/widgets/browser/networkaccessmanager.cpp
new file mode 100644
index 000000000..ae5dfe4de
--- /dev/null
+++ b/examples/widgets/browser/networkaccessmanager.cpp
@@ -0,0 +1,214 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the demonstration applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "networkaccessmanager.h"
+
+#include "browserapplication.h"
+#include "browsermainwindow.h"
+#include "ui_passworddialog.h"
+#include "ui_proxy.h"
+
+#include <QtCore/QSettings>
+
+#include <QtGui/QDesktopServices>
+#include <QtWidgets/QDialog>
+#include <QtWidgets/QMessageBox>
+#include <QtWidgets/QStyle>
+#include <QtGui/QTextDocument>
+
+#include <QtNetwork/QAuthenticator>
+#include <QtNetwork/QNetworkDiskCache>
+#include <QtNetwork/QNetworkProxy>
+#include <QtNetwork/QNetworkRequest>
+#include <QtNetwork/QNetworkReply>
+#include <QtNetwork/QSslError>
+
+NetworkAccessManager::NetworkAccessManager(QObject *parent)
+ : QNetworkAccessManager(parent),
+ requestFinishedCount(0), requestFinishedFromCacheCount(0), requestFinishedPipelinedCount(0),
+ requestFinishedSecureCount(0), requestFinishedDownloadBufferCount(0)
+{
+ connect(this, SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*)),
+ SLOT(authenticationRequired(QNetworkReply*,QAuthenticator*)));
+ connect(this, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
+ SLOT(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)));
+ connect(this, SIGNAL(finished(QNetworkReply*)),
+ SLOT(requestFinished(QNetworkReply*)));
+#ifndef QT_NO_OPENSSL
+ connect(this, SIGNAL(sslErrors(QNetworkReply*,QList<QSslError>)),
+ SLOT(sslErrors(QNetworkReply*,QList<QSslError>)));
+#endif
+ loadSettings();
+
+ QNetworkDiskCache *diskCache = new QNetworkDiskCache(this);
+ QString location = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
+ diskCache->setCacheDirectory(location);
+ setCache(diskCache);
+}
+
+QNetworkReply* NetworkAccessManager::createRequest(Operation op, const QNetworkRequest & req, QIODevice * outgoingData)
+{
+ QNetworkRequest request = req; // copy so we can modify
+ // this is a temporary hack until we properly use the pipelining flags from QtWebkit
+ // pipeline everything! :)
+ request.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true);
+ return QNetworkAccessManager::createRequest(op, request, outgoingData);
+}
+
+void NetworkAccessManager::requestFinished(QNetworkReply *reply)
+{
+ requestFinishedCount++;
+
+ if (reply->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool() == true)
+ requestFinishedFromCacheCount++;
+
+ if (reply->attribute(QNetworkRequest::HttpPipeliningWasUsedAttribute).toBool() == true)
+ requestFinishedPipelinedCount++;
+
+ if (reply->attribute(QNetworkRequest::ConnectionEncryptedAttribute).toBool() == true)
+ requestFinishedSecureCount++;
+
+ if (reply->attribute(QNetworkRequest::DownloadBufferAttribute).isValid() == true)
+ requestFinishedDownloadBufferCount++;
+
+ if (requestFinishedCount % 10)
+ return;
+
+#ifdef QT_DEBUG
+ double pctCached = (double(requestFinishedFromCacheCount) * 100.0/ double(requestFinishedCount));
+ double pctPipelined = (double(requestFinishedPipelinedCount) * 100.0/ double(requestFinishedCount));
+ double pctSecure = (double(requestFinishedSecureCount) * 100.0/ double(requestFinishedCount));
+ double pctDownloadBuffer = (double(requestFinishedDownloadBufferCount) * 100.0/ double(requestFinishedCount));
+
+ qDebug("STATS [%lli requests total] [%3.2f%% from cache] [%3.2f%% pipelined] [%3.2f%% SSL/TLS] [%3.2f%% Zerocopy]", requestFinishedCount, pctCached, pctPipelined, pctSecure, pctDownloadBuffer);
+#endif
+}
+
+void NetworkAccessManager::loadSettings()
+{
+ QSettings settings;
+ settings.beginGroup(QLatin1String("proxy"));
+ QNetworkProxy proxy;
+ if (settings.value(QLatin1String("enabled"), false).toBool()) {
+ if (settings.value(QLatin1String("type"), 0).toInt() == 0)
+ proxy = QNetworkProxy::Socks5Proxy;
+ else
+ proxy = QNetworkProxy::HttpProxy;
+ proxy.setHostName(settings.value(QLatin1String("hostName")).toString());
+ proxy.setPort(settings.value(QLatin1String("port"), 1080).toInt());
+ proxy.setUser(settings.value(QLatin1String("userName")).toString());
+ proxy.setPassword(settings.value(QLatin1String("password")).toString());
+ }
+ setProxy(proxy);
+}
+
+void NetworkAccessManager::authenticationRequired(QNetworkReply *reply, QAuthenticator *auth)
+{
+ BrowserMainWindow *mainWindow = BrowserApplication::instance()->mainWindow();
+
+ QDialog dialog(mainWindow);
+ dialog.setWindowFlags(Qt::Sheet);
+
+ Ui::PasswordDialog passwordDialog;
+ passwordDialog.setupUi(&dialog);
+
+ passwordDialog.iconLabel->setText(QString());
+ passwordDialog.iconLabel->setPixmap(mainWindow->style()->standardIcon(QStyle::SP_MessageBoxQuestion, 0, mainWindow).pixmap(32, 32));
+
+ QString introMessage = tr("<qt>Enter username and password for \"%1\" at %2</qt>");
+ introMessage = introMessage.arg(reply->url().toString().toHtmlEscaped()).arg(reply->url().toString().toHtmlEscaped());
+ passwordDialog.introLabel->setText(introMessage);
+ passwordDialog.introLabel->setWordWrap(true);
+
+ if (dialog.exec() == QDialog::Accepted) {
+ auth->setUser(passwordDialog.userNameLineEdit->text());
+ auth->setPassword(passwordDialog.passwordLineEdit->text());
+ }
+}
+
+void NetworkAccessManager::proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *auth)
+{
+ BrowserMainWindow *mainWindow = BrowserApplication::instance()->mainWindow();
+
+ QDialog dialog(mainWindow);
+ dialog.setWindowFlags(Qt::Sheet);
+
+ Ui::ProxyDialog proxyDialog;
+ proxyDialog.setupUi(&dialog);
+
+ proxyDialog.iconLabel->setText(QString());
+ proxyDialog.iconLabel->setPixmap(mainWindow->style()->standardIcon(QStyle::SP_MessageBoxQuestion, 0, mainWindow).pixmap(32, 32));
+
+ QString introMessage = tr("<qt>Connect to proxy \"%1\" using:</qt>");
+ introMessage = introMessage.arg(proxy.hostName().toHtmlEscaped());
+ proxyDialog.introLabel->setText(introMessage);
+ proxyDialog.introLabel->setWordWrap(true);
+
+ if (dialog.exec() == QDialog::Accepted) {
+ auth->setUser(proxyDialog.userNameLineEdit->text());
+ auth->setPassword(proxyDialog.passwordLineEdit->text());
+ }
+}
+
+#ifndef QT_NO_OPENSSL
+void NetworkAccessManager::sslErrors(QNetworkReply *reply, const QList<QSslError> &error)
+{
+ // check if SSL certificate has been trusted already
+ QString replyHost = reply->url().host() + QString(":%1").arg(reply->url().port());
+ if (! sslTrustedHostList.contains(replyHost)) {
+ BrowserMainWindow *mainWindow = BrowserApplication::instance()->mainWindow();
+
+ QStringList errorStrings;
+ for (int i = 0; i < error.count(); ++i)
+ errorStrings += error.at(i).errorString();
+ QString errors = errorStrings.join(QLatin1String("\n"));
+ int ret = QMessageBox::warning(mainWindow, QCoreApplication::applicationName(),
+ tr("SSL Errors:\n\n%1\n\n%2\n\n"
+ "Do you want to ignore these errors for this host?").arg(reply->url().toString()).arg(errors),
+ QMessageBox::Yes | QMessageBox::No,
+ QMessageBox::No);
+ if (ret == QMessageBox::Yes) {
+ reply->ignoreSslErrors();
+ sslTrustedHostList.append(replyHost);
+ }
+ }
+}
+#endif
diff --git a/examples/widgets/browser/networkaccessmanager.h b/examples/widgets/browser/networkaccessmanager.h
new file mode 100644
index 000000000..8a7aaf808
--- /dev/null
+++ b/examples/widgets/browser/networkaccessmanager.h
@@ -0,0 +1,77 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the demonstration applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef NETWORKACCESSMANAGER_H
+#define NETWORKACCESSMANAGER_H
+
+#include <QtNetwork/QNetworkAccessManager>
+#include <QtNetwork/QNetworkRequest>
+
+class NetworkAccessManager : public QNetworkAccessManager
+{
+ Q_OBJECT
+
+public:
+ NetworkAccessManager(QObject *parent = 0);
+
+ virtual QNetworkReply* createRequest ( Operation op, const QNetworkRequest & req, QIODevice * outgoingData = 0 );
+
+private:
+ QList<QString> sslTrustedHostList;
+ qint64 requestFinishedCount;
+ qint64 requestFinishedFromCacheCount;
+ qint64 requestFinishedPipelinedCount;
+ qint64 requestFinishedSecureCount;
+ qint64 requestFinishedDownloadBufferCount;
+
+public slots:
+ void loadSettings();
+ void requestFinished(QNetworkReply *reply);
+
+private slots:
+ void authenticationRequired(QNetworkReply *reply, QAuthenticator *auth);
+ void proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *auth);
+#ifndef QT_NO_OPENSSL
+ void sslErrors(QNetworkReply *reply, const QList<QSslError> &error);
+#endif
+};
+
+#endif // NETWORKACCESSMANAGER_H
diff --git a/examples/widgets/browser/passworddialog.ui b/examples/widgets/browser/passworddialog.ui
new file mode 100644
index 000000000..7c1665867
--- /dev/null
+++ b/examples/widgets/browser/passworddialog.ui
@@ -0,0 +1,111 @@
+<ui version="4.0" >
+ <class>PasswordDialog</class>
+ <widget class="QDialog" name="PasswordDialog" >
+ <property name="geometry" >
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>399</width>
+ <height>148</height>
+ </rect>
+ </property>
+ <property name="windowTitle" >
+ <string>Authentication Required</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout" >
+ <item row="0" column="0" colspan="2" >
+ <layout class="QHBoxLayout" >
+ <item>
+ <widget class="QLabel" name="iconLabel" >
+ <property name="text" >
+ <string>DUMMY ICON</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="introLabel" >
+ <property name="sizePolicy" >
+ <sizepolicy vsizetype="MinimumExpanding" hsizetype="MinimumExpanding" >
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text" >
+ <string>INTRO TEXT DUMMY</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="1" column="0" >
+ <widget class="QLabel" name="label" >
+ <property name="text" >
+ <string>Username:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1" >
+ <widget class="QLineEdit" name="userNameLineEdit" />
+ </item>
+ <item row="2" column="0" >
+ <widget class="QLabel" name="lblPassword" >
+ <property name="text" >
+ <string>Password:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1" >
+ <widget class="QLineEdit" name="passwordLineEdit" >
+ <property name="echoMode" >
+ <enum>QLineEdit::Password</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0" colspan="2" >
+ <widget class="QDialogButtonBox" name="buttonBox" >
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons" >
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>PasswordDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>PasswordDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/examples/widgets/browser/proxy.ui b/examples/widgets/browser/proxy.ui
new file mode 100644
index 000000000..62a8be627
--- /dev/null
+++ b/examples/widgets/browser/proxy.ui
@@ -0,0 +1,104 @@
+<ui version="4.0" >
+ <class>ProxyDialog</class>
+ <widget class="QDialog" name="ProxyDialog" >
+ <property name="geometry" >
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>369</width>
+ <height>144</height>
+ </rect>
+ </property>
+ <property name="windowTitle" >
+ <string>Proxy Authentication</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout" >
+ <item row="0" column="0" >
+ <widget class="QLabel" name="iconLabel" >
+ <property name="text" >
+ <string>ICON</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1" colspan="2" >
+ <widget class="QLabel" name="introLabel" >
+ <property name="text" >
+ <string>Connect to proxy</string>
+ </property>
+ <property name="wordWrap" >
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0" colspan="2" >
+ <widget class="QLabel" name="usernameLabel" >
+ <property name="text" >
+ <string>Username:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2" >
+ <widget class="QLineEdit" name="userNameLineEdit" />
+ </item>
+ <item row="2" column="0" colspan="2" >
+ <widget class="QLabel" name="passwordLabel" >
+ <property name="text" >
+ <string>Password:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="2" >
+ <widget class="QLineEdit" name="passwordLineEdit" >
+ <property name="echoMode" >
+ <enum>QLineEdit::Password</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0" colspan="3" >
+ <widget class="QDialogButtonBox" name="buttonBox" >
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons" >
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>ProxyDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>ProxyDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/examples/widgets/browser/searchlineedit.cpp b/examples/widgets/browser/searchlineedit.cpp
new file mode 100644
index 000000000..0d387da4b
--- /dev/null
+++ b/examples/widgets/browser/searchlineedit.cpp
@@ -0,0 +1,240 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the demonstration applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "searchlineedit.h"
+
+#include <QtGui/QPainter>
+#include <QtGui/QMouseEvent>
+#include <QtWidgets/QMenu>
+#include <QtWidgets/QStyle>
+#include <QtWidgets/QStyleOptionFrameV2>
+
+ClearButton::ClearButton(QWidget *parent)
+ : QAbstractButton(parent)
+{
+#ifndef QT_NO_CURSOR
+ setCursor(Qt::ArrowCursor);
+#endif // QT_NO_CURSOR
+ setToolTip(tr("Clear"));
+ setVisible(false);
+ setFocusPolicy(Qt::NoFocus);
+}
+
+void ClearButton::paintEvent(QPaintEvent *event)
+{
+ Q_UNUSED(event);
+ QPainter painter(this);
+ int height = this->height();
+
+ painter.setRenderHint(QPainter::Antialiasing, true);
+ painter.setBrush(isDown()
+ ? palette().color(QPalette::Dark)
+ : palette().color(QPalette::Mid));
+ painter.setPen(painter.brush().color());
+ int size = width();
+ int offset = size / 5;
+ int radius = size - offset * 2;
+ painter.drawEllipse(offset, offset, radius, radius);
+
+ painter.setPen(palette().color(QPalette::Base));
+ int border = offset * 2;
+ painter.drawLine(border, border, width() - border, height - border);
+ painter.drawLine(border, height - border, width() - border, border);
+}
+
+void ClearButton::textChanged(const QString &text)
+{
+ setVisible(!text.isEmpty());
+}
+
+/*
+ Search icon on the left hand side of the search widget
+ When a menu is set a down arrow appears
+ */
+class SearchButton : public QAbstractButton {
+public:
+ SearchButton(QWidget *parent = 0);
+ void paintEvent(QPaintEvent *event);
+ QMenu *m_menu;
+
+protected:
+ void mousePressEvent(QMouseEvent *event);
+};
+
+SearchButton::SearchButton(QWidget *parent)
+ : QAbstractButton(parent),
+ m_menu(0)
+{
+ setObjectName(QLatin1String("SearchButton"));
+#ifndef QT_NO_CURSOR
+ setCursor(Qt::ArrowCursor);
+#endif //QT_NO_CURSOR
+ setFocusPolicy(Qt::NoFocus);
+}
+
+void SearchButton::mousePressEvent(QMouseEvent *event)
+{
+ if (m_menu && event->button() == Qt::LeftButton) {
+ QWidget *p = parentWidget();
+ if (p) {
+ QPoint r = p->mapToGlobal(QPoint(0, p->height()));
+ m_menu->exec(QPoint(r.x() + height() / 2, r.y()));
+ }
+ event->accept();
+ }
+ QAbstractButton::mousePressEvent(event);
+}
+
+void SearchButton::paintEvent(QPaintEvent *event)
+{
+ Q_UNUSED(event);
+ QPainterPath myPath;
+
+ int radius = (height() / 5) * 2;
+ QRect circle(height() / 3 - 1, height() / 4, radius, radius);
+ myPath.addEllipse(circle);
+
+ myPath.arcMoveTo(circle, 300);
+ QPointF c = myPath.currentPosition();
+ int diff = height() / 7;
+ myPath.lineTo(qMin(width() - 2, (int)c.x() + diff), c.y() + diff);
+
+ QPainter painter(this);
+ painter.setRenderHint(QPainter::Antialiasing, true);
+ painter.setPen(QPen(Qt::darkGray, 2));
+ painter.drawPath(myPath);
+
+ if (m_menu) {
+ QPainterPath dropPath;
+ dropPath.arcMoveTo(circle, 320);
+ QPointF c = dropPath.currentPosition();
+ c = QPointF(c.x() + 3.5, c.y() + 0.5);
+ dropPath.moveTo(c);
+ dropPath.lineTo(c.x() + 4, c.y());
+ dropPath.lineTo(c.x() + 2, c.y() + 2);
+ dropPath.closeSubpath();
+ painter.setPen(Qt::darkGray);
+ painter.setBrush(Qt::darkGray);
+ painter.setRenderHint(QPainter::Antialiasing, false);
+ painter.drawPath(dropPath);
+ }
+ painter.end();
+}
+
+/*
+ SearchLineEdit is an enhanced QLineEdit
+ - A Search icon on the left with optional menu
+ - When there is no text and doesn't have focus an "inactive text" is displayed
+ - When there is text a clear button is displayed on the right hand side
+ */
+SearchLineEdit::SearchLineEdit(QWidget *parent) : ExLineEdit(parent),
+ m_searchButton(new SearchButton(this))
+{
+ connect(lineEdit(), SIGNAL(textChanged(QString)),
+ this, SIGNAL(textChanged(QString)));
+ setLeftWidget(m_searchButton);
+ m_inactiveText = tr("Search");
+
+ QSizePolicy policy = sizePolicy();
+ setSizePolicy(QSizePolicy::Preferred, policy.verticalPolicy());
+}
+
+void SearchLineEdit::paintEvent(QPaintEvent *event)
+{
+ if (lineEdit()->text().isEmpty() && !hasFocus() && !m_inactiveText.isEmpty()) {
+ ExLineEdit::paintEvent(event);
+ QStyleOptionFrameV2 panel;
+ initStyleOption(&panel);
+ QRect r = style()->subElementRect(QStyle::SE_LineEditContents, &panel, this);
+ QFontMetrics fm = fontMetrics();
+ int horizontalMargin = lineEdit()->x();
+ QRect lineRect(horizontalMargin + r.x(), r.y() + (r.height() - fm.height() + 1) / 2,
+ r.width() - 2 * horizontalMargin, fm.height());
+ QPainter painter(this);
+ painter.setPen(palette().brush(QPalette::Disabled, QPalette::Text).color());
+ painter.drawText(lineRect, Qt::AlignLeft|Qt::AlignVCenter, m_inactiveText);
+ } else {
+ ExLineEdit::paintEvent(event);
+ }
+}
+
+void SearchLineEdit::resizeEvent(QResizeEvent *event)
+{
+ updateGeometries();
+ ExLineEdit::resizeEvent(event);
+}
+
+void SearchLineEdit::updateGeometries()
+{
+ int menuHeight = height();
+ int menuWidth = menuHeight + 1;
+ if (!m_searchButton->m_menu)
+ menuWidth = (menuHeight / 5) * 4;
+ m_searchButton->resize(QSize(menuWidth, menuHeight));
+}
+
+QString SearchLineEdit::inactiveText() const
+{
+ return m_inactiveText;
+}
+
+void SearchLineEdit::setInactiveText(const QString &text)
+{
+ m_inactiveText = text;
+}
+
+void SearchLineEdit::setMenu(QMenu *menu)
+{
+ if (m_searchButton->m_menu)
+ m_searchButton->m_menu->deleteLater();
+ m_searchButton->m_menu = menu;
+ updateGeometries();
+}
+
+QMenu *SearchLineEdit::menu() const
+{
+ if (!m_searchButton->m_menu) {
+ m_searchButton->m_menu = new QMenu(m_searchButton);
+ if (isVisible())
+ (const_cast<SearchLineEdit*>(this))->updateGeometries();
+ }
+ return m_searchButton->m_menu;
+}
diff --git a/examples/widgets/browser/searchlineedit.h b/examples/widgets/browser/searchlineedit.h
new file mode 100644
index 000000000..e01fcbff2
--- /dev/null
+++ b/examples/widgets/browser/searchlineedit.h
@@ -0,0 +1,102 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the demonstration applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef SEARCHLINEEDIT_H
+#define SEARCHLINEEDIT_H
+
+#include "urllineedit.h"
+
+#include <QtWidgets/QLineEdit>
+#include <QtWidgets/QAbstractButton>
+
+QT_BEGIN_NAMESPACE
+class QMenu;
+QT_END_NAMESPACE
+
+class SearchButton;
+
+/*
+ Clear button on the right hand side of the search widget.
+ Hidden by default
+ "A circle with an X in it"
+ */
+class ClearButton : public QAbstractButton
+{
+ Q_OBJECT
+
+public:
+ ClearButton(QWidget *parent = 0);
+ void paintEvent(QPaintEvent *event);
+
+public slots:
+ void textChanged(const QString &text);
+};
+
+
+class SearchLineEdit : public ExLineEdit
+{
+ Q_OBJECT
+ Q_PROPERTY(QString inactiveText READ inactiveText WRITE setInactiveText)
+
+signals:
+ void textChanged(const QString &text);
+
+public:
+ SearchLineEdit(QWidget *parent = 0);
+
+ QString inactiveText() const;
+ void setInactiveText(const QString &text);
+
+ QMenu *menu() const;
+ void setMenu(QMenu *menu);
+
+protected:
+ void resizeEvent(QResizeEvent *event);
+ void paintEvent(QPaintEvent *event);
+
+private:
+ void updateGeometries();
+
+ SearchButton *m_searchButton;
+ QString m_inactiveText;
+};
+
+#endif // SEARCHLINEEDIT_H
diff --git a/examples/widgets/browser/settings.cpp b/examples/widgets/browser/settings.cpp
new file mode 100644
index 000000000..3521ce977
--- /dev/null
+++ b/examples/widgets/browser/settings.cpp
@@ -0,0 +1,321 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the demonstration applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "settings.h"
+
+#include "browserapplication.h"
+#include "browsermainwindow.h"
+#include "cookiejar.h"
+#include "history.h"
+#include "networkaccessmanager.h"
+#include "webview.h"
+
+#include <QtCore/QSettings>
+#include <QtWidgets/QtWidgets>
+#include <QtWebKitWidgets>
+
+SettingsDialog::SettingsDialog(QWidget *parent)
+ : QDialog(parent)
+{
+ setupUi(this);
+ connect(exceptionsButton, SIGNAL(clicked()), this, SLOT(showExceptions()));
+ connect(setHomeToCurrentPageButton, SIGNAL(clicked()), this, SLOT(setHomeToCurrentPage()));
+ connect(cookiesButton, SIGNAL(clicked()), this, SLOT(showCookies()));
+ connect(standardFontButton, SIGNAL(clicked()), this, SLOT(chooseFont()));
+ connect(fixedFontButton, SIGNAL(clicked()), this, SLOT(chooseFixedFont()));
+
+ loadDefaults();
+ loadFromSettings();
+}
+
+void SettingsDialog::loadDefaults()
+{
+ QWebSettings *defaultSettings = QWebSettings::globalSettings();
+ QString standardFontFamily = defaultSettings->fontFamily(QWebSettings::StandardFont);
+ int standardFontSize = defaultSettings->fontSize(QWebSettings::DefaultFontSize);
+ standardFont = QFont(standardFontFamily, standardFontSize);
+ standardLabel->setText(QString(QLatin1String("%1 %2")).arg(standardFont.family()).arg(standardFont.pointSize()));
+
+ QString fixedFontFamily = defaultSettings->fontFamily(QWebSettings::FixedFont);
+ int fixedFontSize = defaultSettings->fontSize(QWebSettings::DefaultFixedFontSize);
+ fixedFont = QFont(fixedFontFamily, fixedFontSize);
+ fixedLabel->setText(QString(QLatin1String("%1 %2")).arg(fixedFont.family()).arg(fixedFont.pointSize()));
+
+ downloadsLocation->setText(QStandardPaths::writableLocation(QStandardPaths::DesktopLocation));
+
+ enableJavascript->setChecked(defaultSettings->testAttribute(QWebSettings::JavascriptEnabled));
+ enablePlugins->setChecked(defaultSettings->testAttribute(QWebSettings::PluginsEnabled));
+}
+
+void SettingsDialog::loadFromSettings()
+{
+ QSettings settings;
+ settings.beginGroup(QLatin1String("MainWindow"));
+ QString defaultHome = QLatin1String("http://qt-project.org/");
+ homeLineEdit->setText(settings.value(QLatin1String("home"), defaultHome).toString());
+ settings.endGroup();
+
+ settings.beginGroup(QLatin1String("history"));
+ int historyExpire = settings.value(QLatin1String("historyExpire")).toInt();
+ int idx = 0;
+ switch (historyExpire) {
+ case 1: idx = 0; break;
+ case 7: idx = 1; break;
+ case 14: idx = 2; break;
+ case 30: idx = 3; break;
+ case 365: idx = 4; break;
+ case -1: idx = 5; break;
+ default:
+ idx = 5;
+ }
+ expireHistory->setCurrentIndex(idx);
+ settings.endGroup();
+
+ settings.beginGroup(QLatin1String("downloadmanager"));
+ QString downloadDirectory = settings.value(QLatin1String("downloadDirectory"), downloadsLocation->text()).toString();
+ downloadsLocation->setText(downloadDirectory);
+ settings.endGroup();
+
+ settings.beginGroup(QLatin1String("general"));
+ openLinksIn->setCurrentIndex(settings.value(QLatin1String("openLinksIn"), openLinksIn->currentIndex()).toInt());
+
+ settings.endGroup();
+
+ // Appearance
+ settings.beginGroup(QLatin1String("websettings"));
+ fixedFont = qvariant_cast<QFont>(settings.value(QLatin1String("fixedFont"), fixedFont));
+ standardFont = qvariant_cast<QFont>(settings.value(QLatin1String("standardFont"), standardFont));
+
+ standardLabel->setText(QString(QLatin1String("%1 %2")).arg(standardFont.family()).arg(standardFont.pointSize()));
+ fixedLabel->setText(QString(QLatin1String("%1 %2")).arg(fixedFont.family()).arg(fixedFont.pointSize()));
+
+ enableJavascript->setChecked(settings.value(QLatin1String("enableJavascript"), enableJavascript->isChecked()).toBool());
+ enablePlugins->setChecked(settings.value(QLatin1String("enablePlugins"), enablePlugins->isChecked()).toBool());
+ userStyleSheet->setText(settings.value(QLatin1String("userStyleSheet")).toUrl().toString());
+ settings.endGroup();
+
+ // Privacy
+ settings.beginGroup(QLatin1String("cookies"));
+
+ QByteArray value = settings.value(QLatin1String("acceptCookies"), QLatin1String("AcceptOnlyFromSitesNavigatedTo")).toByteArray();
+ QMetaEnum acceptPolicyEnum = CookieJar::staticMetaObject.enumerator(CookieJar::staticMetaObject.indexOfEnumerator("AcceptPolicy"));
+ CookieJar::AcceptPolicy acceptCookies = acceptPolicyEnum.keyToValue(value) == -1 ?
+ CookieJar::AcceptOnlyFromSitesNavigatedTo :
+ static_cast<CookieJar::AcceptPolicy>(acceptPolicyEnum.keyToValue(value));
+ switch (acceptCookies) {
+ case CookieJar::AcceptAlways:
+ acceptCombo->setCurrentIndex(0);
+ break;
+ case CookieJar::AcceptNever:
+ acceptCombo->setCurrentIndex(1);
+ break;
+ case CookieJar::AcceptOnlyFromSitesNavigatedTo:
+ acceptCombo->setCurrentIndex(2);
+ break;
+ }
+
+ value = settings.value(QLatin1String("keepCookiesUntil"), QLatin1String("Expire")).toByteArray();
+ QMetaEnum keepPolicyEnum = CookieJar::staticMetaObject.enumerator(CookieJar::staticMetaObject.indexOfEnumerator("KeepPolicy"));
+ CookieJar::KeepPolicy keepCookies = keepPolicyEnum.keyToValue(value) == -1 ?
+ CookieJar::KeepUntilExpire :
+ static_cast<CookieJar::KeepPolicy>(keepPolicyEnum.keyToValue(value));
+ switch (keepCookies) {
+ case CookieJar::KeepUntilExpire:
+ keepUntilCombo->setCurrentIndex(0);
+ break;
+ case CookieJar::KeepUntilExit:
+ keepUntilCombo->setCurrentIndex(1);
+ break;
+ case CookieJar::KeepUntilTimeLimit:
+ keepUntilCombo->setCurrentIndex(2);
+ break;
+ }
+ settings.endGroup();
+
+
+ // Proxy
+ settings.beginGroup(QLatin1String("proxy"));
+ proxySupport->setChecked(settings.value(QLatin1String("enabled"), false).toBool());
+ proxyType->setCurrentIndex(settings.value(QLatin1String("type"), 0).toInt());
+ proxyHostName->setText(settings.value(QLatin1String("hostName")).toString());
+ proxyPort->setValue(settings.value(QLatin1String("port"), 1080).toInt());
+ proxyUserName->setText(settings.value(QLatin1String("userName")).toString());
+ proxyPassword->setText(settings.value(QLatin1String("password")).toString());
+ settings.endGroup();
+}
+
+void SettingsDialog::saveToSettings()
+{
+ QSettings settings;
+ settings.beginGroup(QLatin1String("MainWindow"));
+ settings.setValue(QLatin1String("home"), homeLineEdit->text());
+ settings.endGroup();
+
+ settings.beginGroup(QLatin1String("general"));
+ settings.setValue(QLatin1String("openLinksIn"), openLinksIn->currentIndex());
+ settings.endGroup();
+
+ settings.beginGroup(QLatin1String("history"));
+ int historyExpire = expireHistory->currentIndex();
+ int idx = -1;
+ switch (historyExpire) {
+ case 0: idx = 1; break;
+ case 1: idx = 7; break;
+ case 2: idx = 14; break;
+ case 3: idx = 30; break;
+ case 4: idx = 365; break;
+ case 5: idx = -1; break;
+ }
+ settings.setValue(QLatin1String("historyExpire"), idx);
+ settings.endGroup();
+
+ // Appearance
+ settings.beginGroup(QLatin1String("websettings"));
+ settings.setValue(QLatin1String("fixedFont"), fixedFont);
+ settings.setValue(QLatin1String("standardFont"), standardFont);
+ settings.setValue(QLatin1String("enableJavascript"), enableJavascript->isChecked());
+ settings.setValue(QLatin1String("enablePlugins"), enablePlugins->isChecked());
+ QString userStyleSheetString = userStyleSheet->text();
+ if (QFile::exists(userStyleSheetString))
+ settings.setValue(QLatin1String("userStyleSheet"), QUrl::fromLocalFile(userStyleSheetString));
+ else
+ settings.setValue(QLatin1String("userStyleSheet"), QUrl(userStyleSheetString));
+ settings.endGroup();
+
+ //Privacy
+ settings.beginGroup(QLatin1String("cookies"));
+
+ CookieJar::KeepPolicy keepCookies;
+ switch (acceptCombo->currentIndex()) {
+ default:
+ case 0:
+ keepCookies = CookieJar::KeepUntilExpire;
+ break;
+ case 1:
+ keepCookies = CookieJar::KeepUntilExit;
+ break;
+ case 2:
+ keepCookies = CookieJar::KeepUntilTimeLimit;
+ break;
+ }
+ QMetaEnum acceptPolicyEnum = CookieJar::staticMetaObject.enumerator(CookieJar::staticMetaObject.indexOfEnumerator("AcceptPolicy"));
+ settings.setValue(QLatin1String("acceptCookies"), QLatin1String(acceptPolicyEnum.valueToKey(keepCookies)));
+
+ CookieJar::KeepPolicy keepPolicy;
+ switch (keepUntilCombo->currentIndex()) {
+ default:
+ case 0:
+ keepPolicy = CookieJar::KeepUntilExpire;
+ break;
+ case 1:
+ keepPolicy = CookieJar::KeepUntilExit;
+ break;
+ case 2:
+ keepPolicy = CookieJar::KeepUntilTimeLimit;
+ break;
+ }
+
+ QMetaEnum keepPolicyEnum = CookieJar::staticMetaObject.enumerator(CookieJar::staticMetaObject.indexOfEnumerator("KeepPolicy"));
+ settings.setValue(QLatin1String("keepCookiesUntil"), QLatin1String(keepPolicyEnum.valueToKey(keepPolicy)));
+
+ settings.endGroup();
+
+ // proxy
+ settings.beginGroup(QLatin1String("proxy"));
+ settings.setValue(QLatin1String("enabled"), proxySupport->isChecked());
+ settings.setValue(QLatin1String("type"), proxyType->currentIndex());
+ settings.setValue(QLatin1String("hostName"), proxyHostName->text());
+ settings.setValue(QLatin1String("port"), proxyPort->text());
+ settings.setValue(QLatin1String("userName"), proxyUserName->text());
+ settings.setValue(QLatin1String("password"), proxyPassword->text());
+ settings.endGroup();
+
+ BrowserApplication::instance()->loadSettings();
+ BrowserApplication::networkAccessManager()->loadSettings();
+ BrowserApplication::cookieJar()->loadSettings();
+ BrowserApplication::historyManager()->loadSettings();
+}
+
+void SettingsDialog::accept()
+{
+ saveToSettings();
+ QDialog::accept();
+}
+
+void SettingsDialog::showCookies()
+{
+ CookiesDialog *dialog = new CookiesDialog(BrowserApplication::cookieJar(), this);
+ dialog->exec();
+}
+
+void SettingsDialog::showExceptions()
+{
+ CookiesExceptionsDialog *dialog = new CookiesExceptionsDialog(BrowserApplication::cookieJar(), this);
+ dialog->exec();
+}
+
+void SettingsDialog::chooseFont()
+{
+ bool ok;
+ QFont font = QFontDialog::getFont(&ok, standardFont, this);
+ if ( ok ) {
+ standardFont = font;
+ standardLabel->setText(QString(QLatin1String("%1 %2")).arg(font.family()).arg(font.pointSize()));
+ }
+}
+
+void SettingsDialog::chooseFixedFont()
+{
+ bool ok;
+ QFont font = QFontDialog::getFont(&ok, fixedFont, this);
+ if ( ok ) {
+ fixedFont = font;
+ fixedLabel->setText(QString(QLatin1String("%1 %2")).arg(font.family()).arg(font.pointSize()));
+ }
+}
+
+void SettingsDialog::setHomeToCurrentPage()
+{
+ BrowserMainWindow *mw = static_cast<BrowserMainWindow*>(parent());
+ WebView *webView = mw->currentTab();
+ if (webView)
+ homeLineEdit->setText(webView->url().toString());
+}
diff --git a/examples/widgets/browser/settings.h b/examples/widgets/browser/settings.h
new file mode 100644
index 000000000..0858e0023
--- /dev/null
+++ b/examples/widgets/browser/settings.h
@@ -0,0 +1,73 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the demonstration applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef SETTINGS_H
+#define SETTINGS_H
+
+#include <QtWidgets/QDialog>
+#include "ui_settings.h"
+
+class SettingsDialog : public QDialog, public Ui_Settings
+{
+ Q_OBJECT
+
+public:
+ SettingsDialog(QWidget *parent = 0);
+ void accept();
+
+private slots:
+ void loadDefaults();
+ void loadFromSettings();
+ void saveToSettings();
+
+ void setHomeToCurrentPage();
+ void showCookies();
+ void showExceptions();
+
+ void chooseFont();
+ void chooseFixedFont();
+
+private:
+ QFont standardFont;
+ QFont fixedFont;
+};
+
+#endif // SETTINGS_H
diff --git a/examples/widgets/browser/settings.ui b/examples/widgets/browser/settings.ui
new file mode 100644
index 000000000..3491ce0b0
--- /dev/null
+++ b/examples/widgets/browser/settings.ui
@@ -0,0 +1,614 @@
+<ui version="4.0" >
+ <class>Settings</class>
+ <widget class="QDialog" name="Settings" >
+ <property name="geometry" >
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>657</width>
+ <height>322</height>
+ </rect>
+ </property>
+ <property name="windowTitle" >
+ <string>Settings</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout" >
+ <item row="2" column="0" >
+ <widget class="QDialogButtonBox" name="buttonBox" >
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons" >
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0" >
+ <widget class="QTabWidget" name="tabWidget" >
+ <property name="currentIndex" >
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="tab" >
+ <property name="geometry" >
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>627</width>
+ <height>243</height>
+ </rect>
+ </property>
+ <attribute name="title" >
+ <string>General</string>
+ </attribute>
+ <layout class="QGridLayout" name="gridLayout_4" >
+ <item row="0" column="0" >
+ <widget class="QLabel" name="label_3" >
+ <property name="text" >
+ <string>Home:</string>
+ </property>
+ <property name="alignment" >
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1" colspan="2" >
+ <widget class="QLineEdit" name="homeLineEdit" />
+ </item>
+ <item row="1" column="1" >
+ <widget class="QPushButton" name="setHomeToCurrentPageButton" >
+ <property name="text" >
+ <string>Set to current page</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2" >
+ <spacer name="horizontalSpacer" >
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0" >
+ <size>
+ <width>280</width>
+ <height>18</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="2" column="0" >
+ <widget class="QLabel" name="label_4" >
+ <property name="text" >
+ <string>Remove history items:</string>
+ </property>
+ <property name="alignment" >
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1" colspan="2" >
+ <widget class="QComboBox" name="expireHistory" >
+ <item>
+ <property name="text" >
+ <string>After one day</string>
+ </property>
+ </item>
+ <item>
+ <property name="text" >
+ <string>After one week</string>
+ </property>
+ </item>
+ <item>
+ <property name="text" >
+ <string>After two weeks</string>
+ </property>
+ </item>
+ <item>
+ <property name="text" >
+ <string>After one month</string>
+ </property>
+ </item>
+ <item>
+ <property name="text" >
+ <string>After one year</string>
+ </property>
+ </item>
+ <item>
+ <property name="text" >
+ <string>Manually</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item row="3" column="0" >
+ <widget class="QLabel" name="label_7" >
+ <property name="text" >
+ <string>Save downloads to:</string>
+ </property>
+ <property name="alignment" >
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1" colspan="2" >
+ <widget class="QLineEdit" name="downloadsLocation" />
+ </item>
+ <item row="4" column="0" >
+ <widget class="QLabel" name="label_8" >
+ <property name="text" >
+ <string>Open links from applications:</string>
+ </property>
+ <property name="alignment" >
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="1" colspan="2" >
+ <widget class="QComboBox" name="openLinksIn" >
+ <item>
+ <property name="text" >
+ <string>In a tab in the current window</string>
+ </property>
+ </item>
+ <item>
+ <property name="text" >
+ <string>In a new window</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item row="5" column="1" colspan="2" >
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0" >
+ <size>
+ <width>391</width>
+ <height>262</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="tab_3" >
+ <property name="geometry" >
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>627</width>
+ <height>243</height>
+ </rect>
+ </property>
+ <attribute name="title" >
+ <string>Appearance</string>
+ </attribute>
+ <layout class="QGridLayout" name="gridLayout_3" >
+ <item row="0" column="0" >
+ <widget class="QLabel" name="label_5" >
+ <property name="text" >
+ <string>Standard font:</string>
+ </property>
+ <property name="alignment" >
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1" >
+ <widget class="QLabel" name="standardLabel" >
+ <property name="sizePolicy" >
+ <sizepolicy vsizetype="Preferred" hsizetype="Expanding" >
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="frameShape" >
+ <enum>QFrame::StyledPanel</enum>
+ </property>
+ <property name="text" >
+ <string>Times 16</string>
+ </property>
+ <property name="alignment" >
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2" >
+ <widget class="QPushButton" name="standardFontButton" >
+ <property name="text" >
+ <string>Select...</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0" >
+ <widget class="QLabel" name="label_6" >
+ <property name="text" >
+ <string>Fixed-width font:</string>
+ </property>
+ <property name="alignment" >
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1" >
+ <widget class="QLabel" name="fixedLabel" >
+ <property name="frameShape" >
+ <enum>QFrame::StyledPanel</enum>
+ </property>
+ <property name="text" >
+ <string>Courier 13</string>
+ </property>
+ <property name="alignment" >
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2" >
+ <widget class="QPushButton" name="fixedFontButton" >
+ <property name="text" >
+ <string>Select...</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1" >
+ <spacer name="verticalSpacer" >
+ <property name="orientation" >
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0" >
+ <size>
+ <width>20</width>
+ <height>93</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="tab_2" >
+ <property name="geometry" >
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>627</width>
+ <height>243</height>
+ </rect>
+ </property>
+ <attribute name="title" >
+ <string>Privacy</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_3" >
+ <item>
+ <widget class="QGroupBox" name="groupBox" >
+ <property name="title" >
+ <string>Web Content</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2" >
+ <item>
+ <widget class="QCheckBox" name="enablePlugins" >
+ <property name="text" >
+ <string>Enable Plugins</string>
+ </property>
+ <property name="checked" >
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="enableJavascript" >
+ <property name="text" >
+ <string>Enable Javascript</string>
+ </property>
+ <property name="checked" >
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="cookiesGroupBox" >
+ <property name="title" >
+ <string>Cookies</string>
+ </property>
+ <layout class="QGridLayout" >
+ <item row="0" column="0" >
+ <widget class="QLabel" name="label_2" >
+ <property name="text" >
+ <string>Accept Cookies:</string>
+ </property>
+ <property name="alignment" >
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1" >
+ <widget class="QComboBox" name="acceptCombo" >
+ <item>
+ <property name="text" >
+ <string>Always</string>
+ </property>
+ </item>
+ <item>
+ <property name="text" >
+ <string>Never</string>
+ </property>
+ </item>
+ <item>
+ <property name="text" >
+ <string>Only from sites you navigate to</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item row="0" column="2" >
+ <widget class="QPushButton" name="exceptionsButton" >
+ <property name="text" >
+ <string>Exceptions...</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0" >
+ <widget class="QLabel" name="label" >
+ <property name="text" >
+ <string>Keep until:</string>
+ </property>
+ <property name="alignment" >
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1" >
+ <widget class="QComboBox" name="keepUntilCombo" >
+ <item>
+ <property name="text" >
+ <string>They expire</string>
+ </property>
+ </item>
+ <item>
+ <property name="text" >
+ <string>I exit the application</string>
+ </property>
+ </item>
+ <item>
+ <property name="text" >
+ <string>At most 90 days</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item row="1" column="2" >
+ <widget class="QPushButton" name="cookiesButton" >
+ <property name="text" >
+ <string>Cookies...</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0" >
+ <size>
+ <width>371</width>
+ <height>177</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="tab_4" >
+ <property name="geometry" >
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>627</width>
+ <height>243</height>
+ </rect>
+ </property>
+ <attribute name="title" >
+ <string>Proxy</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout" >
+ <item>
+ <widget class="QGroupBox" name="proxySupport" >
+ <property name="title" >
+ <string>Enable proxy</string>
+ </property>
+ <property name="checkable" >
+ <bool>true</bool>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_6" >
+ <item row="0" column="0" >
+ <widget class="QLabel" name="label_9" >
+ <property name="text" >
+ <string>Type:</string>
+ </property>
+ <property name="alignment" >
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1" colspan="2" >
+ <widget class="QComboBox" name="proxyType" >
+ <item>
+ <property name="text" >
+ <string>Socks5</string>
+ </property>
+ </item>
+ <item>
+ <property name="text" >
+ <string>Http</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item row="1" column="0" >
+ <widget class="QLabel" name="label_10" >
+ <property name="text" >
+ <string>Host:</string>
+ </property>
+ <property name="alignment" >
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1" colspan="2" >
+ <widget class="QLineEdit" name="proxyHostName" />
+ </item>
+ <item row="2" column="0" >
+ <widget class="QLabel" name="label_11" >
+ <property name="text" >
+ <string>Port:</string>
+ </property>
+ <property name="alignment" >
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1" >
+ <widget class="QSpinBox" name="proxyPort" >
+ <property name="maximum" >
+ <number>10000</number>
+ </property>
+ <property name="value" >
+ <number>1080</number>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="2" >
+ <spacer name="horizontalSpacer_2" >
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0" >
+ <size>
+ <width>293</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="3" column="0" >
+ <widget class="QLabel" name="label_12" >
+ <property name="text" >
+ <string>User Name:</string>
+ </property>
+ <property name="alignment" >
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1" colspan="2" >
+ <widget class="QLineEdit" name="proxyUserName" />
+ </item>
+ <item row="4" column="0" >
+ <widget class="QLabel" name="label_13" >
+ <property name="text" >
+ <string>Password:</string>
+ </property>
+ <property name="alignment" >
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="1" colspan="2" >
+ <widget class="QLineEdit" name="proxyPassword" >
+ <property name="echoMode" >
+ <enum>QLineEdit::Password</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="0" >
+ <spacer name="verticalSpacer_2" >
+ <property name="orientation" >
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0" >
+ <size>
+ <width>20</width>
+ <height>8</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="tab_5" >
+ <attribute name="title" >
+ <string>Advanced</string>
+ </attribute>
+ <layout class="QGridLayout" name="gridLayout_2" >
+ <item row="0" column="0" >
+ <widget class="QLabel" name="label_14" >
+ <property name="text" >
+ <string>Style Sheet:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1" >
+ <widget class="QLineEdit" name="userStyleSheet" />
+ </item>
+ <item row="1" column="1" >
+ <spacer name="verticalSpacer_3" >
+ <property name="orientation" >
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0" >
+ <size>
+ <width>20</width>
+ <height>176</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>Settings</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>Settings</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/examples/widgets/browser/squeezelabel.cpp b/examples/widgets/browser/squeezelabel.cpp
new file mode 100644
index 000000000..c40fe97da
--- /dev/null
+++ b/examples/widgets/browser/squeezelabel.cpp
@@ -0,0 +1,60 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the demonstration applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "squeezelabel.h"
+
+SqueezeLabel::SqueezeLabel(QWidget *parent) : QLabel(parent)
+{
+}
+
+void SqueezeLabel::paintEvent(QPaintEvent *event)
+{
+ QFontMetrics fm = fontMetrics();
+ if (fm.width(text()) > contentsRect().width()) {
+ QString elided = fm.elidedText(text(), Qt::ElideMiddle, width());
+ QString oldText = text();
+ setText(elided);
+ QLabel::paintEvent(event);
+ setText(oldText);
+ } else {
+ QLabel::paintEvent(event);
+ }
+}
diff --git a/examples/widgets/browser/squeezelabel.h b/examples/widgets/browser/squeezelabel.h
new file mode 100644
index 000000000..6df84179a
--- /dev/null
+++ b/examples/widgets/browser/squeezelabel.h
@@ -0,0 +1,59 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the demonstration applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef SQUEEZELABEL_H
+#define SQUEEZELABEL_H
+
+#include <QtWidgets/QLabel>
+
+class SqueezeLabel : public QLabel
+{
+ Q_OBJECT
+
+public:
+ SqueezeLabel(QWidget *parent = 0);
+
+protected:
+ void paintEvent(QPaintEvent *event);
+
+};
+
+#endif // SQUEEZELABEL_H
diff --git a/examples/widgets/browser/tabwidget.cpp b/examples/widgets/browser/tabwidget.cpp
new file mode 100644
index 000000000..e451b39d5
--- /dev/null
+++ b/examples/widgets/browser/tabwidget.cpp
@@ -0,0 +1,834 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the demonstration applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "tabwidget.h"
+
+#include "browserapplication.h"
+#include "browsermainwindow.h"
+#include "history.h"
+#include "urllineedit.h"
+#include "webview.h"
+
+#include <QtCore/QMimeData>
+#include <QtGui/QClipboard>
+#include <QtWidgets/QCompleter>
+#include <QtWidgets/QListView>
+#include <QtWidgets/QMenu>
+#include <QtWidgets/QMessageBox>
+#include <QtGui/QDrag>
+#include <QtGui/QMouseEvent>
+#include <QtWidgets/QStackedWidget>
+#include <QtWidgets/QStyle>
+#include <QtWidgets/QToolButton>
+
+#include <QtCore/QDebug>
+
+TabBar::TabBar(QWidget *parent)
+ : QTabBar(parent)
+{
+ setContextMenuPolicy(Qt::CustomContextMenu);
+ setAcceptDrops(true);
+ connect(this, SIGNAL(customContextMenuRequested(QPoint)),
+ this, SLOT(contextMenuRequested(QPoint)));
+
+ QString ctrl = QLatin1String("Ctrl+%1");
+ for (int i = 1; i <= 10; ++i) {
+ int key = i;
+ if (key == 10)
+ key = 0;
+ QShortcut *shortCut = new QShortcut(ctrl.arg(key), this);
+ m_tabShortcuts.append(shortCut);
+ connect(shortCut, SIGNAL(activated()), this, SLOT(selectTabAction()));
+ }
+ setTabsClosable(true);
+ connect(this, SIGNAL(tabCloseRequested(int)),
+ this, SIGNAL(closeTab(int)));
+ setSelectionBehaviorOnRemove(QTabBar::SelectPreviousTab);
+ setMovable(true);
+}
+
+void TabBar::selectTabAction()
+{
+ if (QShortcut *shortCut = qobject_cast<QShortcut*>(sender())) {
+ int index = m_tabShortcuts.indexOf(shortCut);
+ if (index == 0)
+ index = 10;
+ setCurrentIndex(index);
+ }
+}
+
+void TabBar::contextMenuRequested(const QPoint &position)
+{
+ QMenu menu;
+ menu.addAction(tr("New &Tab"), this, SIGNAL(newTab()), QKeySequence::AddTab);
+ int index = tabAt(position);
+ if (-1 != index) {
+ QAction *action = menu.addAction(tr("Clone Tab"),
+ this, SLOT(cloneTab()));
+ action->setData(index);
+
+ menu.addSeparator();
+
+ action = menu.addAction(tr("&Close Tab"),
+ this, SLOT(closeTab()), QKeySequence::Close);
+ action->setData(index);
+
+ action = menu.addAction(tr("Close &Other Tabs"),
+ this, SLOT(closeOtherTabs()));
+ action->setData(index);
+
+ menu.addSeparator();
+
+ action = menu.addAction(tr("Reload Tab"),
+ this, SLOT(reloadTab()), QKeySequence::Refresh);
+ action->setData(index);
+ } else {
+ menu.addSeparator();
+ }
+ menu.addAction(tr("Reload All Tabs"), this, SIGNAL(reloadAllTabs()));
+ menu.exec(QCursor::pos());
+}
+
+void TabBar::cloneTab()
+{
+ if (QAction *action = qobject_cast<QAction*>(sender())) {
+ int index = action->data().toInt();
+ emit cloneTab(index);
+ }
+}
+
+void TabBar::closeTab()
+{
+ if (QAction *action = qobject_cast<QAction*>(sender())) {
+ int index = action->data().toInt();
+ emit closeTab(index);
+ }
+}
+
+void TabBar::closeOtherTabs()
+{
+ if (QAction *action = qobject_cast<QAction*>(sender())) {
+ int index = action->data().toInt();
+ emit closeOtherTabs(index);
+ }
+}
+
+void TabBar::mousePressEvent(QMouseEvent *event)
+{
+ if (event->button() == Qt::LeftButton)
+ m_dragStartPos = event->pos();
+ QTabBar::mousePressEvent(event);
+}
+
+void TabBar::mouseMoveEvent(QMouseEvent *event)
+{
+ if (event->buttons() == Qt::LeftButton) {
+ int diffX = event->pos().x() - m_dragStartPos.x();
+ int diffY = event->pos().y() - m_dragStartPos.y();
+ if ((event->pos() - m_dragStartPos).manhattanLength() > QApplication::startDragDistance()
+ && diffX < 3 && diffX > -3
+ && diffY < -10) {
+ QDrag *drag = new QDrag(this);
+ QMimeData *mimeData = new QMimeData;
+ QList<QUrl> urls;
+ int index = tabAt(event->pos());
+ QUrl url = tabData(index).toUrl();
+ urls.append(url);
+ mimeData->setUrls(urls);
+ mimeData->setText(tabText(index));
+ mimeData->setData(QLatin1String("action"), "tab-reordering");
+ drag->setMimeData(mimeData);
+ drag->exec();
+ }
+ }
+ QTabBar::mouseMoveEvent(event);
+}
+
+// When index is -1 index chooses the current tab
+void TabWidget::reloadTab(int index)
+{
+ if (index < 0)
+ index = currentIndex();
+ if (index < 0 || index >= count())
+ return;
+
+ QWidget *widget = this->widget(index);
+ if (WebView *tab = qobject_cast<WebView*>(widget))
+ tab->reload();
+}
+
+void TabBar::reloadTab()
+{
+ if (QAction *action = qobject_cast<QAction*>(sender())) {
+ int index = action->data().toInt();
+ emit reloadTab(index);
+ }
+}
+
+TabWidget::TabWidget(QWidget *parent)
+ : QTabWidget(parent)
+ , m_recentlyClosedTabsAction(0)
+ , m_newTabAction(0)
+ , m_closeTabAction(0)
+ , m_nextTabAction(0)
+ , m_previousTabAction(0)
+ , m_recentlyClosedTabsMenu(0)
+ , m_lineEditCompleter(0)
+ , m_lineEdits(0)
+ , m_tabBar(new TabBar(this))
+{
+ setElideMode(Qt::ElideRight);
+
+ connect(m_tabBar, SIGNAL(newTab()), this, SLOT(newTab()));
+ connect(m_tabBar, SIGNAL(closeTab(int)), this, SLOT(closeTab(int)));
+ connect(m_tabBar, SIGNAL(cloneTab(int)), this, SLOT(cloneTab(int)));
+ connect(m_tabBar, SIGNAL(closeOtherTabs(int)), this, SLOT(closeOtherTabs(int)));
+ connect(m_tabBar, SIGNAL(reloadTab(int)), this, SLOT(reloadTab(int)));
+ connect(m_tabBar, SIGNAL(reloadAllTabs()), this, SLOT(reloadAllTabs()));
+ connect(m_tabBar, SIGNAL(tabMoved(int,int)), this, SLOT(moveTab(int,int)));
+ setTabBar(m_tabBar);
+ setDocumentMode(true);
+
+ // Actions
+ m_newTabAction = new QAction(QIcon(QLatin1String(":addtab.png")), tr("New &Tab"), this);
+ m_newTabAction->setShortcuts(QKeySequence::AddTab);
+ m_newTabAction->setIconVisibleInMenu(false);
+ connect(m_newTabAction, SIGNAL(triggered()), this, SLOT(newTab()));
+
+ m_closeTabAction = new QAction(QIcon(QLatin1String(":closetab.png")), tr("&Close Tab"), this);
+ m_closeTabAction->setShortcuts(QKeySequence::Close);
+ m_closeTabAction->setIconVisibleInMenu(false);
+ connect(m_closeTabAction, SIGNAL(triggered()), this, SLOT(closeTab()));
+
+ m_nextTabAction = new QAction(tr("Show Next Tab"), this);
+ QList<QKeySequence> shortcuts;
+ shortcuts.append(QKeySequence(Qt::CTRL | Qt::Key_BraceRight));
+ shortcuts.append(QKeySequence(Qt::CTRL | Qt::Key_PageDown));
+ shortcuts.append(QKeySequence(Qt::CTRL | Qt::Key_BracketRight));
+ shortcuts.append(QKeySequence(Qt::CTRL | Qt::Key_Less));
+ m_nextTabAction->setShortcuts(shortcuts);
+ connect(m_nextTabAction, SIGNAL(triggered()), this, SLOT(nextTab()));
+
+ m_previousTabAction = new QAction(tr("Show Previous Tab"), this);
+ shortcuts.clear();
+ shortcuts.append(QKeySequence(Qt::CTRL | Qt::Key_BraceLeft));
+ shortcuts.append(QKeySequence(Qt::CTRL | Qt::Key_PageUp));
+ shortcuts.append(QKeySequence(Qt::CTRL | Qt::Key_BracketLeft));
+ shortcuts.append(QKeySequence(Qt::CTRL | Qt::Key_Greater));
+ m_previousTabAction->setShortcuts(shortcuts);
+ connect(m_previousTabAction, SIGNAL(triggered()), this, SLOT(previousTab()));
+
+ m_recentlyClosedTabsMenu = new QMenu(this);
+ connect(m_recentlyClosedTabsMenu, SIGNAL(aboutToShow()),
+ this, SLOT(aboutToShowRecentTabsMenu()));
+ connect(m_recentlyClosedTabsMenu, SIGNAL(triggered(QAction*)),
+ this, SLOT(aboutToShowRecentTriggeredAction(QAction*)));
+ m_recentlyClosedTabsAction = new QAction(tr("Recently Closed Tabs"), this);
+ m_recentlyClosedTabsAction->setMenu(m_recentlyClosedTabsMenu);
+ m_recentlyClosedTabsAction->setEnabled(false);
+
+ connect(this, SIGNAL(currentChanged(int)),
+ this, SLOT(currentChanged(int)));
+
+ m_lineEdits = new QStackedWidget(this);
+}
+
+void TabWidget::clear()
+{
+ // clear the recently closed tabs
+ m_recentlyClosedTabs.clear();
+ // clear the line edit history
+ for (int i = 0; i < m_lineEdits->count(); ++i) {
+ QLineEdit *qLineEdit = lineEdit(i);
+ qLineEdit->setText(qLineEdit->text());
+ }
+}
+
+void TabWidget::moveTab(int fromIndex, int toIndex)
+{
+ QWidget *lineEdit = m_lineEdits->widget(fromIndex);
+ m_lineEdits->removeWidget(lineEdit);
+ m_lineEdits->insertWidget(toIndex, lineEdit);
+}
+
+void TabWidget::addWebAction(QAction *action, QWebPage::WebAction webAction)
+{
+ if (!action)
+ return;
+ m_actions.append(new WebActionMapper(action, webAction, this));
+}
+
+void TabWidget::currentChanged(int index)
+{
+ WebView *webView = this->webView(index);
+ if (!webView)
+ return;
+
+ Q_ASSERT(m_lineEdits->count() == count());
+
+ WebView *oldWebView = this->webView(m_lineEdits->currentIndex());
+ if (oldWebView) {
+ disconnect(oldWebView, SIGNAL(statusBarMessage(QString)),
+ this, SIGNAL(showStatusBarMessage(QString)));
+ disconnect(oldWebView->page(), SIGNAL(linkHovered(QString,QString,QString)),
+ this, SIGNAL(linkHovered(QString)));
+ disconnect(oldWebView, SIGNAL(loadProgress(int)),
+ this, SIGNAL(loadProgress(int)));
+ }
+
+ connect(webView, SIGNAL(statusBarMessage(QString)),
+ this, SIGNAL(showStatusBarMessage(QString)));
+ connect(webView->page(), SIGNAL(linkHovered(QString,QString,QString)),
+ this, SIGNAL(linkHovered(QString)));
+ connect(webView, SIGNAL(loadProgress(int)),
+ this, SIGNAL(loadProgress(int)));
+
+ for (int i = 0; i < m_actions.count(); ++i) {
+ WebActionMapper *mapper = m_actions[i];
+ mapper->updateCurrent(webView->page());
+ }
+ emit setCurrentTitle(webView->title());
+ m_lineEdits->setCurrentIndex(index);
+ emit loadProgress(webView->progress());
+ emit showStatusBarMessage(webView->lastStatusBarText());
+ if (webView->url().isEmpty())
+ m_lineEdits->currentWidget()->setFocus();
+ else
+ webView->setFocus();
+}
+
+QAction *TabWidget::newTabAction() const
+{
+ return m_newTabAction;
+}
+
+QAction *TabWidget::closeTabAction() const
+{
+ return m_closeTabAction;
+}
+
+QAction *TabWidget::recentlyClosedTabsAction() const
+{
+ return m_recentlyClosedTabsAction;
+}
+
+QAction *TabWidget::nextTabAction() const
+{
+ return m_nextTabAction;
+}
+
+QAction *TabWidget::previousTabAction() const
+{
+ return m_previousTabAction;
+}
+
+QWidget *TabWidget::lineEditStack() const
+{
+ return m_lineEdits;
+}
+
+QLineEdit *TabWidget::currentLineEdit() const
+{
+ return lineEdit(m_lineEdits->currentIndex());
+}
+
+WebView *TabWidget::currentWebView() const
+{
+ return webView(currentIndex());
+}
+
+QLineEdit *TabWidget::lineEdit(int index) const
+{
+ UrlLineEdit *urlLineEdit = qobject_cast<UrlLineEdit*>(m_lineEdits->widget(index));
+ if (urlLineEdit)
+ return urlLineEdit->lineEdit();
+ return 0;
+}
+
+WebView *TabWidget::webView(int index) const
+{
+ QWidget *widget = this->widget(index);
+ if (WebView *webView = qobject_cast<WebView*>(widget)) {
+ return webView;
+ } else {
+ // optimization to delay creating the first webview
+ if (count() == 1) {
+ TabWidget *that = const_cast<TabWidget*>(this);
+ that->setUpdatesEnabled(false);
+ that->newTab();
+ that->closeTab(0);
+ that->setUpdatesEnabled(true);
+ return currentWebView();
+ }
+ }
+ return 0;
+}
+
+int TabWidget::webViewIndex(WebView *webView) const
+{
+ int index = indexOf(webView);
+ return index;
+}
+
+WebView *TabWidget::newTab(bool makeCurrent)
+{
+ // line edit
+ UrlLineEdit *urlLineEdit = new UrlLineEdit;
+ QLineEdit *lineEdit = urlLineEdit->lineEdit();
+ if (!m_lineEditCompleter && count() > 0) {
+ HistoryCompletionModel *completionModel = new HistoryCompletionModel(this);
+ completionModel->setSourceModel(BrowserApplication::historyManager()->historyFilterModel());
+ m_lineEditCompleter = new QCompleter(completionModel, this);
+ // Should this be in Qt by default?
+ QAbstractItemView *popup = m_lineEditCompleter->popup();
+ QListView *listView = qobject_cast<QListView*>(popup);
+ if (listView)
+ listView->setUniformItemSizes(true);
+ }
+ lineEdit->setCompleter(m_lineEditCompleter);
+ connect(lineEdit, SIGNAL(returnPressed()), this, SLOT(lineEditReturnPressed()));
+ m_lineEdits->addWidget(urlLineEdit);
+ m_lineEdits->setSizePolicy(lineEdit->sizePolicy());
+
+ // optimization to delay creating the more expensive WebView, history, etc
+ if (count() == 0) {
+ QWidget *emptyWidget = new QWidget;
+ QPalette p = emptyWidget->palette();
+ p.setColor(QPalette::Window, palette().color(QPalette::Base));
+ emptyWidget->setPalette(p);
+ emptyWidget->setAutoFillBackground(true);
+ disconnect(this, SIGNAL(currentChanged(int)),
+ this, SLOT(currentChanged(int)));
+ addTab(emptyWidget, tr("(Untitled)"));
+ connect(this, SIGNAL(currentChanged(int)),
+ this, SLOT(currentChanged(int)));
+ return 0;
+ }
+
+ // webview
+ WebView *webView = new WebView;
+ urlLineEdit->setWebView(webView);
+ connect(webView, SIGNAL(loadStarted()),
+ this, SLOT(webViewLoadStarted()));
+ connect(webView, SIGNAL(loadFinished(bool)),
+ this, SLOT(webViewIconChanged()));
+ connect(webView, SIGNAL(iconChanged()),
+ this, SLOT(webViewIconChanged()));
+ connect(webView, SIGNAL(titleChanged(QString)),
+ this, SLOT(webViewTitleChanged(QString)));
+ connect(webView, SIGNAL(urlChanged(QUrl)),
+ this, SLOT(webViewUrlChanged(QUrl)));
+ connect(webView->page(), SIGNAL(windowCloseRequested()),
+ this, SLOT(windowCloseRequested()));
+ connect(webView->page(), SIGNAL(geometryChangeRequested(QRect)),
+ this, SIGNAL(geometryChangeRequested(QRect)));
+ connect(webView->page(), SIGNAL(printRequested(QWebFrame*)),
+ this, SIGNAL(printRequested(QWebFrame*)));
+ connect(webView->page(), SIGNAL(menuBarVisibilityChangeRequested(bool)),
+ this, SIGNAL(menuBarVisibilityChangeRequested(bool)));
+ connect(webView->page(), SIGNAL(statusBarVisibilityChangeRequested(bool)),
+ this, SIGNAL(statusBarVisibilityChangeRequested(bool)));
+ connect(webView->page(), SIGNAL(toolBarVisibilityChangeRequested(bool)),
+ this, SIGNAL(toolBarVisibilityChangeRequested(bool)));
+ addTab(webView, tr("(Untitled)"));
+ if (makeCurrent)
+ setCurrentWidget(webView);
+
+ // webview actions
+ for (int i = 0; i < m_actions.count(); ++i) {
+ WebActionMapper *mapper = m_actions[i];
+ mapper->addChild(webView->page()->action(mapper->webAction()));
+ }
+
+ if (count() == 1)
+ currentChanged(currentIndex());
+ emit tabsChanged();
+ return webView;
+}
+
+void TabWidget::reloadAllTabs()
+{
+ for (int i = 0; i < count(); ++i) {
+ QWidget *tabWidget = widget(i);
+ if (WebView *tab = qobject_cast<WebView*>(tabWidget)) {
+ tab->reload();
+ }
+ }
+}
+
+void TabWidget::lineEditReturnPressed()
+{
+ if (QLineEdit *lineEdit = qobject_cast<QLineEdit*>(sender())) {
+ emit loadPage(lineEdit->text());
+ if (m_lineEdits->currentWidget() == lineEdit)
+ currentWebView()->setFocus();
+ }
+}
+
+void TabWidget::windowCloseRequested()
+{
+ WebPage *webPage = qobject_cast<WebPage*>(sender());
+ WebView *webView = qobject_cast<WebView*>(webPage->view());
+ int index = webViewIndex(webView);
+ if (index >= 0) {
+ if (count() == 1)
+ webView->webPage()->mainWindow()->close();
+ else
+ closeTab(index);
+ }
+}
+
+void TabWidget::closeOtherTabs(int index)
+{
+ if (-1 == index)
+ return;
+ for (int i = count() - 1; i > index; --i)
+ closeTab(i);
+ for (int i = index - 1; i >= 0; --i)
+ closeTab(i);
+}
+
+// When index is -1 index chooses the current tab
+void TabWidget::cloneTab(int index)
+{
+ if (index < 0)
+ index = currentIndex();
+ if (index < 0 || index >= count())
+ return;
+ WebView *tab = newTab(false);
+ tab->setUrl(webView(index)->url());
+}
+
+// When index is -1 index chooses the current tab
+void TabWidget::closeTab(int index)
+{
+ if (index < 0)
+ index = currentIndex();
+ if (index < 0 || index >= count())
+ return;
+
+ bool hasFocus = false;
+ if (WebView *tab = webView(index)) {
+ if (tab->isModified()) {
+ QMessageBox closeConfirmation(tab);
+ closeConfirmation.setWindowFlags(Qt::Sheet);
+ closeConfirmation.setWindowTitle(tr("Do you really want to close this page?"));
+ closeConfirmation.setInformativeText(tr("You have modified this page and when closing it you would lose the modification.\n"
+ "Do you really want to close this page?\n"));
+ closeConfirmation.setIcon(QMessageBox::Question);
+ closeConfirmation.addButton(QMessageBox::Yes);
+ closeConfirmation.addButton(QMessageBox::No);
+ closeConfirmation.setEscapeButton(QMessageBox::No);
+ if (closeConfirmation.exec() == QMessageBox::No)
+ return;
+ }
+ hasFocus = tab->hasFocus();
+
+ QWebSettings *globalSettings = QWebSettings::globalSettings();
+ if (!globalSettings->testAttribute(QWebSettings::PrivateBrowsingEnabled)) {
+ m_recentlyClosedTabsAction->setEnabled(true);
+ m_recentlyClosedTabs.prepend(tab->url());
+ if (m_recentlyClosedTabs.size() >= TabWidget::m_recentlyClosedTabsSize)
+ m_recentlyClosedTabs.removeLast();
+ }
+ }
+ QWidget *lineEdit = m_lineEdits->widget(index);
+ m_lineEdits->removeWidget(lineEdit);
+ lineEdit->deleteLater();
+ QWidget *webView = widget(index);
+ removeTab(index);
+ webView->deleteLater();
+ emit tabsChanged();
+ if (hasFocus && count() > 0)
+ currentWebView()->setFocus();
+ if (count() == 0)
+ emit lastTabClosed();
+}
+
+void TabWidget::webViewLoadStarted()
+{
+ WebView *webView = qobject_cast<WebView*>(sender());
+ int index = webViewIndex(webView);
+ if (-1 != index) {
+ QIcon icon(QLatin1String(":loading.gif"));
+ setTabIcon(index, icon);
+ }
+}
+
+void TabWidget::webViewIconChanged()
+{
+ WebView *webView = qobject_cast<WebView*>(sender());
+ int index = webViewIndex(webView);
+ if (-1 != index) {
+ QIcon icon = BrowserApplication::instance()->icon(webView->url());
+ setTabIcon(index, icon);
+ }
+}
+
+void TabWidget::webViewTitleChanged(const QString &title)
+{
+ WebView *webView = qobject_cast<WebView*>(sender());
+ int index = webViewIndex(webView);
+ if (-1 != index) {
+ setTabText(index, title);
+ }
+ if (currentIndex() == index)
+ emit setCurrentTitle(title);
+ BrowserApplication::historyManager()->updateHistoryItem(webView->url(), title);
+}
+
+void TabWidget::webViewUrlChanged(const QUrl &url)
+{
+ WebView *webView = qobject_cast<WebView*>(sender());
+ int index = webViewIndex(webView);
+ if (-1 != index) {
+ m_tabBar->setTabData(index, url);
+ }
+ emit tabsChanged();
+}
+
+void TabWidget::aboutToShowRecentTabsMenu()
+{
+ m_recentlyClosedTabsMenu->clear();
+ for (int i = 0; i < m_recentlyClosedTabs.count(); ++i) {
+ QAction *action = new QAction(m_recentlyClosedTabsMenu);
+ action->setData(m_recentlyClosedTabs.at(i));
+ QIcon icon = BrowserApplication::instance()->icon(m_recentlyClosedTabs.at(i));
+ action->setIcon(icon);
+ action->setText(m_recentlyClosedTabs.at(i).toString());
+ m_recentlyClosedTabsMenu->addAction(action);
+ }
+}
+
+void TabWidget::aboutToShowRecentTriggeredAction(QAction *action)
+{
+ QUrl url = action->data().toUrl();
+ loadUrlInCurrentTab(url);
+}
+
+void TabWidget::mouseDoubleClickEvent(QMouseEvent *event)
+{
+ if (!childAt(event->pos())
+ // Remove the line below when QTabWidget does not have a one pixel frame
+ && event->pos().y() < (tabBar()->y() + tabBar()->height())) {
+ newTab();
+ return;
+ }
+ QTabWidget::mouseDoubleClickEvent(event);
+}
+
+void TabWidget::contextMenuEvent(QContextMenuEvent *event)
+{
+ if (!childAt(event->pos())) {
+ m_tabBar->contextMenuRequested(event->pos());
+ return;
+ }
+ QTabWidget::contextMenuEvent(event);
+}
+
+void TabWidget::mouseReleaseEvent(QMouseEvent *event)
+{
+ if (event->button() == Qt::MidButton && !childAt(event->pos())
+ // Remove the line below when QTabWidget does not have a one pixel frame
+ && event->pos().y() < (tabBar()->y() + tabBar()->height())) {
+ QUrl url(QApplication::clipboard()->text(QClipboard::Selection));
+ if (!url.isEmpty() && url.isValid() && !url.scheme().isEmpty()) {
+ WebView *webView = newTab();
+ webView->setUrl(url);
+ }
+ }
+}
+
+void TabWidget::loadUrlInCurrentTab(const QUrl &url)
+{
+ WebView *webView = currentWebView();
+ if (webView) {
+ webView->loadUrl(url);
+ webView->setFocus();
+ }
+}
+
+void TabWidget::nextTab()
+{
+ int next = currentIndex() + 1;
+ if (next == count())
+ next = 0;
+ setCurrentIndex(next);
+}
+
+void TabWidget::previousTab()
+{
+ int next = currentIndex() - 1;
+ if (next < 0)
+ next = count() - 1;
+ setCurrentIndex(next);
+}
+
+static const qint32 TabWidgetMagic = 0xaa;
+
+QByteArray TabWidget::saveState() const
+{
+ int version = 1;
+ QByteArray data;
+ QDataStream stream(&data, QIODevice::WriteOnly);
+
+ stream << qint32(TabWidgetMagic);
+ stream << qint32(version);
+
+ QStringList tabs;
+ for (int i = 0; i < count(); ++i) {
+ if (WebView *tab = qobject_cast<WebView*>(widget(i))) {
+ tabs.append(tab->url().toString());
+ } else {
+ tabs.append(QString::null);
+ }
+ }
+ stream << tabs;
+ stream << currentIndex();
+ return data;
+}
+
+bool TabWidget::restoreState(const QByteArray &state)
+{
+ int version = 1;
+ QByteArray sd = state;
+ QDataStream stream(&sd, QIODevice::ReadOnly);
+ if (stream.atEnd())
+ return false;
+
+ qint32 marker;
+ qint32 v;
+ stream >> marker;
+ stream >> v;
+ if (marker != TabWidgetMagic || v != version)
+ return false;
+
+ QStringList openTabs;
+ stream >> openTabs;
+
+ for (int i = 0; i < openTabs.count(); ++i) {
+ if (i != 0)
+ newTab();
+ loadPage(openTabs.at(i));
+ }
+
+ int currentTab;
+ stream >> currentTab;
+ setCurrentIndex(currentTab);
+
+ return true;
+}
+
+WebActionMapper::WebActionMapper(QAction *root, QWebPage::WebAction webAction, QObject *parent)
+ : QObject(parent)
+ , m_currentParent(0)
+ , m_root(root)
+ , m_webAction(webAction)
+{
+ if (!m_root)
+ return;
+ connect(m_root, SIGNAL(triggered()), this, SLOT(rootTriggered()));
+ connect(root, SIGNAL(destroyed(QObject*)), this, SLOT(rootDestroyed()));
+ root->setEnabled(false);
+}
+
+void WebActionMapper::rootDestroyed()
+{
+ m_root = 0;
+}
+
+void WebActionMapper::currentDestroyed()
+{
+ updateCurrent(0);
+}
+
+void WebActionMapper::addChild(QAction *action)
+{
+ if (!action)
+ return;
+ connect(action, SIGNAL(changed()), this, SLOT(childChanged()));
+}
+
+QWebPage::WebAction WebActionMapper::webAction() const
+{
+ return m_webAction;
+}
+
+void WebActionMapper::rootTriggered()
+{
+ if (m_currentParent) {
+ QAction *gotoAction = m_currentParent->action(m_webAction);
+ gotoAction->trigger();
+ }
+}
+
+void WebActionMapper::childChanged()
+{
+ if (QAction *source = qobject_cast<QAction*>(sender())) {
+ if (m_root
+ && m_currentParent
+ && source->parent() == m_currentParent) {
+ m_root->setChecked(source->isChecked());
+ m_root->setEnabled(source->isEnabled());
+ }
+ }
+}
+
+void WebActionMapper::updateCurrent(QWebPage *currentParent)
+{
+ if (m_currentParent)
+ disconnect(m_currentParent, SIGNAL(destroyed(QObject*)),
+ this, SLOT(currentDestroyed()));
+
+ m_currentParent = currentParent;
+ if (!m_root)
+ return;
+ if (!m_currentParent) {
+ m_root->setEnabled(false);
+ m_root->setChecked(false);
+ return;
+ }
+ QAction *source = m_currentParent->action(m_webAction);
+ m_root->setChecked(source->isChecked());
+ m_root->setEnabled(source->isEnabled());
+ connect(m_currentParent, SIGNAL(destroyed(QObject*)),
+ this, SLOT(currentDestroyed()));
+}
diff --git a/examples/widgets/browser/tabwidget.h b/examples/widgets/browser/tabwidget.h
new file mode 100644
index 000000000..9a8f10ff1
--- /dev/null
+++ b/examples/widgets/browser/tabwidget.h
@@ -0,0 +1,223 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the demonstration applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef TABWIDGET_H
+#define TABWIDGET_H
+
+#include <QtWidgets/QTabBar>
+
+#include <QtWidgets/QShortcut>
+/*
+ Tab bar with a few more features such as a context menu and shortcuts
+ */
+class TabBar : public QTabBar
+{
+ Q_OBJECT
+
+signals:
+ void newTab();
+ void cloneTab(int index);
+ void closeTab(int index);
+ void closeOtherTabs(int index);
+ void reloadTab(int index);
+ void reloadAllTabs();
+ void tabMoveRequested(int fromIndex, int toIndex);
+
+public:
+ TabBar(QWidget *parent = 0);
+
+protected:
+ void mousePressEvent(QMouseEvent* event);
+ void mouseMoveEvent(QMouseEvent* event);
+
+private slots:
+ void selectTabAction();
+ void cloneTab();
+ void closeTab();
+ void closeOtherTabs();
+ void reloadTab();
+ void contextMenuRequested(const QPoint &position);
+
+private:
+ QList<QShortcut*> m_tabShortcuts;
+ friend class TabWidget;
+
+ QPoint m_dragStartPos;
+ int m_dragCurrentIndex;
+};
+
+#include <QWebPage>
+
+QT_BEGIN_NAMESPACE
+class QAction;
+QT_END_NAMESPACE
+class WebView;
+/*!
+ A proxy object that connects a single browser action
+ to one child webpage action at a time.
+
+ Example usage: used to keep the main window stop action in sync with
+ the current tabs webview's stop action.
+ */
+class WebActionMapper : public QObject
+{
+ Q_OBJECT
+
+public:
+ WebActionMapper(QAction *root, QWebPage::WebAction webAction, QObject *parent);
+ QWebPage::WebAction webAction() const;
+ void addChild(QAction *action);
+ void updateCurrent(QWebPage *currentParent);
+
+private slots:
+ void rootTriggered();
+ void childChanged();
+ void rootDestroyed();
+ void currentDestroyed();
+
+private:
+ QWebPage *m_currentParent;
+ QAction *m_root;
+ QWebPage::WebAction m_webAction;
+};
+
+#include <QtCore/QUrl>
+#include <QtWidgets/QTabWidget>
+QT_BEGIN_NAMESPACE
+class QCompleter;
+class QLineEdit;
+class QMenu;
+class QStackedWidget;
+QT_END_NAMESPACE
+/*!
+ TabWidget that contains WebViews and a stack widget of associated line edits.
+
+ Connects up the current tab's signals to this class's signal and uses WebActionMapper
+ to proxy the actions.
+ */
+class TabWidget : public QTabWidget
+{
+ Q_OBJECT
+
+signals:
+ // tab widget signals
+ void loadPage(const QString &url);
+ void tabsChanged();
+ void lastTabClosed();
+
+ // current tab signals
+ void setCurrentTitle(const QString &url);
+ void showStatusBarMessage(const QString &message);
+ void linkHovered(const QString &link);
+ void loadProgress(int progress);
+ void geometryChangeRequested(const QRect &geometry);
+ void menuBarVisibilityChangeRequested(bool visible);
+ void statusBarVisibilityChangeRequested(bool visible);
+ void toolBarVisibilityChangeRequested(bool visible);
+ void printRequested(QWebFrame *frame);
+
+public:
+ TabWidget(QWidget *parent = 0);
+ void clear();
+ void addWebAction(QAction *action, QWebPage::WebAction webAction);
+
+ QAction *newTabAction() const;
+ QAction *closeTabAction() const;
+ QAction *recentlyClosedTabsAction() const;
+ QAction *nextTabAction() const;
+ QAction *previousTabAction() const;
+
+ QWidget *lineEditStack() const;
+ QLineEdit *currentLineEdit() const;
+ WebView *currentWebView() const;
+ WebView *webView(int index) const;
+ QLineEdit *lineEdit(int index) const;
+ int webViewIndex(WebView *webView) const;
+
+ QByteArray saveState() const;
+ bool restoreState(const QByteArray &state);
+
+protected:
+ void mouseDoubleClickEvent(QMouseEvent *event);
+ void contextMenuEvent(QContextMenuEvent *event);
+ void mouseReleaseEvent(QMouseEvent *event);
+
+public slots:
+ void loadUrlInCurrentTab(const QUrl &url);
+ WebView *newTab(bool makeCurrent = true);
+ void cloneTab(int index = -1);
+ void closeTab(int index = -1);
+ void closeOtherTabs(int index);
+ void reloadTab(int index = -1);
+ void reloadAllTabs();
+ void nextTab();
+ void previousTab();
+
+private slots:
+ void currentChanged(int index);
+ void aboutToShowRecentTabsMenu();
+ void aboutToShowRecentTriggeredAction(QAction *action);
+ void webViewLoadStarted();
+ void webViewIconChanged();
+ void webViewTitleChanged(const QString &title);
+ void webViewUrlChanged(const QUrl &url);
+ void lineEditReturnPressed();
+ void windowCloseRequested();
+ void moveTab(int fromIndex, int toIndex);
+
+private:
+ QAction *m_recentlyClosedTabsAction;
+ QAction *m_newTabAction;
+ QAction *m_closeTabAction;
+ QAction *m_nextTabAction;
+ QAction *m_previousTabAction;
+
+ QMenu *m_recentlyClosedTabsMenu;
+ static const int m_recentlyClosedTabsSize = 10;
+ QList<QUrl> m_recentlyClosedTabs;
+ QList<WebActionMapper*> m_actions;
+
+ QCompleter *m_lineEditCompleter;
+ QStackedWidget *m_lineEdits;
+ TabBar *m_tabBar;
+};
+
+#endif // TABWIDGET_H
diff --git a/examples/widgets/browser/toolbarsearch.cpp b/examples/widgets/browser/toolbarsearch.cpp
new file mode 100644
index 000000000..415be66c4
--- /dev/null
+++ b/examples/widgets/browser/toolbarsearch.cpp
@@ -0,0 +1,163 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the demonstration applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "toolbarsearch.h"
+#include "autosaver.h"
+
+#include <QtCore/QSettings>
+#include <QtCore/QUrl>
+#include <QtCore/QUrlQuery>
+
+#include <QtWidgets/QCompleter>
+#include <QtWidgets/QMenu>
+#include <QtCore/QStringListModel>
+
+#include <QWebSettings>
+
+/*
+ ToolbarSearch is a very basic search widget that also contains a small history.
+ Searches are turned into urls that use Google to perform search
+ */
+ToolbarSearch::ToolbarSearch(QWidget *parent)
+ : SearchLineEdit(parent)
+ , m_autosaver(new AutoSaver(this))
+ , m_maxSavedSearches(10)
+ , m_stringListModel(new QStringListModel(this))
+{
+ QMenu *m = menu();
+ connect(m, SIGNAL(aboutToShow()), this, SLOT(aboutToShowMenu()));
+ connect(m, SIGNAL(triggered(QAction*)), this, SLOT(triggeredMenuAction(QAction*)));
+
+ QCompleter *completer = new QCompleter(m_stringListModel, this);
+ completer->setCompletionMode(QCompleter::InlineCompletion);
+ lineEdit()->setCompleter(completer);
+
+ connect(lineEdit(), SIGNAL(returnPressed()), SLOT(searchNow()));
+ setInactiveText(tr("Google"));
+ load();
+}
+
+ToolbarSearch::~ToolbarSearch()
+{
+ m_autosaver->saveIfNeccessary();
+}
+
+void ToolbarSearch::save()
+{
+ QSettings settings;
+ settings.beginGroup(QLatin1String("toolbarsearch"));
+ settings.setValue(QLatin1String("recentSearches"), m_stringListModel->stringList());
+ settings.setValue(QLatin1String("maximumSaved"), m_maxSavedSearches);
+ settings.endGroup();
+}
+
+void ToolbarSearch::load()
+{
+ QSettings settings;
+ settings.beginGroup(QLatin1String("toolbarsearch"));
+ QStringList list = settings.value(QLatin1String("recentSearches")).toStringList();
+ m_maxSavedSearches = settings.value(QLatin1String("maximumSaved"), m_maxSavedSearches).toInt();
+ m_stringListModel->setStringList(list);
+ settings.endGroup();
+}
+
+void ToolbarSearch::searchNow()
+{
+ QString searchText = lineEdit()->text();
+ QStringList newList = m_stringListModel->stringList();
+ if (newList.contains(searchText))
+ newList.removeAt(newList.indexOf(searchText));
+ newList.prepend(searchText);
+ if (newList.size() >= m_maxSavedSearches)
+ newList.removeLast();
+
+ QWebSettings *globalSettings = QWebSettings::globalSettings();
+ if (!globalSettings->testAttribute(QWebSettings::PrivateBrowsingEnabled)) {
+ m_stringListModel->setStringList(newList);
+ m_autosaver->changeOccurred();
+ }
+
+ QUrl url(QLatin1String("http://www.google.com/search"));
+ QUrlQuery urlQuery;
+ urlQuery.addQueryItem(QLatin1String("q"), searchText);
+ urlQuery.addQueryItem(QLatin1String("ie"), QLatin1String("UTF-8"));
+ urlQuery.addQueryItem(QLatin1String("oe"), QLatin1String("UTF-8"));
+ urlQuery.addQueryItem(QLatin1String("client"), QLatin1String("qtdemobrowser"));
+ url.setQuery(urlQuery);
+ emit search(url);
+}
+
+void ToolbarSearch::aboutToShowMenu()
+{
+ lineEdit()->selectAll();
+ QMenu *m = menu();
+ m->clear();
+ QStringList list = m_stringListModel->stringList();
+ if (list.isEmpty()) {
+ m->addAction(tr("No Recent Searches"));
+ return;
+ }
+
+ QAction *recent = m->addAction(tr("Recent Searches"));
+ recent->setEnabled(false);
+ for (int i = 0; i < list.count(); ++i) {
+ QString text = list.at(i);
+ m->addAction(text)->setData(text);
+ }
+ m->addSeparator();
+ m->addAction(tr("Clear Recent Searches"), this, SLOT(clear()));
+}
+
+void ToolbarSearch::triggeredMenuAction(QAction *action)
+{
+ QVariant v = action->data();
+ if (v.canConvert<QString>()) {
+ QString text = v.toString();
+ lineEdit()->setText(text);
+ searchNow();
+ }
+}
+
+void ToolbarSearch::clear()
+{
+ m_stringListModel->setStringList(QStringList());
+ m_autosaver->changeOccurred();;
+}
diff --git a/examples/widgets/browser/toolbarsearch.h b/examples/widgets/browser/toolbarsearch.h
new file mode 100644
index 000000000..1189785db
--- /dev/null
+++ b/examples/widgets/browser/toolbarsearch.h
@@ -0,0 +1,83 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the demonstration applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef TOOLBARSEARCH_H
+#define TOOLBARSEARCH_H
+
+#include "searchlineedit.h"
+
+QT_BEGIN_NAMESPACE
+class QUrl;
+class QAction;
+class QStringListModel;
+QT_END_NAMESPACE
+
+class AutoSaver;
+
+class ToolbarSearch : public SearchLineEdit
+{
+ Q_OBJECT
+
+signals:
+ void search(const QUrl &url);
+
+public:
+ ToolbarSearch(QWidget *parent = 0);
+ ~ToolbarSearch();
+
+public slots:
+ void clear();
+ void searchNow();
+
+private slots:
+ void save();
+ void aboutToShowMenu();
+ void triggeredMenuAction(QAction *action);
+
+private:
+ void load();
+
+ AutoSaver *m_autosaver;
+ int m_maxSavedSearches;
+ QStringListModel *m_stringListModel;
+};
+
+#endif // TOOLBARSEARCH_H
diff --git a/examples/widgets/browser/urllineedit.cpp b/examples/widgets/browser/urllineedit.cpp
new file mode 100644
index 000000000..47828d95f
--- /dev/null
+++ b/examples/widgets/browser/urllineedit.cpp
@@ -0,0 +1,342 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the demonstration applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "urllineedit.h"
+
+#include "browserapplication.h"
+#include "searchlineedit.h"
+#include "webview.h"
+
+#include <QtCore/QEvent>
+#include <QtCore/QMimeData>
+
+#include <QtWidgets/QApplication>
+#include <QtWidgets/QCompleter>
+#include <QtGui/QFocusEvent>
+#include <QtWidgets/QHBoxLayout>
+#include <QtWidgets/QLabel>
+#include <QtWidgets/QLineEdit>
+#include <QtGui/QDrag>
+#include <QtGui/QPainter>
+#include <QtWidgets/QStyle>
+#include <QtWidgets/QStyleOptionFrameV2>
+
+#include <QtCore/QDebug>
+
+ExLineEdit::ExLineEdit(QWidget *parent)
+ : QWidget(parent)
+ , m_leftWidget(0)
+ , m_lineEdit(new QLineEdit(this))
+ , m_clearButton(0)
+{
+ setFocusPolicy(m_lineEdit->focusPolicy());
+ setAttribute(Qt::WA_InputMethodEnabled);
+ setSizePolicy(m_lineEdit->sizePolicy());
+ setBackgroundRole(m_lineEdit->backgroundRole());
+ setMouseTracking(true);
+ setAcceptDrops(true);
+ setAttribute(Qt::WA_MacShowFocusRect, true);
+ QPalette p = m_lineEdit->palette();
+ setPalette(p);
+
+ // line edit
+ m_lineEdit->setFrame(false);
+ m_lineEdit->setFocusProxy(this);
+ m_lineEdit->setAttribute(Qt::WA_MacShowFocusRect, false);
+ QPalette clearPalette = m_lineEdit->palette();
+ clearPalette.setBrush(QPalette::Base, QBrush(Qt::transparent));
+ m_lineEdit->setPalette(clearPalette);
+
+ // clearButton
+ m_clearButton = new ClearButton(this);
+ connect(m_clearButton, SIGNAL(clicked()),
+ m_lineEdit, SLOT(clear()));
+ connect(m_lineEdit, SIGNAL(textChanged(QString)),
+ m_clearButton, SLOT(textChanged(QString)));
+}
+
+void ExLineEdit::setLeftWidget(QWidget *widget)
+{
+ m_leftWidget = widget;
+}
+
+QWidget *ExLineEdit::leftWidget() const
+{
+ return m_leftWidget;
+}
+
+void ExLineEdit::resizeEvent(QResizeEvent *event)
+{
+ Q_ASSERT(m_leftWidget);
+ updateGeometries();
+ QWidget::resizeEvent(event);
+}
+
+void ExLineEdit::updateGeometries()
+{
+ QStyleOptionFrameV2 panel;
+ initStyleOption(&panel);
+ QRect rect = style()->subElementRect(QStyle::SE_LineEditContents, &panel, this);
+
+ int height = rect.height();
+ int width = rect.width();
+
+ int m_leftWidgetHeight = m_leftWidget->height();
+ m_leftWidget->setGeometry(rect.x() + 2, rect.y() + (height - m_leftWidgetHeight)/2,
+ m_leftWidget->width(), m_leftWidget->height());
+
+ int clearButtonWidth = this->height();
+ m_lineEdit->setGeometry(m_leftWidget->x() + m_leftWidget->width(), 0,
+ width - clearButtonWidth - m_leftWidget->width(), this->height());
+
+ m_clearButton->setGeometry(this->width() - clearButtonWidth, 0,
+ clearButtonWidth, this->height());
+}
+
+void ExLineEdit::initStyleOption(QStyleOptionFrameV2 *option) const
+{
+ option->initFrom(this);
+ option->rect = contentsRect();
+ option->lineWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, option, this);
+ option->midLineWidth = 0;
+ option->state |= QStyle::State_Sunken;
+ if (m_lineEdit->isReadOnly())
+ option->state |= QStyle::State_ReadOnly;
+#ifdef QT_KEYPAD_NAVIGATION
+ if (hasEditFocus())
+ option->state |= QStyle::State_HasEditFocus;
+#endif
+ option->features = QStyleOptionFrameV2::None;
+}
+
+QSize ExLineEdit::sizeHint() const
+{
+ m_lineEdit->setFrame(true);
+ QSize size = m_lineEdit->sizeHint();
+ m_lineEdit->setFrame(false);
+ return size;
+}
+
+void ExLineEdit::focusInEvent(QFocusEvent *event)
+{
+ m_lineEdit->event(event);
+ QWidget::focusInEvent(event);
+}
+
+void ExLineEdit::focusOutEvent(QFocusEvent *event)
+{
+ m_lineEdit->event(event);
+
+ if (m_lineEdit->completer()) {
+ connect(m_lineEdit->completer(), SIGNAL(activated(QString)),
+ m_lineEdit, SLOT(setText(QString)));
+ connect(m_lineEdit->completer(), SIGNAL(highlighted(QString)),
+ m_lineEdit, SLOT(_q_completionHighlighted(QString)));
+ }
+ QWidget::focusOutEvent(event);
+}
+
+void ExLineEdit::keyPressEvent(QKeyEvent *event)
+{
+ m_lineEdit->event(event);
+}
+
+bool ExLineEdit::event(QEvent *event)
+{
+ if (event->type() == QEvent::ShortcutOverride)
+ return m_lineEdit->event(event);
+ return QWidget::event(event);
+}
+
+void ExLineEdit::paintEvent(QPaintEvent *)
+{
+ QPainter p(this);
+ QStyleOptionFrameV2 panel;
+ initStyleOption(&panel);
+ style()->drawPrimitive(QStyle::PE_PanelLineEdit, &panel, &p, this);
+}
+
+QVariant ExLineEdit::inputMethodQuery(Qt::InputMethodQuery property) const
+{
+ return m_lineEdit->inputMethodQuery(property);
+}
+
+void ExLineEdit::inputMethodEvent(QInputMethodEvent *e)
+{
+ m_lineEdit->event(e);
+}
+
+
+class UrlIconLabel : public QLabel
+{
+
+public:
+ UrlIconLabel(QWidget *parent);
+
+ WebView *m_webView;
+
+protected:
+ void mousePressEvent(QMouseEvent *event);
+ void mouseMoveEvent(QMouseEvent *event);
+
+private:
+ QPoint m_dragStartPos;
+
+};
+
+UrlIconLabel::UrlIconLabel(QWidget *parent)
+ : QLabel(parent)
+ , m_webView(0)
+{
+ setMinimumWidth(16);
+ setMinimumHeight(16);
+}
+
+void UrlIconLabel::mousePressEvent(QMouseEvent *event)
+{
+ if (event->button() == Qt::LeftButton)
+ m_dragStartPos = event->pos();
+ QLabel::mousePressEvent(event);
+}
+
+void UrlIconLabel::mouseMoveEvent(QMouseEvent *event)
+{
+ if (event->buttons() == Qt::LeftButton
+ && (event->pos() - m_dragStartPos).manhattanLength() > QApplication::startDragDistance()
+ && m_webView) {
+ QDrag *drag = new QDrag(this);
+ QMimeData *mimeData = new QMimeData;
+ mimeData->setText(QString::fromUtf8(m_webView->url().toEncoded()));
+ QList<QUrl> urls;
+ urls.append(m_webView->url());
+ mimeData->setUrls(urls);
+ drag->setMimeData(mimeData);
+ drag->exec();
+ }
+}
+
+UrlLineEdit::UrlLineEdit(QWidget *parent)
+ : ExLineEdit(parent)
+ , m_webView(0)
+ , m_iconLabel(0)
+{
+ // icon
+ m_iconLabel = new UrlIconLabel(this);
+ m_iconLabel->resize(16, 16);
+ setLeftWidget(m_iconLabel);
+ m_defaultBaseColor = palette().color(QPalette::Base);
+
+ webViewIconChanged();
+}
+
+void UrlLineEdit::setWebView(WebView *webView)
+{
+ Q_ASSERT(!m_webView);
+ m_webView = webView;
+ m_iconLabel->m_webView = webView;
+ connect(webView, SIGNAL(urlChanged(QUrl)),
+ this, SLOT(webViewUrlChanged(QUrl)));
+ connect(webView, SIGNAL(loadFinished(bool)),
+ this, SLOT(webViewIconChanged()));
+ connect(webView, SIGNAL(iconChanged()),
+ this, SLOT(webViewIconChanged()));
+ connect(webView, SIGNAL(loadProgress(int)),
+ this, SLOT(update()));
+}
+
+void UrlLineEdit::webViewUrlChanged(const QUrl &url)
+{
+ m_lineEdit->setText(QString::fromUtf8(url.toEncoded()));
+ m_lineEdit->setCursorPosition(0);
+}
+
+void UrlLineEdit::webViewIconChanged()
+{
+ QUrl url = (m_webView) ? m_webView->url() : QUrl();
+ QIcon icon = BrowserApplication::instance()->icon(url);
+ QPixmap pixmap(icon.pixmap(16, 16));
+ m_iconLabel->setPixmap(pixmap);
+}
+
+QLinearGradient UrlLineEdit::generateGradient(const QColor &color) const
+{
+ QLinearGradient gradient(0, 0, 0, height());
+ gradient.setColorAt(0, m_defaultBaseColor);
+ gradient.setColorAt(0.15, color.lighter(120));
+ gradient.setColorAt(0.5, color);
+ gradient.setColorAt(0.85, color.lighter(120));
+ gradient.setColorAt(1, m_defaultBaseColor);
+ return gradient;
+}
+
+void UrlLineEdit::focusOutEvent(QFocusEvent *event)
+{
+ if (m_lineEdit->text().isEmpty() && m_webView)
+ m_lineEdit->setText(QString::fromUtf8(m_webView->url().toEncoded()));
+ ExLineEdit::focusOutEvent(event);
+}
+
+void UrlLineEdit::paintEvent(QPaintEvent *event)
+{
+ QPalette p = palette();
+ if (m_webView && m_webView->url().scheme() == QLatin1String("https")) {
+ QColor lightYellow(248, 248, 210);
+ p.setBrush(QPalette::Base, generateGradient(lightYellow));
+ } else {
+ p.setBrush(QPalette::Base, m_defaultBaseColor);
+ }
+ setPalette(p);
+ ExLineEdit::paintEvent(event);
+
+ QPainter painter(this);
+ QStyleOptionFrameV2 panel;
+ initStyleOption(&panel);
+ QRect backgroundRect = style()->subElementRect(QStyle::SE_LineEditContents, &panel, this);
+ if (m_webView && !hasFocus()) {
+ int progress = m_webView->progress();
+ QColor loadingColor = QColor(116, 192, 250);
+ painter.setBrush(generateGradient(loadingColor));
+ painter.setPen(Qt::transparent);
+ int mid = backgroundRect.width() / 100 * progress;
+ QRect progressRect(backgroundRect.x(), backgroundRect.y(), mid, backgroundRect.height());
+ painter.drawRect(progressRect);
+ }
+}
diff --git a/examples/widgets/browser/urllineedit.h b/examples/widgets/browser/urllineedit.h
new file mode 100644
index 000000000..c30f40b57
--- /dev/null
+++ b/examples/widgets/browser/urllineedit.h
@@ -0,0 +1,114 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the demonstration applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef URLLINEEDIT_H
+#define URLLINEEDIT_H
+
+#include <QtCore/QUrl>
+#include <QtWidgets/QWidget>
+#include <QtWidgets/QStyleOptionFrame>
+
+QT_BEGIN_NAMESPACE
+class QLineEdit;
+QT_END_NAMESPACE
+
+class ClearButton;
+class ExLineEdit : public QWidget
+{
+ Q_OBJECT
+
+public:
+ ExLineEdit(QWidget *parent = 0);
+
+ inline QLineEdit *lineEdit() const { return m_lineEdit; }
+
+ void setLeftWidget(QWidget *widget);
+ QWidget *leftWidget() const;
+
+ QSize sizeHint() const;
+
+ QVariant inputMethodQuery(Qt::InputMethodQuery property) const;
+protected:
+ void focusInEvent(QFocusEvent *event);
+ void focusOutEvent(QFocusEvent *event);
+ void keyPressEvent(QKeyEvent *event);
+ void paintEvent(QPaintEvent *event);
+ void resizeEvent(QResizeEvent *event);
+ void inputMethodEvent(QInputMethodEvent *e);
+ bool event(QEvent *event);
+
+protected:
+ void updateGeometries();
+ void initStyleOption(QStyleOptionFrameV2 *option) const;
+
+ QWidget *m_leftWidget;
+ QLineEdit *m_lineEdit;
+ ClearButton *m_clearButton;
+};
+
+class UrlIconLabel;
+class WebView;
+class UrlLineEdit : public ExLineEdit
+{
+ Q_OBJECT
+
+public:
+ UrlLineEdit(QWidget *parent = 0);
+ void setWebView(WebView *webView);
+
+protected:
+ void paintEvent(QPaintEvent *event);
+ void focusOutEvent(QFocusEvent *event);
+
+private slots:
+ void webViewUrlChanged(const QUrl &url);
+ void webViewIconChanged();
+
+private:
+ QLinearGradient generateGradient(const QColor &color) const;
+ WebView *m_webView;
+ UrlIconLabel *m_iconLabel;
+ QColor m_defaultBaseColor;
+
+};
+
+
+#endif // URLLINEEDIT_H
diff --git a/examples/widgets/browser/webview.cpp b/examples/widgets/browser/webview.cpp
new file mode 100644
index 000000000..be6f7de53
--- /dev/null
+++ b/examples/widgets/browser/webview.cpp
@@ -0,0 +1,315 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the demonstration applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "browserapplication.h"
+#include "browsermainwindow.h"
+#include "cookiejar.h"
+#include "downloadmanager.h"
+#include "networkaccessmanager.h"
+#include "tabwidget.h"
+#include "webview.h"
+
+#include <QtGui/QClipboard>
+#include <QtWidgets/QMenu>
+#include <QtWidgets/QMessageBox>
+#include <QtGui/QMouseEvent>
+
+#include <QWebHitTestResult>
+
+#ifndef QT_NO_UITOOLS
+#include <QtUiTools/QUiLoader>
+#endif //QT_NO_UITOOLS
+
+#include <QtCore/QDebug>
+#include <QtCore/QBuffer>
+
+WebPage::WebPage(QObject *parent)
+ : QWebPage(parent)
+ , m_keyboardModifiers(Qt::NoModifier)
+ , m_pressedButtons(Qt::NoButton)
+ , m_openInNewTab(false)
+{
+ setNetworkAccessManager(BrowserApplication::networkAccessManager());
+ connect(this, SIGNAL(unsupportedContent(QNetworkReply*)),
+ this, SLOT(handleUnsupportedContent(QNetworkReply*)));
+}
+
+BrowserMainWindow *WebPage::mainWindow()
+{
+ QObject *w = this->parent();
+ while (w) {
+ if (BrowserMainWindow *mw = qobject_cast<BrowserMainWindow*>(w))
+ return mw;
+ w = w->parent();
+ }
+ return BrowserApplication::instance()->mainWindow();
+}
+
+bool WebPage::acceptNavigationRequest(QWebFrame *frame, const QNetworkRequest &request, NavigationType type)
+{
+ // ctrl open in new tab
+ // ctrl-shift open in new tab and select
+ // ctrl-alt open in new window
+ if (type == QWebPage::NavigationTypeLinkClicked
+ && (m_keyboardModifiers & Qt::ControlModifier
+ || m_pressedButtons == Qt::MidButton)) {
+ bool newWindow = (m_keyboardModifiers & Qt::AltModifier);
+ WebView *webView;
+ if (newWindow) {
+ BrowserApplication::instance()->newMainWindow();
+ BrowserMainWindow *newMainWindow = BrowserApplication::instance()->mainWindow();
+ webView = newMainWindow->currentTab();
+ newMainWindow->raise();
+ newMainWindow->activateWindow();
+ webView->setFocus();
+ } else {
+ bool selectNewTab = (m_keyboardModifiers & Qt::ShiftModifier);
+ webView = mainWindow()->tabWidget()->newTab(selectNewTab);
+ }
+ webView->load(request);
+ m_keyboardModifiers = Qt::NoModifier;
+ m_pressedButtons = Qt::NoButton;
+ return false;
+ }
+ if (frame == mainFrame()) {
+ m_loadingUrl = request.url();
+ emit loadingUrl(m_loadingUrl);
+ }
+ return QWebPage::acceptNavigationRequest(frame, request, type);
+}
+
+QWebPage *WebPage::createWindow(QWebPage::WebWindowType type)
+{
+ Q_UNUSED(type);
+ if (m_keyboardModifiers & Qt::ControlModifier || m_pressedButtons == Qt::MidButton)
+ m_openInNewTab = true;
+ if (m_openInNewTab) {
+ m_openInNewTab = false;
+ return mainWindow()->tabWidget()->newTab()->page();
+ }
+ BrowserApplication::instance()->newMainWindow();
+ BrowserMainWindow *mainWindow = BrowserApplication::instance()->mainWindow();
+ return mainWindow->currentTab()->page();
+}
+
+#if !defined(QT_NO_UITOOLS)
+QObject *WebPage::createPlugin(const QString &classId, const QUrl &url, const QStringList &paramNames, const QStringList &paramValues)
+{
+ Q_UNUSED(url);
+ Q_UNUSED(paramNames);
+ Q_UNUSED(paramValues);
+ QUiLoader loader;
+ return loader.createWidget(classId, view());
+}
+#endif // !defined(QT_NO_UITOOLS)
+
+void WebPage::handleUnsupportedContent(QNetworkReply *reply)
+{
+ QString errorString = reply->errorString();
+
+ if (m_loadingUrl != reply->url()) {
+ // sub resource of this page
+ qWarning() << "Resource" << reply->url().toEncoded() << "has unknown Content-Type, will be ignored.";
+ reply->deleteLater();
+ return;
+ }
+
+ if (reply->error() == QNetworkReply::NoError && !reply->header(QNetworkRequest::ContentTypeHeader).isValid()) {
+ errorString = "Unknown Content-Type";
+ }
+
+ QFile file(QLatin1String(":/notfound.html"));
+ bool isOpened = file.open(QIODevice::ReadOnly);
+ Q_ASSERT(isOpened);
+ Q_UNUSED(isOpened)
+
+ QString title = tr("Error loading page: %1").arg(reply->url().toString());
+ QString html = QString(QLatin1String(file.readAll()))
+ .arg(title)
+ .arg(errorString)
+ .arg(reply->url().toString());
+
+ QBuffer imageBuffer;
+ imageBuffer.open(QBuffer::ReadWrite);
+ QIcon icon = view()->style()->standardIcon(QStyle::SP_MessageBoxWarning, 0, view());
+ QPixmap pixmap = icon.pixmap(QSize(32,32));
+ if (pixmap.save(&imageBuffer, "PNG")) {
+ html.replace(QLatin1String("IMAGE_BINARY_DATA_HERE"),
+ QString(QLatin1String(imageBuffer.buffer().toBase64())));
+ }
+
+ QList<QWebFrame*> frames;
+ frames.append(mainFrame());
+ while (!frames.isEmpty()) {
+ QWebFrame *frame = frames.takeFirst();
+ if (frame->url() == reply->url()) {
+ frame->setHtml(html, reply->url());
+ return;
+ }
+ QList<QWebFrame *> children = frame->childFrames();
+ foreach (QWebFrame *frame, children)
+ frames.append(frame);
+ }
+ if (m_loadingUrl == reply->url()) {
+ mainFrame()->setHtml(html, reply->url());
+ }
+}
+
+
+WebView::WebView(QWidget* parent)
+ : QWebView(parent)
+ , m_progress(0)
+ , m_page(new WebPage(this))
+{
+ setPage(m_page);
+ connect(page(), SIGNAL(statusBarMessage(QString)),
+ SLOT(setStatusBarText(QString)));
+ connect(this, SIGNAL(loadProgress(int)),
+ this, SLOT(setProgress(int)));
+ connect(this, SIGNAL(loadFinished(bool)),
+ this, SLOT(loadFinished()));
+ connect(page(), SIGNAL(loadingUrl(QUrl)),
+ this, SIGNAL(urlChanged(QUrl)));
+ connect(page(), SIGNAL(downloadRequested(QNetworkRequest)),
+ this, SLOT(downloadRequested(QNetworkRequest)));
+ page()->setForwardUnsupportedContent(true);
+
+}
+
+void WebView::contextMenuEvent(QContextMenuEvent *event)
+{
+ QWebHitTestResult r = page()->mainFrame()->hitTestContent(event->pos());
+ if (!r.linkUrl().isEmpty()) {
+ QMenu menu(this);
+ menu.addAction(pageAction(QWebPage::OpenLinkInNewWindow));
+ menu.addAction(tr("Open in New Tab"), this, SLOT(openLinkInNewTab()));
+ menu.addSeparator();
+ menu.addAction(pageAction(QWebPage::DownloadLinkToDisk));
+ // Add link to bookmarks...
+ menu.addSeparator();
+ menu.addAction(pageAction(QWebPage::CopyLinkToClipboard));
+ if (page()->settings()->testAttribute(QWebSettings::DeveloperExtrasEnabled))
+ menu.addAction(pageAction(QWebPage::InspectElement));
+ menu.exec(mapToGlobal(event->pos()));
+ return;
+ }
+ QWebView::contextMenuEvent(event);
+}
+
+void WebView::wheelEvent(QWheelEvent *event)
+{
+ if (QApplication::keyboardModifiers() & Qt::ControlModifier) {
+ int numDegrees = event->delta() / 8;
+ int numSteps = numDegrees / 15;
+ setTextSizeMultiplier(textSizeMultiplier() + numSteps * 0.1);
+ event->accept();
+ return;
+ }
+ QWebView::wheelEvent(event);
+}
+
+void WebView::openLinkInNewTab()
+{
+ m_page->m_openInNewTab = true;
+ pageAction(QWebPage::OpenLinkInNewWindow)->trigger();
+}
+
+void WebView::setProgress(int progress)
+{
+ m_progress = progress;
+}
+
+void WebView::loadFinished()
+{
+ if (100 != m_progress) {
+ qWarning() << "Received finished signal while progress is still:" << progress()
+ << "Url:" << url();
+ }
+ m_progress = 0;
+}
+
+void WebView::loadUrl(const QUrl &url)
+{
+ m_initialUrl = url;
+ load(url);
+}
+
+QString WebView::lastStatusBarText() const
+{
+ return m_statusBarText;
+}
+
+QUrl WebView::url() const
+{
+ QUrl url = QWebView::url();
+ if (!url.isEmpty())
+ return url;
+
+ return m_initialUrl;
+}
+
+void WebView::mousePressEvent(QMouseEvent *event)
+{
+ m_page->m_pressedButtons = event->buttons();
+ m_page->m_keyboardModifiers = event->modifiers();
+ QWebView::mousePressEvent(event);
+}
+
+void WebView::mouseReleaseEvent(QMouseEvent *event)
+{
+ QWebView::mouseReleaseEvent(event);
+ if (!event->isAccepted() && (m_page->m_pressedButtons & Qt::MidButton)) {
+ QUrl url(QApplication::clipboard()->text(QClipboard::Selection));
+ if (!url.isEmpty() && url.isValid() && !url.scheme().isEmpty()) {
+ setUrl(url);
+ }
+ }
+}
+
+void WebView::setStatusBarText(const QString &string)
+{
+ m_statusBarText = string;
+}
+
+void WebView::downloadRequested(const QNetworkRequest &request)
+{
+ BrowserApplication::downloadManager()->download(request);
+}
diff --git a/examples/widgets/browser/webview.h b/examples/widgets/browser/webview.h
new file mode 100644
index 000000000..4fd04aca9
--- /dev/null
+++ b/examples/widgets/browser/webview.h
@@ -0,0 +1,119 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the demonstration applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef WEBVIEW_H
+#define WEBVIEW_H
+
+#include <QWebView>
+
+QT_BEGIN_NAMESPACE
+class QAuthenticator;
+class QMouseEvent;
+class QNetworkProxy;
+class QNetworkReply;
+class QSslError;
+QT_END_NAMESPACE
+
+class BrowserMainWindow;
+class WebPage : public QWebPage {
+ Q_OBJECT
+
+signals:
+ void loadingUrl(const QUrl &url);
+
+public:
+ WebPage(QObject *parent = 0);
+ BrowserMainWindow *mainWindow();
+
+protected:
+ bool acceptNavigationRequest(QWebFrame *frame, const QNetworkRequest &request, NavigationType type);
+ QWebPage *createWindow(QWebPage::WebWindowType type);
+#if !defined(QT_NO_UITOOLS)
+ QObject *createPlugin(const QString &classId, const QUrl &url, const QStringList &paramNames, const QStringList &paramValues);
+#endif
+
+private slots:
+ void handleUnsupportedContent(QNetworkReply *reply);
+
+private:
+ friend class WebView;
+
+ // set the webview mousepressedevent
+ Qt::KeyboardModifiers m_keyboardModifiers;
+ Qt::MouseButtons m_pressedButtons;
+ bool m_openInNewTab;
+ QUrl m_loadingUrl;
+};
+
+class WebView : public QWebView {
+ Q_OBJECT
+
+public:
+ WebView(QWidget *parent = 0);
+ WebPage *webPage() const { return m_page; }
+
+ void loadUrl(const QUrl &url);
+ QUrl url() const;
+
+ QString lastStatusBarText() const;
+ inline int progress() const { return m_progress; }
+
+protected:
+ void mousePressEvent(QMouseEvent *event);
+ void mouseReleaseEvent(QMouseEvent *event);
+ void contextMenuEvent(QContextMenuEvent *event);
+ void wheelEvent(QWheelEvent *event);
+
+private slots:
+ void setProgress(int progress);
+ void loadFinished();
+ void setStatusBarText(const QString &string);
+ void downloadRequested(const QNetworkRequest &request);
+ void openLinkInNewTab();
+
+private:
+ QString m_statusBarText;
+ QUrl m_initialUrl;
+ int m_progress;
+ WebPage *m_page;
+};
+
+#endif
diff --git a/examples/widgets/browser/xbel.cpp b/examples/widgets/browser/xbel.cpp
new file mode 100644
index 000000000..ae84a6a37
--- /dev/null
+++ b/examples/widgets/browser/xbel.cpp
@@ -0,0 +1,282 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the demonstration applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "xbel.h"
+
+#include <QtCore/QFile>
+
+BookmarkNode::BookmarkNode(BookmarkNode::Type type, BookmarkNode *parent) :
+ expanded(false)
+ , m_parent(parent)
+ , m_type(type)
+{
+ if (parent)
+ parent->add(this);
+}
+
+BookmarkNode::~BookmarkNode()
+{
+ if (m_parent)
+ m_parent->remove(this);
+ qDeleteAll(m_children);
+ m_parent = 0;
+ m_type = BookmarkNode::Root;
+}
+
+bool BookmarkNode::operator==(const BookmarkNode &other)
+{
+ if (url != other.url
+ || title != other.title
+ || desc != other.desc
+ || expanded != other.expanded
+ || m_type != other.m_type
+ || m_children.count() != other.m_children.count())
+ return false;
+
+ for (int i = 0; i < m_children.count(); ++i)
+ if (!((*(m_children[i])) == (*(other.m_children[i]))))
+ return false;
+ return true;
+}
+
+BookmarkNode::Type BookmarkNode::type() const
+{
+ return m_type;
+}
+
+void BookmarkNode::setType(Type type)
+{
+ m_type = type;
+}
+
+QList<BookmarkNode *> BookmarkNode::children() const
+{
+ return m_children;
+}
+
+BookmarkNode *BookmarkNode::parent() const
+{
+ return m_parent;
+}
+
+void BookmarkNode::add(BookmarkNode *child, int offset)
+{
+ Q_ASSERT(child->m_type != Root);
+ if (child->m_parent)
+ child->m_parent->remove(child);
+ child->m_parent = this;
+ if (-1 == offset)
+ offset = m_children.size();
+ m_children.insert(offset, child);
+}
+
+void BookmarkNode::remove(BookmarkNode *child)
+{
+ child->m_parent = 0;
+ m_children.removeAll(child);
+}
+
+
+XbelReader::XbelReader()
+{
+}
+
+BookmarkNode *XbelReader::read(const QString &fileName)
+{
+ QFile file(fileName);
+ if (!file.exists()) {
+ return new BookmarkNode(BookmarkNode::Root);
+ }
+ file.open(QFile::ReadOnly);
+ return read(&file);
+}
+
+BookmarkNode *XbelReader::read(QIODevice *device)
+{
+ BookmarkNode *root = new BookmarkNode(BookmarkNode::Root);
+ setDevice(device);
+ if (readNextStartElement()) {
+ QString version = attributes().value(QLatin1String("version")).toString();
+ if (name() == QLatin1String("xbel")
+ && (version.isEmpty() || version == QLatin1String("1.0"))) {
+ readXBEL(root);
+ } else {
+ raiseError(QObject::tr("The file is not an XBEL version 1.0 file."));
+ }
+ }
+ return root;
+}
+
+void XbelReader::readXBEL(BookmarkNode *parent)
+{
+ Q_ASSERT(isStartElement() && name() == QLatin1String("xbel"));
+
+ while (readNextStartElement()) {
+ if (name() == QLatin1String("folder"))
+ readFolder(parent);
+ else if (name() == QLatin1String("bookmark"))
+ readBookmarkNode(parent);
+ else if (name() == QLatin1String("separator"))
+ readSeparator(parent);
+ else
+ skipCurrentElement();
+ }
+}
+
+void XbelReader::readFolder(BookmarkNode *parent)
+{
+ Q_ASSERT(isStartElement() && name() == QLatin1String("folder"));
+
+ BookmarkNode *folder = new BookmarkNode(BookmarkNode::Folder, parent);
+ folder->expanded = (attributes().value(QLatin1String("folded")) == QLatin1String("no"));
+
+ while (readNextStartElement()) {
+ if (name() == QLatin1String("title"))
+ readTitle(folder);
+ else if (name() == QLatin1String("desc"))
+ readDescription(folder);
+ else if (name() == QLatin1String("folder"))
+ readFolder(folder);
+ else if (name() == QLatin1String("bookmark"))
+ readBookmarkNode(folder);
+ else if (name() == QLatin1String("separator"))
+ readSeparator(folder);
+ else
+ skipCurrentElement();
+ }
+}
+
+void XbelReader::readTitle(BookmarkNode *parent)
+{
+ Q_ASSERT(isStartElement() && name() == QLatin1String("title"));
+ parent->title = readElementText();
+}
+
+void XbelReader::readDescription(BookmarkNode *parent)
+{
+ Q_ASSERT(isStartElement() && name() == QLatin1String("desc"));
+ parent->desc = readElementText();
+}
+
+void XbelReader::readSeparator(BookmarkNode *parent)
+{
+ new BookmarkNode(BookmarkNode::Separator, parent);
+ // empty elements have a start and end element
+ readNext();
+}
+
+void XbelReader::readBookmarkNode(BookmarkNode *parent)
+{
+ Q_ASSERT(isStartElement() && name() == QLatin1String("bookmark"));
+ BookmarkNode *bookmark = new BookmarkNode(BookmarkNode::Bookmark, parent);
+ bookmark->url = attributes().value(QLatin1String("href")).toString();
+ while (readNextStartElement()) {
+ if (name() == QLatin1String("title"))
+ readTitle(bookmark);
+ else if (name() == QLatin1String("desc"))
+ readDescription(bookmark);
+ else
+ skipCurrentElement();
+ }
+ if (bookmark->title.isEmpty())
+ bookmark->title = QObject::tr("Unknown title");
+}
+
+
+XbelWriter::XbelWriter()
+{
+ setAutoFormatting(true);
+}
+
+bool XbelWriter::write(const QString &fileName, const BookmarkNode *root)
+{
+ QFile file(fileName);
+ if (!root || !file.open(QFile::WriteOnly))
+ return false;
+ return write(&file, root);
+}
+
+bool XbelWriter::write(QIODevice *device, const BookmarkNode *root)
+{
+ setDevice(device);
+
+ writeStartDocument();
+ writeDTD(QLatin1String("<!DOCTYPE xbel>"));
+ writeStartElement(QLatin1String("xbel"));
+ writeAttribute(QLatin1String("version"), QLatin1String("1.0"));
+ if (root->type() == BookmarkNode::Root) {
+ for (int i = 0; i < root->children().count(); ++i)
+ writeItem(root->children().at(i));
+ } else {
+ writeItem(root);
+ }
+
+ writeEndDocument();
+ return true;
+}
+
+void XbelWriter::writeItem(const BookmarkNode *parent)
+{
+ switch (parent->type()) {
+ case BookmarkNode::Folder:
+ writeStartElement(QLatin1String("folder"));
+ writeAttribute(QLatin1String("folded"), parent->expanded ? QLatin1String("no") : QLatin1String("yes"));
+ writeTextElement(QLatin1String("title"), parent->title);
+ for (int i = 0; i < parent->children().count(); ++i)
+ writeItem(parent->children().at(i));
+ writeEndElement();
+ break;
+ case BookmarkNode::Bookmark:
+ writeStartElement(QLatin1String("bookmark"));
+ if (!parent->url.isEmpty())
+ writeAttribute(QLatin1String("href"), parent->url);
+ writeTextElement(QLatin1String("title"), parent->title);
+ if (!parent->desc.isEmpty())
+ writeAttribute(QLatin1String("desc"), parent->desc);
+ writeEndElement();
+ break;
+ case BookmarkNode::Separator:
+ writeEmptyElement(QLatin1String("separator"));
+ break;
+ default:
+ break;
+ }
+}
diff --git a/examples/widgets/browser/xbel.h b/examples/widgets/browser/xbel.h
new file mode 100644
index 000000000..8ee9558ef
--- /dev/null
+++ b/examples/widgets/browser/xbel.h
@@ -0,0 +1,111 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the demonstration applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef XBEL_H
+#define XBEL_H
+
+#include <QtCore/QXmlStreamReader>
+#include <QtCore/QDateTime>
+
+class BookmarkNode
+{
+public:
+ enum Type {
+ Root,
+ Folder,
+ Bookmark,
+ Separator
+ };
+
+ BookmarkNode(Type type = Root, BookmarkNode *parent = 0);
+ ~BookmarkNode();
+ bool operator==(const BookmarkNode &other);
+
+ Type type() const;
+ void setType(Type type);
+ QList<BookmarkNode *> children() const;
+ BookmarkNode *parent() const;
+
+ void add(BookmarkNode *child, int offset = -1);
+ void remove(BookmarkNode *child);
+
+ QString url;
+ QString title;
+ QString desc;
+ bool expanded;
+
+private:
+ BookmarkNode *m_parent;
+ Type m_type;
+ QList<BookmarkNode *> m_children;
+
+};
+
+class XbelReader : public QXmlStreamReader
+{
+public:
+ XbelReader();
+ BookmarkNode *read(const QString &fileName);
+ BookmarkNode *read(QIODevice *device);
+
+private:
+ void readXBEL(BookmarkNode *parent);
+ void readTitle(BookmarkNode *parent);
+ void readDescription(BookmarkNode *parent);
+ void readSeparator(BookmarkNode *parent);
+ void readFolder(BookmarkNode *parent);
+ void readBookmarkNode(BookmarkNode *parent);
+};
+
+#include <QtCore/QXmlStreamWriter>
+
+class XbelWriter : public QXmlStreamWriter
+{
+public:
+ XbelWriter();
+ bool write(const QString &fileName, const BookmarkNode *root);
+ bool write(QIODevice *device, const BookmarkNode *root);
+
+private:
+ void writeItem(const BookmarkNode *parent);
+};
+
+#endif // XBEL_H