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