diff --git a/CMakeLists.txt b/CMakeLists.txt index 3ab7a14..7ddf4e2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,71 +1,73 @@ project(kjots) cmake_minimum_required(VERSION 2.8.12) find_package(ECM 5.16.0 REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/modules ${ECM_MODULE_PATH}) set(CMAKE_CXX_STANDARD 14) include(ECMAddTests) include(CMakePackageConfigHelpers) include(FeatureSummary) include(KDEInstallDirs) include(KDECMakeSettings) include(KDECompilerSettings NO_POLICY_SCOPE) include(ECMInstallIcons) include(ECMQtDeclareLoggingCategory) include(ECMAddAppIcon) set(KJOTS_VERSION "5.0.2") set(KF5_VERSION "5.70.0") set(AKONADINOTES_LIB_VERSION "5.14.41") set(KDEPIMLIBS_LIB_VERSION "5.1.0") set(KMIME_LIB_VERSION "4.87.0") set(KPIMTEXTEDIT_LIB_VERSION "5.14.44") set(KONTACTINTERFACE_LIB_VERSION "4.82.0") +set(LIBKDEPIM_LIB_VERSION "5.14.1") #Qt Packages set(QT_REQUIRED_VERSION "5.6.0") find_package(Qt5 ${QT_REQUIRED_VERSION} CONFIG REQUIRED DBus PrintSupport) # KF5 Packages find_package(KF5KCMUtils ${KF5_VERSION} CONFIG REQUIRED) find_package(KF5KIO ${KF5_VERSION} CONFIG REQUIRED) find_package(KF5Config ${KF5_VERSION} CONFIG REQUIRED) find_package(KF5ConfigWidgets ${KF5_VERSION} CONFIG REQUIRED) find_package(KF5Parts ${KF5_VERSION} CONFIG REQUIRED) find_package(KF5Bookmarks ${KF5_VERSION} CONFIG REQUIRED) find_package(KF5XmlGui ${KF5_VERSION} CONFIG REQUIRED) # PIM packages find_package(KF5Akonadi ${KDEPIMLIBS_LIB_VERSION} CONFIG REQUIRED) find_package(KF5Mime ${KMIME_LIB_VERSION} CONFIG REQUIRED) find_package(KF5AkonadiNotes ${AKONADINOTES_LIB_VERSION} CONFIG REQUIRED) find_package(KF5PimTextEdit ${KPIMTEXTEDIT_LIB_VERSION} CONFIG REQUIRED) find_package(KF5KontactInterface ${KONTACTINTERFACE_LIB_VERSION} CONFIG REQUIRED) +find_package(KF5Libkdepim ${LIBKDEPIM_LIB_VERSION} CONFIG REQUIRED) find_package(Grantlee5 "5.0" CONFIG REQUIRED) ##################### Definitions ##################### add_definitions(-DQT_NO_KEYWORDS) add_definitions(-DQT_NO_CAST_FROM_ASCII) ##################### Targets ####################### add_subdirectory(src) add_subdirectory(icons) ##################### Install ###################### install(DIRECTORY themes DESTINATION ${DATA_INSTALL_DIR}/kjots PATTERN *.svn EXCLUDE ) install(FILES data/org.kde.kjots.appdata.xml DESTINATION ${CMAKE_INSTALL_METAINFODIR} ) install(FILES data/org.kde.kjots.desktop DESTINATION ${XDG_APPS_INSTALL_DIR} ) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3391603..074b6b2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,132 +1,132 @@ add_subdirectory(noteshared) add_subdirectory(kontact_plugin) add_definitions(-DTRANSLATION_DOMAIN=\"kjots\") configure_file(kjots-version.h.in ${CMAKE_CURRENT_BINARY_DIR}/kjots-version.h @ONLY ) ######################################################################### # Code common to the kjots application and the kontact plugin # Don't make it a static library, this code needs to be compiled # with -fPIC for the part and without -fPIC for the executable ######################################################################### set(kjots_config_SRCS kjotsconfigdlg.cpp) kconfig_add_kcfg_files(kjots_config_SRCS KJotsSettings.kcfgc ) ki18n_wrap_ui(kjots_config_SRCS confpagemisc.ui) set(kjots_common_SRCS aboutdata.cpp kjotsedit.cpp kjotsbookmarks.cpp kjotsmodel.cpp - kjotssortproxymodel.cpp kjotswidget.cpp kjotsbrowser.cpp kjotslinkdialog.cpp ${kjots_config_SRCS} ) ki18n_wrap_ui(kjots_common_SRCS linkdialog.ui) add_library(kjots_common STATIC ${kjots_common_SRCS}) target_link_libraries(kjots_common noteshared Qt5::DBus Qt5::PrintSupport KF5::KCMUtils KF5::Bookmarks KF5::ConfigWidgets KF5::TextWidgets KF5::Mime KF5::AkonadiCore KF5::AkonadiWidgets KF5::PimTextEdit KF5::XmlGui KF5::KIOWidgets + KF5::Libkdepim Grantlee5::Templates Grantlee5::TextDocument ) ######################################################################### # D-Bus interface ######################################################################### qt5_generate_dbus_interface( kjotswidget.h org.kde.KJotsWidget.xml OPTIONS -m ) qt5_add_dbus_interfaces(kjots_common_SRCS ${CMAKE_CURRENT_BINARY_DIR}/org.kde.KJotsWidget.xml) ######################################################################### # Main Application SECTION ######################################################################### set(kjots_SRCS main.cpp KJotsMain.cpp ) file(GLOB ICONS_SRCS "${CMAKE_SOURCE_DIR}/icons/*-apps-kjots.png") ecm_add_app_icon(kjots_SRCS ICONS ${ICONS_SRCS}) add_executable(kjots ${kjots_SRCS}) target_link_libraries(kjots kjots_common KF5::KontactInterface ) install(TARGETS kjots ${INSTALL_TARGETS_DEFAULT_ARGS}) install(FILES kjots.kcfg DESTINATION ${KCFG_INSTALL_DIR} ) install(FILES kjotsui.rc DESTINATION ${KXMLGUI_INSTALL_DIR}/kjots ) ############## next target ############## set(kjotspart_PART_SRCS kjotspart.cpp ) add_library(kjotspart MODULE ${kjotspart_PART_SRCS}) target_link_libraries(kjotspart kjots_common KF5::Parts ) install(FILES ${CMAKE_SOURCE_DIR}/data/kjotspart.desktop DESTINATION ${SERVICES_INSTALL_DIR} ) install(TARGETS kjotspart DESTINATION ${PLUGIN_INSTALL_DIR} ) ########################################################################### set(kcm_kjots_PART_SRCS kcm_kjots.cpp ${kjots_config_SRCS} ) add_library(kcm_kjots MODULE ${kcm_kjots_PART_SRCS}) target_link_libraries(kcm_kjots KF5::KCMUtils KF5::I18n ) install(TARGETS kcm_kjots DESTINATION ${PLUGIN_INSTALL_DIR} ) install(FILES ${CMAKE_SOURCE_DIR}/data/kjots_config_misc.desktop DESTINATION ${SERVICES_INSTALL_DIR} ) diff --git a/src/kjots.kcfg b/src/kjots.kcfg index 45d0f6a..e6f7ee4 100644 --- a/src/kjots.kcfg +++ b/src/kjots.kcfg @@ -1,20 +1,24 @@ - - - true 5 + + + + + + 2 + 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..8666c8c 100644 --- a/src/kjotsedit.cpp +++ b/src/kjotsedit.cpp @@ -1,483 +1,488 @@ /* 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 "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 = KJotsModel::etmIndex(index); + + // 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..f622270 100644 --- a/src/kjotsedit.h +++ b/src/kjotsedit.h @@ -1,108 +1,111 @@ /* 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 QItemSelection; class QItemSelectionModel; class KActionCollection; 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/kjotsmodel.cpp b/src/kjotsmodel.cpp index 74880e9..f694bd1 100644 --- a/src/kjotsmodel.cpp +++ b/src/kjotsmodel.cpp @@ -1,335 +1,401 @@ /* 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 #include #include +#include +#include #include #include #include #include "noteshared/notelockattribute.h" Q_DECLARE_METATYPE(QTextDocument *) Q_DECLARE_METATYPE(KPIMTextEdit::ImageList) using namespace Akonadi; KJotsEntity::KJotsEntity(const QModelIndex &index, QObject *parent) : QObject(parent) , m_index(index) { } void KJotsEntity::setIndex(const QModelIndex &index) { m_index = QPersistentModelIndex(index); } QString KJotsEntity::title() const { return m_index.data().toString(); } QString KJotsEntity::content() const { auto *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 { auto *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::EntityUrlRole).toString(); } 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()) { auto *eda = col.attribute(); eda->setDisplayName(value.toString()); } return EntityTreeModel::setData(index, QVariant::fromValue(col), CollectionRole); } NoteUtils::NoteMessageWrapper note(item.payload()); note.setTitle(value.toString()); item.setPayload(note.message()); if (item.hasAttribute()) { auto *displayAttribute = item.attribute(); displayAttribute->setDisplayName(value.toString()); } return EntityTreeModel::setData(index, QVariant::fromValue(item), ItemRole); } if (role == KJotsModel::DocumentRole) { Item item = updateItem(index.data(ItemRole).value(), value.value()); return EntityTreeModel::setData(index, QVariant::fromValue(item), ItemRole); } return EntityTreeModel::setData(index, value, role); } QVariant KJotsModel::data(const QModelIndex &index, int role) const { if (GrantleeObjectRole == role) { auto *obj = new KJotsEntity(index); obj->setIndex(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(); } NoteUtils::NoteMessageWrapper note(item.payload()); const QString doc = note.text(); auto *document = new QTextDocument(); if (note.textFormat() == Qt::RichText || doc.startsWith(u"")) { document->setHtml(doc); } else { document->setPlainText(doc); } document->setModified(false); // Loading embedded images const QVector attachments = note.attachments(); for (const auto &attachment : attachments) { if (attachment.mimetype() == QStringLiteral("image/png") && !attachment.contentID().isEmpty()) { QImage img = QImage::fromData(attachment.data(), "PNG"); document->addResource(QTextDocument::ImageResource, QUrl(QStringLiteral("cid:")+attachment.contentID()), img); } } m_documents.insert(itemId, document); return QVariant::fromValue(document); } - if (role == Qt::DecorationRole) { + if (role == Qt::DecorationRole && index.column() == Title) { const Item item = index.data(ItemRole).value(); if (item.isValid() && item.hasAttribute()) { return QIcon::fromTheme(QStringLiteral("emblem-locked")); } const Collection col = index.data(CollectionRole).value(); if (col.isValid() && col.hasAttribute()) { return QIcon::fromTheme(QStringLiteral("emblem-locked")); } } 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()) { - NoteUtils::NoteMessageWrapper note(item.payload()); - return note.title(); + if (item.hasPayload()) { + auto message = item.payload(); + NoteUtils::NoteMessageWrapper note(message); + if (role == Qt::DisplayRole) { + switch (column) { + case Title: + return note.title(); + case ModificationTime: + return KFormat().formatRelativeDateTime(note.lastModifiedDate(), QLocale::ShortFormat); + case CreationTime: + return KFormat().formatRelativeDateTime(note.creationDate(), QLocale::ShortFormat); + case Size: + return KFormat().formatByteSize(message->storageSize()); + } + } else if (role == Qt::EditRole) { + switch (column) { + case Title: + return note.title(); + case ModificationTime: + return note.lastModifiedDate(); + case CreationTime: + return note.creationDate(); + case Size: + return message->size(); + } + + } } return EntityTreeModel::entityData(item, column, role); } +QVariant KJotsModel::entityHeaderData(int section, Qt::Orientation orientation, int role, HeaderGroup headerGroup) const +{ + if (role != Qt::DisplayRole || orientation != Qt::Horizontal) { + return EntityTreeModel:: entityHeaderData(section, orientation, role, headerGroup); + } + if (headerGroup == EntityTreeModel::CollectionTreeHeaders) { + return i18nc("@title:column", "Name"); + } else if (headerGroup == EntityTreeModel::ItemListHeaders) { + switch (section) { + case Title: + return i18nc("@title:column title of a note", "Title"); + case CreationTime: + return i18nc("@title:column creation date and time of a note", "Created"); + case ModificationTime: + return i18nc("@title:column last modification date and time of a note", "Modified"); + case Size: + return i18nc("@title:column size of a note", "Size"); + } + } + return EntityTreeModel::entityHeaderData(section, orientation, role, headerGroup); +} + +int KJotsModel::entityColumnCount(HeaderGroup headerGroup) const +{ + if (headerGroup == EntityTreeModel::CollectionTreeHeaders) { + return 1; + } else if (headerGroup == EntityTreeModel::ItemListHeaders) { + return 4; + } else { + return EntityTreeModel::entityColumnCount(headerGroup); + } +} + QModelIndex KJotsModel::modelIndexForUrl(const QAbstractItemModel *model, const QUrl &url) { if (url.scheme() != QStringLiteral("akonadi")) { return {}; } const auto item = Item::fromUrl(url); const auto col = Collection::fromUrl(url); if (item.isValid()) { const QModelIndexList idxs = EntityTreeModel::modelIndexesForItem(model, item); if (!idxs.isEmpty()) { return idxs.first(); } } else if (col.isValid()) { return EntityTreeModel::modelIndexForCollection(model, col); } return {}; } Item KJotsModel::updateItem(const Item &item, QTextDocument *document) { if (!item.hasPayload()) { return {}; } NoteUtils::NoteMessageWrapper note(item.payload()); // Saving embedded images const auto images = document->property("images").value(); QVector &attachments = note.attachments(); attachments.clear(); attachments.reserve(images.count()); std::transform(images.cbegin(), images.cend(), std::back_inserter(attachments), [](const QSharedPointer &img) { NoteUtils::Attachment attachment(img->image, QStringLiteral("image/png")); attachment.setDataBase64Encoded(true); attachment.setContentID(img->contentID); return attachment; }); // Setting text bool isRichText = KPIMTextEdit::TextUtils::containsFormatting(document); if (isRichText) { const QByteArray html = KPIMTextEdit::RichTextComposerImages::imageNamesToContentIds( document->toHtml().toUtf8(), images); note.setText( QString::fromUtf8(html), Qt::RichText ); } else { note.setText( document->toPlainText(), Qt::PlainText ); } note.setLastModifiedDate(QDateTime::currentDateTime()); Item newItem = item; newItem.setPayload(note.message()); return newItem; } QString KJotsModel::itemPath(const QModelIndex &index, const QString &sep) { - QString caption; + QStringList path; QModelIndex curIndex = index; while (curIndex.isValid()) { - QModelIndex parentBook = curIndex.parent(); - if (parentBook.isValid()) { - caption = sep + curIndex.data().toString() + caption; - } else { - caption = curIndex.data().toString() + caption; - } - curIndex = parentBook; + path.prepend(curIndex.data().toString()); + curIndex = curIndex.parent(); } - return caption; + return path.join(sep); +} + +QModelIndex KJotsModel::etmIndex(const QModelIndex &index) +{ + QModelIndex result = index; + const QAbstractProxyModel *proxy = qobject_cast(index.model()); + while (proxy) { + result = proxy->mapToSource(result); + proxy = qobject_cast(result.model()); + } + return result; } diff --git a/src/kjotsmodel.h b/src/kjotsmodel.h index d9e4fb5..03afb39 100644 --- a/src/kjotsmodel.h +++ b/src/kjotsmodel.h @@ -1,111 +1,131 @@ /* 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; } /** * 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 Akonadi::EntityTreeModel { Q_OBJECT public: explicit KJotsModel(Akonadi::ChangeRecorder *monitor, QObject *parent = nullptr); ~KJotsModel(); enum KJotsRoles { GrantleeObjectRole = EntityTreeModel::UserRole, DocumentRole }; + enum Column { + // Title of a note + Title, + + // Modification date / time of a note + ModificationTime, + + // Creation date / time of a note + CreationTime, + + // Size of a note + Size + }; + // We don't reimplement the Collection overload. using EntityTreeModel::entityData; QVariant entityData(const Akonadi::Item &item, int column, int role = Qt::DisplayRole) const override; + int entityColumnCount(HeaderGroup headerGroup) const override; + QVariant entityHeaderData(int section, Qt::Orientation orientation, int role, HeaderGroup headerGroup) 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); /** * Sets the content of @p item to @p document */ static Akonadi::Item updateItem(const Akonadi::Item &item, QTextDocument *document); /** * A helper function which returns a full "path" to the @p item (e.g. "Resource / Notebook / Note") * using @p sep as a separator. If multiple items are selected, returns "Multiple selection" */ static QString itemPath(const QModelIndex &index, const QString &sep = QStringLiteral(" / ")); + + /** + * A helper function which returns an index belonging to the ETM model through bunch of proxy models + */ + static QModelIndex etmIndex(const QModelIndex &index); private: QHash m_colors; mutable QHash m_documents; }; #endif diff --git a/src/kjotssortproxymodel.cpp b/src/kjotssortproxymodel.cpp deleted file mode 100644 index 79d7e43..0000000 --- a/src/kjotssortproxymodel.cpp +++ /dev/null @@ -1,95 +0,0 @@ -/* - This file is part of KJots. - - Copyright (c) 2010 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 "kjotssortproxymodel.h" - -#include -#include - -using namespace Akonadi; - -KJotsSortProxyModel::KJotsSortProxyModel(QObject *parent) - : QSortFilterProxyModel(parent) -{ - setDynamicSortFilter(true); -} - -bool KJotsSortProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const -{ - const Collection::Id colId = left.data(EntityTreeModel::ParentCollectionRole).value().id(); - - if (colId < 0 || m_alphaSorted.contains(colId) || !m_dateTimeSorted.contains(colId)) { - return QSortFilterProxyModel::lessThan(left, right); - } - - const Item leftItem = left.data(EntityTreeModel::ItemRole).value(); - const Item rightItem = right.data(EntityTreeModel::ItemRole).value(); - - if (!leftItem.isValid() || !rightItem.isValid()) { - return true; - } - - const KMime::Message::Ptr leftNote = leftItem.payload(); - const KMime::Message::Ptr rightNote = rightItem.payload(); - - return leftNote->date()->dateTime() < rightNote->date()->dateTime(); -} - -Collection::Id KJotsSortProxyModel::collectionId(const QModelIndex &parent) const -{ - const QModelIndex childIndex = index(0, 0, parent); - if (!childIndex.isValid()) { - return -1; - } - - const Collection collection = childIndex.data(EntityTreeModel::ParentCollectionRole).value(); - - if (!collection.isValid()) { - return -1; - } - - return collection.id(); -} - -void KJotsSortProxyModel::sortChildrenAlphabetically(const QModelIndex &parent) -{ - const Collection::Id id = collectionId(parent); - if (id < 0) { - return; - } - - m_dateTimeSorted.remove(id); - m_alphaSorted.insert(id); - invalidate(); -} - -void KJotsSortProxyModel::sortChildrenByCreationTime(const QModelIndex &parent) -{ - const Collection::Id id = collectionId(parent); - if (id < 0) { - return; - } - - m_alphaSorted.remove(id); - m_dateTimeSorted.insert(id); - invalidate(); -} - diff --git a/src/kjotssortproxymodel.h b/src/kjotssortproxymodel.h deleted file mode 100644 index 47ff056..0000000 --- a/src/kjotssortproxymodel.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - This file is part of KJots. - - Copyright (c) 2010 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 KJOTSSORTPROXYMODEL_H -#define KJOTSSORTPROXYMODEL_H - -#include - -#include - -class KJotsSortProxyModel : public QSortFilterProxyModel -{ - Q_OBJECT -public: - explicit KJotsSortProxyModel(QObject *parent = nullptr); - - void sortChildrenAlphabetically(const QModelIndex &parent); - void sortChildrenByCreationTime(const QModelIndex &parent); - -protected: - bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; - -private: - Akonadi::Collection::Id collectionId(const QModelIndex &parent) const; - -private: - QSet m_alphaSorted; - QSet m_dateTimeSorted; -}; - -#endif diff --git a/src/kjotsui.rc b/src/kjotsui.rc index 1d59e75..8d08270 100644 --- a/src/kjotsui.rc +++ b/src/kjotsui.rc @@ -1,161 +1,161 @@ - + - - + F&ormat + + &View + + + + &Go &Tools - + - - + - + - - - - - + Main Toolbar Text Toolbar Format Toolbar Tools Toolbar diff --git a/src/kjotswidget.cpp b/src/kjotswidget.cpp index 6c24769..150d73d 100644 --- a/src/kjotswidget.cpp +++ b/src/kjotswidget.cpp @@ -1,792 +1,777 @@ /* 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_sortProxyModel = new KJotsSortProxyModel(this); - m_sortProxyModel->setSourceModel(m_kjotsModel); + 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_sortProxyModel); + m_orderProxy->setSourceModel(m_collectionModel); KConfigGroup cfg(KSharedConfig::openConfig(), "KJotsEntityOrder"); m_orderProxy->setOrderConfig(cfg); - m_treeview->setModel(m_orderProxy); - m_actionManager->setCollectionSelectionModel(m_treeview->selectionModel()); - m_actionManager->setItemSelectionModel(m_treeview->selectionModel()); + 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_collectionSelectionProxyModel = new KSelectionProxyModel(m_treeview->selectionModel(), this); - m_collectionSelectionProxyModel->setSourceModel(m_treeview->model()); + 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_treeview->selectionModel(), &QItemSelectionModel::selectionChanged, this, &KJotsWidget::renderSelection); - connect(m_treeview->selectionModel(), &QItemSelectionModel::selectionChanged, this, &KJotsWidget::updateMenu); - connect(m_treeview->selectionModel(), &QItemSelectionModel::selectionChanged, this, &KJotsWidget::updateCaption); - connect(m_treeview->model(), &QAbstractItemModel::dataChanged, this, &KJotsWidget::renderSelection); - connect(m_treeview->model(), &QAbstractItemModel::dataChanged, this, &KJotsWidget::updateCaption); + 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_treeview->selectionModel(), &QItemSelectionModel::selectionChanged, m_autosaveTimer, qOverload<>(&QTimer::start)); + 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 browser / editor - m_splitter = new QSplitter(this); - m_splitter->setStretchFactor(1, 1); - layout->addWidget(m_splitter); - if (!KJotsSettings::splitterSizes().isEmpty()) { - m_splitter->setSizes(KJotsSettings::splitterSizes()); - } + // 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_treeview = new EntityTreeView(m_xmlGuiClient, m_splitter); - m_treeview->setSelectionMode(QAbstractItemView::ExtendedSelection); - m_treeview->setEditTriggers(QAbstractItemView::DoubleClicked); - - // Make sure the editor gets focus again after naming a new book/page. - connect(m_treeview->itemDelegate(), &QItemDelegate::closeEditor, this, [this](){ - activeEditor()->setFocus(); - }); + 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_splitter); + 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)); - actionCollection->setDefaultShortcut(m_actionManager->action(StandardActionManager::DeleteCollections), - QKeySequence(Qt::CTRL + Qt::Key_Delete)); - actionCollection->setDefaultShortcut(m_actionManager->action(StandardActionManager::DeleteItems), - QKeySequence(Qt::CTRL + Qt::Key_Delete)); - QAction *action; // Standard actions KStandardAction::preferences(this, &KJotsWidget::configure, actionCollection); KStandardAction::save( m_editor, &KJotsEdit::savePage, actionCollection); - action = KStandardAction::next(this, &KJotsWidget::nextPage, actionCollection); + action = KStandardAction::next(this, [this](){ + 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, &KJotsWidget::prevPage, actionCollection); + action = KStandardAction::prior(this, [this](){ + 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(m_treeview, [this](){ - const QModelIndexList rows = m_treeview->selectionModel()->selectedRows(); + 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; } - m_treeview->edit(rows.first()); + 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); + // Default Shift+Delete is ambiguous and used both for cut and delete + actionCollection->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_Delete)); 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_treeview->selectionModel(), this); + 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); - // Some extra actions + // 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::Key_D)); - connect(action, &QAction::triggered, this, &KJotsWidget::nextBook); + 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_D)); - connect(action, &QAction::triggered, this, &KJotsWidget::prevBook); + 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); - action = actionCollection->addAction(QStringLiteral("sort_children_alpha")); - action->setText(i18n("Sort children alphabetically")); - connect(action, &QAction::triggered, this, &KJotsWidget::actionSortChildrenAlpha); + // View mode actions + m_viewModeGroup = new QActionGroup(this); - action = actionCollection->addAction(QStringLiteral("sort_children_by_date")); - action->setText(i18n("Sort children by creation date")); - connect(action, &QAction::triggered, this, &KJotsWidget::actionSortChildrenByDate); -} + action = new QAction(i18nc("@action:inmenu", "Two Columns"), m_viewModeGroup); + action->setCheckable(true); + action->setData(1); + actionCollection->addAction(QStringLiteral("view_mode_two_columns"), action); -void KJotsWidget::restoreState() -{ - auto *saver = new ETMViewStateSaver; - saver->setView(m_treeview); - KConfigGroup cfg(KSharedConfig::openConfig(), "TreeState"); - saver->restoreState(cfg); -} + action = new QAction(i18nc("@action:inmenu", "Three Columns"), m_viewModeGroup); + action->setCheckable(true); + action->setData(2); + actionCollection->addAction(QStringLiteral("view_mode_three_columns"), action); -void KJotsWidget::saveState() -{ - ETMViewStateSaver saver; - saver.setView(m_treeview); - KConfigGroup cfg(KSharedConfig::openConfig(), "TreeState"); - saver.saveState(cfg); - cfg.sync(); + connect(m_viewModeGroup, &QActionGroup::triggered, this, [this](QAction *action){ + setViewMode(action->data().toInt()); + }); } void KJotsWidget::delayedInitialization() { - // 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) + // 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. - entryActions = { actionCollection->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Find))), - actionCollection->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Print))), - actionCollection->action(QString::fromLatin1(KStandardAction::name(KStandardAction::RenameFile))), - actionCollection->action(QStringLiteral("save_to")) }; - - // Actions that are used only when a page is selected. - pageActions = { 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))) }; - - // Actions that are used only when a book is selected. - bookActions = { actionCollection->action(QStringLiteral("sort_children_alpha")), - actionCollection->action(QStringLiteral("sort_children_by_date")) }; - - // Actions that are used when multiple items are selected. - multiselectionActions = { actionCollection->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Find))), - actionCollection->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Print))), - actionCollection->action(QStringLiteral("save_to")) }; + 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() { - QModelIndexList selection = m_treeview->selectionModel()->selectedRows(); - int selectionSize = selection.size(); + const int collectionsSelected = m_collectionView->selectionModel()->selectedRows().count(); + const int itemsSelected = m_itemView->selectionModel()->selectedRows().count(); + const int selectionSize = itemsSelected + collectionsSelected; - m_editor->setEnableActions(m_stackedWidget->currentWidget() == m_editorWidget); - - 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); - } - } 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); - } - } else { - - for (QAction *action : qAsConst(multiselectionActions)) { - action->setEnabled(false); - } - for (QAction *action : qAsConst(entryActions)) { - action->setEnabled(true); - } + // Actions available only when editor is shown + m_editor->setEnableActions(itemsSelected == 1); + for (QAction *action : qAsConst(editorActions)) { + action->setEnabled(itemsSelected == 1); + } - QModelIndex idx = selection.at(0); + // 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)); - Collection col = idx.data(KJotsModel::CollectionRole).value(); - if (col.isValid()) { - for (QAction *action : qAsConst(pageActions)) { - action->setEnabled(false); - } - for (QAction *action : qAsConst(bookActions)) { - action->setEnabled(true); - } - } else { - for (QAction *action : qAsConst(pageActions)) { - if (action->objectName() == QString::fromLatin1(name(KStandardAction::Cut))) { - action->setEnabled(activeEditor()->textCursor().hasSelection()); - } else { - action->setEnabled(true); - } - } - for (QAction *action : qAsConst(bookActions)) { - action->setEnabled(false); - } - } + // 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 int rows = m_collectionSelectionProxyModel->rowCount(); - const int column = 0; - for (int row = 0; row < rows; ++row) { - QModelIndex idx = m_collectionSelectionProxyModel->index(row, column, QModelIndex()); - objectList << idx.data(KJotsModel::GrantleeObjectRole); + + 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); } -void KJotsWidget::selectNext(int role, int step) +QModelIndex KJotsWidget::previousNextEntity(QTreeView *view, int step) { - QModelIndexList list = m_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) { - m_treeview->selectionModel()->select(sibling, QItemSelectionModel::SelectCurrent); - return; - } - sibling = sibling.sibling(sibling.row() + step, column); + 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); } - 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 = m_treeview->selectionModel()->selectedRows(); - if (list.size() != 1) { - return false; + if (selection.size() != 1) { + return {}; } - - 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); + return step > 0 ? view->indexBelow(selection.first()) : view->indexAbove(selection.first()); } -bool KJotsWidget::canGoPreviousPage() const -{ - return canGo(EntityTreeModel::ItemIdRole, -1); -} - -bool KJotsWidget::canGoNextBook() const +void KJotsWidget::renderSelection() { - return canGo(EntityTreeModel::CollectionIdRole, 1); -} + 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()); -bool KJotsWidget::canGoPreviousBook() const -{ - return canGo(EntityTreeModel::CollectionIdRole, -1); -} + const QModelIndexList selectedItems = m_itemView->selectionModel()->selectedRows(); -void KJotsWidget::renderSelection() -{ - Q_EMIT canGoNextBookChanged(canGoPreviousBook()); - Q_EMIT canGoNextPageChanged(canGoNextPage()); - Q_EMIT canGoPreviousBookChanged(canGoPreviousBook()); - Q_EMIT canGoPreviousPageChanged(canGoPreviousPage()); - - const int rows = m_collectionSelectionProxyModel->rowCount(); - // If the selection is a single page, present it for editing... - if (rows == 1) { - QModelIndex idx = m_collectionSelectionProxyModel->index(0, 0, QModelIndex()); - if (m_editor->setModelIndex(m_collectionSelectionProxyModel->mapToSource(idx))) { + // 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_treeview->selectionModel()->selectedRows(); QString caption; - if (selection.size() == 1) { - caption = KJotsModel::itemPath(selection.first()); + const QModelIndexList itemSelection = m_itemView->selectionModel()->selectedRows(); + const QModelIndexList collectionSelection = m_collectionView->selectionModel()->selectedRows(); + if (itemSelection.size() == 1) { + caption = KJotsModel::itemPath(KJotsModel::etmIndex(itemSelection.first())); if (m_editor->modified()) { caption.append(QStringLiteral(" *")); } - } else if (selection.size() > 1) { + } else if (itemSelection.size() == 0 && collectionSelection.size() == 1) { + caption = KJotsModel::itemPath(collectionSelection.first()); + } else if (itemSelection.size() > 1 || collectionSelection.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); } - } - KJotsSettings::setSplitterSizes(m_splitter->sizes()); + saveSplitterStates(); KJotsSettings::self()->save(); m_orderProxy->saveOrder(); return true; } -void KJotsWidget::actionSortChildrenAlpha() -{ - const QModelIndexList selection = m_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 = m_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("akonadi")) { - m_treeview->selectionModel()->select(KJotsModel::modelIndexForUrl(m_treeview->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); } } diff --git a/src/kjotswidget.h b/src/kjotswidget.h index 7bbb84e..c93f5ac 100644 --- a/src/kjotswidget.h +++ b/src/kjotswidget.h @@ -1,170 +1,166 @@ /* 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 +class QActionGroup; class QCheckBox; class QTextEdit; class QTextCharFormat; class QSplitter; class QStackedWidget; class QModelIndex; +class QTreeView; class KActionMenu; class KFindDialog; class KJob; class KReplaceDialog; class KSelectionProxyModel; class KJotsBrowser; class KJotsBrowserWidget; class KXMLGUIClient; namespace Akonadi { +class EntityMimeTypeFilterModel; class EntityOrderProxyModel; class EntityTreeView; class StandardNoteActionManager; } namespace Grantlee { class Engine; } namespace KPIMTextEdit { class RichTextEditorWidget; } class KJotsEdit; class KJotsModel; 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 = Qt::WindowFlags()); ~KJotsWidget(); QTextEdit *activeEditor(); public Q_SLOTS: void configure(); - void prevPage(); - void nextPage(); - void prevBook(); - void nextBook(); - void updateCaption(); void updateMenu(); Q_SCRIPTABLE bool queryClose(); + void setViewMode(int mode); + Q_SIGNALS: void canGoNextPageChanged(bool); void canGoPreviousPageChanged(bool); void canGoNextBookChanged(bool); void canGoPreviousBookChanged(bool); void captionChanged(const QString &newCaption); protected: + static QModelIndex previousNextEntity(QTreeView *view, int step); + void setupGui(); void setupActions(); - bool canGo(int role, int step) const; - bool canGoNextPage() const; - bool canGoPreviousPage() const; - bool canGoNextBook() const; - bool canGoPreviousBook() const; - QString renderSelectionTo(const QString &theme, const QString &templ); QString renderSelectionToHtml(); - void selectNext(int role, int step); - std::unique_ptr setupPrinter(QPrinter::PrinterMode mode = QPrinter::ScreenResolution); + + void saveSplitterStates() const; + void restoreSplitterStates(); protected Q_SLOTS: /** * Renders contents on either KJotsEdit or KJotsBrowser based on KJotsTreeView selection */ void renderSelection(); void exportSelection(const QString &theme, const QString &templ); void printSelection(); void printPreviewSelection(); void openLink(const QUrl &url); private Q_SLOTS: void delayedInitialization(); - void actionSortChildrenAlpha(); - void actionSortChildrenByDate(); - void saveState(); void restoreState(); - void updateConfiguration(); void print(QPrinter *printer); private: // Grantlee Grantlee::Engine *m_templateEngine = nullptr; QSharedPointer m_loader; // XMLGui && Actions KXMLGUIClient *m_xmlGuiClient = nullptr; Akonadi::StandardNoteActionManager *m_actionManager = nullptr; - QSet entryActions, pageActions, bookActions, multiselectionActions; + QSet anySelectionActions, editorActions; + QActionGroup *m_viewModeGroup = nullptr; // UI - QSplitter *m_splitter = nullptr; + QSplitter *m_splitter1 = nullptr; + QSplitter *m_splitter2 = nullptr; QStackedWidget *m_stackedWidget = nullptr; - Akonadi::EntityTreeView *m_treeview = nullptr; + Akonadi::EntityTreeView *m_collectionView = nullptr; + Akonadi::EntityTreeView *m_itemView = nullptr; KJotsEdit *m_editor = nullptr; KPIMTextEdit::RichTextEditorWidget *m_editorWidget = nullptr; KJotsBrowserWidget *m_browserWidget = nullptr; // Models KJotsModel *m_kjotsModel = nullptr; + Akonadi::EntityMimeTypeFilterModel *m_collectionModel = nullptr; + Akonadi::EntityMimeTypeFilterModel *m_itemModel = nullptr; KSelectionProxyModel *m_collectionSelectionProxyModel = nullptr; - KJotsSortProxyModel *m_sortProxyModel = nullptr; Akonadi::EntityOrderProxyModel *m_orderProxy = nullptr; QTimer *m_autosaveTimer = nullptr; }; #endif diff --git a/src/noteshared/notecreatorandselector.cpp b/src/noteshared/notecreatorandselector.cpp index 1f973f3..b193882 100644 --- a/src/noteshared/notecreatorandselector.cpp +++ b/src/noteshared/notecreatorandselector.cpp @@ -1,127 +1,126 @@ /* Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net, author 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 "notecreatorandselector.h" #include #include #include #include #include #include #include "noteshared_debug.h" using namespace Akonadi; using namespace NoteShared; NoteCreatorAndSelector::NoteCreatorAndSelector(QItemSelectionModel *primaryModel, QItemSelectionModel *secondaryModel, QObject *parent) : QObject(parent), m_primarySelectionModel(primaryModel), m_secondarySelectionModel(secondaryModel == nullptr ? primaryModel : secondaryModel), m_containerCollectionId(-1), m_newNoteId(-1), m_giveupTimer(new QTimer(this)) { // If the note doesn't exist after 5 seconds, give up waiting for it. m_giveupTimer->setInterval(5000); connect(m_giveupTimer, &QTimer::timeout, this, &NoteCreatorAndSelector::deleteLater); } NoteCreatorAndSelector::~NoteCreatorAndSelector() = default; void NoteCreatorAndSelector::createNote(const Akonadi::Collection &containerCollection) { m_containerCollectionId = containerCollection.id(); if (m_primarySelectionModel == m_secondarySelectionModel) { doCreateNote(); } else { m_giveupTimer->start(); connect(m_primarySelectionModel->model(), &QAbstractItemModel::rowsInserted, this, &NoteCreatorAndSelector::trySelectCollection); trySelectCollection(); } } void NoteCreatorAndSelector::trySelectCollection() { QModelIndex idx = EntityTreeModel::modelIndexForCollection(m_primarySelectionModel->model(), Collection(m_containerCollectionId)); if (!idx.isValid()) { return; } m_giveupTimer->stop(); - m_primarySelectionModel->select(QItemSelection(idx, idx), QItemSelectionModel::Select); + m_primarySelectionModel->select(idx, QItemSelectionModel::SelectCurrent); disconnect(m_primarySelectionModel->model(), &QAbstractItemModel::rowsInserted, this, &NoteCreatorAndSelector::trySelectCollection); doCreateNote(); } void NoteCreatorAndSelector::doCreateNote() { Item newItem; newItem.setMimeType(Akonadi::NoteUtils::noteMimeType()); Akonadi::NoteUtils::NoteMessageWrapper note(KMime::Message::Ptr(new KMime::Message)); note.setFrom(QStringLiteral("KJots@KDE5")); note.setTitle(i18nc("The default name for new pages.", "New Page")); note.setCreationDate(QDateTime::currentDateTime()); note.setLastModifiedDate(QDateTime::currentDateTime()); // Need a non-empty body part so that the serializer regards this as a valid message. note.setText(QStringLiteral(" ")); newItem.setPayload(note.message()); newItem.attribute(Akonadi::Item::AddIfMissing)->setIconName(QStringLiteral("text-plain")); auto *job = new Akonadi::ItemCreateJob(newItem, Collection(m_containerCollectionId), this); connect(job, &Akonadi::ItemCreateJob::result, this, &NoteCreatorAndSelector::noteCreationFinished); - } void NoteCreatorAndSelector::noteCreationFinished(KJob *job) { if (job->error()) { qCWarning(NOTESHARED_LOG) << job->errorString(); return; } auto *createJob = qobject_cast(job); Q_ASSERT(createJob); Item newItem = createJob->item(); m_newNoteId = newItem.id(); m_giveupTimer->start(); - connect(m_primarySelectionModel->model(), &QAbstractItemModel::rowsInserted, this, &NoteCreatorAndSelector::trySelectNote); + connect(m_secondarySelectionModel->model(), &QAbstractItemModel::rowsInserted, this, &NoteCreatorAndSelector::trySelectNote); trySelectNote(); } void NoteCreatorAndSelector::trySelectNote() { QModelIndexList list = EntityTreeModel::modelIndexesForItem(m_secondarySelectionModel->model(), Item(m_newNoteId)); if (list.isEmpty()) { return; } const QModelIndex idx = list.first(); - m_secondarySelectionModel->select(QItemSelection(idx, idx), QItemSelectionModel::ClearAndSelect); + m_secondarySelectionModel->select(idx, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); } diff --git a/src/noteshared/standardnoteactionmanager.cpp b/src/noteshared/standardnoteactionmanager.cpp index eaa4f4b..26a1b29 100644 --- a/src/noteshared/standardnoteactionmanager.cpp +++ b/src/noteshared/standardnoteactionmanager.cpp @@ -1,616 +1,663 @@ /* This file is part of KJots. Copyright (c) 2020 Igor Poboiko 2009 - 2010 Tobias Koenig 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 "standardnoteactionmanager.h" #include #include #include #include #include #include #include #include #include #include #include "notecreatorandselector.h" #include "notelockattribute.h" using namespace Akonadi; class Q_DECL_HIDDEN StandardNoteActionManager::Private { public: Private(KActionCollection *actionCollection, QWidget *parentWidget, StandardNoteActionManager *parent) : mActionCollection(actionCollection) , mParentWidget(parentWidget) , mGenericManager(std::make_unique(actionCollection, parentWidget)) , mParent(parent) { QObject::connect(mGenericManager.get(), &StandardActionManager::actionStateUpdated, mParent, &StandardNoteActionManager::actionStateUpdated); mGenericManager->setMimeTypeFilter({ NoteUtils::noteMimeType() }); mGenericManager->setCapabilityFilter({ QStringLiteral("Resource") }); } ~Private() = default; void updateGenericAllActions() { updateGenericAction(StandardActionManager::CreateCollection); updateGenericAction(StandardActionManager::CopyCollections); updateGenericAction(StandardActionManager::DeleteCollections); updateGenericAction(StandardActionManager::SynchronizeCollections); updateGenericAction(StandardActionManager::CollectionProperties); updateGenericAction(StandardActionManager::CopyItems); updateGenericAction(StandardActionManager::Paste); updateGenericAction(StandardActionManager::DeleteItems); updateGenericAction(StandardActionManager::ManageLocalSubscriptions); updateGenericAction(StandardActionManager::AddToFavoriteCollections); updateGenericAction(StandardActionManager::RemoveFromFavoriteCollections); updateGenericAction(StandardActionManager::RenameFavoriteCollection); updateGenericAction(StandardActionManager::CopyCollectionToMenu); updateGenericAction(StandardActionManager::CopyItemToMenu); updateGenericAction(StandardActionManager::MoveItemToMenu); updateGenericAction(StandardActionManager::MoveCollectionToMenu); updateGenericAction(StandardActionManager::CutItems); updateGenericAction(StandardActionManager::CutCollections); updateGenericAction(StandardActionManager::CreateResource); updateGenericAction(StandardActionManager::DeleteResources); updateGenericAction(StandardActionManager::ResourceProperties); updateGenericAction(StandardActionManager::SynchronizeResources); updateGenericAction(StandardActionManager::ToggleWorkOffline); updateGenericAction(StandardActionManager::CopyCollectionToDialog); updateGenericAction(StandardActionManager::MoveCollectionToDialog); updateGenericAction(StandardActionManager::CopyItemToDialog); updateGenericAction(StandardActionManager::MoveItemToDialog); updateGenericAction(StandardActionManager::SynchronizeCollectionsRecursive); updateGenericAction(StandardActionManager::MoveCollectionsToTrash); updateGenericAction(StandardActionManager::MoveItemsToTrash); updateGenericAction(StandardActionManager::RestoreCollectionsFromTrash); updateGenericAction(StandardActionManager::RestoreItemsFromTrash); updateGenericAction(StandardActionManager::MoveToTrashRestoreCollection); updateGenericAction(StandardActionManager::MoveToTrashRestoreCollectionAlternative); updateGenericAction(StandardActionManager::MoveToTrashRestoreItem); updateGenericAction(StandardActionManager::MoveToTrashRestoreItemAlternative); updateGenericAction(StandardActionManager::SynchronizeFavoriteCollections); } void updateGenericAction(StandardActionManager::Type type) { switch (type) { case StandardActionManager::CreateCollection: mGenericManager->action(StandardActionManager::CreateCollection)->setText( i18n("New Note Book...")); mGenericManager->action(StandardActionManager::CreateCollection)->setIcon( QIcon::fromTheme(QStringLiteral("address-book-new"))); mGenericManager->action(StandardActionManager::CreateCollection)->setWhatsThis( i18n("Add a new note book to the currently selected bookshelf.")); mGenericManager->setContextText( StandardActionManager::CreateCollection, StandardActionManager::DialogTitle, i18nc("@title:window", "New Note Book")); mGenericManager->setContextText( StandardActionManager::CreateCollection, StandardActionManager::ErrorMessageText, ki18n("Could not create note book: %1")); mGenericManager->setContextText( StandardActionManager::CreateCollection, StandardActionManager::ErrorMessageTitle, i18n("Note book creation failed")); mGenericManager->action(StandardActionManager::CreateCollection)->setProperty("ContentMimeTypes", { NoteUtils::noteMimeType() }); break; case StandardActionManager::CopyCollections: mGenericManager->setActionText(StandardActionManager::CopyCollections, ki18np("Copy Note Book", "Copy %1 Note Books")); mGenericManager->action(StandardActionManager::CopyCollections)->setWhatsThis( i18n("Copy the selected note books to the clipboard.")); break; case StandardActionManager::DeleteCollections: mGenericManager->setActionText(StandardActionManager::DeleteCollections, ki18np("Delete Note Book", "Delete %1 Note Books")); mGenericManager->action(StandardActionManager::DeleteCollections)->setWhatsThis( i18n("Delete the selected note books from the bookshelf.")); mGenericManager->setContextText( StandardActionManager::DeleteCollections, StandardActionManager::MessageBoxText, ki18np("Do you really want to delete this note book and all its contents?", "Do you really want to delete %1 note books and all their contents?")); mGenericManager->setContextText( StandardActionManager::DeleteCollections, StandardActionManager::MessageBoxTitle, ki18ncp("@title:window", "Delete note book?", "Delete note books?")); mGenericManager->setContextText( StandardActionManager::DeleteCollections, StandardActionManager::ErrorMessageText, ki18n("Could not delete note book: %1")); mGenericManager->setContextText( StandardActionManager::DeleteCollections, StandardActionManager::ErrorMessageTitle, i18n("Note book deletion failed")); break; case StandardActionManager::SynchronizeCollections: mGenericManager->setActionText(StandardActionManager::SynchronizeCollections, ki18np("Update Note Book", "Update %1 Note Books")); mGenericManager->action(StandardActionManager::SynchronizeCollections)->setWhatsThis( i18n("Update the content of the selected note books.")); break; case StandardActionManager::CutCollections: mGenericManager->setActionText(StandardActionManager::CutCollections, ki18np("Cut Note Book", "Cut %1 Note Books")); mGenericManager->action(StandardActionManager::CutCollections)->setWhatsThis( i18n("Cut the selected note books from the bookshelf.")); break; case StandardActionManager::CollectionProperties: mGenericManager->action(StandardActionManager::CollectionProperties)->setText( i18n("Note Book Properties...")); mGenericManager->action(StandardActionManager::CollectionProperties)->setWhatsThis( i18n("Open a dialog to edit the properties of the selected note book.")); mGenericManager->setContextText( StandardActionManager::CollectionProperties, StandardActionManager::DialogTitle, ki18nc("@title:window", "Properties of Note Book %1")); break; case StandardActionManager::CopyItems: mGenericManager->setActionText(StandardActionManager::CopyItems, ki18np("Copy Note", "Copy %1 Notes")); mGenericManager->action(StandardActionManager::CopyItems)->setWhatsThis( i18n("Copy the selected notes to the clipboard.")); break; case StandardActionManager::DeleteItems: mGenericManager->setActionText(StandardActionManager::DeleteItems, ki18np("Delete Note", "Delete %1 Notes")); mGenericManager->action(StandardActionManager::DeleteItems)->setWhatsThis( i18n("Delete the selected notes from the note book.")); mGenericManager->setContextText( StandardActionManager::DeleteItems, StandardActionManager::MessageBoxText, ki18np("Do you really want to delete the selected note?", "Do you really want to delete %1 notes?")); mGenericManager->setContextText( StandardActionManager::DeleteItems, StandardActionManager::MessageBoxTitle, ki18ncp("@title:window", "Delete Note?", "Delete Notes?")); mGenericManager->setContextText( StandardActionManager::DeleteItems, StandardActionManager::ErrorMessageText, ki18n("Could not delete note: %1")); mGenericManager->setContextText( StandardActionManager::DeleteItems, StandardActionManager::ErrorMessageTitle, i18n("Note deletion failed")); break; case StandardActionManager::CutItems: mGenericManager->setActionText(StandardActionManager::CutItems, ki18np("Cut Note", "Cut %1 Notes")); mGenericManager->action(StandardActionManager::CutItems)->setWhatsThis( i18n("Cut the selected notes from the note book.")); break; case StandardActionManager::CreateResource: mGenericManager->action(StandardActionManager::CreateResource)->setText( i18n("Add &Bookshelf...")); mGenericManager->action(StandardActionManager::CreateResource)->setWhatsThis( i18n("Add a new bookshelf

