view demos/browser/bookmarks.d @ 45:71b382c10ef6

add coarse and incomplete QT browser port
author mandel
date Sun, 17 May 2009 18:49:59 +0000
parents
children b149ef2cb18b
line wrap: on
line source

/****************************************************************************
**
** 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<BookmarkNode*> others;
    for (int i = m_bookmarkRootNode.children().count() - 1; i >= 0; --i) {
        BookmarkNode *node = m_bookmarkRootNode.children().at(i);
        if (node.type() == BookmarkNode::Folder) {
            // Automatically convert
            if (node.title == tr("Toolbar Bookmarks") && !toolbar) {
                node.title = tr(BOOKMARKBAR);
            }
            if (node.title == tr(BOOKMARKBAR) && !toolbar) {
                toolbar = node;
            }

            // Automatically convert
            if (node.title == tr("Menu") && !menu) {
                node.title = tr(BOOKMARKMENU);
            }
            if (node.title == tr(BOOKMARKMENU) && !menu) {
                menu = node;
            }
        } else {
            others.append(node);
        }
        m_bookmarkRootNode.remove(node);
    }
    Q_ASSERT(m_bookmarkRootNode.children().count() == 0);
    if (!toolbar) {
        toolbar = new BookmarkNode(BookmarkNode::Folder, m_bookmarkRootNode);
        toolbar.title = tr(BOOKMARKBAR);
    } else {
        m_bookmarkRootNode.add(toolbar);
    }

    if (!menu) {
        menu = new BookmarkNode(BookmarkNode::Folder, m_bookmarkRootNode);
        menu.title = tr(BOOKMARKMENU);
    } else {
        m_bookmarkRootNode.add(menu);
    }

    for (int i = 0; i < others.count(); ++i)
        menu.add(others.at(i));
}


    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<BookmarkNode*>(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<BookmarkNode*> children = rootNode.children();
        for (int i = 0; i < children.count(); ++i) {
            BookmarkNode *bookmarkNode = children.at(i);
            rootNode.remove(bookmarkNode);
            row = qMax(0, row);
            BookmarkNode *parentNode = node(parent);
            m_bookmarksManager.addBookmark(parentNode, bookmarkNode, row);
            m_endMacro = true;
        }
        delete rootNode;
    }
    return true;
}


    bool 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<BookmarkNode*>(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<QAction*> 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<QAction*> 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<QUrl> urls = mimeData.urls();
        QAction *action = actionAt(event.pos());
        QString dropText;
        if (action)
            dropText = action.text();
        int row = -1;
        QModelIndex parentIndex = m_root;
        for (int i = 0; i < m_bookmarksModel.rowCount(m_root); ++i) {
            QModelIndex idx = m_bookmarksModel.index(i, 0, m_root);
            QString title = idx.data().toString();
            if (title == dropText) {
                row = i;
                if (m_bookmarksModel.hasChildren(idx)) {
                    parentIndex = idx;
                    row = -1;
                }
                break;
            }
        }
        BookmarkNode *bookmark = new BookmarkNode(BookmarkNode::Bookmark);
        bookmark.url = urls.at(0).toString();
        bookmark.title = mimeData.text();

        BookmarkNode *parent = m_bookmarksModel.node(parentIndex);
        BookmarksManager *bookmarksManager = m_bookmarksModel.bookmarksManager();
        bookmarksManager.addBookmark(parent, bookmark, row);
        event.acceptProposedAction();
    }
    QToolBar::dropEvent(event);
}

private slots:
    void triggered(QAction *action)
{
    QVariant v = action.data();
    if (v.canConvert<QUrl>()) {
        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;
}