Update copyright headers
[qt:qt.git] / demos / browser / bookmarks.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2015 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the demonstration applications of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 or version 3 as published by the Free
20 ** Software Foundation and appearing in the file LICENSE.LGPLv21 and
21 ** LICENSE.LGPLv3 included in the packaging of this file. Please review the
22 ** following information to ensure the GNU Lesser General Public License
23 ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
24 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 **
26 ** As a special exception, The Qt Company gives you certain additional
27 ** rights. These rights are described in The Qt Company LGPL Exception
28 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 **
30 ** GNU General Public License Usage
31 ** Alternatively, this file may be used under the terms of the GNU
32 ** General Public License version 3.0 as published by the Free Software
33 ** Foundation and appearing in the file LICENSE.GPL included in the
34 ** packaging of this file.  Please review the following information to
35 ** ensure the GNU General Public License version 3.0 requirements will be
36 ** met: http://www.gnu.org/copyleft/gpl.html.
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "bookmarks.h"
43
44 #include "autosaver.h"
45 #include "browserapplication.h"
46 #include "history.h"
47 #include "xbel.h"
48
49 #include <QtCore/QBuffer>
50 #include <QtCore/QFile>
51 #include <QtCore/QMimeData>
52
53 #include <QtGui/QDesktopServices>
54 #include <QtGui/QDragEnterEvent>
55 #include <QtGui/QFileDialog>
56 #include <QtGui/QHeaderView>
57 #include <QtGui/QIcon>
58 #include <QtGui/QMessageBox>
59 #include <QtGui/QToolButton>
60
61 #include <QtWebKit/QWebSettings>
62
63 #include <QtCore/QDebug>
64
65 #define BOOKMARKBAR "Bookmarks Bar"
66 #define BOOKMARKMENU "Bookmarks Menu"
67
68 BookmarksManager::BookmarksManager(QObject *parent)
69     : QObject(parent)
70     , m_loaded(false)
71     , m_saveTimer(new AutoSaver(this))
72     , m_bookmarkRootNode(0)
73     , m_bookmarkModel(0)
74 {
75     connect(this, SIGNAL(entryAdded(BookmarkNode*)),
76             m_saveTimer, SLOT(changeOccurred()));
77     connect(this, SIGNAL(entryRemoved(BookmarkNode*,int,BookmarkNode*)),
78             m_saveTimer, SLOT(changeOccurred()));
79     connect(this, SIGNAL(entryChanged(BookmarkNode*)),
80             m_saveTimer, SLOT(changeOccurred()));
81 }
82
83 BookmarksManager::~BookmarksManager()
84 {
85     m_saveTimer->saveIfNeccessary();
86 }
87
88 void BookmarksManager::changeExpanded()
89 {
90     m_saveTimer->changeOccurred();
91 }
92
93 void BookmarksManager::load()
94 {
95     if (m_loaded)
96         return;
97     m_loaded = true;
98
99     QString dir = QDesktopServices::storageLocation(QDesktopServices::DataLocation);
100     QString bookmarkFile = dir + QLatin1String("/bookmarks.xbel");
101     if (!QFile::exists(bookmarkFile))
102         bookmarkFile = QLatin1String(":defaultbookmarks.xbel");
103
104     XbelReader reader;
105     m_bookmarkRootNode = reader.read(bookmarkFile);
106     if (reader.error() != QXmlStreamReader::NoError) {
107         QMessageBox::warning(0, QLatin1String("Loading Bookmark"),
108             tr("Error when loading bookmarks on line %1, column %2:\n"
109                "%3").arg(reader.lineNumber()).arg(reader.columnNumber()).arg(reader.errorString()));
110     }
111
112     BookmarkNode *toolbar = 0;
113     BookmarkNode *menu = 0;
114     QList<BookmarkNode*> others;
115     for (int i = m_bookmarkRootNode->children().count() - 1; i >= 0; --i) {
116         BookmarkNode *node = m_bookmarkRootNode->children().at(i);
117         if (node->type() == BookmarkNode::Folder) {
118             // Automatically convert
119             if (node->title == tr("Toolbar Bookmarks") && !toolbar) {
120                 node->title = tr(BOOKMARKBAR);
121             }
122             if (node->title == tr(BOOKMARKBAR) && !toolbar) {
123                 toolbar = node;
124             }
125
126             // Automatically convert
127             if (node->title == tr("Menu") && !menu) {
128                 node->title = tr(BOOKMARKMENU);
129             }
130             if (node->title == tr(BOOKMARKMENU) && !menu) {
131                 menu = node;
132             }
133         } else {
134             others.append(node);
135         }
136         m_bookmarkRootNode->remove(node);
137     }
138     Q_ASSERT(m_bookmarkRootNode->children().count() == 0);
139     if (!toolbar) {
140         toolbar = new BookmarkNode(BookmarkNode::Folder, m_bookmarkRootNode);
141         toolbar->title = tr(BOOKMARKBAR);
142     } else {
143         m_bookmarkRootNode->add(toolbar);
144     }
145
146     if (!menu) {
147         menu = new BookmarkNode(BookmarkNode::Folder, m_bookmarkRootNode);
148         menu->title = tr(BOOKMARKMENU);
149     } else {
150         m_bookmarkRootNode->add(menu);
151     }
152
153     for (int i = 0; i < others.count(); ++i)
154         menu->add(others.at(i));
155 }
156
157 void BookmarksManager::save() const
158 {
159     if (!m_loaded)
160         return;
161
162     XbelWriter writer;
163     QString dir = QDesktopServices::storageLocation(QDesktopServices::DataLocation);
164     QString bookmarkFile = dir + QLatin1String("/bookmarks.xbel");
165     if (!writer.write(bookmarkFile, m_bookmarkRootNode))
166         qWarning() << "BookmarkManager: error saving to" << bookmarkFile;
167 }
168
169 void BookmarksManager::addBookmark(BookmarkNode *parent, BookmarkNode *node, int row)
170 {
171     if (!m_loaded)
172         return;
173     Q_ASSERT(parent);
174     InsertBookmarksCommand *command = new InsertBookmarksCommand(this, parent, node, row);
175     m_commands.push(command);
176 }
177
178 void BookmarksManager::removeBookmark(BookmarkNode *node)
179 {
180     if (!m_loaded)
181         return;
182
183     Q_ASSERT(node);
184     BookmarkNode *parent = node->parent();
185     int row = parent->children().indexOf(node);
186     RemoveBookmarksCommand *command = new RemoveBookmarksCommand(this, parent, row);
187     m_commands.push(command);
188 }
189
190 void BookmarksManager::setTitle(BookmarkNode *node, const QString &newTitle)
191 {
192     if (!m_loaded)
193         return;
194
195     Q_ASSERT(node);
196     ChangeBookmarkCommand *command = new ChangeBookmarkCommand(this, node, newTitle, true);
197     m_commands.push(command);
198 }
199
200 void BookmarksManager::setUrl(BookmarkNode *node, const QString &newUrl)
201 {
202     if (!m_loaded)
203         return;
204
205     Q_ASSERT(node);
206     ChangeBookmarkCommand *command = new ChangeBookmarkCommand(this, node, newUrl, false);
207     m_commands.push(command);
208 }
209
210 BookmarkNode *BookmarksManager::bookmarks()
211 {
212     if (!m_loaded)
213         load();
214     return m_bookmarkRootNode;
215 }
216
217 BookmarkNode *BookmarksManager::menu()
218 {
219     if (!m_loaded)
220         load();
221
222     for (int i = m_bookmarkRootNode->children().count() - 1; i >= 0; --i) {
223         BookmarkNode *node = m_bookmarkRootNode->children().at(i);
224         if (node->title == tr(BOOKMARKMENU))
225             return node;
226     }
227     Q_ASSERT(false);
228     return 0;
229 }
230
231 BookmarkNode *BookmarksManager::toolbar()
232 {
233     if (!m_loaded)
234         load();
235
236     for (int i = m_bookmarkRootNode->children().count() - 1; i >= 0; --i) {
237         BookmarkNode *node = m_bookmarkRootNode->children().at(i);
238         if (node->title == tr(BOOKMARKBAR))
239             return node;
240     }
241     Q_ASSERT(false);
242     return 0;
243 }
244
245 BookmarksModel *BookmarksManager::bookmarksModel()
246 {
247     if (!m_bookmarkModel)
248         m_bookmarkModel = new BookmarksModel(this, this);
249     return m_bookmarkModel;
250 }
251
252 void BookmarksManager::importBookmarks()
253 {
254     QString fileName = QFileDialog::getOpenFileName(0, tr("Open File"),
255                                                      QString(),
256                                                      tr("XBEL (*.xbel *.xml)"));
257     if (fileName.isEmpty())
258         return;
259
260     XbelReader reader;
261     BookmarkNode *importRootNode = reader.read(fileName);
262     if (reader.error() != QXmlStreamReader::NoError) {
263         QMessageBox::warning(0, QLatin1String("Loading Bookmark"),
264             tr("Error when loading bookmarks on line %1, column %2:\n"
265                "%3").arg(reader.lineNumber()).arg(reader.columnNumber()).arg(reader.errorString()));
266     }
267
268     importRootNode->setType(BookmarkNode::Folder);
269     importRootNode->title = (tr("Imported %1").arg(QDate::currentDate().toString(Qt::SystemLocaleShortDate)));
270     addBookmark(menu(), importRootNode);
271 }
272
273 void BookmarksManager::exportBookmarks()
274 {
275     QString fileName = QFileDialog::getSaveFileName(0, tr("Save File"),
276                                 tr("%1 Bookmarks.xbel").arg(QCoreApplication::applicationName()),
277                                 tr("XBEL (*.xbel *.xml)"));
278     if (fileName.isEmpty())
279         return;
280
281     XbelWriter writer;
282     if (!writer.write(fileName, m_bookmarkRootNode))
283         QMessageBox::critical(0, tr("Export error"), tr("error saving bookmarks"));
284 }
285
286 RemoveBookmarksCommand::RemoveBookmarksCommand(BookmarksManager *m_bookmarkManagaer, BookmarkNode *parent, int row)
287     : QUndoCommand(BookmarksManager::tr("Remove Bookmark"))
288     , m_row(row)
289     , m_bookmarkManagaer(m_bookmarkManagaer)
290     , m_node(parent->children().value(row))
291     , m_parent(parent)
292     , m_done(false)
293 {
294 }
295
296 RemoveBookmarksCommand::~RemoveBookmarksCommand()
297 {
298     if (m_done && !m_node->parent()) {
299         delete m_node;
300     }
301 }
302
303 void RemoveBookmarksCommand::undo()
304 {
305     m_parent->add(m_node, m_row);
306     emit m_bookmarkManagaer->entryAdded(m_node);
307     m_done = false;
308 }
309
310 void RemoveBookmarksCommand::redo()
311 {
312     m_parent->remove(m_node);
313     emit m_bookmarkManagaer->entryRemoved(m_parent, m_row, m_node);
314     m_done = true;
315 }
316
317 InsertBookmarksCommand::InsertBookmarksCommand(BookmarksManager *m_bookmarkManagaer,
318                 BookmarkNode *parent, BookmarkNode *node, int row)
319     : RemoveBookmarksCommand(m_bookmarkManagaer, parent, row)
320 {
321     setText(BookmarksManager::tr("Insert Bookmark"));
322     m_node = node;
323 }
324
325 ChangeBookmarkCommand::ChangeBookmarkCommand(BookmarksManager *m_bookmarkManagaer, BookmarkNode *node,
326                         const QString &newValue, bool title)
327     : QUndoCommand()
328     , m_bookmarkManagaer(m_bookmarkManagaer)
329     , m_title(title)
330     , m_newValue(newValue)
331     , m_node(node)
332 {
333     if (m_title) {
334         m_oldValue = m_node->title;
335         setText(BookmarksManager::tr("Name Change"));
336     } else {
337         m_oldValue = m_node->url;
338         setText(BookmarksManager::tr("Address Change"));
339     }
340 }
341
342 void ChangeBookmarkCommand::undo()
343 {
344     if (m_title)
345         m_node->title = m_oldValue;
346     else
347         m_node->url = m_oldValue;
348     emit m_bookmarkManagaer->entryChanged(m_node);
349 }
350
351 void ChangeBookmarkCommand::redo()
352 {
353     if (m_title)
354         m_node->title = m_newValue;
355     else
356         m_node->url = m_newValue;
357     emit m_bookmarkManagaer->entryChanged(m_node);
358 }
359
360 BookmarksModel::BookmarksModel(BookmarksManager *bookmarkManager, QObject *parent)
361     : QAbstractItemModel(parent)
362     , m_endMacro(false)
363     , m_bookmarksManager(bookmarkManager)
364 {
365     connect(bookmarkManager, SIGNAL(entryAdded(BookmarkNode*)),
366             this, SLOT(entryAdded(BookmarkNode*)));
367     connect(bookmarkManager, SIGNAL(entryRemoved(BookmarkNode*,int,BookmarkNode*)),
368             this, SLOT(entryRemoved(BookmarkNode*,int,BookmarkNode*)));
369     connect(bookmarkManager, SIGNAL(entryChanged(BookmarkNode*)),
370             this, SLOT(entryChanged(BookmarkNode*)));
371 }
372
373 QModelIndex BookmarksModel::index(BookmarkNode *node) const
374 {
375     BookmarkNode *parent = node->parent();
376     if (!parent)
377         return QModelIndex();
378     return createIndex(parent->children().indexOf(node), 0, node);
379 }
380
381 void BookmarksModel::entryAdded(BookmarkNode *item)
382 {
383     Q_ASSERT(item && item->parent());
384     int row = item->parent()->children().indexOf(item);
385     BookmarkNode *parent = item->parent();
386     // item was already added so remove beore beginInsertRows is called
387     parent->remove(item);
388     beginInsertRows(index(parent), row, row);
389     parent->add(item, row);
390     endInsertRows();
391 }
392
393 void BookmarksModel::entryRemoved(BookmarkNode *parent, int row, BookmarkNode *item)
394 {
395     // item was already removed, re-add so beginRemoveRows works
396     parent->add(item, row);
397     beginRemoveRows(index(parent), row, row);
398     parent->remove(item);
399     endRemoveRows();
400 }
401
402 void BookmarksModel::entryChanged(BookmarkNode *item)
403 {
404     QModelIndex idx = index(item);
405     emit dataChanged(idx, idx);
406 }
407
408 bool BookmarksModel::removeRows(int row, int count, const QModelIndex &parent)
409 {
410     if (row < 0 || count <= 0 || row + count > rowCount(parent))
411         return false;
412
413     BookmarkNode *bookmarkNode = node(parent);
414     for (int i = row + count - 1; i >= row; --i) {
415         BookmarkNode *node = bookmarkNode->children().at(i);
416         if (node == m_bookmarksManager->menu()
417             || node == m_bookmarksManager->toolbar())
418             continue;
419
420         m_bookmarksManager->removeBookmark(node);
421     }
422     if (m_endMacro) {
423         m_bookmarksManager->undoRedoStack()->endMacro();
424         m_endMacro = false;
425     }
426     return true;
427 }
428
429 QVariant BookmarksModel::headerData(int section, Qt::Orientation orientation, int role) const
430 {
431     if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
432         switch (section) {
433             case 0: return tr("Title");
434             case 1: return tr("Address");
435         }
436     }
437     return QAbstractItemModel::headerData(section, orientation, role);
438 }
439
440 QVariant BookmarksModel::data(const QModelIndex &index, int role) const
441 {
442     if (!index.isValid() || index.model() != this)
443         return QVariant();
444
445     const BookmarkNode *bookmarkNode = node(index);
446     switch (role) {
447     case Qt::EditRole:
448     case Qt::DisplayRole:
449         if (bookmarkNode->type() == BookmarkNode::Separator) {
450             switch (index.column()) {
451             case 0: return QString(50, 0xB7);
452             case 1: return QString();
453             }
454         }
455
456         switch (index.column()) {
457         case 0: return bookmarkNode->title;
458         case 1: return bookmarkNode->url;
459         }
460         break;
461     case BookmarksModel::UrlRole:
462         return QUrl(bookmarkNode->url);
463         break;
464     case BookmarksModel::UrlStringRole:
465         return bookmarkNode->url;
466         break;
467     case BookmarksModel::TypeRole:
468         return bookmarkNode->type();
469         break;
470     case BookmarksModel::SeparatorRole:
471         return (bookmarkNode->type() == BookmarkNode::Separator);
472         break;
473     case Qt::DecorationRole:
474         if (index.column() == 0) {
475             if (bookmarkNode->type() == BookmarkNode::Folder)
476                 return QApplication::style()->standardIcon(QStyle::SP_DirIcon);
477             return BrowserApplication::instance()->icon(bookmarkNode->url);
478         }
479     }
480
481     return QVariant();
482 }
483
484 int BookmarksModel::columnCount(const QModelIndex &parent) const
485 {
486     return (parent.column() > 0) ? 0 : 2;
487 }
488
489 int BookmarksModel::rowCount(const QModelIndex &parent) const
490 {
491     if (parent.column() > 0)
492         return 0;
493
494     if (!parent.isValid())
495         return m_bookmarksManager->bookmarks()->children().count();
496
497     const BookmarkNode *item = static_cast<BookmarkNode*>(parent.internalPointer());
498     return item->children().count();
499 }
500
501 QModelIndex BookmarksModel::index(int row, int column, const QModelIndex &parent) const
502 {
503     if (row < 0 || column < 0 || row >= rowCount(parent) || column >= columnCount(parent))
504         return QModelIndex();
505
506     // get the parent node
507     BookmarkNode *parentNode = node(parent);
508     return createIndex(row, column, parentNode->children().at(row));
509 }
510
511 QModelIndex BookmarksModel::parent(const QModelIndex &index) const
512 {
513     if (!index.isValid())
514         return QModelIndex();
515
516     BookmarkNode *itemNode = node(index);
517     BookmarkNode *parentNode = (itemNode ? itemNode->parent() : 0);
518     if (!parentNode || parentNode == m_bookmarksManager->bookmarks())
519         return QModelIndex();
520
521     // get the parent's row
522     BookmarkNode *grandParentNode = parentNode->parent();
523     int parentRow = grandParentNode->children().indexOf(parentNode);
524     Q_ASSERT(parentRow >= 0);
525     return createIndex(parentRow, 0, parentNode);
526 }
527
528 bool BookmarksModel::hasChildren(const QModelIndex &parent) const
529 {
530     if (!parent.isValid())
531         return true;
532     const BookmarkNode *parentNode = node(parent);
533     return (parentNode->type() == BookmarkNode::Folder);
534 }
535
536 Qt::ItemFlags BookmarksModel::flags(const QModelIndex &index) const
537 {
538     if (!index.isValid())
539         return Qt::NoItemFlags;
540
541     Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
542
543     BookmarkNode *bookmarkNode = node(index);
544
545     if (bookmarkNode != m_bookmarksManager->menu()
546         && bookmarkNode != m_bookmarksManager->toolbar()) {
547         flags |= Qt::ItemIsDragEnabled;
548         if (bookmarkNode->type() != BookmarkNode::Separator)
549             flags |= Qt::ItemIsEditable;
550     }
551     if (hasChildren(index))
552         flags |= Qt::ItemIsDropEnabled;
553     return flags;
554 }
555
556 Qt::DropActions BookmarksModel::supportedDropActions () const
557 {
558     return Qt::CopyAction | Qt::MoveAction;
559 }
560
561 #define MIMETYPE QLatin1String("application/bookmarks.xbel")
562
563 QStringList BookmarksModel::mimeTypes() const
564 {
565     QStringList types;
566     types << MIMETYPE;
567     return types;
568 }
569
570 QMimeData *BookmarksModel::mimeData(const QModelIndexList &indexes) const
571 {
572     QMimeData *mimeData = new QMimeData();
573     QByteArray data;
574     QDataStream stream(&data, QIODevice::WriteOnly);
575     foreach (QModelIndex index, indexes) {
576         if (index.column() != 0 || !index.isValid())
577             continue;
578         QByteArray encodedData;
579         QBuffer buffer(&encodedData);
580         buffer.open(QBuffer::ReadWrite);
581         XbelWriter writer;
582         const BookmarkNode *parentNode = node(index);
583         writer.write(&buffer, parentNode);
584         stream << encodedData;
585     }
586     mimeData->setData(MIMETYPE, data);
587     return mimeData;
588 }
589
590 bool BookmarksModel::dropMimeData(const QMimeData *data,
591      Qt::DropAction action, int row, int column, const QModelIndex &parent)
592 {
593     if (action == Qt::IgnoreAction)
594         return true;
595
596     if (!data->hasFormat(MIMETYPE)
597         || column > 0)
598         return false;
599
600     QByteArray ba = data->data(MIMETYPE);
601     QDataStream stream(&ba, QIODevice::ReadOnly);
602     if (stream.atEnd())
603         return false;
604
605     QUndoStack *undoStack = m_bookmarksManager->undoRedoStack();
606     undoStack->beginMacro(QLatin1String("Move Bookmarks"));
607
608     while (!stream.atEnd()) {
609         QByteArray encodedData;
610         stream >> encodedData;
611         QBuffer buffer(&encodedData);
612         buffer.open(QBuffer::ReadOnly);
613
614         XbelReader reader;
615         BookmarkNode *rootNode = reader.read(&buffer);
616         QList<BookmarkNode*> children = rootNode->children();
617         for (int i = 0; i < children.count(); ++i) {
618             BookmarkNode *bookmarkNode = children.at(i);
619             rootNode->remove(bookmarkNode);
620             row = qMax(0, row);
621             BookmarkNode *parentNode = node(parent);
622             m_bookmarksManager->addBookmark(parentNode, bookmarkNode, row);
623             m_endMacro = true;
624         }
625         delete rootNode;
626     }
627     return true;
628 }
629
630 bool BookmarksModel::setData(const QModelIndex &index, const QVariant &value, int role)
631 {
632     if (!index.isValid() || (flags(index) & Qt::ItemIsEditable) == 0)
633         return false;
634
635     BookmarkNode *item = node(index);
636
637     switch (role) {
638     case Qt::EditRole:
639     case Qt::DisplayRole:
640         if (index.column() == 0) {
641             m_bookmarksManager->setTitle(item, value.toString());
642             break;
643         }
644         if (index.column() == 1) {
645             m_bookmarksManager->setUrl(item, value.toString());
646             break;
647         }
648         return false;
649     case BookmarksModel::UrlRole:
650         m_bookmarksManager->setUrl(item, value.toUrl().toString());
651         break;
652     case BookmarksModel::UrlStringRole:
653         m_bookmarksManager->setUrl(item, value.toString());
654         break;
655     default:
656         break;
657         return false;
658     }
659
660     return true;
661 }
662
663 BookmarkNode *BookmarksModel::node(const QModelIndex &index) const
664 {
665     BookmarkNode *itemNode = static_cast<BookmarkNode*>(index.internalPointer());
666     if (!itemNode)
667         return m_bookmarksManager->bookmarks();
668     return itemNode;
669 }
670
671
672 AddBookmarkProxyModel::AddBookmarkProxyModel(QObject *parent)
673     : QSortFilterProxyModel(parent)
674 {
675 }
676
677 int AddBookmarkProxyModel::columnCount(const QModelIndex &parent) const
678 {
679     return qMin(1, QSortFilterProxyModel::columnCount(parent));
680 }
681
682 bool AddBookmarkProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
683 {
684     QModelIndex idx = sourceModel()->index(source_row, 0, source_parent);
685     return sourceModel()->hasChildren(idx);
686 }
687
688 AddBookmarkDialog::AddBookmarkDialog(const QString &url, const QString &title, QWidget *parent, BookmarksManager *bookmarkManager)
689     : QDialog(parent)
690     , m_url(url)
691     , m_bookmarksManager(bookmarkManager)
692 {
693     setWindowFlags(Qt::Sheet);
694     if (!m_bookmarksManager)
695         m_bookmarksManager = BrowserApplication::bookmarksManager();
696     setupUi(this);
697     QTreeView *view = new QTreeView(this);
698     m_proxyModel = new AddBookmarkProxyModel(this);
699     BookmarksModel *model = m_bookmarksManager->bookmarksModel();
700     m_proxyModel->setSourceModel(model);
701     view->setModel(m_proxyModel);
702     view->expandAll();
703     view->header()->setStretchLastSection(true);
704     view->header()->hide();
705     view->setItemsExpandable(false);
706     view->setRootIsDecorated(false);
707     view->setIndentation(10);
708     location->setModel(m_proxyModel);
709     view->show();
710     location->setView(view);
711     BookmarkNode *menu = m_bookmarksManager->menu();
712     QModelIndex idx = m_proxyModel->mapFromSource(model->index(menu));
713     view->setCurrentIndex(idx);
714     location->setCurrentIndex(idx.row());
715     name->setText(title);
716 }
717
718 void AddBookmarkDialog::accept()
719 {
720     QModelIndex index = location->view()->currentIndex();
721     index = m_proxyModel->mapToSource(index);
722     if (!index.isValid())
723         index = m_bookmarksManager->bookmarksModel()->index(0, 0);
724     BookmarkNode *parent = m_bookmarksManager->bookmarksModel()->node(index);
725     BookmarkNode *bookmark = new BookmarkNode(BookmarkNode::Bookmark);
726     bookmark->url = m_url;
727     bookmark->title = name->text();
728     m_bookmarksManager->addBookmark(parent, bookmark);
729     QDialog::accept();
730 }
731
732 BookmarksMenu::BookmarksMenu(QWidget *parent)
733     : ModelMenu(parent)
734     , m_bookmarksManager(0)
735 {
736     connect(this, SIGNAL(activated(QModelIndex)),
737             this, SLOT(activated(QModelIndex)));
738     setMaxRows(-1);
739     setHoverRole(BookmarksModel::UrlStringRole);
740     setSeparatorRole(BookmarksModel::SeparatorRole);
741 }
742
743 void BookmarksMenu::activated(const QModelIndex &index)
744 {
745     emit openUrl(index.data(BookmarksModel::UrlRole).toUrl());
746 }
747
748 bool BookmarksMenu::prePopulated()
749 {
750     m_bookmarksManager = BrowserApplication::bookmarksManager();
751     setModel(m_bookmarksManager->bookmarksModel());
752     setRootIndex(m_bookmarksManager->bookmarksModel()->index(1, 0));
753     // initial actions
754     for (int i = 0; i < m_initialActions.count(); ++i)
755         addAction(m_initialActions.at(i));
756     if (!m_initialActions.isEmpty())
757         addSeparator();
758     createMenu(model()->index(0, 0), 1, this);
759     return true;
760 }
761
762 void BookmarksMenu::setInitialActions(QList<QAction*> actions)
763 {
764     m_initialActions = actions;
765     for (int i = 0; i < m_initialActions.count(); ++i)
766         addAction(m_initialActions.at(i));
767 }
768
769 BookmarksDialog::BookmarksDialog(QWidget *parent, BookmarksManager *manager)
770     : QDialog(parent)
771 {
772     m_bookmarksManager = manager;
773     if (!m_bookmarksManager)
774         m_bookmarksManager = BrowserApplication::bookmarksManager();
775     setupUi(this);
776
777     tree->setUniformRowHeights(true);
778     tree->setSelectionBehavior(QAbstractItemView::SelectRows);
779     tree->setSelectionMode(QAbstractItemView::ContiguousSelection);
780     tree->setTextElideMode(Qt::ElideMiddle);
781     m_bookmarksModel = m_bookmarksManager->bookmarksModel();
782     m_proxyModel = new TreeProxyModel(this);
783     connect(search, SIGNAL(textChanged(QString)),
784             m_proxyModel, SLOT(setFilterFixedString(QString)));
785     connect(removeButton, SIGNAL(clicked()), tree, SLOT(removeOne()));
786     m_proxyModel->setSourceModel(m_bookmarksModel);
787     tree->setModel(m_proxyModel);
788     tree->setDragDropMode(QAbstractItemView::InternalMove);
789     tree->setExpanded(m_proxyModel->index(0, 0), true);
790     tree->setAlternatingRowColors(true);
791     QFontMetrics fm(font());
792     int header = fm.width(QLatin1Char('m')) * 40;
793     tree->header()->resizeSection(0, header);
794     tree->header()->setStretchLastSection(true);
795     connect(tree, SIGNAL(activated(QModelIndex)),
796             this, SLOT(open()));
797     tree->setContextMenuPolicy(Qt::CustomContextMenu);
798     connect(tree, SIGNAL(customContextMenuRequested(QPoint)),
799             this, SLOT(customContextMenuRequested(QPoint)));
800     connect(addFolderButton, SIGNAL(clicked()),
801             this, SLOT(newFolder()));
802     expandNodes(m_bookmarksManager->bookmarks());
803     setAttribute(Qt::WA_DeleteOnClose);
804 }
805
806 BookmarksDialog::~BookmarksDialog()
807 {
808     if (saveExpandedNodes(tree->rootIndex()))
809         m_bookmarksManager->changeExpanded();
810 }
811
812 bool BookmarksDialog::saveExpandedNodes(const QModelIndex &parent)
813 {
814     bool changed = false;
815     for (int i = 0; i < m_proxyModel->rowCount(parent); ++i) {
816         QModelIndex child = m_proxyModel->index(i, 0, parent);
817         QModelIndex sourceIndex = m_proxyModel->mapToSource(child);
818         BookmarkNode *childNode = m_bookmarksModel->node(sourceIndex);
819         bool wasExpanded = childNode->expanded;
820         if (tree->isExpanded(child)) {
821             childNode->expanded = true;
822             changed |= saveExpandedNodes(child);
823         } else {
824             childNode->expanded = false;
825         }
826         changed |= (wasExpanded != childNode->expanded);
827     }
828     return changed;
829 }
830
831 void BookmarksDialog::expandNodes(BookmarkNode *node)
832 {
833     for (int i = 0; i < node->children().count(); ++i) {
834         BookmarkNode *childNode = node->children()[i];
835         if (childNode->expanded) {
836             QModelIndex idx = m_bookmarksModel->index(childNode);
837             idx = m_proxyModel->mapFromSource(idx);
838             tree->setExpanded(idx, true);
839             expandNodes(childNode);
840         }
841     }
842 }
843
844 void BookmarksDialog::customContextMenuRequested(const QPoint &pos)
845 {
846     QMenu menu;
847     QModelIndex index = tree->indexAt(pos);
848     index = index.sibling(index.row(), 0);
849     if (index.isValid() && !tree->model()->hasChildren(index)) {
850         menu.addAction(tr("Open"), this, SLOT(open()));
851         menu.addSeparator();
852     }
853     menu.addAction(tr("Delete"), tree, SLOT(removeOne()));
854     menu.exec(QCursor::pos());
855 }
856
857 void BookmarksDialog::open()
858 {
859     QModelIndex index = tree->currentIndex();
860     if (!index.parent().isValid())
861         return;
862     emit openUrl(index.sibling(index.row(), 1).data(BookmarksModel::UrlRole).toUrl());
863 }
864
865 void BookmarksDialog::newFolder()
866 {
867     QModelIndex currentIndex = tree->currentIndex();
868     QModelIndex idx = currentIndex;
869     if (idx.isValid() && !idx.model()->hasChildren(idx))
870         idx = idx.parent();
871     if (!idx.isValid())
872         idx = tree->rootIndex();
873     idx = m_proxyModel->mapToSource(idx);
874     BookmarkNode *parent = m_bookmarksManager->bookmarksModel()->node(idx);
875     BookmarkNode *node = new BookmarkNode(BookmarkNode::Folder);
876     node->title = tr("New Folder");
877     m_bookmarksManager->addBookmark(parent, node, currentIndex.row() + 1);
878 }
879
880 BookmarksToolBar::BookmarksToolBar(BookmarksModel *model, QWidget *parent)
881     : QToolBar(tr("Bookmark"), parent)
882     , m_bookmarksModel(model)
883 {
884     connect(this, SIGNAL(actionTriggered(QAction*)), this, SLOT(triggered(QAction*)));
885     setRootIndex(model->index(0, 0));
886     connect(m_bookmarksModel, SIGNAL(modelReset()), this, SLOT(build()));
887     connect(m_bookmarksModel, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(build()));
888     connect(m_bookmarksModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(build()));
889     connect(m_bookmarksModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(build()));
890     setAcceptDrops(true);
891 }
892
893 void BookmarksToolBar::dragEnterEvent(QDragEnterEvent *event)
894 {
895     const QMimeData *mimeData = event->mimeData();
896     if (mimeData->hasUrls())
897         event->acceptProposedAction();
898     QToolBar::dragEnterEvent(event);
899 }
900
901 void BookmarksToolBar::dropEvent(QDropEvent *event)
902 {
903     const QMimeData *mimeData = event->mimeData();
904     if (mimeData->hasUrls() && mimeData->hasText()) {
905         QList<QUrl> urls = mimeData->urls();
906         QAction *action = actionAt(event->pos());
907         QString dropText;
908         if (action)
909             dropText = action->text();
910         int row = -1;
911         QModelIndex parentIndex = m_root;
912         for (int i = 0; i < m_bookmarksModel->rowCount(m_root); ++i) {
913             QModelIndex idx = m_bookmarksModel->index(i, 0, m_root);
914             QString title = idx.data().toString();
915             if (title == dropText) {
916                 row = i;
917                 if (m_bookmarksModel->hasChildren(idx)) {
918                     parentIndex = idx;
919                     row = -1;
920                 }
921                 break;
922             }
923         }
924         BookmarkNode *bookmark = new BookmarkNode(BookmarkNode::Bookmark);
925         bookmark->url = urls.at(0).toString();
926         bookmark->title = mimeData->text();
927
928         BookmarkNode *parent = m_bookmarksModel->node(parentIndex);
929         BookmarksManager *bookmarksManager = m_bookmarksModel->bookmarksManager();
930         bookmarksManager->addBookmark(parent, bookmark, row);
931         event->acceptProposedAction();
932     }
933     QToolBar::dropEvent(event);
934 }
935
936
937 void BookmarksToolBar::setRootIndex(const QModelIndex &index)
938 {
939     m_root = index;
940     build();
941 }
942
943 QModelIndex BookmarksToolBar::rootIndex() const
944 {
945     return m_root;
946 }
947
948 void BookmarksToolBar::build()
949 {
950     clear();
951     for (int i = 0; i < m_bookmarksModel->rowCount(m_root); ++i) {
952         QModelIndex idx = m_bookmarksModel->index(i, 0, m_root);
953         if (m_bookmarksModel->hasChildren(idx)) {
954             QToolButton *button = new QToolButton(this);
955             button->setPopupMode(QToolButton::InstantPopup);
956             button->setArrowType(Qt::DownArrow);
957             button->setText(idx.data().toString());
958             ModelMenu *menu = new ModelMenu(this);
959             connect(menu, SIGNAL(activated(QModelIndex)),
960                     this, SLOT(activated(QModelIndex)));
961             menu->setModel(m_bookmarksModel);
962             menu->setRootIndex(idx);
963             menu->addAction(new QAction(menu));
964             button->setMenu(menu);
965             button->setToolButtonStyle(Qt::ToolButtonTextOnly);
966             QAction *a = addWidget(button);
967             a->setText(idx.data().toString());
968         } else {
969             QAction *action = addAction(idx.data().toString());
970             action->setData(idx.data(BookmarksModel::UrlRole));
971         }
972     }
973 }
974
975 void BookmarksToolBar::triggered(QAction *action)
976 {
977     QVariant v = action->data();
978     if (v.canConvert<QUrl>()) {
979         emit openUrl(v.toUrl());
980     }
981 }
982
983 void BookmarksToolBar::activated(const QModelIndex &index)
984 {
985     emit openUrl(index.data(BookmarksModel::UrlRole).toUrl());
986 }
987