diff options
author | Andrea Diamantini <adjam7@gmail.com> | 2008-10-30 01:50:51 +0100 |
---|---|---|
committer | Andrea Diamantini <adjam7@gmail.com> | 2008-10-30 01:50:51 +0100 |
commit | 63b34dc6ccd32c9bc7c3d8c0137ff12530238bde (patch) | |
tree | f0f93e418c0556377487754c2e740f90497ffc51 | |
download | rekonq-63b34dc6ccd32c9bc7c3d8c0137ff12530238bde.tar.xz |
reKonq initial commit. Yeah!
68 files changed, 14449 insertions, 0 deletions
diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 00000000..187011aa --- /dev/null +++ b/AUTHORS @@ -0,0 +1 @@ +Andrea Diamantini adjam7@gmail.com diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..78d320d6 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,22 @@ +# Andrea Diamantini - adjam7 at gmail dot com +# rekonq project + +PROJECT( rekonq ) + +CMAKE_MINIMUM_REQUIRED(VERSION 2.6.0) + +SET( QT_MIN_VERSION 4.4.0) +FIND_PACKAGE( KDE4 REQUIRED ) + +INCLUDE(KDE4Defaults) +INCLUDE(MacroLibrary) + +INCLUDE_DIRECTORIES( ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} + ${KDE4_INCLUDES} + ${QT4_INCLUDES} + ${QT_QTNETWORK_INCLUDE_DIR} + ${QT_QTWEBKIT_INCLUDE_DIR} +) + +ADD_SUBDIRECTORY( src ) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 00000000..26283c2e --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,53 @@ +SET( rekonq_SRCS + 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 +) + +KDE4_ADD_UI_FILES( rekonq_SRCS + addbookmarkdialog.ui + bookmarks.ui + cookies.ui + cookiesexceptions.ui + downloaditem.ui + downloads.ui + history.ui + passworddialog.ui + proxy.ui + settings.ui + ) + +# include(${QT_USE_FILE}) + +ADD_DEFINITIONS( ${KDE4_DEFINITIONS} ) + +KDE4_ADD_EXECUTABLE( rekonq ${rekonq_SRCS} ) + +TARGET_LINK_LIBRARIES( rekonq + ${QT_LIBRARIES} + ${QT_QTNETWORK_LIBRARY} + ${QT_QTWEBKIT_LIBRARY} + ${KDE4_KDEUI_LIBS} + ${QT_QTUITOOLS_LIBRARY} +) + +INSTALL( TARGETS rekonq ${INSTALL_TARGETS_DEFAULT_ARGS} ) +# install(FILES kekonq.rc DESTINATION ${DATA_INSTALL_DIR}/rekonq) diff --git a/src/addbookmarkdialog.ui b/src/addbookmarkdialog.ui new file mode 100644 index 00000000..3460d7bb --- /dev/null +++ b/src/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/src/autosaver.cpp b/src/autosaver.cpp new file mode 100644 index 00000000..f8d088dd --- /dev/null +++ b/src/autosaver.cpp @@ -0,0 +1,90 @@ +/**************************************************************************** +** +** Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the demonstration applications of the Qt Toolkit. +** +** Commercial Usage +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License versions 2.0 or 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 GNU General Public Licensing requirements will be met: +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. In addition, as a special +** exception, Nokia gives you certain additional rights. These rights +** are described in the Nokia Qt GPL Exception version 1.3, included in +** the file GPL_EXCEPTION.txt in this package. +** +** Qt for Windows(R) Licensees +** As a special exception, Nokia, as the sole copyright holder for Qt +** Designer, grants users of the Qt/Eclipse Integration plug-in the +** right for the Qt/Eclipse Integration to link to functionality +** provided by Qt Designer and its related libraries. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** +****************************************************************************/ + +#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/src/autosaver.h b/src/autosaver.h new file mode 100644 index 00000000..162b3b6c --- /dev/null +++ b/src/autosaver.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the demonstration applications of the Qt Toolkit. +** +** Commercial Usage +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License versions 2.0 or 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 GNU General Public Licensing requirements will be met: +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. In addition, as a special +** exception, Nokia gives you certain additional rights. These rights +** are described in the Nokia Qt GPL Exception version 1.3, included in +** the file GPL_EXCEPTION.txt in this package. +** +** Qt for Windows(R) Licensees +** As a special exception, Nokia, as the sole copyright holder for Qt +** Designer, grants users of the Qt/Eclipse Integration plug-in the +** right for the Qt/Eclipse Integration to link to functionality +** provided by Qt Designer and its related libraries. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** +****************************************************************************/ + +#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/src/bookmarks.cpp b/src/bookmarks.cpp new file mode 100644 index 00000000..9b47747d --- /dev/null +++ b/src/bookmarks.cpp @@ -0,0 +1,983 @@ +/**************************************************************************** +** +** Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the demonstration applications of the Qt Toolkit. +** +** Commercial Usage +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License versions 2.0 or 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 GNU General Public Licensing requirements will be met: +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. In addition, as a special +** exception, Nokia gives you certain additional rights. These rights +** are described in the Nokia Qt GPL Exception version 1.3, included in +** the file GPL_EXCEPTION.txt in this package. +** +** Qt for Windows(R) Licensees +** As a special exception, Nokia, as the sole copyright holder for Qt +** Designer, grants users of the Qt/Eclipse Integration plug-in the +** right for the Qt/Eclipse Integration to link to functionality +** provided by Qt Designer and its related libraries. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** +****************************************************************************/ + +#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/QFileDialog> +#include <QtGui/QHeaderView> +#include <QtGui/QIcon> +#include <QtGui/QMessageBox> +#include <QtGui/QToolButton> + +#include <QtWebKit/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 = QDesktopServices::storageLocation(QDesktopServices::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 = QDesktopServices::storageLocation(QDesktopServices::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(const QModelIndex &)), + this, SLOT(activated(const 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(const QModelIndex&)), + this, SLOT(open())); + tree->setContextMenuPolicy(Qt::CustomContextMenu); + connect(tree, SIGNAL(customContextMenuRequested(const QPoint &)), + this, SLOT(customContextMenuRequested(const 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(const QModelIndex &, int, int)), this, SLOT(build())); + connect(m_bookmarksModel, SIGNAL(rowsRemoved(const QModelIndex &, int, int)), this, SLOT(build())); + connect(m_bookmarksModel, SIGNAL(dataChanged(const QModelIndex &, const 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(const QModelIndex &)), + this, SLOT(activated(const 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/src/bookmarks.h b/src/bookmarks.h new file mode 100644 index 00000000..768efd53 --- /dev/null +++ b/src/bookmarks.h @@ -0,0 +1,306 @@ +/**************************************************************************** +** +** Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the demonstration applications of the Qt Toolkit. +** +** Commercial Usage +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License versions 2.0 or 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 GNU General Public Licensing requirements will be met: +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. In addition, as a special +** exception, Nokia gives you certain additional rights. These rights +** are described in the Nokia Qt GPL Exception version 1.3, included in +** the file GPL_EXCEPTION.txt in this package. +** +** Qt for Windows(R) Licensees +** As a special exception, Nokia, as the sole copyright holder for Qt +** Designer, grants users of the Qt/Eclipse Integration plug-in the +** right for the Qt/Eclipse Integration to link to functionality +** provided by Qt Designer and its related libraries. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** +****************************************************************************/ + +#ifndef BOOKMARKS_H +#define BOOKMARKS_H + +#include <QtCore/QObject> +#include <QtCore/QAbstractItemModel> + +#include <QtGui/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 + */ +#include <QtGui/QIcon> +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 <QtGui/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 <QtGui/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/src/bookmarks.ui b/src/bookmarks.ui new file mode 100644 index 00000000..c893e941 --- /dev/null +++ b/src/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>&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/src/browser.icns b/src/browser.icns Binary files differnew file mode 100644 index 00000000..8cc82ba9 --- /dev/null +++ b/src/browser.icns diff --git a/src/browser.ico b/src/browser.ico Binary files differnew file mode 100644 index 00000000..f15627a1 --- /dev/null +++ b/src/browser.ico diff --git a/src/browser.pro b/src/browser.pro new file mode 100644 index 00000000..38ef03b5 --- /dev/null +++ b/src/browser.pro @@ -0,0 +1,87 @@ +TEMPLATE = app +TARGET = browser +QT += webkit network + +CONFIG += qt warn_on +contains(QT_BUILD_PARTS, tools): CONFIG += 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 +} + +# install +target.path = $$[QT_INSTALL_DEMOS]/browser +sources.files = $$SOURCES $$HEADERS $$RESOURCES $$FORMS *.plist *.icns *.ico *.rc *.pro *.html *.doc images htmls +sources.path = $$[QT_INSTALL_DEMOS]/browser +INSTALLS += target sources diff --git a/src/browser.rc b/src/browser.rc new file mode 100644 index 00000000..89a237ce --- /dev/null +++ b/src/browser.rc @@ -0,0 +1,2 @@ +IDI_ICON1 ICON DISCARDABLE "browser.ico" + diff --git a/src/browserapplication.cpp b/src/browserapplication.cpp new file mode 100644 index 00000000..f904ea1c --- /dev/null +++ b/src/browserapplication.cpp @@ -0,0 +1,439 @@ +/**************************************************************************** +** +** Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the demonstration applications of the Qt Toolkit. +** +** Commercial Usage +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License versions 2.0 or 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 GNU General Public Licensing requirements will be met: +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. In addition, as a special +** exception, Nokia gives you certain additional rights. These rights +** are described in the Nokia Qt GPL Exception version 1.3, included in +** the file GPL_EXCEPTION.txt in this package. +** +** Qt for Windows(R) Licensees +** As a special exception, Nokia, as the sole copyright holder for Qt +** Designer, grants users of the Qt/Eclipse Integration plug-in the +** right for the Qt/Eclipse Integration to link to functionality +** provided by Qt Designer and its related libraries. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** +****************************************************************************/ + +#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 <QtNetwork/QLocalServer> +#include <QtNetwork/QLocalSocket> +#include <QtNetwork/QNetworkProxy> + +#include <QtWebKit/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("Trolltech")); + 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); + } + } + + 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; + qDeleteAll(m_mainWindows); + 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 <QtGui/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 = QDesktopServices::storageLocation(QDesktopServices::DataLocation); + if (directory.isEmpty()) + directory = QDir::homePath() + QLatin1String("/.") + QCoreApplication::applicationName(); + QWebSettings::setIconDatabasePath(directory); + +// setWindowIcon(QIcon(QLatin1String(":browser.svg"))); // FIXME setICON + + 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 = qVariantValue<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 = qVariantValue<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); + + 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/src/browserapplication.h b/src/browserapplication.h new file mode 100644 index 00000000..19d02a47 --- /dev/null +++ b/src/browserapplication.h @@ -0,0 +1,115 @@ +/**************************************************************************** +** +** Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the demonstration applications of the Qt Toolkit. +** +** Commercial Usage +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License versions 2.0 or 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 GNU General Public Licensing requirements will be met: +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. In addition, as a special +** exception, Nokia gives you certain additional rights. These rights +** are described in the Nokia Qt GPL Exception version 1.3, included in +** the file GPL_EXCEPTION.txt in this package. +** +** Qt for Windows(R) Licensees +** As a special exception, Nokia, as the sole copyright holder for Qt +** Designer, grants users of the Qt/Eclipse Integration plug-in the +** right for the Qt/Eclipse Integration to link to functionality +** provided by Qt Designer and its related libraries. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** +****************************************************************************/ + +#ifndef BROWSERAPPLICATION_H +#define BROWSERAPPLICATION_H + +#include <QtGui/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/src/browsermainwindow.cpp b/src/browsermainwindow.cpp new file mode 100644 index 00000000..3d1617e4 --- /dev/null +++ b/src/browsermainwindow.cpp @@ -0,0 +1,969 @@ +/**************************************************************************** +** +** Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the demonstration applications of the Qt Toolkit. +** +** Commercial Usage +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License versions 2.0 or 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 GNU General Public Licensing requirements will be met: +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. In addition, as a special +** exception, Nokia gives you certain additional rights. These rights +** are described in the Nokia Qt GPL Exception version 1.3, included in +** the file GPL_EXCEPTION.txt in this package. +** +** Qt for Windows(R) Licensees +** As a special exception, Nokia, as the sole copyright holder for Qt +** Designer, grants users of the Qt/Eclipse Integration plug-in the +** right for the Qt/Eclipse Integration to link to functionality +** provided by Qt Designer and its related libraries. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** +****************************************************************************/ + +#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 <QtGui/QDesktopWidget> +#include <QtGui/QFileDialog> +#include <QtGui/QPlainTextEdit> +#include <QtGui/QPrintDialog> +#include <QtGui/QPrintPreviewDialog> +#include <QtGui/QPrinter> +#include <QtGui/QMenuBar> +#include <QtGui/QMessageBox> +#include <QtGui/QStatusBar> +#include <QtGui/QToolBar> +#include <QtGui/QInputDialog> + +#include <QtWebKit/QWebFrame> +#include <QtWebKit/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) +{ + 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(const QUrl&)), + m_tabWidget, SLOT(loadUrlInCurrentTab(const 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(const QString &)), + this, SLOT(loadPage(const QString &))); + connect(m_tabWidget, SIGNAL(setCurrentTitle(const QString &)), + this, SLOT(slotUpdateWindowTitle(const QString &))); + connect(m_tabWidget, SIGNAL(showStatusBarMessage(const QString&)), + statusBar(), SLOT(showMessage(const QString&))); + connect(m_tabWidget, SIGNAL(linkHovered(const QString&)), + statusBar(), SLOT(showMessage(const 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(const QRect &)), + this, SLOT(geometryChangeRequested(const 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() * 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("&Make Text Bigger"), this, SLOT(slotViewTextBigger()), QKeySequence(Qt::CTRL | Qt::Key_Plus)); + viewMenu->addAction(tr("&Make Text Normal"), this, SLOT(slotViewTextNormal()), QKeySequence(Qt::CTRL | Qt::Key_0)); + viewMenu->addAction(tr("&Make Text Smaller"), this, SLOT(slotViewTextSmaller()), QKeySequence(Qt::CTRL | Qt::Key_Minus)); + + 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(const QUrl&)), + m_tabWidget, SLOT(loadUrlInCurrentTab(const QUrl&))); + connect(historyMenu, SIGNAL(hovered(const QString&)), this, + SLOT(slotUpdateStatusbar(const 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(const QUrl&)), + m_tabWidget, SLOT(loadUrlInCurrentTab(const QUrl&))); + connect(bookmarksMenu, SIGNAL(hovered(const QString&)), + this, SLOT(slotUpdateStatusbar(const 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"))); +#ifndef Q_CC_MINGW + a = toolsMenu->addAction(tr("Enable Web &Inspector"), this, SLOT(slotToggleInspector(bool))); + a->setCheckable(true); +#endif + + 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(const QUrl&)), SLOT(loadUrl(const QUrl&))); + + m_chaseWidget = new ChaseWidget(this); + m_navigationBar->addWidget(m_chaseWidget); +} + +void BrowserMainWindow::slotShowBookmarksDialog() +{ + BookmarksDialog *dialog = new BookmarksDialog(this); + connect(dialog, SIGNAL(openUrl(const QUrl&)), + m_tabWidget, SLOT(loadUrlInCurrentTab(const 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(); +} + +QUrl BrowserMainWindow::guessUrlFromString(const QString &string) +{ + QString urlStr = string.trimmed(); + QRegExp test(QLatin1String("^[a-zA-Z]+\\:.*")); + + // Check if it looks like a qualified URL. Try parsing it and see. + bool hasSchema = test.exactMatch(urlStr); + if (hasSchema) { + QUrl url(urlStr, QUrl::TolerantMode); + if (url.isValid()) + return url; + } + + // Might be a file. + if (QFile::exists(urlStr)) { + QFileInfo info(urlStr); + return QUrl::fromLocalFile(info.absoluteFilePath()); + } + + // Might be a shorturl - try to detect the schema. + if (!hasSchema) { + int dotIndex = urlStr.indexOf(QLatin1Char('.')); + if (dotIndex != -1) { + QString prefix = urlStr.left(dotIndex).toLower(); + QString schema = (prefix == QLatin1String("ftp")) ? prefix : QLatin1String("http"); + QUrl url(schema + QLatin1String("://") + urlStr, QUrl::TolerantMode); + if (url.isValid()) + return url; + } + } + + // Fall back to QUrl's own tolerant parser. + QUrl url = QUrl(string, QUrl::TolerantMode); + + // finally for cases where the user just types in a hostname add http + if (url.scheme().isEmpty()) + url = QUrl(QLatin1String("http://") + string, QUrl::TolerantMode); + return url; +} + +void BrowserMainWindow::loadUrl(const QUrl &url) +{ + loadPage(url.toString()); +} + +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() +{ + if (!currentTab()) + return; + QPrintPreviewDialog *dialog = new QPrintPreviewDialog(this); + connect(dialog, SIGNAL(paintRequested(QPrinter *)), + currentTab(), SLOT(print(QPrinter *))); + dialog->exec(); +} + +void BrowserMainWindow::slotFilePrint() +{ + if (!currentTab()) + return; + printRequested(currentTab()->page()->mainFrame()); +} + +void BrowserMainWindow::printRequested(QWebFrame *frame) +{ + QPrinter printer; + QPrintDialog *dialog = new QPrintDialog(&printer, this); + dialog->setWindowTitle(tr("Print Document")); + if (dialog->exec() != QDialog::Accepted) + return; + frame->print(&printer); +} + +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 addded 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 tab 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::slotViewTextBigger() +{ + if (!currentTab()) + return; + currentTab()->setTextSizeMultiplier(currentTab()->textSizeMultiplier() + 0.1); +} + +void BrowserMainWindow::slotViewTextNormal() +{ + if (!currentTab()) + return; + currentTab()->setTextSizeMultiplier(1.0); +} + +void BrowserMainWindow::slotViewTextSmaller() +{ + if (!currentTab()) + return; + currentTab()->setTextSizeMultiplier(currentTab()->textSizeMultiplier() - 0.1); +} + +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://www.trolltech.com/")).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) +{ + if (!currentTab() || page.isEmpty()) + return; + + QUrl url = guessUrlFromString(page); + m_tabWidget->currentLineEdit()->setText(url.toString()); + m_tabWidget->loadUrlInCurrentTab(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/src/browsermainwindow.h b/src/browsermainwindow.h new file mode 100644 index 00000000..17f87056 --- /dev/null +++ b/src/browsermainwindow.h @@ -0,0 +1,164 @@ +/**************************************************************************** +** +** Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the demonstration applications of the Qt Toolkit. +** +** Commercial Usage +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License versions 2.0 or 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 GNU General Public Licensing requirements will be met: +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. In addition, as a special +** exception, Nokia gives you certain additional rights. These rights +** are described in the Nokia Qt GPL Exception version 1.3, included in +** the file GPL_EXCEPTION.txt in this package. +** +** Qt for Windows(R) Licensees +** As a special exception, Nokia, as the sole copyright holder for Qt +** Designer, grants users of the Qt/Eclipse Integration plug-in the +** right for the Qt/Eclipse Integration to link to functionality +** provided by Qt Designer and its related libraries. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** +****************************************************************************/ + +#ifndef BROWSERMAINWINDOW_H +#define BROWSERMAINWINDOW_H + +#include <QtGui/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: + static QUrl guessUrlFromString(const QString &url); + 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 slotViewTextBigger(); + void slotViewTextNormal(); + void slotViewTextSmaller(); + 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/src/chasewidget.cpp b/src/chasewidget.cpp new file mode 100644 index 00000000..ea2b0f8e --- /dev/null +++ b/src/chasewidget.cpp @@ -0,0 +1,138 @@ +/**************************************************************************** +** +** Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the demonstration applications of the Qt Toolkit. +** +** Commercial Usage +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License versions 2.0 or 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 GNU General Public Licensing requirements will be met: +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. In addition, as a special +** exception, Nokia gives you certain additional rights. These rights +** are described in the Nokia Qt GPL Exception version 1.3, included in +** the file GPL_EXCEPTION.txt in this package. +** +** Qt for Windows(R) Licensees +** As a special exception, Nokia, as the sole copyright holder for Qt +** Designer, grants users of the Qt/Eclipse Integration plug-in the +** right for the Qt/Eclipse Integration to link to functionality +** provided by Qt Designer and its related libraries. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** +****************************************************************************/ + +#include "chasewidget.h" + +#include <QtCore/QPoint> + +#include <QtGui/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/src/chasewidget.h b/src/chasewidget.h new file mode 100644 index 00000000..ce5b2dae --- /dev/null +++ b/src/chasewidget.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the demonstration applications of the Qt Toolkit. +** +** Commercial Usage +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License versions 2.0 or 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 GNU General Public Licensing requirements will be met: +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. In addition, as a special +** exception, Nokia gives you certain additional rights. These rights +** are described in the Nokia Qt GPL Exception version 1.3, included in +** the file GPL_EXCEPTION.txt in this package. +** +** Qt for Windows(R) Licensees +** As a special exception, Nokia, as the sole copyright holder for Qt +** Designer, grants users of the Qt/Eclipse Integration plug-in the +** right for the Qt/Eclipse Integration to link to functionality +** provided by Qt Designer and its related libraries. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** +****************************************************************************/ + +#ifndef CHASEWIDGET_H +#define CHASEWIDGET_H + +#include <QtGui/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/src/cookiejar.cpp b/src/cookiejar.cpp new file mode 100644 index 00000000..bed7258a --- /dev/null +++ b/src/cookiejar.cpp @@ -0,0 +1,728 @@ +/**************************************************************************** +** +** Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the demonstration applications of the Qt Toolkit. +** +** Commercial Usage +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License versions 2.0 or 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 GNU General Public Licensing requirements will be met: +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. In addition, as a special +** exception, Nokia gives you certain additional rights. These rights +** are described in the Nokia Qt GPL Exception version 1.3, included in +** the file GPL_EXCEPTION.txt in this package. +** +** Qt for Windows(R) Licensees +** As a special exception, Nokia, as the sole copyright holder for Qt +** Designer, grants users of the Qt/Eclipse Integration plug-in the +** right for the Qt/Eclipse Integration to link to functionality +** provided by Qt Designer and its related libraries. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** +****************************************************************************/ + +#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 <QtGui/QCompleter> +#include <QtGui/QDesktopServices> +#include <QtGui/QFont> +#include <QtGui/QFontMetrics> +#include <QtGui/QHeaderView> +#include <QtGui/QKeyEvent> +#include <QtGui/QSortFilterProxyModel> + +#include <QtWebKit/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(QDesktopServices::storageLocation(QDesktopServices::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 = QDesktopServices::storageLocation(QDesktopServices::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"), qVariantFromValue<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() +{ + reset(); +} + +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(const QString &)), + this, SLOT(textChanged(const 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->reset(); +} + +void CookiesExceptionsDialog::allow() +{ + if (domainLineEdit->text().isEmpty()) + return; + m_exceptionsModel->m_allowedCookies.append(domainLineEdit->text()); + m_cookieJar->setAllowedCookies(m_exceptionsModel->m_allowedCookies); + m_exceptionsModel->reset(); +} + +void CookiesExceptionsDialog::allowForSession() +{ + if (domainLineEdit->text().isEmpty()) + return; + m_exceptionsModel->m_sessionCookies.append(domainLineEdit->text()); + m_cookieJar->setAllowForSessionCookies(m_exceptionsModel->m_sessionCookies); + m_exceptionsModel->reset(); +} diff --git a/src/cookiejar.h b/src/cookiejar.h new file mode 100644 index 00000000..50d007f0 --- /dev/null +++ b/src/cookiejar.h @@ -0,0 +1,200 @@ +/**************************************************************************** +** +** Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the demonstration applications of the Qt Toolkit. +** +** Commercial Usage +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License versions 2.0 or 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 GNU General Public Licensing requirements will be met: +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. In addition, as a special +** exception, Nokia gives you certain additional rights. These rights +** are described in the Nokia Qt GPL Exception version 1.3, included in +** the file GPL_EXCEPTION.txt in this package. +** +** Qt for Windows(R) Licensees +** As a special exception, Nokia, as the sole copyright holder for Qt +** Designer, grants users of the Qt/Eclipse Integration plug-in the +** right for the Qt/Eclipse Integration to link to functionality +** provided by Qt Designer and its related libraries. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** +****************************************************************************/ + +#ifndef COOKIEJAR_H +#define COOKIEJAR_H + +#include <QtNetwork/QNetworkCookieJar> + +#include <QtCore/QAbstractItemModel> +#include <QtCore/QStringList> + +#include <QtGui/QDialog> +#include <QtGui/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/src/cookies.ui b/src/cookies.ui new file mode 100644 index 00000000..c4bccc54 --- /dev/null +++ b/src/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>&Remove</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="removeAllButton" > + <property name="text" > + <string>Remove &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/src/cookiesexceptions.ui b/src/cookiesexceptions.ui new file mode 100644 index 00000000..3d9ef624 --- /dev/null +++ b/src/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>&Remove</string> + </property> + </widget> + </item> + <item row="2" column="1" > + <widget class="QPushButton" name="removeAllButton" > + <property name="text" > + <string>Remove &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/src/data/addtab.png b/src/data/addtab.png Binary files differnew file mode 100644 index 00000000..20928fb4 --- /dev/null +++ b/src/data/addtab.png diff --git a/src/data/browser.svg b/src/data/browser.svg new file mode 100644 index 00000000..c42af2bc --- /dev/null +++ b/src/data/browser.svg @@ -0,0 +1,1598 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> +<svg + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + version="1.0" + width="277.35901" + height="277.43799" + viewBox="0 0 277.359 277.438" + id="Layer_1" + xml:space="preserve"><defs + id="defs285" /> +<pattern + x="64" + y="192.077" + width="69" + height="69" + patternUnits="userSpaceOnUse" + id="Polka_Dot_Pattern" + viewBox="2.125 -70.896 69 69" + overflow="visible"> + <g + id="g4"> + <polygon + fill="none" + points="71.125,-1.896 2.125,-1.896 2.125,-70.896 71.125,-70.896 " + id="polygon6" /> + <polygon + fill="#F7BC60" + points="71.125,-1.896 2.125,-1.896 2.125,-70.896 71.125,-70.896 " + id="polygon8" /> + <g + id="g10"> + <path + fill="#FFFFFF" + d="M61.772-71.653c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path12" /> + <path + fill="#FFFFFF" + d="M54.105-71.653c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path14" /> + <path + fill="#FFFFFF" + d="M46.439-71.653c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path16" /> + <path + fill="#FFFFFF" + d="M38.772-71.653c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path18" /> + <path + fill="#FFFFFF" + d="M31.105-71.653c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path20" /> + <path + fill="#FFFFFF" + d="M23.439-71.653c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path22" /> + <path + fill="#FFFFFF" + d="M15.772-71.653c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path24" /> + <path + fill="#FFFFFF" + d="M8.105-71.653c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path26" /> + <path + fill="#FFFFFF" + d="M0.439-71.653c0.018,0.072,0.008,0.127-0.026,0.19C0.361-71.362,0.3-71.4,0.248-71.335 c-0.051,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.07,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.038-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.051-0.12-0.064-0.187c-0.021-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.215,0.124-0.215,0.224c0.002,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path28" /> + </g> + <g + id="g30"> + <path + fill="#FFFFFF" + d="M69.439-71.653c0.018,0.072,0.008,0.127-0.026,0.19c-0.052,0.101-0.113,0.062-0.165,0.128 c-0.051,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.07,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.038-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.051-0.12-0.064-0.187c-0.021-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.215,0.124-0.215,0.224c0.002,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path32" /> + </g> + <path + fill="#FFFFFF" + d="M0.495-71.653c0.018,0.072,0.008,0.127-0.026,0.19c-0.052,0.101-0.113,0.062-0.165,0.128 c-0.051,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.07,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.038-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.051-0.12-0.064-0.187c-0.021-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.215,0.124-0.215,0.224C0.5-71.68,0.503-71.744,0.51-71.626 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path34" /> + <g + id="g36"> + <g + id="g38"> + <path + fill="#FFFFFF" + d="M69.439-64.001c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path40" /> + <path + fill="#FFFFFF" + d="M61.778-64.001c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path42" /> + <path + fill="#FFFFFF" + d="M54.118-64.001c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path44" /> + <path + fill="#FFFFFF" + d="M46.458-64.001c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path46" /> + <path + fill="#FFFFFF" + d="M38.797-64.001c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path48" /> + <path + fill="#FFFFFF" + d="M31.137-64.001c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path50" /> + <path + fill="#FFFFFF" + d="M23.477-64.001c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path52" /> + <path + fill="#FFFFFF" + d="M15.816-64.001c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path54" /> + <path + fill="#FFFFFF" + d="M8.156-64.001c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path56" /> + <path + fill="#FFFFFF" + d="M0.495-64.001c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143C2-61.45,2.217-61.397,2.391-61.46c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path58" /> + </g> + <g + id="g60"> + <path + fill="#FFFFFF" + d="M69.439-56.348c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path62" /> + <path + fill="#FFFFFF" + d="M61.778-56.348c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path64" /> + <path + fill="#FFFFFF" + d="M54.118-56.348c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path66" /> + <path + fill="#FFFFFF" + d="M46.458-56.348c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path68" /> + <path + fill="#FFFFFF" + d="M38.797-56.348c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path70" /> + <path + fill="#FFFFFF" + d="M31.137-56.348c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path72" /> + <path + fill="#FFFFFF" + d="M23.477-56.348c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path74" /> + <path + fill="#FFFFFF" + d="M15.816-56.348c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path76" /> + <path + fill="#FFFFFF" + d="M8.156-56.348c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path78" /> + <path + fill="#FFFFFF" + d="M0.495-56.348c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224C0.5-56.374,0.503-56.438,0.51-56.32 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path80" /> + </g> + <g + id="g82"> + <path + fill="#FFFFFF" + d="M69.439-48.695c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path84" /> + <path + fill="#FFFFFF" + d="M61.778-48.695c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path86" /> + <path + fill="#FFFFFF" + d="M54.118-48.695c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path88" /> + <path + fill="#FFFFFF" + d="M46.458-48.695c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path90" /> + <path + fill="#FFFFFF" + d="M38.797-48.695c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path92" /> + <path + fill="#FFFFFF" + d="M31.137-48.695c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path94" /> + <path + fill="#FFFFFF" + d="M23.477-48.695c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path96" /> + <path + fill="#FFFFFF" + d="M15.816-48.695c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path98" /> + <path + fill="#FFFFFF" + d="M8.156-48.695c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path100" /> + <path + fill="#FFFFFF" + d="M0.495-48.695c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path102" /> + </g> + <g + id="g104"> + <path + fill="#FFFFFF" + d="M69.439-41.042c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path106" /> + <path + fill="#FFFFFF" + d="M61.778-41.042c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path108" /> + <path + fill="#FFFFFF" + d="M54.118-41.042c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path110" /> + <path + fill="#FFFFFF" + d="M46.458-41.042c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path112" /> + <path + fill="#FFFFFF" + d="M38.797-41.042c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path114" /> + <path + fill="#FFFFFF" + d="M31.137-41.042c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path116" /> + <path + fill="#FFFFFF" + d="M23.477-41.042c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path118" /> + <path + fill="#FFFFFF" + d="M15.816-41.042c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path120" /> + <path + fill="#FFFFFF" + d="M8.156-41.042c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 C8.15-41.004,8.149-41.02,8.14-41.04" + id="path122" /> + <path + fill="#FFFFFF" + d="M0.495-41.042c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path124" /> + </g> + <g + id="g126"> + <path + fill="#FFFFFF" + d="M69.439-33.39c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path128" /> + <path + fill="#FFFFFF" + d="M61.778-33.39c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path130" /> + <path + fill="#FFFFFF" + d="M54.118-33.39c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path132" /> + <path + fill="#FFFFFF" + d="M46.458-33.39c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path134" /> + <path + fill="#FFFFFF" + d="M38.797-33.39c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path136" /> + <path + fill="#FFFFFF" + d="M31.137-33.39c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path138" /> + <path + fill="#FFFFFF" + d="M23.477-33.39c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path140" /> + <path + fill="#FFFFFF" + d="M15.816-33.39c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path142" /> + <path + fill="#FFFFFF" + d="M8.156-33.39c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path144" /> + <path + fill="#FFFFFF" + d="M0.495-33.39c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224C0.5-33.416,0.503-33.48,0.51-33.362 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path146" /> + </g> + <g + id="g148"> + <path + fill="#FFFFFF" + d="M69.439-25.736c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path150" /> + <path + fill="#FFFFFF" + d="M61.778-25.736c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path152" /> + <path + fill="#FFFFFF" + d="M54.118-25.736c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path154" /> + <path + fill="#FFFFFF" + d="M46.458-25.736c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path156" /> + <path + fill="#FFFFFF" + d="M38.797-25.736c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path158" /> + <path + fill="#FFFFFF" + d="M31.137-25.736c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path160" /> + <path + fill="#FFFFFF" + d="M23.477-25.736c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path162" /> + <path + fill="#FFFFFF" + d="M15.816-25.736c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path164" /> + <path + fill="#FFFFFF" + d="M8.156-25.736c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path166" /> + <path + fill="#FFFFFF" + d="M0.495-25.736c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path168" /> + </g> + <g + id="g170"> + <path + fill="#FFFFFF" + d="M69.439-18.084c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path172" /> + <path + fill="#FFFFFF" + d="M61.778-18.084c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path174" /> + <path + fill="#FFFFFF" + d="M54.118-18.084c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path176" /> + <path + fill="#FFFFFF" + d="M46.458-18.084c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path178" /> + <path + fill="#FFFFFF" + d="M38.797-18.084c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path180" /> + <path + fill="#FFFFFF" + d="M31.137-18.084c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path182" /> + <path + fill="#FFFFFF" + d="M23.477-18.084c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path184" /> + <path + fill="#FFFFFF" + d="M15.816-18.084c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path186" /> + <path + fill="#FFFFFF" + d="M8.156-18.084c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path188" /> + <path + fill="#FFFFFF" + d="M0.495-18.084c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224C0.5-18.11,0.503-18.175,0.51-18.057 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path190" /> + </g> + <g + id="g192"> + <path + fill="#FFFFFF" + d="M69.439-10.431c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362C69-9.692,69.159-9.523,69.154-9.4c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path194" /> + <path + fill="#FFFFFF" + d="M61.778-10.431c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path196" /> + <path + fill="#FFFFFF" + d="M54.118-10.431c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path198" /> + <path + fill="#FFFFFF" + d="M46.458-10.431c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path200" /> + <path + fill="#FFFFFF" + d="M38.797-10.431c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path202" /> + <path + fill="#FFFFFF" + d="M31.137-10.431c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path204" /> + <path + fill="#FFFFFF" + d="M23.477-10.431c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path206" /> + <path + fill="#FFFFFF" + d="M15.816-10.431c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053C17.933-7.969,17.839-8.227,18-8.34 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path208" /> + <path + fill="#FFFFFF" + d="M8.156-10.431c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 C7.915-10.05,7.866-9.836,7.886-9.75C7.717-9.692,7.876-9.523,7.871-9.4C7.868-9.351,7.83-9.295,7.826-9.239 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 C9.114-7.652,9.321-7.799,9.48-7.837c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path210" /> + <path + fill="#FFFFFF" + d="M0.495-10.431c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 C0.254-10.05,0.205-9.836,0.225-9.75C0.056-9.692,0.215-9.523,0.21-9.4c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37C0.33-8.671,0.501-8.456,0.668-8.325c0.19,0.148,0.365,0.572,0.608,0.631 C1.454-7.652,1.66-7.799,1.819-7.837C2-7.88,2.217-7.827,2.391-7.89c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46C3.477-8.933,3.471-8.995,3.5-9.071 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path212" /> + </g> + </g> + <g + id="g214"> + <path + fill="#FFFFFF" + d="M69.439-2.778c0.018,0.072,0.008,0.127-0.026,0.19C69.361-2.487,69.3-2.525,69.248-2.46 c-0.051,0.062-0.099,0.276-0.079,0.362C69-2.04,69.159-1.871,69.154-1.748c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 C70.397,0,70.604-0.146,70.763-0.185c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.07,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.038-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.051-0.12-0.064-0.187c-0.021-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.215,0.124-0.215,0.224c0.002,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path216" /> + <path + fill="#FFFFFF" + d="M61.778-2.778c0.018,0.072,0.007,0.127-0.026,0.19C61.7-2.487,61.64-2.525,61.587-2.46 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 C62.737,0,62.943-0.146,63.103-0.185c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224C61.915-3.117,61.78-3.02,61.781-2.92c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path218" /> + <path + fill="#FFFFFF" + d="M54.118-2.778c0.018,0.072,0.007,0.127-0.026,0.19C54.04-2.487,53.98-2.525,53.927-2.46 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 C55.077,0,55.283-0.146,55.442-0.185c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224C54.255-3.117,54.12-3.02,54.121-2.92c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path220" /> + <path + fill="#FFFFFF" + d="M46.458-2.778c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 C47.416,0,47.623-0.146,47.782-0.185c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224C46.594-3.117,46.459-3.02,46.46-2.92c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path222" /> + <path + fill="#FFFFFF" + d="M38.797-2.778c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 C39.756,0,39.962-0.146,40.122-0.185c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224C38.934-3.117,38.799-3.02,38.8-2.92c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path224" /> + <path + fill="#FFFFFF" + d="M31.137-2.778c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 C32.095,0,32.302-0.146,32.461-0.185c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224C31.273-3.117,31.139-3.02,31.14-2.92c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path226" /> + <path + fill="#FFFFFF" + d="M23.477-2.778c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 C24.435,0,24.642-0.146,24.801-0.185c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025" + id="path228" /> + <path + fill="#FFFFFF" + d="M15.816-2.778c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 C16.774,0,16.981-0.146,17.14-0.185c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 C15.81-2.74,15.809-2.756,15.8-2.776" + id="path230" /> + <path + fill="#FFFFFF" + d="M8.156-2.778c0.018,0.072,0.007,0.127-0.026,0.19C8.077-2.487,8.018-2.525,7.965-2.46 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35C7.868-1.698,7.83-1.643,7.826-1.587 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 C9.114,0,9.321-0.146,9.48-0.185c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789C8.954-3.54,8.847-3.448,8.692-3.367 c-0.17,0.088-0.139,0.166-0.318,0.224C8.292-3.117,8.158-3.02,8.159-2.92C8.16-2.805,8.164-2.869,8.17-2.751 C8.15-2.74,8.149-2.756,8.14-2.776" + id="path232" /> + <path + fill="#FFFFFF" + d="M0.495-2.778c0.018,0.072,0.008,0.127-0.026,0.19C0.417-2.487,0.356-2.525,0.304-2.46 C0.253-2.397,0.205-2.184,0.225-2.098C0.056-2.04,0.215-1.871,0.21-1.748c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37C0.33-1.019,0.501-0.804,0.668-0.673c0.19,0.148,0.365,0.572,0.608,0.631 C1.454,0,1.66-0.146,1.819-0.185C2-0.228,2.217-0.175,2.391-0.237c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.07,0.339-0.263,0.376-0.46C3.477-1.28,3.471-1.343,3.5-1.419 c0.038-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.051-0.12-0.064-0.187c-0.021-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789C1.293-3.54,1.187-3.448,1.031-3.367 c-0.17,0.088-0.139,0.166-0.318,0.224C0.632-3.117,0.498-3.02,0.498-2.92C0.5-2.805,0.503-2.869,0.51-2.751 C0.489-2.74,0.488-2.756,0.479-2.776" + id="path234" /> + </g> + </g> +</pattern> +<g + id="g236"> + <path + 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" + id="path238" + style="fill:#024c1c" /> + <path + 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" + id="path240" + style="fill:#66b036" /> + <g + id="g242"> + <path + 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" + id="path244" + style="fill:#ffffff" /> + </g> + <polygon + points="159.027,83.112 145.722,82.501 145.722,82.785 152.854,83.112 159.027,83.112 " + id="polygon246" + style="fill:#0a6333" /> + <path + 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" + id="path248" + style="fill:#024c1c" /> + <path + 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" + id="path250" + style="fill:#024c1c" /> + <path + 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" + id="path252" + style="fill:#0a6333" /> + <path + 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" + id="path254" + style="fill:#ffffff" /> + <path + 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" + id="path256" + style="fill:#024c1c" /> +</g> + +<image + xlink:href="data:image/png;base64,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 +DwAAAABJRU5ErkJggg== +" + x="98.21727" + y="99.928093" + width="177.23477" + height="175.07507" + id="image2749" /></svg>
\ No newline at end of file diff --git a/src/data/closetab.png b/src/data/closetab.png Binary files differnew file mode 100644 index 00000000..ab9d669e --- /dev/null +++ b/src/data/closetab.png diff --git a/src/data/data.qrc b/src/data/data.qrc new file mode 100644 index 00000000..c7d0294c --- /dev/null +++ b/src/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/src/data/defaultbookmarks.xbel b/src/data/defaultbookmarks.xbel new file mode 100644 index 00000000..f1c9253d --- /dev/null +++ b/src/data/defaultbookmarks.xbel @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE xbel> +<xbel version="1.0"> + <folder folded="yes"> + <title>Bookmarks Bar</title> + <bookmark href="http://trolltech.com/"> + <title>Trolltech.com</title> + </bookmark> + <bookmark href="http://webkit.org/"> + <title>WebKit.org</title> + </bookmark> + <bookmark href="http://doc.trolltech.com/"> + <title>Qt Documentation</title> + </bookmark> + <bookmark href="http://doc.trolltech.com/qq/"> + <title>Qt Quarterly</title> + </bookmark> + <bookmark href="http://labs.trolltech.com/"> + <title>Trolltech Labs</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://qtnode.net/"> + <title>qtnode</title> + </bookmark> + <bookmark href="http://xkcd.com/"> + <title>xkcd</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/src/data/defaulticon.png b/src/data/defaulticon.png Binary files differnew file mode 100644 index 00000000..01a0920c --- /dev/null +++ b/src/data/defaulticon.png diff --git a/src/data/history.png b/src/data/history.png Binary files differnew file mode 100644 index 00000000..552a1cbd --- /dev/null +++ b/src/data/history.png diff --git a/src/data/loading.gif b/src/data/loading.gif Binary files differnew file mode 100644 index 00000000..c1545eb0 --- /dev/null +++ b/src/data/loading.gif diff --git a/src/downloaditem.ui b/src/downloaditem.ui new file mode 100644 index 00000000..4a0a0fd9 --- /dev/null +++ b/src/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/src/downloadmanager.cpp b/src/downloadmanager.cpp new file mode 100644 index 00000000..d8f19c5e --- /dev/null +++ b/src/downloadmanager.cpp @@ -0,0 +1,575 @@ +/**************************************************************************** +** +** Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the demonstration applications of the Qt Toolkit. +** +** Commercial Usage +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License versions 2.0 or 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 GNU General Public Licensing requirements will be met: +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. In addition, as a special +** exception, Nokia gives you certain additional rights. These rights +** are described in the Nokia Qt GPL Exception version 1.3, included in +** the file GPL_EXCEPTION.txt in this package. +** +** Qt for Windows(R) Licensees +** As a special exception, Nokia, as the sole copyright holder for Qt +** Designer, grants users of the Qt/Eclipse Integration plug-in the +** right for the Qt/Eclipse Integration to link to functionality +** provided by Qt Designer and its related libraries. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** +****************************************************************************/ + +#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 <QtGui/QFileDialog> +#include <QtGui/QHeaderView> +#include <QtGui/QFileIconProvider> + +#include <QtCore/QDebug> + +#include <QtWebKit/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 = QDesktopServices::storageLocation(QDesktopServices::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 = QString(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/src/downloadmanager.h b/src/downloadmanager.h new file mode 100644 index 00000000..6f27622b --- /dev/null +++ b/src/downloadmanager.h @@ -0,0 +1,158 @@ +/**************************************************************************** +** +** Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the demonstration applications of the Qt Toolkit. +** +** Commercial Usage +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License versions 2.0 or 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 GNU General Public Licensing requirements will be met: +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. In addition, as a special +** exception, Nokia gives you certain additional rights. These rights +** are described in the Nokia Qt GPL Exception version 1.3, included in +** the file GPL_EXCEPTION.txt in this package. +** +** Qt for Windows(R) Licensees +** As a special exception, Nokia, as the sole copyright holder for Qt +** Designer, grants users of the Qt/Eclipse Integration plug-in the +** right for the Qt/Eclipse Integration to link to functionality +** provided by Qt Designer and its related libraries. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** +****************************************************************************/ + +#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/src/downloads.ui b/src/downloads.ui new file mode 100644 index 00000000..a2e25693 --- /dev/null +++ b/src/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/src/edittableview.cpp b/src/edittableview.cpp new file mode 100644 index 00000000..d47ac78b --- /dev/null +++ b/src/edittableview.cpp @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the demonstration applications of the Qt Toolkit. +** +** Commercial Usage +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License versions 2.0 or 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 GNU General Public Licensing requirements will be met: +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. In addition, as a special +** exception, Nokia gives you certain additional rights. These rights +** are described in the Nokia Qt GPL Exception version 1.3, included in +** the file GPL_EXCEPTION.txt in this package. +** +** Qt for Windows(R) Licensees +** As a special exception, Nokia, as the sole copyright holder for Qt +** Designer, grants users of the Qt/Eclipse Integration plug-in the +** right for the Qt/Eclipse Integration to link to functionality +** provided by Qt Designer and its related libraries. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** +****************************************************************************/ + +#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/src/edittableview.h b/src/edittableview.h new file mode 100644 index 00000000..f22c3ce9 --- /dev/null +++ b/src/edittableview.h @@ -0,0 +1,57 @@ +/**************************************************************************** +** +** Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the demonstration applications of the Qt Toolkit. +** +** Commercial Usage +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License versions 2.0 or 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 GNU General Public Licensing requirements will be met: +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. In addition, as a special +** exception, Nokia gives you certain additional rights. These rights +** are described in the Nokia Qt GPL Exception version 1.3, included in +** the file GPL_EXCEPTION.txt in this package. +** +** Qt for Windows(R) Licensees +** As a special exception, Nokia, as the sole copyright holder for Qt +** Designer, grants users of the Qt/Eclipse Integration plug-in the +** right for the Qt/Eclipse Integration to link to functionality +** provided by Qt Designer and its related libraries. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** +****************************************************************************/ + +#ifndef EDITTABLEVIEW_H +#define EDITTABLEVIEW_H + +#include <QtGui/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/src/edittreeview.cpp b/src/edittreeview.cpp new file mode 100644 index 00000000..5e55fd16 --- /dev/null +++ b/src/edittreeview.cpp @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the demonstration applications of the Qt Toolkit. +** +** Commercial Usage +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License versions 2.0 or 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 GNU General Public Licensing requirements will be met: +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. In addition, as a special +** exception, Nokia gives you certain additional rights. These rights +** are described in the Nokia Qt GPL Exception version 1.3, included in +** the file GPL_EXCEPTION.txt in this package. +** +** Qt for Windows(R) Licensees +** As a special exception, Nokia, as the sole copyright holder for Qt +** Designer, grants users of the Qt/Eclipse Integration plug-in the +** right for the Qt/Eclipse Integration to link to functionality +** provided by Qt Designer and its related libraries. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** +****************************************************************************/ + +#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/src/edittreeview.h b/src/edittreeview.h new file mode 100644 index 00000000..1341ad62 --- /dev/null +++ b/src/edittreeview.h @@ -0,0 +1,57 @@ +/**************************************************************************** +** +** Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the demonstration applications of the Qt Toolkit. +** +** Commercial Usage +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License versions 2.0 or 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 GNU General Public Licensing requirements will be met: +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. In addition, as a special +** exception, Nokia gives you certain additional rights. These rights +** are described in the Nokia Qt GPL Exception version 1.3, included in +** the file GPL_EXCEPTION.txt in this package. +** +** Qt for Windows(R) Licensees +** As a special exception, Nokia, as the sole copyright holder for Qt +** Designer, grants users of the Qt/Eclipse Integration plug-in the +** right for the Qt/Eclipse Integration to link to functionality +** provided by Qt Designer and its related libraries. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** +****************************************************************************/ + +#ifndef EDITTREEVIEW_H +#define EDITTREEVIEW_H + +#include <QtGui/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/src/history.cpp b/src/history.cpp new file mode 100644 index 00000000..fef1920c --- /dev/null +++ b/src/history.cpp @@ -0,0 +1,1277 @@ +/**************************************************************************** +** +** Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the demonstration applications of the Qt Toolkit. +** +** Commercial Usage +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License versions 2.0 or 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 GNU General Public Licensing requirements will be met: +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. In addition, as a special +** exception, Nokia gives you certain additional rights. These rights +** are described in the Nokia Qt GPL Exception version 1.3, included in +** the file GPL_EXCEPTION.txt in this package. +** +** Qt for Windows(R) Licensees +** As a special exception, Nokia, as the sole copyright holder for Qt +** Designer, grants users of the Qt/Eclipse Integration plug-in the +** right for the Qt/Eclipse Integration to link to functionality +** provided by Qt Designer and its related libraries. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** +****************************************************************************/ + +#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 <QtGui/QHeaderView> +#include <QtGui/QStyle> + +#include <QtWebKit/QWebHistoryInterface> +#include <QtWebKit/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(const HistoryItem &)), + m_saveTimer, SLOT(changeOccurred())); + connect(this, SIGNAL(entryRemoved(const 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(QDesktopServices::storageLocation(QDesktopServices::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 = QDesktopServices::storageLocation(QDesktopServices::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(const HistoryItem &)), + this, SLOT(historyReset())); + + connect(m_history, SIGNAL(entryAdded(const HistoryItem &)), + this, SLOT(entryAdded())); + connect(m_history, SIGNAL(entryUpdated(int)), + this, SLOT(entryUpdated(int))); +} + +void HistoryModel::historyReset() +{ + reset(); +} + +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() == -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() == -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, -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(const QModelIndex &)), + this, SLOT(activated(const 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(const QUrl&)), + this, SIGNAL(openUrl(const 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(const QModelIndex&)), + this, SLOT(open())); + tree->setContextMenuPolicy(Qt::CustomContextMenu); + connect(tree, SIGNAL(customContextMenuRequested(const QPoint &)), + this, SLOT(customContextMenuRequested(const 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(const QModelIndex &, const QModelIndex &)), + this, SLOT(dataChanged(const QModelIndex &, const QModelIndex &))); + disconnect(sourceModel(), SIGNAL(rowsInserted(const QModelIndex &, int, int)), + this, SLOT(sourceRowsInserted(const QModelIndex &, int, int))); + disconnect(sourceModel(), SIGNAL(rowsRemoved(const QModelIndex &, int, int)), + this, SLOT(sourceRowsRemoved(const QModelIndex &, int, int))); + } + + QAbstractProxyModel::setSourceModel(newSourceModel); + + if (sourceModel()) { + m_loaded = false; + connect(sourceModel(), SIGNAL(modelReset()), this, SLOT(sourceReset())); + connect(sourceModel(), SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &)), + this, SLOT(sourceDataChanged(const QModelIndex &, const QModelIndex &))); + connect(sourceModel(), SIGNAL(rowsInserted(const QModelIndex &, int, int)), + this, SLOT(sourceRowsInserted(const QModelIndex &, int, int))); + connect(sourceModel(), SIGNAL(rowsRemoved(const QModelIndex &, int, int)), + this, SLOT(sourceRowsRemoved(const 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; + reset(); +} + +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(const QModelIndex &, int, int)), + this, SLOT(sourceRowsRemoved(const 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(const QModelIndex &, int, int)), + this, SLOT(sourceRowsRemoved(const QModelIndex &, int, int))); + m_loaded = false; + if (oldCount - count != rowCount()) + reset(); + 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, 0); +} + +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(const QModelIndex &, int, int)), + this, SLOT(sourceReset())); + disconnect(sourceModel(), SIGNAL(rowsRemoved(const QModelIndex &, int, int)), + this, SLOT(sourceReset())); + } + + QAbstractProxyModel::setSourceModel(newSourceModel); + + if (newSourceModel) { + connect(newSourceModel, SIGNAL(modelReset()), this, SLOT(sourceReset())); + connect(sourceModel(), SIGNAL(rowsInserted(const QModelIndex &, int, int)), + this, SLOT(sourceReset())); + connect(sourceModel(), SIGNAL(rowsRemoved(const QModelIndex &, int, int)), + this, SLOT(sourceReset())); + } + + reset(); +} + +void HistoryCompletionModel::sourceReset() +{ + reset(); +} + +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, 0); + 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, 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(const QModelIndex &, int, int)), + this, SLOT(sourceRowsInserted(const QModelIndex &, int, int))); + disconnect(sourceModel(), SIGNAL(rowsRemoved(const QModelIndex &, int, int)), + this, SLOT(sourceRowsRemoved(const 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(const QModelIndex &, int, int)), + this, SLOT(sourceRowsInserted(const QModelIndex &, int, int))); + connect(sourceModel(), SIGNAL(rowsRemoved(const QModelIndex &, int, int)), + this, SLOT(sourceRowsRemoved(const QModelIndex &, int, int))); + } + + reset(); +} + +void HistoryTreeModel::sourceReset() +{ + m_sourceRowCache.clear(); + reset(); +} + +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) { + m_sourceRowCache.clear(); + reset(); + 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()) { + m_sourceRowCache.clear(); + reset(); + 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/src/history.h b/src/history.h new file mode 100644 index 00000000..cda78a5a --- /dev/null +++ b/src/history.h @@ -0,0 +1,346 @@ +/**************************************************************************** +** +** Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the demonstration applications of the Qt Toolkit. +** +** Commercial Usage +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License versions 2.0 or 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 GNU General Public Licensing requirements will be met: +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. In addition, as a special +** exception, Nokia gives you certain additional rights. These rights +** are described in the Nokia Qt GPL Exception version 1.3, included in +** the file GPL_EXCEPTION.txt in this package. +** +** Qt for Windows(R) Licensees +** As a special exception, Nokia, as the sole copyright holder for Qt +** Designer, grants users of the Qt/Eclipse Integration plug-in the +** right for the Qt/Eclipse Integration to link to functionality +** provided by Qt Designer and its related libraries. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** +****************************************************************************/ + +#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 <QtGui/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/src/history.ui b/src/history.ui new file mode 100644 index 00000000..0944940e --- /dev/null +++ b/src/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>&Remove</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="removeAllButton" > + <property name="text" > + <string>Remove &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/src/htmls/htmls.qrc b/src/htmls/htmls.qrc new file mode 100644 index 00000000..03b256cc --- /dev/null +++ b/src/htmls/htmls.qrc @@ -0,0 +1,5 @@ +<!DOCTYPE RCC><RCC version="1.0"> +<qresource> + <file>notfound.html</file> +</qresource> +</RCC> diff --git a/src/htmls/notfound.html b/src/htmls/notfound.html new file mode 100755 index 00000000..b428a6a3 --- /dev/null +++ b/src/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>.trolltech.com + instead of <b>www</b>.trolltech.com</li> + <li>If the address is correct, try to check 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/src/main.cpp b/src/main.cpp new file mode 100644 index 00000000..ae4c2173 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,57 @@ +/* ============================================================ +Â * +Â * This file is a part of the reKonq project +Â * + * Copyright (C) 2008 by Andrea Diamantini <adjam7 at gmail dot com> +Â * + * +Â * This program is free software; you can redistribute it +Â * and/or modify it under the terms of the GNU General +Â * Public License as published by the Free Software Foundation; +Â * either version 2, or (at your option) any later version. +Â * +Â * This program is distributed in the hope that it will be useful, +Â * but WITHOUT ANY WARRANTY; without even the implied warranty of +Â * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Â See the +Â * GNU General Public License for more details. +Â * +Â * ============================================================ */ + + +#include <KAboutData> +#include <KCmdLineArgs> + +#include "browserapplication.h" + +int main(int argc, char **argv) +{ + KAboutData aboutData( + "reKonq", + 0, + ki18n("reKonq"), + "0.0.1", + ki18n("A KDE browser webkit based"), + KAboutData::License_GPL, + ki18n("Copyright (c) 2008 Andrea Diamantini"), + KLocalizedString(), + "http://www.adjam.org", + "adjam7@gmail.com" // bug report mail + ); + + aboutData.addAuthor(ki18n("Andrea Diamantini"), ki18n("reKonq author"), "adjam7@gmail.com"); + aboutData.setProgramIconName("application-internet"); + + KCmdLineArgs::init( argc, argv, &aboutData ); + KCmdLineOptions options; + + //TODO make this work + options.add("+URL", ki18n("Location to open")); + KCmdLineArgs::addCmdLineOptions( options ); + + BrowserApplication app(argc, argv); + if (!app.isTheOnlyBrowser()) + return 0; + app.newMainWindow(); + return app.exec(); +} + diff --git a/src/modelmenu.cpp b/src/modelmenu.cpp new file mode 100644 index 00000000..3a8fb0cd --- /dev/null +++ b/src/modelmenu.cpp @@ -0,0 +1,222 @@ +/**************************************************************************** +** +** Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the demonstration applications of the Qt Toolkit. +** +** Commercial Usage +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License versions 2.0 or 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 GNU General Public Licensing requirements will be met: +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. In addition, as a special +** exception, Nokia gives you certain additional rights. These rights +** are described in the Nokia Qt GPL Exception version 1.3, included in +** the file GPL_EXCEPTION.txt in this package. +** +** Qt for Windows(R) Licensees +** As a special exception, Nokia, as the sole copyright holder for Qt +** Designer, grants users of the Qt/Eclipse Integration plug-in the +** right for the Qt/Eclipse Integration to link to functionality +** provided by Qt Designer and its related libraries. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** +****************************************************************************/ + +#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/src/modelmenu.h b/src/modelmenu.h new file mode 100644 index 00000000..81928256 --- /dev/null +++ b/src/modelmenu.h @@ -0,0 +1,101 @@ +/**************************************************************************** +** +** Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the demonstration applications of the Qt Toolkit. +** +** Commercial Usage +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License versions 2.0 or 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 GNU General Public Licensing requirements will be met: +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. In addition, as a special +** exception, Nokia gives you certain additional rights. These rights +** are described in the Nokia Qt GPL Exception version 1.3, included in +** the file GPL_EXCEPTION.txt in this package. +** +** Qt for Windows(R) Licensees +** As a special exception, Nokia, as the sole copyright holder for Qt +** Designer, grants users of the Qt/Eclipse Integration plug-in the +** right for the Qt/Eclipse Integration to link to functionality +** provided by Qt Designer and its related libraries. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** +****************************************************************************/ + +#ifndef MODELMENU_H +#define MODELMENU_H + +#include <QtGui/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/src/networkaccessmanager.cpp b/src/networkaccessmanager.cpp new file mode 100644 index 00000000..8922c8c0 --- /dev/null +++ b/src/networkaccessmanager.cpp @@ -0,0 +1,154 @@ +/**************************************************************************** +** +** Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the demonstration applications of the Qt Toolkit. +** +** Commercial Usage +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License versions 2.0 or 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 GNU General Public Licensing requirements will be met: +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. In addition, as a special +** exception, Nokia gives you certain additional rights. These rights +** are described in the Nokia Qt GPL Exception version 1.3, included in +** the file GPL_EXCEPTION.txt in this package. +** +** Qt for Windows(R) Licensees +** As a special exception, Nokia, as the sole copyright holder for Qt +** Designer, grants users of the Qt/Eclipse Integration plug-in the +** right for the Qt/Eclipse Integration to link to functionality +** provided by Qt Designer and its related libraries. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** +****************************************************************************/ + +#include "networkaccessmanager.h" + +#include "browserapplication.h" +#include "browsermainwindow.h" +#include "ui_passworddialog.h" +#include "ui_proxy.h" + +#include <QtCore/QSettings> + +#include <QtGui/QDialog> +#include <QtGui/QMessageBox> +#include <QtGui/QStyle> +#include <QtGui/QTextDocument> + +#include <QtNetwork/QAuthenticator> +#include <QtNetwork/QNetworkProxy> +#include <QtNetwork/QNetworkReply> +#include <QtNetwork/QSslError> + +NetworkAccessManager::NetworkAccessManager(QObject *parent) + : QNetworkAccessManager(parent) +{ + connect(this, SIGNAL(authenticationRequired(QNetworkReply*, QAuthenticator*)), + SLOT(authenticationRequired(QNetworkReply*,QAuthenticator*))); + connect(this, SIGNAL(proxyAuthenticationRequired(const QNetworkProxy&, QAuthenticator*)), + SLOT(proxyAuthenticationRequired(const QNetworkProxy&, QAuthenticator*))); +#ifndef QT_NO_OPENSSL + connect(this, SIGNAL(sslErrors(QNetworkReply*, const QList<QSslError>&)), + SLOT(sslErrors(QNetworkReply*, const QList<QSslError>&))); +#endif + loadSettings(); +} + +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.setType(QNetworkProxy::Socks5Proxy); + else + proxy.setType(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(Qt::escape(reply->url().toString())).arg(Qt::escape(reply->url().toString())); + 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(Qt::escape(proxy.hostName())); + 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) +{ + 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?").arg(reply->url().toString()).arg(errors), + QMessageBox::Yes | QMessageBox::No, + QMessageBox::No); + if (ret == QMessageBox::Yes) + reply->ignoreSslErrors(); +} +#endif diff --git a/src/networkaccessmanager.h b/src/networkaccessmanager.h new file mode 100644 index 00000000..d49c7fa4 --- /dev/null +++ b/src/networkaccessmanager.h @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the demonstration applications of the Qt Toolkit. +** +** Commercial Usage +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License versions 2.0 or 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 GNU General Public Licensing requirements will be met: +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. In addition, as a special +** exception, Nokia gives you certain additional rights. These rights +** are described in the Nokia Qt GPL Exception version 1.3, included in +** the file GPL_EXCEPTION.txt in this package. +** +** Qt for Windows(R) Licensees +** As a special exception, Nokia, as the sole copyright holder for Qt +** Designer, grants users of the Qt/Eclipse Integration plug-in the +** right for the Qt/Eclipse Integration to link to functionality +** provided by Qt Designer and its related libraries. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** +****************************************************************************/ + +#ifndef NETWORKACCESSMANAGER_H +#define NETWORKACCESSMANAGER_H + +#include <QtNetwork/QNetworkAccessManager> + +class NetworkAccessManager : public QNetworkAccessManager +{ + Q_OBJECT + +public: + NetworkAccessManager(QObject *parent = 0); + +public slots: + void loadSettings(); + +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/src/passworddialog.ui b/src/passworddialog.ui new file mode 100644 index 00000000..7c166586 --- /dev/null +++ b/src/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/src/proxy.ui b/src/proxy.ui new file mode 100644 index 00000000..62a8be62 --- /dev/null +++ b/src/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/src/searchlineedit.cpp b/src/searchlineedit.cpp new file mode 100644 index 00000000..97fdf726 --- /dev/null +++ b/src/searchlineedit.cpp @@ -0,0 +1,233 @@ +/**************************************************************************** +** +** Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the demonstration applications of the Qt Toolkit. +** +** Commercial Usage +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License versions 2.0 or 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 GNU General Public Licensing requirements will be met: +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. In addition, as a special +** exception, Nokia gives you certain additional rights. These rights +** are described in the Nokia Qt GPL Exception version 1.3, included in +** the file GPL_EXCEPTION.txt in this package. +** +** Qt for Windows(R) Licensees +** As a special exception, Nokia, as the sole copyright holder for Qt +** Designer, grants users of the Qt/Eclipse Integration plug-in the +** right for the Qt/Eclipse Integration to link to functionality +** provided by Qt Designer and its related libraries. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** +****************************************************************************/ + +#include "searchlineedit.h" + +#include <QtGui/QPainter> +#include <QtGui/QMouseEvent> +#include <QtGui/QMenu> +#include <QtGui/QStyle> +#include <QtGui/QStyleOptionFrameV2> + +ClearButton::ClearButton(QWidget *parent) + : QAbstractButton(parent) +{ + setCursor(Qt::ArrowCursor); + 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); + QColor color = palette().color(QPalette::Mid); + 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")); + setCursor(Qt::ArrowCursor); + 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(const QString &)), + this, SIGNAL(textChanged(const 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/src/searchlineedit.h b/src/searchlineedit.h new file mode 100644 index 00000000..2f2a8362 --- /dev/null +++ b/src/searchlineedit.h @@ -0,0 +1,99 @@ +/**************************************************************************** +** +** Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the demonstration applications of the Qt Toolkit. +** +** Commercial Usage +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License versions 2.0 or 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 GNU General Public Licensing requirements will be met: +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. In addition, as a special +** exception, Nokia gives you certain additional rights. These rights +** are described in the Nokia Qt GPL Exception version 1.3, included in +** the file GPL_EXCEPTION.txt in this package. +** +** Qt for Windows(R) Licensees +** As a special exception, Nokia, as the sole copyright holder for Qt +** Designer, grants users of the Qt/Eclipse Integration plug-in the +** right for the Qt/Eclipse Integration to link to functionality +** provided by Qt Designer and its related libraries. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** +****************************************************************************/ + +#ifndef SEARCHLINEEDIT_H +#define SEARCHLINEEDIT_H + +#include "urllineedit.h" + +#include <QtGui/QLineEdit> +#include <QtGui/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/src/settings.cpp b/src/settings.cpp new file mode 100644 index 00000000..a3a85ee1 --- /dev/null +++ b/src/settings.cpp @@ -0,0 +1,320 @@ +/**************************************************************************** +** +** Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the demonstration applications of the Qt Toolkit. +** +** Commercial Usage +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License versions 2.0 or 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 GNU General Public Licensing requirements will be met: +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. In addition, as a special +** exception, Nokia gives you certain additional rights. These rights +** are described in the Nokia Qt GPL Exception version 1.3, included in +** the file GPL_EXCEPTION.txt in this package. +** +** Qt for Windows(R) Licensees +** As a special exception, Nokia, as the sole copyright holder for Qt +** Designer, grants users of the Qt/Eclipse Integration plug-in the +** right for the Qt/Eclipse Integration to link to functionality +** provided by Qt Designer and its related libraries. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** +****************************************************************************/ + +#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 <QtGui/QtGui> +#include <QtWebKit/QtWebKit> + +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(QDesktopServices::storageLocation(QDesktopServices::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://www.trolltech.com"); + 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 = qVariantValue<QFont>(settings.value(QLatin1String("fixedFont"), fixedFont)); + standardFont = qVariantValue<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")); + + CookieJar *jar = BrowserApplication::cookieJar(); + QByteArray value = settings.value(QLatin1String("acceptCookies"), QLatin1String("AcceptOnlyFromSitesNavigatedTo")).toByteArray(); + QMetaEnum acceptPolicyEnum = jar->staticMetaObject.enumerator(jar->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 = jar->staticMetaObject.enumerator(jar->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; + } + CookieJar *jar = BrowserApplication::cookieJar(); + QMetaEnum acceptPolicyEnum = jar->staticMetaObject.enumerator(jar->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 = jar->staticMetaObject.enumerator(jar->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/src/settings.h b/src/settings.h new file mode 100644 index 00000000..dfd8dd15 --- /dev/null +++ b/src/settings.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the demonstration applications of the Qt Toolkit. +** +** Commercial Usage +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License versions 2.0 or 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 GNU General Public Licensing requirements will be met: +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. In addition, as a special +** exception, Nokia gives you certain additional rights. These rights +** are described in the Nokia Qt GPL Exception version 1.3, included in +** the file GPL_EXCEPTION.txt in this package. +** +** Qt for Windows(R) Licensees +** As a special exception, Nokia, as the sole copyright holder for Qt +** Designer, grants users of the Qt/Eclipse Integration plug-in the +** right for the Qt/Eclipse Integration to link to functionality +** provided by Qt Designer and its related libraries. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** +****************************************************************************/ + +#ifndef SETTINGS_H +#define SETTINGS_H + +#include <QtGui/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/src/settings.ui b/src/settings.ui new file mode 100644 index 00000000..3491ce0b --- /dev/null +++ b/src/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/src/squeezelabel.cpp b/src/squeezelabel.cpp new file mode 100644 index 00000000..f8687ca7 --- /dev/null +++ b/src/squeezelabel.cpp @@ -0,0 +1,56 @@ +/**************************************************************************** +** +** Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the demonstration applications of the Qt Toolkit. +** +** Commercial Usage +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License versions 2.0 or 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 GNU General Public Licensing requirements will be met: +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. In addition, as a special +** exception, Nokia gives you certain additional rights. These rights +** are described in the Nokia Qt GPL Exception version 1.3, included in +** the file GPL_EXCEPTION.txt in this package. +** +** Qt for Windows(R) Licensees +** As a special exception, Nokia, as the sole copyright holder for Qt +** Designer, grants users of the Qt/Eclipse Integration plug-in the +** right for the Qt/Eclipse Integration to link to functionality +** provided by Qt Designer and its related libraries. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** +****************************************************************************/ + +#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/src/squeezelabel.h b/src/squeezelabel.h new file mode 100644 index 00000000..8dd996ef --- /dev/null +++ b/src/squeezelabel.h @@ -0,0 +1,56 @@ +/**************************************************************************** +** +** Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the demonstration applications of the Qt Toolkit. +** +** Commercial Usage +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License versions 2.0 or 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 GNU General Public Licensing requirements will be met: +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. In addition, as a special +** exception, Nokia gives you certain additional rights. These rights +** are described in the Nokia Qt GPL Exception version 1.3, included in +** the file GPL_EXCEPTION.txt in this package. +** +** Qt for Windows(R) Licensees +** As a special exception, Nokia, as the sole copyright holder for Qt +** Designer, grants users of the Qt/Eclipse Integration plug-in the +** right for the Qt/Eclipse Integration to link to functionality +** provided by Qt Designer and its related libraries. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** +****************************************************************************/ + +#ifndef SQUEEZELABEL_H +#define SQUEEZELABEL_H + +#include <QtGui/QLabel> + +class SqueezeLabel : public QLabel +{ + Q_OBJECT + +public: + SqueezeLabel(QWidget *parent = 0); + +protected: + void paintEvent(QPaintEvent *event); + +}; + +#endif // SQUEEZELABEL_H + diff --git a/src/tabwidget.cpp b/src/tabwidget.cpp new file mode 100644 index 00000000..f873ec6f --- /dev/null +++ b/src/tabwidget.cpp @@ -0,0 +1,859 @@ +/**************************************************************************** +** +** Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the demonstration applications of the Qt Toolkit. +** +** Commercial Usage +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License versions 2.0 or 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 GNU General Public Licensing requirements will be met: +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. In addition, as a special +** exception, Nokia gives you certain additional rights. These rights +** are described in the Nokia Qt GPL Exception version 1.3, included in +** the file GPL_EXCEPTION.txt in this package. +** +** Qt for Windows(R) Licensees +** As a special exception, Nokia, as the sole copyright holder for Qt +** Designer, grants users of the Qt/Eclipse Integration plug-in the +** right for the Qt/Eclipse Integration to link to functionality +** provided by Qt Designer and its related libraries. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** +****************************************************************************/ + +#include "tabwidget.h" + +#include "browserapplication.h" +#include "browsermainwindow.h" +#include "history.h" +#include "urllineedit.h" +#include "webview.h" + +#include <QtGui/QClipboard> +#include <QtGui/QCompleter> +#include <QtGui/QListView> +#include <QtGui/QMenu> +#include <QtGui/QMessageBox> +#include <QtGui/QMouseEvent> +#include <QtGui/QStackedWidget> +#include <QtGui/QStyle> +#include <QtGui/QToolButton> + +#include <QtCore/QDebug> + +TabBar::TabBar(QWidget *parent) + : QTabBar(parent) +{ + setContextMenuPolicy(Qt::CustomContextMenu); + setAcceptDrops(true); + connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), + this, SLOT(contextMenuRequested(const QPoint &))); + + QString alt = QLatin1String("Alt+%1"); + for (int i = 1; i <= 10; ++i) { + int key = i; + if (key == 10) + key = 0; + QShortcut *shortCut = new QShortcut(alt.arg(key), this); + m_tabShortcuts.append(shortCut); + connect(shortCut, SIGNAL(activated()), this, SLOT(selectTabAction())); + } +} + +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 + && (event->pos() - m_dragStartPos).manhattanLength() > QApplication::startDragDistance()) { + 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); +} + +void TabBar::dragEnterEvent(QDragEnterEvent *event) +{ + const QMimeData *mimeData = event->mimeData(); + QStringList formats = mimeData->formats(); + if (formats.contains(QLatin1String("action")) + && (mimeData->data(QLatin1String("action")) == "tab-reordering")) { + event->acceptProposedAction(); + } + QTabBar::dragEnterEvent(event); +} + +void TabBar::dropEvent(QDropEvent *event) +{ + int fromIndex = tabAt(m_dragStartPos); + int toIndex = tabAt(event->pos()); + if (fromIndex != toIndex) { + emit tabMoveRequested(fromIndex, toIndex); + event->acceptProposedAction(); + } + QTabBar::dropEvent(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(tabMoveRequested(int, int)), this, SLOT(moveTab(int, int))); + setTabBar(m_tabBar); + + // 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); + + // corner buttons + QToolButton *addTabButton = new QToolButton(this); + addTabButton->setDefaultAction(m_newTabAction); + addTabButton->setAutoRaise(true); + addTabButton->setToolButtonStyle(Qt::ToolButtonIconOnly); + setCornerWidget(addTabButton, Qt::TopLeftCorner); + + QToolButton *closeTabButton = new QToolButton(this); + closeTabButton->setDefaultAction(m_closeTabAction); + closeTabButton->setAutoRaise(true); + closeTabButton->setToolButtonStyle(Qt::ToolButtonIconOnly); + setCornerWidget(closeTabButton, Qt::TopRightCorner); + + 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) +{ + disconnect(this, SIGNAL(currentChanged(int)), + this, SLOT(currentChanged(int))); + + QWidget *tabWidget = widget(fromIndex); + QIcon icon = tabIcon(fromIndex); + QString text = tabText(fromIndex); + QVariant data = m_tabBar->tabData(fromIndex); + removeTab(fromIndex); + insertTab(toIndex, tabWidget, icon, text); + m_tabBar->setTabData(toIndex, data); + connect(this, SIGNAL(currentChanged(int)), + this, SLOT(currentChanged(int))); + setCurrentIndex(toIndex); +} + +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(const QString&)), + this, SIGNAL(showStatusBarMessage(const QString&))); + disconnect(oldWebView->page(), SIGNAL(linkHovered(const QString&, const QString&, const QString&)), + this, SIGNAL(linkHovered(const QString&))); + disconnect(oldWebView, SIGNAL(loadProgress(int)), + this, SIGNAL(loadProgress(int))); + } + + connect(webView, SIGNAL(statusBarMessage(const QString&)), + this, SIGNAL(showStatusBarMessage(const QString&))); + connect(webView->page(), SIGNAL(linkHovered(const QString&, const QString&, const QString&)), + this, SIGNAL(linkHovered(const 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(const QString &)), + this, SLOT(webViewTitleChanged(const QString &))); + connect(webView, SIGNAL(urlChanged(const QUrl &)), + this, SLOT(webViewUrlChanged(const QUrl &))); + connect(webView->page(), SIGNAL(windowCloseRequested()), + this, SLOT(windowCloseRequested())); + connect(webView->page(), SIGNAL(geometryChangeRequested(const QRect &)), + this, SIGNAL(geometryChangeRequested(const 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(); + + 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/src/tabwidget.h b/src/tabwidget.h new file mode 100644 index 00000000..1bdd56bf --- /dev/null +++ b/src/tabwidget.h @@ -0,0 +1,222 @@ +/**************************************************************************** +** +** Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the demonstration applications of the Qt Toolkit. +** +** Commercial Usage +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License versions 2.0 or 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 GNU General Public Licensing requirements will be met: +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. In addition, as a special +** exception, Nokia gives you certain additional rights. These rights +** are described in the Nokia Qt GPL Exception version 1.3, included in +** the file GPL_EXCEPTION.txt in this package. +** +** Qt for Windows(R) Licensees +** As a special exception, Nokia, as the sole copyright holder for Qt +** Designer, grants users of the Qt/Eclipse Integration plug-in the +** right for the Qt/Eclipse Integration to link to functionality +** provided by Qt Designer and its related libraries. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** +****************************************************************************/ + +#ifndef TABWIDGET_H +#define TABWIDGET_H + +#include <QtGui/QTabBar> + +#include <QtGui/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); + void dragEnterEvent(QDragEnterEvent *event); + void dropEvent(QDropEvent *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 <QtWebKit/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 <QtGui/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/src/toolbarsearch.cpp b/src/toolbarsearch.cpp new file mode 100644 index 00000000..16022fe0 --- /dev/null +++ b/src/toolbarsearch.cpp @@ -0,0 +1,156 @@ +/**************************************************************************** +** +** Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the demonstration applications of the Qt Toolkit. +** +** Commercial Usage +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License versions 2.0 or 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 GNU General Public Licensing requirements will be met: +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. In addition, as a special +** exception, Nokia gives you certain additional rights. These rights +** are described in the Nokia Qt GPL Exception version 1.3, included in +** the file GPL_EXCEPTION.txt in this package. +** +** Qt for Windows(R) Licensees +** As a special exception, Nokia, as the sole copyright holder for Qt +** Designer, grants users of the Qt/Eclipse Integration plug-in the +** right for the Qt/Eclipse Integration to link to functionality +** provided by Qt Designer and its related libraries. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** +****************************************************************************/ + +#include "toolbarsearch.h" +#include "autosaver.h" + +#include <QtCore/QSettings> +#include <QtCore/QUrl> + +#include <QtGui/QCompleter> +#include <QtGui/QMenu> +#include <QtGui/QStringListModel> + +#include <QtWebKit/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")); + url.addQueryItem(QLatin1String("q"), searchText); + url.addQueryItem(QLatin1String("ie"), QLatin1String("UTF-8")); + url.addQueryItem(QLatin1String("oe"), QLatin1String("UTF-8")); + url.addQueryItem(QLatin1String("client"), QLatin1String("qtdemobrowser")); + 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/src/toolbarsearch.h b/src/toolbarsearch.h new file mode 100644 index 00000000..9ecbf183 --- /dev/null +++ b/src/toolbarsearch.h @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the demonstration applications of the Qt Toolkit. +** +** Commercial Usage +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License versions 2.0 or 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 GNU General Public Licensing requirements will be met: +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. In addition, as a special +** exception, Nokia gives you certain additional rights. These rights +** are described in the Nokia Qt GPL Exception version 1.3, included in +** the file GPL_EXCEPTION.txt in this package. +** +** Qt for Windows(R) Licensees +** As a special exception, Nokia, as the sole copyright holder for Qt +** Designer, grants users of the Qt/Eclipse Integration plug-in the +** right for the Qt/Eclipse Integration to link to functionality +** provided by Qt Designer and its related libraries. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** +****************************************************************************/ + +#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/src/urllineedit.cpp b/src/urllineedit.cpp new file mode 100644 index 00000000..47900c04 --- /dev/null +++ b/src/urllineedit.cpp @@ -0,0 +1,327 @@ +/**************************************************************************** +** +** Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the demonstration applications of the Qt Toolkit. +** +** Commercial Usage +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License versions 2.0 or 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 GNU General Public Licensing requirements will be met: +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. In addition, as a special +** exception, Nokia gives you certain additional rights. These rights +** are described in the Nokia Qt GPL Exception version 1.3, included in +** the file GPL_EXCEPTION.txt in this package. +** +** Qt for Windows(R) Licensees +** As a special exception, Nokia, as the sole copyright holder for Qt +** Designer, grants users of the Qt/Eclipse Integration plug-in the +** right for the Qt/Eclipse Integration to link to functionality +** provided by Qt Designer and its related libraries. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** +****************************************************************************/ + +#include "urllineedit.h" + +#include "browserapplication.h" +#include "searchlineedit.h" +#include "webview.h" + +#include <QtCore/QEvent> + +#include <QtGui/QApplication> +#include <QtGui/QCompleter> +#include <QtGui/QFocusEvent> +#include <QtGui/QHBoxLayout> +#include <QtGui/QLabel> +#include <QtGui/QLineEdit> +#include <QtGui/QPainter> +#include <QtGui/QStyle> +#include <QtGui/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(const QString&)), + m_clearButton, SLOT(textChanged(const 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); + QWidget::keyPressEvent(event); +} + +bool ExLineEdit::event(QEvent *event) +{ + if (event->type() == QEvent::ShortcutOverride) + 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); +} + + +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(m_webView->url().toString()); + 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(const QUrl &)), + this, SLOT(webViewUrlChanged(const 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(url.toString()); + 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(m_webView->url().toString()); + 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/src/urllineedit.h b/src/urllineedit.h new file mode 100644 index 00000000..283ff48e --- /dev/null +++ b/src/urllineedit.h @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the demonstration applications of the Qt Toolkit. +** +** Commercial Usage +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License versions 2.0 or 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 GNU General Public Licensing requirements will be met: +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. In addition, as a special +** exception, Nokia gives you certain additional rights. These rights +** are described in the Nokia Qt GPL Exception version 1.3, included in +** the file GPL_EXCEPTION.txt in this package. +** +** Qt for Windows(R) Licensees +** As a special exception, Nokia, as the sole copyright holder for Qt +** Designer, grants users of the Qt/Eclipse Integration plug-in the +** right for the Qt/Eclipse Integration to link to functionality +** provided by Qt Designer and its related libraries. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** +****************************************************************************/ + +#ifndef URLLINEEDIT_H +#define URLLINEEDIT_H + +#include <QtCore/QUrl> +#include <QtGui/QWidget> +#include <QtGui/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; + +protected: + void focusInEvent(QFocusEvent *event); + void focusOutEvent(QFocusEvent *event); + void keyPressEvent(QKeyEvent *event); + void paintEvent(QPaintEvent *event); + void resizeEvent(QResizeEvent *event); + 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/src/webview.cpp b/src/webview.cpp new file mode 100644 index 00000000..39cedd95 --- /dev/null +++ b/src/webview.cpp @@ -0,0 +1,299 @@ +/**************************************************************************** +** +** Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the demonstration applications of the Qt Toolkit. +** +** Commercial Usage +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License versions 2.0 or 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 GNU General Public Licensing requirements will be met: +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. In addition, as a special +** exception, Nokia gives you certain additional rights. These rights +** are described in the Nokia Qt GPL Exception version 1.3, included in +** the file GPL_EXCEPTION.txt in this package. +** +** Qt for Windows(R) Licensees +** As a special exception, Nokia, as the sole copyright holder for Qt +** Designer, grants users of the Qt/Eclipse Integration plug-in the +** right for the Qt/Eclipse Integration to link to functionality +** provided by Qt Designer and its related libraries. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** +****************************************************************************/ + +#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 <QtGui/QMenu> +#include <QtGui/QMessageBox> +#include <QtGui/QMouseEvent> + +#include <QtWebKit/QWebHitTestResult> + +#include <QtUiTools/QUiLoader> + +#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 ¶mNames, const QStringList ¶mValues) +{ + 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) +{ + if (reply->error() == QNetworkReply::NoError) { + BrowserApplication::downloadManager()->handleUnsupportedContent(reply); + return; + } + + QFile file(QLatin1String(":/notfound.html")); + bool isOpened = file.open(QIODevice::ReadOnly); + Q_ASSERT(isOpened); + QString title = tr("Error loading page: %1").arg(reply->url().toString()); + QString html = QString(QLatin1String(file.readAll())) + .arg(title) + .arg(reply->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(const QString&)), + SLOT(setStatusBarText(const QString&))); + connect(this, SIGNAL(loadProgress(int)), + this, SLOT(setProgress(int))); + connect(this, SIGNAL(loadFinished(bool)), + this, SLOT(loadFinished())); + connect(page(), SIGNAL(loadingUrl(const QUrl&)), + this, SIGNAL(urlChanged(const QUrl &))); + connect(page(), SIGNAL(downloadRequested(const QNetworkRequest &)), + this, SLOT(downloadRequested(const 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() << "Recieved 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/src/webview.h b/src/webview.h new file mode 100644 index 00000000..73c9dafb --- /dev/null +++ b/src/webview.h @@ -0,0 +1,115 @@ +/**************************************************************************** +** +** Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the demonstration applications of the Qt Toolkit. +** +** Commercial Usage +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License versions 2.0 or 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 GNU General Public Licensing requirements will be met: +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. In addition, as a special +** exception, Nokia gives you certain additional rights. These rights +** are described in the Nokia Qt GPL Exception version 1.3, included in +** the file GPL_EXCEPTION.txt in this package. +** +** Qt for Windows(R) Licensees +** As a special exception, Nokia, as the sole copyright holder for Qt +** Designer, grants users of the Qt/Eclipse Integration plug-in the +** right for the Qt/Eclipse Integration to link to functionality +** provided by Qt Designer and its related libraries. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** +****************************************************************************/ + +#ifndef WEBVIEW_H +#define WEBVIEW_H + +#include <QtWebKit/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 ¶mNames, const QStringList ¶mValues); +#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/src/xbel.cpp b/src/xbel.cpp new file mode 100644 index 00000000..3accd76d --- /dev/null +++ b/src/xbel.cpp @@ -0,0 +1,315 @@ +/**************************************************************************** +** +** Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the demonstration applications of the Qt Toolkit. +** +** Commercial Usage +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License versions 2.0 or 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 GNU General Public Licensing requirements will be met: +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. In addition, as a special +** exception, Nokia gives you certain additional rights. These rights +** are described in the Nokia Qt GPL Exception version 1.3, included in +** the file GPL_EXCEPTION.txt in this package. +** +** Qt for Windows(R) Licensees +** As a special exception, Nokia, as the sole copyright holder for Qt +** Designer, grants users of the Qt/Eclipse Integration plug-in the +** right for the Qt/Eclipse Integration to link to functionality +** provided by Qt Designer and its related libraries. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** +****************************************************************************/ + +#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); + while (!atEnd()) { + readNext(); + if (isStartElement()) { + 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 (!atEnd()) { + readNext(); + if (isEndElement()) + break; + + if (isStartElement()) { + if (name() == QLatin1String("folder")) + readFolder(parent); + else if (name() == QLatin1String("bookmark")) + readBookmarkNode(parent); + else if (name() == QLatin1String("separator")) + readSeparator(parent); + else + skipUnknownElement(); + } + } +} + +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 (!atEnd()) { + readNext(); + + if (isEndElement()) + break; + + if (isStartElement()) { + 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 + skipUnknownElement(); + } + } +} + +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 (!atEnd()) { + readNext(); + if (isEndElement()) + break; + + if (isStartElement()) { + if (name() == QLatin1String("title")) + readTitle(bookmark); + else if (name() == QLatin1String("desc")) + readDescription(bookmark); + else + skipUnknownElement(); + } + } + if (bookmark->title.isEmpty()) + bookmark->title = QObject::tr("Unknown title"); +} + +void XbelReader::skipUnknownElement() +{ + Q_ASSERT(isStartElement()); + + while (!atEnd()) { + readNext(); + + if (isEndElement()) + break; + + if (isStartElement()) + skipUnknownElement(); + } +} + + +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/src/xbel.h b/src/xbel.h new file mode 100644 index 00000000..fdacd1b9 --- /dev/null +++ b/src/xbel.h @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the demonstration applications of the Qt Toolkit. +** +** Commercial Usage +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License versions 2.0 or 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 GNU General Public Licensing requirements will be met: +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. In addition, as a special +** exception, Nokia gives you certain additional rights. These rights +** are described in the Nokia Qt GPL Exception version 1.3, included in +** the file GPL_EXCEPTION.txt in this package. +** +** Qt for Windows(R) Licensees +** As a special exception, Nokia, as the sole copyright holder for Qt +** Designer, grants users of the Qt/Eclipse Integration plug-in the +** right for the Qt/Eclipse Integration to link to functionality +** provided by Qt Designer and its related libraries. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** +****************************************************************************/ + +#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 skipUnknownElement(); + 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 + |