view demos/browser/bookmarks.d @ 282:256ab6cb8e85

Signals look-up andNew syntax for connect. The old one will not work from now on. This will allow for the signals overload. Although changes are done for both D1 and D2 versions, D1 won't work because of compiler bugs. I am tired of waiting for fixes.
author eldar
date Fri, 16 Oct 2009 02:43:59 +0000
parents a8d76a9a85aa
children
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$
**
****************************************************************************/

module bookmarks;


import qt.core.QObject;
import qt.core.QAbstractItemModel;
import qt.core.QBuffer;
import qt.core.QFile;
import qt.core.QMimeData;
import qt.core.QPersistentModelIndex;
import qt.gui.QUndoStack;
import qt.gui.QUndoCommand;
import qt.gui.QDesktopServices;
import qt.gui.QDragEnterEvent;
import qt.gui.QFileDialog;
import qt.gui.QHeaderView;
import qt.gui.QIcon;
import qt.gui.QMessageBox;
import qt.gui.QToolButton;
import qt.gui.QTreeView;
import qt.xml.QXmlStreamReader;
import qt.webkit.QWebSettings;

import tango.text.convert.Format;

//import qt.core.QDebug;

import autosaver;
import browserapplication;
import history;
import xbel;


const string BOOKMARKBAR = "Bookmarks Bar";
const string BOOKMARKMENU = "Bookmarks Menu";

// workaround for QList implementation
int indexOf(T)(T[] items, T item)
{
	for(auto i = 0; i < items.length; ++i)
	{
		if(items[i] == item)
			return i;
	}
	return -1;
}

/*!
Bookmark manager, owner of the bookmarks, loads, saves and basic tasks
*/
class BookmarksManager : public QObject
{
	mixin Signal!("entryAdded", BookmarkNode /*item*/);
	mixin Signal!("entryRemoved", BookmarkNode /*parent*/, int /*row*/, BookmarkNode /*item*/);
	mixin Signal!("entryChanged", BookmarkNode /*item*/);

public:

	this(QObject parent = null)
	{
		super(parent);
		m_loaded = false;
		m_saveTimer = new AutoSaver(this);
		m_bookmarkRootNode = null;
		m_bookmarkModel = null;
		this.entryAdded.connect(&m_saveTimer.changeOccurred);
		this.entryRemoved.connect(&m_saveTimer.changeOccurred);
		this.entryChanged.connect(&m_saveTimer.changeOccurred);
	}

	~this()
	{
		m_saveTimer.saveIfNeccessary();
	}

	void addBookmark(BookmarkNode parent, BookmarkNode node, int row = -1)
	{
		if (!m_loaded)
			return;
		assert(parent);
		InsertBookmarksCommand command = new InsertBookmarksCommand(this, parent, node, row);
		m_commands.push(command);
	}

	void removeBookmark(BookmarkNode node)
	{
		if (!m_loaded)
			return;

		assert(node);
		BookmarkNode parent = node.parent();
		int row = indexOf(parent.children(), node);
		RemoveBookmarksCommand command = new RemoveBookmarksCommand(this, parent, row);
		m_commands.push(command);
	}

	void setTitle(BookmarkNode node, string newTitle)
	{
		if (!m_loaded)
			return;

		assert(node);
		ChangeBookmarkCommand command = new ChangeBookmarkCommand(this, node, newTitle, true);
		m_commands.push(command);
	}

	void setUrl(BookmarkNode node, string newUrl)
	{
		if (!m_loaded)
			return;

		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().length - 1; i >= 0; --i) {
			BookmarkNode node = m_bookmarkRootNode.children()[i];
			if (node.title == tr(BOOKMARKMENU))
				return node;
		}
		assert(false);
		return null;
	}

	BookmarkNode toolbar()
	{
		if (!m_loaded)
			load();

		for (int i = m_bookmarkRootNode.children().length - 1; i >= 0; --i) {
			BookmarkNode node = m_bookmarkRootNode.children()[i];
			if (node.title == tr(BOOKMARKBAR))
				return node;
		}
		assert(false);
		return null;
	}

	BookmarksModel bookmarksModel()
	{
		if (!m_bookmarkModel)
			m_bookmarkModel = new BookmarksModel(this, this);
		return m_bookmarkModel;
	}

	QUndoStack undoRedoStack() { return m_commands; };

