BibleTime
btbookmarksmodel.cpp
Go to the documentation of this file.
1 /*********
2 *
3 * In the name of the Father, and of the Son, and of the Holy Spirit.
4 *
5 * This file is part of BibleTime's source code, https://bibletime.info/
6 *
7 * Copyright 1999-2021 by the BibleTime developers.
8 * The BibleTime source code is licensed under the GNU General Public License
9 * version 2.0.
10 *
11 **********/
12 
13 /**
14  Total change list that should be applied after refactoring complete:
15  non latin bookmark titles shown with unrecognized symbols
16  feature request: hold Shift and Ctrl upon dragging item
17  move loader to private class
18  add ability to create bookmarks data with setData/insertRows
19  unrecognized characters increaases in size file each save/load
20  root folder for bookmarks
21 */
22 
23 #include "btbookmarksmodel.h"
24 
25 #include <algorithm>
26 #include <memory>
27 #include <QDir>
28 #include <QDomElement>
29 #include <QDomNode>
30 #include <QFile>
31 #include <QIODevice>
32 #include <QList>
33 #include <QString>
34 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
35  #include <QTextCodec>
36 #endif
37 #include <QTextStream>
38 #include <QTime>
39 #include <QTimer>
40 #include <utility>
41 #include "../util/btassert.h"
42 #include "../util/btconnect.h"
43 #include "../util/cresmgr.h"
44 #include "../util/directory.h"
45 #include "../util/tool.h"
46 #include "btglobal.h"
47 #include "config/btconfig.h"
49 #include "keys/cswordversekey.h"
50 #include "managers/cswordbackend.h"
51 
52 
53 #define CURRENT_SYNTAX_VERSION 1
54 
55 
56 namespace {
57 
58 inline QString toHeader(QString const & key, QString const & moduleName)
59 { return QStringLiteral("%1 (%2)").arg(key).arg(moduleName); }
60 
61 class BookmarkFolder;
62 
64 
65 public: // methods:
66 
67  BookmarkItemBase(BookmarkFolder * parent = nullptr);
68 
70  : m_parent(other.m_parent)
71  , m_text(other.m_text)
72  {}
73 
74  virtual ~BookmarkItemBase() {}
75 
76  void setText(QString const & text) { m_text = text; }
77 
78  QString const & text() const { return m_text; }
79 
80  virtual QString toolTip() const { return {}; }
81 
82  virtual Qt::ItemFlags flags() const noexcept { return Qt::NoItemFlags; }
83 
84  virtual QIcon const & icon() const noexcept {
85  static QIcon const noIcon;
86  return noIcon;
87  }
88 
89  void setParent(BookmarkFolder * parent) { m_parent = parent; }
90 
91  BookmarkFolder * parent() const { return m_parent; }
92 
93  /** \returns index of this item in parent's child array. */
94  int index() const;
95 
96 private:
97 
99  QString m_text;
100 
101 };
102 
103 class BookmarkItem final : public BookmarkItemBase {
104 public:
105 
106  BookmarkItem(BookmarkFolder * parent);
107 
108  /** Creates a bookmark with module, key and description. */
109  BookmarkItem(const CSwordModuleInfo & module, const QString & key,
110  const QString & description, const QString & title);
111 
112  /** Creates a copy. */
113  BookmarkItem(const BookmarkItem & other);
114 
115  Qt::ItemFlags flags() const noexcept final override {
116  return Qt::ItemIsSelectable // | Qt::ItemIsEditable
117  | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled
118  | Qt::ItemIsEnabled;
119  }
120 
121  QIcon const & icon() const noexcept final override
122  { return CResMgr::mainIndex::bookmark::icon(); }
123 
124  /** Returns the used module, 0 if there is no such module. */
126 
127  /** Returns the used key. */
128  QString key() const;
129 
130  void setKey(QString const & key) { m_key = key; }
131 
132  /** Returns the used description. */
133  QString const & description() const { return m_description; }
134 
135  void setDescription(QString const & description)
137 
138  /** Returns a tooltip for this bookmark. */
139  QString toolTip() const override;
140 
141  /** Returns the english key.*/
142  QString const & englishKey() const { return m_key; }
143 
144  void setModule(QString const & moduleName)
145  { m_moduleName = moduleName; }
146 
147  QString const & moduleName() const { return m_moduleName; }
148 
149 private:
150  QString m_key;
151  QString m_description;
152  QString m_moduleName;
153 
154 };
155 
156 class BookmarkFolder final : public BookmarkItemBase {
157 public:
158 
159  BookmarkFolder(const QString & name, BookmarkFolder * parent = nullptr);
160 
161  ~BookmarkFolder() final override { qDeleteAll(m_children); }
162 
163  Qt::ItemFlags flags() const noexcept final override {
164  return Qt::ItemIsSelectable | Qt::ItemIsEditable
165  | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled
166  | Qt::ItemIsEnabled;
167  }
168 
169  QIcon const & icon() const noexcept final override
170  { return CResMgr::mainIndex::closedFolder::icon(); }
171 
172  /** Children routines. */
173  void addChild(BookmarkItemBase * child) {
174  child->setParent(this);
175  BT_ASSERT(!m_children.contains(child));
176  m_children.append(child);
177  }
178 
179  QList<BookmarkItemBase *> & children() noexcept { return m_children; }
180  QList<BookmarkItemBase *> const & children() const noexcept
181  { return m_children; }
182 
183  void insertChild(int index, BookmarkItemBase * child) {
184  child->setParent(this);
185  BT_ASSERT(!m_children.contains(child));
186  m_children.insert(index, child);
187  }
188 
189  void insertChildren(int index, QList<BookmarkItemBase *> children) {
190  for (auto * const c : children)
191  insertChild(index++, c);
192  }
193 
194  void removeChild(int index) {
195  delete m_children[index];
196  m_children.removeAt(index);
197  }
198 
199  /** Returns true if the given item is this or a direct or indirect subitem of this. */
200  bool hasDescendant(BookmarkItemBase const * item) const;
201 
202  /** Creates a deep copy of this item. */
203  BookmarkFolder * deepCopy() const;
204 
205 private: // Fields:
206 
208 
209 };
210 
211 
212 BookmarkItemBase::BookmarkItemBase(BookmarkFolder * parent)
213  : m_parent(parent)
214 {
215  if (m_parent)
216  m_parent->addChild(this);
217 }
218 
221  return m_parent->children().indexOf(
222  const_cast<BookmarkItemBase *>(this));
223 }
224 
225 }
226 
228 
229 public: // methods:
230 
232  : m_rootItem(new BookmarkFolder(QStringLiteral("Root")))
233  , q_ptr(parent)
234  {
235  m_saveTimer.setInterval(30 * 1000);
236  m_saveTimer.setSingleShot(true);
237  }
238  ~BtBookmarksModelPrivate() { delete m_rootItem; }
239 
240  static QString defaultBookmarksFile() {
241  return util::directory::getUserBaseDir().absolutePath()
242  + QStringLiteral("/bookmarks.xml");
243  }
244 
245  BookmarkItemBase * item(const QModelIndex & index) const {
246  if (index.isValid())
247  return static_cast<BookmarkItemBase *>(index.internalPointer());
248  return m_rootItem;
249  }
250 
251  template <typename T>
252  T * itemAs(QModelIndex const & index) const
253  { return dynamic_cast<T *>(item(index)); }
254 
255  void needSave(){
256  if(m_defaultModel == q_ptr){
257  if(!m_saveTimer.isActive())
258  m_saveTimer.start();
259  }
260  }
261 
262  /** Loads a list of items (with subitem trees) from a named file
263  * or from the default bookmarks file. */
264  QList<BookmarkItemBase *> loadTree(QString fileName = QString()) {
265  QList<BookmarkItemBase*> itemList;
266 
267  QDomDocument doc;
268  QString bookmarksFile = loadXmlFromFile(fileName);
269  if (bookmarksFile.isNull())
270  return QList<BookmarkItemBase*>();
271 
272  doc.setContent(bookmarksFile);
273  QDomElement document = doc.documentElement();
274  if (document.tagName() != QStringLiteral("SwordBookmarks")) {
275  qWarning("Not a BibleTime Bookmark XML file");
276  return QList<BookmarkItemBase*>();
277  }
278 
279  QDomElement child = document.firstChild().toElement();
280 
281  while ( !child.isNull() && child.parentNode() == document) {
282  BookmarkItemBase* i = handleXmlElement(child, nullptr);
283  itemList.append(i);
284  if (!child.nextSibling().isNull()) {
285  child = child.nextSibling().toElement();
286  }
287  else {
288  child = QDomElement(); //null
289  }
290 
291  }
292 
293  return itemList;
294  }
295 
296  /** Create a new item from a document element. */
297  BookmarkItemBase * handleXmlElement(QDomElement & element, BookmarkFolder * parent) {
298  if (element.tagName() == QStringLiteral("Folder")) {
299  BookmarkFolder* newFolder = new BookmarkFolder(QString(), parent);
300  if (element.hasAttribute(QStringLiteral("caption"))) {
301  newFolder->setText(element.attribute(
302  QStringLiteral("caption")));
303  }
304  QDomNodeList childList = element.childNodes();
305  for (int i = 0; i < childList.length(); i++) {
306  QDomElement newElement = childList.at(i).toElement();
307  handleXmlElement(newElement, newFolder); // passing parent in constructor will add items to tree
308  }
309  return newFolder;
310  }
311  if (element.tagName() == QStringLiteral("Bookmark")) {
312  BookmarkItem* newBookmarkItem = new BookmarkItem(parent);
313  if (element.hasAttribute(QStringLiteral("modulename"))) {
314  //we use the name in all cases, even if the module isn't installed anymore
315  newBookmarkItem->setModule(
316  element.attribute(QStringLiteral("modulename")));
317  }
318  if (element.hasAttribute(QStringLiteral("key"))) {
319  newBookmarkItem->setKey(
320  element.attribute(QStringLiteral("key")));
321  }
322  if (element.hasAttribute(QStringLiteral("description"))) {
323  newBookmarkItem->setDescription(
324  element.attribute(QStringLiteral("description")));
325  }
326  if (element.hasAttribute(QStringLiteral("title"))) {
327  newBookmarkItem->setText(
328  element.attribute(QStringLiteral("title")));
329  }
330  return newBookmarkItem;
331  }
332  return nullptr;
333  }
334 
335  /** Loads a bookmark XML document from a named file or from the default bookmarks file. */
336  QString loadXmlFromFile(QString fileName = QString()) {
337  if (fileName.isEmpty())
338  fileName = defaultBookmarksFile();
339 
340  QTextStream t;
341  t.setAutoDetectUnicode(false);
342  #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
343  t.setCodec(QTextCodec::codecForName(QByteArrayLiteral("UTF-8")));
344  #else
345  t.setEncoding(QStringConverter::Utf8);
346  #endif
347 
348  QFile file(fileName);
349  if (!file.open(QIODevice::ReadOnly))
350  return {};
351 
352  t.setDevice(&file);
353  return t.readAll();
354  }
355 
356  /** Takes one item and saves the tree which is under it to a named file
357  * or to the default bookmarks file, asking the user about overwriting if necessary. */
358  QString serializeTreeFromRootItem(BookmarkFolder * rootItem) {
359  BT_ASSERT(rootItem);
360 
361  QDomDocument doc(QStringLiteral("DOC"));
362  doc.appendChild(
363  doc.createProcessingInstruction(
364  QStringLiteral("xml"),
365  QStringLiteral("version=\"1.0\" encoding=\"UTF-8\"")));
366 
367  auto content =
368  doc.createElement(QStringLiteral("SwordBookmarks"));
369  content.setAttribute(QStringLiteral("syntaxVersion"),
371  doc.appendChild(content);
372 
373  //append the XML nodes of all child items
374 
375  for (auto const * const itemPtr : rootItem->children())
376  saveItem(itemPtr, content);
377  return doc.toString();
378  }
379 
380  /** Writes one item to a document element. */
381  void saveItem(BookmarkItemBase const * item, QDomElement & parentElement) {
382  BookmarkFolder const * folderItem = nullptr;
383  BookmarkItem const * bookmarkItem = nullptr;
384 
385  if ((folderItem = dynamic_cast<BookmarkFolder const *>(item))) {
386  auto elem =
387  parentElement.ownerDocument().createElement(
388  QStringLiteral("Folder"));
389  elem.setAttribute(QStringLiteral("caption"), folderItem->text());
390 
391  parentElement.appendChild(elem);
392 
393  for (auto const * const itemPtr : folderItem->children())
394  saveItem(itemPtr, elem);
395  }
396  else if ((bookmarkItem = dynamic_cast<BookmarkItem const *>(item))) {
397  auto elem =
398  parentElement.ownerDocument().createElement(
399  QStringLiteral("Bookmark"));
400 
401  elem.setAttribute(QStringLiteral("key"),
402  bookmarkItem->englishKey());
403  elem.setAttribute(QStringLiteral("description"),
404  bookmarkItem->description());
405  elem.setAttribute(QStringLiteral("modulename"),
406  bookmarkItem->moduleName());
407  elem.setAttribute(QStringLiteral("moduledescription"),
408  bookmarkItem->module()
409  ? bookmarkItem->module()->config(
411  : QString());
412  if (!bookmarkItem->text().isEmpty()) {
413  elem.setAttribute(QStringLiteral("title"), bookmarkItem->text());
414  }
415  parentElement.appendChild(elem);
416  }
417  }
418 
419 
420 public: // fields:
421 
422  BookmarkFolder * m_rootItem;
423  QTimer m_saveTimer;
425 
426  Q_DECLARE_PUBLIC(BtBookmarksModel)
427  BtBookmarksModel * const q_ptr;
428 
429 };
430 
431 BtBookmarksModel * BtBookmarksModelPrivate::m_defaultModel = nullptr;
432 
433 BookmarkFolder::BookmarkFolder(const QString & name, BookmarkFolder * parent)
434  : BookmarkItemBase(parent) {
435  setText(name);
436 }
437 
438 bool BookmarkFolder::hasDescendant(BookmarkItemBase const * const item) const {
439  if (this == item)
440  return true;
441  if (children().indexOf(const_cast<BookmarkItemBase *>(item)) > -1)
442  return true;
443  for (auto const * const childItem : children())
444  if (BookmarkFolder const * const folder =
445  dynamic_cast<BookmarkFolder const *>(childItem))
446  if (folder->hasDescendant(childItem))
447  return true;
448  return false;
449 }
450 
452  BookmarkFolder* newFolder = new BookmarkFolder(this->text());
453  for (auto const * const subitem : children()) {
454  if (BookmarkItem const * const bmItem =
455  dynamic_cast<BookmarkItem const *>(subitem))
456  {
457  newFolder->addChild(new BookmarkItem(*bmItem));
458  } else if (BookmarkFolder const * const bmFolder =
459  dynamic_cast<BookmarkFolder const *>(subitem))
460  {
461  newFolder->addChild(bmFolder->deepCopy());
462  }
463  }
464  return newFolder;
465 }
466 
468  const QString & key,
469  const QString & description,
470  const QString & title)
471  : m_description(description)
472  , m_moduleName(module.name())
473 {
474  Q_UNUSED(title)
475 
476  if (((module.type() == CSwordModuleInfo::Bible) || (module.type() == CSwordModuleInfo::Commentary))) {
477  /// here we only translate \param key into english
478  sword::VerseKey vk(key.toUtf8().constData(), key.toUtf8().constData(),
479  static_cast<sword::VerseKey *>(module.swordModule().getKey())->getVersificationSystem());
480  CSwordVerseKey k(&vk, &module);
481  k.setLocale("en");
482  m_key = k.key();
483  }
484  else {
485  m_key = key;
486  }
487 
488  setText(toHeader(key, module.name()));
489 }
490 
492  : BookmarkItemBase(parent) {
493  setText(toHeader(key(), module() ? module()->name() : QObject::tr("unknown")));
494 }
495 
497  : BookmarkItemBase(other)
498  , m_key(other.m_key)
499  , m_description(other.m_description)
500  , m_moduleName(other.m_moduleName)
501 {
502  setText(toHeader(key(), module() ? module()->name() : QObject::tr("unknown")));
503 }
504 
507 
508 QString BookmarkItem::key() const {
509  const QString englishKeyName = englishKey();
510  if (!module()) {
511  return englishKeyName;
512  }
513 
514  QString returnKeyName = englishKeyName;
515  if ((module()->type() == CSwordModuleInfo::Bible) || (module()->type() == CSwordModuleInfo::Commentary)) {
516  /// here we only translate \param key into current book name language
517  sword::VerseKey vk(englishKeyName.toUtf8().constData(), englishKeyName.toUtf8().constData(),
518  static_cast<sword::VerseKey *>(module()->swordModule().getKey())->getVersificationSystem());
519  CSwordVerseKey k(&vk, module());
520  k.setLocale(CSwordBackend::instance().booknameLanguage().toLatin1());
521  returnKeyName = k.key();
522  }
523 
524  return returnKeyName;
525 }
526 
527 QString BookmarkItem::toolTip() const {
528  auto const * const m = module();
529  if (!m)
530  return QString();
531 
532  FilterOptions filterOptions = btConfig().getFilterOptions();
533  filterOptions.footnotes = false;
534  filterOptions.scriptureReferences = false;
535  CSwordBackend::instance().setFilterOptions(filterOptions);
536 
537  std::unique_ptr<CSwordKey> k(m->createKey());
538  BT_ASSERT(k);
539  k->setKey(key());
540 
541  // Language const * lang = m->language();
542  // BtConfig::FontSettingsPair fontPair = getBtConfig().getFontForLanguage(lang);
543 
544  auto const header = toHeader(key(), m->name());
545  auto const & txt = text();
546  if (txt == header)
547  return QStringLiteral("<b>%1)</b><hr>%2").arg(header, description());
548  return QStringLiteral("<b>%1)</b><br>%2<hr>%3")
549  .arg(header, txt, description());
550 }
551 
552 
554  : QAbstractItemModel(parent)
555  , d_ptr(new BtBookmarksModelPrivate(this))
556 {
557  load();
558 }
559 
560 BtBookmarksModel::BtBookmarksModel(QString const & fileName, QObject * parent)
561  : QAbstractItemModel(parent)
562  , d_ptr(new BtBookmarksModelPrivate(this))
563 { load(fileName); }
564 
566  Q_D(BtBookmarksModel);
567 
568  if(d->m_saveTimer.isActive())
569  save();
570 
571  delete d_ptr;
572 }
573 
574 int BtBookmarksModel::rowCount(const QModelIndex & parent) const {
575  Q_D(const BtBookmarksModel);
576 
577  if (auto const * const f = dynamic_cast<BookmarkFolder *>(d->item(parent)))
578  return f->children().size();
579  return 0;
580 }
581 
582 int BtBookmarksModel::columnCount(const QModelIndex & parent) const {
583  Q_UNUSED(parent)
584  return 1;
585 }
586 
587 bool BtBookmarksModel::hasChildren(const QModelIndex & parent) const {
588  return rowCount(parent) > 0;
589 }
590 QModelIndex BtBookmarksModel::index(int row, int column, const QModelIndex & parent) const {
591  Q_D(const BtBookmarksModel);
592 
593  auto const * const f = dynamic_cast<BookmarkFolder *>(d->item(parent));
594  if (f && f->children().size() > row && row >= 0)
595  return createIndex(row, column, f->children()[row]);
596  return QModelIndex();
597 }
598 
599 QModelIndex BtBookmarksModel::parent(const QModelIndex & index) const {
600  Q_D(const BtBookmarksModel);
601 
602  const BookmarkItemBase * i = d->item(index);
603  return (i->parent() == nullptr || i->parent()->parent() == nullptr) ?
604  QModelIndex() : createIndex(i->parent()->index(), 0, i->parent());
605 }
606 
607 QVariant BtBookmarksModel::data(const QModelIndex & index, int role) const {
608  Q_D(const BtBookmarksModel);
609 
610  const BookmarkItemBase * i = d->item(index);
611 
612  switch(role)
613  {
614  case Qt::DisplayRole:
615  case Qt::EditRole:
616  return i->text();
617  case Qt::ToolTipRole:
618  return i->toolTip();
619  case Qt::DecorationRole:
620  return i->icon();
621  case TypeRole:
622  {
623  if(isBookmark(index))
624  return QStringLiteral("bookmark");
625  else
626  return QStringLiteral("folder");
627  }
628  }
629  return QVariant();
630 }
631 
632 Qt::ItemFlags BtBookmarksModel::flags(const QModelIndex & index) const {
633  Q_D(const BtBookmarksModel);
634 
635  return d->item(index)->flags();
636 }
637 
638 QVariant BtBookmarksModel::headerData(int section, Qt::Orientation orientation, int role) const {
639  Q_UNUSED(section)
640  Q_UNUSED(orientation)
641  Q_UNUSED(role)
642 
643  return QVariant();
644 }
645 
646 bool BtBookmarksModel::setData(const QModelIndex & index, const QVariant & val, int role) {
647  Q_D(BtBookmarksModel);
648 
649  BookmarkItemBase * i = d->item(index);
650  if (role == Qt::DisplayRole || role == Qt::EditRole) {
651  i->setText(val.toString());
652  if(dynamic_cast<BookmarkFolder *>(i) || dynamic_cast<BookmarkItem *>(i))
653  d->needSave();
654  return true;
655  }
656  return false;
657 }
658 
659 bool BtBookmarksModel::removeRows(int row, int count, const QModelIndex & parent)
660 {
661  Q_D(BtBookmarksModel);
662  if (auto * const f = dynamic_cast<BookmarkFolder *>(d->item(parent))) {
663  BT_ASSERT(row + count <= f->children().size());
664  beginRemoveRows(parent, row, row + count - 1);
665  for(int i = 0; i < count; ++i)
666  f->removeChild(row);
667  endRemoveRows();
668  d->needSave();
669  return true;
670  }
671  return false;
672 }
673 
674 bool BtBookmarksModel::insertRows(int row, int count, const QModelIndex &parent)
675 {
676  Q_D(BtBookmarksModel);
677  if (auto * const f = dynamic_cast<BookmarkFolder *>(d->item(parent))) {
678  BT_ASSERT(row <= f->children().size());
679  beginInsertRows(parent, row, row + count - 1);
680  for(int i = 0; i < count; ++i)
681  f->insertChild(row, new BookmarkItemBase);
682  endInsertRows();
683  return true;
684  }
685  return false;
686 }
687 
688 bool BtBookmarksModel::save(QString fileName, const QModelIndex & rootItem) {
689  Q_D(BtBookmarksModel);
690  BT_ASSERT(dynamic_cast<BookmarkFolder *>(d->item(rootItem)));
691  auto const serializedTree =
692  d->serializeTreeFromRootItem(
693  static_cast<BookmarkFolder *>(d->item(rootItem)));
694  if (fileName.isEmpty())
696 
697  util::tool::savePlainFile(fileName, serializedTree);
698 
699  if(d->m_saveTimer.isActive())
700  d->m_saveTimer.stop();
701 
702  return true;
703 }
704 
705 bool BtBookmarksModel::load(QString fileName, const QModelIndex & rootItem) {
706  Q_D(BtBookmarksModel);
707  BT_ASSERT(dynamic_cast<BookmarkFolder *>(d->item(rootItem)));
708  auto * const i = static_cast<BookmarkFolder *>(d->item(rootItem));
709  QList<BookmarkItemBase *> items = d->loadTree(fileName);
710 
711  if(!rootItem.isValid() && fileName.isEmpty()) {
712  BT_ASSERT(!d->m_defaultModel && "Only one default model allowed!");
713  BT_CONNECT(&d->m_saveTimer, &QTimer::timeout,
715  d->m_defaultModel = this;
716  }
717 
718  if(items.size() == 0)
719  return false;
720 
721  auto const numChildren = i->children().size();
722  beginInsertRows(rootItem, numChildren, numChildren + items.size() - 1);
723  i->insertChildren(numChildren, items);
724  endInsertRows();
725  return true;
726 }
727 
728 bool BtBookmarksModel::isFolder(const QModelIndex &index) const
729 {
730  Q_D(const BtBookmarksModel);
731  return d->itemAs<BookmarkFolder const>(index);
732 }
733 
734 bool BtBookmarksModel::isBookmark(const QModelIndex &index) const
735 {
736  Q_D(const BtBookmarksModel);
737  return d->itemAs<BookmarkItem const>(index);
738 }
739 
740 void BtBookmarksModel::copyItems(int row, const QModelIndex & parent, const QModelIndexList & toCopy)
741 {
742  Q_D(BtBookmarksModel);
743  BT_ASSERT(dynamic_cast<BookmarkFolder *>(d->item(parent)));
744  auto * const targetFolder = static_cast<BookmarkFolder *>(d->item(parent));
745 
746  bool bookmarksOnly = true;
747  bool targetIncluded = false;
748  bool moreThanOneFolder = false;
749 
751 
752  for (auto const & index : toCopy) {
753  if (BookmarkFolder const * const folder =
754  d->itemAs<BookmarkFolder const>(index))
755  {
756  bookmarksOnly = false;
757  if (toCopy.count() > 1) { // only one item allowed if a folder is selected
758  moreThanOneFolder = true;
759  break;
760  }
761  if (folder->hasDescendant(d->item(parent))) { // dropping to self or descendand not allowed
762  targetIncluded = true;
763  break;
764  }
765  }
766  else {
767  newList.append(new BookmarkItem(*(d->itemAs<BookmarkItem>(index))));
768  }
769  }
770 
771  if (!bookmarksOnly && toCopy.count() == 1)
772  newList.append(d->itemAs<BookmarkFolder const>(toCopy[0])->deepCopy());
773  if (!bookmarksOnly && toCopy.count() > 1) {
774  // wrong amount of items
775  moreThanOneFolder = true;
776  }
777 
778  if (moreThanOneFolder || targetIncluded) {
779  return;
780  }
781 
782 
783  beginInsertRows(parent, row, row + newList.size() - 1);
784 
785  targetFolder->insertChildren(row, newList);
786 
787  endInsertRows();
788 
789  d->needSave();
790 }
791 
792 CSwordModuleInfo * BtBookmarksModel::module(const QModelIndex & index) const
793 {
794  Q_D(const BtBookmarksModel);
795 
796  if (BookmarkItem const * const i = d->itemAs<BookmarkItem const>(index))
797  return i->module();
798  return nullptr;
799 }
800 
801 QString BtBookmarksModel::key(const QModelIndex & index) const
802 {
803  Q_D(const BtBookmarksModel);
804 
805  if (BookmarkItem const * const i = d->itemAs<BookmarkItem const>(index))
806  return i->key();
807  return QString();
808 }
809 
810 QString BtBookmarksModel::description(const QModelIndex &index) const
811 {
812  Q_D(const BtBookmarksModel);
813 
814  if (BookmarkItem const * const i = d->itemAs<BookmarkItem const>(index))
815  return i->description();
816  return QString();
817 }
818 
819 void BtBookmarksModel::setDescription(const QModelIndex &index, const QString &description)
820 {
821  Q_D(BtBookmarksModel);
822 
823  if (BookmarkItem * const i = d->itemAs<BookmarkItem>(index)) {
824  i->setDescription(description);
825  d->needSave();
826  }
827 }
828 
829 QModelIndex BtBookmarksModel::addBookmark(int const row,
830  QModelIndex const & parent,
831  CSwordModuleInfo const & module,
832  QString const & key,
833  QString const & description,
834  QString const & title)
835 {
836  Q_D(BtBookmarksModel);
837 
838  if (BookmarkFolder * const i = d->itemAs<BookmarkFolder>(parent)) {
839  int r = row < 0 ? row + rowCount(parent) + 1 : row;
840 
841  beginInsertRows(parent, r, r);
842 
843  BookmarkItem * c = new BookmarkItem(module, key, description, title);
844  i->insertChild(r, c);
845 
846  endInsertRows();
847 
848  d->needSave();
849 
850  return createIndex(c->index(), 0, c);
851  }
852  return QModelIndex();
853 }
854 
855 QModelIndex BtBookmarksModel::addFolder(int row, const QModelIndex &parent, const QString &name)
856 {
857  Q_D(BtBookmarksModel);
858 
859  if (BookmarkFolder * const i = d->itemAs<BookmarkFolder>(parent)) {
860  beginInsertRows(parent, row, row);
861 
862  BookmarkFolder * c = new BookmarkFolder(name.isEmpty() ? QObject::tr("New folder") : name);
863  i->insertChild(row, c);
864 
865  endInsertRows();
866 
867  d->needSave();
868 
869  return createIndex(c->index(), 0, c);
870  }
871  return QModelIndex();
872 }
873 
874 bool BtBookmarksModel::hasDescendant(const QModelIndex &baseIndex, const QModelIndex &testIndex) const
875 {
876  Q_D(const BtBookmarksModel);
877 
878  if (BookmarkFolder const * const f =
879  d->itemAs<BookmarkFolder const>(baseIndex))
880  return f->hasDescendant(d->item(testIndex));
881  return false;
882 }
883 
884 bool BtBookmarksModelSortAscending(BookmarkItemBase * i1, BookmarkItemBase * i2)
885 {
886  return i1->text().localeAwareCompare(i2->text()) < 0;
887 }
888 
889 bool BtBookmarksModelSortDescending(BookmarkItemBase * i1, BookmarkItemBase * i2)
890 {
891  return i1->text().localeAwareCompare(i2->text()) > 0;
892 }
893 
894 void BtBookmarksModel::sortItems(QModelIndex const & parent,
895  Qt::SortOrder const order)
896 {
897  Q_D(BtBookmarksModel);
898 
899  if(BookmarkFolder * const f = d->itemAs<BookmarkFolder>(parent)) {
900  QList<BookmarkFolder *> parents;
901  if(f == d->m_rootItem) {
903  items.append(f);
904  for(int i = 0; i < items.size(); ++i) {
905  if (auto * const ff = dynamic_cast<BookmarkFolder *>(items[i])){
906  items.append(ff->children());
907  parents.append(ff);
908  }
909  }
910  }
911  else
912  parents.append(f);
913 
914  for (auto * const f : parents) {
915  Q_EMIT layoutAboutToBeChanged();
916 
917  QModelIndexList indexes;
918  for(int i = 0; i < f->children().size(); ++i)
919  indexes.append(createIndex(i, 0, f->children()[i]));
920 
921  std::sort(f->children().begin(),
922  f->children().end(),
923  order == Qt::AscendingOrder
926 
927  for(int i = 0; i < f->children().size(); ++i) {
928  BookmarkItemBase * iii = f->children()[i];
929  for(int ii = 0; ii < indexes.size(); ++ii)
930  if(iii == indexes[ii].internalPointer())
931  changePersistentIndex(createIndex(ii, 0, iii), createIndex(i, 0, iii));
932  }
933  Q_EMIT layoutChanged();
934 
935  d->needSave();
936  }
937  }
938 }
939 
941  static auto const staticRoleNames = {
942  std::make_pair(static_cast<int>(Qt::DisplayRole),
943  QByteArrayLiteral("display")),
944  std::make_pair(static_cast<int>(Qt::DecorationRole),
945  QByteArrayLiteral("icon")),
946  std::make_pair(static_cast<int>(Qt::EditRole),
947  QByteArrayLiteral("edit")),
948  std::make_pair(static_cast<int>(TypeRole),
949  QByteArrayLiteral("itemtype"))
950  };
951  return staticRoleNames;
952 }
#define BT_ASSERT(...)
Definition: btassert.h:17
bool BtBookmarksModelSortAscending(BookmarkItemBase *i1, BookmarkItemBase *i2)
#define CURRENT_SYNTAX_VERSION
bool BtBookmarksModelSortDescending(BookmarkItemBase *i1, BookmarkItemBase *i2)
BtConfig & btConfig()
This is a shortchand for BtConfig::getInstance().
Definition: btconfig.h:305
#define BT_CONNECT(...)
Definition: btconnect.h:20
QString const & key() const noexcept
Definition: BookmarkItem.h:37
BookmarkItem(QString module, QString key, QString description)
Definition: BookmarkItem.h:27
QString m_moduleName
Definition: BookmarkItem.h:44
QString m_key
Definition: BookmarkItem.h:45
QString const & description() const noexcept
Definition: BookmarkItem.h:40
QString m_description
Definition: BookmarkItem.h:46
QString const & module() const noexcept
Definition: BookmarkItem.h:34
BookmarkItemBase * item(const QModelIndex &index) const
BtBookmarksModelPrivate(BtBookmarksModel *parent)
static QString defaultBookmarksFile()
QString loadXmlFromFile(QString fileName=QString())
void saveItem(BookmarkItemBase const *item, QDomElement &parentElement)
QString serializeTreeFromRootItem(BookmarkFolder *rootItem)
BookmarkItemBase * handleXmlElement(QDomElement &element, BookmarkFolder *parent)
QList< BookmarkItemBase * > loadTree(QString fileName=QString())
static BtBookmarksModel * m_defaultModel
T * itemAs(QModelIndex const &index) const
int rowCount(const QModelIndex &parent=QModelIndex()) const override
void copyItems(int row, const QModelIndex &parent, const QModelIndexList &toCopy)
Copies item to target position.
bool isBookmark(const QModelIndex &index) const
bool hasDescendant(const QModelIndex &baseIndex, const QModelIndex &testIndex) const
bool isFolder(const QModelIndex &index) const
void sortItems(QModelIndex const &parent=QModelIndex(), Qt::SortOrder const order=Qt::AscendingOrder)
QModelIndex index(int row, int column, const QModelIndex &parent=QModelIndex()) const override
QString description(const QModelIndex &index) const
QVariant headerData(int section, Qt::Orientation orientation, int role=Qt::DisplayRole) const override
Qt::ItemFlags flags(const QModelIndex &index) const override
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const override
QHash< int, QByteArray > roleNames() const override
~BtBookmarksModel() override
bool removeRows(int row, int count, const QModelIndex &parent=QModelIndex()) override
bool setData(const QModelIndex &index, const QVariant &value, int role=Qt::EditRole) override
void setDescription(const QModelIndex &index, const QString &description)
set descritpion for index.
QModelIndex parent(const QModelIndex &index) const override
bool load(QString fileName=QString(), const QModelIndex &rootItem=QModelIndex())
Import bookmarks from file.
BtBookmarksModel(QObject *parent=nullptr)
bool hasChildren(const QModelIndex &parent=QModelIndex()) const override
CSwordModuleInfo * module(const QModelIndex &index) const
bool insertRows(int row, int count, const QModelIndex &parent=QModelIndex()) override
QModelIndex addFolder(int row, const QModelIndex &parent, const QString &name=QString())
add new folder.
bool save(QString fileName=QString(), const QModelIndex &rootItem=QModelIndex())
Save bookmarks or specified branch to file.
BtBookmarksModelPrivate *const d_ptr
QModelIndex addBookmark(int const row, QModelIndex const &parent, CSwordModuleInfo const &module, QString const &key, QString const &description=QString(), QString const &title=QString())
add new item with given parameters
int columnCount(const QModelIndex &parent=QModelIndex()) const override
QString key(const QModelIndex &index) const
FilterOptions getFilterOptions() const
Definition: btconfig.h:160
CSwordModuleInfo * findModuleByName(const QString &name) const
Searches for a module with the given name.
static CSwordBackend & instance() noexcept
Definition: cswordbackend.h:98
void setFilterOptions(const FilterOptions &options)
ModuleType type() const
QString const & name() const
sword::SWModule & swordModule() const
CSwordKey implementation for Sword's VerseKey.
void insertChild(int index, BookmarkItemBase *child)
bool hasDescendant(BookmarkItemBase const *item) const
QList< BookmarkItemBase * > const & children() const noexcept
void insertChildren(int index, QList< BookmarkItemBase * > children)
Qt::ItemFlags flags() const noexcept final override
QIcon const & icon() const noexcept final override
QList< BookmarkItemBase * > & children() noexcept
Qt::ItemFlags flags() const noexcept final override
BookmarkItem(const CSwordModuleInfo &module, const QString &key, const QString &description, const QString &title)
QIcon const & icon() const noexcept final override
#define T(f)
QStringList r(content.left(bodyIndex))
QString toHeader(QString const &key, QString const &moduleName)
const QDir & getUserBaseDir()
Definition: directory.cpp:334
bool savePlainFile(const QString &filename, void(&writer)(QTextStream &, void *), void *userPtr)
Definition: tool.cpp:35
int footnotes
Definition: btglobal.h:26
int scriptureReferences
Definition: btglobal.h:36