# HG changeset patch # User mandel # Date 1242586199 0 # Node ID 71b382c10ef648b4bada0f21f9985df67b924db2 # Parent 3cb15c92ac28a329d0581ec654e086b906c4476a add coarse and incomplete QT browser port diff -r 3cb15c92ac28 -r 71b382c10ef6 demos/browser/TODO.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/demos/browser/TODO.txt Sun May 17 18:49:59 2009 +0000 @@ -0,0 +1,13 @@ +This is a very coarse port of ./demos/browser from the QT 4.5.1 linux sources. + +Done: +- *.cpp and *.h files have been merged into *.d files +- C preprocessor include lines where replaced by import lines. +- const from function headers were removed. +- some '::' and most '->' have been replaced by '.' + +Todo: +- *.ui files need to converted to source files and translated to D +- imports need to be corrected +- module declarations are probably missing +- everything else... diff -r 3cb15c92ac28 -r 71b382c10ef6 demos/browser/autosaver.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/demos/browser/autosaver.d Sun May 17 18:49:59 2009 +0000 @@ -0,0 +1,114 @@ +/**************************************************************************** +** +** Copyright (C) 2009 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. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ +module autosaver; + +import QtCore.QObject; +import QtCore.QBasicTimer; +import QtCore.QTime; + +import QtCore.QDir; +import QtCore.QCoreApplication; +import QtCore.QMetaObject; +import QtDebug; + +const uint AUTOSAVE_IN = 1000 * 3 // seconds +const uint MAXWAIT = 1000 * 15 // seconds + +/* + 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: + this(QObject *parent) +{ + super(parent); + Q_ASSERT(parent); +} + ~this(); +{ + if (m_timer.isActive()) + qWarning() << "AutoSaver: still active when destroyed, changes not saved."; +} + void 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"; + } +} + +public slots: + void changeOccurred(); +{ + if (m_firstChange.isNull()) + m_firstChange.start(); + + if (m_firstChange.elapsed() > MAXWAIT) { + saveIfNeccessary(); + } else { + m_timer.start(AUTOSAVE_IN, this); + } +} + +protected: + void timerEvent(QTimerEvent *event) +{ + if (event.timerId() == m_timer.timerId()) { + saveIfNeccessary(); + } else { + QObject::timerEvent(event); + } +} + + +private: + QBasicTimer m_timer; + QTime m_firstChange; +} + diff -r 3cb15c92ac28 -r 71b382c10ef6 demos/browser/bookmarks.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/demos/browser/bookmarks.d Sun May 17 18:49:59 2009 +0000 @@ -0,0 +1,1170 @@ +/**************************************************************************** +** +** Copyright (C) 2009 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. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtCore.QObject; +import QtCore.QAbstractItemModel; + +import QtGui.QUndoCommand; + + +import bookmarks; + +import autosaver; +import browserapplication; +import history; +import xbel; + +import QtCore.QBuffer; +import QtCore.QFile; +import QtCore.QMimeData; + +import QtGui.QDesktopServices; +import QtGui.QDragEnterEvent; +import QtGui.QFileDialog; +import QtGui.QHeaderView; +import QtGui.QIcon; +import QtGui.QMessageBox; +import QtGui.QToolButton; + +import QtWebKit.QWebSettings; + +import QtCore.QDebug; + +const char[] BOOKMARKBAR = "Bookmarks Bar"; +const char[] BOOKMARKMENU = "Bookmarks Menu"; + + +/*! + 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 = null) +{ + super(parent); + m_loaded = false; + m_saveTimer = new AutoSaver(this); + m_bookmarkRootNode = null; + m_bookmarkModel = null; + 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())); +} + + + ~this() +{ + m_saveTimer.saveIfNeccessary(); +} + + + void addBookmark(BookmarkNode *parent, BookmarkNode *node, int row = -1) +{ + if (!m_loaded) + return; + Q_ASSERT(parent); + InsertBookmarksCommand *command = new InsertBookmarksCommand(this, parent, node, row); + m_commands.push(command); +} + + + void 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 setTitle(BookmarkNode *node, QString &newTitle); +{ + if (!m_loaded) + return; + + Q_ASSERT(node); + ChangeBookmarkCommand *command = new ChangeBookmarkCommand(this, node, newTitle, true); + m_commands.push(command); +} + + + void setUrl(BookmarkNode *node, QString &newUrl); +{ + if (!m_loaded) + return; + + Q_ASSERT(node); + ChangeBookmarkCommand *command = new ChangeBookmarkCommand(this, node, newUrl, false); + m_commands.push(command); +} + + +void changeExpanded() +{ + m_saveTimer.changeOccurred(); +} + + BookmarkNode *bookmarks(); +{ + if (!m_loaded) + load(); + return m_bookmarkRootNode; +} + + BookmarkNode *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 *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* bookmarksModel() +{ + if (!m_bookmarkModel) + m_bookmarkModel = new BookmarksModel(this, this); + return m_bookmarkModel; +} + + QUndoStack *undoRedoStack() { return &m_commands; }; + +public slots: + void 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 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")); +} + +private slots: + void save() +{ + 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; +} + +private: + void 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 = null; + BookmarkNode *menu = null; + QList 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)); +} + + + 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: + this(BookmarksManager *m_bookmarkManagaer, BookmarkNode *parent, int row) +{ + super(BookmarksManager::tr("Remove Bookmark")) + m_row = row; + m_bookmarkManagaer = m_bookmarkManagaer; + m_node = parent.children().value(row); + m_parent = parent; + m_done = false; +} + +~this() +{ + if (m_done && !m_node.parent()) { + delete m_node; + } +} + +void undo() +{ + m_parent.add(m_node, m_row); + emit m_bookmarkManagaer.entryAdded(m_node); + m_done = false; +} + +void redo() +{ + m_parent.remove(m_node); + emit m_bookmarkManagaer.entryRemoved(m_parent, m_row, m_node); + m_done = true; +} + +protected: + int m_row; + BookmarksManager *m_bookmarkManagaer; + BookmarkNode *m_node; + BookmarkNode *m_parent; + bool m_done; +}; + +class InsertBookmarksCommand : public RemoveBookmarksCommand +{ + +public: +this(BookmarksManager *m_bookmarkManagaer, + BookmarkNode *parent, BookmarkNode *node, int row) +{ + super(m_bookmarkManagaer, parent, row); + + setText(BookmarksManager::tr("Insert Bookmark")); + m_node = node; +} + + void undo() { RemoveBookmarksCommand::redo(); } + void redo() { RemoveBookmarksCommand::undo(); } +} + +class ChangeBookmarkCommand : public QUndoCommand +{ + +public: + this(BookmarksManager *m_bookmarkManagaer, + BookmarkNode *node, QString &newValue, bool title) +{ + super(); + 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 undo() +{ + if (m_title) + m_node.title = m_oldValue; + else + m_node.url = m_oldValue; + emit m_bookmarkManagaer.entryChanged(m_node); +} + +void redo() +{ + if (m_title) + m_node.title = m_newValue; + else + m_node.url = m_newValue; + emit m_bookmarkManagaer.entryChanged(m_node); +} + +private: + BookmarksManager *m_bookmarkManagaer; + bool m_title; + QString m_oldValue; + QString m_newValue; + BookmarkNode *m_node; +} + +/*! + BookmarksModel is a QAbstractItemModel wrapper around the BookmarkManager + */ +import QtGui/QIcon; +class BookmarksModel : public QAbstractItemModel +{ + Q_OBJECT + +public slots: + void 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 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 entryChanged(BookmarkNode *item); +{ + QModelIndex idx = index(item); + emit dataChanged(idx, idx); +} +public: + enum Roles { + TypeRole = Qt.UserRole + 1, + UrlRole = Qt.UserRole + 2, + UrlStringRole = Qt.UserRole + 3, + SeparatorRole = Qt.UserRole + 4 + }; + + BookmarksModel(BookmarksManager *bookmarkManager, QObject *parent = null) +{ + super(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 *))); +} + + + inline BookmarksManager *bookmarksManager() { return m_bookmarksManager; } + + QVariant headerData(int section, Qt.Orientation orientation, int role = Qt.DisplayRole); +{ + 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 data(QModelIndex &index, int role = Qt.DisplayRole) +{ + if (!index.isValid() || index.model() != this) + return QVariant(); + + 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 columnCount(QModelIndex &parent = QModelIndex()) +{ + return (parent.column() > 0) ? 0 : 2; +} + + int rowCount(QModelIndex &parent = QModelIndex()) +{ + if (parent.column() > 0) + return 0; + + if (!parent.isValid()) + return m_bookmarksManager.bookmarks().children().count(); + + const BookmarkNode *item = static_cast(parent.internalPointer()); + return item.children().count(); +} + + QModelIndex index(int, int, QModelIndex& = QModelIndex()) +{ + 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 parent(QModelIndex& index= QModelIndex()) +{ + 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); +} + + + Qt.ItemFlags flags(QModelIndex &index) +{ + 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 supportedDropActions (); +{ + return Qt.CopyAction | Qt.MoveAction; +} + + + bool removeRows(int row, int count, QModelIndex &parent = QModelIndex()); +{ + 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; +} + + bool setData(QModelIndex &index, QVariant &value, int role = Qt.EditRole) +{ + 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; +} + + + QMimeData *mimeData(QModelIndexList &indexes); +{ + 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; +} + + +#define MIMETYPE QLatin1String("application/bookmarks.xbel") + +QStringList mimeTypes() +{ + QStringList types; + types << MIMETYPE; + return types; +} + +bool dropMimeData(QMimeData *data, Qt.DropAction action, int row, int column, 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 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 hasChildren(QModelIndex &parent = QModelIndex()) +{ + if (!parent.isValid()) + return true; + const BookmarkNode *parentNode = node(parent); + return (parentNode.type() == BookmarkNode::Folder); +} + + BookmarkNode *node(QModelIndex &index) +{ + BookmarkNode *itemNode = static_cast(index.internalPointer()); + if (!itemNode) + return m_bookmarksManager.bookmarks(); + return itemNode; +} + + QModelIndex index(BookmarkNode *node) +{ + BookmarkNode *parent = node.parent(); + if (!parent) + return QModelIndex(); + return createIndex(parent.children().indexOf(node), 0, node); +} + +private: + + bool m_endMacro; + BookmarksManager *m_bookmarksManager; +} + +// Menu that is dynamically populated from the bookmarks +import modelmenu; +class BookmarksMenu : public ModelMenu +{ + Q_OBJECT + +signals: + void openUrl(QUrl &url); + +public: + BookmarksMenu(QWidget *parent = null) +{ +super(parent); + m_bookmarksManager = 0; + connect(this, SIGNAL(activated(QModelIndex &)), + this, SLOT(activated(QModelIndex &))); + setMaxRows(-1); + setHoverRole(BookmarksModel::UrlStringRole); + setSeparatorRole(BookmarksModel::SeparatorRole); +} + void setInitialActions(QList actions); +{ + m_initialActions = actions; + for (int i = 0; i < m_initialActions.count(); ++i) + addAction(m_initialActions.at(i)); +} +protected: + bool 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; +} + +private slots: + void activated(QModelIndex &index) +{ + emit openUrl(index.data(BookmarksModel::UrlRole).toUrl()); +} + +private: + BookmarksManager *m_bookmarksManager; + QList m_initialActions; +} + +/* + Proxy model that filters out the bookmarks so only the folders + are left behind. Used in the add bookmark dialog combobox. + */ +import QtGui/QSortFilterProxyModel; +class AddBookmarkProxyModel : public QSortFilterProxyModel +{ + Q_OBJECT +public: + this(QObject * parent = null) +{ + super(parent); +} + int columnCount(QModelIndex & parent = QModelIndex()) +{ + return qMin(1, QSortFilterProxyModel::columnCount(parent)); +} +protected: + bool filterAcceptsRow(int source_row, QModelIndex &source_parent) +{ + QModelIndex idx = sourceModel().index(source_row, 0, source_parent); + return sourceModel().hasChildren(idx); +} +} + +/*! + Add bookmark dialog + */ +import ui_addbookmarkdialog; +class AddBookmarkDialog : public QDialog, public Ui_AddBookmarkDialog +{ + Q_OBJECT + +public: + this(QString &url, QString &title, QWidget *parent = null, BookmarksManager *bookmarkManager = null) + : 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); +} +private slots: + +void 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(); +} + +private: + QString m_url; + BookmarksManager *m_bookmarksManager; + AddBookmarkProxyModel *m_proxyModel; +} + +import ui_bookmarks; + +//class TreeProxyModel; +class BookmarksDialog : public QDialog, public Ui_BookmarksDialog +{ + Q_OBJECT + +signals: + void openUrl(QUrl &url); + +public: + this(QWidget *parent = null, BookmarksManager *manager = null) + : QDialog(parent) +{ + m_bookmarksManager = manager; + if (!m_bookmarksManager) + m_bookmarksManager = BrowserApplication::bookmarksManager(); + setupUi(this); + + tree.setUniformRowHeights(true); + tree.setSelectionBehavior(QAbstractItemView::SelectRows); + tree.setSelectionMode(QAbstractItemView::ContiguousSelection); + tree.setTextElideMode(Qt.ElideMiddle); + m_bookmarksModel = m_bookmarksManager.bookmarksModel(); + m_proxyModel = new TreeProxyModel(this); + connect(search, SIGNAL(textChanged(QString)), + m_proxyModel, SLOT(setFilterFixedString(QString))); + connect(removeButton, SIGNAL(clicked()), tree, SLOT(removeOne())); + m_proxyModel.setSourceModel(m_bookmarksModel); + tree.setModel(m_proxyModel); + tree.setDragDropMode(QAbstractItemView::InternalMove); + tree.setExpanded(m_proxyModel.index(0, 0), true); + tree.setAlternatingRowColors(true); + QFontMetrics fm(font()); + int header = fm.width(QLatin1Char('m')) * 40; + tree.header().resizeSection(0, header); + tree.header().setStretchLastSection(true); + connect(tree, SIGNAL(activated(QModelIndex&)), + this, SLOT(open())); + tree.setContextMenuPolicy(Qt.CustomContextMenu); + connect(tree, SIGNAL(customContextMenuRequested(QPoint &)), + this, SLOT(customContextMenuRequested(QPoint &))); + connect(addFolderButton, SIGNAL(clicked()), + this, SLOT(newFolder())); + expandNodes(m_bookmarksManager.bookmarks()); + setAttribute(Qt.WA_DeleteOnClose); +} + ~this() +{ + if (saveExpandedNodes(tree.rootIndex())) + m_bookmarksManager.changeExpanded(); +} + +private slots: + void customContextMenuRequested(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 open() +{ + QModelIndex index = tree.currentIndex(); + if (!index.parent().isValid()) + return; + emit openUrl(index.sibling(index.row(), 1).data(BookmarksModel::UrlRole).toUrl()); +} + + void 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); +} + +private: + void 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); + } + } +} + +bool saveExpandedNodes(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; +} + + BookmarksManager *m_bookmarksManager; + BookmarksModel *m_bookmarksModel; + TreeProxyModel *m_proxyModel; +}; + +import QtGui/QToolBar; +class BookmarksToolBar : public QToolBar +{ + Q_OBJECT + +signals: + void openUrl(QUrl &url); + +public: + BookmarksToolBar(BookmarksModel *model, QWidget *parent = null) +{ + super(tr("Bookmark"), parent); + m_bookmarksModel = model; + connect(this, SIGNAL(actionTriggered(QAction*)), this, SLOT(triggered(QAction*))); + setRootIndex(model.index(0, 0)); + connect(m_bookmarksModel, SIGNAL(modelReset()), this, SLOT(build())); + connect(m_bookmarksModel, SIGNAL(rowsInserted(QModelIndex &, int, int)), this, SLOT(build())); + connect(m_bookmarksModel, SIGNAL(rowsRemoved(QModelIndex &, int, int)), this, SLOT(build())); + connect(m_bookmarksModel, SIGNAL(dataChanged(QModelIndex &, QModelIndex &)), this, SLOT(build())); + setAcceptDrops(true); +} + + void setRootIndex(QModelIndex &index); +{ + m_root = index; + build(); +} + + QModelIndex rootIndex() +{ + return m_root; +} + +protected: + void dragEnterEvent(QDragEnterEvent *event) +{ + const QMimeData *mimeData = event.mimeData(); + if (mimeData.hasUrls()) + event.acceptProposedAction(); + QToolBar::dragEnterEvent(event); +} + + void dropEvent(QDropEvent *event) +{ + const QMimeData *mimeData = event.mimeData(); + if (mimeData.hasUrls() && mimeData.hasText()) { + QList 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); +} + +private slots: + void triggered(QAction *action) +{ + QVariant v = action.data(); + if (v.canConvert()) { + emit openUrl(v.toUrl()); + } +} + + void activated(QModelIndex &index) +{ + emit openUrl(index.data(BookmarksModel::UrlRole).toUrl()); +} + + + void build() +{ + clear(); + for (int i = 0; i < m_bookmarksModel.rowCount(m_root); ++i) { + QModelIndex idx = m_bookmarksModel.index(i, 0, m_root); + if (m_bookmarksModel.hasChildren(idx)) { + QToolButton *button = new QToolButton(this); + button.setPopupMode(QToolButton::InstantPopup); + button.setArrowType(Qt.DownArrow); + button.setText(idx.data().toString()); + ModelMenu *menu = new ModelMenu(this); + connect(menu, SIGNAL(activated(QModelIndex &)), + this, SLOT(activated(QModelIndex &))); + menu.setModel(m_bookmarksModel); + menu.setRootIndex(idx); + menu.addAction(new QAction(menu)); + button.setMenu(menu); + button.setToolButtonStyle(Qt.ToolButtonTextOnly); + QAction *a = addWidget(button); + a.setText(idx.data().toString()); + } else { + QAction *action = addAction(idx.data().toString()); + action.setData(idx.data(BookmarksModel::UrlRole)); + } + } +} + +private: + BookmarksModel *m_bookmarksModel; + QPersistentModelIndex m_root; +} diff -r 3cb15c92ac28 -r 71b382c10ef6 demos/browser/browser.icns Binary file demos/browser/browser.icns has changed diff -r 3cb15c92ac28 -r 71b382c10ef6 demos/browser/browser.ico Binary file demos/browser/browser.ico has changed diff -r 3cb15c92ac28 -r 71b382c10ef6 demos/browser/browserapplication.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/demos/browser/browserapplication.d Sun May 17 18:49:59 2009 +0000 @@ -0,0 +1,507 @@ +/**************************************************************************** +** +** Copyright (C) 2009 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. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtGui.QApplication; + +import QtCore.QUrl; +import QtCore.QPointer; + +import QtGui.QIcon; + +import browserapplication; + +import bookmarks; +import browsermainwindow; +import cookiejar; +import downloadmanager; +import history; +import networkaccessmanager; +import tabwidget; +import webview; + +import QtCore.QBuffer; +import QtCore.QDir; +import QtCore.QLibraryInfo; +import QtCore.QSettings; +import QtCore.QTextStream; +import QtCore.QTranslator; + +import QtGui.QDesktopServices; +import QtGui.QFileOpenEvent; +import QtGui.QMessageBox; + +import QtNetwork.QLocalServer; +import QtNetwork.QLocalSocket; +import QtNetwork.QNetworkProxy; +import QtNetwork.QSslSocket; + +import QtWebKit.QWebSettings; + +import QtCore.QDebug; + +DownloadManager *BrowserApplication::s_downloadManager = 0; +HistoryManager *BrowserApplication::s_historyManager = 0; +NetworkAccessManager *BrowserApplication::s_networkAccessManager = 0; +BookmarksManager *BrowserApplication::s_bookmarksManager = 0; + + +/* +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: +this(char[] args) +{ + super(args); + m_localServer = 0; + QCoreApplication::setOrganizationName(QLatin1String("Trolltech")); + QCoreApplication::setApplicationName(QLatin1String("demobrowser")); + QCoreApplication::setApplicationVersion(QLatin1String("0.1")); +version(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(); +} + 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; + } + +version(Q_WS_MAC) { + QApplication::setQuitOnLastWindowClosed(false); +} else { + QApplication::setQuitOnLastWindowClosed(true); +} + + m_localServer = new QLocalServer(this); + connect(m_localServer, SIGNAL(newConnection()), + this, SLOT(newLocalSocketConnection())); + if (!m_localServer->listen(serverName)) { + if (m_localServer->serverError() == QAbstractSocket::AddressInUseError + && QFile::exists(m_localServer->serverName())) { + QFile::remove(m_localServer->serverName()); + m_localServer->listen(serverName); + } + } + +#ifndef QT_NO_OPENSSL + if (!QSslSocket::supportsSsl()) { + QMessageBox::information(0, "Demo Browser", + "This system does not support OpenSSL. SSL websites will not be available."); + } +} + + 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(); + +version(Q_WS_MAC) { + connect(this, SIGNAL(lastWindowClosed()), + this, SLOT(lastWindowClosed())); +} + + QTimer::singleShot(0, this, SLOT(postLaunch())); +} + + ~this() +{ + delete s_downloadManager; + for (int i = 0; i < m_mainWindows.size(); ++i) { + BrowserMainWindow *window = m_mainWindows.at(i); + delete window; + } + delete s_networkAccessManager; + delete s_bookmarksManager; +} + + static BrowserApplication *instance() +{ + return (static_cast(QCoreApplication::instance())); +} + + + + void 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(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(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(); +} + + bool isTheOnlyBrowser() const +{ + return (m_localServer != 0); +} + + BrowserMainWindow *mainWindow() +{ + clean(); + if (m_mainWindows.isEmpty()) + newMainWindow(); + return m_mainWindows[0]; +} + + QList mainWindows() +{ + clean(); + QList list; + for (int i = 0; i < m_mainWindows.count(); ++i) + list.append(m_mainWindows.at(i)); + return list; +} + + + QIcon 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); +} + + void 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 canRestoreSession() const +{ + return !m_lastSession.isEmpty(); +} + + static HistoryManager *historyManager() +{ + if (!s_historyManager) { + s_historyManager = new HistoryManager(); + QWebHistoryInterface::setDefaultInterface(s_historyManager); + } + return s_historyManager; +} + + static CookieJar *cookieJar() +{ + return (CookieJar*)networkAccessManager()->cookieJar(); +} + static DownloadManager *downloadManager() +{ + if (!s_downloadManager) { + s_downloadManager = new DownloadManager(); + } + return s_downloadManager; +} + + + static NetworkAccessManager *networkAccessManager() +{ + if (!s_networkAccessManager) { + s_networkAccessManager = new NetworkAccessManager(); + s_networkAccessManager->setCookieJar(new CookieJar); + } + return s_networkAccessManager; +} + + + static BookmarksManager *bookmarksManager() +{ + if (!s_bookmarksManager) { + s_bookmarksManager = new BookmarksManager; + } + return s_bookmarksManager; +} + + +version(Q_WS_MAC) { +bool 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(event)->file()); + return true; + } + default: + break; + } + return QApplication::event(event); +} +} + +public slots: + BrowserMainWindow *newMainWindow() +{ + BrowserMainWindow *browser = new BrowserMainWindow(); + m_mainWindows.prepend(browser); + browser->show(); + return browser; +} + + void restoreLastSession() +{ + QList 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)); + } +} + + +version(Q_WS_MAC) { +import 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); +} +} + +version(Q_WS_MAC) { +void lastWindowClosed() +{ + clean(); + BrowserMainWindow *mw = new BrowserMainWindow; + mw->slotHome(); + m_mainWindows.prepend(mw); +} +} + + +private slots: + +/*! + Any actions that can be delayed until the window is visible + */ +void postLaunch() +{ + QString directory = QDesktopServices::storageLocation(QDesktopServices::DataLocation); + if (directory.isEmpty()) + directory = QDir::homePath() + QLatin1String("/.") + QCoreApplication::applicationName(); + QWebSettings::setIconDatabasePath(directory); + + setWindowIcon(QIcon(QLatin1String(":browser.svg"))); + + loadSettings(); + + // newMainWindow() needs to be called in main() for this to happen + if (m_mainWindows.count() > 0) { + QStringList args = QCoreApplication::arguments(); + if (args.count() > 1) + mainWindow()->loadPage(args.last()); + else + mainWindow()->slotHome(); + } + BrowserApplication::historyManager(); +} + + void openUrl(const QUrl &url) +{ + mainWindow()->loadPage(url.toString()); +} + + void 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(); +} + +private: + void 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 installTranslator(const QString &name) +{ + QTranslator *translator = new QTranslator(this); + translator->load(name, QLibraryInfo::location(QLibraryInfo::TranslationsPath)); + QApplication::installTranslator(translator); +} + + static HistoryManager *s_historyManager; + static DownloadManager *s_downloadManager; + static NetworkAccessManager *s_networkAccessManager; + static BookmarksManager *s_bookmarksManager; + + QList > m_mainWindows; + QLocalServer *m_localServer; + QByteArray m_lastSession; + mutable QIcon m_defaultIcon; +}; + +} + diff -r 3cb15c92ac28 -r 71b382c10ef6 demos/browser/browsermainwindow.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/demos/browser/browsermainwindow.d Sun May 17 18:49:59 2009 +0000 @@ -0,0 +1,1073 @@ +/**************************************************************************** +** +** Copyright (C) 2009 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. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtGui.QMainWindow; +import QtGui.QIcon; +import QtCore.QUrl; + +import browsermainwindow; + +import autosaver; +import bookmarks; +import browserapplication; +import chasewidget; +import downloadmanager; +import history; +import settings; +import tabwidget; +import toolbarsearch; +import ui_passworddialog; +import webview; + +import QtCore.QSettings; + +import QtGui.QDesktopWidget; +import QtGui.QFileDialog; +import QtGui.QPlainTextEdit; +import QtGui.QPrintDialog; +import QtGui.QPrintPreviewDialog; +import QtGui.QPrinter; +import QtGui.QMenuBar; +import QtGui.QMessageBox; +import QtGui.QStatusBar; +import QtGui.QToolBar; +import QtGui.QInputDialog; + +import QtWebKit.QWebFrame; +import QtWebKit.QWebHistory; + +import QtCore.QDebug; + + +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 + +static const qint32 BrowserMainWindowMagic = 0xba; + +public: + this(QWidget *parent = null, Qt.WindowFlags flags = 0); +{ + + super(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); +version(Q_WS_MAC) +{ + layout.addWidget(m_bookmarksToolbar); + layout.addWidget(new QWidget); // <- OS X tab widget style bug +} else { + addToolBarBreak(); + addToolBar(m_bookmarksToolbar); +} + 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))); +version(Q_WS_MAC) { + connect(m_tabWidget, SIGNAL(lastTabClosed()), + this, SLOT(close())); +} else { + connect(m_tabWidget, SIGNAL(lastTabClosed()), + m_tabWidget, SLOT(newTab())); +} + + slotUpdateWindowTitle(); + loadDefaultState(); + m_tabWidget.newTab(); + + int size = m_tabWidget.lineEditStack().sizeHint().height(); + m_navigationBar.setIconSize(QSize(size, size)); + +} + + + + ~this() +{ + m_autoSaver.changeOccurred(); + m_autoSaver.saveIfNeccessary(); +} + + +QSize sizeHint() +{ + QRect desktopRect = QApplication::desktop().screenGeometry(); + QSize size = desktopRect.size() * qreal(0.9); + return size; +} + +public: +static QUrl 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 = QUrl::fromEncoded(urlStr.toUtf8(), 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(); + QByteArray schema = (prefix == QLatin1String("ftp")) ? prefix.toLatin1() : "http"; + QUrl url = + QUrl::fromEncoded(schema + "://" + urlStr.toUtf8(), QUrl::TolerantMode); + if (url.isValid()) + return url; + } + } + + // Fall back to QUrl's own tolerant parser. + QUrl url = QUrl::fromEncoded(string.toUtf8(), QUrl::TolerantMode); + + // finally for cases where the user just types in a hostname add http + if (url.scheme().isEmpty()) + url = QUrl::fromEncoded("http://" + string.toUtf8(), QUrl::TolerantMode); + return url; +} + + + +TabWidget* tabWidget() +{ + return m_tabWidget; +} + +WebView* currentTab() +{ + return m_tabWidget.currentWebView(); +} + +QByteArray saveState(bool withTabs) +{ + 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 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; +} + + +public slots: +void loadPage(const QString &page) +{ + QUrl url = guessUrlFromString(page); + loadUrl(url); +} + + +void slotHome() +{ + QSettings settings; + settings.beginGroup(QLatin1String("MainWindow")); + QString home = settings.value(QLatin1String("home"), QLatin1String("http://qtsoftware.com/")).toString(); + loadPage(home); +} + +protected: + void 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(); +} + +private slots: +void save() +{ + BrowserApplication::instance().saveSession(); + + QSettings settings; + settings.beginGroup(QLatin1String("BrowserMainWindow")); + QByteArray data = saveState(false); + settings.setValue(QLatin1String("defaultState"), data); + settings.endGroup(); +} + +void 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 slotUpdateStatusbar(const QString &string); +{ + statusBar().showMessage(string, 2000); +} + + + void slotUpdateWindowTitle(const QString &title = QString()) +{ + if (title.isEmpty()) { + setWindowTitle(tr("Qt Demo Browser")); + } else { +version(Q_WS_MAC) +{ + setWindowTitle(title); +} else { + setWindowTitle(tr("%1 - Qt Demo Browser", "Page title and Browser name").arg(title)); +} + } +} + + void loadUrl(const QUrl &url); +{ + if (!currentTab() || !url.isValid()) + return; + + m_tabWidget.currentLineEdit().setText(QString::fromUtf8(url.toEncoded())); + m_tabWidget.loadUrlInCurrentTab(url); +} + + void slotPreferences() +{ + SettingsDialog *s = new SettingsDialog(this); + s.show(); +} + +void slotFileNew() +{ + BrowserApplication::instance().newMainWindow(); + BrowserMainWindow *mw = BrowserApplication::instance().mainWindow(); + mw.slotHome(); +} + +void 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 slotFilePrintPreview(); +{ +version(QT_NO_PRINTER) + { + if (!currentTab()) + return; + QPrintPreviewDialog *dialog = new QPrintPreviewDialog(this); + connect(dialog, SIGNAL(paintRequested(QPrinter *)), + currentTab(), SLOT(print(QPrinter *))); + dialog.exec(); +} +} + + + void slotFilePrint() +{ + if (!currentTab()) + return; + printRequested(currentTab().page().mainFrame()); +} + + void 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("%1

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 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 slotFileSaveAs() +{ + BrowserApplication::downloadManager().download(currentTab().url(), true); +} + + void 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 slotEditFindNext() +{ + if (!currentTab() && !m_lastSearch.isEmpty()) + return; + currentTab().findText(m_lastSearch); +} + +void slotEditFindPrevious() +{ + if (!currentTab() && !m_lastSearch.isEmpty()) + return; + currentTab().findText(m_lastSearch, QWebPage::FindBackward); +} + + + void slotShowBookmarksDialog(); +{ + BookmarksDialog *dialog = new BookmarksDialog(this); + connect(dialog, SIGNAL(openUrl(const QUrl&)), + m_tabWidget, SLOT(loadUrlInCurrentTab(const QUrl&))); + dialog.show(); +} + +void slotAddBookmark() +{ + WebView *webView = currentTab(); + QString url = webView.url().toString(); + QString title = webView.title(); + AddBookmarkDialog dialog(url, title); + dialog.exec(); +} + +void slotViewZoomIn() +{ + if (!currentTab()) + return; + currentTab().setZoomFactor(currentTab().zoomFactor() + 0.1); +} + +void BslotViewZoomOut() +{ + if (!currentTab()) + return; + currentTab().setZoomFactor(currentTab().zoomFactor() - 0.1); +} + +void slotViewResetZoom() +{ + if (!currentTab()) + return; + currentTab().setZoomFactor(1.0); +} + +void slotViewZoomTextOnly(bool enable) +{ + if (!currentTab()) + return; + currentTab().page().settings().setAttribute(QWebSettings::ZoomTextOnly, enable); +} + + + void slotViewToolbar() +{ + if (m_navigationBar.isVisible()) { + updateToolbarActionText(false); + m_navigationBar.close(); + } else { + updateToolbarActionText(true); + m_navigationBar.show(); + } + m_autoSaver.changeOccurred(); +} + + + void slotViewBookmarksBar() +{ + if (m_bookmarksToolbar.isVisible()) { + updateBookmarksToolbarActionText(false); + m_bookmarksToolbar.close(); + } else { + updateBookmarksToolbarActionText(true); + m_bookmarksToolbar.show(); + } + m_autoSaver.changeOccurred(); +} + + void slotViewStatusbar() +{ + if (statusBar().isVisible()) { + updateStatusbarActionText(false); + statusBar().close(); + } else { + updateStatusbarActionText(true); + statusBar().show(); + } + m_autoSaver.changeOccurred(); +} + +void 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 slotViewFullScreen(bool makeFullScreen) +{ + if (makeFullScreen) { + showFullScreen(); + } else { + if (isMinimized()) + showMinimized(); + else if (isMaximized()) + showMaximized(); + else showNormal(); + } +} + +void slotWebSearch() +{ + m_toolbarSearch.lineEdit().selectAll(); + m_toolbarSearch.lineEdit().setFocus(); +} + + + void 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 slotAboutApplication() +{ + QMessageBox.about(this, tr("About"), tr( + "Version %1" + "

This demo demonstrates Qt's " + "webkit facilities in action, providing an example " + "browser for you to experiment with.

" + "

QtWebKit is based on the Open Source WebKit Project developed at http://webkit.org/." + ).arg(QCoreApplication::applicationVersion())); +} + + void slotDownloadManager() +{ + BrowserApplication::downloadManager().show(); +} + + void slotSelectLineEdit(); +{ + m_tabWidget.currentLineEdit().selectAll(); + m_tabWidget.currentLineEdit().setFocus(); +} + + void 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 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 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 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 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 slotShowWindow() +{ + if (QAction *action = qobject_cast(sender())) { + QVariant v = action.data(); + if (v.canConvert()) { + int offset = qvariant_cast(v); + QList windows = BrowserApplication::instance().mainWindows(); + windows.at(offset).activateWindow(); + windows.at(offset).currentTab().setFocus(); + } + } +} + +void slotSwapFocus() +{ + if (currentTab().hasFocus()) + m_tabWidget.currentLineEdit().setFocus(); + else + currentTab().setFocus(); +} + +void printRequested(QWebFrame *frame) +{ + version(QT_NO_PRINTER) + { + QPrinter printer; + QPrintDialog *dialog = new QPrintDialog(&printer, this); + dialog.setWindowTitle(tr("Print Document")); + if (dialog.exec() != QDialog::Accepted) + return; + frame.print(&printer); + } +} + + void geometryChangeRequested(const QRect &geometry) +{ + setGeometry(geometry); +} + + + void updateToolbarActionText(bool visible) +{ + m_viewToolbar.setText(!visible ? tr("Show Toolbar") : tr("Hide Toolbar")); +} + + void updateBookmarksToolbarActionText(bool visible) +{ + m_viewBookmarkBar.setText(!visible ? tr("Show Bookmarks bar") : tr("Hide Bookmarks bar")); +} + + +private: +void loadDefaultState() +{ + QSettings settings; + settings.beginGroup(QLatin1String("BrowserMainWindow")); + QByteArray data = settings.value(QLatin1String("defaultState")).toByteArray(); + restoreState(data); + settings.endGroup(); +} + + + void 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(); + +version(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)); +} + + // 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 shortcuts; + shortcuts.append(QKeySequence(Qt.CTRL | Qt.Key_Period)); + shortcuts.append(Qt.Key_Escape); + m_stop.setShortcuts(shortcuts); + m_tabWidget.addWebAction(m_stop, QWebPage::Stop); + + m_reload = viewMenu.addAction(tr("Reload Page")); + m_reload.setShortcuts(QKeySequence::Refresh); + m_tabWidget.addWebAction(m_reload, QWebPage::Reload); + + viewMenu.addAction(tr("Zoom &In"), this, SLOT(slotViewZoomIn()), QKeySequence(Qt.CTRL | Qt.Key_Plus)); + viewMenu.addAction(tr("Zoom &Out"), this, SLOT(slotViewZoomOut()), QKeySequence(Qt.CTRL | Qt.Key_Minus)); + viewMenu.addAction(tr("Reset &Zoom"), this, SLOT(slotViewResetZoom()), QKeySequence(Qt.CTRL | Qt.Key_0)); + QAction *zoomTextOnlyAction = viewMenu.addAction(tr("Zoom &Text Only")); + connect(zoomTextOnlyAction, SIGNAL(toggled(bool)), this, SLOT(slotViewZoomTextOnly(bool))); + zoomTextOnlyAction.setCheckable(true); + zoomTextOnlyAction.setChecked(false); + + viewMenu.addSeparator(); + viewMenu.addAction(tr("Page S&ource"), this, SLOT(slotViewPageSource()), tr("Ctrl+Alt+U")); + QAction *a = viewMenu.addAction(tr("&Full Screen"), this, SLOT(slotViewFullScreen(bool)), Qt.Key_F11); + a.setCheckable(true); + + // History + HistoryMenu *historyMenu = new HistoryMenu(this); + connect(historyMenu, SIGNAL(openUrl(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 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 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 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 updateStatusbarActionText(bool visible) +{ + m_viewStatusbar.setText(!visible ? tr("Show Status Bar") : tr("Hide Status Bar")); +} + + +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; +} diff -r 3cb15c92ac28 -r 71b382c10ef6 demos/browser/chasewidget.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/demos/browser/chasewidget.d Sun May 17 18:49:59 2009 +0000 @@ -0,0 +1,174 @@ +/**************************************************************************** +** +** Copyright (C) 2009 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. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +module chasewidget; + + +import QtGui.QWidget; + +import QtCore.QSize; +import QtGui.QColor; +import QtGui.QPixmap; + +import QtCore.QPoint; + +import QtGui.QApplication; +import QtGui.QHideEvent; +import QtGui.QPainter; +import QtGui.QPaintEvent; +import QtGui.QShowEvent; + +/* +QT_BEGIN_NAMESPACE +class QHideEvent; +class QShowEvent; +class QPaintEvent; +class QTimerEvent; +QT_END_NAMESPACE +*/ + +class ChaseWidget : public QWidget +{ + Q_OBJECT +public: + this(QWidget *parent = 0, QPixmap pixmap = QPixmap(), bool pixmapEnabled = false) +{ + super(parent); + m_segment = 0; + m_delay = 100; + m_step = 40; + m_timerId = -1; + m_animated = false; + m_pixmap = pixmap; + m_pixmapEnabled = pixmapEnabled; +} + + void 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 setPixmapEnabled(bool enable); +{ + m_pixmapEnabled = enable; +} + + + QSize sizeHint() +{ + return QSize(32, 32); +} + +protected: + void paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event); + QPainter p(this); + if (m_pixmapEnabled && !m_pixmap.isNull()) { + p.drawPixmap(0, 0, m_pixmap); + return; + } + + int extent = qMin(width() - 8, height() - 8); + int displ = extent / 4; + 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)); + } +} + + void timerEvent(QTimerEvent *event) +{ + if (event->timerId() == m_timerId) { + ++m_segment; + update(); + } + QWidget.timerEvent(event); +} + +private: + int segmentCount() +{ + return 360 / m_step; +} + + +QColor colorForSegment(int seg) +{ + int index = ((seg + m_segment) % segmentCount()); + int comp = qMax(0, 255 - (index * (255 / segmentCount()))); + return QColor(comp, comp, comp, 255); +} + + int m_segment; + int m_delay; + int m_step; + int m_timerId; + bool m_animated; + QPixmap m_pixmap; + bool m_pixmapEnabled; +} diff -r 3cb15c92ac28 -r 71b382c10ef6 demos/browser/cookiejar.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/demos/browser/cookiejar.d Sun May 17 18:49:59 2009 +0000 @@ -0,0 +1,849 @@ +/**************************************************************************** +** +** Copyright (C) 2009 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. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ +module cookiejar; + +import QtNetwork.QNetworkCookieJar; + +import QtCore.QAbstractItemModel; +import QtCore.QStringList; + +import QtGui.QDialog; +import QtGui.QTableView; + + +#include "cookiejar.h" + +#include "autosaver.h" + +import QtCore.QDateTime; +import QtCore.QDir; +import QtCore.QFile; +import QtCore.QMetaEnum; +import QtCore.QSettings; +import QtCore.QUrl; + +import QtGui.QCompleter; +import QtGui.QDesktopServices; +import QtGui.QFont; +import QtGui.QFontMetrics; +import QtGui.QHeaderView; +import QtGui.QKeyEvent; +import QtGui.QSortFilterProxyModel; + +import QtWebKit.QWebSettings; + +import QtCore.QDebug; + +/* +QT_BEGIN_NAMESPACE +class QSortFilterProxyModel; +class QKeyEvent; +QT_END_NAMESPACE + +class AutoSaver; +*/ + +static const unsigned int JAR_VERSION = 23; + +QDataStream &operator<<(QDataStream &stream, const QList &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 &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 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; +} + + +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 + }; + + this(QObject *parent = null) + { + super(parent); + m_loaded = false; + m_saveTimer = new AutoSaver(this); + m_acceptCookies = AcceptOnlyFromSitesNavigatedTo; + } + + ~this() + { + if (m_keepCookies == KeepUntilExit) + clear(); + m_saveTimer.saveIfNeccessary(); + } + + QList cookiesForUrl(const QUrl &url) +{ + CookieJar *that = const_cast(this); + if (!m_loaded) + that.load(); + + QWebSettings *globalSettings = QWebSettings::globalSettings(); + if (globalSettings.testAttribute(QWebSettings::PrivateBrowsingEnabled)) { + QList noCookies; + return noCookies; + } + + return QNetworkCookieJar::cookiesForUrl(url); +} + + + bool setCookiesFromUrl(const QList &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 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 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; +} + + AcceptPolicy acceptPolicy() + { + if (!m_loaded) + (const_cast(this)).load(); + return m_acceptCookies; + } + + void setAcceptPolicy(AcceptPolicy policy) + { + if (!m_loaded) + load(); + if (policy == m_acceptCookies) + return; + m_acceptCookies = policy; + m_saveTimer.changeOccurred(); + } + + KeepPolicy keepPolicy() + { + if (!m_loaded) + (const_cast(this)).load(); + return m_keepCookies; + } + + void setKeepPolicy(KeepPolicy policy) + { + if (!m_loaded) + load(); + if (policy == m_keepCookies) + return; + m_keepCookies = policy; + m_saveTimer.changeOccurred(); + } + + + QStringList blockedCookies() + { + if (!m_loaded) + (const_cast(this)).load(); + return m_exceptions_block; + } + + QStringList allowedCookies() + { + if (!m_loaded) + (const_cast(this)).load(); + return m_exceptions_allow; + } + + QStringList allowForSessionCookies() + { + if (!m_loaded) + (const_cast(this)).load(); + return m_exceptions_allowForSession; + } + + void 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 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 setAllowForSessionCookies(const QStringList &list) + { + if (!m_loaded) + load(); + m_exceptions_allowForSession = list; + qSort(m_exceptions_allowForSession.begin(), m_exceptions_allowForSession.end()); + m_saveTimer.changeOccurred(); + } + +public slots: + + void clear() + { + setAllCookies(QList()); + m_saveTimer.changeOccurred(); + emit cookiesChanged(); + } + + void 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(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(keepPolicyEnum.keyToValue(value)); + + if (m_keepCookies == KeepUntilExit) + setAllCookies(QList()); + + m_loaded = true; + emit cookiesChanged(); + } + +private slots: +void 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 cookies = allCookies(); + for (int i = cookies.count() - 1; i >= 0; --i) { + if (cookies.at(i).isSessionCookie()) + cookies.removeAt(i); + } + cookieSettings.setValue(QLatin1String("cookies"), qVariantFromValue >(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))); +} + +private: + void purgeOldCookies() +{ + QList 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(); +} + + void load() + { + if (m_loaded) + return; + // load cookies and exceptions + qRegisterMetaTypeStreamOperators >("QList"); + QSettings cookieSettings(QDesktopServices::storageLocation(QDesktopServices::DataLocation) + QLatin1String("/cookies.ini"), QSettings::IniFormat); + setAllCookies(qvariant_cast >(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(); + } + + 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: + this(CookieJar *jar, QObject *parent = null ) + { + super(parent); + m_cookieJar = cookieJar; + connect(m_cookieJar, SIGNAL(cookiesChanged()), this, SLOT(cookiesChanged())); + m_cookieJar.load(); + } + +QVariant headerData(int section, Qt.Orientation orientation, int role) +{ + 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 data(const QModelIndex &index, int role = Qt.DisplayRole) + { + QList 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 columnCount(const QModelIndex &parent = QModelIndex()) + { + return (parent.isValid()) ? 0 : 6; + } + + + int rowCount(const QModelIndex &parent = QModelIndex()) + { + return (parent.isValid() || !m_cookieJar) ? 0 : m_cookieJar.allCookies().count(); + } + + + bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) + { + if (parent.isValid() || !m_cookieJar) + return false; + int lastRow = row + count - 1; + beginRemoveRows(parent, row, lastRow); + QList lst = m_cookieJar.allCookies(); + for (int i = lastRow; i >= row; --i) { + lst.removeAt(i); + } + m_cookieJar.setAllCookies(lst); + endRemoveRows(); + return true; + } + +private slots: + + void cookiesChanged() + { + reset(); + } + +private: + CookieJar *m_cookieJar; +} + +#include "ui_cookies.h" +#include "ui_cookiesexceptions.h" + +class CookiesDialog : public QDialog, public Ui_CookiesDialog +{ + Q_OBJECT + +public: + + this(CookieJar *cookieJar, QWidget *parent = this) : 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); + } + +private: + + QSortFilterProxyModel *m_proxyModel; +} + +class CookieExceptionsModel : public QAbstractTableModel +{ + Q_OBJECT + friend class CookiesExceptionsDialog; + +public: + zhis(CookieJar *cookieJar, QObject *parent = null) + { + super(parent); + m_cookieJar = cookiejar; + m_allowedCookies = m_cookieJar.allowedCookies(); + m_blockedCookies = m_cookieJar.blockedCookies(); + m_sessionCookies = m_cookieJar.allowForSessionCookies(); + } + + QVariant headerData(int section, Qt.Orientation orientation, int role) + { + 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 data(const QModelIndex &index, int role = Qt.DisplayRole) + { + 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 columnCount(const QModelIndex &parent = QModelIndex()); + { + return (parent.isValid()) ? 0 : 2; + } + + int rowCount(const QModelIndex &parent = QModelIndex()) + { + return (parent.isValid() || !m_cookieJar) ? 0 : m_allowedCookies.count() + m_blockedCookies.count() + m_sessionCookies.count(); + } + + bool 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; + } +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: + this(CookieJar *cookieJar, QWidget *parent = null) + : 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); + } + } + +private slots: + void block() + { + if (domainLineEdit.text().isEmpty()) + return; + m_exceptionsModel.m_blockedCookies.append(domainLineEdit.text()); + m_cookieJar.setBlockedCookies(m_exceptionsModel.m_blockedCookies); + m_exceptionsModel.reset(); + } + + void allow() + { + if (domainLineEdit.text().isEmpty()) + return; + m_exceptionsModel.m_allowedCookies.append(domainLineEdit.text()); + m_cookieJar.setAllowedCookies(m_exceptionsModel.m_allowedCookies); + m_exceptionsModel.reset(); + } + void allowForSession() + { + if (domainLineEdit.text().isEmpty()) + return; + m_exceptionsModel.m_sessionCookies.append(domainLineEdit.text()); + m_cookieJar.setAllowForSessionCookies(m_exceptionsModel.m_sessionCookies); + m_exceptionsModel.reset(); + } + + void textChanged(const QString &text) + { + bool enabled = !text.isEmpty(); + blockButton.setEnabled(enabled); + allowButton.setEnabled(enabled); + allowForSessionButton.setEnabled(enabled); + } + +private: + + CookieExceptionsModel *m_exceptionsModel; + QSortFilterProxyModel *m_proxyModel; + CookieJar *m_cookieJar; +} diff -r 3cb15c92ac28 -r 71b382c10ef6 demos/browser/data/addtab.png Binary file demos/browser/data/addtab.png has changed diff -r 3cb15c92ac28 -r 71b382c10ef6 demos/browser/data/browser.svg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/demos/browser/data/browser.svg Sun May 17 18:49:59 2009 +0000 @@ -0,0 +1,411 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + Qt Browser + + + Jens Bache-Wiig + + + + + Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -r 3cb15c92ac28 -r 71b382c10ef6 demos/browser/data/closetab.png Binary file demos/browser/data/closetab.png has changed diff -r 3cb15c92ac28 -r 71b382c10ef6 demos/browser/data/data.qrc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/demos/browser/data/data.qrc Sun May 17 18:49:59 2009 +0000 @@ -0,0 +1,11 @@ + + + addtab.png + closetab.png + history.png + browser.svg + defaultbookmarks.xbel + loading.gif + defaulticon.png + + diff -r 3cb15c92ac28 -r 71b382c10ef6 demos/browser/data/defaultbookmarks.xbel --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/demos/browser/data/defaultbookmarks.xbel Sun May 17 18:49:59 2009 +0000 @@ -0,0 +1,40 @@ + + + + + Bookmarks Bar + + Qt Software + + + WebKit.org + + + Qt Documentation + + + Qt Quarterly + + + Qt Labs + + + Qt Centre + + + Qt-Apps.org + + + qtnode + + + xkcd + + + + Bookmarks Menu + + reddit.com: what's new online! + + + diff -r 3cb15c92ac28 -r 71b382c10ef6 demos/browser/data/defaulticon.png Binary file demos/browser/data/defaulticon.png has changed diff -r 3cb15c92ac28 -r 71b382c10ef6 demos/browser/data/history.png Binary file demos/browser/data/history.png has changed diff -r 3cb15c92ac28 -r 71b382c10ef6 demos/browser/data/loading.gif Binary file demos/browser/data/loading.gif has changed diff -r 3cb15c92ac28 -r 71b382c10ef6 demos/browser/downloadmanager.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/demos/browser/downloadmanager.d Sun May 17 18:49:59 2009 +0000 @@ -0,0 +1,663 @@ +/**************************************************************************** +** +** Copyright (C) 2009 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. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ +module downloadmanager; + +import ui_downloads; +import ui_downloaditem; + +import QtNetwork.QNetworkReply; + +import QtCore.QFile; +import QtCore.QTime; + + +import autosaver; +import browserapplication; +import networkaccessmanager; + +import math; + +import QtCore.QMetaEnum; +import QtCore.QSettings; + +import QtGui.QDesktopServices; +import QtGui.QFileDialog; +import QtGui.QHeaderView; +import QtGui.QFileIconProvider; + +import QtCore.QDebug; + +import QtWebKit.QWebSettings; + + + +class DownloadItem : public QWidget, public Ui_DownloadItem +{ + Q_OBJECT + +signals: + void statusChanged(); + +public: + +/*! + 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(QNetworkReply *reply = 0, bool requestFileName = false, QWidget *parent = 0) + : 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(); +} + + + bool downloading() +{ + return (progressBar.isVisible()); +} + + + bool downloadedSuccessfully() +{ + return (stopButton.isHidden() && tryAgainButton.isHidden()); +} + + + QUrl m_url; + + QFile m_output; + QNetworkReply *m_reply; + +private slots: + void stop() +{ + setUpdatesEnabled(false); + stopButton.setEnabled(false); + stopButton.hide(); + tryAgainButton.setEnabled(true); + tryAgainButton.show(); + setUpdatesEnabled(true); + m_reply.abort(); +} + + void 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 open() +{ + QFileInfo info(m_output); + QUrl url = QUrl::fromLocalFile(info.absolutePath()); + QDesktopServices::openUrl(url); +} + + void 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 error(QNetworkReply::NetworkError code) +{ + 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 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 metaDataChanged() +{ + qDebug() << "DownloadItem::metaDataChanged: not handled."; +} + + void finished() +{ + progressBar.hide(); + stopButton.setEnabled(false); + stopButton.hide(); + m_output.close(); + updateInfoLabel(); + emit statusChanged(); +} + +private: + void 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(); +} + + void 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 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 dataString(int size) +{ + 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); +} + + QString saveFileName(const QString &directory); +{ + // 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; +} + + 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 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. + */ +this(QWidget *parent = null) + : 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(); +} + + ~this() +{ + m_autoSaver.changeOccurred(); + m_autoSaver.saveIfNeccessary(); + if (m_iconProvider) + delete m_iconProvider; +} + + int activeDownloads() +{ + int count = 0; + for (int i = 0; i < m_downloads.count(); ++i) { + if (m_downloads.at(i).stopButton.isEnabled()) + ++count; + } + return count; +} + + RemovePolicy removePolicy() +{ + return m_removePolicy; +} + + void setRemovePolicy(RemovePolicy policy) +{ + if (policy == m_removePolicy) + return; + m_removePolicy = policy; + m_autoSaver.changeOccurred(); +} + + +public slots: + void download(const QNetworkRequest &request, bool requestFileName = false); +{ + if (request.url().isEmpty()) + return; + handleUnsupportedContent(m_manager.get(request), requestFileName); +} + + + inline void download(const QUrl &url, bool requestFileName = false) + { download(QNetworkRequest(url), requestFileName); } + void handleUnsupportedContent(QNetworkReply *reply, bool requestFileName = false); +{ + 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 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(); +} + +private slots: + void save() +{ + 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 updateRow() +{ + DownloadItem *item = qobject_cast(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); +} + +private: + void 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 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) +{ +} + + void 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(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); +} + + AutoSaver *m_autoSaver; + DownloadModel *m_model; + QNetworkAccessManager *m_manager; + QFileIconProvider *m_iconProvider; + QList 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) +{ + 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 rowCount(const QModelIndex &parent = QModelIndex()) +{ + return (parent.isValid()) ? 0 : m_downloadManager.m_downloads.count(); +} + + + bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) +{ + 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; +} + +private: + DownloadManager *m_downloadManager; + +} diff -r 3cb15c92ac28 -r 71b382c10ef6 demos/browser/edittableview.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/demos/browser/edittableview.d Sun May 17 18:49:59 2009 +0000 @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** Copyright (C) 2009 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. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +module edittableview; + + +import QtGui.QTableView; + + +class EditTableView : public QTableView +{ + Q_OBJECT + +public: + EditTableView(QWidget *parent = null) +{ + super(parent); +} + + void keyPressEvent(QKeyEvent *event) +{ + if ((event.key() == Qt::Key_Delete + || event.key() == Qt::Key_Backspace) + && model()) { + removeOne(); + } else { + QAbstractItemView::keyPressEvent(event); + } +} + +public slots: + void 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 removeAll() +{ + if (model()) + model().removeRows(0, model().rowCount(rootIndex()), rootIndex()); +} +} diff -r 3cb15c92ac28 -r 71b382c10ef6 demos/browser/edittreeview.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/demos/browser/edittreeview.d Sun May 17 18:49:59 2009 +0000 @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2009 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. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +module edittreeview; + + +import QtGui.QTreeView; +import QtGui.QKeyEvent; + +class EditTreeView : public QTreeView +{ + Q_OBJECT + +public: +this(QWidget *parent = null) +{ + super(parent); +} + void keyPressEvent(QKeyEvent *event) +{ + if ((event.key() == Qt::Key_Delete + || event.key() == Qt::Key_Backspace) + && model()) { + removeOne(); + } else { + QAbstractItemView::keyPressEvent(event); + } +} + +public slots: + void removeOne() +{ + if (!model()) + return; + QModelIndex ci = currentIndex(); + int row = ci.row(); + model().removeRow(row, ci.parent()); +} + + void removeAll() +{ + if (!model()) + return; + model().removeRows(0, model().rowCount(rootIndex()), rootIndex()); +} +} diff -r 3cb15c92ac28 -r 71b382c10ef6 demos/browser/history.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/demos/browser/history.d Sun May 17 18:49:59 2009 +0000 @@ -0,0 +1,1517 @@ +/**************************************************************************** +** +** Copyright (C) 2009 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. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ +module history; + +#include "autosaver.h" +#include "browserapplication.h" + +import QtCore.QBuffer; +import QtCore.QDir; +import QtCore.QFile; +import QtCore.QFileInfo; +import QtCore.QSettings; +import QtCore.QTemporaryFile; +import QtCore.QTextStream; + +import QtCore.QtAlgorithms; + +import QtGui.QClipboard; +import QtGui.QDesktopServices; +import QtGui.QHeaderView; +import QtGui.QStyle; + +import QtWebKit.QWebHistoryInterface; +import QtWebKit.QWebSettings; + +import QtCore.QDebug; + + +#include "modelmenu.h" + +import QtCore.QDateTime; +import QtCore.QHash; +import QtCore.QObject; +import QtCore.QTimer; +import QtCore.QUrl; + +import QtGui.QSortFilterProxyModel; + +import QWebHistoryInterface; + +static const unsigned int HISTORY_VERSION = 23; + + +class HistoryItem +{ +public: + this() {} + this(const QString &u, const QDateTime &d = QDateTime(), const QString &t = QString()) + { + title = t; + url = u; + dateTime = d; + } + + inline bool operator==(const HistoryItem &other) + { return other.title == title + && other.url == url && other.dateTime == dateTime; + } + + // history is sorted in reverse + inline bool operator <(const HistoryItem &other) + { 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: + + this(QObject *parent = null) + { + super(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); + } + + + ~this() + { + m_saveTimer.saveIfNeccessary(); + } + + + bool historyContains(const QString &url) + { + return m_historyFilterModel.historyContains(url); + } + + void addHistoryEntry(QString &url) + { + QUrl cleanUrl(url); + cleanUrl.setPassword(QString()); + cleanUrl.setHost(cleanUrl.host().toLower()); + HistoryItem item(cleanUrl.toString(), QDateTime::currentDateTime()); + addHistoryItem(item); + } + + void updateHistoryItem(QUrl &url, 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 historyLimit() + { + return m_historyLimit; + } + + + void setHistoryLimit(int limit) + { + if (m_historyLimit == limit) + return; + m_historyLimit = limit; + checkForExpired(); + m_saveTimer.changeOccurred(); + } + + + QList history() + { + return m_history; + } + + + void setHistory(QList &history, bool loadedAndSorted = false); + { + 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(); + } + + + // History manager keeps around these models for use by the completer and other classes + HistoryModel *historyModel(); + { + return m_historyModel; + } + + + HistoryFilterModel *historyFilterModel() + { + return m_historyFilterModel; + } + + + HistoryTreeModel *historyTreeModel() + { + return m_historyTreeModel; + } + + +public slots: + + void clear() + { + m_history.clear(); + m_lastSavedUrl = QString(); + m_saveTimer.changeOccurred(); + m_saveTimer.saveIfNeccessary(); + historyReset(); + } + + void loadSettings() + { + // load settings + QSettings settings; + settings.beginGroup(QLatin1String("history")); + m_historyLimit = settings.value(QLatin1String("historyLimit"), 30).toInt(); + } + +private slots: + void 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; +} + +void 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); +} + +protected: +void addHistoryItem(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(); +} + +private: + +void 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 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(); + } +} + + AutoSaver *m_saveTimer; + int m_historyLimit; + QTimer m_expiredTimer; + QList m_history; + QString m_lastSavedUrl; + + HistoryModel *m_historyModel; + HistoryFilterModel *m_historyFilterModel; + HistoryTreeModel *m_historyTreeModel; +}; + +class HistoryModel : public QAbstractTableModel +{ + Q_OBJECT + +public slots: +void historyReset() +{ + reset(); +} + +void entryAdded() +{ + beginInsertRows(QModelIndex(), 0, 0); + endInsertRows(); +} + + void entryUpdated(int offset) +{ + QModelIndex idx = index(offset, 0); + emit dataChanged(idx, idx); +} + +public: + + enum Roles { + DateRole = Qt.UserRole + 1, + DateTimeRole = Qt.UserRole + 2, + UrlRole = Qt.UserRole + 3, + UrlStringRole = Qt.UserRole + 4 + }; + + this(HistoryManager *history, QObject *parent = null) + { + super(parent); + m_history = history; + + Q_ASSERT(m_history); + connect(m_history, SIGNAL(historyReset()), + this, SLOT(historyReset())); + connect(m_history, SIGNAL(entryRemoved(HistoryItem &)), + this, SLOT(historyReset())); + + connect(m_history, SIGNAL(entryAdded(HistoryItem &)), + this, SLOT(entryAdded())); + connect(m_history, SIGNAL(entryUpdated(int)), + this, SLOT(entryUpdated(int))); + } + + QVariant headerData(int section, Qt.Orientation orientation, int role = Qt.DisplayRole) + { + 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 data(QModelIndex &index, int role = Qt.DisplayRole) + { + QList 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 columnCount(QModelIndex &parent = QModelIndex()) + { + return (parent.isValid()) ? 0 : 2; + } + + int rowCount(QModelIndex &parent = QModelIndex()) + { + return (parent.isValid()) ? 0 : m_history.history().count(); + } + + bool removeRows(int row, int count, QModelIndex &parent = QModelIndex()) + { + if (parent.isValid()) + return false; + int lastRow = row + count - 1; + beginRemoveRows(parent, row, lastRow); + QList 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; + } + + +private: + HistoryManager *m_history; +} + +const uint MOVEDROWS = 15; + +/*! + 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: + + this(QAbstractItemModel *sourceModel, QObject *parent = null) + { + super(parent); + m_loaded = false; + setSourceModel(sourceModel); + } + + inline bool historyContains(QString &url) + { + load(); + return m_historyHash.contains(url); + } + + int historyLocation(QString &url); +{ + load(); + if (!m_historyHash.contains(url)) + return 0; + return sourceModel().rowCount() - m_historyHash.value(url); +} + + QModelIndex mapFromSource(QModelIndex &sourceIndex) +{ + 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 mapToSource(QModelIndex &proxyIndex) + { + load(); + int sourceRow = sourceModel().rowCount() - proxyIndex.internalId(); + return sourceModel().index(sourceRow, proxyIndex.column()); + } + + void setSourceModel(QAbstractItemModel *newSourceModel) + { + if (sourceModel()) { + disconnect(sourceModel(), SIGNAL(modelReset()), this, SLOT(sourceReset())); + disconnect(sourceModel(), SIGNAL(dataChanged(QModelIndex &, QModelIndex &)), + this, SLOT(dataChanged(QModelIndex &, QModelIndex &))); + disconnect(sourceModel(), SIGNAL(rowsInserted(QModelIndex &, int, int)), + this, SLOT(sourceRowsInserted(QModelIndex &, int, int))); + disconnect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex &, int, int)), + this, SLOT(sourceRowsRemoved(QModelIndex &, int, int))); + } + + QAbstractProxyModel::setSourceModel(newSourceModel); + + if (sourceModel()) { + m_loaded = false; + connect(sourceModel(), SIGNAL(modelReset()), this, SLOT(sourceReset())); + connect(sourceModel(), SIGNAL(dataChanged(QModelIndex &, QModelIndex &)), + this, SLOT(sourceDataChanged(QModelIndex &, QModelIndex &))); + connect(sourceModel(), SIGNAL(rowsInserted(QModelIndex &, int, int)), + this, SLOT(sourceRowsInserted(QModelIndex &, int, int))); + connect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex &, int, int)), + this, SLOT(sourceRowsRemoved(QModelIndex &, int, int))); + } + } + + QVariant headerData(int section, Qt.Orientation orientation, int role = Qt.DisplayRole); + { + return sourceModel().headerData(section, orientation, role); + } + + int rowCount(QModelIndex &parent = QModelIndex()) + { + load(); + if (parent.isValid()) + return 0; + return m_historyHash.count(); + } + + int columnCount(QModelIndex &parent = QModelIndex()); + { + return (parent.isValid()) ? 0 : 2; + } + + QModelIndex index(int, int, QModelIndex& = QModelIndex()) + { + load(); + if (row < 0 || row >= rowCount(parent) || column < 0 || column >= columnCount(parent)) + return QModelIndex(); + + return createIndex(row, column, m_sourceRow[row]); + } + + + QModelIndex parent(QModelIndex& index= QModelIndex()) + { + return QModelIndex(); + } + +/* + Removing a continuous block of rows will remove filtered rows too as this is + the users intention. +*/ + bool removeRows(int row, int count, QModelIndex &parent = QModelIndex()); + { + if (row < 0 || count <= 0 || row + count > rowCount(parent) || parent.isValid()) + return false; + int lastRow = row + count - 1; + disconnect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex &, int, int)), + this, SLOT(sourceRowsRemoved(QModelIndex &, int, int))); + beginRemoveRows(parent, row, lastRow); + int oldCount = rowCount(); + int start = sourceModel().rowCount() - m_sourceRow.value(row); + int end = sourceModel().rowCount() - m_sourceRow.value(lastRow); + sourceModel().removeRows(start, end - start + 1); + endRemoveRows(); + connect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex &, int, int)), + this, SLOT(sourceRowsRemoved(QModelIndex &, int, int))); + m_loaded = false; + if (oldCount - count != rowCount()) + reset(); + return true; + } + + + QVariant data(QModelIndex &index, int role = Qt.DisplayRole); + { + return QAbstractProxyModel::data(index, role); + } + +private slots: + + void sourceReset() + { + m_loaded = false; + reset(); + } + + + void sourceDataChanged(QModelIndex &topLeft, QModelIndex &bottomRight) + { + emit dataChanged(mapFromSource(topLeft), mapFromSource(bottomRight)); + } + + void sourceRowsRemoved(QModelIndex &, int start, int end) + { + Q_UNUSED(start); + Q_UNUSED(end); + sourceReset(); + } + + + void sourceRowsInserted(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(); + } + +private: + void load() + { + 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; + } + + + mutable QList m_sourceRow; + mutable QHash 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: + +/* + Maps the first bunch of items of the source model to the root +*/ +HistoryMenuModel(HistoryTreeModel *sourceModel, QObject *parent = null) +{ + super(parent); + m_treeModel = sourceModel; + setSourceModel(sourceModel); +} + +int columnCount(QModelIndex &parent) +{ + return m_treeModel.columnCount(mapToSource(parent)); +} + + int rowCount(QModelIndex &parent = QModelIndex()); + { + 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 mapFromSource(QModelIndex &sourceIndex) + { + // currently not used or autotested + Q_ASSERT(false); + int sr = m_treeModel.mapToSource(sourceIndex).row(); + return createIndex(sourceIndex.row(), sourceIndex.column(), sr); + } + + QModelIndex mapToSource(QModelIndex &proxyIndex) + { + 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 index(int, int, QModelIndex &parent = QModelIndex()); + { + 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 parent(QModelIndex &index = QModelIndex()); + { + 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); + } + + int bumpedRows() + { + QModelIndex first = m_treeModel.index(0, 0); + if (!first.isValid()) + return 0; + return qMin(m_treeModel.rowCount(first), MOVEDROWS); + } + +private: + + HistoryTreeModel *m_treeModel; +} + +// Menu that is dynamically populated from the history +class HistoryMenu : public ModelMenu +{ + Q_OBJECT + +signals: + void openUrl(QUrl &url); + +public: + + this(QWidget *parent = null) + { + super(parent); + m_history = 0; + connect(this, SIGNAL(activated(QModelIndex &)), + this, SLOT(activated(QModelIndex &))); + setHoverRole(HistoryModel.UrlStringRole); + } + +void setInitialActions(QList actions) +{ + m_initialActions = actions; + for (int i = 0; i < m_initialActions.count(); ++i) + addAction(m_initialActions.at(i)); +} + +protected: + + bool 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 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); + } + +private slots: + void activated(QModelIndex &index) + { + emit openUrl(index.data(HistoryModel.UrlRole).toUrl()); + } + + void showHistoryDialog() + { + HistoryDialog *dialog = new HistoryDialog(this); + connect(dialog, SIGNAL(openUrl(QUrl&)), + this, SIGNAL(openUrl(QUrl&))); + dialog.show(); + } + + +private: + + HistoryManager *m_history; + HistoryMenuModel *m_historyMenuModel; + QList 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: + this(QObject *parent = null) + { + super(parent); + } + + QVariant data(QModelIndex &index, int role) + { + 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 rowCount(QModelIndex &parent = QModelIndex()) + { + return (parent.isValid() || !sourceModel()) ? 0 : sourceModel().rowCount(parent) * 2; + } + + int columnCount(QModelIndex &parent = QModelIndex()) + { + return (parent.isValid()) ? 0 : 1; + } + + QModelIndex mapFromSource(QModelIndex &sourceIndex) + { + int row = sourceIndex.row() * 2; + return index(row, sourceIndex.column()); + } + + + QModelIndex mapToSource(QModelIndex &proxyIndex) + { + if (!sourceModel()) + return QModelIndex(); + int row = proxyIndex.row() / 2; + return sourceModel().index(row, proxyIndex.column()); + } + + QModelIndex index(int, int, QModelIndex& = QModelIndex()) + { + if (row < 0 || row >= rowCount(parent) + || column < 0 || column >= columnCount(parent)) + return QModelIndex(); + return createIndex(row, column, 0); + } + + QModelIndex parent(QModelIndex& index= QModelIndex()); + { + return QModelIndex(); + } + + void setSourceModel(QAbstractItemModel *sourceModel); + { + if (sourceModel()) { + disconnect(sourceModel(), SIGNAL(modelReset()), this, SLOT(sourceReset())); + disconnect(sourceModel(), SIGNAL(rowsInserted(QModelIndex &, int, int)), + this, SLOT(sourceReset())); + disconnect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex &, int, int)), + this, SLOT(sourceReset())); + } + + QAbstractProxyModel::setSourceModel(newSourceModel); + + if (newSourceModel) { + connect(newSourceModel, SIGNAL(modelReset()), this, SLOT(sourceReset())); + connect(sourceModel(), SIGNAL(rowsInserted(QModelIndex &, int, int)), + this, SLOT(sourceReset())); + connect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex &, int, int)), + this, SLOT(sourceReset())); + } + + reset(); + } + +private slots: + + void sourceReset() + { + reset(); + } +} + +// 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: + + this(QAbstractItemModel *sourceModel, QObject *parent = null) + { + super(parent); + setSourceModel(sourceModel); + } + + QVariant data(QModelIndex &index, int role = Qt.DisplayRole); + { + 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 columnCount(QModelIndex &parent); + { + return sourceModel().columnCount(mapToSource(parent)); + } + + + int rowCount(QModelIndex &parent = QModelIndex()) +{ + 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); +} + + + QModelIndex mapFromSource(QModelIndex &sourceIndex); + { + if (!sourceIndex.isValid()) + return QModelIndex(); + + if (m_sourceRowCache.isEmpty()) + rowCount(QModelIndex()); + + QList::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); + } + + + QModelIndex mapToSource(QModelIndex &proxyIndex) + { + int offset = proxyIndex.internalId(); + if (offset == 0) + return QModelIndex(); + int startDateRow = sourceDateRow(offset - 1); + return sourceModel().index(startDateRow + proxyIndex.row(), proxyIndex.column()); + } + + + QModelIndex index(int row, int column, QModelIndex &parent = QModelIndex()) + { + 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 parent(QModelIndex &index= QModelIndex()) + { + int offset = index.internalId(); + if (offset == 0 || !index.isValid()) + return QModelIndex(); + return createIndex(offset - 1, 0, 0); + } + + + bool hasChildren(QModelIndex &parent = QModelIndex()) + { + QModelIndex grandparent = parent.parent(); + if (!grandparent.isValid()) + return true; + return false; + } + + + Qt.ItemFlags flags(QModelIndex &index) + { + if (!index.isValid()) + return Qt.NoItemFlags; + return Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled; + } + + bool removeRows(int row, int count, QModelIndex &parent = QModelIndex()); +{ + 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; +} + + + QVariant headerData(int section, Qt.Orientation orientation, int role = Qt.DisplayRole) +{ + return sourceModel().headerData(section, orientation, role); +} + + + +void setSourceModel(QAbstractItemModel *newSourceModel) +{ + if (sourceModel()) { + disconnect(sourceModel(), SIGNAL(modelReset()), this, SLOT(sourceReset())); + disconnect(sourceModel(), SIGNAL(layoutChanged()), this, SLOT(sourceReset())); + disconnect(sourceModel(), SIGNAL(rowsInserted(QModelIndex &, int, int)), + this, SLOT(sourceRowsInserted(QModelIndex &, int, int))); + disconnect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex &, int, int)), + this, SLOT(sourceRowsRemoved(QModelIndex &, int, int))); + } + + QAbstractProxyModel::setSourceModel(newSourceModel); + + if (newSourceModel) { + connect(sourceModel(), SIGNAL(modelReset()), this, SLOT(sourceReset())); + connect(sourceModel(), SIGNAL(layoutChanged()), this, SLOT(sourceReset())); + connect(sourceModel(), SIGNAL(rowsInserted(QModelIndex &, int, int)), + this, SLOT(sourceRowsInserted(QModelIndex &, int, int))); + connect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex &, int, int)), + this, SLOT(sourceRowsRemoved(QModelIndex &, int, int))); + } + + reset(); +} + + +private slots: + + void sourceReset() + { + m_sourceRowCache.clear(); + reset(); + } + + void sourceRowsInserted(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(); + } + } + + + void sourceRowsRemoved(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::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(); + } +} + +private: + + // Translate the top level date row into the offset where that date starts + int sourceDateRow(int row) + { + 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); + } + + mutable QList 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: + this(QObject *parent = null) + { + super(parent); + setSortRole(HistoryModel.DateTimeRole); + setFilterCaseSensitivity(Qt.CaseInsensitive); + } + +protected: + bool filterAcceptsRow(int source_row, QModelIndex &source_parent) + { + if (!source_parent.isValid()) + return true; + return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent); + } +} + +#include "ui_history.h" + +class HistoryDialog : public QDialog, public Ui_HistoryDialog +{ + Q_OBJECT + +signals: + void openUrl(QUrl &url); + +public: + + this(QWidget *parent = null, HistoryManager *history = null) : QDialog(parent) + { + HistoryManager *history = setHistory; + if (!history) + history = BrowserApplication::historyManager(); + setupUi(this); + tree.setUniformRowHeights(true); + tree.setSelectionBehavior(QAbstractItemView::SelectRows); + tree.setTextElideMode(Qt.ElideMiddle); + QAbstractItemModel *model = history.historyTreeModel(); + TreeProxyModel *proxyModel = new TreeProxyModel(this); + connect(search, SIGNAL(textChanged(QString)), + proxyModel, SLOT(setFilterFixedString(QString))); + connect(removeButton, SIGNAL(clicked()), tree, SLOT(removeOne())); + connect(removeAllButton, SIGNAL(clicked()), history, SLOT(clear())); + proxyModel.setSourceModel(model); + tree.setModel(proxyModel); + tree.setExpanded(proxyModel.index(0, 0), true); + tree.setAlternatingRowColors(true); + QFontMetrics fm(font()); + int header = fm.width(QLatin1Char('m')) * 40; + tree.header().resizeSection(0, header); + tree.header().setStretchLastSection(true); + connect(tree, SIGNAL(activated(QModelIndex&)), + this, SLOT(open())); + tree.setContextMenuPolicy(Qt.CustomContextMenu); + connect(tree, SIGNAL(customContextMenuRequested(QPoint &)), + this, SLOT(customContextMenuRequested(QPoint &))); + } + +private slots: + + void customContextMenuRequested(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 open() + { + QModelIndex index = tree.currentIndex(); + if (!index.parent().isValid()) + return; + emit openUrl(index.data(HistoryModel.UrlRole).toUrl()); + } + + void copy() + { + QModelIndex index = tree.currentIndex(); + if (!index.parent().isValid()) + return; + QString url = index.data(HistoryModel.UrlStringRole).toString(); + + QClipboard *clipboard = QApplication::clipboard(); + clipboard.setText(url); + } +} diff -r 3cb15c92ac28 -r 71b382c10ef6 demos/browser/htmls/htmls.qrc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/demos/browser/htmls/htmls.qrc Sun May 17 18:49:59 2009 +0000 @@ -0,0 +1,5 @@ + + + notfound.html + + diff -r 3cb15c92ac28 -r 71b382c10ef6 demos/browser/htmls/notfound.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/demos/browser/htmls/notfound.html Sun May 17 18:49:59 2009 +0000 @@ -0,0 +1,63 @@ + + +%1 + + + +

+ +

%2

+

When connecting to: %3.

+
    +
  • Check the address for errors such as ww.trolltech.com + instead of www.trolltech.com
  • +
  • If the address is correct, try checking the network + connection.
  • +
  • If your computer or network is protected by a firewall or + proxy, make sure that the browser demo is permitted to access + the network.
  • +
+

+
+ + diff -r 3cb15c92ac28 -r 71b382c10ef6 demos/browser/main.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/demos/browser/main.d Sun May 17 18:49:59 2009 +0000 @@ -0,0 +1,53 @@ +/**************************************************************************** +** +** Copyright (C) 2009 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. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import browserapplication; + +int main(char[][] args) +{ + Q_INIT_RESOURCE(data); + scope application = new BrowserApplication(args); + if (!application.isTheOnlyBrowser()) + return 0; + application.newMainWindow(); + return application.exec(); +} + diff -r 3cb15c92ac28 -r 71b382c10ef6 demos/browser/modelmenu.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/demos/browser/modelmenu.d Sun May 17 18:49:59 2009 +0000 @@ -0,0 +1,254 @@ +/**************************************************************************** +** +** Copyright (C) 2009 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. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtGui.QMenu +import QtCore.QAbstractItemModel; + +import modelmenu; + +import QtCore.QAbstractItemModel; +import qdebug; + + + +// 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 = null) +{ + super(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())); +} + + void setModel(QAbstractItemModel *model) +{ + m_model = model; +} + + QAbstractItemModel *model() const +{ + return m_model; +} + + void setMaxRows(int max) +{ + m_maxRows = max; +} + + int maxRows() const +{ + return m_maxRows; +} + + void setFirstSeparator(int offset) +{ + m_firstSeparator = offset; +} + + int firstSeparator() const +{ + return m_firstSeparator; +} + + void setRootIndex(const QModelIndex &index) +{ + m_root = index; +} + QModelIndex rootIndex() const +{ + return m_root; +} + + void setHoverRole(int role) +{ + m_hoverRole = role; +} + int hoverRole() const +{ + return m_hoverRole; +} + + void setSeparatorRole(int role) +{ + m_separatorRole = role; +} + + int separatorRole() const +{ + return m_separatorRole; +} + + QAction *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); +} + +protected: + // add any actions before the tree, return true if any actions are added. + virtual bool prePopulated() +{ + return false; +} + // 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 = null, QMenu *menu = null) +{ + if (!menu) { + QString title = parent.data().toString(); + menu = new QMenu(title, this); + QIcon icon = qvariant_cast(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(); + } +} + +private slots: +Q_DECLARE_METATYPE(QModelIndex) +void aboutToShow() +{ + if (QMenu *menu = qobject_cast(sender())) { + QVariant v = menu.menuAction().data(); + if (v.canConvert()) { + QModelIndex idx = qvariant_cast(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 triggered(QAction *action) +{ + QVariant v = action.data(); + if (v.canConvert()) { + QModelIndex idx = qvariant_cast(v); + emit activated(idx); + } +} + + void hovered(QAction *action) +{ + QVariant v = action.data(); + if (v.canConvert()) { + QModelIndex idx = qvariant_cast(v); + QString hoveredString = idx.data(m_hoverRole).toString(); + if (!hoveredString.isEmpty()) + emit hovered(hoveredString); + } +} + +private: + QAction *makeAction(const QModelIndex &index); +{ + QIcon icon = qvariant_cast(index.data(Qt.DecorationRole)); + QAction *action = makeAction(icon, index.data().toString(), this); + QVariant v; + v.setValue(index); + action.setData(v); + return action; +} + + int m_maxRows; + int m_firstSeparator; + int m_maxWidth; + int m_hoverRole; + int m_separatorRole; + QAbstractItemModel *m_model; + QPersistentModelIndex m_root; +} diff -r 3cb15c92ac28 -r 71b382c10ef6 demos/browser/networkaccessmanager.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/demos/browser/networkaccessmanager.d Sun May 17 18:49:59 2009 +0000 @@ -0,0 +1,189 @@ +/**************************************************************************** +** +** Copyright (C) 2009 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. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +module networkaccessmanager; + + +import QtNetwork.QNetworkAccessManager; + +import browserapplication; +import browsermainwindow; +import ui_passworddialog; +import ui_proxy; + +import QtCore.QSettings; + +import QtGui.QDesktopServices; +import QtGui.QDialog; +import QtGui.QMessageBox; +import QtGui.QStyle; +import QtGui.QTextDocument; + +import QtNetwork.QAuthenticator; +import QtNetwork.QNetworkDiskCache; +import QtNetwork.QNetworkProxy; +import QtNetwork.QNetworkReply; +import QtNetwork.QSslError; + + +class NetworkAccessManager : public QNetworkAccessManager +{ + Q_OBJECT + +public: + this(QObject *parent = null) +{ + super(parent); + + connect(this, SIGNAL(authenticationRequired(QNetworkReply*, QAuthenticator*)), + SLOT(authenticationRequired(QNetworkReply*,QAuthenticator*))); + connect(this, SIGNAL(proxyAuthenticationRequired(const QNetworkProxy&, QAuthenticator*)), + SLOT(proxyAuthenticationRequired(const QNetworkProxy&, QAuthenticator*))); +version(QT_NO_OPENSSL) { + connect(this, SIGNAL(sslErrors(QNetworkReply*, const QList&)), + SLOT(sslErrors(QNetworkReply*, const QList&))); +} + loadSettings(); + + QNetworkDiskCache *diskCache = new QNetworkDiskCache(this); + QString location = QDesktopServices::storageLocation(QDesktopServices::CacheLocation); + diskCache.setCacheDirectory(location); + setCache(diskCache); +} + +private: + QList sslTrustedHostList; + +public slots: + void loadSettings() +{ + QSettings settings; + settings.beginGroup(QLatin1String("proxy")); + QNetworkProxy proxy; + if (settings.value(QLatin1String("enabled"), false).toBool()) { + if (settings.value(QLatin1String("type"), 0).toInt() == 0) + proxy = QNetworkProxy::Socks5Proxy; + else + proxy = QNetworkProxy::HttpProxy; + proxy.setHostName(settings.value(QLatin1String("hostName")).toString()); + proxy.setPort(settings.value(QLatin1String("port"), 1080).toInt()); + proxy.setUser(settings.value(QLatin1String("userName")).toString()); + proxy.setPassword(settings.value(QLatin1String("password")).toString()); + } + setProxy(proxy); +} + + +private slots: + void 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("Enter username and password for \"%1\" at %2"); + 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 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("Connect to proxy \"%1\" using:"); + 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()); + } +} + +version(QT_NO_OPENSSL) { +void sslErrors(QNetworkReply *reply, const QList &error) +{ + // check if SSL certificate has been trusted already + QString replyHost = reply.url().host() + ":" + reply.url().port(); + if(! sslTrustedHostList.contains(replyHost)) { + BrowserMainWindow *mainWindow = BrowserApplication::instance().mainWindow(); + + QStringList errorStrings; + for (int i = 0; i < error.count(); ++i) + errorStrings += error.at(i).errorString(); + QString errors = errorStrings.join(QLatin1String("\n")); + int ret = QMessageBox::warning(mainWindow, QCoreApplication::applicationName(), + tr("SSL Errors:\n\n%1\n\n%2\n\n" + "Do you want to ignore these errors for this host?").arg(reply.url().toString()).arg(errors), + QMessageBox::Yes | QMessageBox::No, + QMessageBox::No); + if (ret == QMessageBox::Yes) { + reply.ignoreSslErrors(); + sslTrustedHostList.append(replyHost); + } + } +} +} + +} diff -r 3cb15c92ac28 -r 71b382c10ef6 demos/browser/searchlineedit.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/demos/browser/searchlineedit.d Sun May 17 18:49:59 2009 +0000 @@ -0,0 +1,272 @@ +/**************************************************************************** +** +** Copyright (C) 2009 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. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import urllineedit; + +import QtGui.QLineEdit; +import QtGui.QAbstractButton; + +import searchlineedit; + +import QtGui.QPainter; +import QtGui.QMouseEvent; +import QtGui.QMenu; +import QtGui.QStyle; +import QtGui.QStyleOptionFrameV2; + +/* +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: + this(QWidget *parent = null) +{ + super(parent); + setCursor(Qt.ArrowCursor); + setToolTip(tr("Clear")); + setVisible(false); + setFocusPolicy(Qt.NoFocus); +} + void 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); +} + +public slots: + void textChanged(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 = null) +{ + super(parent); + m_menu = 0; + setObjectName(QLatin1String("SearchButton")); + setCursor(Qt.ArrowCursor); + setFocusPolicy(Qt.NoFocus); +} + + void 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(); +} + QMenu *m_menu; + +protected: + void 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); +} +} + +class SearchLineEdit : public ExLineEdit +{ + Q_OBJECT + Q_PROPERTY(QString inactiveText READ inactiveText WRITE setInactiveText) + +signals: + void textChanged(QString &text); + +public: +/* + 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 + */ +this(QWidget *parent = null) : ExLineEdit(parent), + m_searchButton(new SearchButton(this)) +{ + connect(lineEdit(), SIGNAL(textChanged(QString &)), + this, SIGNAL(textChanged(QString &))); + setLeftWidget(m_searchButton); + m_inactiveText = tr("Search"); + + QSizePolicy policy = sizePolicy(); + setSizePolicy(QSizePolicy::Preferred, policy.verticalPolicy()); +} + + QString inactiveText() +{ + return m_inactiveText; +} + + void setInactiveText(QString &text) +{ + m_inactiveText = text; +} + + QMenu *menu() +{ + if (!m_searchButton.m_menu) { + m_searchButton.m_menu = new QMenu(m_searchButton); + if (isVisible()) + (const_cast(this)).updateGeometries(); + } + return m_searchButton.m_menu; +} + + void setMenu(QMenu *menu) +{ + if (m_searchButton.m_menu) + m_searchButton.m_menu.deleteLater(); + m_searchButton.m_menu = menu; + updateGeometries(); +} + +protected: + void resizeEvent(QResizeEvent *event) +{ + updateGeometries(); + ExLineEdit.resizeEvent(event); +} + + void 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); + } +} + +private: + void updateGeometries() +{ + int menuHeight = height(); + int menuWidth = menuHeight + 1; + if (!m_searchButton.m_menu) + menuWidth = (menuHeight / 5) * 4; + m_searchButton.resize(QSize(menuWidth, menuHeight)); +} + + SearchButton *m_searchButton; + QString m_inactiveText; +} diff -r 3cb15c92ac28 -r 71b382c10ef6 demos/browser/settings.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/demos/browser/settings.d Sun May 17 18:49:59 2009 +0000 @@ -0,0 +1,339 @@ +/**************************************************************************** +** +** Copyright (C) 2009 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. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ +module settings; + + +import QtGui.QDialog; +import ui_settings; + + +import settings; + +import browserapplication; +import browsermainwindow; +import cookiejar; +import history; +import networkaccessmanager; +import webview; + +import QtCore.QSettings; +import QtGui.QtGui; +import QtWebKit.QtWebKit; + + +class SettingsDialog : public QDialog, public Ui_Settings +{ + Q_OBJECT + +public: + SettingsDialog(QWidget *parent = null); + : 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 accept(); +{ + saveToSettings(); + QDialog::accept(); +} + +private slots: + void 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 loadFromSettings() +{ + QSettings settings; + settings.beginGroup(QLatin1String("MainWindow")); + QString defaultHome = QLatin1String("http://qtsoftware.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(settings.value(QLatin1String("fixedFont"), fixedFont)); + standardFont = qVariantValue(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(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(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 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 setHomeToCurrentPage() +{ + BrowserMainWindow *mw = static_cast(parent()); + WebView *webView = mw.currentTab(); + if (webView) + homeLineEdit.setText(webView.url().toString()); +} + + void showCookies() +{ + CookiesDialog *dialog = new CookiesDialog(BrowserApplication::cookieJar(), this); + dialog.exec(); +} + void showExceptions() +{ + CookiesExceptionsDialog *dialog = new CookiesExceptionsDialog(BrowserApplication::cookieJar(), this); + dialog.exec(); +} + + void 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 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())); + } +} + +private: + QFont standardFont; + QFont fixedFont; +} diff -r 3cb15c92ac28 -r 71b382c10ef6 demos/browser/squeezelabel.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/demos/browser/squeezelabel.d Sun May 17 18:49:59 2009 +0000 @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** Copyright (C) 2009 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. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ +module squeezelabel; + + +import QtGui.QLabel; + + +class SqueezeLabel : public QLabel +{ + Q_OBJECT + +public: +this(QWidget *parent = null) +{ + super(parent); +} + +protected: + void 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 -r 3cb15c92ac28 -r 71b382c10ef6 demos/browser/tabwidget.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/demos/browser/tabwidget.d Sun May 17 18:49:59 2009 +0000 @@ -0,0 +1,952 @@ +/**************************************************************************** +** +** Copyright (C) 2009 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. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtGui.QTabBar; + +import QtGui.QShortcut; + +import tabwidget; + +import browserapplication; +import browsermainwindow; +import history; +import urllineedit; +import webview; + +import QtGui.QClipboard; +import QtGui.QCompleter; +import QtGui.QListView; +import QtGui.QMenu; +import QtGui.QMessageBox; +import QtGui.QMouseEvent; +import QtGui.QStackedWidget; +import QtGui.QStyle; +import QtGui.QToolButton; + +import QtCore.QDebug; + +/* + 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) +{ + if (-1 == index) + return; + for (int i = count() - 1; i > index; --i) + closeTab(i); + for (int i = index - 1; i >= 0; --i) + closeTab(i); +} + + void reloadTab(int index); + void reloadAllTabs(); + void tabMoveRequested(int fromIndex, int toIndex); + +public: + this(QWidget *parent = null) +{ + super(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())); + } + setTabsClosable(true); + connect(this, SIGNAL(tabCloseRequested(int)), + this, SIGNAL(closeTab(int))); + setSelectionBehaviorOnRemove(QTabBar::SelectPreviousTab); + setMovable(true); +} + +protected: + void mousePressEvent(QMouseEvent* event) +{ + if (event.button() == Qt.LeftButton) + m_dragStartPos = event.pos(); + QTabBar::mousePressEvent(event); +} + + void mouseMoveEvent(QMouseEvent* event) +{ + if (event.buttons() == Qt.LeftButton) { + int diffX = event.pos().x() - m_dragStartPos.x(); + int diffY = event.pos().y() - m_dragStartPos.y(); + if ((event.pos() - m_dragStartPos).manhattanLength() > QApplication::startDragDistance() + && diffX < 3 && diffX > -3 + && diffY < -10) { + QDrag *drag = new QDrag(this); + QMimeData *mimeData = new QMimeData; + QList 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); +} + +private slots: + void selectTabAction() +{ + if (QShortcut *shortCut = qobject_cast(sender())) { + int index = m_tabShortcuts.indexOf(shortCut); + if (index == 0) + index = 10; + setCurrentIndex(index); + } +} + void cloneTab(); +{ + if (QAction *action = qobject_cast(sender())) { + int index = action.data().toInt(); + emit cloneTab(index); + } +} + + void closeTab(); +{ + if (QAction *action = qobject_cast(sender())) { + int index = action.data().toInt(); + emit closeTab(index); + } +} + + void closeOtherTabs() +{ + if (QAction *action = qobject_cast(sender())) { + int index = action.data().toInt(); + emit closeOtherTabs(index); + } +} + +void reloadTab() +{ + if (QAction *action = qobject_cast(sender())) { + int index = action.data().toInt(); + emit reloadTab(index); + } +} + + void 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()); +} + +private: + QList m_tabShortcuts; + friend class TabWidget; + + QPoint m_dragStartPos; + int m_dragCurrentIndex; +} + +import 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: + this(QAction *root, QWebPage::WebAction webAction, QObject *parent) +{ +super(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); +} + + QWebPage::WebAction webAction() const +{ + return m_webAction; +} + + void addChild(QAction *action) +{ + if (!action) + return; + connect(action, SIGNAL(changed()), this, SLOT(childChanged())); +} + + + void 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())); +} + +private slots: + void rootTriggered() +{ + if (m_currentParent) { + QAction *gotoAction = m_currentParent.action(m_webAction); + gotoAction.trigger(); + } +} + + void childChanged() +{ + if (QAction *source = qobject_cast(sender())) { + if (m_root + && m_currentParent + && source.parent() == m_currentParent) { + m_root.setChecked(source.isChecked()); + m_root.setEnabled(source.isEnabled()); + } + } +} + + void rootDestroyed() +{ + m_root = 0; +} + void currentDestroyed() +{ + updateCurrent(0); +} + +private: + QWebPage *m_currentParent; + QAction *m_root; + QWebPage::WebAction m_webAction; +}; + +import QtCore.QUrl; +import 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: + this(QWidget *parent = null) +{ + QTabWidget(parent) + m_recentlyClosedTabsAction = 0; + m_newTabAction = 0; + m_closeTabAction = 0; + m_nextTabAction = 0; + m_previousTabAction = 0; + m_recentlyClosedTabsMenu = 0; + m_lineEditCompleter = 0; + m_lineEdits = 0; + m_tabBar = new TabBar(this); + + + setElideMode(Qt.ElideRight); + + connect(m_tabBar, SIGNAL(newTab()), this, SLOT(newTab())); + connect(m_tabBar, SIGNAL(closeTab(int)), this, SLOT(closeTab(int))); + connect(m_tabBar, SIGNAL(cloneTab(int)), this, SLOT(cloneTab(int))); + connect(m_tabBar, SIGNAL(closeOtherTabs(int)), this, SLOT(closeOtherTabs(int))); + connect(m_tabBar, SIGNAL(reloadTab(int)), this, SLOT(reloadTab(int))); + connect(m_tabBar, SIGNAL(reloadAllTabs()), this, SLOT(reloadAllTabs())); + connect(m_tabBar, SIGNAL(tabMoved(int, int)), this, SLOT(moveTab(int, int))); + setTabBar(m_tabBar); + setDocumentMode(true); + + // Actions + m_newTabAction = new QAction(QIcon(QLatin1String(":addtab.png")), tr("New &Tab"), this); + m_newTabAction.setShortcuts(QKeySequence::AddTab); + m_newTabAction.setIconVisibleInMenu(false); + connect(m_newTabAction, SIGNAL(triggered()), this, SLOT(newTab())); + + m_closeTabAction = new QAction(QIcon(QLatin1String(":closetab.png")), tr("&Close Tab"), this); + m_closeTabAction.setShortcuts(QKeySequence::Close); + m_closeTabAction.setIconVisibleInMenu(false); + connect(m_closeTabAction, SIGNAL(triggered()), this, SLOT(closeTab())); + + m_nextTabAction = new QAction(tr("Show Next Tab"), this); + QList shortcuts; + shortcuts.append(QKeySequence(Qt.CTRL | Qt.Key_BraceRight)); + shortcuts.append(QKeySequence(Qt.CTRL | Qt.Key_PageDown)); + shortcuts.append(QKeySequence(Qt.CTRL | Qt.Key_BracketRight)); + shortcuts.append(QKeySequence(Qt.CTRL | Qt.Key_Less)); + m_nextTabAction.setShortcuts(shortcuts); + connect(m_nextTabAction, SIGNAL(triggered()), this, SLOT(nextTab())); + + m_previousTabAction = new QAction(tr("Show Previous Tab"), this); + shortcuts.clear(); + shortcuts.append(QKeySequence(Qt.CTRL | Qt.Key_BraceLeft)); + shortcuts.append(QKeySequence(Qt.CTRL | Qt.Key_PageUp)); + shortcuts.append(QKeySequence(Qt.CTRL | Qt.Key_BracketLeft)); + shortcuts.append(QKeySequence(Qt.CTRL | Qt.Key_Greater)); + m_previousTabAction.setShortcuts(shortcuts); + connect(m_previousTabAction, SIGNAL(triggered()), this, SLOT(previousTab())); + + m_recentlyClosedTabsMenu = new QMenu(this); + connect(m_recentlyClosedTabsMenu, SIGNAL(aboutToShow()), + this, SLOT(aboutToShowRecentTabsMenu())); + connect(m_recentlyClosedTabsMenu, SIGNAL(triggered(QAction *)), + this, SLOT(aboutToShowRecentTriggeredAction(QAction *))); + m_recentlyClosedTabsAction = new QAction(tr("Recently Closed Tabs"), this); + m_recentlyClosedTabsAction.setMenu(m_recentlyClosedTabsMenu); + m_recentlyClosedTabsAction.setEnabled(false); + + connect(this, SIGNAL(currentChanged(int)), + this, SLOT(currentChanged(int))); + + m_lineEdits = new QStackedWidget(this); +} + + void 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 addWebAction(QAction *action, QWebPage::WebAction webAction) +{ + if (!action) + return; + m_actions.append(new WebActionMapper(action, webAction, this)); +} + + + QAction *newTabAction() const; +{ + return m_newTabAction; +} + + QAction *closeTabAction() const; +{ + return m_closeTabAction; +} + QAction *recentlyClosedTabsAction() const; +{ + return m_recentlyClosedTabsAction; +} + + QAction *nextTabAction() const +{ + return m_nextTabAction; +} + QAction *previousTabAction() const +{ + return m_previousTabAction; +} + + QWidget *lineEditStack() const +{ + return m_lineEdits; +} + + QLineEdit *currentLineEdit() const +{ + return lineEdit(m_lineEdits.currentIndex()); +} + + WebView *currentWebView() const +{ + return webView(currentIndex()); +} + + WebView *webView(int index) const +{ + QWidget *widget = this.widget(index); + if (WebView *webView = qobject_cast(widget)) { + return webView; + } else { + // optimization to delay creating the first webview + if (count() == 1) { + TabWidget *that = const_cast(this); + that.setUpdatesEnabled(false); + that.newTab(); + that.closeTab(0); + that.setUpdatesEnabled(true); + return currentWebView(); + } + } + return 0; +} + + QLineEdit *lineEdit(int index) const +{ + UrlLineEdit *urlLineEdit = qobject_cast(m_lineEdits.widget(index)); + if (urlLineEdit) + return urlLineEdit.lineEdit(); + return 0; +} + + int webViewIndex(WebView *webView) const +{ + int index = indexOf(webView); + return index; +} + + +static const qint32 TabWidgetMagic = 0xaa; + +QByteArray 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(widget(i))) { + tabs.append(tab.url().toString()); + } else { + tabs.append(QString::null); + } + } + stream << tabs; + stream << currentIndex(); + return data; +} + + bool 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; +} + +protected: + void 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 contextMenuEvent(QContextMenuEvent *event) +{ + if (!childAt(event.pos())) { + m_tabBar.contextMenuRequested(event.pos()); + return; + } + QTabWidget::contextMenuEvent(event); +} + + void 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); + } + } +} + +public slots: + void loadUrlInCurrentTab(const QUrl &url) +{ + WebView *webView = currentWebView(); + if (webView) { + webView.loadUrl(url); + webView.setFocus(); + } +} + + WebView *newTab(bool makeCurrent = true) +{ + // 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(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; +} + + +// When index is -1 index chooses the current tab + void cloneTab(int index = -1) +{ + 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 closeTab(int index = -1); +{ + 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 closeOtherTabs(int index); +// When index is -1 index chooses the current tab + void reloadTab(int index = -1); +{ + if (index < 0) + index = currentIndex(); + if (index < 0 || index >= count()) + return; + + QWidget *widget = this.widget(index); + if (WebView *tab = qobject_cast(widget)) + tab.reload(); +} + + void reloadAllTabs(); +{ + for (int i = 0; i < count(); ++i) { + QWidget *tabWidget = widget(i); + if (WebView *tab = qobject_cast(tabWidget)) { + tab.reload(); + } + } +} + void nextTab() +{ + int next = currentIndex() + 1; + if (next == count()) + next = 0; + setCurrentIndex(next); +} + + + void previousTab() +{ + int next = currentIndex() - 1; + if (next < 0) + next = count() - 1; + setCurrentIndex(next); +} + +private slots: + void currentChanged(int index) +{ + WebView *webView = this.webView(index); + if (!webView) + return; + + 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(); +} + + void 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 aboutToShowRecentTriggeredAction(QAction *action) +{ + QUrl url = action.data().toUrl(); + loadUrlInCurrentTab(url); +} + + void webViewLoadStarted() +{ + WebView *webView = qobject_cast(sender()); + int index = webViewIndex(webView); + if (-1 != index) { + QIcon icon(QLatin1String(":loading.gif")); + setTabIcon(index, icon); + } +} + + void webViewIconChanged() +{ + WebView *webView = qobject_cast(sender()); + int index = webViewIndex(webView); + if (-1 != index) { + QIcon icon = BrowserApplication::instance().icon(webView.url()); + setTabIcon(index, icon); + } +} + + void webViewTitleChanged(const QString &title) +{ + WebView *webView = qobject_cast(sender()); + int index = webViewIndex(webView); + if (-1 != index) { + setTabText(index, title); + } + if (currentIndex() == index) + emit setCurrentTitle(title); + BrowserApplication::historyManager().updateHistoryItem(webView.url(), title); +} + + void webViewUrlChanged(const QUrl &url) +{ + WebView *webView = qobject_cast(sender()); + int index = webViewIndex(webView); + if (-1 != index) { + m_tabBar.setTabData(index, url); + } + emit tabsChanged(); +} + + void lineEditReturnPressed() +{ + if (QLineEdit *lineEdit = qobject_cast(sender())) { + emit loadPage(lineEdit.text()); + if (m_lineEdits.currentWidget() == lineEdit) + currentWebView().setFocus(); + } +} + + void windowCloseRequested() +{ + WebPage *webPage = qobject_cast(sender()); + WebView *webView = qobject_cast(webPage.view()); + int index = webViewIndex(webView); + if (index >= 0) { + if (count() == 1) + webView.webPage().mainWindow().close(); + else + closeTab(index); + } +} + + void moveTab(int fromIndex, int toIndex) +{ + QWidget *lineEdit = m_lineEdits.widget(fromIndex); + m_lineEdits.removeWidget(lineEdit); + m_lineEdits.insertWidget(toIndex, lineEdit); +} + +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 m_recentlyClosedTabs; + QList m_actions; + + QCompleter *m_lineEditCompleter; + QStackedWidget *m_lineEdits; + TabBar *m_tabBar; +} diff -r 3cb15c92ac28 -r 71b382c10ef6 demos/browser/toolbarsearch.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/demos/browser/toolbarsearch.d Sun May 17 18:49:59 2009 +0000 @@ -0,0 +1,191 @@ +/**************************************************************************** +** +** Copyright (C) 2009 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. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ +module toolbarsearch; + +import searchlineedit; + +import autosaver; + +import QtCore.QSettings; +import QtCore.QUrl; + +import QtGui.QCompleter; +import QtGui.QMenu; +import QtGui.QStringListModel; + +import QtWebKit/QWebSettings; + +/* +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 is a very basic search widget that also contains a small history. + Searches are turned into urls that use Google to perform search + */ +this(QWidget *parent = null) + +{ + super(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(); +} + ~this() +{ + m_autosaver.saveIfNeccessary(); +} + +public slots: + void clear() +{ + m_stringListModel.setStringList(QStringList()); + m_autosaver.changeOccurred();; +} + + void 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); +} + +private slots: + void save() +{ + QSettings settings; + settings.beginGroup(QLatin1String("toolbarsearch")); + settings.setValue(QLatin1String("recentSearches"), m_stringListModel.stringList()); + settings.setValue(QLatin1String("maximumSaved"), m_maxSavedSearches); + settings.endGroup(); +} + + + void 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 triggeredMenuAction(QAction *action) +{ + QVariant v = action.data(); + if (v.canConvert()) { + QString text = v.toString(); + lineEdit().setText(text); + searchNow(); + } +} + +private: + void 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(); +} + + AutoSaver *m_autosaver; + int m_maxSavedSearches; + QStringListModel *m_stringListModel; +} diff -r 3cb15c92ac28 -r 71b382c10ef6 demos/browser/urllineedit.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/demos/browser/urllineedit.d Sun May 17 18:49:59 2009 +0000 @@ -0,0 +1,378 @@ +/**************************************************************************** +** +** Copyright (C) 2009 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. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +module urllineedit; + +import QtCore.QUrl; +import QtGui.QWidget; +import QtGui.QStyleOptionFrame; + +import browserapplication; +import searchlineedit; +import webview; + +import QtCore.QEvent; + +import QtGui.QApplication; +import QtGui.QCompleter; +import QtGui.QFocusEvent; +import QtGui.QHBoxLayout; +import QtGui.QLabel; +import QtGui.QLineEdit; +import QtGui.QPainter; +import QtGui.QStyle; +import QtGui.QStyleOptionFrameV2; + +import QtCore.QDebug; + + +/* +QT_BEGIN_NAMESPACE +class QLineEdit; +QT_END_NAMESPACE + +class ClearButton; +*/ +class ExLineEdit : public QWidget +{ + Q_OBJECT + +public: + this(QWidget *parent = null) +{ + super(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&))); +} + + inline QLineEdit *lineEdit() { return m_lineEdit; } + + void setLeftWidget(QWidget *widget) +{ + m_leftWidget = widget; +} + + QWidget *leftWidget() +{ + return m_leftWidget; +} + + QSize sizeHint() +{ + m_lineEdit.setFrame(true); + QSize size = m_lineEdit.sizeHint(); + m_lineEdit.setFrame(false); + return size; +} + + QVariant inputMethodQuery(Qt.InputMethodQuery property) +{ + return m_lineEdit.inputMethodQuery(property); +} + +protected: + void focusInEvent(QFocusEvent *event) +{ + m_lineEdit.event(event); + QWidget::focusInEvent(event); +} + + void 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 keyPressEvent(QKeyEvent *event) +{ + m_lineEdit.event(event); +} + + void paintEvent(QPaintEvent *event) +{ + QPainter p(this); + QStyleOptionFrameV2 panel; + initStyleOption(&panel); + style().drawPrimitive(QStyle::PE_PanelLineEdit, &panel, &p, this); +} + + void resizeEvent(QResizeEvent *event) +{ + Q_ASSERT(m_leftWidget); + updateGeometries(); + QWidget::resizeEvent(event); +} + + void inputMethodEvent(QInputMethodEvent *e) +{ + m_lineEdit.event(e); +} + + bool event(QEvent *event) +{ + if (event.type() == QEvent::ShortcutOverride) + return m_lineEdit.event(event); + return QWidget::event(event); +} + +protected: + void 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 initStyleOption(QStyleOptionFrameV2 *option) +{ + 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; +version(QT_KEYPAD_NAVIGATION) + if (hasEditFocus()) + option.state |= QStyle::State_HasEditFocus; +} + option.features = QStyleOptionFrameV2::None; +} + + QWidget *m_leftWidget; + QLineEdit *m_lineEdit; + ClearButton *m_clearButton; +}; + +class UrlIconLabel : public QLabel +{ + +public: + this(QWidget *parent) +{ + super(parent); + m_webView = 0; + setMinimumWidth(16); + setMinimumHeight(16); +} + + + WebView *m_webView; + +protected: + void mousePressEvent(QMouseEvent *event) +{ + if (event.button() == Qt.LeftButton) + m_dragStartPos = event.pos(); + QLabel::mousePressEvent(event); +} + + void mouseMoveEvent(QMouseEvent *event) +{ + if (event.buttons() == Qt.LeftButton + && (event.pos() - m_dragStartPos).manhattanLength() > QApplication::startDragDistance() + && m_webView) { + QDrag *drag = new QDrag(this); + QMimeData *mimeData = new QMimeData; + mimeData.setText(QString::fromUtf8(m_webView.url().toEncoded())); + QList urls; + urls.append(m_webView.url()); + mimeData.setUrls(urls); + drag.setMimeData(mimeData); + drag.exec(); + } +} + +private: + QPoint m_dragStartPos; + +} + + +//class UrlIconLabel; +//class WebView; +class UrlLineEdit : public ExLineEdit +{ + Q_OBJECT + +public: + this(QWidget *parent = null) +{ + super(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 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())); +} + +protected: + void 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); + } +} + + void focusOutEvent(QFocusEvent *event); +{ + if (m_lineEdit.text().isEmpty() && m_webView) + m_lineEdit.setText(QString::fromUtf8(m_webView.url().toEncoded())); + ExLineEdit::focusOutEvent(event); +} + +private slots: + void webViewUrlChanged(const QUrl &url) +{ + m_lineEdit.setText(QString::fromUtf8(url.toEncoded())); + m_lineEdit.setCursorPosition(0); +} + + void 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); +} + +private: + QLinearGradient generateGradient(const QColor &color) +{ + 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; +} + + WebView *m_webView; + UrlIconLabel *m_iconLabel; + QColor m_defaultBaseColor; +} diff -r 3cb15c92ac28 -r 71b382c10ef6 demos/browser/webview.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/demos/browser/webview.d Sun May 17 18:49:59 2009 +0000 @@ -0,0 +1,354 @@ +/**************************************************************************** +** +** Copyright (C) 2009 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. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtWebKit.QWebView; + +import browserapplication; +import browsermainwindow; +import cookiejar; +import downloadmanager; +import networkaccessmanager; +import tabwidget; +import webview; + +import QtGui.QClipboard; +import QtGui.QMenu; +import QtGui.QMessageBox; +import QtGui.QMouseEvent; + +import QtWebKit.QWebHitTestResult; + +import QtUiTools.QUiLoader; + +import QtCore.QDebug; +import QtCore.QBuffer; + +/* +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: + this(QObject *parent = null) +{ + super(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 *mainWindow() +{ + QObject *w = this.parent(); + while (w) { + if (BrowserMainWindow *mw = qobject_cast(w)) + return mw; + w = w.parent(); + } + return BrowserApplication::instance().mainWindow(); +} + +protected: + bool 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 *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(); +} + +version(QT_NO_UITOOLS) {} else +{ + QObject *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()); + } +} + + + +private slots: + void 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 frames; + frames.append(mainFrame()); + while (!frames.isEmpty()) { + QWebFrame *frame = frames.takeFirst(); + if (frame.url() == reply.url()) { + frame.setHtml(html, reply.url()); + return; + } + QList children = frame.childFrames(); + foreach(QWebFrame *frame, children) + frames.append(frame); + } + if (m_loadingUrl == reply.url()) { + mainFrame().setHtml(html, reply.url()); + } +} + +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 = null) + : 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); + +} + + WebPage *webPage() const { return m_page; } + + void loadUrl(const QUrl &url) +{ + m_initialUrl = url; + load(url); +} + + QUrl url() const +{ + QUrl url = QWebView::url(); + if (!url.isEmpty()) + return url; + + return m_initialUrl; +} + + QString lastStatusBarText() const +{ + return m_statusBarText; +} + + inline int progress() const { return m_progress; } + +protected: + void mousePressEvent(QMouseEvent *event); +{ + m_page.m_pressedButtons = event.buttons(); + m_page.m_keyboardModifiers = event.modifiers(); + QWebView::mousePressEvent(event); +} + + void 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 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 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); +} + + +private slots: + void setProgress(int progress) +{ + m_progress = progress; +} + + void loadFinished() +{ + if (100 != m_progress) { + qWarning() << "Recieved finished signal while progress is still:" << progress() + << "Url:" << url(); + } + m_progress = 0; +} + + void setStatusBarText(const QString &string) +{ + m_statusBarText = string; +} + + void downloadRequested(const QNetworkRequest &request) +{ + BrowserApplication::downloadManager().download(request); +} + + void openLinkInNewTab() +{ + m_page.m_openInNewTab = true; + pageAction(QWebPage::OpenLinkInNewWindow).trigger(); +} +private: + QString m_statusBarText; + QUrl m_initialUrl; + int m_progress; + WebPage *m_page; +} diff -r 3cb15c92ac28 -r 71b382c10ef6 demos/browser/xbel.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/demos/browser/xbel.d Sun May 17 18:49:59 2009 +0000 @@ -0,0 +1,359 @@ +/**************************************************************************** +** +** Copyright (C) 2009 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. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +module xbel; + +import QtCore.QXmlStreamReader; +import QtCore.QDateTime; + + +import QtCore.QFile; + +class BookmarkNode +{ +public: + enum Type { + Root, + Folder, + Bookmark, + Separator + }; + + this(Type type = Root, BookmarkNode *parent = null) +{ + expanded = false; + m_parent = parent; + m_type = type; + + if (parent) + parent.add(this); +} + + ~this() +{ + if (m_parent) + m_parent.remove(this); + qDeleteAll(m_children); + m_parent = 0; + m_type = BookmarkNode::Root; +} + + bool 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; +} + + Type type() const +{ + return m_type; +} + + void setType(Type type) +{ + m_type = type; +} + + QList children() const +{ + return m_children; +} + + + BookmarkNode *parent() const +{ + return m_parent; +} + + void add(BookmarkNode *child, int offset = -1) +{ + 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 remove(BookmarkNode *child) +{ + child.m_parent = 0; + m_children.removeAll(child); +} + + QString url; + QString title; + QString desc; + bool expanded; + +private: + BookmarkNode *m_parent; + Type m_type; + QList m_children; + +}; + +class XbelReader : public QXmlStreamReader +{ +public: +this() +{ +} + +BookmarkNode *read(const QString &fileName) +{ + QFile file(fileName); + if (!file.exists()) { + return new BookmarkNode(BookmarkNode::Root); + } + file.open(QFile::ReadOnly); + return read(&file); +} + +BookmarkNode *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; +} + +private: + void skipUnknownElement() +{ + Q_ASSERT(isStartElement()); + + while (!atEnd()) { + readNext(); + + if (isEndElement()) + break; + + if (isStartElement()) + skipUnknownElement(); + } +} + + void 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 readTitle(BookmarkNode *parent) +{ + Q_ASSERT(isStartElement() && name() == QLatin1String("title")); + parent.title = readElementText(); +} + + void readDescription(BookmarkNode *parent) +{ + Q_ASSERT(isStartElement() && name() == QLatin1String("desc")); + parent.desc = readElementText(); +} + + void readSeparator(BookmarkNode *parent) +{ + new BookmarkNode(BookmarkNode::Separator, parent); + // empty elements have a start and end element + readNext(); +} + + + void 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 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"); +} + +}; + +import QtCore.QXmlStreamWriter; + +class XbelWriter : public QXmlStreamWriter +{ +public: + this() +{ + setAutoFormatting(true); +} + bool write(const QString &fileName, const BookmarkNode *root); +{ + QFile file(fileName); + if (!root || !file.open(QFile::WriteOnly)) + return false; + return write(&file, root); +} + +bool write(QIODevice *device, const BookmarkNode *root); +{ + setDevice(device); + + writeStartDocument(); + writeDTD(QLatin1String("")); + 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; +} + + +private: + void 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; + } +} +}