bookmarks.cpp Example File

webenginewidgets/demobrowser/bookmarks.cpp

  /****************************************************************************
  **
  ** Copyright (C) 2016 The Qt Company Ltd.
  ** Contact: https://www.qt.io/licensing/
  **
  ** This file is part of the demonstration applications of the Qt Toolkit.
  **
  ** $QT_BEGIN_LICENSE:BSD$
  ** Commercial License Usage
  ** Licensees holding valid commercial Qt licenses may use this file in
  ** accordance with the commercial license agreement provided with the
  ** Software or, alternatively, in accordance with the terms contained in
  ** a written agreement between you and The Qt Company. For licensing terms
  ** and conditions see https://www.qt.io/terms-conditions. For further
  ** information use the contact form at https://www.qt.io/contact-us.
  **
  ** BSD License Usage
  ** Alternatively, you may use this file under the terms of the BSD license
  ** as follows:
  **
  ** "Redistribution and use in source and binary forms, with or without
  ** modification, are permitted provided that the following conditions are
  ** met:
  **   * Redistributions of source code must retain the above copyright
  **     notice, this list of conditions and the following disclaimer.
  **   * Redistributions in binary form must reproduce the above copyright
  **     notice, this list of conditions and the following disclaimer in
  **     the documentation and/or other materials provided with the
  **     distribution.
  **   * Neither the name of The Qt Company Ltd nor the names of its
  **     contributors may be used to endorse or promote products derived
  **     from this software without specific prior written permission.
  **
  **
  ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
  **
  ** $QT_END_LICENSE$
  **
  ****************************************************************************/

  #include "bookmarks.h"

  #include "autosaver.h"
  #include "browserapplication.h"
  #include "history.h"
  #include "xbel.h"

  #include <QtCore/QBuffer>
  #include <QtCore/QFile>
  #include <QtCore/QMimeData>

  #include <QtGui/QDesktopServices>
  #include <QtGui/QDragEnterEvent>
  #include <QtGui/QIcon>
  #include <QtWidgets/QFileDialog>
  #include <QtWidgets/QHeaderView>
  #include <QtWidgets/QMessageBox>
  #include <QtWidgets/QToolButton>

  #include <QtCore/QDebug>

  #define BOOKMARKBAR "Bookmarks Bar"
  #define BOOKMARKMENU "Bookmarks Menu"

  BookmarksManager::BookmarksManager(QObject *parent)
      : QObject(parent)
      , m_loaded(false)
      , m_saveTimer(new AutoSaver(this))
      , m_bookmarkRootNode(0)
      , m_bookmarkModel(0)
  {
      connect(this, SIGNAL(entryAdded(BookmarkNode*)),
              m_saveTimer, SLOT(changeOccurred()));
      connect(this, SIGNAL(entryRemoved(BookmarkNode*,int,BookmarkNode*)),
              m_saveTimer, SLOT(changeOccurred()));
      connect(this, SIGNAL(entryChanged(BookmarkNode*)),
              m_saveTimer, SLOT(changeOccurred()));
  }

  BookmarksManager::~BookmarksManager()
  {
      m_saveTimer->saveIfNeccessary();
  }

  void BookmarksManager::changeExpanded()
  {
      m_saveTimer->changeOccurred();
  }

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

      QString dir = QStandardPaths::writableLocation(QStandardPaths::DataLocation);
      QString bookmarkFile = dir + QLatin1String("/bookmarks.xbel");
      if (!QFile::exists(bookmarkFile))
          bookmarkFile = QLatin1String(":defaultbookmarks.xbel");

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

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

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

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

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

  void BookmarksManager::save() const
  {
      if (!m_loaded)
          return;

      XbelWriter writer;
      QString dir = QStandardPaths::writableLocation(QStandardPaths::DataLocation);
      QString bookmarkFile = dir + QLatin1String("/bookmarks.xbel");
      if (!writer.write(bookmarkFile, m_bookmarkRootNode))
          qWarning() << "BookmarkManager: error saving to" << bookmarkFile;
  }

  void BookmarksManager::addBookmark(BookmarkNode *parent, BookmarkNode *node, int row)
  {
      if (!m_loaded)
          return;
      Q_ASSERT(parent);
      InsertBookmarksCommand *command = new InsertBookmarksCommand(this, parent, node, row);
      m_commands.push(command);
  }

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

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

  void BookmarksManager::setTitle(BookmarkNode *node, const QString &newTitle)
  {
      if (!m_loaded)
          return;

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

  void BookmarksManager::setUrl(BookmarkNode *node, const QString &newUrl)
  {
      if (!m_loaded)
          return;

      Q_ASSERT(node);
      ChangeBookmarkCommand *command = new ChangeBookmarkCommand(this, node, newUrl, false);
      m_commands.push(command);
  }

  BookmarkNode *BookmarksManager::bookmarks()
  {
      if (!m_loaded)
          load();
      return m_bookmarkRootNode;
  }

  BookmarkNode *BookmarksManager::menu()
  {
      if (!m_loaded)
          load();

      for (int i = m_bookmarkRootNode->children().count() - 1; i >= 0; --i) {
          BookmarkNode *node = m_bookmarkRootNode->children().at(i);
          if (node->title == tr(BOOKMARKMENU))
              return node;
      }
      Q_ASSERT(false);
      return 0;
  }

  BookmarkNode *BookmarksManager::toolbar()
  {
      if (!m_loaded)
          load();

      for (int i = m_bookmarkRootNode->children().count() - 1; i >= 0; --i) {
          BookmarkNode *node = m_bookmarkRootNode->children().at(i);
          if (node->title == tr(BOOKMARKBAR))
              return node;
      }
      Q_ASSERT(false);
      return 0;
  }

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

  void BookmarksManager::importBookmarks()
  {
      QString fileName = QFileDialog::getOpenFileName(0, tr("Open File"),
                                                       QString(),
                                                       tr("XBEL (*.xbel *.xml)"));
      if (fileName.isEmpty())
          return;

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

      importRootNode->setType(BookmarkNode::Folder);
      importRootNode->title = (tr("Imported %1").arg(QDate::currentDate().toString(Qt::SystemLocaleShortDate)));
      addBookmark(menu(), importRootNode);
  }

  void BookmarksManager::exportBookmarks()
  {
      QString fileName = QFileDialog::getSaveFileName(0, tr("Save File"),
                                  tr("%1 Bookmarks.xbel").arg(QCoreApplication::applicationName()),
                                  tr("XBEL (*.xbel *.xml)"));
      if (fileName.isEmpty())
          return;

      XbelWriter writer;
      if (!writer.write(fileName, m_bookmarkRootNode))
          QMessageBox::critical(0, tr("Export error"), tr("error saving bookmarks"));
  }

  RemoveBookmarksCommand::RemoveBookmarksCommand(BookmarksManager *m_bookmarkManagaer, BookmarkNode *parent, int row)
      : QUndoCommand(BookmarksManager::tr("Remove Bookmark"))
      , m_row(row)
      , m_bookmarkManagaer(m_bookmarkManagaer)
      , m_node(parent->children().value(row))
      , m_parent(parent)
      , m_done(false)
  {
  }

  RemoveBookmarksCommand::~RemoveBookmarksCommand()
  {
      if (m_done && !m_node->parent()) {
          delete m_node;
      }
  }

  void RemoveBookmarksCommand::undo()
  {
      m_parent->add(m_node, m_row);
      emit m_bookmarkManagaer->entryAdded(m_node);
      m_done = false;
  }

  void RemoveBookmarksCommand::redo()
  {
      m_parent->remove(m_node);
      emit m_bookmarkManagaer->entryRemoved(m_parent, m_row, m_node);
      m_done = true;
  }

  InsertBookmarksCommand::InsertBookmarksCommand(BookmarksManager *m_bookmarkManagaer,
                  BookmarkNode *parent, BookmarkNode *node, int row)
      : RemoveBookmarksCommand(m_bookmarkManagaer, parent, row)
  {
      setText(BookmarksManager::tr("Insert Bookmark"));
      m_node = node;
  }

  ChangeBookmarkCommand::ChangeBookmarkCommand(BookmarksManager *m_bookmarkManagaer, BookmarkNode *node,
                          const QString &newValue, bool title)
      : QUndoCommand()
      , m_bookmarkManagaer(m_bookmarkManagaer)
      , m_title(title)
      , m_newValue(newValue)
      , m_node(node)
  {
      if (m_title) {
          m_oldValue = m_node->title;
          setText(BookmarksManager::tr("Name Change"));
      } else {
          m_oldValue = m_node->url;
          setText(BookmarksManager::tr("Address Change"));
      }
  }

  void ChangeBookmarkCommand::undo()
  {
      if (m_title)
          m_node->title = m_oldValue;
      else
          m_node->url = m_oldValue;
      emit m_bookmarkManagaer->entryChanged(m_node);
  }

  void ChangeBookmarkCommand::redo()
  {
      if (m_title)
          m_node->title = m_newValue;
      else
          m_node->url = m_newValue;
      emit m_bookmarkManagaer->entryChanged(m_node);
  }

  BookmarksModel::BookmarksModel(BookmarksManager *bookmarkManager, QObject *parent)
      : QAbstractItemModel(parent)
      , m_endMacro(false)
      , m_bookmarksManager(bookmarkManager)
  {
      connect(bookmarkManager, SIGNAL(entryAdded(BookmarkNode*)),
              this, SLOT(entryAdded(BookmarkNode*)));
      connect(bookmarkManager, SIGNAL(entryRemoved(BookmarkNode*,int,BookmarkNode*)),
              this, SLOT(entryRemoved(BookmarkNode*,int,BookmarkNode*)));
      connect(bookmarkManager, SIGNAL(entryChanged(BookmarkNode*)),
              this, SLOT(entryChanged(BookmarkNode*)));
  }

  QModelIndex BookmarksModel::index(BookmarkNode *node) const
  {
      BookmarkNode *parent = node->parent();
      if (!parent)
          return QModelIndex();
      return createIndex(parent->children().indexOf(node), 0, node);
  }

  void BookmarksModel::entryAdded(BookmarkNode *item)
  {
      Q_ASSERT(item && item->parent());
      int row = item->parent()->children().indexOf(item);
      BookmarkNode *parent = item->parent();
      // item was already added so remove beore beginInsertRows is called
      parent->remove(item);
      beginInsertRows(index(parent), row, row);
      parent->add(item, row);
      endInsertRows();
  }

  void BookmarksModel::entryRemoved(BookmarkNode *parent, int row, BookmarkNode *item)
  {
      // item was already removed, re-add so beginRemoveRows works
      parent->add(item, row);
      beginRemoveRows(index(parent), row, row);
      parent->remove(item);
      endRemoveRows();
  }

  void BookmarksModel::entryChanged(BookmarkNode *item)
  {
      QModelIndex idx = index(item);
      emit dataChanged(idx, idx);
  }

  bool BookmarksModel::removeRows(int row, int count, const QModelIndex &parent)
  {
      if (row < 0 || count <= 0 || row + count > rowCount(parent))
          return false;

      BookmarkNode *bookmarkNode = node(parent);
      for (int i = row + count - 1; i >= row; --i) {
          BookmarkNode *node = bookmarkNode->children().at(i);
          if (node == m_bookmarksManager->menu()
              || node == m_bookmarksManager->toolbar())
              continue;

          m_bookmarksManager->removeBookmark(node);
      }
      if (m_endMacro) {
          m_bookmarksManager->undoRedoStack()->endMacro();
          m_endMacro = false;
      }
      return true;
  }

  QVariant BookmarksModel::headerData(int section, Qt::Orientation orientation, int role) const
  {
      if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
          switch (section) {
              case 0: return tr("Title");
              case 1: return tr("Address");
          }
      }
      return QAbstractItemModel::headerData(section, orientation, role);
  }

  QVariant BookmarksModel::data(const QModelIndex &index, int role) const
  {
      if (!index.isValid() || index.model() != this)
          return QVariant();

      const BookmarkNode *bookmarkNode = node(index);
      switch (role) {
      case Qt::EditRole:
      case Qt::DisplayRole:
          if (bookmarkNode->type() == BookmarkNode::Separator) {
              switch (index.column()) {
              case 0: return QString(50, 0xB7);
              case 1: return QString();
              }
          }

          switch (index.column()) {
          case 0: return bookmarkNode->title;
          case 1: return bookmarkNode->url;
          }
          break;
      case BookmarksModel::UrlRole:
          return QUrl(bookmarkNode->url);
          break;
      case BookmarksModel::UrlStringRole:
          return bookmarkNode->url;
          break;
      case BookmarksModel::TypeRole:
          return bookmarkNode->type();
          break;
      case BookmarksModel::SeparatorRole:
          return (bookmarkNode->type() == BookmarkNode::Separator);
          break;
      case Qt::DecorationRole:
          if (index.column() == 0) {
              if (bookmarkNode->type() == BookmarkNode::Folder)
                  return QApplication::style()->standardIcon(QStyle::SP_DirIcon);
              return BrowserApplication::instance()->icon(bookmarkNode->url);
          }
      }

      return QVariant();
  }

  int BookmarksModel::columnCount(const QModelIndex &parent) const
  {
      return (parent.column() > 0) ? 0 : 2;
  }

  int BookmarksModel::rowCount(const QModelIndex &parent) const
  {
      if (parent.column() > 0)
          return 0;

      if (!parent.isValid())
          return m_bookmarksManager->bookmarks()->children().count();

      const BookmarkNode *item = static_cast<BookmarkNode*>(parent.internalPointer());
      return item->children().count();
  }

  QModelIndex BookmarksModel::index(int row, int column, const QModelIndex &parent) const
  {
      if (row < 0 || column < 0 || row >= rowCount(parent) || column >= columnCount(parent))
          return QModelIndex();

      // get the parent node
      BookmarkNode *parentNode = node(parent);
      return createIndex(row, column, parentNode->children().at(row));
  }

  QModelIndex BookmarksModel::parent(const QModelIndex &index) const
  {
      if (!index.isValid())
          return QModelIndex();

      BookmarkNode *itemNode = node(index);
      BookmarkNode *parentNode = (itemNode ? itemNode->parent() : 0);
      if (!parentNode || parentNode == m_bookmarksManager->bookmarks())
          return QModelIndex();

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

  bool BookmarksModel::hasChildren(const QModelIndex &parent) const
  {
      if (!parent.isValid())
          return true;
      const BookmarkNode *parentNode = node(parent);
      return (parentNode->type() == BookmarkNode::Folder);
  }

  Qt::ItemFlags BookmarksModel::flags(const QModelIndex &index) const
  {
      if (!index.isValid())
          return Qt::NoItemFlags;

      Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled;

      BookmarkNode *bookmarkNode = node(index);

      if (bookmarkNode != m_bookmarksManager->menu()
          && bookmarkNode != m_bookmarksManager->toolbar()) {
          flags |= Qt::ItemIsDragEnabled;
          if (bookmarkNode->type() != BookmarkNode::Separator)
              flags |= Qt::ItemIsEditable;
      }
      if (hasChildren(index))
          flags |= Qt::ItemIsDropEnabled;
      return flags;
  }

  Qt::DropActions BookmarksModel::supportedDropActions () const
  {
      return Qt::CopyAction | Qt::MoveAction;
  }

  #define MIMETYPE QLatin1String("application/bookmarks.xbel")

  QStringList BookmarksModel::mimeTypes() const
  {
      QStringList types;
      types << MIMETYPE;
      return types;
  }

  QMimeData *BookmarksModel::mimeData(const QModelIndexList &indexes) const
  {
      QMimeData *mimeData = new QMimeData();
      QByteArray data;
      QDataStream stream(&data, QIODevice::WriteOnly);
      foreach (QModelIndex index, indexes) {
          if (index.column() != 0 || !index.isValid())
              continue;
          QByteArray encodedData;
          QBuffer buffer(&encodedData);
          buffer.open(QBuffer::ReadWrite);
          XbelWriter writer;
          const BookmarkNode *parentNode = node(index);
          writer.write(&buffer, parentNode);
          stream << encodedData;
      }
      mimeData->setData(MIMETYPE, data);
      return mimeData;
  }

  bool BookmarksModel::dropMimeData(const QMimeData *data,
       Qt::DropAction action, int row, int column, const QModelIndex &parent)
  {
      if (action == Qt::IgnoreAction)
          return true;

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

      QByteArray ba = data->data(MIMETYPE);
      QDataStream stream(&ba, QIODevice::ReadOnly);
      if (stream.atEnd())
          return false;

      QUndoStack *undoStack = m_bookmarksManager->undoRedoStack();
      undoStack->beginMacro(QLatin1String("Move Bookmarks"));

      while (!stream.atEnd()) {
          QByteArray encodedData;
          stream >> encodedData;
          QBuffer buffer(&encodedData);
          buffer.open(QBuffer::ReadOnly);

          XbelReader reader;
          BookmarkNode *rootNode = reader.read(&buffer);
          QList<BookmarkNode*> children = rootNode->children();
          for (int i = 0; i < children.count(); ++i) {
              BookmarkNode *bookmarkNode = children.at(i);
              rootNode->remove(bookmarkNode);
              row = qMax(0, row);
              BookmarkNode *parentNode = node(parent);
              m_bookmarksManager->addBookmark(parentNode, bookmarkNode, row);
              m_endMacro = true;
          }
          delete rootNode;
      }
      return true;
  }

  bool BookmarksModel::setData(const QModelIndex &index, const QVariant &value, int role)
  {
      if (!index.isValid() || (flags(index) & Qt::ItemIsEditable) == 0)
          return false;

      BookmarkNode *item = node(index);

      switch (role) {
      case Qt::EditRole:
      case Qt::DisplayRole:
          if (index.column() == 0) {
              m_bookmarksManager->setTitle(item, value.toString());
              break;
          }
          if (index.column() == 1) {
              m_bookmarksManager->setUrl(item, value.toString());
              break;
          }
          return false;
      case BookmarksModel::UrlRole:
          m_bookmarksManager->setUrl(item, value.toUrl().toString());
          break;
      case BookmarksModel::UrlStringRole:
          m_bookmarksManager->setUrl(item, value.toString());
          break;
      default:
          break;
          return false;
      }

      return true;
  }

  BookmarkNode *BookmarksModel::node(const QModelIndex &index) const
  {
      BookmarkNode *itemNode = static_cast<BookmarkNode*>(index.internalPointer());
      if (!itemNode)
          return m_bookmarksManager->bookmarks();
      return itemNode;
  }

  AddBookmarkProxyModel::AddBookmarkProxyModel(QObject *parent)
      : QSortFilterProxyModel(parent)
  {
  }

  int AddBookmarkProxyModel::columnCount(const QModelIndex &parent) const
  {
      return qMin(1, QSortFilterProxyModel::columnCount(parent));
  }

  bool AddBookmarkProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
  {
      QModelIndex idx = sourceModel()->index(source_row, 0, source_parent);
      return sourceModel()->hasChildren(idx);
  }

  AddBookmarkDialog::AddBookmarkDialog(const QString &url, const QString &title, QWidget *parent, BookmarksManager *bookmarkManager)
      : QDialog(parent)
      , m_url(url)
      , m_bookmarksManager(bookmarkManager)
  {
      setWindowFlags(Qt::Sheet);
      if (!m_bookmarksManager)
          m_bookmarksManager = BrowserApplication::bookmarksManager();
      setupUi(this);
      QTreeView *view = new QTreeView(this);
      m_proxyModel = new AddBookmarkProxyModel(this);
      BookmarksModel *model = m_bookmarksManager->bookmarksModel();
      m_proxyModel->setSourceModel(model);
      view->setModel(m_proxyModel);
      view->expandAll();
      view->header()->setStretchLastSection(true);
      view->header()->hide();
      view->setItemsExpandable(false);
      view->setRootIsDecorated(false);
      view->setIndentation(10);
      location->setModel(m_proxyModel);
      view->show();
      location->setView(view);
      BookmarkNode *menu = m_bookmarksManager->menu();
      QModelIndex idx = m_proxyModel->mapFromSource(model->index(menu));
      view->setCurrentIndex(idx);
      location->setCurrentIndex(idx.row());
      name->setText(title);
  }

  void AddBookmarkDialog::accept()
  {
      QModelIndex index = location->view()->currentIndex();
      index = m_proxyModel->mapToSource(index);
      if (!index.isValid())
          index = m_bookmarksManager->bookmarksModel()->index(0, 0);
      BookmarkNode *parent = m_bookmarksManager->bookmarksModel()->node(index);
      BookmarkNode *bookmark = new BookmarkNode(BookmarkNode::Bookmark);
      bookmark->url = m_url;
      bookmark->title = name->text();
      m_bookmarksManager->addBookmark(parent, bookmark);
      QDialog::accept();
  }

  BookmarksMenu::BookmarksMenu(QWidget *parent)
      : ModelMenu(parent)
      , m_bookmarksManager(0)
  {
      connect(this, SIGNAL(activated(QModelIndex)),
              this, SLOT(activated(QModelIndex)));
      setMaxRows(-1);
      setHoverRole(BookmarksModel::UrlStringRole);
      setSeparatorRole(BookmarksModel::SeparatorRole);
  }

  void BookmarksMenu::activated(const QModelIndex &index)
  {
      emit openUrl(index.data(BookmarksModel::UrlRole).toUrl());
  }

  bool BookmarksMenu::prePopulated()
  {
      m_bookmarksManager = BrowserApplication::bookmarksManager();
      setModel(m_bookmarksManager->bookmarksModel());
      setRootIndex(m_bookmarksManager->bookmarksModel()->index(1, 0));
      // initial actions
      for (int i = 0; i < m_initialActions.count(); ++i)
          addAction(m_initialActions.at(i));
      if (!m_initialActions.isEmpty())
          addSeparator();
      createMenu(model()->index(0, 0), 1, this);
      return true;
  }

  void BookmarksMenu::setInitialActions(QList<QAction*> actions)
  {
      m_initialActions = actions;
      for (int i = 0; i < m_initialActions.count(); ++i)
          addAction(m_initialActions.at(i));
  }

  BookmarksDialog::BookmarksDialog(QWidget *parent, BookmarksManager *manager)
      : QDialog(parent)
  {
      m_bookmarksManager = manager;
      if (!m_bookmarksManager)
          m_bookmarksManager = BrowserApplication::bookmarksManager();
      setupUi(this);

      tree->setUniformRowHeights(true);
      tree->setSelectionBehavior(QAbstractItemView::SelectRows);
      tree->setSelectionMode(QAbstractItemView::ContiguousSelection);
      tree->setTextElideMode(Qt::ElideMiddle);
      m_bookmarksModel = m_bookmarksManager->bookmarksModel();
      m_proxyModel = new TreeProxyModel(this);
      connect(search, SIGNAL(textChanged(QString)),
              m_proxyModel, SLOT(setFilterFixedString(QString)));
      connect(removeButton, SIGNAL(clicked()), tree, SLOT(removeOne()));
      m_proxyModel->setSourceModel(m_bookmarksModel);
      tree->setModel(m_proxyModel);
      tree->setDragDropMode(QAbstractItemView::InternalMove);
      tree->setExpanded(m_proxyModel->index(0, 0), true);
      tree->setAlternatingRowColors(true);
      QFontMetrics fm(font());
      int header = fm.width(QLatin1Char('m')) * 40;
      tree->header()->resizeSection(0, header);
      tree->header()->setStretchLastSection(true);
      connect(tree, SIGNAL(activated(QModelIndex)),
              this, SLOT(open()));
      tree->setContextMenuPolicy(Qt::CustomContextMenu);
      connect(tree, SIGNAL(customContextMenuRequested(QPoint)),
              this, SLOT(customContextMenuRequested(QPoint)));
      connect(addFolderButton, SIGNAL(clicked()),
              this, SLOT(newFolder()));
      expandNodes(m_bookmarksManager->bookmarks());
      setAttribute(Qt::WA_DeleteOnClose);
  }

  BookmarksDialog::~BookmarksDialog()
  {
      if (saveExpandedNodes(tree->rootIndex()))
          m_bookmarksManager->changeExpanded();
  }

  bool BookmarksDialog::saveExpandedNodes(const QModelIndex &parent)
  {
      bool changed = false;
      for (int i = 0; i < m_proxyModel->rowCount(parent); ++i) {
          QModelIndex child = m_proxyModel->index(i, 0, parent);
          QModelIndex sourceIndex = m_proxyModel->mapToSource(child);
          BookmarkNode *childNode = m_bookmarksModel->node(sourceIndex);
          bool wasExpanded = childNode->expanded;
          if (tree->isExpanded(child)) {
              childNode->expanded = true;
              changed |= saveExpandedNodes(child);
          } else {
              childNode->expanded = false;
          }
          changed |= (wasExpanded != childNode->expanded);
      }
      return changed;
  }

  void BookmarksDialog::expandNodes(BookmarkNode *node)
  {
      for (int i = 0; i < node->children().count(); ++i) {
          BookmarkNode *childNode = node->children()[i];
          if (childNode->expanded) {
              QModelIndex idx = m_bookmarksModel->index(childNode);
              idx = m_proxyModel->mapFromSource(idx);
              tree->setExpanded(idx, true);
              expandNodes(childNode);
          }
      }
  }

  void BookmarksDialog::customContextMenuRequested(const QPoint &pos)
  {
      QMenu menu;
      QModelIndex index = tree->indexAt(pos);
      index = index.sibling(index.row(), 0);
      if (index.isValid() && !tree->model()->hasChildren(index)) {
          menu.addAction(tr("Open"), this, SLOT(open()));
          menu.addSeparator();
      }
      menu.addAction(tr("Delete"), tree, SLOT(removeOne()));
      menu.exec(QCursor::pos());
  }

  void BookmarksDialog::open()
  {
      QModelIndex index = tree->currentIndex();
      if (!index.parent().isValid())
          return;
      emit openUrl(index.sibling(index.row(), 1).data(BookmarksModel::UrlRole).toUrl());
  }

  void BookmarksDialog::newFolder()
  {
      QModelIndex currentIndex = tree->currentIndex();
      QModelIndex idx = currentIndex;
      if (idx.isValid() && !idx.model()->hasChildren(idx))
          idx = idx.parent();
      if (!idx.isValid())
          idx = tree->rootIndex();
      idx = m_proxyModel->mapToSource(idx);
      BookmarkNode *parent = m_bookmarksManager->bookmarksModel()->node(idx);
      BookmarkNode *node = new BookmarkNode(BookmarkNode::Folder);
      node->title = tr("New Folder");
      m_bookmarksManager->addBookmark(parent, node, currentIndex.row() + 1);
  }

  BookmarksToolBar::BookmarksToolBar(BookmarksModel *model, QWidget *parent)
      : QToolBar(tr("Bookmark"), parent)
      , m_bookmarksModel(model)
  {
      connect(this, SIGNAL(actionTriggered(QAction*)), this, SLOT(triggered(QAction*)));
      setRootIndex(model->index(0, 0));
      connect(m_bookmarksModel, SIGNAL(modelReset()), this, SLOT(build()));
      connect(m_bookmarksModel, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(build()));
      connect(m_bookmarksModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(build()));
      connect(m_bookmarksModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(build()));
      setAcceptDrops(true);
  }

  void BookmarksToolBar::dragEnterEvent(QDragEnterEvent *event)
  {
      const QMimeData *mimeData = event->mimeData();
      if (mimeData->hasUrls())
          event->acceptProposedAction();
      QToolBar::dragEnterEvent(event);
  }

  void BookmarksToolBar::dropEvent(QDropEvent *event)
  {
      const QMimeData *mimeData = event->mimeData();
      if (mimeData->hasUrls() && mimeData->hasText()) {
          QList<QUrl> urls = mimeData->urls();
          QAction *action = actionAt(event->pos());
          QString dropText;
          if (action)
              dropText = action->text();
          int row = -1;
          QModelIndex parentIndex = m_root;
          for (int i = 0; i < m_bookmarksModel->rowCount(m_root); ++i) {
              QModelIndex idx = m_bookmarksModel->index(i, 0, m_root);
              QString title = idx.data().toString();
              if (title == dropText) {
                  row = i;
                  if (m_bookmarksModel->hasChildren(idx)) {
                      parentIndex = idx;
                      row = -1;
                  }
                  break;
              }
          }
          BookmarkNode *bookmark = new BookmarkNode(BookmarkNode::Bookmark);
          bookmark->url = urls.at(0).toString();
          bookmark->title = mimeData->text();

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

  void BookmarksToolBar::setRootIndex(const QModelIndex &index)
  {
      m_root = index;
      build();
  }

  QModelIndex BookmarksToolBar::rootIndex() const
  {
      return m_root;
  }

  void BookmarksToolBar::build()
  {
      clear();
      for (int i = 0; i < m_bookmarksModel->rowCount(m_root); ++i) {
          QModelIndex idx = m_bookmarksModel->index(i, 0, m_root);
          if (m_bookmarksModel->hasChildren(idx)) {
              QToolButton *button = new QToolButton(this);
              button->setPopupMode(QToolButton::InstantPopup);
              button->setArrowType(Qt::DownArrow);
              button->setText(idx.data().toString());
              ModelMenu *menu = new ModelMenu(this);
              connect(menu, SIGNAL(activated(QModelIndex)),
                      this, SLOT(activated(QModelIndex)));
              menu->setModel(m_bookmarksModel);
              menu->setRootIndex(idx);
              menu->addAction(new QAction(menu));
              button->setMenu(menu);
              button->setToolButtonStyle(Qt::ToolButtonTextOnly);
              QAction *a = addWidget(button);
              a->setText(idx.data().toString());
          } else {
              QAction *action = addAction(idx.data().toString());
              action->setData(idx.data(BookmarksModel::UrlRole));
          }
      }
  }

  void BookmarksToolBar::triggered(QAction *action)
  {
      QVariant v = action->data();
      if (v.canConvert<QUrl>()) {
          emit openUrl(v.toUrl());
      }
  }

  void BookmarksToolBar::activated(const QModelIndex &index)
  {
      emit openUrl(index.data(BookmarksModel::UrlRole).toUrl());
  }