1 /**************************************************************************
2 * Otter Browser: Web browser controlled by the user, not vice-versa.
3 * Copyright (C) 2013 - 2020 Michal Dutkiewicz aka Emdek <michal@emdek.pl>
4 * Copyright (C) 2017 Jan Bajer aka bajasoft <jbajer@gmail.com>
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 *
19 **************************************************************************/
20
21 #include "BookmarksModel.h"
22 #include "Console.h"
23 #include "FeedsManager.h"
24 #include "HistoryManager.h"
25 #include "SessionsManager.h"
26 #include "ThemesManager.h"
27 #include "Utils.h"
28
29 #include <QtCore/QCoreApplication>
30 #include <QtCore/QFile>
31 #include <QtCore/QMimeData>
32 #include <QtCore/QSaveFile>
33 #include <QtWidgets/QMessageBox>
34
35 namespace Otter
36 {
37
Bookmark()38 BookmarksModel::Bookmark::Bookmark() : QStandardItem()
39 {
40 }
41
remove()42 void BookmarksModel::Bookmark::remove()
43 {
44 BookmarksModel *model(qobject_cast<BookmarksModel*>(this->model()));
45
46 if (model)
47 {
48 model->removeBookmark(this);
49 }
50 else
51 {
52 delete this;
53 }
54 }
55
setData(const QVariant & value,int role)56 void BookmarksModel::Bookmark::setData(const QVariant &value, int role)
57 {
58 if (model() && qobject_cast<BookmarksModel*>(model()))
59 {
60 model()->setData(index(), value, role);
61 }
62 else
63 {
64 QStandardItem::setData(value, role);
65 }
66 }
67
setItemData(const QVariant & value,int role)68 void BookmarksModel::Bookmark::setItemData(const QVariant &value, int role)
69 {
70 QStandardItem::setData(value, role);
71 }
72
clone() const73 QStandardItem* BookmarksModel::Bookmark::clone() const
74 {
75 Bookmark *bookmark(new Bookmark());
76 bookmark->setData(data(TypeRole), TypeRole);
77 bookmark->setData(data(UrlRole), UrlRole);
78 bookmark->setData(data(TitleRole), TitleRole);
79 bookmark->setData(data(DescriptionRole), DescriptionRole);
80 bookmark->setData(data(KeywordRole), KeywordRole);
81 bookmark->setData(data(TimeAddedRole), TimeAddedRole);
82 bookmark->setData(data(TimeModifiedRole), TimeModifiedRole);
83 bookmark->setData(data(TimeVisitedRole), TimeVisitedRole);
84 bookmark->setData(data(VisitsRole), VisitsRole);
85
86 return bookmark;
87 }
88
getChild(int index) const89 BookmarksModel::Bookmark* BookmarksModel::Bookmark::getChild(int index) const
90 {
91 BookmarksModel *model(qobject_cast<BookmarksModel*>(this->model()));
92
93 if (model)
94 {
95 return model->getBookmark(model->index(index, 0, this->index()));
96 }
97
98 return nullptr;
99 }
100
getTitle() const101 QString BookmarksModel::Bookmark::getTitle() const
102 {
103 return data(TitleRole).toString();
104 }
105
getDescription() const106 QString BookmarksModel::Bookmark::getDescription() const
107 {
108 return QStandardItem::data(DescriptionRole).toString();
109 }
110
getKeyword() const111 QString BookmarksModel::Bookmark::getKeyword() const
112 {
113 return QStandardItem::data(KeywordRole).toString();
114 }
115
getUrl() const116 QUrl BookmarksModel::Bookmark::getUrl() const
117 {
118 return QStandardItem::data(UrlRole).toUrl();
119 }
120
getTimeAdded() const121 QDateTime BookmarksModel::Bookmark::getTimeAdded() const
122 {
123 return QStandardItem::data(TimeAddedRole).toDateTime();
124 }
125
getTimeModified() const126 QDateTime BookmarksModel::Bookmark::getTimeModified() const
127 {
128 return QStandardItem::data(TimeModifiedRole).toDateTime();
129 }
130
getTimeVisited() const131 QDateTime BookmarksModel::Bookmark::getTimeVisited() const
132 {
133 return QStandardItem::data(TimeVisitedRole).toDateTime();
134 }
135
getIcon() const136 QIcon BookmarksModel::Bookmark::getIcon() const
137 {
138 return data(Qt::DecorationRole).value<QIcon>();
139 }
140
data(int role) const141 QVariant BookmarksModel::Bookmark::data(int role) const
142 {
143 if (role == Qt::DisplayRole)
144 {
145 const BookmarkType type(getType());
146
147 switch (type)
148 {
149 case RootBookmark:
150 {
151 const BookmarksModel *model(qobject_cast<BookmarksModel*>(this->model()));
152
153 if (model && model->getFormatMode() == NotesMode)
154 {
155 return QCoreApplication::translate("Otter::BookmarksModel", "Notes");
156 }
157 }
158
159 return QCoreApplication::translate("Otter::BookmarksModel", "Bookmarks");
160 case TrashBookmark:
161 return QCoreApplication::translate("Otter::BookmarksModel", "Trash");
162 case FeedBookmark:
163 case FolderBookmark:
164 case UrlBookmark:
165 if (QStandardItem::data(role).isNull())
166 {
167 if (type == UrlBookmark)
168 {
169 const BookmarksModel *model(qobject_cast<BookmarksModel*>(this->model()));
170
171 if (model && model->getFormatMode() == NotesMode)
172 {
173 const QString text(data(DescriptionRole).toString());
174 const int newLinePosition(text.indexOf(QLatin1Char('\n')));
175
176 if (newLinePosition > 0 && newLinePosition < (text.count() - 1))
177 {
178 return text.left(newLinePosition) + QStringLiteral("…");
179 }
180
181 return text;
182 }
183 }
184
185 return QCoreApplication::translate("Otter::BookmarksModel", "(Untitled)");
186 }
187
188 break;
189 default:
190 break;
191 }
192 }
193
194 if (role == Qt::DecorationRole)
195 {
196 switch (getType())
197 {
198 case RootBookmark:
199 case FolderBookmark:
200 return ThemesManager::createIcon(QLatin1String("inode-directory"));
201 case FeedBookmark:
202 return ThemesManager::createIcon(QLatin1String("application-rss+xml"));
203 case TrashBookmark:
204 return ThemesManager::createIcon(QLatin1String("user-trash"));
205 case UrlBookmark:
206 return HistoryManager::getIcon(data(UrlRole).toUrl());
207 default:
208 break;
209 }
210
211 return {};
212 }
213
214 if (role == Qt::AccessibleDescriptionRole && getType() == SeparatorBookmark)
215 {
216 return QLatin1String("separator");
217 }
218
219 if (role == IsTrashedRole)
220 {
221 QModelIndex parent(index().parent());
222
223 while (parent.isValid())
224 {
225 const BookmarkType type(static_cast<BookmarkType>(parent.data(TypeRole).toInt()));
226
227 if (type == RootBookmark)
228 {
229 break;
230 }
231
232 if (type == TrashBookmark)
233 {
234 return true;
235 }
236
237 parent = parent.parent();
238 }
239
240 return false;
241 }
242
243 return QStandardItem::data(role);
244 }
245
getRawData(int role) const246 QVariant BookmarksModel::Bookmark::getRawData(int role) const
247 {
248 return QStandardItem::data(role);
249 }
250
getUrls() const251 QVector<QUrl> BookmarksModel::Bookmark::getUrls() const
252 {
253 QVector<QUrl> urls;
254
255 if (getType() == UrlBookmark)
256 {
257 urls.append(data(UrlRole).toUrl());
258 }
259
260 for (int i = 0; i < rowCount(); ++i)
261 {
262 const Bookmark *bookmark(static_cast<Bookmark*>(child(i, 0)));
263
264 if (!bookmark)
265 {
266 continue;
267 }
268
269 switch (bookmark->getType())
270 {
271 case FolderBookmark:
272 urls.append(bookmark->getUrls());
273
274 break;
275 case UrlBookmark:
276 urls.append(bookmark->getUrl());
277
278 break;
279 default:
280 break;
281 }
282 }
283
284 return urls;
285 }
286
getIdentifier() const287 quint64 BookmarksModel::Bookmark::getIdentifier() const
288 {
289 return QStandardItem::data(IdentifierRole).toULongLong();
290 }
291
getType() const292 BookmarksModel::BookmarkType BookmarksModel::Bookmark::getType() const
293 {
294 return static_cast<BookmarkType>(QStandardItem::data(TypeRole).toInt());
295 }
296
getVisits() const297 int BookmarksModel::Bookmark::getVisits() const
298 {
299 return QStandardItem::data(VisitsRole).toInt();
300 }
301
isAncestorOf(Bookmark * child) const302 bool BookmarksModel::Bookmark::isAncestorOf(Bookmark *child) const
303 {
304 if (child == nullptr || child == this)
305 {
306 return false;
307 }
308
309 QStandardItem *parent(child->parent());
310
311 while (parent)
312 {
313 if (parent == this)
314 {
315 return true;
316 }
317
318 parent = parent->parent();
319 }
320
321 return false;
322 }
323
operator <(const QStandardItem & other) const324 bool BookmarksModel::Bookmark::operator<(const QStandardItem &other) const
325 {
326 const BookmarkType type(getType());
327
328 if (type == RootBookmark || type == TrashBookmark)
329 {
330 return false;
331 }
332
333 return QStandardItem::operator<(other);
334 }
335
BookmarksModel(const QString & path,FormatMode mode,QObject * parent)336 BookmarksModel::BookmarksModel(const QString &path, FormatMode mode, QObject *parent) : QStandardItemModel(parent),
337 m_rootItem(new Bookmark()),
338 m_trashItem(new Bookmark()),
339 m_importTargetItem(nullptr),
340 m_mode(mode)
341 {
342 m_rootItem->setData(RootBookmark, TypeRole);
343 m_rootItem->setDragEnabled(false);
344 m_trashItem->setData(TrashBookmark, TypeRole);
345 m_trashItem->setDragEnabled(false);
346 m_trashItem->setEnabled(false);
347
348 appendRow(m_rootItem);
349 appendRow(m_trashItem);
350 setItemPrototype(new Bookmark());
351
352 if (!QFile::exists(path))
353 {
354 return;
355 }
356
357 QFile file(path);
358
359 if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
360 {
361 Console::addMessage(((mode == NotesMode) ? tr("Failed to open notes file: %1") : tr("Failed to open bookmarks file: %1")).arg(file.errorString()), Console::OtherCategory, Console::ErrorLevel, path);
362
363 return;
364 }
365
366 QXmlStreamReader reader(&file);
367
368 if (reader.readNextStartElement() && reader.name() == QLatin1String("xbel") && reader.attributes().value(QLatin1String("version")).toString() == QLatin1String("1.0"))
369 {
370 while (reader.readNextStartElement())
371 {
372 if (reader.name() == QLatin1String("folder") || reader.name() == QLatin1String("bookmark") || reader.name() == QLatin1String("separator"))
373 {
374 readBookmark(&reader, m_rootItem);
375 }
376 else
377 {
378 reader.skipCurrentElement();
379 }
380
381 if (reader.hasError())
382 {
383 m_rootItem->removeRows(0, m_rootItem->rowCount());
384
385 Console::addMessage(((m_mode == NotesMode) ? tr("Failed to load notes file: %1") : tr("Failed to load bookmarks file: %1")).arg(reader.errorString()), Console::OtherCategory, Console::ErrorLevel, path);
386
387 QMessageBox::warning(nullptr, tr("Error"), ((m_mode == NotesMode) ? tr("Failed to load notes file.") : tr("Failed to load bookmarks file.")), QMessageBox::Close);
388
389 return;
390 }
391 }
392 }
393
394 connect(this, &BookmarksModel::itemChanged, this, &BookmarksModel::modelModified);
395 connect(this, &BookmarksModel::rowsInserted, this, &BookmarksModel::modelModified);
396 connect(this, &BookmarksModel::rowsInserted, this, &BookmarksModel::notifyBookmarkModified);
397 connect(this, &BookmarksModel::rowsRemoved, this, &BookmarksModel::modelModified);
398 connect(this, &BookmarksModel::rowsRemoved, this, &BookmarksModel::notifyBookmarkModified);
399 connect(this, &BookmarksModel::rowsMoved, this, &BookmarksModel::modelModified);
400 }
401
beginImport(Bookmark * target,int estimatedUrlsAmount,int estimatedKeywordsAmount)402 void BookmarksModel::beginImport(Bookmark *target, int estimatedUrlsAmount, int estimatedKeywordsAmount)
403 {
404 m_importTargetItem = target;
405
406 beginResetModel();
407 blockSignals(true);
408
409 if (estimatedUrlsAmount > 0)
410 {
411 m_urls.reserve(m_urls.count() + estimatedUrlsAmount);
412 }
413
414 if (estimatedKeywordsAmount > 0)
415 {
416 m_keywords.reserve(m_keywords.count() + estimatedKeywordsAmount);
417 }
418 }
419
endImport()420 void BookmarksModel::endImport()
421 {
422 m_urls.squeeze();
423 m_keywords.squeeze();
424
425 blockSignals(false);
426 endResetModel();
427
428 if (m_importTargetItem)
429 {
430 emit bookmarkModified(m_importTargetItem);
431
432 m_importTargetItem = nullptr;
433 }
434
435 emit modelModified();
436 }
437
trashBookmark(Bookmark * bookmark)438 void BookmarksModel::trashBookmark(Bookmark *bookmark)
439 {
440 if (!bookmark)
441 {
442 return;
443 }
444
445 const BookmarkType type(bookmark->getType());
446
447 if (type != RootBookmark && type != TrashBookmark)
448 {
449 if (type == SeparatorBookmark || bookmark->data(IsTrashedRole).toBool())
450 {
451 bookmark->remove();
452 }
453 else
454 {
455 Bookmark *previousParent(static_cast<Bookmark*>(bookmark->parent()));
456
457 m_trash[bookmark] = {bookmark->parent()->index(), bookmark->row()};
458
459 m_trashItem->appendRow(bookmark->parent()->takeRow(bookmark->row()));
460 m_trashItem->setEnabled(true);
461
462 removeBookmarkUrl(bookmark);
463
464 emit bookmarkModified(bookmark);
465 emit bookmarkTrashed(bookmark, previousParent);
466 emit modelModified();
467 }
468 }
469 }
470
restoreBookmark(Bookmark * bookmark)471 void BookmarksModel::restoreBookmark(Bookmark *bookmark)
472 {
473 if (!bookmark)
474 {
475 return;
476 }
477
478 Bookmark *formerParent(m_trash.contains(bookmark) ? getBookmark(m_trash[bookmark].first) : m_rootItem);
479
480 if (!formerParent || static_cast<BookmarkType>(formerParent->data(TypeRole).toInt()) != FolderBookmark)
481 {
482 formerParent = m_rootItem;
483 }
484
485 if (m_trash.contains(bookmark))
486 {
487 formerParent->insertRow(m_trash[bookmark].second, bookmark->parent()->takeRow(bookmark->row()));
488
489 m_trash.remove(bookmark);
490 }
491 else
492 {
493 formerParent->appendRow(bookmark->parent()->takeRow(bookmark->row()));
494 }
495
496 readdBookmarkUrl(bookmark);
497
498 m_trashItem->setEnabled(m_trashItem->rowCount() > 0);
499
500 emit bookmarkModified(bookmark);
501 emit bookmarkRestored(bookmark);
502 emit modelModified();
503 }
504
removeBookmark(Bookmark * bookmark)505 void BookmarksModel::removeBookmark(Bookmark *bookmark)
506 {
507 if (!bookmark)
508 {
509 return;
510 }
511
512 removeBookmarkUrl(bookmark);
513
514 const quint64 identifier(bookmark->data(IdentifierRole).toULongLong());
515
516 if (identifier > 0 && m_identifiers.contains(identifier))
517 {
518 m_identifiers.remove(identifier);
519 }
520
521 if (!bookmark->data(KeywordRole).toString().isEmpty() && m_keywords.contains(bookmark->data(KeywordRole).toString()))
522 {
523 m_keywords.remove(bookmark->data(KeywordRole).toString());
524 }
525
526 emit bookmarkRemoved(bookmark, static_cast<Bookmark*>(bookmark->parent()));
527
528 bookmark->parent()->removeRow(bookmark->row());
529
530 emit modelModified();
531 }
532
readBookmark(QXmlStreamReader * reader,Bookmark * parent)533 void BookmarksModel::readBookmark(QXmlStreamReader *reader, Bookmark *parent)
534 {
535 Bookmark *bookmark(nullptr);
536
537 if (reader->name() == QLatin1String("folder"))
538 {
539 bookmark = addBookmark(FolderBookmark, {{IdentifierRole, reader->attributes().value(QLatin1String("id")).toULongLong()}, {TimeAddedRole, readDateTime(reader, QLatin1String("added"))}, {TimeModifiedRole, readDateTime(reader, QLatin1String("modified"))}}, parent);
540
541 while (reader->readNext())
542 {
543 if (reader->isStartElement())
544 {
545 if (reader->name() == QLatin1String("title"))
546 {
547 bookmark->setItemData(reader->readElementText().trimmed(), TitleRole);
548 }
549 else if (reader->name() == QLatin1String("desc"))
550 {
551 bookmark->setItemData(reader->readElementText().trimmed(), DescriptionRole);
552 }
553 else if (reader->name() == QLatin1String("folder") || reader->name() == QLatin1String("bookmark") || reader->name() == QLatin1String("separator"))
554 {
555 readBookmark(reader, bookmark);
556 }
557 else if (reader->name() == QLatin1String("info"))
558 {
559 while (reader->readNext())
560 {
561 if (reader->isStartElement())
562 {
563 if (reader->name() == QLatin1String("metadata") && reader->attributes().value(QLatin1String("owner")).toString().startsWith(QLatin1String("http://otter-browser.org/")))
564 {
565 while (reader->readNext())
566 {
567 if (reader->isStartElement())
568 {
569 if (reader->name() == QLatin1String("keyword"))
570 {
571 const QString keyword(reader->readElementText().trimmed());
572
573 if (!keyword.isEmpty())
574 {
575 bookmark->setItemData(keyword, KeywordRole);
576
577 handleKeywordChanged(bookmark, keyword);
578 }
579 }
580 else
581 {
582 reader->skipCurrentElement();
583 }
584 }
585 else if (reader->isEndElement() && reader->name() == QLatin1String("metadata"))
586 {
587 break;
588 }
589 }
590 }
591 else
592 {
593 reader->skipCurrentElement();
594 }
595 }
596 else if (reader->isEndElement() && reader->name() == QLatin1String("info"))
597 {
598 break;
599 }
600 }
601 }
602 else
603 {
604 reader->skipCurrentElement();
605 }
606 }
607 else if (reader->isEndElement() && reader->name() == QLatin1String("folder"))
608 {
609 break;
610 }
611 else if (reader->hasError())
612 {
613 return;
614 }
615 }
616 }
617 else if (reader->name() == QLatin1String("bookmark"))
618 {
619 const bool isFeed(reader->attributes().hasAttribute(QLatin1String("feed")));
620
621 bookmark = addBookmark((isFeed ? FeedBookmark : UrlBookmark), {{IdentifierRole, reader->attributes().value(QLatin1String("id")).toULongLong()}, {UrlRole, reader->attributes().value(QLatin1String("href")).toString()}, {TimeAddedRole, readDateTime(reader, QLatin1String("added"))}, {TimeModifiedRole, readDateTime(reader, QLatin1String("modified"))}, {TimeVisitedRole, readDateTime(reader, QLatin1String("visited"))}}, parent);
622
623 while (reader->readNext())
624 {
625 if (reader->isStartElement())
626 {
627 if (reader->name() == QLatin1String("title"))
628 {
629 bookmark->setItemData(reader->readElementText().trimmed(), TitleRole);
630 }
631 else if (reader->name() == QLatin1String("desc"))
632 {
633 bookmark->setItemData(reader->readElementText().trimmed(), DescriptionRole);
634 }
635 else if (reader->name() == QLatin1String("info"))
636 {
637 while (reader->readNext())
638 {
639 if (reader->isStartElement())
640 {
641 if (reader->name() == QLatin1String("metadata") && reader->attributes().value(QLatin1String("owner")).toString().startsWith(QLatin1String("http://otter-browser.org/")))
642 {
643 while (reader->readNext())
644 {
645 if (reader->isStartElement())
646 {
647 if (reader->name() == QLatin1String("keyword"))
648 {
649 const QString keyword(reader->readElementText().trimmed());
650
651 if (!keyword.isEmpty())
652 {
653 bookmark->setItemData(keyword, KeywordRole);
654
655 handleKeywordChanged(bookmark, keyword);
656 }
657 }
658 else if (reader->name() == QLatin1String("visits"))
659 {
660 bookmark->setItemData(reader->readElementText().toInt(), VisitsRole);
661 }
662 else
663 {
664 reader->skipCurrentElement();
665 }
666 }
667 else if (reader->isEndElement() && reader->name() == QLatin1String("metadata"))
668 {
669 break;
670 }
671 }
672 }
673 else
674 {
675 reader->skipCurrentElement();
676 }
677 }
678 else if (reader->isEndElement() && reader->name() == QLatin1String("info"))
679 {
680 break;
681 }
682 }
683 }
684 else
685 {
686 reader->skipCurrentElement();
687 }
688 }
689 else if (reader->isEndElement() && reader->name() == QLatin1String("bookmark"))
690 {
691 break;
692 }
693 else if (reader->hasError())
694 {
695 return;
696 }
697 }
698
699 if (isFeed)
700 {
701 setupFeed(bookmark);
702 }
703 }
704 else if (reader->name() == QLatin1String("separator"))
705 {
706 addBookmark(SeparatorBookmark, {}, parent);
707
708 reader->readNext();
709 }
710 }
711
writeBookmark(QXmlStreamWriter * writer,Bookmark * bookmark) const712 void BookmarksModel::writeBookmark(QXmlStreamWriter *writer, Bookmark *bookmark) const
713 {
714 if (!bookmark)
715 {
716 return;
717 }
718
719 const BookmarkType type(bookmark->getType());
720
721 switch (type)
722 {
723 case FeedBookmark:
724 case UrlBookmark:
725 writer->writeStartElement(QLatin1String("bookmark"));
726 writer->writeAttribute(QLatin1String("id"), QString::number(bookmark->getRawData(IdentifierRole).toULongLong()));
727
728 if (type == FeedBookmark)
729 {
730 writer->writeAttribute(QLatin1String("feed"), QLatin1String("true"));
731 }
732
733 if (!bookmark->getRawData(UrlRole).toString().isEmpty())
734 {
735 writer->writeAttribute(QLatin1String("href"), bookmark->getRawData(UrlRole).toString());
736 }
737
738 if (bookmark->getRawData(TimeAddedRole).toDateTime().isValid())
739 {
740 writer->writeAttribute(QLatin1String("added"), bookmark->getRawData(TimeAddedRole).toDateTime().toString(Qt::ISODate));
741 }
742
743 if (bookmark->getRawData(TimeModifiedRole).toDateTime().isValid())
744 {
745 writer->writeAttribute(QLatin1String("modified"), bookmark->getRawData(TimeModifiedRole).toDateTime().toString(Qt::ISODate));
746 }
747
748 if (m_mode != NotesMode)
749 {
750 if (bookmark->getRawData(TimeVisitedRole).toDateTime().isValid())
751 {
752 writer->writeAttribute(QLatin1String("visited"), bookmark->getRawData(TimeVisitedRole).toDateTime().toString(Qt::ISODate));
753 }
754
755 writer->writeTextElement(QLatin1String("title"), bookmark->getRawData(TitleRole).toString());
756 }
757
758 if (!bookmark->getRawData(DescriptionRole).toString().isEmpty())
759 {
760 writer->writeTextElement(QLatin1String("desc"), bookmark->getRawData(DescriptionRole).toString());
761 }
762
763 if (m_mode == BookmarksMode && (!bookmark->getRawData(KeywordRole).toString().isEmpty() || bookmark->getRawData(VisitsRole).toInt() > 0))
764 {
765 writer->writeStartElement(QLatin1String("info"));
766 writer->writeStartElement(QLatin1String("metadata"));
767 writer->writeAttribute(QLatin1String("owner"), QLatin1String("http://otter-browser.org/otter-xbel-bookmark"));
768
769 if (!bookmark->getRawData(KeywordRole).toString().isEmpty())
770 {
771 writer->writeTextElement(QLatin1String("keyword"), bookmark->getRawData(KeywordRole).toString());
772 }
773
774 if (bookmark->getRawData(VisitsRole).toInt() > 0)
775 {
776 writer->writeTextElement(QLatin1String("visits"), QString::number(bookmark->getRawData(VisitsRole).toInt()));
777 }
778
779 writer->writeEndElement();
780 writer->writeEndElement();
781 }
782
783 writer->writeEndElement();
784
785 break;
786 case FolderBookmark:
787 writer->writeStartElement(QLatin1String("folder"));
788 writer->writeAttribute(QLatin1String("id"), QString::number(bookmark->getRawData(IdentifierRole).toULongLong()));
789
790 if (bookmark->getRawData(TimeAddedRole).toDateTime().isValid())
791 {
792 writer->writeAttribute(QLatin1String("added"), bookmark->getRawData(TimeAddedRole).toDateTime().toString(Qt::ISODate));
793 }
794
795 if (bookmark->getRawData(TimeModifiedRole).toDateTime().isValid())
796 {
797 writer->writeAttribute(QLatin1String("modified"), bookmark->getRawData(TimeModifiedRole).toDateTime().toString(Qt::ISODate));
798 }
799
800 writer->writeTextElement(QLatin1String("title"), bookmark->getRawData(TitleRole).toString());
801
802 if (!bookmark->getRawData(DescriptionRole).toString().isEmpty())
803 {
804 writer->writeTextElement(QLatin1String("desc"), bookmark->getRawData(DescriptionRole).toString());
805 }
806
807 if (m_mode == BookmarksMode && !bookmark->getRawData(KeywordRole).toString().isEmpty())
808 {
809 writer->writeStartElement(QLatin1String("info"));
810 writer->writeStartElement(QLatin1String("metadata"));
811 writer->writeAttribute(QLatin1String("owner"), QLatin1String("http://otter-browser.org/otter-xbel-bookmark"));
812 writer->writeTextElement(QLatin1String("keyword"), bookmark->getRawData(KeywordRole).toString());
813 writer->writeEndElement();
814 writer->writeEndElement();
815 }
816
817 for (int i = 0; i < bookmark->rowCount(); ++i)
818 {
819 writeBookmark(writer, bookmark->getChild(i));
820 }
821
822 writer->writeEndElement();
823
824 break;
825 default:
826 writer->writeEmptyElement(QLatin1String("separator"));
827
828 break;
829 }
830 }
831
removeBookmarkUrl(Bookmark * bookmark)832 void BookmarksModel::removeBookmarkUrl(Bookmark *bookmark)
833 {
834 if (!bookmark)
835 {
836 return;
837 }
838
839 switch (bookmark->getType())
840 {
841 case FeedBookmark:
842 case UrlBookmark:
843 {
844 const QUrl url(Utils::normalizeUrl(bookmark->data(UrlRole).toUrl()));
845
846 if (!url.isEmpty() && m_urls.contains(url))
847 {
848 m_urls[url].removeAll(bookmark);
849
850 if (m_urls[url].isEmpty())
851 {
852 m_urls.remove(url);
853 }
854 }
855 }
856
857 break;
858 case FolderBookmark:
859 for (int i = 0; i < bookmark->rowCount(); ++i)
860 {
861 removeBookmarkUrl(bookmark->getChild(i));
862 }
863
864 break;
865 default:
866 break;
867 }
868 }
869
readdBookmarkUrl(Bookmark * bookmark)870 void BookmarksModel::readdBookmarkUrl(Bookmark *bookmark)
871 {
872 if (!bookmark)
873 {
874 return;
875 }
876
877 switch (bookmark->getType())
878 {
879 case FolderBookmark:
880 for (int i = 0; i < bookmark->rowCount(); ++i)
881 {
882 readdBookmarkUrl(bookmark->getChild(i));
883 }
884
885 break;
886 case UrlBookmark:
887 {
888 const QUrl url(Utils::normalizeUrl(bookmark->data(UrlRole).toUrl()));
889
890 if (!url.isEmpty())
891 {
892 if (!m_urls.contains(url))
893 {
894 m_urls[url] = QVector<Bookmark*>();
895 }
896
897 m_urls[url].append(bookmark);
898 }
899 }
900
901 break;
902 default:
903 break;
904 }
905 }
906
setupFeed(BookmarksModel::Bookmark * bookmark)907 void BookmarksModel::setupFeed(BookmarksModel::Bookmark *bookmark)
908 {
909 const QUrl normalizedUrl(Utils::normalizeUrl(bookmark->getUrl()));
910 Feed *feed(FeedsManager::createFeed(bookmark->getUrl(), bookmark->getTitle()));
911
912 if (!m_feeds.contains(normalizedUrl))
913 {
914 m_feeds[normalizedUrl] = {};
915
916 connect(feed, &Feed::entriesModified, this, &BookmarksModel::handleFeedModified);
917 }
918
919 m_feeds[normalizedUrl].append(bookmark);
920
921 if (feed)
922 {
923 handleFeedModified(feed);
924 }
925 }
926
emptyTrash()927 void BookmarksModel::emptyTrash()
928 {
929 m_trashItem->removeRows(0, m_trashItem->rowCount());
930 m_trashItem->setEnabled(false);
931
932 m_trash.clear();
933
934 emit modelModified();
935 }
936
handleFeedModified(Feed * feed)937 void BookmarksModel::handleFeedModified(Feed *feed)
938 {
939 if (!hasFeed(feed->getUrl()))
940 {
941 return;
942 }
943
944 const QUrl normalizedUrl(Utils::normalizeUrl(feed->getUrl()));
945 QVector<Bookmark*> bookmarks(m_feeds.value(feed->getUrl()));
946
947 if (feed->getUrl() != normalizedUrl)
948 {
949 bookmarks.append(m_feeds.value(normalizedUrl));
950 }
951
952 if (bookmarks.isEmpty())
953 {
954 return;
955 }
956
957 beginResetModel();
958 blockSignals(true);
959
960 for (int i = 0; i < bookmarks.count(); ++i)
961 {
962 Bookmark *bookmark(bookmarks.at(i));
963 bookmark->removeRows(0, bookmark->rowCount());
964
965 const QVector<Feed::Entry> entries(feed->getEntries());
966
967 for (int j = 0; j < entries.count(); ++j)
968 {
969 const Feed::Entry entry(entries.at(j));
970
971 if (entry.url.isValid())
972 {
973 addBookmark(UrlBookmark, {{UrlRole, entry.url}, {TitleRole, entry.title}, {DescriptionRole, entry.summary}}, bookmark);
974 }
975 }
976 }
977
978 blockSignals(false);
979 endResetModel();
980
981 for (int i = 0; i < bookmarks.count(); ++i)
982 {
983 emit bookmarkModified(bookmarks.at(i));
984 }
985
986 emit modelModified();
987 }
988
handleKeywordChanged(Bookmark * bookmark,const QString & newKeyword,const QString & oldKeyword)989 void BookmarksModel::handleKeywordChanged(Bookmark *bookmark, const QString &newKeyword, const QString &oldKeyword)
990 {
991 if (!oldKeyword.isEmpty() && m_keywords.contains(oldKeyword))
992 {
993 m_keywords.remove(oldKeyword);
994 }
995
996 if (!newKeyword.isEmpty())
997 {
998 m_keywords[newKeyword] = bookmark;
999 }
1000 }
1001
handleUrlChanged(Bookmark * bookmark,const QUrl & newUrl,const QUrl & oldUrl)1002 void BookmarksModel::handleUrlChanged(Bookmark *bookmark, const QUrl &newUrl, const QUrl &oldUrl)
1003 {
1004 if (!oldUrl.isEmpty() && m_urls.contains(oldUrl))
1005 {
1006 m_urls[oldUrl].removeAll(bookmark);
1007
1008 if (m_urls[oldUrl].isEmpty())
1009 {
1010 m_urls.remove(oldUrl);
1011 }
1012 }
1013
1014 if (!newUrl.isEmpty())
1015 {
1016 if (!m_urls.contains(newUrl))
1017 {
1018 m_urls[newUrl] = QVector<Bookmark*>();
1019 }
1020
1021 m_urls[newUrl].append(bookmark);
1022 }
1023 }
1024
notifyBookmarkModified(const QModelIndex & index)1025 void BookmarksModel::notifyBookmarkModified(const QModelIndex &index)
1026 {
1027 Bookmark *bookmark(getBookmark(index));
1028
1029 if (bookmark)
1030 {
1031 emit bookmarkModified(bookmark);
1032 }
1033 }
1034
addBookmark(BookmarkType type,const QMap<int,QVariant> & metaData,Bookmark * parent,int index)1035 BookmarksModel::Bookmark* BookmarksModel::addBookmark(BookmarkType type, const QMap<int, QVariant> &metaData, Bookmark *parent, int index)
1036 {
1037 Bookmark *bookmark(new Bookmark());
1038
1039 if (!parent)
1040 {
1041 parent = m_rootItem;
1042 }
1043
1044 parent->insertRow(((index < 0) ? parent->rowCount() : index), bookmark);
1045
1046 if (type == FeedBookmark || type == UrlBookmark || type == SeparatorBookmark)
1047 {
1048 bookmark->setDropEnabled(false);
1049 }
1050
1051 if (type == FeedBookmark || type == FolderBookmark || type == UrlBookmark)
1052 {
1053 quint64 identifier(metaData.value(IdentifierRole).toULongLong());
1054
1055 if (identifier == 0 || m_identifiers.contains(identifier))
1056 {
1057 identifier = (m_identifiers.isEmpty() ? 1 : (m_identifiers.keys().last() + 1));
1058 }
1059
1060 m_identifiers[identifier] = bookmark;
1061
1062 setItemData(bookmark->index(), metaData);
1063
1064 bookmark->setItemData(identifier, IdentifierRole);
1065
1066 if (!metaData.contains(TimeAddedRole) || !metaData.contains(TimeModifiedRole))
1067 {
1068 const QDateTime currentDateTime(QDateTime::currentDateTimeUtc());
1069
1070 bookmark->setItemData(currentDateTime, TimeAddedRole);
1071 bookmark->setItemData(currentDateTime, TimeModifiedRole);
1072 }
1073
1074 if (type == FeedBookmark || type == UrlBookmark)
1075 {
1076 const QUrl url(metaData.value(UrlRole).toUrl());
1077
1078 if (!url.isEmpty())
1079 {
1080 handleUrlChanged(bookmark, Utils::normalizeUrl(url));
1081 }
1082
1083 if (type == UrlBookmark)
1084 {
1085 bookmark->setFlags(bookmark->flags() | Qt::ItemNeverHasChildren);
1086 }
1087 }
1088 }
1089
1090 if (type == FeedBookmark)
1091 {
1092 setupFeed(bookmark);
1093 }
1094
1095 bookmark->setItemData(type, TypeRole);
1096
1097 emit bookmarkAdded(bookmark);
1098 emit modelModified();
1099
1100 return bookmark;
1101 }
1102
getBookmarkByKeyword(const QString & keyword) const1103 BookmarksModel::Bookmark* BookmarksModel::getBookmarkByKeyword(const QString &keyword) const
1104 {
1105 if (m_keywords.contains(keyword))
1106 {
1107 return m_keywords[keyword];
1108 }
1109
1110 return nullptr;
1111 }
1112
getBookmarkByPath(const QString & path) const1113 BookmarksModel::Bookmark* BookmarksModel::getBookmarkByPath(const QString &path) const
1114 {
1115 if (path == QLatin1String("/"))
1116 {
1117 return m_rootItem;
1118 }
1119
1120 if (path.startsWith(QLatin1Char('#')))
1121 {
1122 return getBookmark(path.mid(1).toULongLong());
1123 }
1124
1125 Bookmark *bookmark(m_rootItem);
1126 const QStringList directories(path.split(QLatin1Char('/'), QString::SkipEmptyParts));
1127
1128 for (int i = 0; i < directories.count(); ++i)
1129 {
1130 bool hasMatch(false);
1131
1132 for (int j = 0; j < bookmark->rowCount(); ++j)
1133 {
1134 Bookmark *childBookmark(bookmark->getChild(j));
1135
1136 if (childBookmark && childBookmark->getTitle() == directories.at(i))
1137 {
1138 bookmark = childBookmark;
1139
1140 hasMatch = true;
1141
1142 break;
1143 }
1144 }
1145
1146 if (!hasMatch)
1147 {
1148 return nullptr;
1149 }
1150 }
1151
1152 return bookmark;
1153 }
1154
getBookmark(const QModelIndex & index) const1155 BookmarksModel::Bookmark* BookmarksModel::getBookmark(const QModelIndex &index) const
1156 {
1157 Bookmark *bookmark(static_cast<Bookmark*>(itemFromIndex(index)));
1158
1159 if (bookmark)
1160 {
1161 return bookmark;
1162 }
1163
1164 const QVariant data(index.data(BookmarksModel::IdentifierRole));
1165
1166 return (data.isValid() ? getBookmark(data.toULongLong()) : nullptr);
1167 }
1168
getBookmark(quint64 identifier) const1169 BookmarksModel::Bookmark* BookmarksModel::getBookmark(quint64 identifier) const
1170 {
1171 if (identifier == 0)
1172 {
1173 return m_rootItem;
1174 }
1175
1176 if (m_identifiers.contains(identifier))
1177 {
1178 return m_identifiers[identifier];
1179 }
1180
1181 return nullptr;
1182 }
1183
getRootItem() const1184 BookmarksModel::Bookmark* BookmarksModel::getRootItem() const
1185 {
1186 return m_rootItem;
1187 }
1188
getTrashItem() const1189 BookmarksModel::Bookmark* BookmarksModel::getTrashItem() const
1190 {
1191 return m_trashItem;
1192 }
1193
mimeData(const QModelIndexList & indexes) const1194 QMimeData* BookmarksModel::mimeData(const QModelIndexList &indexes) const
1195 {
1196 QMimeData *mimeData(new QMimeData());
1197 QStringList texts;
1198 texts.reserve(indexes.count());
1199
1200 QList<QUrl> urls;
1201 urls.reserve(indexes.count());
1202
1203 if (indexes.count() == 1)
1204 {
1205 mimeData->setProperty("x-item-index", indexes.at(0));
1206 }
1207
1208 for (int i = 0; i < indexes.count(); ++i)
1209 {
1210 if (indexes.at(i).isValid() && static_cast<BookmarkType>(indexes.at(i).data(TypeRole).toInt()) == UrlBookmark)
1211 {
1212 texts.append(indexes.at(i).data(UrlRole).toString());
1213 urls.append(indexes.at(i).data(UrlRole).toUrl());
1214 }
1215 }
1216
1217 mimeData->setText(texts.join(QLatin1String(", ")));
1218 mimeData->setUrls(urls);
1219
1220 return mimeData;
1221 }
1222
readDateTime(QXmlStreamReader * reader,const QString & attribute)1223 QDateTime BookmarksModel::readDateTime(QXmlStreamReader *reader, const QString &attribute)
1224 {
1225 QDateTime dateTime(QDateTime::fromString(reader->attributes().value(attribute).toString(), Qt::ISODate));
1226 dateTime.setTimeSpec(Qt::UTC);
1227
1228 return dateTime;
1229 }
1230
mimeTypes() const1231 QStringList BookmarksModel::mimeTypes() const
1232 {
1233 return {QLatin1String("text/uri-list")};
1234 }
1235
getKeywords() const1236 QStringList BookmarksModel::getKeywords() const
1237 {
1238 return m_keywords.keys();
1239 }
1240
findBookmarks(const QString & prefix) const1241 QVector<BookmarksModel::BookmarkMatch> BookmarksModel::findBookmarks(const QString &prefix) const
1242 {
1243 QVector<Bookmark*> matchedBookmarks;
1244 QVector<BookmarkMatch> allMatches;
1245 QVector<BookmarkMatch> currentMatches;
1246 QMultiMap<QDateTime, BookmarkMatch> matchesMap;
1247 QHash<QString, Bookmark*>::const_iterator keywordsIterator;
1248
1249 for (keywordsIterator = m_keywords.constBegin(); keywordsIterator != m_keywords.constEnd(); ++keywordsIterator)
1250 {
1251 if (keywordsIterator.key().startsWith(prefix, Qt::CaseInsensitive))
1252 {
1253 BookmarkMatch match;
1254 match.bookmark = keywordsIterator.value();
1255 match.match = keywordsIterator.key();
1256
1257 matchesMap.insert(match.bookmark->getTimeVisited(), match);
1258
1259 matchedBookmarks.append(match.bookmark);
1260 }
1261 }
1262
1263 currentMatches = matchesMap.values().toVector();
1264
1265 matchesMap.clear();
1266
1267 for (int i = (currentMatches.count() - 1); i >= 0; --i)
1268 {
1269 allMatches.append(currentMatches.at(i));
1270 }
1271
1272 QHash<QUrl, QVector<Bookmark*> >::const_iterator urlsIterator;
1273
1274 for (urlsIterator = m_urls.constBegin(); urlsIterator != m_urls.constEnd(); ++urlsIterator)
1275 {
1276 if (urlsIterator.value().isEmpty() || matchedBookmarks.contains(urlsIterator.value().first()))
1277 {
1278 continue;
1279 }
1280
1281 const QString result(Utils::matchUrl(urlsIterator.key(), prefix));
1282
1283 if (!result.isEmpty())
1284 {
1285 BookmarkMatch match;
1286 match.bookmark = urlsIterator.value().first();
1287 match.match = result;
1288
1289 matchesMap.insert(match.bookmark->getTimeVisited(), match);
1290
1291 matchedBookmarks.append(match.bookmark);
1292 }
1293 }
1294
1295 currentMatches = matchesMap.values().toVector();
1296
1297 matchesMap.clear();
1298
1299 for (int i = (currentMatches.count() - 1); i >= 0; --i)
1300 {
1301 allMatches.append(currentMatches.at(i));
1302 }
1303
1304 return allMatches;
1305 }
1306
findUrls(const QUrl & url,QStandardItem * branch) const1307 QVector<BookmarksModel::Bookmark*> BookmarksModel::findUrls(const QUrl &url, QStandardItem *branch) const
1308 {
1309 if (!branch)
1310 {
1311 branch = m_rootItem;
1312 }
1313
1314 const QUrl normalizedUrl(Utils::normalizeUrl(url));
1315 QVector<Bookmark*> bookmarks;
1316
1317 for (int i = 0; i < branch->rowCount(); ++i)
1318 {
1319 Bookmark *bookmark(static_cast<Bookmark*>(branch->child(i)));
1320
1321 if (bookmark)
1322 {
1323 switch (bookmark->getType())
1324 {
1325 case FolderBookmark:
1326 bookmarks.append(findUrls(url, bookmark));
1327
1328 break;
1329 case UrlBookmark:
1330 if (normalizedUrl == Utils::normalizeUrl(bookmark->getUrl()))
1331 {
1332 bookmarks.append(bookmark);
1333 }
1334
1335 break;
1336 default:
1337 break;
1338 }
1339 }
1340 }
1341
1342 return bookmarks;
1343 }
1344
getBookmarks(const QUrl & url) const1345 QVector<BookmarksModel::Bookmark*> BookmarksModel::getBookmarks(const QUrl &url) const
1346 {
1347 const QUrl normalizedUrl(Utils::normalizeUrl(url));
1348 QVector<BookmarksModel::Bookmark*> bookmarks;
1349
1350 if (m_urls.contains(url))
1351 {
1352 bookmarks = m_urls[url];
1353 }
1354
1355 if (url != normalizedUrl && m_urls.contains(normalizedUrl))
1356 {
1357 bookmarks.append(m_urls[normalizedUrl]);
1358 }
1359
1360 return bookmarks;
1361 }
1362
getFormatMode() const1363 BookmarksModel::FormatMode BookmarksModel::getFormatMode() const
1364 {
1365 return m_mode;
1366 }
1367
moveBookmark(Bookmark * bookmark,Bookmark * newParent,int newRow)1368 bool BookmarksModel::moveBookmark(Bookmark *bookmark, Bookmark *newParent, int newRow)
1369 {
1370 if (!bookmark || !newParent || bookmark == newParent || bookmark->isAncestorOf(newParent))
1371 {
1372 return false;
1373 }
1374
1375 Bookmark *previousParent(static_cast<Bookmark*>(bookmark->parent()));
1376
1377 if (!previousParent)
1378 {
1379 if (newRow < 0)
1380 {
1381 newParent->appendRow(bookmark);
1382 }
1383 else
1384 {
1385 newParent->insertRow(newRow, bookmark);
1386 }
1387
1388 emit modelModified();
1389
1390 return true;
1391 }
1392
1393 const int previousRow(bookmark->row());
1394
1395 if (newRow < 0)
1396 {
1397 newParent->appendRow(bookmark->parent()->takeRow(bookmark->row()));
1398
1399 emit bookmarkMoved(bookmark, previousParent, previousRow);
1400 emit modelModified();
1401
1402 return true;
1403 }
1404
1405 int targetRow(newRow);
1406
1407 if (bookmark->parent() == newParent && bookmark->row() < newRow)
1408 {
1409 --targetRow;
1410 }
1411
1412 newParent->insertRow(targetRow, bookmark->parent()->takeRow(bookmark->row()));
1413
1414 emit bookmarkMoved(bookmark, previousParent, previousRow);
1415 emit modelModified();
1416
1417 return true;
1418 }
1419
canDropMimeData(const QMimeData * data,Qt::DropAction action,int row,int column,const QModelIndex & parent) const1420 bool BookmarksModel::canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const
1421 {
1422 const QModelIndex index(data->property("x-item-index").toModelIndex());
1423
1424 if (index.isValid())
1425 {
1426 const Bookmark *bookmark(getBookmark(index));
1427 Bookmark *newParent(getBookmark(parent));
1428
1429 return (bookmark && newParent && bookmark != newParent && !bookmark->isAncestorOf(newParent));
1430 }
1431
1432 return QStandardItemModel::canDropMimeData(data, action, row, column, parent);
1433 }
1434
dropMimeData(const QMimeData * data,Qt::DropAction action,int row,int column,const QModelIndex & parent)1435 bool BookmarksModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
1436 {
1437 const BookmarkType type(static_cast<BookmarkType>(parent.data(TypeRole).toInt()));
1438
1439 if (type == FolderBookmark || type == RootBookmark || type == TrashBookmark)
1440 {
1441 const QModelIndex index(data->property("x-item-index").toModelIndex());
1442
1443 if (index.isValid())
1444 {
1445 return moveBookmark(getBookmark(index), getBookmark(parent), row);
1446 }
1447
1448 if (data->hasUrls())
1449 {
1450 const QVector<QUrl> urls(Utils::extractUrls(data));
1451
1452 for (int i = 0; i < urls.count(); ++i)
1453 {
1454 addBookmark(UrlBookmark, {{UrlRole, urls.at(i)}, {TitleRole, (data->property("x-url-title").toString().isEmpty() ? urls.at(i).toString() : data->property("x-url-title").toString())}}, getBookmark(parent), row);
1455 }
1456
1457 return true;
1458 }
1459
1460 return QStandardItemModel::dropMimeData(data, action, row, column, parent);
1461 }
1462
1463 return false;
1464 }
1465
save(const QString & path) const1466 bool BookmarksModel::save(const QString &path) const
1467 {
1468 if (SessionsManager::isReadOnly())
1469 {
1470 return false;
1471 }
1472
1473 QSaveFile file(path);
1474
1475 if (!file.open(QIODevice::WriteOnly))
1476 {
1477 return false;
1478 }
1479
1480 QXmlStreamWriter writer(&file);
1481 writer.setAutoFormatting(true);
1482 writer.setAutoFormattingIndent(-1);
1483 writer.writeStartDocument();
1484 writer.writeDTD(QLatin1String("<!DOCTYPE xbel>"));
1485 writer.writeStartElement(QLatin1String("xbel"));
1486 writer.writeAttribute(QLatin1String("version"), QLatin1String("1.0"));
1487
1488 for (int i = 0; i < m_rootItem->rowCount(); ++i)
1489 {
1490 writeBookmark(&writer, m_rootItem->getChild(i));
1491 }
1492
1493 writer.writeEndDocument();
1494
1495 return file.commit();
1496 }
1497
setData(const QModelIndex & index,const QVariant & value,int role)1498 bool BookmarksModel::setData(const QModelIndex &index, const QVariant &value, int role)
1499 {
1500 Bookmark *bookmark(getBookmark(index));
1501
1502 if (!bookmark)
1503 {
1504 return QStandardItemModel::setData(index, value, role);
1505 }
1506
1507 switch (role)
1508 {
1509 case DescriptionRole:
1510 if (m_mode == NotesMode)
1511 {
1512 const QString title(value.toString().section(QLatin1Char('\n'), 0, 0).left(100));
1513
1514 setData(index, ((title == value.toString().trimmed()) ? title : title + QStringLiteral("…")), TitleRole);
1515 }
1516
1517 break;
1518 case KeywordRole:
1519 if (value.toString() != index.data(KeywordRole).toString())
1520 {
1521 handleKeywordChanged(bookmark, value.toString(), index.data(KeywordRole).toString());
1522 }
1523
1524 break;
1525 case UrlRole:
1526 if (value.toUrl() != index.data(UrlRole).toUrl())
1527 {
1528 handleUrlChanged(bookmark, Utils::normalizeUrl(value.toUrl()), Utils::normalizeUrl(index.data(UrlRole).toUrl()));
1529 }
1530
1531 break;
1532 default:
1533 break;
1534 }
1535
1536 bookmark->setItemData(value, role);
1537
1538 switch (role)
1539 {
1540 case TitleRole:
1541 case UrlRole:
1542 case DescriptionRole:
1543 case IdentifierRole:
1544 case TypeRole:
1545 case KeywordRole:
1546 case TimeAddedRole:
1547 case TimeModifiedRole:
1548 case TimeVisitedRole:
1549 case VisitsRole:
1550 emit bookmarkModified(bookmark);
1551 emit modelModified();
1552
1553 break;
1554 default:
1555 break;
1556 }
1557
1558 return true;
1559 }
1560
hasBookmark(const QUrl & url) const1561 bool BookmarksModel::hasBookmark(const QUrl &url) const
1562 {
1563 return (m_urls.contains(Utils::normalizeUrl(url)) || m_urls.contains(url));
1564 }
1565
hasFeed(const QUrl & url) const1566 bool BookmarksModel::hasFeed(const QUrl &url) const
1567 {
1568 return (m_feeds.contains(Utils::normalizeUrl(url)) || m_feeds.contains(url));
1569 }
1570
hasKeyword(const QString & keyword) const1571 bool BookmarksModel::hasKeyword(const QString &keyword) const
1572 {
1573 return m_keywords.contains(keyword);
1574 }
1575
1576 }
1577