" "You will be presented with a dialog where you can select " "the type of the bookshelf that shall be added.

")); mGenericManager->setContextText( StandardActionManager::CreateResource, StandardActionManager::DialogTitle, i18nc("@title:window", "Add Bookshelf")); mGenericManager->setContextText( StandardActionManager::CreateResource, StandardActionManager::ErrorMessageText, ki18n("Could not create bookshelf: %1")); mGenericManager->setContextText( StandardActionManager::CreateResource, StandardActionManager::ErrorMessageTitle, i18n("Bookshelf creation failed")); break; case StandardActionManager::DeleteResources: mGenericManager->setActionText(StandardActionManager::DeleteResources, ki18np("&Delete Bookshelf", "&Delete %1 Bookshelfs")); mGenericManager->action(StandardActionManager::DeleteResources)->setWhatsThis( i18n("Delete the selected bookshelfs

" "The currently selected bookshelfs will be deleted, " "along with all the notes they contain.

")); mGenericManager->setContextText( StandardActionManager::DeleteResources, StandardActionManager::MessageBoxText, ki18np("Do you really want to delete this bookshelf?", "Do you really want to delete %1 bookshelfs?")); mGenericManager->setContextText( StandardActionManager::DeleteResources, StandardActionManager::MessageBoxTitle, ki18ncp("@title:window", "Delete Bookshelf?", "Delete Bookshelfs?")); break; case StandardActionManager::ResourceProperties: mGenericManager->action(StandardActionManager::ResourceProperties)->setText( i18n("Bookshelf Properties...")); mGenericManager->action(StandardActionManager::ResourceProperties)->setWhatsThis( i18n("Open a dialog to edit properties of the selected bookshelf.")); break; case StandardActionManager::SynchronizeResources: mGenericManager->setActionText(StandardActionManager::SynchronizeResources, ki18np("Update Bookshelf", "Update %1 Bookshelfs")); mGenericManager->action(StandardActionManager::SynchronizeResources)->setWhatsThis (i18n("Updates the content of all note books of the selected bookshelfs.")); break; case StandardActionManager::Paste: mGenericManager->setContextText( StandardActionManager::Paste, StandardActionManager::ErrorMessageText, ki18n("Could not paste note: %1")); mGenericManager->setContextText( StandardActionManager::Paste, StandardActionManager::ErrorMessageTitle, i18n("Paste failed")); break; default: break; } } void updateActions() { - if (mItemSelectionModel && mCollectionSelectionModel) { - const Collection::List collections = mParent->selectedCollections(); + if (mItemSelectionModel) { const Item::List items = mParent->selectedItems(); - QAction *action = mActions.value(StandardNoteActionManager::Lock); + const int countUnlocked = std::count_if(items.cbegin(), items.cend(), [](const Item &item){ + return item.isValid() && !item.hasAttribute(); + }); + + QAction *action = mActions.value(StandardNoteActionManager::LockUnlockNote); if (action) { - bool canLock = std::any_of(collections.cbegin(), collections.cend(), [](const Collection &collection){ - return collection.isValid() && !collection.hasAttribute(); - }); - canLock = canLock || std::any_of(items.cbegin(), items.cend(), [](const Item &item){ - return item.isValid() && !item.hasAttribute(); - }); - action->setEnabled(canLock); + action->setEnabled(items.size() > 0); + if (countUnlocked > 0) { + action->setData(true); + action->setText(i18ncp("@action", "Lock Note", "Lock Notes", countUnlocked)); + action->setIcon(QIcon::fromTheme(QStringLiteral("emblem-locked"))); + action->setWhatsThis(i18n("Lock selected notes")); + } else if (items.size() > 0) { + action->setData(false); + action->setText(i18ncp("@action", "Unlock Note", "Unlock Notes", items.size())); + action->setIcon(QIcon::fromTheme(QStringLiteral("emblem-locked"))); + action->setWhatsThis(i18n("Unlock selected notes")); + } } - action = mActions.value(StandardNoteActionManager::Unlock); + action = mGenericManager->action(StandardActionManager::DeleteItems); if (action) { - const bool hasLockedCollection = std::any_of(collections.cbegin(), collections.cend(), [](const Collection &collection){ - return collection.isValid() && collection.hasAttribute(); - }); - if (hasLockedCollection) { - mGenericManager->action(StandardActionManager::DeleteCollections)->setEnabled(false); - } - const bool hasLockedItems = std::any_of(items.cbegin(), items.cend(), [](const Item &item){ - return item.isValid() && item.hasAttribute(); - }); - if (hasLockedItems) { - mGenericManager->action(StandardActionManager::DeleteItems)->setEnabled(false); + action->setEnabled(countUnlocked == items.size()); + } + } else { + QAction *action = mActions.value(StandardNoteActionManager::LockUnlockNote); + if (action) { + action->setEnabled(false); + } + } + + if (mCollectionSelectionModel) { + const Collection::List collections = mParent->selectedCollections(); + const int countUnlocked = std::count_if(collections.cbegin(), collections.cend(), [](const Collection &collection){ + return collection.isValid() && !collection.hasAttribute(); + }); + + QAction *action = mActions.value(StandardNoteActionManager::LockUnlockNoteBook); + if (action) { + action->setEnabled(collections.size() > 0); + if (countUnlocked > 0) { + action->setData(true); + action->setText(i18ncp("@action", "Lock Note Book", "Lock Note Books", countUnlocked)); + action->setIcon(QIcon::fromTheme(QStringLiteral("emblem-locked"))); + action->setWhatsThis(i18n("Lock selected note books")); + } else if (collections.size() > 0) { + action->setData(false); + action->setText(i18ncp("@action", "Unlock Note Book", "Unlock Note Books", collections.size())); + action->setIcon(QIcon::fromTheme(QStringLiteral("emblem-locked"))); + action->setWhatsThis(i18n("Unlock selected note books")); } - action->setEnabled(hasLockedItems || hasLockedCollection); } - action = mActions.value(StandardNoteActionManager::CreateNote); + action = mGenericManager->action(StandardActionManager::DeleteCollections); + if (action) { + action->setEnabled(countUnlocked == collections.size()); + } + } else { + QAction *action = mActions.value(StandardNoteActionManager::LockUnlockNoteBook); + if (action) { + action->setEnabled(false); + } + } + + + if (mItemSelectionModel && mCollectionSelectionModel) { + const Collection::List collections = mParent->selectedCollections(); + const Item::List items = mParent->selectedItems(); + + QAction *action = mActions.value(StandardNoteActionManager::CreateNote); if (action) { Akonadi::Collection collection; if (collections.count() == 1) { collection = collections.first(); } else if (collections.count() == 0) { if (items.count() > 0) { collection = mItemSelectionModel->selectedRows().first().data(EntityTreeModel::ParentCollectionRole).value(); } } action->setEnabled(collection.isValid() && (collection.rights() & Akonadi::Collection::CanCreateItem) && (!collection.hasAttribute())); } - action = mActions.value(StandardNoteActionManager::ChangeColor); + action = mActions.value(StandardNoteActionManager::ChangeNoteColor); if (action) { action->setEnabled(collections.count() > 0 || items.count() > 0); } - } else { - if (mActions.contains(StandardNoteActionManager::Lock)) { - mActions[StandardNoteActionManager::Lock]->setEnabled(false); - } - if (mActions.contains(StandardNoteActionManager::Unlock)) { - mActions[StandardNoteActionManager::Unlock]->setEnabled(false); - } } Q_EMIT mParent->actionStateUpdated(); } - void slotLockUnlock(bool lock) { - if (!mItemSelectionModel || !mCollectionSelectionModel) { - return; - } - if ((lock && mInterceptedActions.contains(Lock)) || - (!lock && mInterceptedActions.contains(Unlock))) - { + void slotLockUnlockNote() { + if (!mItemSelectionModel || mInterceptedActions.contains(LockUnlockNote)) { return; } + const bool lock = mActions[StandardNoteActionManager::LockUnlockNote]->data().toBool(); const Item::List items = mParent->selectedItems(); for (auto item : items) { if (item.isValid()) { if (lock) { item.addAttribute(new NoteShared::NoteLockAttribute); } else { item.removeAttribute(); } new ItemModifyJob(item, mParent); } } + } + + void slotLockUnlockNoteBook() { + if (!mCollectionSelectionModel || mInterceptedActions.contains(LockUnlockNoteBook)) { + return; + } + const bool lock = mActions[StandardNoteActionManager::LockUnlockNoteBook]->data().toBool(); const Collection::List collections = mParent->selectedCollections(); for (auto collection : collections) { if (collection.isValid()) { if (lock) { collection.addAttribute(new NoteShared::NoteLockAttribute); } else { collection.removeAttribute(); } new CollectionModifyJob(collection, mParent); } } } void slotCreateNote() { if (mInterceptedActions.contains(CreateNote)) { return; } const Collection::List collections = mParent->selectedCollections(); if (collections.count() > 1) { return; } Akonadi::Collection collection; if (collections.count() == 1) { collection = collections.first(); } else { const Item::List items = mParent->selectedItems(); if (items.count() == 0) { return; } collection = items.first().parentCollection(); } auto *creatorAndSelector = new NoteShared::NoteCreatorAndSelector(mCollectionSelectionModel, mItemSelectionModel, mParent); creatorAndSelector->createNote(collection); } - void slotChangeColor() { - if (mInterceptedActions.contains(ChangeColor)) { + void slotChangeNoteColor() { + if (mInterceptedActions.contains(ChangeNoteColor)) { return; } QColor color = Qt::white; - const Collection::List collections = mParent->selectedCollections(); const Item::List items = mParent->selectedItems(); - if (collections.size() + items.size() == 1) { - const EntityDisplayAttribute *attr = (collections.size() == 1) - ? collections.first().attribute() - : items.first().attribute();; - + if (items.size() == 1) { + const EntityDisplayAttribute *attr = items.first().attribute(); if (attr) { color = attr->backgroundColor(); } } color = QColorDialog::getColor(color, mParentWidget, QString(), QColorDialog::ShowAlphaChannel); if (!color.isValid()) { return; } for (auto item : items) { item.attribute(Item::AddIfMissing)->setBackgroundColor(color); new ItemModifyJob(item, mParent); } + } + + void slotChangeNoteBookColor() { + if (mInterceptedActions.contains(ChangeNoteBookColor)) { + return; + } + QColor color = Qt::white; + const Collection::List collections = mParent->selectedCollections(); + if (collections.size() == 1) { + const EntityDisplayAttribute *attr = collections.first().attribute(); + if (attr) { + color = attr->backgroundColor(); + } + } + color = QColorDialog::getColor(color, mParentWidget, QString(), QColorDialog::ShowAlphaChannel); + if (!color.isValid()) { + return; + } for (auto collection : collections) { collection.attribute(Collection::AddIfMissing)->setBackgroundColor(color); new CollectionModifyJob(collection, mParent); } } KActionCollection *mActionCollection = nullptr; QWidget *mParentWidget = nullptr; std::unique_ptr mGenericManager; QItemSelectionModel *mCollectionSelectionModel = nullptr; QItemSelectionModel *mItemSelectionModel = nullptr; QHash mActions; QSet mInterceptedActions; StandardNoteActionManager *mParent = nullptr; }; StandardNoteActionManager::StandardNoteActionManager(KActionCollection *actionCollection, QWidget *parent) : QObject(parent) , d(std::make_unique(actionCollection, parent, this)) { } StandardNoteActionManager::~StandardNoteActionManager() = default; void StandardNoteActionManager::setCollectionSelectionModel(QItemSelectionModel *selectionModel) { d->mCollectionSelectionModel = selectionModel; d->mGenericManager->setCollectionSelectionModel(selectionModel); connect(selectionModel->model(), &QAbstractItemModel::dataChanged, this, [this]() { d->updateActions(); }); connect(selectionModel->model(), &QAbstractItemModel::rowsInserted, this, [this]() { d->updateActions(); }); connect(selectionModel->model(), &QAbstractItemModel::rowsRemoved, this, [this]() { d->updateActions(); }); connect(selectionModel, &QItemSelectionModel::selectionChanged, this, [this]() { d->updateActions(); }); d->updateActions(); } void StandardNoteActionManager::setItemSelectionModel(QItemSelectionModel *selectionModel) { d->mItemSelectionModel = selectionModel; d->mGenericManager->setItemSelectionModel(selectionModel); connect(selectionModel->model(), &QAbstractItemModel::dataChanged, this, [this]() { d->updateActions(); }); connect(selectionModel, &QItemSelectionModel::selectionChanged, this, [this]() { d->updateActions(); }); d->updateActions(); } QAction *StandardNoteActionManager::createAction(Type type) { QAction *action = d->mActions.value(type); if (action) { return action; } switch (type) { case CreateNote: action = new QAction(d->mParentWidget); action->setIcon(QIcon::fromTheme(QStringLiteral("document-new"))); action->setText(i18n("&New Note")); action->setWhatsThis(i18n("Add a new note to a selected note book")); d->mActions.insert(CreateNote, action); d->mActionCollection->addAction(QStringLiteral("akonadi_note_create"), action); d->mActionCollection->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_N)); connect(action, &QAction::triggered, this, [this](){ d->slotCreateNote(); }); break; - case Lock: + case LockUnlockNote: action = new QAction(d->mParentWidget); - action->setIcon(QIcon::fromTheme(QStringLiteral("emblem-locked"))); - action->setText(i18n("Lock Selected")); - action->setWhatsThis(i18n("Lock selected note book or notes")); - d->mActions.insert(Lock, action); - d->mActionCollection->addAction(QStringLiteral("akonadi_note_lock"), action); - connect(action, &QAction::triggered, this, [this]() { - d->slotLockUnlock(true); - }); + d->mActions.insert(LockUnlockNote, action); + d->mActionCollection->addAction(QStringLiteral("akonadi_note_lock_unlock"), action); + connect(action, &QAction::triggered, this, [this](){ d->slotLockUnlockNote(); }); break; - case Unlock: + case LockUnlockNoteBook: action = new QAction(d->mParentWidget); - action->setIcon(QIcon::fromTheme(QStringLiteral("emblem-unlocked"))); - action->setText(i18n("Unlock Selected")); - action->setWhatsThis(i18n("Unlock selected note books or notes")); - d->mActions.insert(Unlock, action); - d->mActionCollection->addAction(QStringLiteral("akonadi_note_unlock"), action); - connect(action, &QAction::triggered, this, [this]() { - d->slotLockUnlock(false); - }); + d->mActions.insert(LockUnlockNoteBook, action); + d->mActionCollection->addAction(QStringLiteral("akonadi_notebook_lock_unlock"), action); + connect(action, &QAction::triggered, this, [this](){ d->slotLockUnlockNoteBook(); }); + case ChangeNoteColor: + action = new QAction(d->mParentWidget); + action->setIcon(QIcon::fromTheme(QStringLiteral("format-fill-color"))); + action->setText(i18n("Change Note Color...")); + action->setWhatsThis(i18n("Changes the color of a selected notes")); + d->mActions.insert(ChangeNoteColor, action); + d->mActionCollection->addAction(QStringLiteral("akonadi_note_change_color"), action); + connect(action, &QAction::triggered, this, [this](){ d->slotChangeNoteColor(); }); break; - case ChangeColor: + case ChangeNoteBookColor: action = new QAction(d->mParentWidget); action->setIcon(QIcon::fromTheme(QStringLiteral("format-fill-color"))); - action->setText(i18n("Change Color...")); - action->setWhatsThis(i18n("Changes the color of a selected note books or notes")); - d->mActions.insert(ChangeColor, action); - d->mActionCollection->addAction(QStringLiteral("akonadi_change_color"), action); - connect(action, &QAction::triggered, this, [this](){ - d->slotChangeColor(); - }); + action->setText(i18n("Change Note Book Color...")); + action->setWhatsThis(i18n("Changes the color of a selected note books")); + d->mActions.insert(ChangeNoteColor, action); + d->mActionCollection->addAction(QStringLiteral("akonadi_notebook_change_color"), action); + connect(action, &QAction::triggered, this, [this](){ d->slotChangeNoteBookColor(); }); break; default: Q_ASSERT(false); // should never happen break; } return action; } QAction *StandardNoteActionManager::createAction(StandardActionManager::Type type) { QAction *act = d->mGenericManager->action(type); if (!act) { act = d->mGenericManager->createAction(type); } d->updateGenericAction(type); return act; } void StandardNoteActionManager::createAllActions() { (void)createAction(CreateNote); - (void)createAction(Lock); - (void)createAction(Unlock); - (void)createAction(ChangeColor); + (void)createAction(LockUnlockNote); + (void)createAction(LockUnlockNoteBook); + (void)createAction(ChangeNoteColor); + (void)createAction(ChangeNoteBookColor); d->mGenericManager->createAllActions(); d->updateGenericAllActions(); d->updateActions(); } QAction *StandardNoteActionManager::action(Type type) const { if (d->mActions.contains(type)) { return d->mActions.value(type); } return nullptr; } QAction *StandardNoteActionManager::action(StandardActionManager::Type type) const { return d->mGenericManager->action(type); } void StandardNoteActionManager::setActionText(StandardActionManager::Type type, const KLocalizedString &text) { d->mGenericManager->setActionText(type, text); } void StandardNoteActionManager::interceptAction(Type type, bool intercept) { if (intercept) { d->mInterceptedActions.insert(type); } else { d->mInterceptedActions.remove(type); } } void StandardNoteActionManager::interceptAction(StandardActionManager::Type type, bool intercept) { d->mGenericManager->interceptAction(type, intercept); } Collection::List StandardNoteActionManager::selectedCollections() const { return d->mGenericManager->selectedCollections(); } Item::List StandardNoteActionManager::selectedItems() const { return d->mGenericManager->selectedItems(); } void StandardNoteActionManager::setCollectionPropertiesPageNames(const QStringList &names) { d->mGenericManager->setCollectionPropertiesPageNames(names); } diff --git a/src/noteshared/standardnoteactionmanager.h b/src/noteshared/standardnoteactionmanager.h index fe23927..2667533 100644 --- a/src/noteshared/standardnoteactionmanager.h +++ b/src/noteshared/standardnoteactionmanager.h @@ -1,189 +1,190 @@ /* This file is part of KJots Copyright (c) 2020 Igor Poboiko 2009 - 2010 Tobias Koenig 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 AKONADI_STANDARDNOTESACTIONMANAGER_H #define AKONADI_STANDARDNOTESACTIONMANAGER_H #include #include class QAction; class KActionCollection; class QItemSelectionModel; class QWidget; namespace Akonadi { class Item; /** * @short Manages note specific actions for collection and item views. * * @author Igor Poboiko */ class StandardNoteActionManager : public QObject { Q_OBJECT public: /** * Describes the supported actions. */ enum Type { CreateNote = StandardActionManager::LastType + 1, ///< Creates a new note - Lock, ///< Locks a note or a note book - Unlock, ///< Unlocks a note or a note book - ChangeColor ///< Changes a color of a note or a note book + LockUnlockNote, ///< Locks or unlocks a note + LockUnlockNoteBook, ///< Locks or unlocks a note book + ChangeNoteColor, ///< Changes a color of a note + ChangeNoteBookColor ///< Changes a color of a note book }; /** * Creates a new standard note action manager. * * @param actionCollection The action collection to operate on. * @param parent The parent widget. */ explicit StandardNoteActionManager(KActionCollection *actionCollection, QWidget *parent = nullptr); /** * Destroys the standard note action manager. */ ~StandardNoteActionManager(); /** * Sets the collection selection model based on which the collection * related actions should operate. If none is set, all collection actions * will be disabled. * * @param selectionModel selection model for collections */ void setCollectionSelectionModel(QItemSelectionModel *selectionModel); /** * Sets the item selection model based on which the item related actions * should operate. If none is set, all item actions will be disabled. * * @param selectionModel the selection model for items */ void setItemSelectionModel(QItemSelectionModel *selectionModel); /** * Creates the action of the given type and adds it to the action collection * specified in the constructor if it does not exist yet. The action is * connected to its default implementation provided by this class. * * @param type the type of action to create */ Q_REQUIRED_RESULT QAction *createAction(Type type); /** * Creates the action of the given type and adds it to the action collection * specified in the constructor if it does not exist yet. The action is * connected to its default implementation provided by this class. * * @param type the type of action to create */ QAction *createAction(StandardActionManager::Type type); /** * Convenience method to create all standard actions. * @see createAction() */ void createAllActions(); /** * Returns the action of the given type, 0 if it has not been created (yet). */ QAction *action(Type type) const; /** * Returns the action of the given type, 0 if it has not been created (yet). * * @param type the type of action to return */ QAction *action(StandardActionManager::Type type) const; /** * Sets the label of the action @p type to @p text, which is used during * updating the action state and substituted according to the number of * selected objects. This is mainly useful to customize the label of actions * that can operate on multiple objects. * * Example: * @code * acctMgr->setActionText( Akonadi::StandardActionManager::CopyItems, * ki18np( "Copy Item", "Copy %1 Items" ) ); * @endcode */ void setActionText(StandardActionManager::Type type, const KLocalizedString &text); /** * Sets whether the default implementation for the given action @p type * shall be executed when the action is triggered. * * @param intercept If @c false, the default implementation will be executed, * if @c true no action is taken. */ void interceptAction(Type type, bool intercept = true); /** * Sets whether the default implementation for the given action @p type * shall be executed when the action is triggered. * * @param intercept If @c false, the default implementation will be executed, * if @c true no action is taken. */ void interceptAction(StandardActionManager::Type type, bool intercept = true); /** * Returns the list of collections that are currently selected. * The list is empty if no collection is currently selected. */ Q_REQUIRED_RESULT Akonadi::Collection::List selectedCollections() const; /** * Returns the list of items that are currently selected. * The list is empty if no item is currently selected. */ Q_REQUIRED_RESULT Akonadi::Item::List selectedItems() const; /** * @param names the list of names to set as collection properties page names */ void setCollectionPropertiesPageNames(const QStringList &names); Q_SIGNALS: /** * This signal is emitted whenever the action state has been updated. * In case you have special needs for changing the state of some actions, * connect to this signal and adjust the action state. */ void actionStateUpdated(); private: //@cond PRIVATE class Private; std::unique_ptr const d; //@endcond }; } #endif