diff --git a/src/kjotsbrowser.cpp b/src/kjotsbrowser.cpp index cc2dfef..e8fcf21 100644 --- a/src/kjotsbrowser.cpp +++ b/src/kjotsbrowser.cpp @@ -1,199 +1,206 @@ /* 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. */ //Own Header #include "kjotsbrowser.h" #include "kjotsmodel.h" #include #include #include #include #include #include #include #include #include #include #include using namespace Akonadi; class Q_DECL_HIDDEN KJotsBrowserWidgetPrivate { public: explicit KJotsBrowserWidgetPrivate(std::unique_ptr browser, QWidget *widget) : mBrowser(std::move(browser)) , mSliderContainer(widget) , mFindBar(mBrowser.get(), &mSliderContainer) , mTextToSpeechWidget(widget) { } std::unique_ptr mBrowser; KPIMTextEdit::SlideContainer mSliderContainer; KPIMTextEdit::RichTextEditFindBar mFindBar; KPIMTextEdit::TextToSpeechWidget mTextToSpeechWidget; }; KJotsBrowserWidget::KJotsBrowserWidget(std::unique_ptr browser, QWidget *parent) : QWidget(parent) , d(new KJotsBrowserWidgetPrivate(std::move(browser), this)) { d->mBrowser->setParent(this); d->mSliderContainer.setContent(&d->mFindBar); d->mFindBar.setHideWhenClose(false); connect(&d->mFindBar, &KPIMTextEdit::RichTextEditFindBar::hideFindBar, this, &KJotsBrowserWidget::slotHideFindBar); connect(d->mBrowser.get(), &KJotsBrowser::say, &d->mTextToSpeechWidget, &KPIMTextEdit::TextToSpeechWidget::say); QVBoxLayout *lay = new QVBoxLayout(this); lay->setContentsMargins(0, 0, 0, 0); lay->addWidget(&d->mTextToSpeechWidget); lay->addWidget(d->mBrowser.get()); lay->addWidget(&d->mSliderContainer); } KJotsBrowserWidget::~KJotsBrowserWidget() = default; KJotsBrowser* KJotsBrowserWidget::browser() { return d->mBrowser.get(); } void KJotsBrowserWidget::slotFind() { if (d->mBrowser->textCursor().hasSelection()) { d->mFindBar.setText(d->mBrowser->textCursor().selectedText()); } d->mBrowser->moveCursor(QTextCursor::Start); d->mFindBar.showFind(); d->mSliderContainer.slideIn(); d->mFindBar.focusAndSetCursor(); } void KJotsBrowserWidget::slotFindNext() { if (d->mFindBar.isVisible()) { d->mFindBar.findNext(); } else { slotFind(); } } void KJotsBrowserWidget::slotHideFindBar() { d->mSliderContainer.slideOut(); d->mBrowser->setFocus(); } -KJotsBrowser::KJotsBrowser(QAbstractItemModel *model, KActionCollection *actionCollection, QWidget *parent) +KJotsBrowser::KJotsBrowser(KActionCollection *actionCollection, QWidget *parent) : QTextBrowser(parent) - , m_model(model) , m_actionCollection(actionCollection) { setWordWrapMode(QTextOption::WordWrap); 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 overridden by setting the source to an invalid QUrl setSource(QUrl()); Q_EMIT linkClicked(url); } }); } +void KJotsBrowser::setModel(QAbstractItemModel *model) +{ + m_model = model; +} + void KJotsBrowser::contextMenuEvent(QContextMenuEvent *event) { QMenu *popup = createStandardContextMenu(event->pos()); if (!popup) { return; } popup->addSeparator(); popup->addAction(m_actionCollection->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Find)))); popup->addSeparator(); if (!document()->isEmpty() && KPIMTextEdit::TextToSpeech::self()->isReady()) { QAction *speakAction = popup->addAction(i18nc("@info:action", "Speak Text")); speakAction->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-text-to-speech"))); connect(speakAction, &QAction::triggered, this, [this](){ const QString text = textCursor().hasSelection() ? textCursor().selectedText() : document()->toPlainText(); Q_EMIT say(text); }); } popup->exec(event->globalPos()); delete popup; } bool KJotsBrowser::event(QEvent *event) { if (event->type() == QEvent::ToolTip) { tooltipEvent(static_cast(event)); } return QTextBrowser::event(event); } void KJotsBrowser::tooltipEvent(QHelpEvent *event) { + if (!m_model) { + return; + } // This code is somewhat shared with KJotsEdit QUrl url(anchorAt(event->pos())); QString message; if (url.isValid()) { QModelIndex idx; if (url.scheme() == QStringLiteral("akonadi")) { idx = KJotsModel::modelIndexForUrl(m_model, url); } else // This is #page_XXX internal links if (url.scheme().isEmpty() && url.host().isEmpty() && url.path().isEmpty() && url.query().isEmpty() && url.fragment().startsWith(QLatin1String("page_"))) { bool ok; Item::Id id = url.fragment().midRef(5).toInt(&ok); const QModelIndexList idxs = EntityTreeModel::modelIndexesForItem(m_model, Item(id)); if (ok && !idxs.isEmpty()) { idx = idxs.first(); } } else { message = i18nc("@info:tooltip %1 is hyperlink address", "Click to follow the hyperlink: %1", url.toString(QUrl::RemovePassword)); } if (idx.isValid()) { if (idx.data(EntityTreeModel::ItemRole).value().isValid()) { message = i18nc("@info:tooltip %1 is a full path to note (i.e. Notes / Notebook / Note)", "Click to open note: %1", KJotsModel::itemPath(idx)); } else if (idx.data(EntityTreeModel::CollectionRole).value().isValid()) { message = i18nc("@info:tooltip %1 is a full path to book (i.e. Notes / Notebook)", "Click to open book: %1", KJotsModel::itemPath(idx)); } } } if (!message.isEmpty()) { QToolTip::showText(event->globalPos(), message); } else { QToolTip::hideText(); } } /* ex: set tabstop=4 softtabstop=4 shiftwidth=4 expandtab: */ diff --git a/src/kjotsbrowser.h b/src/kjotsbrowser.h index 4df32bd..adfdbac 100644 --- a/src/kjotsbrowser.h +++ b/src/kjotsbrowser.h @@ -1,77 +1,82 @@ /* 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 KJOTSBROWSER_H #define KJOTSBROWSER_H #include class QHelpEvent; class QAbstractItemModel; class KActionCollection; class KJotsBrowserWidgetPrivate; class KJotsBrowser : public QTextBrowser { Q_OBJECT public: - explicit KJotsBrowser(QAbstractItemModel *model, KActionCollection *actionCollection, QWidget *parent = nullptr); + explicit KJotsBrowser(KActionCollection *actionCollection, QWidget *parent = nullptr); + /** + * @brief set the ETM which will be used to display + * additional information in tooltips + */ + void setModel(QAbstractItemModel *model); protected: bool event(QEvent *event) override; void contextMenuEvent(QContextMenuEvent *event) override; void tooltipEvent(QHelpEvent *event); Q_SIGNALS: void linkClicked(const QUrl &); void say(const QString &text); private: QAbstractItemModel *m_model = nullptr; KActionCollection *m_actionCollection = nullptr; }; /** * @brief A widget-wrapper around KJotsBrowser * * It also contains: find bar, text-to-speech widget. * @see KPIMTextEdit::RichTextEditorWidget */ class KJotsBrowserWidget : public QWidget { Q_OBJECT public: explicit KJotsBrowserWidget(std::unique_ptr browser, QWidget *parent = nullptr); ~KJotsBrowserWidget(); KJotsBrowser *browser(); public Q_SLOTS: void slotFind(); void slotFindNext(); private: void slotHideFindBar(); std::unique_ptr const d; }; #endif /* ex: set tabstop=4 softtabstop=4 shiftwidth=4 expandtab: */ diff --git a/src/kjotsedit.cpp b/src/kjotsedit.cpp index 334fe56..578badd 100644 --- a/src/kjotsedit.cpp +++ b/src/kjotsedit.cpp @@ -1,483 +1,494 @@ /* 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-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. */ //Own Header #include "kjotsedit.h" #include #include #include #include #include #include #include #include #include +#include +#include #include #include #include #include #include #include #include #include #include "kjotslinkdialog.h" #include "kjotsmodel.h" #include "noteshared/noteeditorutils.h" #include "noteshared/notelockattribute.h" Q_DECLARE_METATYPE(QTextCursor) Q_DECLARE_METATYPE(KPIMTextEdit::ImageList) using namespace Akonadi; using namespace KPIMTextEdit; class Q_DECL_HIDDEN KJotsEdit::Private { public: Private() = default; ~Private() = default; QPersistentModelIndex index; + QAbstractItemModel *model = nullptr; QAction *action_copy_into_title = nullptr; QAction *action_manage_link = nullptr; QAction *action_auto_bullet = nullptr; QAction *action_auto_decimal = nullptr; QAction *action_insert_date = nullptr; QVector richTextActionList; }; KJotsEdit::KJotsEdit(QWidget *parent, KActionCollection *actionCollection) : RichTextComposer(parent) , d(new Private) , m_actionCollection(actionCollection) , allowAutoDecimal(false) { setMouseTracking(true); setAcceptRichText(true); setWordWrapMode(QTextOption::WordWrap); setCheckSpellingEnabled(true); setFocusPolicy(Qt::StrongFocus); createActions(m_actionCollection); activateRichText(); } KJotsEdit::~KJotsEdit() = default; void KJotsEdit::createActions(KActionCollection *ac) { RichTextComposer::createActions(ac); d->action_copy_into_title = new QAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18nc("@action", "Copy &Into Page Title"), this); connect(d->action_copy_into_title, &QAction::triggered, this, &KJotsEdit::copySelectionIntoTitle); connect(this, &KJotsEdit::copyAvailable, d->action_copy_into_title, &QAction::setEnabled); d->action_copy_into_title->setEnabled(false); d->richTextActionList.append(d->action_copy_into_title); if (ac) { ac->addAction(QStringLiteral("copy_into_title"), d->action_copy_into_title); } d->action_manage_link = new QAction(QIcon::fromTheme(QStringLiteral("insert-link")), i18nc("@action creates and manages hyperlinks", "Link"), this); connect(d->action_manage_link, &QAction::triggered, this, &KJotsEdit::onLinkify); d->richTextActionList.append(d->action_manage_link); if (ac) { ac->addAction(QStringLiteral("manage_note_link"), d->action_manage_link); } d->action_auto_bullet = new QAction(QIcon::fromTheme(QStringLiteral("format-list-unordered")), i18nc("@action", "Auto Bullet List"), this); d->action_auto_bullet->setCheckable(true); connect(d->action_auto_bullet, &QAction::triggered, this, &KJotsEdit::onAutoBullet); d->richTextActionList.append(d->action_auto_bullet); if (ac) { ac->addAction(QStringLiteral("auto_bullet"), d->action_auto_bullet); } d->action_auto_decimal = new QAction(QIcon::fromTheme(QStringLiteral("format-list-ordered")), i18nc("@action", "Auto Decimal List"), this); d->action_auto_decimal->setCheckable(true); connect(d->action_auto_decimal, &QAction::triggered, this, &KJotsEdit::onAutoDecimal); d->richTextActionList.append(d->action_auto_decimal); if (ac) { ac->addAction(QStringLiteral("auto_decimal"), d->action_auto_decimal); } d->action_insert_date = new QAction(QIcon::fromTheme(QStringLiteral("view-calendar-time-spent")), i18nc("@action", "Insert Date"), this); connect(d->action_insert_date, &QAction::triggered, this, &KJotsEdit::insertDate); d->richTextActionList.append(d->action_insert_date); if (ac) { ac->addAction(QStringLiteral("insert_date"), d->action_insert_date); ac->setDefaultShortcut(d->action_insert_date, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_I)); } } void KJotsEdit::setEnableActions(bool enable) { // FIXME: RichTextComposer::setEnableActions(enable) messes with indent actions // due to bug in KPIMTextEdit (should be fixed in 20.08?) composerActions()->setActionsEnabled(enable); for (QAction *action : qAsConst(d->richTextActionList)) { action->setEnabled(enable); } } void KJotsEdit::contextMenuEvent(QContextMenuEvent *event) { QMenu *popup = mousePopupMenu(event->pos()); if (popup) { const QList actionList = popup->actions(); if (!qApp->clipboard()->text().isEmpty()) { QAction *act = m_actionCollection->action(QStringLiteral("paste_without_formatting")); act->setIcon(QIcon::fromTheme(QStringLiteral("edit-paste"))); act->setEnabled(!isReadOnly()); // HACK: menu actions are following: Undo, Redo, Separator, Cut, Copy, Paste, Delete, Clear // We want to insert "Paste Without Formatting" right after standard Paste (which is at pos 6) // Let's hope QTextEdit and KPIMTextEdit::RichTextEditor doesn't break it // (and we don't break anything either) const int pasteActionPosition = 6; if (actionList.count() >= pasteActionPosition) { popup->insertAction(popup->actions().at(pasteActionPosition), act); } else { popup->addAction(act); } } popup->addSeparator(); popup->addAction(d->action_copy_into_title); if (!anchorAt(event->pos()).isNull()) { popup->addAction(d->action_manage_link); } popup->exec(event->globalPos()); delete popup; } } bool KJotsEdit::modified() { return document()->isModified(); } void KJotsEdit::insertDate() { NoteShared::NoteEditorUtils::insertDate(this); } bool KJotsEdit::setModelIndex(const QModelIndex &index) { - bool newDocument = d->index.isValid() && (d->index != index); - // Saving the old document, if it wa changed + // Mapping index to ETM + QModelIndex etmIndex = index; + const QAbstractProxyModel *proxy = qobject_cast(index.model()); + while (proxy) { + etmIndex = proxy->mapToSource(etmIndex); + proxy = qobject_cast(etmIndex.model()); + } + + // Saving the old document, if it was changed + bool newDocument = d->index.isValid() && (d->index != etmIndex); if (newDocument) { savePage(); } - d->index = QPersistentModelIndex(index); + + d->model = const_cast(etmIndex.model()); + d->index = QPersistentModelIndex(etmIndex); // Loading document auto *doc = d->index.data(KJotsModel::DocumentRole).value(); if (!doc) { setReadOnly(true); return false; } disconnect(document(), &QTextDocument::modificationChanged, this, &KJotsEdit::documentModified); setDocument(doc); connect(doc, &QTextDocument::modificationChanged, this, &KJotsEdit::documentModified); // Setting cursor auto cursor = doc->property("textCursor").value(); if (!cursor.isNull()) { setTextCursor(cursor); } else { // This is a work-around for QTextEdit bug. If the first letter of the document is formatted, // QTextCursor doesn't follow this format. One can either move the cursor 1 symbol to the right // and then 1 symbol to the left as a workaround, or just explicitly move it to the start. // Submitted to qt-bugs, id 192886. // -- (don't know the fate of this bug, as for April 2020 it is inaccessible) moveCursor(QTextCursor::Start); } // Setting focus if document was changed if (newDocument) { setFocus(); } // Setting ReadOnly auto item = d->index.data(EntityTreeModel::ItemRole).value(); if (!item.isValid()) { setReadOnly(true); return false; } else if (item.hasAttribute()) { setReadOnly(true); return true; } else { setReadOnly(false); return true; } } void KJotsEdit::onAutoBullet() { KTextEdit::AutoFormatting currentFormatting = autoFormatting(); //TODO: set line spacing properly. if (currentFormatting == KTextEdit::AutoBulletList) { setAutoFormatting(KTextEdit::AutoNone); d->action_auto_bullet->setChecked(false); } else { setAutoFormatting(KTextEdit::AutoBulletList); d->action_auto_bullet->setChecked(true); } } void KJotsEdit::createAutoDecimalList() { //this is an adaptation of Qt's createAutoBulletList() function for creating a bulleted list, except in this case I use it to create a decimal list. QTextCursor cursor = textCursor(); cursor.beginEditBlock(); QTextBlockFormat blockFmt = cursor.blockFormat(); QTextListFormat listFmt; listFmt.setStyle(QTextListFormat::ListDecimal); listFmt.setIndent(blockFmt.indent() + 1); blockFmt.setIndent(0); cursor.setBlockFormat(blockFmt); cursor.createList(listFmt); cursor.endEditBlock(); setTextCursor(cursor); } void KJotsEdit::DecimalList() { QTextCursor cursor = textCursor(); if (cursor.currentList()) { return; } QString blockText = cursor.block().text(); if (blockText.length() == 2 && blockText == QLatin1String("1.")) { cursor.movePosition(QTextCursor::StartOfLine, QTextCursor::KeepAnchor); cursor.removeSelectedText(); createAutoDecimalList(); } } void KJotsEdit::onAutoDecimal() { if (allowAutoDecimal) { allowAutoDecimal = false; disconnect(this, &KJotsEdit::textChanged, this, &KJotsEdit::DecimalList); d->action_auto_decimal->setChecked(false); } else { allowAutoDecimal = true; connect(this, &KJotsEdit::textChanged, this, &KJotsEdit::DecimalList); d->action_auto_decimal->setChecked(true); } } void KJotsEdit::onLinkify() { // Nothing is yet opened, ignoring if (!d->index.isValid()) { return; } composerControler()->selectLinkText(); auto linkDialog = std::make_unique(const_cast(d->index.model()), this); linkDialog->setLinkText(composerControler()->currentLinkText()); linkDialog->setLinkUrl(composerControler()->currentLinkUrl()); if (linkDialog->exec()) { composerControler()->updateLink(linkDialog->linkUrl(), linkDialog->linkText()); } } void KJotsEdit::copySelectionIntoTitle() { if (!d->index.isValid()) { return; } const QString newTitle(textCursor().selectedText()); - auto *model = const_cast(d->index.model()); - model->setData(d->index, newTitle); + d->model->setData(d->index, newTitle); } bool KJotsEdit::canInsertFromMimeData(const QMimeData *source) const { if (source->hasUrls()) { return true; } else { return RichTextComposer::canInsertFromMimeData(source); } } void KJotsEdit::insertFromMimeData(const QMimeData *source) { // Nothing is opened, ignoring if (!d->index.isValid()) { return; } if (source->hasUrls()) { const QList urls = source->urls(); for (const QUrl &url : urls) { if (url.scheme() == QStringLiteral("akonadi")) { - QModelIndex idx = KJotsModel::modelIndexForUrl(d->index.model(), url); + QModelIndex idx = KJotsModel::modelIndexForUrl(d->model, url); if (idx.isValid()) { insertHtml(QStringLiteral("%2").arg(idx.data(KJotsModel::EntityUrlRole).toString(), idx.data().toString())); } } else { QString text = source->hasText() ? source->text() : url.toString(QUrl::RemovePassword); insertHtml(QStringLiteral("%2").arg(QString::fromUtf8(url.toEncoded()), text)); } } } else if (source->hasHtml()) { // Don't have an action to set top and bottom margins on paragraphs yet. // Remove the margins for all inserted html. QTextDocument dummy; dummy.setHtml(source->html()); QTextCursor c(&dummy); QTextBlockFormat fmt = c.blockFormat(); fmt.setTopMargin(0); fmt.setBottomMargin(0); fmt.setLeftMargin(0); fmt.setRightMargin(0); c.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); c.mergeBlockFormat(fmt); textCursor().insertFragment(QTextDocumentFragment(c)); ensureCursorVisible(); } else { RichTextComposer::insertFromMimeData(source); } } void KJotsEdit::mouseMoveEvent(QMouseEvent *event) { if ((event->modifiers() & Qt::ControlModifier) && !anchorAt(event->pos()).isEmpty()) { if (!m_cursorChanged) { QApplication::setOverrideCursor(Qt::PointingHandCursor); m_cursorChanged = true; } } else { if (m_cursorChanged) { QApplication::restoreOverrideCursor(); m_cursorChanged = false; } } RichTextComposer::mouseMoveEvent(event); } void KJotsEdit::leaveEvent(QEvent *event) { if (m_cursorChanged) { QApplication::restoreOverrideCursor(); m_cursorChanged = false; } RichTextComposer::leaveEvent(event); } void KJotsEdit::mousePressEvent(QMouseEvent *event) { QUrl url = anchorAt(event->pos()); if ((event->modifiers() & Qt::ControlModifier) && (event->button() & Qt::LeftButton) && !url.isEmpty()) { Q_EMIT linkClicked(url); } else { RichTextComposer::mousePressEvent(event); } } bool KJotsEdit::event(QEvent *event) { if (event->type() == QEvent::WindowDeactivate) { savePage(); } else if (event->type() == QEvent::ToolTip) { tooltipEvent(static_cast(event)); } return RichTextComposer::event(event); } void KJotsEdit::tooltipEvent(QHelpEvent *event) { // Nothing is opened, ignoring if (!d->index.isValid()) { return; } QUrl url(anchorAt(event->pos())); QString message; if (url.isValid()) { if (url.scheme() == QStringLiteral("akonadi")) { - const QModelIndex idx = KJotsModel::modelIndexForUrl(d->index.model(), url); + const QModelIndex idx = KJotsModel::modelIndexForUrl(d->model, url); if (idx.data(EntityTreeModel::ItemRole).value().isValid()) { message = i18nc("@info:tooltip %1 is a full path to note (i.e. Notes / Notebook / Note)", "Ctrl+click to open note: %1", KJotsModel::itemPath(idx)); } else if (idx.data(EntityTreeModel::CollectionRole).value().isValid()) { message = i18nc("@info:tooltip %1 is a full path to book (i.e. Notes / Notebook)", "Ctrl+click to open book: %1", KJotsModel::itemPath(idx)); } } else { message = i18nc("@info:tooltip %1 is hyperlink address", "Ctrl+click to follow the hyperlink: %1", url.toString(QUrl::RemovePassword)); } } if (!message.isEmpty()) { QToolTip::showText(event->globalPos(), message); } else { QToolTip::hideText(); } } void KJotsEdit::focusOutEvent(QFocusEvent *event) { savePage(); RichTextComposer::focusOutEvent(event); } void KJotsEdit::prepareDocumentForSaving() { document()->setModified(false); document()->setProperty("textCursor", QVariant::fromValue(textCursor())); document()->setProperty("images", QVariant::fromValue(composerControler()->composerImages()->embeddedImages())); } void KJotsEdit::savePage() { if (!document()->isModified() || !d->index.isValid()) { return; } prepareDocumentForSaving(); - auto *model = const_cast(d->index.model()); - model->setData(d->index, QVariant::fromValue(document()), KJotsModel::DocumentRole); + d->model->setData(d->index, QVariant::fromValue(document()), KJotsModel::DocumentRole); } /* ex: set tabstop=4 softtabstop=4 shiftwidth=4 expandtab: */ diff --git a/src/kjotsedit.h b/src/kjotsedit.h index fda9a67..14236ae 100644 --- a/src/kjotsedit.h +++ b/src/kjotsedit.h @@ -1,108 +1,113 @@ /* 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-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. */ #ifndef KJOTSEDIT_H #define KJOTSEDIT_H #include #include +class QAbstractItemModel; class QItemSelection; class QItemSelectionModel; class KActionCollection; +class KJotsModel; class KJotsEdit : public KPIMTextEdit::RichTextComposer { Q_OBJECT public: explicit KJotsEdit(QWidget *parent, KActionCollection *m_actionCollection); ~KJotsEdit(); bool canInsertFromMimeData(const QMimeData *) const override; void insertFromMimeData(const QMimeData *) override; + /** - * Load document based on KJotsModel index + * Load document based on @p index + * Index can belong to some proxy model, in that case + * it will be mapped to the ETM * * @returns true if loaded successfully */ bool setModelIndex(const QModelIndex &index); /** * Returns the current modified state of the document */ bool modified(); /** * Prepares document for saving. * * Currently it includes providing it with embedded images * Note: savePage calls it explicitly */ void prepareDocumentForSaving(); void createActions(KActionCollection *ac); void setEnableActions(bool enable); protected: /* To be able to change cursor when hovering over links */ void mouseMoveEvent(QMouseEvent *event) override; void leaveEvent(QEvent *event) override; /** Override to make ctrl+click follow links */ void mousePressEvent(QMouseEvent *) override; void contextMenuEvent(QContextMenuEvent *event) override; void focusOutEvent(QFocusEvent *) override; bool event(QEvent *event) override; void tooltipEvent(QHelpEvent *event); public Q_SLOTS: void onAutoBullet(); void onLinkify(void); void onAutoDecimal(void); void DecimalList(void); void savePage(); void insertDate(); void copySelectionIntoTitle(); Q_SIGNALS: void linkClicked(const QUrl &url); void documentModified(bool modified); private: class Private; std::unique_ptr const d; void createAutoDecimalList(); KActionCollection *m_actionCollection; bool allowAutoDecimal; bool m_cursorChanged = false; }; #endif // __KJOTSEDIT_H /* ex: set tabstop=4 softtabstop=4 shiftwidth=4 expandtab: */ diff --git a/src/kjotswidget.cpp b/src/kjotswidget.cpp index cff523e..027b8b3 100644 --- a/src/kjotswidget.cpp +++ b/src/kjotswidget.cpp @@ -1,757 +1,772 @@ /* 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 // Akonadi #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // 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 "kjotsconfigdlg.h" #include "KJotsSettings.h" #include "kjotsbrowser.h" #include "noteshared/notelockattribute.h" #include "noteshared/standardnoteactionmanager.h" #include using namespace Akonadi; using namespace Grantlee; KJotsWidget::KJotsWidget(QWidget *parent, KXMLGUIClient *xmlGuiClient, Qt::WindowFlags f) : QWidget(parent, f) , m_xmlGuiClient(xmlGuiClient) { ControlGui::widgetNeedsAkonadi(this); // Grantlee m_loader = QSharedPointer(new FileSystemTemplateLoader()); m_loader->setTemplateDirs(QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("kjots/themes"), QStandardPaths::LocateDirectory)); m_loader->setTheme(QStringLiteral("default")); m_templateEngine = new Engine(this); m_templateEngine->addTemplateLoader(m_loader); // GUI & Actions setupGui(); setupActions(); // Models 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 >(); auto *monitor = new ChangeRecorder(this); monitor->fetchCollection(true); monitor->setItemFetchScope(scope); monitor->setCollectionMonitored(Collection::root()); monitor->setMimeTypeMonitored(NoteUtils::noteMimeType()); m_kjotsModel = new KJotsModel(monitor, this); + m_browserWidget->browser()->setModel(m_kjotsModel); + m_collectionModel = new EntityMimeTypeFilterModel(this); m_collectionModel->setSourceModel(m_kjotsModel); m_collectionModel->addMimeTypeInclusionFilter(Collection::mimeType()); m_collectionModel->setHeaderGroup(EntityTreeModel::CollectionTreeHeaders); m_collectionModel->setDynamicSortFilter(true); m_collectionModel->setSortCaseSensitivity(Qt::CaseInsensitive); m_orderProxy = new EntityOrderProxyModel(this); m_orderProxy->setSourceModel(m_collectionModel); KConfigGroup cfg(KSharedConfig::openConfig(), "KJotsEntityOrder"); m_orderProxy->setOrderConfig(cfg); m_collectionView->setModel(m_orderProxy); m_collectionSelectionProxyModel = new KSelectionProxyModel(m_collectionView->selectionModel(), this); m_collectionSelectionProxyModel->setSourceModel(m_kjotsModel); m_collectionSelectionProxyModel->setFilterBehavior(KSelectionProxyModel::ChildrenOfExactSelection); m_itemModel = new EntityMimeTypeFilterModel(this); m_itemModel->setSourceModel(m_collectionSelectionProxyModel); m_itemModel->addMimeTypeExclusionFilter(Collection::mimeType()); m_itemModel->setHeaderGroup(EntityTreeModel::ItemListHeaders); m_itemModel->setSortRole(Qt::EditRole); m_itemView->setModel(m_itemModel); m_actionManager->setCollectionSelectionModel(m_collectionView->selectionModel()); m_actionManager->setItemSelectionModel(m_itemView->selectionModel()); connect(m_kjotsModel, &EntityTreeModel::modelAboutToBeReset, this, &KJotsWidget::saveState); connect(m_kjotsModel, &EntityTreeModel::modelReset, this, &KJotsWidget::restoreState); // TODO: handle dataChanged properly, i.e. if item was changed from outside connect(m_collectionView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &KJotsWidget::renderSelection); connect(m_collectionView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &KJotsWidget::updateMenu); connect(m_collectionView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &KJotsWidget::updateCaption); connect(m_collectionView->model(), &QAbstractItemModel::dataChanged, this, &KJotsWidget::renderSelection); connect(m_collectionView->model(), &QAbstractItemModel::dataChanged, this, &KJotsWidget::updateCaption); connect(m_itemView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &KJotsWidget::renderSelection); connect(m_itemView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &KJotsWidget::updateMenu); connect(m_itemView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &KJotsWidget::updateCaption); connect(m_itemView->model(), &QAbstractItemModel::dataChanged, this, &KJotsWidget::renderSelection); connect(m_itemView->model(), &QAbstractItemModel::dataChanged, this, &KJotsWidget::updateCaption); connect(m_editor, &KJotsEdit::documentModified, this, &KJotsWidget::updateCaption); QTimer::singleShot(0, this, &KJotsWidget::delayedInitialization); // Autosave timer m_autosaveTimer = new QTimer(this); updateConfiguration(); connect(m_autosaveTimer, &QTimer::timeout, m_editor, &KJotsEdit::savePage); connect(m_collectionView->selectionModel(), &QItemSelectionModel::selectionChanged, m_autosaveTimer, qOverload<>(&QTimer::start)); restoreState(); QDBusConnection::sessionBus().registerObject(QStringLiteral("/KJotsWidget"), this, QDBusConnection::ExportScriptableContents); } KJotsWidget::~KJotsWidget() { saveState(); } void KJotsWidget::setupGui() { // Main horizontal layout auto *layout = new QHBoxLayout(this); layout->setMargin(0); // Splitter between (collection view) and (item view + editor) m_splitter1 = new QSplitter(this); m_splitter1->setObjectName(QStringLiteral("CollectionSplitter")); m_splitter1->setStretchFactor(1, 1); layout->addWidget(m_splitter1); // Collection view m_collectionView = new EntityTreeView(m_xmlGuiClient, m_splitter1); m_collectionView->setSelectionMode(QAbstractItemView::ExtendedSelection); m_collectionView->setEditTriggers(QAbstractItemView::DoubleClicked); m_collectionView->setManualSortingActive(true); m_collectionView->header()->setDefaultAlignment(Qt::AlignCenter); // Splitter between item view and editor m_splitter2 = new QSplitter(m_splitter1); m_splitter2->setObjectName(QStringLiteral("EditorSplitter")); // Item view m_itemView = new EntityTreeView(m_xmlGuiClient, m_splitter2); m_itemView->setSelectionMode(QAbstractItemView::ExtendedSelection); + m_itemView->setSelectionBehavior(QAbstractItemView::SelectRows); m_itemView->setEditTriggers(QAbstractItemView::DoubleClicked); m_itemView->setRootIsDecorated(false); m_itemView->header()->setDefaultAlignment(Qt::AlignCenter); // Stacked widget containing editor & browser m_stackedWidget = new QStackedWidget(m_splitter2); // Editor m_editor = new KJotsEdit(m_stackedWidget, m_xmlGuiClient->actionCollection()); m_editorWidget = new KPIMTextEdit::RichTextEditorWidget(m_editor, m_stackedWidget); m_editor->setParent(m_editorWidget); m_stackedWidget->addWidget(m_editorWidget); connect(m_editor, &KJotsEdit::linkClicked, this, &KJotsWidget::openLink); // Browser - m_browserWidget = new KJotsBrowserWidget(std::make_unique(m_kjotsModel, m_xmlGuiClient->actionCollection()), + m_browserWidget = new KJotsBrowserWidget(std::make_unique(m_xmlGuiClient->actionCollection()), m_stackedWidget); m_stackedWidget->addWidget(m_browserWidget); m_stackedWidget->setCurrentWidget(m_browserWidget); connect(m_browserWidget->browser(), &KJotsBrowser::linkClicked, this, &KJotsWidget::openLink); // Make sure the editor gets focus again after naming a new book/page. connect(m_collectionView->itemDelegate(), &QItemDelegate::closeEditor, this, [this](){ activeEditor()->setFocus(); }); } void KJotsWidget::restoreState() { { auto *saver = new ETMViewStateSaver; saver->setView(m_collectionView); KConfigGroup cfg(KSharedConfig::openConfig(), "CollectionViewState"); saver->restoreState(cfg); } { auto *saver = new ETMViewStateSaver; saver->setView(m_itemView); KConfigGroup cfg(KSharedConfig::openConfig(), "ItemViewState"); saver->restoreState(cfg); } } void KJotsWidget::saveState() { { ETMViewStateSaver saver; saver.setView(m_collectionView); KConfigGroup cfg(KSharedConfig::openConfig(), "CollectionViewState"); saver.saveState(cfg); cfg.sync(); } { ETMViewStateSaver saver; saver.setView(m_itemView); KConfigGroup cfg(KSharedConfig::openConfig(), "ItemViewState"); saver.saveState(cfg); cfg.sync(); } } void KJotsWidget::saveSplitterStates() const { const QString groupName = QStringLiteral("UiState_MainWidgetSplitter_%1").arg(KJotsSettings::viewMode()); KConfigGroup group(KSharedConfig::openConfig(), groupName); KPIM::UiStateSaver::saveState(m_splitter1, group); KPIM::UiStateSaver::saveState(m_splitter2, group); } void KJotsWidget::restoreSplitterStates() { const QString groupName = QStringLiteral("UiState_MainWidgetSplitter_%1").arg(KJotsSettings::viewMode()); KConfigGroup group(KSharedConfig::openConfig(), groupName); KPIM::UiStateSaver::restoreState(m_splitter1, group); KPIM::UiStateSaver::restoreState(m_splitter2, group); } void KJotsWidget::setViewMode(int mode) { const int newMode = (mode == 0) ? KJotsSettings::viewMode() : mode; m_splitter2->setOrientation(newMode == 1 ? Qt::Vertical : Qt::Horizontal); if (mode != 0) { KJotsSettings::setViewMode(mode); saveSplitterStates(); } restoreSplitterStates(); m_viewModeGroup->actions().at(newMode-1)->setChecked(true); } void KJotsWidget::setupActions() { KActionCollection *actionCollection = m_xmlGuiClient->actionCollection(); // Default Akonadi Notes actions m_actionManager = new StandardNoteActionManager(actionCollection, this); m_actionManager->createAllActions(); actionCollection->setDefaultShortcut(m_actionManager->action(StandardActionManager::CreateCollection), QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_N)); QAction *action; // Standard actions KStandardAction::preferences(this, &KJotsWidget::configure, actionCollection); KStandardAction::save( m_editor, &KJotsEdit::savePage, actionCollection); action = KStandardAction::next(this, [this](){ - m_itemView->selectionModel()->select(previousNextEntity(m_itemView, +1), QItemSelectionModel::ClearAndSelect); + m_itemView->selectionModel()->select(previousNextEntity(m_itemView, +1), QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); }, actionCollection); actionCollection->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_PageDown)); connect(this, &KJotsWidget::canGoNextPageChanged, action, &QAction::setEnabled); action = KStandardAction::prior(this, [this](){ - m_itemView->selectionModel()->select(previousNextEntity(m_itemView, -1), QItemSelectionModel::ClearAndSelect); + m_itemView->selectionModel()->select(previousNextEntity(m_itemView, -1), QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); }, actionCollection); actionCollection->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_PageUp)); connect(this, &KJotsWidget::canGoPreviousPageChanged, action, &QAction::setEnabled); KStandardAction::renameFile(this, [this](){ EntityTreeView *activeView; if (m_collectionView->hasFocus()) { activeView = m_collectionView; } else { activeView = m_itemView; } const QModelIndexList rows = activeView->selectionModel()->selectedRows(); if (rows.size() != 1) { return; } activeView->edit(rows.first()); }, actionCollection); action = KStandardAction::deleteFile(this, [this](){ if (m_collectionView->hasFocus()) { m_actionManager->action(StandardActionManager::DeleteCollections)->trigger(); } else { m_actionManager->action(StandardActionManager::DeleteItems)->trigger(); } }, actionCollection); action = KStandardAction::cut(m_editor, &KJotsEdit::cut, actionCollection); connect(m_editor, &KJotsEdit::copyAvailable, action, &QAction::setEnabled); action->setEnabled(false); action = KStandardAction::copy(this, [this](){ activeEditor()->copy(); }, actionCollection); connect(m_editor, &KJotsEdit::copyAvailable, action, &QAction::setEnabled); connect(m_browserWidget->browser(), &KJotsBrowser::copyAvailable, action, &QAction::setEnabled); action->setEnabled(false); KStandardAction::paste(m_editor, &KJotsEdit::paste, actionCollection); KStandardAction::undo(m_editor, &KJotsEdit::undo, actionCollection); KStandardAction::redo(m_editor, &KJotsEdit::redo, actionCollection); KStandardAction::selectAll(m_editor, &KJotsEdit::selectAll, actionCollection); KStandardAction::find(this, [this](){ if (m_editorWidget->isVisible()) { m_editorWidget->slotFind(); } else { m_browserWidget->slotFind(); } }, actionCollection); KStandardAction::findNext(this, [this](){ if (m_editorWidget->isVisible()) { m_editorWidget->slotFindNext(); } else { m_browserWidget->slotFindNext(); } }, actionCollection); KStandardAction::replace(this, [this](){ if (m_editorWidget->isVisible()) { m_editorWidget->slotReplace(); } }, actionCollection); KStandardAction::print(this, &KJotsWidget::printSelection, actionCollection); KStandardAction::printPreview(this, &KJotsWidget::printPreviewSelection, actionCollection); // Bookmarks actions auto *bookmarkMenu = actionCollection->add(QStringLiteral("bookmarks")); bookmarkMenu->setText(i18n("&Bookmarks")); auto *bookmarks = new KJotsBookmarks(m_collectionView->selectionModel(), this); connect(bookmarks, &KJotsBookmarks::openLink, this, &KJotsWidget::openLink); auto *bmm = new KBookmarkMenu( KBookmarkManager::managerForFile( QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + 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. action = bmm->addBookmarkAction(); actionCollection->addAction(QStringLiteral("add_bookmark"), action); actionCollection->setDefaultShortcut(action, Qt::CTRL | Qt::SHIFT | Qt::Key_B); actionCollection->addAction(QStringLiteral("edit_bookmark"), bmm->editBookmarksAction()); actionCollection->addAction(QStringLiteral("add_bookmarks_list"), bmm->bookmarkTabsAsFolderAction()); // Export actions auto *exportMenu = actionCollection->add(QStringLiteral("save_to")); exportMenu->setText(i18n("Export")); exportMenu->setIcon(QIcon::fromTheme(QStringLiteral("document-export"))); action = actionCollection->addAction(QStringLiteral("save_to_ascii")); action->setText(i18n("To Text File...")); action->setIcon(QIcon::fromTheme(QStringLiteral("text-plain"))); connect(action, &QAction::triggered, this, [this](){ exportSelection(QStringLiteral("plain_text"), QStringLiteral("template.txt")); }); exportMenu->menu()->addAction(action); action = actionCollection->addAction(QStringLiteral("save_to_html")); action->setText(i18n("To HTML File...")); action->setIcon(QIcon::fromTheme(QStringLiteral("text-html"))); connect(action, &QAction::triggered, this, [this](){ exportSelection(QStringLiteral("default"), QStringLiteral("template.html")); }); exportMenu->menu()->addAction(action); // Move actions action = actionCollection->addAction(QStringLiteral("go_next_book")); action->setText(i18n("Next Book")); action->setIcon(QIcon::fromTheme(QStringLiteral("go-down"))); actionCollection->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_PageDown)); connect(action, &QAction::triggered, this, [this](){ const QModelIndex idx = previousNextEntity(m_collectionView, +1); m_collectionView->selectionModel()->select(idx, QItemSelectionModel::SelectCurrent); m_collectionView->expand(idx); }); connect(this, &KJotsWidget::canGoNextBookChanged, action, &QAction::setEnabled); action = actionCollection->addAction(QStringLiteral("go_prev_book")); action->setText(i18n("Previous Book")); action->setIcon(QIcon::fromTheme(QStringLiteral("go-up"))); actionCollection->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_PageUp)); connect(action, &QAction::triggered, this, [this](){ const QModelIndex idx = previousNextEntity(m_collectionView, -1); m_collectionView->selectionModel()->select(idx, QItemSelectionModel::SelectCurrent); m_collectionView->expand(idx); }); connect(this, &KJotsWidget::canGoPreviousBookChanged, action, &QAction::setEnabled); // View mode actions m_viewModeGroup = new QActionGroup(this); action = new QAction(i18nc("@action:inmenu", "Two Columns"), m_viewModeGroup); action->setCheckable(true); action->setData(1); actionCollection->addAction(QStringLiteral("view_mode_two_columns"), action); action = new QAction(i18nc("@action:inmenu", "Three Columns"), m_viewModeGroup); action->setCheckable(true); action->setData(2); actionCollection->addAction(QStringLiteral("view_mode_three_columns"), action); connect(m_viewModeGroup, &QActionGroup::triggered, this, [this](QAction *action){ setViewMode(action->data().toInt()); }); } void KJotsWidget::delayedInitialization() { // anySelectionActions are available when at least something is selected (i.e. editor/browser are not empty // editorActions are only available when there is a single selection, i.e. when the editor is visible KActionCollection *actionCollection = m_xmlGuiClient->actionCollection(); // Actions for a single item selection. anySelectionActions = { actionCollection->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Find))), actionCollection->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Print))), actionCollection->action(QStringLiteral("save_to")) }; // Actions that are used only when a note is selected. editorActions = { actionCollection->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Cut))), actionCollection->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Paste))), actionCollection->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Replace))), actionCollection->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Save))) }; updateMenu(); // Load view mode and splitters setViewMode(0); } inline QTextEdit *KJotsWidget::activeEditor() { if (m_browserWidget->isVisible()) { return m_browserWidget->browser(); } else { return m_editor; } } void KJotsWidget::updateMenu() { const int collectionsSelected = m_collectionView->selectionModel()->selectedRows().count(); const int itemsSelected = m_itemView->selectionModel()->selectedRows().count(); const int selectionSize = itemsSelected + collectionsSelected; // Actions available only when editor is shown m_editor->setEnableActions(itemsSelected == 1); for (QAction *action : qAsConst(editorActions)) { action->setEnabled(itemsSelected == 1); } // Rename is available only when single something is selected m_xmlGuiClient->actionCollection() ->action(QString::fromLatin1(KStandardAction::name(KStandardAction::RenameFile))) ->setEnabled((itemsSelected == 1) || (m_collectionView->hasFocus() && collectionsSelected == 1)); // Actions available when at least something is shown for (QAction *action : qAsConst(anySelectionActions)) { action->setEnabled(selectionSize >= 1); } } void KJotsWidget::configure() { if (KConfigDialog::showDialog(QStringLiteral("kjotssettings"))) { return; } auto* dialog = new KConfigDialog(this, QStringLiteral("kjotssettings"), KJotsSettings::self()); dialog->addPage(new KJotsConfigMisc(dialog), i18nc("@title:window config dialog page", "Misc"), QStringLiteral("preferences-other")); connect(dialog, &KConfigDialog::settingsChanged, this, &KJotsWidget::updateConfiguration); dialog->show(); } void KJotsWidget::updateConfiguration() { if (KJotsSettings::autoSave()) { m_autosaveTimer->setInterval(KJotsSettings::autoSaveInterval() * 1000 * 60); m_autosaveTimer->start(); } else { m_autosaveTimer->stop(); } } QString KJotsWidget::renderSelectionTo(const QString &theme, const QString &templ) { QList objectList; const QModelIndexList selectedItems = m_itemView->selectionModel()->selectedRows(); if (selectedItems.count() > 0) { objectList.reserve(selectedItems.size()); std::transform(selectedItems.cbegin(), selectedItems.cend(), std::back_inserter(objectList), [](const QModelIndex &idx){ return idx.data(KJotsModel::GrantleeObjectRole); }); } else { const QModelIndexList selectedCollections = m_collectionView->selectionModel()->selectedRows(); objectList.reserve(selectedCollections.size()); std::transform(selectedCollections.cbegin(), selectedCollections.cend(), std::back_inserter(objectList), [](const QModelIndex &idx){ return idx.data(KJotsModel::GrantleeObjectRole); }); } QHash hash = {{QStringLiteral("entities"), objectList}, {QStringLiteral("i18n_TABLE_OF_CONTENTS"), i18nc("Header for 'Table of contents' section of rendered output", "Table of contents")}}; Context c(hash); const QString currentTheme = m_loader->themeName(); m_loader->setTheme(theme); Template t = m_templateEngine->loadByName(templ); const QString result = t->render(&c); m_loader->setTheme(currentTheme); return result; } QString KJotsWidget::renderSelectionToHtml() { return renderSelectionTo(QStringLiteral("default"), QStringLiteral("template.html")); } void KJotsWidget::exportSelection(const QString &theme, const QString &templ) { // TODO: dialog captions & etc QString filename = QFileDialog::getSaveFileName(); if (filename.isEmpty()) { return; } QFile exportFile(filename); if (!exportFile.open(QIODevice::WriteOnly | QIODevice::Text)) { KMessageBox::error(this, i18n("Could not open \"%1\" for writing", filename)); return; } exportFile.write(renderSelectionTo(theme, templ).toUtf8()); exportFile.close(); } 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(QStringLiteral("default")); printDocument.setHtml(renderSelectionToHtml()); m_loader->setTheme(currentTheme); } printDocument.print(printer); } QModelIndex KJotsWidget::previousNextEntity(QTreeView *view, int step) { const QModelIndexList selection = view->selectionModel()->selectedRows(); if (selection.size() == 0) { return step > 0 ? view->model()->index(0, 0) : view->model()->index(view->model()->rowCount()-1, 0); } if (selection.size() != 1) { return {}; } return step > 0 ? view->indexBelow(selection.first()) : view->indexAbove(selection.first()); } void KJotsWidget::renderSelection() { Q_EMIT canGoNextBookChanged(previousNextEntity(m_collectionView, +1).isValid()); Q_EMIT canGoNextPageChanged(previousNextEntity(m_itemView, +1).isValid()); Q_EMIT canGoPreviousBookChanged(previousNextEntity(m_collectionView, -1).isValid()); Q_EMIT canGoPreviousPageChanged(previousNextEntity(m_itemView, -1).isValid()); const QModelIndexList selectedItems = m_itemView->selectionModel()->selectedRows(); // If the selection is a single note, present it for editing... if (selectedItems.count() == 1) { if (m_editor->setModelIndex(selectedItems.first())) { m_stackedWidget->setCurrentWidget(m_editorWidget); return; } // If something went wrong, we show user the browser } // ... Otherwise, render the selection read-only. m_browserWidget->browser()->setHtml(renderSelectionToHtml()); m_stackedWidget->setCurrentWidget(m_browserWidget); } void KJotsWidget::updateCaption() { const QModelIndexList selection = m_collectionView->selectionModel()->selectedRows(); QString caption; if (selection.size() == 1) { caption = KJotsModel::itemPath(selection.first()); if (m_editor->modified()) { caption.append(QStringLiteral(" *")); } } else if (selection.size() > 1) { caption = i18nc("@title:window", "Multiple selection"); } Q_EMIT captionChanged(caption); } bool KJotsWidget::queryClose() { // Saving the current note // We cannot use async interface (i.e. ETM) here // because we need to abort the close if something went wrong - if ((m_collectionSelectionProxyModel->rowCount() == 1) && (m_editor->document()->isModified())) { - QModelIndex idx = m_collectionSelectionProxyModel->mapToSource(m_collectionSelectionProxyModel->index(0, 0, QModelIndex())); + const QModelIndexList selection = m_itemView->selectionModel()->selectedRows(); + if ((selection.size() == 1) && (m_editor->document()->isModified())) { + QModelIndex idx = selection.first(); m_editor->prepareDocumentForSaving(); auto job = new ItemModifyJob(KJotsModel::updateItem(idx.data(EntityTreeModel::ItemRole).value(), m_editor->document())); if (!job->exec()) { int res = KMessageBox::warningContinueCancelDetailed(this, i18n("Unable to save the note.\n" "You can save your note to a local file using the \"File - Export\" menu,\n" "otherwise you will lose your changes!\n" "Do you want to close anyways?"), i18n("Close Document"), KStandardGuiItem::quit(), KStandardGuiItem::cancel(), QString(), KMessageBox::Notify, i18n("Error message:\n" "%1", job->errorString())); if (res == KMessageBox::Cancel) { return false; } - } else { - // Saved successfully. - // However, KJotsEdit will still catch focusOutEvent and try saving using async interface - // (application will quit soon, so it doesn't really make much sense doing it) - // Marking the document as saved explicitly it order to avoid it - m_editor->document()->setModified(false); } - } saveSplitterStates(); KJotsSettings::self()->save(); m_orderProxy->saveOrder(); return true; } void KJotsWidget::openLink(const QUrl &url) { if (url.scheme() == QStringLiteral("akonadi")) { - m_collectionView->selectionModel()->select(KJotsModel::modelIndexForUrl(m_collectionView->model(), url), QItemSelectionModel::ClearAndSelect); + QModelIndex idx = KJotsModel::modelIndexForUrl(m_kjotsModel, url); + + // Trying to map it to collection view model + QModelIndex colIdx = m_collectionModel->mapFromSource(idx); + if (colIdx.isValid()) { + colIdx = m_orderProxy->mapFromSource(colIdx); + m_collectionView->selectionModel()->select(colIdx, QItemSelectionModel::SelectCurrent); + m_itemView->selectionModel()->clearSelection(); + } else { + // Selecting parent collection + QModelIndex parentCollectionIdx = EntityTreeModel::modelIndexForCollection(m_collectionView->model(), + idx.data(EntityTreeModel::ParentCollectionRole).value()); + m_collectionView->selectionModel()->select(parentCollectionIdx, QItemSelectionModel::SelectCurrent); + + // Mapping idx to item view model + idx = m_collectionSelectionProxyModel->mapFromSource(idx); + idx = m_itemModel->mapFromSource(idx); + m_itemView->selectionModel()->select(idx, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); + } } else { new KRun(url, this); } }