diff --git a/src/kjotsbookmarks.cpp b/src/kjotsbookmarks.cpp index 4ec0f15..48c3628 100644 --- a/src/kjotsbookmarks.cpp +++ b/src/kjotsbookmarks.cpp @@ -1,72 +1,76 @@ // // kjotsbookmarks // // Copyright (C) 1997 Christoph Neerfeld // Copyright (C) 2002, 2003 Aaron J. Seigo // Copyright (C) 2003 Stanislav Kljuhhin // Copyright (C) 2005-2006 Jaison Lee // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // #include "kjotsbookmarks.h" #include #include "kjotsmodel.h" #include "kjotstreeview.h" KJotsBookmarks::KJotsBookmarks(KJotsTreeView *treeView) : m_treeView(treeView) { } void KJotsBookmarks::openBookmark(const KBookmark &bookmark, Qt::MouseButtons, Qt::KeyboardModifiers) { -#if 0 - QModelIndexList rows = m_treeView->model()->match(QModelIndex(), KJotsModel::EntityUrlRole, bookmark.url().url()); - - if (rows.isEmpty()) { + if (bookmark.url().scheme() != QStringLiteral("kjots")) { return; } - - // Arbitrarily chooses the first one if multiple are returned. - return m_treeView->selectionModel()->select(rows.at(0), QItemSelectionModel::ClearAndSelect); -#endif + Q_EMIT openLink(bookmark.url()); } -QUrl KJotsBookmarks::currentUrl() const +QString KJotsBookmarks::currentIcon() const { -#if 0 //QT5 - QModelIndexList rows = m_treeView->selectionModel()->selectedRows(); - + const QModelIndexList rows = m_treeView->selectionModel()->selectedRows(); if (rows.size() != 1) { return QString(); } -#if 0 - return rows.at(0).data(EntityTreeModel::EntityUrlRole).toString(); -#else + const QModelIndex idx = rows.first(); + auto collection = idx.data(EntityTreeModel::CollectionRole).value(); + if (collection.isValid()) { + return QStringLiteral("x-office-address-book"); + } + auto item = idx.data(EntityTreeModel::ItemRole).value(); + if (item.isValid()) { + return QStringLiteral("x-office-document"); + } return QString(); -#endif -#else - return QUrl(); -#endif +} + +QUrl KJotsBookmarks::currentUrl() const +{ + const QModelIndexList rows = m_treeView->selectionModel()->selectedRows(); + if (rows.size() != 1) { + return QUrl(); + } else { + return QUrl(rows.first().data(KJotsModel::UrlRole).value()); + } } QString KJotsBookmarks::currentTitle() const { return m_treeView->captionForSelection(QLatin1String(": ")); } diff --git a/src/kjotsbookmarks.h b/src/kjotsbookmarks.h index 9f436ea..bad42ed 100644 --- a/src/kjotsbookmarks.h +++ b/src/kjotsbookmarks.h @@ -1,47 +1,51 @@ // // kjotsbookmarks // // Copyright (C) 1997 Christoph Neerfeld // Copyright (C) 2002, 2003 Aaron J. Seigo // Copyright (C) 2003 Stanislav Kljuhhin // Copyright (C) 2005-2006 Jaison Lee // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // #ifndef KJOTSBOOKMARKS #define KJOTSBOOKMARKS #include class KJotsTreeView; class KJotsBookmarks : public QObject, public KBookmarkOwner { Q_OBJECT public: explicit KJotsBookmarks(KJotsTreeView *treeView); QUrl currentUrl() const override; + QString currentIcon() const override; QString currentTitle() const override; void openBookmark(const KBookmark &bm, Qt::MouseButtons mb, Qt::KeyboardModifiers km) override; +Q_SIGNALS: + void openLink(const QUrl &url); + private: KJotsTreeView *m_treeView; }; #endif /* ex: set tabstop=4 softtabstop=4 shiftwidth=4 expandtab: */ diff --git a/src/kjotsbrowser.cpp b/src/kjotsbrowser.cpp index 902a5d9..2a1fd89 100644 --- a/src/kjotsbrowser.cpp +++ b/src/kjotsbrowser.cpp @@ -1,83 +1,50 @@ // // kjots // // Copyright (C) 1997 Christoph Neerfeld // Copyright (C) 2002, 2003 Aaron J. Seigo // Copyright (C) 2003 Stanislav Kljuhhin // Copyright (C) 2005-2006 Jaison Lee // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // //Own Header #include "kjotsbrowser.h" #include -#include - #include #include KJotsBrowser::KJotsBrowser(QItemSelectionModel *selectionModel, QWidget *parent) : QTextBrowser(parent), m_itemSelectionModel(selectionModel) { setWordWrapMode(QTextOption::WordWrap); } void KJotsBrowser::delayedInitialization() { - connect(this, &KJotsBrowser::anchorClicked, this, &KJotsBrowser::linkClicked); -} - -/*! - \brief Handle link clicks. -*/ -void KJotsBrowser::linkClicked(const QUrl &link) -{ - //Stop QTextBrowser from being stupid by giving it an invalid url. - QUrl url; - setSource(url); - - QString anchor = link.fragment(); - - if (link.toString().startsWith(QLatin1String("#")) && (anchor.startsWith(QLatin1String("book_")) - || anchor.startsWith(QLatin1String("page_")))) { - scrollToAnchor(anchor); - return; - } - - if (link.scheme() == QLatin1String("kjots")) { - const quint64 targetId = link.path().mid(1).toULongLong(); - if (link.host().endsWith(QLatin1String("book"))) { - const QModelIndex colIndex = Akonadi::EntityTreeModel::modelIndexForCollection(m_itemSelectionModel->model(), Akonadi::Collection(targetId)); - if (!colIndex.isValid()) { - return; - } - m_itemSelectionModel->select(colIndex, QItemSelectionModel::ClearAndSelect); - } else { - Q_ASSERT(link.host().endsWith(QLatin1String("page"))); - const QModelIndexList itemIndexes = Akonadi::EntityTreeModel::modelIndexesForItem(m_itemSelectionModel->model(), Akonadi::Item(targetId)); - if (itemIndexes.size() != 1) { - return; - } - m_itemSelectionModel->select(itemIndexes.first(), QItemSelectionModel::ClearAndSelect); + connect(this, &KJotsBrowser::anchorClicked, this, [this](const QUrl &url){ + if (!url.toString().startsWith(QLatin1Char('#'))) { + // QTextBrowser tries to automatically handle the url. We only want it for anchor navigation + // (i.e. "#page12" links). This can be overriden by setting the source to an invalid QUrl + setSource(QUrl()); + Q_EMIT linkClicked(url); } - } else { - new KRun(link, this); - } + }); } /* ex: set tabstop=4 softtabstop=4 shiftwidth=4 expandtab: */ diff --git a/src/kjotsbrowser.h b/src/kjotsbrowser.h index 607767a..75324e8 100644 --- a/src/kjotsbrowser.h +++ b/src/kjotsbrowser.h @@ -1,46 +1,46 @@ // // kjots // // Copyright (C) 1997 Christoph Neerfeld // Copyright (C) 2002, 2003 Aaron J. Seigo // Copyright (C) 2003 Stanislav Kljuhhin // Copyright (C) 2005-2006 Jaison Lee // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // #ifndef KJOTSBROWSER_H #define KJOTSBROWSER_H #include class QItemSelectionModel; class KJotsBrowser : public QTextBrowser { Q_OBJECT public: explicit KJotsBrowser(QItemSelectionModel *selectionModel, QWidget *); void delayedInitialization(); -protected Q_SLOTS: +Q_SIGNALS: void linkClicked(const QUrl &); private: QItemSelectionModel *m_itemSelectionModel; }; #endif /* ex: set tabstop=4 softtabstop=4 shiftwidth=4 expandtab: */ diff --git a/src/kjotslinkdialog.cpp b/src/kjotslinkdialog.cpp index 78f0b00..27f2678 100644 --- a/src/kjotslinkdialog.cpp +++ b/src/kjotslinkdialog.cpp @@ -1,191 +1,167 @@ // // kjots // // Copyright (C) 2008 Stephen Kelly // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // #include "kjotslinkdialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "KJotsSettings.h" #include "kjotsbookshelfentryvalidator.h" +#include "kjotsmodel.h" #include #include KJotsLinkDialog::KJotsLinkDialog(QAbstractItemModel *kjotsModel, QWidget *parent) : QDialog(parent), m_kjotsModel(kjotsModel) { setWindowTitle(i18n("Manage Link")); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); QVBoxLayout *mainLayout = new QVBoxLayout; setLayout(mainLayout); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttonBox, &QDialogButtonBox::accepted, this, &KJotsLinkDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &KJotsLinkDialog::reject); okButton->setDefault(true); setModal(true); KDescendantsProxyModel *proxyModel = new KDescendantsProxyModel(this); proxyModel->setSourceModel(kjotsModel); proxyModel->setAncestorSeparator(QLatin1String(" / ")); m_descendantsProxyModel = proxyModel; QWidget *entries = new QWidget(this); QGridLayout *layout = new QGridLayout(entries); textLabel = new QLabel(i18n("Link Text:"), this); textLineEdit = new QLineEdit(this); textLineEdit->setClearButtonEnabled(true); linkUrlLabel = new QLabel(i18n("Link URL:"), this); linkUrlLineEdit = new QLineEdit(this); hrefCombo = new QComboBox(this); linkUrlLineEdit->setClearButtonEnabled(true); tree = new QTreeView(); tree->setModel(proxyModel); tree->expandAll(); tree->setColumnHidden(1, true); hrefCombo->setModel(proxyModel); hrefCombo->setView(tree); hrefCombo->setEditable(true); QCompleter *completer = new QCompleter(proxyModel, this); completer->setCaseSensitivity(Qt::CaseInsensitive); hrefCombo->setCompleter(completer); KJotsBookshelfEntryValidator *validator = new KJotsBookshelfEntryValidator(proxyModel, this); hrefCombo->setValidator(validator); QGridLayout *linkLayout = new QGridLayout(); linkUrlLineEditRadioButton = new QRadioButton(entries); hrefComboRadioButton = new QRadioButton(entries); connect(linkUrlLineEditRadioButton, &QRadioButton::toggled, linkUrlLineEdit, &QLineEdit::setEnabled); connect(hrefComboRadioButton, &QRadioButton::toggled, hrefCombo, &QComboBox::setEnabled); hrefCombo->setEnabled(false); linkUrlLineEditRadioButton->setChecked(true); linkLayout->addWidget(linkUrlLineEditRadioButton, 0, 0); linkLayout->addWidget(linkUrlLineEdit, 0, 1); linkLayout->addWidget(hrefComboRadioButton, 1, 0); linkLayout->addWidget(hrefCombo, 1, 1); layout->addWidget(textLabel, 0, 0); layout->addWidget(textLineEdit, 0, 1); layout->addWidget(linkUrlLabel, 1, 0); layout->addLayout(linkLayout, 1, 1); mainLayout->addWidget(entries); mainLayout->addWidget(buttonBox); textLineEdit->setFocus(); connect(hrefCombo, SIGNAL(editTextChanged(QString)), this, SLOT(trySetEntry(QString))); } void KJotsLinkDialog::setLinkText(const QString &linkText) { textLineEdit->setText(linkText); if (!linkText.trimmed().isEmpty()) { linkUrlLineEdit->setFocus(); } } void KJotsLinkDialog::setLinkUrl(const QString &linkUrl) { - Akonadi::Item item = Akonadi::Item::fromUrl(QUrl::fromUserInput(linkUrl)); - Akonadi::Collection collection = Akonadi::Collection::fromUrl(QUrl::fromUserInput(linkUrl)); - - if (!item.isValid() && !collection.isValid()) { + const QUrl url(linkUrl); + if (url.scheme() != QStringLiteral("kjots")) { linkUrlLineEdit->setText(linkUrl); linkUrlLineEditRadioButton->setChecked(true); return; } - - QModelIndex idx; - - if (collection.isValid()) { - idx = Akonadi::EntityTreeModel::modelIndexForCollection(m_descendantsProxyModel, collection); - } else if (item.isValid()) { - const QModelIndexList list = Akonadi::EntityTreeModel::modelIndexesForItem(m_descendantsProxyModel, item); - if (list.isEmpty()) { - return; - } - - idx = list.first(); + QModelIndex idx = KJotsModel::modelIndexForUrl(m_descendantsProxyModel, url); + if (idx.isValid()) { + hrefComboRadioButton->setChecked(true); + hrefCombo->view()->setCurrentIndex(idx); + hrefCombo->setCurrentIndex(idx.row()); } - - if (!idx.isValid()) { - return; - } - - hrefComboRadioButton->setChecked(true); - - hrefCombo->view()->setCurrentIndex(idx); - hrefCombo->setCurrentIndex(idx.row()); } QString KJotsLinkDialog::linkText() const { return textLineEdit->text().trimmed(); } void KJotsLinkDialog::trySetEntry(const QString &text) { QString t(text); int pos = hrefCombo->lineEdit()->cursorPosition(); if (hrefCombo->validator()->validate(t, pos) == KJotsBookshelfEntryValidator::Acceptable) { int row = hrefCombo->findText(t, Qt::MatchFixedString); QModelIndex index = hrefCombo->model()->index(row, 0); hrefCombo->view()->setCurrentIndex(index); hrefCombo->setCurrentIndex(row); } } QString KJotsLinkDialog::linkUrl() const { if (hrefComboRadioButton->isChecked()) { - const QModelIndex index = hrefCombo->view()->currentIndex(); - const Akonadi::Collection collection = index.data(Akonadi::EntityTreeModel::CollectionRole).value(); - if (collection.isValid()) { - return QLatin1String("kjots://org.kjots.book/") + QString::number(collection.id()); - } - const Akonadi::Item item = index.data(Akonadi::EntityTreeModel::ItemRole).value(); - Q_ASSERT(item.isValid()); - return QLatin1String("kjots://org.kjots.page/") + QString::number(item.id()); + return hrefCombo->view()->currentIndex().data(KJotsModel::UrlRole).value(); } else { return linkUrlLineEdit->text(); } } diff --git a/src/kjotsmodel.cpp b/src/kjotsmodel.cpp index c2ab153..22b4d13 100644 --- a/src/kjotsmodel.cpp +++ b/src/kjotsmodel.cpp @@ -1,286 +1,323 @@ /* This file is part of KJots. Copyright (c) 2009 Stephen Kelly This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kjotsmodel.h" #include #include #include #include #include #include #include "noteshared/notelockattribute.h" #include #include #include #include #include Q_DECLARE_METATYPE(QTextDocument *) KJotsEntity::KJotsEntity(const QModelIndex &index, QObject *parent) : QObject(parent) { m_index = QPersistentModelIndex(index); } void KJotsEntity::setIndex(const QModelIndex &index) { m_index = QPersistentModelIndex(index); } QString KJotsEntity::title() const { return m_index.data().toString(); } QString KJotsEntity::content() const { QTextDocument *document = m_index.data(KJotsModel::DocumentRole).value(); if (!document) { return QString(); } Grantlee::TextHTMLBuilder builder; Grantlee::MarkupDirector director(&builder); director.processDocument(document); QString result = builder.getResult(); return result; } QString KJotsEntity::plainContent() const { QTextDocument *document = m_index.data(KJotsModel::DocumentRole).value(); if (!document) { return QString(); } Grantlee::PlainTextMarkupBuilder builder; Grantlee::MarkupDirector director(&builder); director.processDocument(document); QString result = builder.getResult(); return result; } +QString KJotsEntity::url() const +{ + return m_index.data(KJotsModel::UrlRole).value(); +} + qint64 KJotsEntity::entityId() const { Item item = m_index.data(EntityTreeModel::ItemRole).value(); if (!item.isValid()) { Collection col = m_index.data(EntityTreeModel::CollectionRole).value(); if (!col.isValid()) { return -1; } return col.id(); } return item.id(); } bool KJotsEntity::isBook() const { Collection col = m_index.data(EntityTreeModel::CollectionRole).value(); if (col.isValid()) { return col.contentMimeTypes().contains(Akonadi::NoteUtils::noteMimeType()); } return false; } bool KJotsEntity::isPage() const { Item item = m_index.data(EntityTreeModel::ItemRole).value(); if (item.isValid()) { return item.hasPayload(); } return false; } QVariantList KJotsEntity::entities() const { const QAbstractItemModel *model = m_index.model(); QVariantList list; int row = 0; const int column = 0; QModelIndex childIndex = model->index(row++, column, m_index); while (childIndex.isValid()) { auto obj = new KJotsEntity(childIndex); list << QVariant::fromValue(obj); childIndex = model->index(row++, column, m_index); } return list; } QVariantList KJotsEntity::breadcrumbs() const { QVariantList list; QModelIndex parent = m_index.parent(); while (parent.isValid()) { QObject *obj = new KJotsEntity(parent); list << QVariant::fromValue(obj); parent = parent.parent(); } return list; } KJotsModel::KJotsModel(ChangeRecorder *monitor, QObject *parent) : EntityTreeModel(monitor, parent) { } KJotsModel::~KJotsModel() { qDeleteAll(m_documents); } bool KJotsModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (role == Qt::EditRole) { Item item = index.data(ItemRole).value(); if (!item.isValid()) { Collection col = index.data(CollectionRole).value(); col.setName(value.toString()); if (col.hasAttribute()) { EntityDisplayAttribute *eda = col.attribute(); eda->setDisplayName(value.toString()); } return EntityTreeModel::setData(index, QVariant::fromValue(col), CollectionRole); } KMime::Message::Ptr m = item.payload(); m->subject(true)->fromUnicodeString(value.toString(), "utf-8"); m->assemble(); item.setPayload(m); if (item.hasAttribute()) { EntityDisplayAttribute *displayAttribute = item.attribute(); displayAttribute->setDisplayName(value.toString()); } return EntityTreeModel::setData(index, QVariant::fromValue(item), ItemRole); } if (role == KJotsModel::DocumentRole) { Item item = EntityTreeModel::data(index, ItemRole).value(); if (!item.hasPayload()) { return false; } KMime::Message::Ptr note = item.payload(); QTextDocument *document = value.value(); bool isRichText = KPIMTextEdit::TextUtils::containsFormatting(document); note->contentType()->setMimeType(isRichText ? "text/html" : "text/plain"); note->contentType()->setCharset("utf-8"); note->contentTransferEncoding(true)->setEncoding(KMime::Headers::CEquPr); note->mainBodyPart()->fromUnicodeString(isRichText ? document->toHtml() : document->toPlainText()); note->assemble(); item.setPayload(note); return EntityTreeModel::setData(index, QVariant::fromValue(item), ItemRole); } if (role == KJotsModel::DocumentCursorPositionRole) { Item item = index.data(ItemRole).value(); m_cursorPositions.insert(item.id(), value.toInt()); return true; } return EntityTreeModel::setData(index, value, role); } QVariant KJotsModel::data(const QModelIndex &index, int role) const { if (GrantleeObjectRole == role) { QObject *obj = new KJotsEntity(index); return QVariant::fromValue(obj); } if (role == KJotsModel::DocumentRole) { const Item item = index.data(ItemRole).value(); Item::Id itemId = item.id(); if (m_documents.contains(itemId)) { return QVariant::fromValue(m_documents.value(itemId)); } if (!item.hasPayload()) { return QVariant(); } KMime::Message::Ptr note = item.payload(); QTextDocument *document = new QTextDocument; const QString decodedText = note->mainBodyPart()->decodedText(); if (note->contentType()->isHTMLText() || decodedText.startsWith(QLatin1String(""))) { document->setHtml(decodedText); } else { document->setPlainText(decodedText); } m_documents.insert(itemId, document); return QVariant::fromValue(document); } if (role == KJotsModel::DocumentCursorPositionRole) { const Item item = index.data(ItemRole).value(); if (!item.isValid()) { return 0; } if (m_cursorPositions.contains(item.id())) { return m_cursorPositions.value(item.id()); } return 0; } if (role == Qt::DecorationRole) { const Item item = index.data(ItemRole).value(); if (item.isValid() && item.hasAttribute()) { return QIcon::fromTheme(QLatin1String("emblem-locked")); } else { const Collection col = index.data(CollectionRole).value(); if (col.isValid() && col.hasAttribute()) { return QIcon::fromTheme(QLatin1String("emblem-locked")); } } } + if (role == KJotsModel::UrlRole) { + const auto item = index.data(ItemRole).value(); + if (item.isValid()) { + return QStringLiteral("kjots://org.kjots.page/%1").arg(item.id()); + } + const auto col = index.data(CollectionRole).value(); + if (col.isValid()) { + return QStringLiteral("kjots://org.kjots.book/%1").arg(col.id()); + } + return QString(); + } + return EntityTreeModel::data(index, role); } QVariant KJotsModel::entityData(const Akonadi::Item &item, int column, int role) const { if ((role == Qt::EditRole || role == Qt::DisplayRole) && item.hasPayload()) { KMime::Message::Ptr page = item.payload(); return page->subject()->asUnicodeString(); } return EntityTreeModel::entityData(item, column, role); } +QModelIndex KJotsModel::modelIndexForUrl(const QAbstractItemModel *model, const QUrl &url) +{ + if (url.scheme() != QStringLiteral("kjots")) { + return QModelIndex(); + } + const qint64 targetId = url.path().mid(1).toLongLong(); + + if (url.host() == QStringLiteral("org.kjots.book")) { + return Akonadi::EntityTreeModel::modelIndexForCollection(model, Collection(targetId)); + } else if (url.host() == QStringLiteral("org.kjots.page")) { + const QModelIndexList itemIndexes = Akonadi::EntityTreeModel::modelIndexesForItem(model, Item(targetId)); + if (itemIndexes.isEmpty()) { + return QModelIndex(); + } + return itemIndexes.first(); + } else { + qWarning() << "Could not recognize URL" << url; + return QModelIndex(); + } +} diff --git a/src/kjotsmodel.h b/src/kjotsmodel.h index 650429c..584b883 100644 --- a/src/kjotsmodel.h +++ b/src/kjotsmodel.h @@ -1,103 +1,108 @@ /* This file is part of KJots. Copyright (c) 2009 Stephen Kelly This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KJOTSMODEL_H #define KJOTSMODEL_H #include class QTextDocument; namespace Akonadi { class ChangeRecorder; } using namespace Akonadi; /** * A wrapper QObject making some book and page properties available to Grantlee. */ class KJotsEntity : public QObject { Q_OBJECT Q_PROPERTY(QString title READ title) Q_PROPERTY(QString content READ content) Q_PROPERTY(QString plainContent READ plainContent) + Q_PROPERTY(QString url READ url) Q_PROPERTY(qint64 entityId READ entityId) Q_PROPERTY(bool isBook READ isBook) Q_PROPERTY(bool isPage READ isPage) Q_PROPERTY(QVariantList entities READ entities) Q_PROPERTY(QVariantList breadcrumbs READ breadcrumbs) public: explicit KJotsEntity(const QModelIndex &index, QObject *parent = nullptr); void setIndex(const QModelIndex &index); bool isBook() const; bool isPage() const; QString title() const; QString content() const; QString plainContent() const; + QString url() const; + qint64 entityId() const; QVariantList entities() const; QVariantList breadcrumbs() const; private: QPersistentModelIndex m_index; }; class KJotsModel : public EntityTreeModel { Q_OBJECT public: explicit KJotsModel(ChangeRecorder *monitor, QObject *parent = nullptr); ~KJotsModel() override; enum KJotsRoles { GrantleeObjectRole = EntityTreeModel::UserRole, DocumentRole, - DocumentCursorPositionRole + DocumentCursorPositionRole, + UrlRole }; // We don't reimplement the Collection overload. using EntityTreeModel::entityData; QVariant entityData(const Akonadi::Item &item, int column, int role = Qt::DisplayRole) const override; QVariant data(const QModelIndex &index, int role) const override; bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; + static QModelIndex modelIndexForUrl(const QAbstractItemModel *model, const QUrl &url); private: QHash m_colors; mutable QHash m_documents; QHash m_cursorPositions; }; #endif diff --git a/src/kjotswidget.cpp b/src/kjotswidget.cpp index 9e20d97..ebc6f2e 100644 --- a/src/kjotswidget.cpp +++ b/src/kjotswidget.cpp @@ -1,1748 +1,1759 @@ /* This file is part of KJots. Copyright (C) 1997 Christoph Neerfeld Copyright (C) 2002, 2003 Aaron J. Seigo Copyright (C) 2003 Stanislav Kljuhhin Copyright (C) 2005-2006 Jaison Lee Copyright (C) 2007-2009 Stephen Kelly This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kjotswidget.h" // Qt #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Akonadi #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "akonadi_next/notecreatorandselector.h" // Grantlee #include #include #include // KDE #include #include #include #include #include #include #include #include #include #include #include #include #include +#include // KMime #include // KJots #include "kjotsbookmarks.h" #include "kjotssortproxymodel.h" #include "kjotsmodel.h" #include "kjotsedit.h" #include "kjotstreeview.h" #include "kjotsconfigdlg.h" #include "kjotsreplacenextdialog.h" #include "KJotsSettings.h" #include "kjotslockjob.h" #include "kjotsbrowser.h" #include "noteshared/notelockattribute.h" #include "localresourcecreator.h" #include Q_DECLARE_METATYPE(QTextDocument *) Q_DECLARE_METATYPE(QTextCursor) using namespace Akonadi; using namespace Grantlee; KJotsWidget::KJotsWidget(QWidget *parent, KXMLGUIClient *xmlGuiClient, Qt::WindowFlags f) : QWidget(parent, f), m_xmlGuiClient(xmlGuiClient) { Akonadi::ControlGui::widgetNeedsAkonadi(this); KConfigGroup config(KSharedConfig::openConfig(), "General"); const bool autoCreate = config.readEntry("AutoCreateResourceOnStart", true); config.writeEntry("AutoCreateResourceOnStart", autoCreate); config.sync(); if (autoCreate) { LocalResourceCreator *creator = new LocalResourceCreator(this); creator->createIfMissing(); } m_splitter = new QSplitter(this); m_splitter->setStretchFactor(1, 1); // I think we can live without this //m_splitter->setOpaqueResize(KGlobalSettings::opaqueResize()); QHBoxLayout *layout = new QHBoxLayout(this); layout->setMargin(0); m_templateEngine = new Engine(this); // We don't have custom plugins, so this should not be needed //m_templateEngine->setPluginPaths(KStd.findDirs("lib", QString())); m_loader = QSharedPointer(new FileSystemTemplateLoader()); m_loader->setTemplateDirs(QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("kjots/themes"), QStandardPaths::LocateDirectory)); m_loader->setTheme(QLatin1String("default")); m_templateEngine->addTemplateLoader(m_loader); treeview = new KJotsTreeView(xmlGuiClient, m_splitter); ItemFetchScope scope; scope.fetchFullPayload(true); // Need to have full item when adding it to the internal data structure scope.fetchAttribute< EntityDisplayAttribute >(); scope.fetchAttribute< NoteShared::NoteLockAttribute >(); ChangeRecorder *monitor = new ChangeRecorder(this); monitor->fetchCollection(true); monitor->setItemFetchScope(scope); monitor->setCollectionMonitored(Collection::root()); monitor->setMimeTypeMonitored(Akonadi::NoteUtils::noteMimeType()); m_kjotsModel = new KJotsModel(monitor, this); m_sortProxyModel = new KJotsSortProxyModel(this); m_sortProxyModel->setSourceModel(m_kjotsModel); m_orderProxy = new EntityOrderProxyModel(this); m_orderProxy->setSourceModel(m_sortProxyModel); KConfigGroup cfg(KSharedConfig::openConfig(), "KJotsEntityOrder"); m_orderProxy->setOrderConfig(cfg); treeview->setModel(m_orderProxy); treeview->setSelectionMode(QAbstractItemView::ExtendedSelection); treeview->setEditTriggers(QAbstractItemView::DoubleClicked); connect(treeview->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), SLOT(selectionChanged(QItemSelection,QItemSelection))); selProxy = new KSelectionProxyModel(treeview->selectionModel(), this); selProxy->setSourceModel(treeview->model()); // TODO: Write a QAbstractItemView subclass to render kjots selection. connect(selProxy, &KSelectionProxyModel::dataChanged, this, &KJotsWidget::renderSelection); connect(selProxy, &KSelectionProxyModel::rowsInserted, this, &KJotsWidget::renderSelection); connect(selProxy, &KSelectionProxyModel::rowsRemoved, this, &KJotsWidget::renderSelection); stackedWidget = new QStackedWidget(m_splitter); KActionCollection *actionCollection = xmlGuiClient->actionCollection(); editor = new KJotsEdit(treeview->selectionModel(), stackedWidget); actionCollection->addActions(editor->createActions()); stackedWidget->addWidget(editor); layout->addWidget(m_splitter); browser = new KJotsBrowser(treeview->selectionModel(), stackedWidget); + connect(browser, &KJotsBrowser::linkClicked, this, &KJotsWidget::openLink); stackedWidget->addWidget(browser); stackedWidget->setCurrentWidget(browser); QAction *action; action = actionCollection->addAction(QLatin1String("go_next_book")); action->setText(i18n("Next Book")); action->setIcon(QIcon::fromTheme(QLatin1String("go-down"))); actionCollection->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_D)); connect(action, &QAction::triggered, this, &KJotsWidget::nextBook); connect(this, &KJotsWidget::canGoNextBookChanged, action, &QAction::setEnabled); action = actionCollection->addAction(QLatin1String("go_prev_book")); action->setText(i18n("Previous Book")); action->setIcon(QIcon::fromTheme(QLatin1String("go-up"))); actionCollection->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_D)); connect(action, &QAction::triggered, this, &KJotsWidget::prevBook); connect(this, &KJotsWidget::canGoPreviousBookChanged, action, &QAction::setEnabled); action = actionCollection->addAction(QLatin1String("go_next_page")); action->setText(i18n("Next Page")); action->setIcon(QIcon::fromTheme(QLatin1String("go-next"))); actionCollection->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_PageDown)); connect(action, &QAction::triggered, this, &KJotsWidget::nextPage); connect(this, &KJotsWidget::canGoNextPageChanged, action, &QAction::setEnabled); action = actionCollection->addAction(QLatin1String("go_prev_page")); action->setText(i18n("Previous Page")); action->setIcon(QIcon::fromTheme(QLatin1String("go-previous"))); actionCollection->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_PageUp)); connect(action, &QAction::triggered, this, &KJotsWidget::prevPage); connect(this, &KJotsWidget::canGoPreviousPageChanged, action, &QAction::setEnabled); action = actionCollection->addAction(QLatin1String("new_page")); action->setText(i18n("&New Page")); actionCollection->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_N)); action->setIcon(QIcon::fromTheme(QLatin1String("document-new"))); connect(action, &QAction::triggered, this, &KJotsWidget::newPage); action = actionCollection->addAction(QLatin1String("new_book")); action->setText(i18n("New &Book...")); actionCollection->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_N)); action->setIcon(QIcon::fromTheme(QLatin1String("address-book-new"))); connect(action, &QAction::triggered, this, &KJotsWidget::newBook); action = actionCollection->addAction(QLatin1String("del_page")); action->setText(i18n("&Delete Page")); actionCollection->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_Delete)); action->setIcon(QIcon::fromTheme(QLatin1String("edit-delete-page"))); connect(action, &QAction::triggered, this, &KJotsWidget::deletePage); action = actionCollection->addAction(QLatin1String("del_folder")); action->setText(i18n("Delete Boo&k")); actionCollection->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_Delete)); action->setIcon(QIcon::fromTheme(QLatin1String("edit-delete"))); connect(action, &QAction::triggered, this, &KJotsWidget::deleteBook); action = actionCollection->addAction(QLatin1String("del_mult")); action->setText(i18n("Delete Selected")); action->setIcon(QIcon::fromTheme(QLatin1String("edit-delete"))); connect(action, &QAction::triggered, this, &KJotsWidget::deleteMultiple); action = actionCollection->addAction(QLatin1String("manual_save")); action->setText(i18n("Manual Save")); action->setIcon(QIcon::fromTheme(QLatin1String("document-save"))); actionCollection->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_S)); action = actionCollection->addAction(QLatin1String("auto_bullet")); action->setText(i18n("Auto Bullets")); action->setIcon(QIcon::fromTheme(QLatin1String("format-list-unordered"))); action->setCheckable(true); action = actionCollection->addAction(QLatin1String("auto_decimal")); action->setText(i18n("Auto Decimal List")); action->setIcon(QIcon::fromTheme(QLatin1String("format-list-ordered"))); action->setCheckable(true); action = actionCollection->addAction(QLatin1String("manage_link")); action->setText(i18n("Link")); action->setIcon(QIcon::fromTheme(QLatin1String("insert-link"))); action = actionCollection->addAction(QLatin1String("insert_image")); action->setText(i18n("Insert Image")); action->setIcon(QIcon::fromTheme(QLatin1String("insert-image"))); action = actionCollection->addAction(QLatin1String("insert_checkmark")); action->setText(i18n("Insert Checkmark")); action->setIcon(QIcon::fromTheme(QLatin1String("checkmark"))); action->setEnabled(false); action = actionCollection->addAction(QLatin1String("rename_entry")); action->setText(i18n("Rename...")); action->setIcon(QIcon::fromTheme(QLatin1String("edit-rename"))); actionCollection->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_M)); action = actionCollection->addAction(QLatin1String("insert_date")); action->setText(i18n("Insert Date")); actionCollection->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_I)); action->setIcon(QIcon::fromTheme(QLatin1String("view-calendar-time-spent"))); action = actionCollection->addAction(QLatin1String("change_color")); action->setIcon(QIcon::fromTheme(QLatin1String("format-fill-color"))); action->setText(i18n("Change Color...")); action = actionCollection->addAction(QLatin1String("copy_link_address")); action->setText(i18n("Copy Link Address")); action = actionCollection->addAction(QLatin1String("lock")); action->setText(i18n("Lock Selected")); action->setIcon(QIcon::fromTheme(QLatin1String("emblem-locked"))); connect(action, &QAction::triggered, this, &KJotsWidget::actionLock); action = actionCollection->addAction(QLatin1String("unlock")); action->setText(i18n("Unlock Selected")); action->setIcon(QIcon::fromTheme(QLatin1String("emblem-unlocked"))); connect(action, &QAction::triggered, this, &KJotsWidget::actionUnlock); action = actionCollection->addAction(QLatin1String("sort_children_alpha")); action->setText(i18n("Sort children alphabetically")); connect(action, &QAction::triggered, this, &KJotsWidget::actionSortChildrenAlpha); action = actionCollection->addAction(QLatin1String("sort_children_by_date")); action->setText(i18n("Sort children by creation date")); connect(action, &QAction::triggered, this, &KJotsWidget::actionSortChildrenByDate); action = KStandardAction::cut(editor, SLOT(cut()), actionCollection); connect(editor, &KJotsEdit::copyAvailable, action, &QAction::setEnabled); action->setEnabled(false); action = KStandardAction::copy(this, SLOT(copy()), actionCollection); connect(editor, &KJotsEdit::copyAvailable, action, &QAction::setEnabled); connect(browser, &KJotsBrowser::copyAvailable, action, &QAction::setEnabled); action->setEnabled(false); KStandardAction::paste(editor, SLOT(paste()), actionCollection); KStandardAction::undo(editor, SLOT(undo()), actionCollection); KStandardAction::redo(editor, SLOT(redo()), actionCollection); KStandardAction::selectAll(editor, SLOT(selectAll()), actionCollection); action = actionCollection->addAction(QLatin1String("copyIntoTitle")); action->setText(i18n("Copy &into Page Title")); actionCollection->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_T)); action->setIcon(QIcon::fromTheme(QLatin1String("edit-copy"))); connect(action, &QAction::triggered, this, &KJotsWidget::copySelectionToTitle); connect(editor, &KJotsEdit::copyAvailable, action, &QAction::setEnabled); action->setEnabled(false); action = actionCollection->addAction(QLatin1String("paste_plain_text")); action->setText(i18nc("@action Paste the text in the clipboard without rich text formatting.", "Paste Plain Text")); connect(action, &QAction::triggered, editor, &KJotsEdit::pastePlainText); KStandardAction::preferences(this, SLOT(configure()), actionCollection); bookmarkMenu = actionCollection->add(QLatin1String("bookmarks")); bookmarkMenu->setText(i18n("&Bookmarks")); KJotsBookmarks *bookmarks = new KJotsBookmarks(treeview); + connect(bookmarks, &KJotsBookmarks::openLink, this, &KJotsWidget::openLink); KBookmarkMenu *bmm = new KBookmarkMenu( KBookmarkManager::managerForFile( QStandardPaths::standardLocations(QStandardPaths::AppDataLocation).first() + QStringLiteral("/kjots/bookmarks.xml"), QStringLiteral("kjots")), bookmarks, bookmarkMenu->menu()); // "Add bookmark" and "make text bold" actions have conflicting shortcuts (ctrl + b) // Make add_bookmark use ctrl+shift+b to resolve that. QAction *bm_action = bmm->addBookmarkAction(); actionCollection->addAction(QStringLiteral("add_bookmark"), bm_action); actionCollection->setDefaultShortcut(bm_action, Qt::CTRL | Qt::SHIFT | Qt::Key_B); actionCollection->addAction(QStringLiteral("edit_bookmark"), bmm->editBookmarksAction()); actionCollection->addAction(QStringLiteral("add_bookmarks_list"), bmm->bookmarkTabsAsFolderAction()); KStandardAction::find(this, SLOT(onShowSearch()), actionCollection); action = KStandardAction::findNext(this, SLOT(onRepeatSearch()), actionCollection); action->setEnabled(false); KStandardAction::replace(this, SLOT(onShowReplace()), actionCollection); action = actionCollection->addAction(QLatin1String("save_to")); action->setText(i18n("Rename...")); action->setIcon(QIcon::fromTheme(QLatin1String("edit-rename"))); actionCollection->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_M)); KActionMenu *exportMenu = actionCollection->add(QLatin1String("save_to")); exportMenu->setText(i18n("Export")); exportMenu->setIcon(QIcon::fromTheme(QLatin1String("document-export"))); action = actionCollection->addAction(QLatin1String("save_to_ascii")); action->setText(i18n("To Text File...")); action->setIcon(QIcon::fromTheme(QLatin1String("text-plain"))); connect(action, &QAction::triggered, this, &KJotsWidget::exportSelectionToPlainText); exportMenu->menu()->addAction(action); action = actionCollection->addAction(QLatin1String("save_to_html")); action->setText(i18n("To HTML File...")); action->setIcon(QIcon::fromTheme(QLatin1String("text-html"))); connect(action, &QAction::triggered, this, &KJotsWidget::exportSelectionToHtml); exportMenu->menu()->addAction(action); action = actionCollection->addAction(QLatin1String("save_to_book")); action->setText(i18n("To Book File...")); action->setIcon(QIcon::fromTheme(QLatin1String("x-office-address-book"))); connect(action, &QAction::triggered, this, &KJotsWidget::exportSelectionToXml); exportMenu->menu()->addAction(action); KStandardAction::print(this, SLOT(printSelection()), actionCollection); KStandardAction::printPreview(this, SLOT(printPreviewSelection()), actionCollection); if (!KJotsSettings::splitterSizes().isEmpty()) { m_splitter->setSizes(KJotsSettings::splitterSizes()); } QTimer::singleShot(0, this, SLOT(delayedInitialization())); connect(treeview->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), SLOT(updateMenu())); connect(treeview->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), SLOT(updateCaption())); connect(m_kjotsModel, &Akonadi::EntityTreeModel::modelAboutToBeReset, this, &KJotsWidget::saveState); connect(m_kjotsModel, &Akonadi::EntityTreeModel::modelReset, this, &KJotsWidget::restoreState); restoreState(); QDBusConnection::sessionBus().registerObject(QLatin1String("/KJotsWidget"), this, QDBusConnection::ExportScriptableContents); } KJotsWidget::~KJotsWidget() { saveState(); } void KJotsWidget::restoreState() { ETMViewStateSaver *saver = new ETMViewStateSaver; saver->setView(treeview); KConfigGroup cfg(KSharedConfig::openConfig(), "TreeState"); saver->restoreState(cfg); } void KJotsWidget::saveState() { ETMViewStateSaver saver; saver.setView(treeview); KConfigGroup cfg(KSharedConfig::openConfig(), "TreeState"); saver.saveState(cfg); cfg.sync(); } void KJotsWidget::delayedInitialization() { //TODO: Save previous searches in settings file? searchDialog = new KFindDialog(this, 0, QStringList(), false); QGridLayout *layout = new QGridLayout(searchDialog->findExtension()); layout->setMargin(0); searchAllPages = new QCheckBox(i18n("Search all pages"), searchDialog->findExtension()); layout->addWidget(searchAllPages, 0, 0); connect(searchDialog, &KFindDialog::okClicked, this, &KJotsWidget::onStartSearch); connect(searchDialog, &KFindDialog::cancelClicked, this, &KJotsWidget::onEndSearch); connect(treeview->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), SLOT(onUpdateSearch())); connect(searchDialog, &KFindDialog::optionsChanged, this, &KJotsWidget::onUpdateSearch); connect(searchAllPages, &QCheckBox::stateChanged, this, &KJotsWidget::onUpdateSearch); replaceDialog = new KReplaceDialog(this, 0, searchHistory, replaceHistory, false); QGridLayout *layout2 = new QGridLayout(replaceDialog->findExtension()); layout2->setMargin(0); replaceAllPages = new QCheckBox(i18n("Search all pages"), replaceDialog->findExtension()); layout2->addWidget(replaceAllPages, 0, 0); connect(replaceDialog, &KReplaceDialog::okClicked, this, &KJotsWidget::onStartReplace); connect(replaceDialog, &KReplaceDialog::cancelClicked, this, &KJotsWidget::onEndReplace); connect(replaceDialog, &KReplaceDialog::optionsChanged, this, &KJotsWidget::onUpdateReplace); connect(replaceAllPages, &QCheckBox::stateChanged, this, &KJotsWidget::onUpdateReplace); // Actions are enabled or disabled based on whether the selection is a single page, a single book // multiple selections, or no selection. // // The entryActions are enabled for all single pages and single books, and the multiselectionActions // are enabled when the user has made multiple selections. // // Some actions are in neither (eg, new book) and are available even when there is no selection. // // Some actions are in both, so that they are available for valid selections, but not available // for invalid selections (eg, print/find are disabled when there is no selection) KActionCollection *actionCollection = m_xmlGuiClient->actionCollection(); // Actions for a single item selection. entryActions.insert(actionCollection->action(QLatin1String(KStandardAction::name(KStandardAction::Find)))); entryActions.insert(actionCollection->action(QLatin1String(KStandardAction::name(KStandardAction::Print)))); entryActions.insert(actionCollection->action(QLatin1String("rename_entry"))); entryActions.insert(actionCollection->action(QLatin1String("change_color"))); entryActions.insert(actionCollection->action(QLatin1String("save_to"))); entryActions.insert(actionCollection->action(QLatin1String("copy_link_address"))); // Actions that are used only when a page is selected. pageActions.insert(actionCollection->action(QLatin1String(KStandardAction::name(KStandardAction::Cut)))); pageActions.insert(actionCollection->action(QLatin1String(KStandardAction::name(KStandardAction::Paste)))); pageActions.insert(actionCollection->action(QLatin1String(KStandardAction::name(KStandardAction::Replace)))); pageActions.insert(actionCollection->action(QLatin1String("del_page"))); pageActions.insert(actionCollection->action(QLatin1String("insert_date"))); pageActions.insert(actionCollection->action(QLatin1String("auto_bullet"))); pageActions.insert(actionCollection->action(QLatin1String("auto_decimal"))); pageActions.insert(actionCollection->action(QLatin1String("manage_link"))); pageActions.insert(actionCollection->action(QLatin1String("insert_checkmark"))); // Actions that are used only when a book is selected. bookActions.insert(actionCollection->action(QLatin1String("save_to_book"))); bookActions.insert(actionCollection->action(QLatin1String("del_folder"))); bookActions.insert(actionCollection->action(QLatin1String("sort_children_alpha"))); bookActions.insert(actionCollection->action(QLatin1String("sort_children_by_date"))); // Actions that are used when multiple items are selected. multiselectionActions.insert(actionCollection->action(QLatin1String(KStandardAction::name(KStandardAction::Find)))); multiselectionActions.insert(actionCollection->action(QLatin1String(KStandardAction::name(KStandardAction::Print)))); multiselectionActions.insert(actionCollection->action(QLatin1String("del_mult"))); multiselectionActions.insert(actionCollection->action(QLatin1String("save_to"))); multiselectionActions.insert(actionCollection->action(QLatin1String("change_color"))); m_autosaveTimer = new QTimer(this); updateConfiguration(); connect(m_autosaveTimer, &QTimer::timeout, editor, &KJotsEdit::savePage); connect(treeview->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), m_autosaveTimer, SLOT(start())); treeview->delayedInitialization(); editor->delayedInitialization(m_xmlGuiClient->actionCollection()); browser->delayedInitialization(); connect(treeview->itemDelegate(), SIGNAL(closeEditor(QWidget*,QAbstractItemDelegate::EndEditHint)), SLOT(bookshelfEditItemFinished(QWidget*,QAbstractItemDelegate::EndEditHint))); connect(editor, SIGNAL(currentCharFormatChanged(QTextCharFormat)), SLOT(currentCharFormatChanged(QTextCharFormat))); updateMenu(); } void KJotsWidget::bookshelfEditItemFinished(QWidget *, QAbstractItemDelegate::EndEditHint) { // Make sure the editor gets focus again after naming a new book/page. activeEditor()->setFocus(); } void KJotsWidget::currentCharFormatChanged(const QTextCharFormat &fmt) { QString selectedAnchor = fmt.anchorHref(); if (selectedAnchor != activeAnchor) { activeAnchor = selectedAnchor; if (!selectedAnchor.isEmpty()) { QTextCursor c(editor->textCursor()); editor->selectLinkText(&c); QString selectedText = c.selectedText(); if (!selectedText.isEmpty()) { Q_EMIT activeAnchorChanged(selectedAnchor, selectedText); } } else { Q_EMIT activeAnchorChanged(QString(), QString()); } } } inline QTextEdit *KJotsWidget::activeEditor() { if (browser->isVisible()) { return browser; } else { return editor; } } void KJotsWidget::updateMenu() { QModelIndexList selection = treeview->selectionModel()->selectedRows(); int selectionSize = selection.size(); if (!selectionSize) { // no (meaningful?) selection for (QAction *action : qAsConst(multiselectionActions)) { action->setEnabled(false); } for (QAction *action : qAsConst(entryActions)) { action->setEnabled(false); } for (QAction *action : qAsConst(bookActions)) { action->setEnabled(false); } for (QAction *action : qAsConst(pageActions)) { action->setEnabled(false); } editor->setActionsEnabled(false); } else if (selectionSize > 1) { for (QAction *action : qAsConst(entryActions)) { action->setEnabled(false); } for (QAction *action : qAsConst(bookActions)) { action->setEnabled(false); } for (QAction *action : qAsConst(pageActions)) { action->setEnabled(false); } for (QAction *action : qAsConst(multiselectionActions)) { action->setEnabled(true); } editor->setActionsEnabled(false); } else { for (QAction *action : qAsConst(multiselectionActions)) { action->setEnabled(false); } for (QAction *action : qAsConst(entryActions)) { action->setEnabled(true); } QModelIndex idx = selection.at(0); Collection col = idx.data(KJotsModel::CollectionRole).value(); if (col.isValid()) { for (QAction *action : qAsConst(pageActions)) { action->setEnabled(false); } const bool colIsRootCollection = (col.parentCollection() == Collection::root()); for (QAction *action : qAsConst(bookActions)) { if (action->objectName() == QLatin1String("del_folder") && colIsRootCollection) { action->setEnabled(false); } else { action->setEnabled(true); } } editor->setActionsEnabled(false); } else { for (QAction *action : qAsConst(pageActions)) { if (action->objectName() == QLatin1String(name(KStandardAction::Cut))) { action->setEnabled(activeEditor()->textCursor().hasSelection()); } else { action->setEnabled(true); } } for (QAction *action : qAsConst(bookActions)) { action->setEnabled(false); } editor->setActionsEnabled(true); } } } void KJotsWidget::copy() { activeEditor()->copy(); } void KJotsWidget::configure() { // create a new preferences dialog... KJotsConfigDlg *dialog = new KJotsConfigDlg(i18n("Settings"), this); connect(dialog, SIGNAL(configCommitted()), SLOT(updateConfiguration())); dialog->show(); } void KJotsWidget::updateConfiguration() { if (KJotsSettings::autoSave()) { m_autosaveTimer->setInterval(KJotsSettings::autoSaveInterval() * 1000 * 60); m_autosaveTimer->start(); } else { m_autosaveTimer->stop(); } } void KJotsWidget::copySelectionToTitle() { QString newTitle(editor->textCursor().selectedText()); if (!newTitle.isEmpty()) { QModelIndexList rows = treeview->selectionModel()->selectedRows(); if (rows.size() != 1) { return; } QModelIndex idx = rows.at(0); treeview->model()->setData(idx, newTitle); } } void KJotsWidget::deleteMultiple() { const QModelIndexList selectedRows = treeview->selectionModel()->selectedRows(); if (KMessageBox::questionYesNo(this, i18n("Do you really want to delete all selected books and pages?"), i18n("Delete?"), KStandardGuiItem::del(), KStandardGuiItem::cancel(), QString(), KMessageBox::Dangerous) != KMessageBox::Yes) { return; } for (const QModelIndex &index : selectedRows) { bool ok; qlonglong id = index.data(EntityTreeModel::ItemIdRole).toLongLong(&ok); Q_ASSERT(ok); if (id >= 0) { new ItemDeleteJob(Item(id), this); } else { id = index.data(EntityTreeModel::CollectionIdRole).toLongLong(&ok); Q_ASSERT(ok); if (id >= 0) { new CollectionDeleteJob(Collection(id), this); } } } } void KJotsWidget::deletePage() { QModelIndexList selectedRows = treeview->selectionModel()->selectedRows(); if (selectedRows.size() != 1) { return; } const QModelIndex idx = selectedRows.at(0); Item item = idx.data(EntityTreeModel::ItemRole).value(); if (!item.isValid()) { return; } if (item.hasAttribute()) { KMessageBox::information(topLevelWidget(), i18n("This page is locked. You can only delete it when you first unlock it."), i18n("Item is locked")); return; } if (KMessageBox::warningContinueCancel(topLevelWidget(), i18nc("remove the page, by title", "Are you sure you want to delete the page %1?", idx.data().toString()), i18n("Delete"), KStandardGuiItem::del(), KStandardGuiItem::cancel(), QLatin1String("DeletePageWarning")) == KMessageBox::Cancel) { return; } (void) new Akonadi::ItemDeleteJob(item, this); } void KJotsWidget::deleteBook() { QModelIndexList selectedRows = treeview->selectionModel()->selectedRows(); if (selectedRows.size() != 1) { return; } const QModelIndex idx = selectedRows.at(0); Collection col = idx.data(EntityTreeModel::CollectionRole).value(); if (!col.isValid()) { return; } if (col.parentCollection() == Collection::root()) { return; } if (col.hasAttribute()) { KMessageBox::information(topLevelWidget(), i18n("This book is locked. You can only delete it when you first unlock it."), i18n("Item is locked")); return; } if (KMessageBox::warningContinueCancel(topLevelWidget(), i18nc("remove the book, by title", "Are you sure you want to delete the book %1?", idx.data().toString()), i18n("Delete"), KStandardGuiItem::del(), KStandardGuiItem::cancel(), QLatin1String("DeleteBookWarning")) == KMessageBox::Cancel) { return; } (void) new Akonadi::CollectionDeleteJob(col, this); } void KJotsWidget::newBook() { QModelIndexList selectedRows = treeview->selectionModel()->selectedRows(); if (selectedRows.size() != 1) { return; } Collection col = selectedRows.at(0).data(EntityTreeModel::CollectionRole).value(); if (!col.isValid()) { return; } Collection newCollection; newCollection.setParentCollection(col); QString title = i18nc("The default name for new books.", "New Book"); newCollection.setName(KRandom::randomString(10)); newCollection.setContentMimeTypes({Akonadi::Collection::mimeType(), Akonadi::NoteUtils::noteMimeType()}); Akonadi::EntityDisplayAttribute *eda = new Akonadi::EntityDisplayAttribute(); eda->setIconName(QLatin1String("x-office-address-book")); eda->setDisplayName(title); newCollection.addAttribute(eda); Akonadi::CollectionCreateJob *job = new Akonadi::CollectionCreateJob(newCollection); connect(job, &Akonadi::CollectionCreateJob::result, this, &KJotsWidget::newBookResult); } void KJotsWidget::newPage() { QModelIndexList selectedRows = treeview->selectionModel()->selectedRows(); if (selectedRows.size() != 1) { return; } Item item = selectedRows.at(0).data(EntityTreeModel::ItemRole).value(); Collection col; if (item.isValid()) { col = selectedRows.at(0).data(EntityTreeModel::ParentCollectionRole).value(); } else { col = selectedRows.at(0).data(EntityTreeModel::CollectionRole).value(); } if (!col.isValid()) { return; } doCreateNewPage(col); } void KJotsWidget::doCreateNewPage(const Collection &collection) { Akonotes::NoteCreatorAndSelector *creatorAndSelector = new Akonotes::NoteCreatorAndSelector(treeview->selectionModel()); creatorAndSelector->createNote(collection); } void KJotsWidget::newPageResult(KJob *job) { if (job->error()) { qDebug() << job->errorString(); } } void KJotsWidget::newBookResult(KJob *job) { if (job->error()) { qDebug() << job->errorString(); return; } Akonadi::CollectionCreateJob *createJob = qobject_cast(job); if (!createJob) { return; } const Collection collection = createJob->collection(); if (!collection.isValid()) { return; } doCreateNewPage(collection); } QString KJotsWidget::renderSelectionToHtml() { QHash hash; QList objectList; const int rows = selProxy->rowCount(); const int column = 0; for (int row = 0; row < rows; ++row) { QModelIndex idx = selProxy->index(row, column, QModelIndex()); QObject *obj = idx.data(KJotsModel::GrantleeObjectRole).value(); KJotsEntity *kjotsEntity = qobject_cast(obj); kjotsEntity->setIndex(idx); objectList << QVariant::fromValue(static_cast(kjotsEntity)); } hash.insert(QLatin1String("entities"), objectList); hash.insert(QLatin1String("i18n_TABLE_OF_CONTENTS"), i18nc("Header for 'Table of contents' section of rendered output", "Table of contents")); Context c(hash); Template t = m_templateEngine->loadByName(QLatin1String("template.html")); QString result = t->render(&c); // TODO: handle errors. return result; } QString KJotsWidget::renderSelectionToPlainText() { QHash hash; QList objectList; const int rows = selProxy->rowCount(); const int column = 0; for (int row = 0; row < rows; ++row) { QModelIndex idx = selProxy->index(row, column, QModelIndex()); QObject *obj = idx.data(KJotsModel::GrantleeObjectRole).value(); KJotsEntity *kjotsEntity = qobject_cast(obj); kjotsEntity->setIndex(idx); objectList << QVariant::fromValue(static_cast(kjotsEntity)); } hash.insert(QLatin1String("entities"), objectList); hash.insert(QLatin1String("i18n_TABLE_OF_CONTENTS"), i18nc("Header for 'Table of contents' section of rendered output", "Table of contents")); Context c(hash); Template t = m_templateEngine->loadByName(QLatin1String("template.txt")); QString result = t->render(&c); // TODO: handle errors. return result; } QString KJotsWidget::renderSelectionToXml() { QHash hash; QList objectList; const int rows = selProxy->rowCount(); const int column = 0; for (int row = 0; row < rows; ++row) { QModelIndex idx = selProxy->index(row, column, QModelIndex()); QObject *obj = idx.data(KJotsModel::GrantleeObjectRole).value(); KJotsEntity *kjotsEntity = qobject_cast(obj); kjotsEntity->setIndex(idx); objectList << QVariant::fromValue(static_cast(kjotsEntity)); } hash.insert(QLatin1String("entities"), objectList); Context c(hash); QString currentTheme = m_loader->themeName(); m_loader->setTheme(QLatin1String("xml_output")); Template t = m_templateEngine->loadByName(QLatin1String("template.xml")); QString result = t->render(&c); m_loader->setTheme(currentTheme); return result; } void KJotsWidget::renderSelection() { const int rows = selProxy->rowCount(); // If the selection is a single page, present it for editing... if (rows == 1) { QModelIndex idx = selProxy->index(0, 0, QModelIndex()); QTextDocument *document = idx.data(KJotsModel::DocumentRole).value(); if (document) { editor->setDocument(document); QTextCursor textCursor = document->property("textCursor").value(); if (!textCursor.isNull()) { editor->setTextCursor(textCursor); } stackedWidget->setCurrentWidget(editor); editor->setFocus(); return; } // else fallthrough } // ... Otherwise, render the selection read-only. QTextDocument doc; QTextCursor cursor(&doc); browser->setHtml(renderSelectionToHtml()); stackedWidget->setCurrentWidget(browser); } QString KJotsWidget::getThemeFromUser() { return QString(); #if 0 bool ok; QString text = QInputDialog::getText(this, i18n("Change Theme"), tr("Theme name:"), QLineEdit::Normal, m_loader->themeName(), &ok); if (!ok || text.isEmpty()) { return QLatin1String("default"); } return text; #endif } void KJotsWidget::changeTheme() { #if 0 m_loader->setTheme(getThemeFromUser()); renderSelection(); #endif } void KJotsWidget::exportSelectionToHtml() { QString currentTheme = m_loader->themeName(); QString themeName = getThemeFromUser(); if (themeName.isEmpty()) { themeName = QLatin1String("default"); } m_loader->setTheme(themeName); QString filename = QFileDialog::getSaveFileName(); if (!filename.isEmpty()) { QFile exportFile(filename); if (!exportFile.open(QIODevice::WriteOnly | QIODevice::Text)) { m_loader->setTheme(currentTheme); KMessageBox::error(nullptr, i18n("Error opening internal file.")); return; } exportFile.write(renderSelectionToHtml().toUtf8()); exportFile.close(); } m_loader->setTheme(currentTheme); } void KJotsWidget::exportSelectionToPlainText() { QString currentTheme = m_loader->themeName(); m_loader->setTheme(QLatin1String("plain_text")); QString filename = QFileDialog::getSaveFileName(); if (!filename.isEmpty()) { QFile exportFile(filename); if (!exportFile.open(QIODevice::WriteOnly | QIODevice::Text)) { m_loader->setTheme(currentTheme); KMessageBox::error(nullptr, i18n("Error opening internal file.")); return; } exportFile.write(renderSelectionToPlainText().toUtf8()); exportFile.close(); } m_loader->setTheme(currentTheme); } void KJotsWidget::exportSelectionToXml() { QString currentTheme = m_loader->themeName(); m_loader->setTheme(QLatin1String("xml_output")); QString filename = QFileDialog::getSaveFileName(); if (!filename.isEmpty()) { QFile exportFile(filename); if (!exportFile.open(QIODevice::WriteOnly | QIODevice::Text)) { m_loader->setTheme(currentTheme); KMessageBox::error(nullptr, i18n("Error opening internal file.")); return; } exportFile.write(renderSelectionToXml().toUtf8()); exportFile.close(); } m_loader->setTheme(currentTheme); } std::unique_ptr KJotsWidget::setupPrinter(QPrinter::PrinterMode mode) { auto printer = std::make_unique(mode); printer->setDocName(QStringLiteral("KJots_Print")); printer->setCreator(QStringLiteral("KJots")); if (!activeEditor()->textCursor().selection().isEmpty()) { printer->setPrintRange(QPrinter::Selection); } return printer; } void KJotsWidget::printPreviewSelection() { auto printer = setupPrinter(QPrinter::ScreenResolution); QPrintPreviewDialog previewdlg(printer.get(), this); connect(&previewdlg, &QPrintPreviewDialog::paintRequested, this, &KJotsWidget::print); previewdlg.exec(); } void KJotsWidget::printSelection() { auto printer = setupPrinter(QPrinter::HighResolution); QPrintDialog printDialog(printer.get(), this); if (printDialog.exec() == QDialog::Accepted) { print(printer.get()); } } void KJotsWidget::print(QPrinter *printer) { QTextDocument printDocument; if (printer->printRange() == QPrinter::Selection) { printDocument.setHtml(activeEditor()->textCursor().selection().toHtml()); } else { QString currentTheme = m_loader->themeName(); m_loader->setTheme(QLatin1String("default")); printDocument.setHtml(renderSelectionToHtml()); m_loader->setTheme(currentTheme); } printDocument.print(printer); } void KJotsWidget::selectNext(int role, int step) { QModelIndexList list = treeview->selectionModel()->selectedRows(); Q_ASSERT(list.size() == 1); QModelIndex idx = list.at(0); const int column = idx.column(); QModelIndex sibling = idx.sibling(idx.row() + step, column); while (sibling.isValid()) { if (sibling.data(role).toInt() >= 0) { treeview->selectionModel()->select(sibling, QItemSelectionModel::SelectCurrent); return; } sibling = sibling.sibling(sibling.row() + step, column); } qWarning() << "No valid selection"; } void KJotsWidget::nextBook() { return selectNext(EntityTreeModel::CollectionIdRole, 1); } void KJotsWidget::nextPage() { return selectNext(EntityTreeModel::ItemIdRole, 1); } void KJotsWidget::prevBook() { return selectNext(EntityTreeModel::CollectionIdRole, -1); } void KJotsWidget::prevPage() { return selectNext(EntityTreeModel::ItemIdRole, -1); } bool KJotsWidget::canGo(int role, int step) const { QModelIndexList list = treeview->selectionModel()->selectedRows(); if (list.size() != 1) { return false; } QModelIndex currentIdx = list.at(0); const int column = currentIdx.column(); Q_ASSERT(currentIdx.isValid()); QModelIndex sibling = currentIdx.sibling(currentIdx.row() + step, column); while (sibling.isValid() && sibling != currentIdx) { if (sibling.data(role).toInt() >= 0) { return true; } sibling = sibling.sibling(sibling.row() + step, column); } return false; } bool KJotsWidget::canGoNextPage() const { return canGo(EntityTreeModel::ItemIdRole, 1); } bool KJotsWidget::canGoPreviousPage() const { return canGo(EntityTreeModel::ItemIdRole, -1); } bool KJotsWidget::canGoNextBook() const { return canGo(EntityTreeModel::CollectionIdRole, 1); } bool KJotsWidget::canGoPreviousBook() const { return canGo(EntityTreeModel::CollectionIdRole, -1); } void KJotsWidget::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) { Q_UNUSED(selected) Q_EMIT canGoNextBookChanged(canGoPreviousBook()); Q_EMIT canGoNextPageChanged(canGoNextPage()); Q_EMIT canGoPreviousBookChanged(canGoPreviousBook()); Q_EMIT canGoPreviousPageChanged(canGoPreviousPage()); if (deselected.size() == 1) { editor->document()->setProperty("textCursor", QVariant::fromValue(editor->textCursor())); if (editor->document()->isModified()) { treeview->model()->setData(deselected.indexes().first(), QVariant::fromValue(editor->document()), KJotsModel::DocumentRole); } } } /*! Shows the search dialog when "Find" is selected. */ void KJotsWidget::onShowSearch() { onUpdateSearch(); QTextEdit *browserOrEditor = activeEditor(); if (browserOrEditor->textCursor().hasSelection()) { searchDialog->setHasSelection(true); long dialogOptions = searchDialog->options(); dialogOptions |= KFind::SelectedText; searchDialog->setOptions(dialogOptions); } else { searchDialog->setHasSelection(false); } searchDialog->setFindHistory(searchHistory); searchDialog->show(); onUpdateSearch(); } /*! Updates the search dialog if the user is switching selections while it is open. */ void KJotsWidget::onUpdateSearch() { if (searchDialog->isVisible()) { long searchOptions = searchDialog->options(); if (searchOptions & KFind::SelectedText) { searchAllPages->setCheckState(Qt::Unchecked); searchAllPages->setEnabled(false); } else { searchAllPages->setEnabled(true); } if (searchAllPages->checkState() == Qt::Checked) { searchOptions &= ~KFind::SelectedText; searchDialog->setOptions(searchOptions); searchDialog->setHasSelection(false); } else { if (activeEditor()->textCursor().hasSelection()) { searchDialog->setHasSelection(true); } } if (activeEditor()->textCursor().hasSelection()) { if (searchAllPages->checkState() == Qt::Unchecked) { searchDialog->setHasSelection(true); } } else { searchOptions &= ~KFind::SelectedText; searchDialog->setOptions(searchOptions); searchDialog->setHasSelection(false); } } } /*! Called when the user presses OK in the search dialog. */ void KJotsWidget::onStartSearch() { QString searchPattern = searchDialog->pattern(); if (!searchHistory.contains(searchPattern)) { searchHistory.prepend(searchPattern); } QTextEdit *browserOrEditor = activeEditor(); QTextCursor cursor = browserOrEditor->textCursor(); long searchOptions = searchDialog->options(); if (searchOptions & KFind::FromCursor) { searchPos = cursor.position(); searchBeginPos = 0; cursor.movePosition(QTextCursor::End); searchEndPos = cursor.position(); } else { if (searchOptions & KFind::SelectedText) { searchBeginPos = cursor.selectionStart(); searchEndPos = cursor.selectionEnd(); } else { searchBeginPos = 0; cursor.movePosition(QTextCursor::End); searchEndPos = cursor.position(); } if (searchOptions & KFind::FindBackwards) { searchPos = searchEndPos; } else { searchPos = searchBeginPos; } } m_xmlGuiClient->actionCollection()->action(QLatin1String(KStandardAction::name(KStandardAction::FindNext)))->setEnabled(true); onRepeatSearch(); } /*! Called when user chooses "Find Next" */ void KJotsWidget::onRepeatSearch() { if (search(false) == 0) { KMessageBox::sorry(nullptr, i18n("No matches found.")); m_xmlGuiClient->actionCollection()->action(QLatin1String(KStandardAction::name(KStandardAction::FindNext)))->setEnabled(false); } } /*! Called when user presses Cancel in find dialog. */ void KJotsWidget::onEndSearch() { m_xmlGuiClient->actionCollection()->action(QLatin1String(KStandardAction::name(KStandardAction::FindNext)))->setEnabled(false); } /*! Shows the replace dialog when "Replace" is selected. */ void KJotsWidget::onShowReplace() { Q_ASSERT(editor->isVisible()); if (editor->textCursor().hasSelection()) { replaceDialog->setHasSelection(true); long dialogOptions = replaceDialog->options(); dialogOptions |= KFind::SelectedText; replaceDialog->setOptions(dialogOptions); } else { replaceDialog->setHasSelection(false); } replaceDialog->setFindHistory(searchHistory); replaceDialog->setReplacementHistory(replaceHistory); replaceDialog->show(); onUpdateReplace(); } /*! Updates the replace dialog if the user is switching selections while it is open. */ void KJotsWidget::onUpdateReplace() { if (replaceDialog->isVisible()) { long replaceOptions = replaceDialog->options(); if (replaceOptions & KFind::SelectedText) { replaceAllPages->setCheckState(Qt::Unchecked); replaceAllPages->setEnabled(false); } else { replaceAllPages->setEnabled(true); } if (replaceAllPages->checkState() == Qt::Checked) { replaceOptions &= ~KFind::SelectedText; replaceDialog->setOptions(replaceOptions); replaceDialog->setHasSelection(false); } else { if (activeEditor()->textCursor().hasSelection()) { replaceDialog->setHasSelection(true); } } } } /*! Called when the user presses OK in the replace dialog. */ void KJotsWidget::onStartReplace() { QString searchPattern = replaceDialog->pattern(); if (!searchHistory.contains(searchPattern)) { searchHistory.prepend(searchPattern); } QString replacePattern = replaceDialog->replacement(); if (!replaceHistory.contains(replacePattern)) { replaceHistory.prepend(replacePattern); } QTextCursor cursor = editor->textCursor(); long replaceOptions = replaceDialog->options(); if (replaceOptions & KFind::FromCursor) { replacePos = cursor.position(); replaceBeginPos = 0; cursor.movePosition(QTextCursor::End); replaceEndPos = cursor.position(); } else { if (replaceOptions & KFind::SelectedText) { replaceBeginPos = cursor.selectionStart(); replaceEndPos = cursor.selectionEnd(); } else { replaceBeginPos = 0; cursor.movePosition(QTextCursor::End); replaceEndPos = cursor.position(); } if (replaceOptions & KFind::FindBackwards) { replacePos = replaceEndPos; } else { replacePos = replaceBeginPos; } } replaceStartPage = treeview->selectionModel()->selectedRows().first(); //allow KReplaceDialog to exit so the user can see. QTimer::singleShot(0, this, SLOT(onRepeatReplace())); } /*! Only called after onStartReplace. Kept the name scheme for consistancy. */ void KJotsWidget::onRepeatReplace() { KJotsReplaceNextDialog *dlg = nullptr; QString searchPattern = replaceDialog->pattern(); QString replacePattern = replaceDialog->replacement(); int found = 0; int replaced = 0; long replaceOptions = replaceDialog->options(); if (replaceOptions & KReplaceDialog::PromptOnReplace) { dlg = new KJotsReplaceNextDialog(this); } while (true) { if (!search(true)) { break; } QTextCursor cursor = editor->textCursor(); if (!cursor.hasSelection()) { break; } else { ++found; } QString replacementText = replacePattern; if (replaceOptions & KReplaceDialog::BackReference) { QRegExp regExp(searchPattern, (replaceOptions & Qt::CaseSensitive) ? Qt::CaseSensitive : Qt::CaseInsensitive, QRegExp::RegExp2); regExp.indexIn(cursor.selectedText()); int capCount = regExp.captureCount(); for (int i = 0; i <= capCount; ++i) { QString c = QString::fromLatin1("\\%1").arg(i); replacementText.replace(c, regExp.cap(i)); } } if (replaceOptions & KReplaceDialog::PromptOnReplace) { dlg->setLabel(cursor.selectedText(), replacementText); if (!dlg->exec()) { break; } if (dlg->answer() != KJotsReplaceNextDialog::Skip) { cursor.insertText(replacementText); editor->setTextCursor(cursor); ++replaced; } if (dlg->answer() == KJotsReplaceNextDialog::All) { replaceOptions |= ~KReplaceDialog::PromptOnReplace; } } else { cursor.insertText(replacementText); editor->setTextCursor(cursor); ++replaced; } } if (replaced == found) { KMessageBox::information(nullptr, i18np("Replaced 1 occurrence.", "Replaced %1 occurrences.", replaced)); } else if (replaced < found) { KMessageBox::information(nullptr, i18np("Replaced %2 of 1 occurrence.", "Replaced %2 of %1 occurrences.", found, replaced)); } if (dlg) { delete dlg; } } /*! Called when user presses Cancel in replace dialog. Just a placeholder for now. */ void KJotsWidget::onEndReplace() { } /*! Searches for the given pattern, with the given options. This is huge and unwieldly function, but the operation we're performing is huge and unwieldly. */ int KJotsWidget::search(bool replacing) { int rc = 0; int *beginPos = replacing ? &replaceBeginPos : &searchBeginPos; int *endPos = replacing ? &replaceEndPos : &searchEndPos; long options = replacing ? replaceDialog->options() : searchDialog->options(); QString pattern = replacing ? replaceDialog->pattern() : searchDialog->pattern(); int *curPos = replacing ? &replacePos : &searchPos; QModelIndex startPage = replacing ? replaceStartPage : treeview->selectionModel()->selectedRows().first(); bool allPages = false; QCheckBox *box = replacing ? replaceAllPages : searchAllPages; if (box->isEnabled() && box->checkState() == Qt::Checked) { allPages = true; } QTextDocument::FindFlags findFlags; if (options & Qt::CaseSensitive) { findFlags |= QTextDocument::FindCaseSensitively; } if (options & KFind::WholeWordsOnly) { findFlags |= QTextDocument::FindWholeWords; } if (options & KFind::FindBackwards) { findFlags |= QTextDocument::FindBackward; } // We will find a match or return 0 int attempts = 0; while (true) { ++attempts; QTextEdit *browserOrEditor = activeEditor(); QTextDocument *theDoc = browserOrEditor->document(); QTextCursor cursor; if (options & KFind::RegularExpression) { QRegExp regExp(pattern, (options & Qt::CaseSensitive) ? Qt::CaseSensitive : Qt::CaseInsensitive, QRegExp::RegExp2); cursor = theDoc->find(regExp, *curPos, findFlags); } else { cursor = theDoc->find(pattern, *curPos, findFlags); } if (cursor.hasSelection()) { if (cursor.selectionStart() >= *beginPos && cursor.selectionEnd() <= *endPos) { browserOrEditor->setTextCursor(cursor); browserOrEditor->ensureCursorVisible(); *curPos = (options & KFind::FindBackwards) ? cursor.selectionStart() : cursor.selectionEnd(); rc = 1; break; } } //No match. Determine what to do next. if (replacing && !(options & KFind::FromCursor) && !allPages) { break; } if ((options & KFind::FromCursor) && !allPages) { if (KMessageBox::questionYesNo(this, i18n("End of search area reached. Do you want to wrap around and continue?")) == KMessageBox::No) { rc = 3; break; } } if (allPages) { if (options & KFind::FindBackwards) { if (canGoPreviousPage()) { prevPage(); } } else { if (canGoNextPage()) { nextPage(); } } if (startPage == treeview->selectionModel()->selectedRows().first()) { rc = 0; break; } *beginPos = 0; cursor = editor->textCursor(); cursor.movePosition(QTextCursor::End); *endPos = cursor.position(); *curPos = (options & KFind::FindBackwards) ? *endPos : *beginPos; continue; } // By now, we should have figured out what to do. In all remaining cases we // will automatically loop and try to "find next" from the top/bottom, because // I like this behavior the best. if (attempts <= 1) { *curPos = (options & KFind::FindBackwards) ? *endPos : *beginPos; } else { // We've already tried the loop and failed to find anything. Bail. rc = 0; break; } } return rc; } void KJotsWidget::updateCaption() { Q_EMIT captionChanged(treeview->captionForSelection(QLatin1String(" / "))); } void KJotsWidget::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) { QModelIndexList rows = treeview->selectionModel()->selectedRows(); if (rows.size() != 1) { return; } QItemSelection changed(topLeft, bottomRight); if (changed.contains(rows.first())) { Q_EMIT captionChanged(treeview->captionForSelection(QLatin1String(" / "))); } } bool KJotsWidget::queryClose() { KJotsSettings::setSplitterSizes(m_splitter->sizes()); KJotsSettings::self()->save(); m_orderProxy->saveOrder(); return true; } void KJotsWidget::actionLock() { const QModelIndexList selection = treeview->selectionModel()->selectedRows(); if (selection.isEmpty()) { return; } Collection::List collections; Item::List items; for (const QModelIndex &idx : selection) { Collection col = idx.data(EntityTreeModel::CollectionRole).value(); if (col.isValid()) { collections << col; } else { Item item = idx.data(EntityTreeModel::ItemRole).value(); if (item.isValid()) { items << item; } } } if (collections.isEmpty() && items.isEmpty()) { return; } new KJotsLockJob(collections, items, this); } void KJotsWidget::actionUnlock() { const QModelIndexList selection = treeview->selectionModel()->selectedRows(); if (selection.isEmpty()) { return; } Collection::List collections; Item::List items; for (const QModelIndex &idx : selection) { Collection col = idx.data(EntityTreeModel::CollectionRole).value(); if (col.isValid()) { collections << col; } else { Item item = idx.data(EntityTreeModel::ItemRole).value(); if (item.isValid()) { items << item; } } } if (collections.isEmpty() && items.isEmpty()) { return; } new KJotsLockJob(collections, items, KJotsLockJob::UnlockJob, this); } void KJotsWidget::actionSortChildrenAlpha() { const QModelIndexList selection = treeview->selectionModel()->selectedRows(); for (const QModelIndex &index : selection) { const QPersistentModelIndex persistent(index); m_sortProxyModel->sortChildrenAlphabetically(m_orderProxy->mapToSource(index)); m_orderProxy->clearOrder(persistent); } } void KJotsWidget::actionSortChildrenByDate() { const QModelIndexList selection = treeview->selectionModel()->selectedRows(); for (const QModelIndex &index : selection) { const QPersistentModelIndex persistent(index); m_sortProxyModel->sortChildrenByCreationTime(m_orderProxy->mapToSource(index)); m_orderProxy->clearOrder(persistent); } } +void KJotsWidget::openLink(const QUrl &url) +{ + if (url.scheme() == QStringLiteral("kjots")) { + treeview->selectionModel()->select(KJotsModel::modelIndexForUrl(treeview->model(), url), QItemSelectionModel::ClearAndSelect); + } else { + new KRun(url, this); + } +} diff --git a/src/kjotswidget.h b/src/kjotswidget.h index f4275ea..0da9362 100644 --- a/src/kjotswidget.h +++ b/src/kjotswidget.h @@ -1,210 +1,210 @@ /* This file is part of KJots. Copyright (C) 1997 Christoph Neerfeld Copyright (C) 2002, 2003 Aaron J. Seigo Copyright (C) 2003 Stanislav Kljuhhin Copyright (C) 2005-2006 Jaison Lee Copyright (C) 2007-2009 Stephen Kelly This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KJOTSWIDGET_H #define KJOTSWIDGET_H #include #include #include #include #include #include class QCheckBox; class QTextEdit; class QTextCharFormat; class QSplitter; class QStackedWidget; class QModelIndex; class KActionMenu; class KFindDialog; class KJob; class KReplaceDialog; class KSelectionProxyModel; class KJotsBrowser; class KXMLGUIClient; namespace Akonadi { class EntityTreeModel; class EntityOrderProxyModel; } namespace Grantlee { class Engine; } class KJotsEdit; class KJotsTreeView; class KJotsSortProxyModel; class KJotsWidget : public QWidget { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.KJotsWidget") public: explicit KJotsWidget(QWidget *parent, KXMLGUIClient *xmlGuiclient, Qt::WindowFlags f = 0); ~KJotsWidget(); QTextEdit *activeEditor(); - public Q_SLOTS: void prevPage(); void nextPage(); void prevBook(); void nextBook(); bool canGoNextPage() const; bool canGoPreviousPage() const; bool canGoNextBook() const; bool canGoPreviousBook() const; void updateCaption(); void updateMenu(); void doCreateNewPage(const Akonadi::Collection &collection); Q_SCRIPTABLE void newPage(); Q_SCRIPTABLE void newBook(); Q_SCRIPTABLE bool queryClose(); Q_SIGNALS: void canGoNextPageChanged(bool); void canGoPreviousPageChanged(bool); void canGoNextBookChanged(bool); void canGoPreviousBookChanged(bool); void captionChanged(const QString &newCaption); /** Signals that the text cursor in the editor is now on a different anchor, or not on an anchor anymore. @param anchorTarget The href of the focused anchor. @param anchorText The display text of the focused anchor. */ void activeAnchorChanged(const QString &anchorTarget, const QString &anchorText); protected: QString renderSelectionToHtml(); QString renderSelectionToXml(); QString renderSelectionToPlainText(); QString getThemeFromUser(); void selectNext(int role, int step); int search(bool); std::unique_ptr setupPrinter(QPrinter::PrinterMode mode = QPrinter::ScreenResolution); protected Q_SLOTS: void renderSelection(); void changeTheme(); void exportSelectionToHtml(); void exportSelectionToPlainText(); void exportSelectionToXml(); void printSelection(); void printPreviewSelection(); void deletePage(); void deleteBook(); void deleteMultiple(); + void openLink(const QUrl &url); private Q_SLOTS: void delayedInitialization(); void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected); void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); void bookshelfEditItemFinished(QWidget *, QAbstractItemDelegate::EndEditHint); bool canGo(int role, int step) const; void newPageResult(KJob *job); void newBookResult(KJob *job); void copySelectionToTitle(); void copy(); void configure(); void onShowSearch(); void onUpdateSearch(); void onStartSearch(); void onRepeatSearch(); void onEndSearch(); void onShowReplace(); void onUpdateReplace(); void onStartReplace(); void onRepeatReplace(); void onEndReplace(); void actionLock(); void actionUnlock(); void actionSortChildrenAlpha(); void actionSortChildrenByDate(); void saveState(); void restoreState(); void currentCharFormatChanged(const QTextCharFormat &); void updateConfiguration(); void print(QPrinter *printer); private: KXMLGUIClient *m_xmlGuiClient; KJotsEdit *editor; KJotsBrowser *browser; QStackedWidget *stackedWidget; KActionMenu *bookmarkMenu; Akonadi::EntityTreeModel *m_kjotsModel; KSelectionProxyModel *selProxy; KJotsSortProxyModel *m_sortProxyModel; Akonadi::EntityOrderProxyModel *m_orderProxy; KJotsTreeView *treeview; QSplitter *m_splitter; QTimer *m_autosaveTimer; QString activeAnchor; Grantlee::Engine *m_templateEngine; QSharedPointer m_loader; KFindDialog *searchDialog; QStringList searchHistory; int searchBeginPos, searchEndPos, searchPos; QCheckBox *searchAllPages; KReplaceDialog *replaceDialog; QStringList replaceHistory; int replaceBeginPos, replaceEndPos, replacePos; QCheckBox *replaceAllPages; QModelIndex replaceStartPage; QSet entryActions, pageActions, bookActions, multiselectionActions; }; #endif diff --git a/themes/default/pagetemplate.html b/themes/default/pagetemplate.html index aa4f5cc..d3865b4 100644 --- a/themes/default/pagetemplate.html +++ b/themes/default/pagetemplate.html @@ -1,14 +1,14 @@
-

{{ entity.title }}

+

{{ entity.title }}

{{ entity.content|safe }}