public:

	void importBookmarks()
	{
		string fileName = QFileDialog.getOpenFileName(null, tr("Open File"), null, tr("XBEL (*.xbel *.xml)"));
		if (fileName.length == 0)
			return;

		auto reader = new XbelReader;
		BookmarkNode importRootNode = reader.read(fileName);
		if (reader.error() != QXmlStreamReader.NoError) {
			QMessageBox.warning(null, "Loading Bookmark",
				Format(tr("Error when loading bookmarks on line %1, column %2:\n"
				"%3"), reader.lineNumber(), reader.columnNumber(), reader.errorString()));
		}

		importRootNode.setType(BookmarkNode.Type.Folder);
		importRootNode.title = Format(tr("Imported %1"), QDate.currentDate().toString(Qt_DateFormat.SystemLocaleShortDate));
		addBookmark(menu(), importRootNode);
	}

	void exportBookmarks()
	{
		string fileName = QFileDialog.getSaveFileName(null, tr("Save File"),
				Format(tr("%1 Bookmarks.xbel"), QCoreApplication.applicationName()),
				tr("XBEL (*.xbel *.xml)"));
		if (fileName.length == 0)
			return;

		auto writer = new XbelWriter;
		if (!writer.write(fileName, m_bookmarkRootNode))
			QMessageBox.critical(null, tr("Export error"), tr("error saving bookmarks"));
	}

private:

	void save()
	{
		if (!m_loaded)
			return;

		auto writer = new XbelWriter;
		string dir = QDesktopServices.storageLocation(QDesktopServices.DataLocation);
		string bookmarkFile = dir ~ "/bookmarks.xbel";
		if (!writer.write(bookmarkFile, m_bookmarkRootNode))
			qWarning("BookmarkManager: error saving to" ~ bookmarkFile);
	}

private:
	
	void load()
	{
		if (m_loaded)
			return;
		m_loaded = true;

		string dir = QDesktopServices.storageLocation(QDesktopServices.DataLocation);
		string bookmarkFile = dir ~ "/bookmarks.xbel";
		if (!QFile.exists(bookmarkFile))
			bookmarkFile = ":defaultbookmarks.xbel";

		XbelReader reader;
		m_bookmarkRootNode = reader.read(bookmarkFile);
		if (reader.error() != QXmlStreamReader.NoError) {
			QMessageBox.warning(null, "Loading Bookmark",
			Format(tr("Error when loading bookmarks on line %1, column %2:\n"
			"%3"), reader.lineNumber(), reader.columnNumber(), reader.errorString()));
		}

		BookmarkNode toolbar = null;
		BookmarkNode menu = null;
		BookmarkNode[] others;
		for (int i = m_bookmarkRootNode.children().length - 1; i >= 0; --i) {
			BookmarkNode node = m_bookmarkRootNode.children()[i];
			if (node.type() == BookmarkNode.Type.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 ~= node;
			}
			m_bookmarkRootNode.remove(node);
		}
		assert(m_bookmarkRootNode.children().length == 0);
		if (!toolbar) {
			toolbar = new BookmarkNode(BookmarkNode.Type.Folder, m_bookmarkRootNode);
			toolbar.title = tr(BOOKMARKBAR);
		} else {
			m_bookmarkRootNode.add(toolbar);
		}

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

		for (int i = 0; i < others.length; ++i)
			menu.add(others[i]);
	}

	bool m_loaded;
	AutoSaver m_saveTimer;
	BookmarkNode m_bookmarkRootNode;
	BookmarksModel m_bookmarkModel;
	QUndoStack m_commands;
}

class RemoveBookmarksCommand : public QUndoCommand
{
public:

	this(BookmarksManager m_bookmarkManager, BookmarkNode parent, int row)
	{
		super(tr("Remove Bookmark"));
		m_row = row;
		m_bookmarkManager = m_bookmarkManager;
		m_node = (row < parent.children().length) ? parent.children()[row] : null;
		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);
		m_bookmarkManager.entryAdded.emit(m_node);
		m_done = false;
	}

	void redo()
	{
		m_parent.remove(m_node);
		m_bookmarkManager.entryRemoved.emit(m_parent, m_row, m_node);
		m_done = true;
	}

protected:

	int m_row;
	BookmarksManager m_bookmarkManager;
	BookmarkNode m_node;
	BookmarkNode m_parent;
	bool m_done;
}

class InsertBookmarksCommand : public RemoveBookmarksCommand
{
public:

	this(BookmarksManager m_bookmarkManager, BookmarkNode parent, BookmarkNode node, int row)
	{
		super(m_bookmarkManager, parent, row);

		setText(tr("Insert Bookmark"));
		m_node = node;
	}

	void undo() { RemoveBookmarksCommand.redo(); }
	void redo() { RemoveBookmarksCommand.undo(); }
}

class ChangeBookmarkCommand : public QUndoCommand
{
public:

	this(BookmarksManager m_bookmarkManager, BookmarkNode node, string newValue, bool title)
	{
		super();
		m_bookmarkManager = m_bookmarkManager;
		m_title = title;
		m_newValue = newValue;
		m_node = node;
		if (m_title) {
			m_oldValue = m_node.title;
			setText(tr("Name Change"));
		} else {
			m_oldValue = m_node.url;
			setText(tr("Address Change"));
		}
	}

	void undo()
	{
		if (m_title)
			m_node.title = m_oldValue;
		else
			m_node.url = m_oldValue;
		m_bookmarkManager.entryChanged.emit(m_node);
	}

	void redo()
	{
		if (m_title)
			m_node.title = m_newValue;
		else
			m_node.url = m_newValue;
		m_bookmarkManager.entryChanged.emit(m_node);
	}

private:
	
	BookmarksManager m_bookmarkManager;
	bool m_title;
	string m_oldValue;
	string m_newValue;
	BookmarkNode m_node;
}

/*!
BookmarksModel is a QAbstractItemModel wrapper around the BookmarkManager
*/
import qt.gui.QIcon;

class BookmarksModel : public QAbstractItemModel
{
public:

	void entryAdded(BookmarkNode item)
	{
		assert(item && item.parent());
		int row = indexOf(item.parent().children(), 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);
		dataChanged.emit(idx, idx);
	}

public:

	enum Roles {
		TypeRole = Qt.UserRole + 1,
		UrlRole = Qt.UserRole + 2,
		UrlStringRole = Qt.UserRole + 3,
		SeparatorRole = Qt.UserRole + 4
	};

	this(BookmarksManager bookmarkManager, QObject parent = null)
	{
		super(parent);
		m_endMacro = false;
		m_bookmarksManager = bookmarkManager;
		bookmarkManager.entryAdded.connect(&this.entryAdded);
		bookmarkManager.entryRemoved.connect(&this.entryRemoved);
		bookmarkManager.entryChanged.connect(&this.entryChanged);
	}
    
	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 new QVariant(tr("Title"));
				case 1: return QVariant(tr("Address"));
			}
		}
		return QAbstractItemModel.headerData(section, orientation, role);
	}
    
	QVariant data(QModelIndex index, int role = Qt.DisplayRole)
	{
		if (!index.isValid() || index.model() != this)
			return new QVariant();

		BookmarkNode bookmarkNode = node(index);
		switch (role) {
			case Qt_ItemDataRole.EditRole:
			case Qt_ItemDataRole.DisplayRole:
				if (bookmarkNode.type() == BookmarkNode.Type.Separator) {
					switch (index.column()) {
						case 0:
							char[] tmp = new char[](50);
							tmp[] = 0xB7;
							return new QVariant(tmp);
						case 1: return null;
					}
				}

				switch (index.column()) {
					case 0: return new QVariant(bookmarkNode.title);
					case 1: return new QVariant(bookmarkNode.url);
				}
				break;
			case BookmarksModel.Roles.UrlRole:
				return new QVariant(new QUrl(bookmarkNode.url));
				break;
			case BookmarksModel.Roles.UrlStringRole:
				return new QVariant(bookmarkNode.url);
				break;
			case BookmarksModel.Roles.TypeRole:
				return new QVariant(cast(ulong) bookmarkNode.type());
				break;
			case BookmarksModel.Roles.SeparatorRole:
				return new QVariant(bookmarkNode.type() == BookmarkNode.Type.Separator);
				break;
			case Qt_ItemDataRole.DecorationRole:
				if (index.column() == 0) {
					//TODO: is cast(void*) ok?
					if (bookmarkNode.type() == BookmarkNode.Type.Folder)
						return new QVariant(cast(void*) QApplication.style().standardIcon(QStyle.SP_DirIcon));
					return new QVariant(cast(void*) BrowserApplication.instance().icon(new QUrl(bookmarkNode.url)));
				}
		}

		return new 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().length;

		BookmarkNode item = cast(BookmarkNode) parent.internalPointer();
		return item.children().length;
	}

	QModelIndex index(int row, int column, QModelIndex parent = 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, cast(void*) parentNode.children()[row]);
	}

	QModelIndex parent(QModelIndex index = QModelIndex())
	{
		if (!index.isValid())
			return QModelIndex();

		BookmarkNode itemNode = node(index);
		BookmarkNode parentNode = (itemNode ? itemNode.parent() : null);
		if (!parentNode || parentNode == m_bookmarksManager.bookmarks())
			return QModelIndex();

		// get the parent's row
		BookmarkNode grandParentNode = parentNode.parent();
		int parentRow = grandParentNode.children().indexOf(parentNode);
		assert(parentRow >= 0);
		return createIndex(parentRow, 0, cast(void*) parentNode);
	}

	int flags(QModelIndex index)
	{
		if (!index.isValid())
			return Qt.NoItemFlags;

		int flags = Qt_ItemFlag.ItemIsSelectable | Qt_ItemFlag.ItemIsEnabled;

		BookmarkNode bookmarkNode = node(index);

		if (bookmarkNode != m_bookmarksManager.menu() && bookmarkNode != m_bookmarksManager.toolbar()) {
			flags |= Qt_ItemFlag.ItemIsDragEnabled;
			if (bookmarkNode.type() != BookmarkNode.Type.Separator)
				flags |= Qt_ItemFlag.ItemIsEditable;
		}
		if (hasChildren(index))
			flags |= Qt_ItemFlag.ItemIsDropEnabled;
		return flags;
	}

	int supportedDropActions()
	{
		return Qt_DropAction.CopyAction | Qt_DropAction.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()[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_ItemDataRole.EditRole)
	{
		if (!index.isValid() || (flags(index) & Qt_ItemFlag.ItemIsEditable) == 0)
			return false;

		BookmarkNode item = node(index);

		switch (role) {
			case Qt_ItemDataRole.EditRole:
			case Qt_ItemDataRole.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.Roles.UrlRole:
				m_bookmarksManager.setUrl(item, value.toUrl().toString());
				break;
			case BookmarksModel.Roles.UrlStringRole:
				m_bookmarksManager.setUrl(item, value.toString());
				break;
			default:
				break;
				return false;
		}

		return true;
	}

	QMimeData mimeData(QModelIndex[] indexes)
	{
		auto mimeData = new QMimeData();
		auto data = new QByteArray;
		auto stream = new QDataStream(data, QIODevice.WriteOnly);
		foreach (QModelIndex index; indexes) {
			if (index.column() != 0 || !index.isValid())
				continue;
			auto encodedData = new QByteArray;
			auto buffer = new QBuffer(encodedData);
			buffer.open(QBuffer.ReadWrite);
			auto writer = new XbelWriter;
			BookmarkNode parentNode = node(index);
			writer.write(buffer, parentNode);
			foreach(b; encodedData.data()[0..encodedData.size()])
				stream.writeByte(cast(byte) b);
		}
		mimeData.setData(MIMETYPE, data);
		return mimeData;
	}

	const string MIMETYPE = "application/bookmarks.xbel";

	string[] mimeTypes()
	{
		return [ MIMETYPE ];
	}

	bool dropMimeData(QMimeData data,  Qt_DropAction action, int row, int column, QModelIndex parent)
	{
		if (action == Qt_DropAction.IgnoreAction)
			return true;

		if (!data.hasFormat(MIMETYPE) || column > 0)
			return false;

		QByteArray ba = data.data(MIMETYPE);
		QDataStream stream = new QDataStream(ba, QIODevice.ReadOnly);
		if (stream.atEnd())
			return false;

		QUndoStack undoStack = m_bookmarksManager.undoRedoStack();
		undoStack.beginMacro("Move Bookmarks");

		while (!stream.atEnd()) {
			auto encodedData = new QByteArray;
			//stream >> encodedData; //TODO
			
			auto buffer = new QBuffer(encodedData);
			buffer.open(QBuffer.ReadOnly);

			auto reader = new XbelReader;
			BookmarkNode rootNode = reader.read(buffer);
			BookmarkNode[] children = rootNode.children();
			for (int i = 0; i < children.length; ++i) {
				BookmarkNode bookmarkNode = children[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;
		BookmarkNode parentNode = node(parent);
		return (parentNode.type() == BookmarkNode.Type.Folder);
	}

	BookmarkNode node(QModelIndex index)
	{
		BookmarkNode itemNode = 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(indexOf(parent.children(), node), 0, cast(void*) node);
	}

private:

	bool m_endMacro;
	BookmarksManager m_bookmarksManager;
}

// Menu that is dynamically populated from the bookmarks
import modelmenu;

class BookmarksMenu : public ModelMenu
{
	mixin Signal!("openUrl", QUrl /*url*/);

public:

	this(QWidget parent = null)
	{
		super(parent);	
		m_bookmarksManager = null;
		this.activated.connect(&this.activatedIndex);
		setMaxRows(-1);
		setHoverRole(BookmarksModel.Roles.UrlStringRole);
		setSeparatorRole(BookmarksModel.Roles.SeparatorRole);
	}
	
	void setInitialActions(QAction[] actions)
	{
		m_initialActions = actions;
		for (int i = 0; i < m_initialActions.length; ++i)
			addAction(m_initialActions[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.length; ++i)
			addAction(m_initialActions[i]);
		if (m_initialActions.length)
			addSeparator();
		createMenu(model().index(0, 0), 1, this);
		return true;
	}

private:

	void activatedIndex(QModelIndex index)
	{
		openUrl.emit(index.data(BookmarksModel.Roles.UrlRole).toUrl());
	}

private:

	BookmarksManager m_bookmarksManager;
	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 qt.gui.QSortFilterProxyModel;

class AddBookmarkProxyModel : public QSortFilterProxyModel
{
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
{
	mixin Ui_AddBookmarkDialog;
	
public:

	this(string url, string title, QWidget parent = null, BookmarksManager bookmarkManager = null)
	{
		super(parent);
		m_url = url;
		m_bookmarksManager = bookmarkManager;

		setWindowFlags(Qt.Sheet);
		if (!m_bookmarksManager)
			m_bookmarksManager = BrowserApplication.bookmarksManager();
		setupUi(this);
		auto 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:
	
	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.Type.Bookmark);
		bookmark.url = m_url;
		bookmark.title = name.text();
		m_bookmarksManager.addBookmark(parent, bookmark);
		QDialog.accept();
	}

private:

	string m_url;
	BookmarksManager m_bookmarksManager;
	AddBookmarkProxyModel m_proxyModel;
}

import ui_bookmarksdialog;

//class TreeProxyModel;
class BookmarksDialog : public QDialog //, public Ui_BookmarksDialog
{
	mixin Ui_BookmarksDialog;
	
	mixin Signal!("openUrl", QUrl /*url*/);

public:

	this(QWidget parent = null, BookmarksManager manager = null)
	{
		super(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);
		search.textChanged.connect(&m_proxyModel.setFilterFixedString);
		removeButton.clicked.connect(&tree.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);
		auto fm = new QFontMetrics(font());
		int header = fm.width("m") * 40;
		tree.header().resizeSection(0, header);
		tree.header().setStretchLastSection(true);
		tree.activated.connect(&this.open);
		tree.setContextMenuPolicy(Qt.CustomContextMenu);
		tree.customContextMenuRequested.connect(&this.customContextMenuRequested);
		addFolderButton.clicked.connect(&this.newFolder);
		expandNodes(m_bookmarksManager.bookmarks());
		setAttribute(Qt.WA_DeleteOnClose);
	}
	
	~this()
	{
		if (saveExpandedNodes(tree.rootIndex()))
			m_bookmarksManager.changeExpanded();
	}

private:

	void customContextMenuRequested(QPoint pos)
	{
		auto menu = new QMenu;
		QModelIndex index = tree.indexAt(pos);
		index = index.sibling(index.row(), 0);
		if (index.isValid() && !tree.model().hasChildren(index)) {
			auto action = new QAction(tr("Open"), this);
			action.triggered.connect(&this.open);
			menu.addAction(action);
			menu.addSeparator();
		}
		auto action = new QAction(tr("Delete"), this);
		action.triggered.connect(&tree.removeOne);
		menu.addAction(action);
		menu.exec(QCursor.pos());
	}
	
	void open()
	{
		QModelIndex index = tree.currentIndex();
		if (!index.parent().isValid())
			return;
		openUrl.emit(index.sibling(index.row(), 1).data(BookmarksModel.Roles.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.Type.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().length; ++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 qt.gui.QToolBar;


class BookmarksToolBar : public QToolBar
{
	mixin Signal!("openUrl", QUrl /*url*/);

public:

	this(BookmarksModel model, QWidget parent = null)
	{
		super(tr("Bookmark"), parent);
		m_bookmarksModel = model;
		this.actionTriggered.connect(&this.triggered);
		setRootIndex(model.index(0, 0));
		m_bookmarksModel.modelReset.connect(&this.build);
		m_bookmarksModel.rowsInserted.connect(&this.build);
		m_bookmarksModel.rowsRemoved.connect(&this.build);
		m_bookmarksModel.dataChanged.connect(&this.build);
		setAcceptDrops(true);
	}

	void setRootIndex(QModelIndex index)
	{
		m_root = index;
		build();
	}

	QModelIndex rootIndex()
	{
		return m_root;
	}

protected:

	void dragEnterEvent(QDragEnterEvent event)
	{
		QMimeData mimeData = event.mimeData();
		if (mimeData.hasUrls())
			event.acceptProposedAction();
		super.dragEnterEvent(event);
	}

	void dropEvent(QDropEvent event)
	{
		QMimeData mimeData = event.mimeData();
		if (mimeData.hasUrls() && mimeData.hasText()) {
			QUrl[] urls = mimeData.urls();
			QAction action = actionAt(event.pos());
			string 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);
				string title = idx.data().toString();
				if (title == dropText) {
					row = i;
					if (m_bookmarksModel.hasChildren(idx)) {
						parentIndex = idx;
						row = -1;
					}
					break;
				}
			}
			BookmarkNode bookmark = new BookmarkNode(BookmarkNode.Type.Bookmark);
			bookmark.url = urls[0].toString();
			bookmark.title = mimeData.text();

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

private:

	void triggered(QAction action)
	{
		QVariant v = action.data();
		if (v.canConvert!(QUrl)) {
			openUrl.emit(v.toUrl());
		}
	}

	void activated(QModelIndex index)
	{
		openUrl.emit(index.data(BookmarksModel.Roles.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_ArrowType.DownArrow);
				button.setText(idx.data().toString());
				ModelMenu menu = new ModelMenu(this);
				menu.activated.connect(&this.activated);
				menu.setModel(m_bookmarksModel);
				menu.setRootIndex(idx);
				menu.addAction(new QAction(menu));
				button.setMenu(menu);
				button.setToolButtonStyle(Qt_ToolButtonStyle.ToolButtonTextOnly);
				QAction a = addWidget(button);
				a.setText(idx.data().toString());
			} else {
				QAction action = addAction(idx.data().toString());
				action.setData(idx.data(BookmarksModel.Roles.UrlRole));
			}
		}
	}

private:

	BookmarksModel m_bookmarksModel;
	/*TODO: needed? QPersistentModelIndex*/ QModelIndex m_root;
}