File indexing completed on 2024-05-19 05:19:22
0001 /* 0002 This file is part of KJots. 0003 0004 SPDX-FileCopyrightText: 1997 Christoph Neerfeld <Christoph.Neerfeld@home.ivm.de> 0005 2002, 2003 Aaron J. Seigo <aseigo@kde.org> 0006 2003 Stanislav Kljuhhin <crz@hot.ee> 0007 2005-2006 Jaison Lee <lee.jaison@gmail.com> 0008 2007-2009 Stephen Kelly <steveire@gmail.com> 0009 2020 Igor Poboiko <igor.poboiko@gmail.com> 0010 0011 SPDX-License-Identifier: LGPL-2.0-or-later 0012 */ 0013 0014 #include "kjotswidget.h" 0015 0016 // Qt 0017 #include <QHBoxLayout> 0018 #include <QSplitter> 0019 #include <QStackedWidget> 0020 #include <QTextCursor> 0021 #include <QTextDocument> 0022 #include <QTextDocumentFragment> 0023 #include <QTimer> 0024 #include <QPrintDialog> 0025 #include <QPrinter> 0026 #include <QPrintPreviewDialog> 0027 #include <QDBusConnection> 0028 #include <QMenu> 0029 #include <QFileDialog> 0030 #include <QAction> 0031 #include <QIcon> 0032 #include <QItemDelegate> 0033 #include <QHeaderView> 0034 #include <QDebug> 0035 #include <QActionGroup> 0036 0037 // Akonadi 0038 #include <akonadi_version.h> 0039 #include <Akonadi/NoteUtils> 0040 #include <Akonadi/AttributeFactory> 0041 #include <Akonadi/CollectionCreateJob> 0042 #include <Akonadi/CollectionDeleteJob> 0043 #include <Akonadi/ChangeRecorder> 0044 #include <Akonadi/EntityDisplayAttribute> 0045 #include <Akonadi/EntityMimeTypeFilterModel> 0046 #include <Akonadi/Item> 0047 #include <Akonadi/ItemCreateJob> 0048 #include <Akonadi/ItemModifyJob> 0049 #include <Akonadi/ItemDeleteJob> 0050 #include <Akonadi/ItemFetchScope> 0051 #include <Akonadi/EntityOrderProxyModel> 0052 #include <Akonadi/EntityTreeView> 0053 #include <Akonadi/ETMViewStateSaver> 0054 #include <Akonadi/ControlGui> 0055 0056 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0057 #include <grantlee/template.h> 0058 #include <grantlee/engine.h> 0059 #include <grantlee/context.h> 0060 #else 0061 #include <KTextTemplate/Engine> 0062 #include <KTextTemplate/Template> 0063 #include <KTextTemplate/Context> 0064 #endif 0065 0066 // KDE 0067 #include <KActionCollection> 0068 #include <KBookmarkMenu> 0069 #include <KLocalizedString> 0070 #include <KMessageBox> 0071 #include <KSelectionProxyModel> 0072 #include <KXMLGUIClient> 0073 #include <KActionMenu> 0074 #include <KRandom> 0075 #include <KSharedConfig> 0076 #include <KConfigDialog> 0077 #include <KIO/OpenUrlJob> 0078 0079 #include <KPIMTextEdit/RichTextComposerActions> 0080 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0081 #include <KPIMTextEdit/RichTextEditorWidget> 0082 #else 0083 #include <TextCustomEditor/RichTextEditorWidget> 0084 #endif 0085 0086 0087 // KMime 0088 #include <KMime/Message> 0089 0090 // KJots 0091 #include "uistatesaver.h" 0092 #include "kjotsbookmarks.h" 0093 #include "kjotsmodel.h" 0094 #include "kjotsedit.h" 0095 #include "kjotsconfigdlg.h" 0096 #include "KJotsSettings.h" 0097 #include "kjotsbrowser.h" 0098 #include "noteshared/notelockattribute.h" 0099 #include "noteshared/notepinattribute.h" 0100 #include "noteshared/standardnoteactionmanager.h" 0101 #include "notesortproxymodel.h" 0102 0103 #include <memory> 0104 0105 using namespace Akonadi; 0106 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0107 using namespace Grantlee; 0108 #else 0109 using namespace KTextTemplate; 0110 #endif 0111 KJotsWidget::KJotsWidget(QWidget *parent, KXMLGUIClient *xmlGuiClient, Qt::WindowFlags f) 0112 : QWidget(parent, f) 0113 , m_xmlGuiClient(xmlGuiClient) 0114 { 0115 ControlGui::widgetNeedsAkonadi(this); 0116 0117 Akonadi::AttributeFactory::registerAttribute<NoteShared::NoteLockAttribute>(); 0118 Akonadi::AttributeFactory::registerAttribute<NoteShared::NotePinAttribute>(); 0119 0120 // Grantlee 0121 m_loader = QSharedPointer<FileSystemTemplateLoader>(new FileSystemTemplateLoader()); 0122 m_loader->setTemplateDirs(QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, 0123 QStringLiteral("kjots/themes"), 0124 QStandardPaths::LocateDirectory)); 0125 m_loader->setTheme(QStringLiteral("default")); 0126 m_templateEngine = new Engine(this); 0127 m_templateEngine->addTemplateLoader(m_loader); 0128 0129 // GUI & Actions 0130 setupGui(); 0131 setupActions(); 0132 // Models 0133 ItemFetchScope scope; 0134 scope.fetchFullPayload(true); // Need to have full item when adding it to the internal data structure 0135 scope.fetchAttribute<EntityDisplayAttribute>(); 0136 scope.fetchAttribute<NoteShared::NoteLockAttribute>(); 0137 scope.fetchAttribute<NoteShared::NotePinAttribute>(); 0138 0139 auto monitor = new ChangeRecorder(this); 0140 monitor->fetchCollection(true); 0141 monitor->setItemFetchScope(scope); 0142 monitor->setCollectionMonitored(Collection::root()); 0143 monitor->setMimeTypeMonitored(NoteUtils::noteMimeType()); 0144 0145 m_kjotsModel = new KJotsModel(monitor, this); 0146 0147 m_browserWidget->browser()->setModel(m_kjotsModel); 0148 0149 m_collectionModel = new EntityMimeTypeFilterModel(this); 0150 m_collectionModel->setSourceModel(m_kjotsModel); 0151 m_collectionModel->addMimeTypeInclusionFilter(Collection::mimeType()); 0152 m_collectionModel->setHeaderGroup(EntityTreeModel::CollectionTreeHeaders); 0153 m_collectionModel->setDynamicSortFilter(true); 0154 m_collectionModel->setSortCaseSensitivity(Qt::CaseInsensitive); 0155 0156 m_orderProxy = new EntityOrderProxyModel(this); 0157 m_orderProxy->setSourceModel(m_collectionModel); 0158 KConfigGroup cfg(KSharedConfig::openConfig(), QStringLiteral("KJotsEntityOrder")); 0159 m_orderProxy->setOrderConfig(cfg); 0160 0161 m_collectionView->setModel(m_orderProxy); 0162 0163 m_collectionSelectionProxyModel = new KSelectionProxyModel(m_collectionView->selectionModel(), this); 0164 m_collectionSelectionProxyModel->setSourceModel(m_kjotsModel); 0165 m_collectionSelectionProxyModel->setFilterBehavior(KSelectionProxyModel::ChildrenOfExactSelection); 0166 0167 m_itemModel = new EntityMimeTypeFilterModel(this); 0168 m_itemModel->setSourceModel(m_collectionSelectionProxyModel); 0169 m_itemModel->addMimeTypeExclusionFilter(Collection::mimeType()); 0170 m_itemModel->setHeaderGroup(EntityTreeModel::ItemListHeaders); 0171 0172 m_itemSortModel = new NoteSortProxyModel(this); 0173 m_itemSortModel->setSourceModel(m_itemModel); 0174 m_itemSortModel->setSortRole(Qt::EditRole); 0175 0176 m_itemView->setModel(m_itemSortModel); 0177 0178 m_actionManager->setCollectionSelectionModel(m_collectionView->selectionModel()); 0179 m_actionManager->setItemSelectionModel(m_itemView->selectionModel()); 0180 0181 connect(m_kjotsModel, &EntityTreeModel::modelAboutToBeReset, this, &KJotsWidget::saveState); 0182 connect(m_kjotsModel, &EntityTreeModel::modelReset, this, &KJotsWidget::restoreState); 0183 0184 // TODO: handle dataChanged properly, i.e. if item was changed from outside 0185 connect(m_collectionView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &KJotsWidget::renderSelection); 0186 connect(m_collectionView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &KJotsWidget::updateMenu); 0187 connect(m_collectionView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &KJotsWidget::updateCaption); 0188 connect(m_collectionView->model(), &QAbstractItemModel::dataChanged, this, &KJotsWidget::renderSelection); 0189 connect(m_collectionView->model(), &QAbstractItemModel::dataChanged, this, &KJotsWidget::updateCaption); 0190 0191 connect(m_itemView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &KJotsWidget::renderSelection); 0192 connect(m_itemView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &KJotsWidget::updateMenu); 0193 connect(m_itemView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &KJotsWidget::updateCaption); 0194 connect(m_itemView->model(), &QAbstractItemModel::dataChanged, this, &KJotsWidget::renderSelection); 0195 connect(m_itemView->model(), &QAbstractItemModel::dataChanged, this, &KJotsWidget::updateCaption); 0196 0197 connect(m_editor, &KJotsEdit::documentModified, this, &KJotsWidget::updateCaption); 0198 0199 QTimer::singleShot(0, this, &KJotsWidget::delayedInitialization); 0200 0201 // Autosave timer 0202 m_autosaveTimer = new QTimer(this); 0203 updateConfiguration(); 0204 connect(m_autosaveTimer, &QTimer::timeout, m_editor, &KJotsEdit::savePage); 0205 connect(m_collectionView->selectionModel(), &QItemSelectionModel::selectionChanged, m_autosaveTimer, qOverload<>(&QTimer::start)); 0206 0207 restoreState(); 0208 0209 QDBusConnection::sessionBus().registerObject(QStringLiteral("/KJotsWidget"), this, QDBusConnection::ExportScriptableContents); 0210 } 0211 0212 KJotsWidget::~KJotsWidget() 0213 { 0214 saveState(); 0215 } 0216 0217 void KJotsWidget::setupGui() 0218 { 0219 // Main horizontal layout 0220 auto layout = new QHBoxLayout(this); 0221 layout->setContentsMargins(0, 0, 0, 0); 0222 0223 // Splitter between (collection view) and (item view + editor) 0224 m_splitter1 = new QSplitter(this); 0225 m_splitter1->setObjectName(QStringLiteral("CollectionSplitter")); 0226 m_splitter1->setStretchFactor(1, 1); 0227 layout->addWidget(m_splitter1); 0228 0229 // Collection view 0230 m_collectionView = new EntityTreeView(m_xmlGuiClient, m_splitter1); 0231 m_collectionView->setObjectName(QStringLiteral("CollectionView")); 0232 m_collectionView->setSelectionMode(QAbstractItemView::ExtendedSelection); 0233 m_collectionView->setEditTriggers(QAbstractItemView::DoubleClicked); 0234 m_collectionView->setManualSortingActive(true); 0235 m_collectionView->header()->setDefaultAlignment(Qt::AlignCenter); 0236 0237 // Splitter between item view and editor 0238 m_splitter2 = new QSplitter(m_splitter1); 0239 m_splitter2->setObjectName(QStringLiteral("EditorSplitter")); 0240 0241 // Item view 0242 m_itemView = new EntityTreeView(m_xmlGuiClient, m_splitter2); 0243 m_itemView->setObjectName(QStringLiteral("ItemView")); 0244 m_itemView->setSelectionMode(QAbstractItemView::ExtendedSelection); 0245 m_itemView->setSelectionBehavior(QAbstractItemView::SelectRows); 0246 m_itemView->setEditTriggers(QAbstractItemView::DoubleClicked); 0247 m_itemView->setRootIsDecorated(false); 0248 m_itemView->header()->setDefaultAlignment(Qt::AlignCenter); 0249 0250 // Stacked widget containing editor & browser 0251 m_stackedWidget = new QStackedWidget(m_splitter2); 0252 0253 // Editor 0254 m_editor = new KJotsEdit(m_stackedWidget, m_xmlGuiClient->actionCollection()); 0255 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0256 m_editorWidget = new KPIMTextEdit::RichTextEditorWidget(m_editor, m_stackedWidget); 0257 #else 0258 m_editorWidget = new TextCustomEditor::RichTextEditorWidget(m_editor, m_stackedWidget); 0259 #endif 0260 m_editor->setParent(m_editorWidget); 0261 m_stackedWidget->addWidget(m_editorWidget); 0262 connect(m_editor, &KJotsEdit::linkClicked, this, &KJotsWidget::openLink); 0263 // Browser 0264 m_browserWidget = new KJotsBrowserWidget(std::make_unique<KJotsBrowser>(m_xmlGuiClient->actionCollection()), 0265 m_stackedWidget); 0266 m_stackedWidget->addWidget(m_browserWidget); 0267 m_stackedWidget->setCurrentWidget(m_browserWidget); 0268 connect(m_browserWidget->browser(), &KJotsBrowser::linkClicked, this, &KJotsWidget::openLink); 0269 0270 // Make sure the editor gets focus again after naming a new book/page. 0271 connect(m_collectionView->itemDelegate(), &QItemDelegate::closeEditor, this, [this](){ 0272 activeEditor()->setFocus(); 0273 }); 0274 } 0275 0276 void KJotsWidget::restoreState() 0277 { 0278 { 0279 auto saver = new ETMViewStateSaver; 0280 saver->setView(m_collectionView); 0281 KConfigGroup cfg(KSharedConfig::openConfig(), QStringLiteral("CollectionViewState")); 0282 saver->restoreState(cfg); 0283 } 0284 { 0285 auto saver = new ETMViewStateSaver; 0286 saver->setView(m_itemView); 0287 KConfigGroup cfg(KSharedConfig::openConfig(), QStringLiteral("ItemViewState")); 0288 saver->restoreState(cfg); 0289 } 0290 } 0291 0292 void KJotsWidget::saveState() 0293 { 0294 { 0295 ETMViewStateSaver saver; 0296 saver.setView(m_collectionView); 0297 KConfigGroup cfg(KSharedConfig::openConfig(), QStringLiteral("CollectionViewState")); 0298 saver.saveState(cfg); 0299 cfg.sync(); 0300 } 0301 { 0302 ETMViewStateSaver saver; 0303 saver.setView(m_itemView); 0304 KConfigGroup cfg(KSharedConfig::openConfig(), QStringLiteral("ItemViewState")); 0305 saver.saveState(cfg); 0306 cfg.sync(); 0307 } 0308 } 0309 0310 void KJotsWidget::saveUIStates() const 0311 { 0312 const QString groupName = QStringLiteral("UiState_MainWidget_%1").arg(KJotsSettings::viewMode()); 0313 KConfigGroup group(KSharedConfig::openConfig(), groupName); 0314 KJots::UiStateSaver::saveState(m_splitter1, group); 0315 KJots::UiStateSaver::saveState(m_splitter2, group); 0316 KJots::UiStateSaver::saveState(m_collectionView, group); 0317 KJots::UiStateSaver::saveState(m_itemView, group); 0318 group.sync(); 0319 } 0320 0321 void KJotsWidget::restoreUIStates() 0322 { 0323 const QString groupName = QStringLiteral("UiState_MainWidget_%1").arg(KJotsSettings::viewMode()); 0324 KConfigGroup group(KSharedConfig::openConfig(), groupName); 0325 KJots::UiStateSaver::restoreState(m_splitter1, group); 0326 KJots::UiStateSaver::restoreState(m_splitter2, group); 0327 KJots::UiStateSaver::restoreState(m_collectionView, group); 0328 KJots::UiStateSaver::restoreState(m_itemView, group); 0329 group.sync(); 0330 } 0331 0332 void KJotsWidget::setViewMode(int mode) 0333 { 0334 const int newMode = (mode == 0) ? KJotsSettings::viewMode() : mode; 0335 m_splitter2->setOrientation(newMode == 1 ? Qt::Vertical : Qt::Horizontal); 0336 if (mode != 0) { 0337 KJotsSettings::setViewMode(mode); 0338 saveUIStates(); 0339 } 0340 restoreUIStates(); 0341 m_viewModeGroup->actions().at(newMode-1)->setChecked(true); 0342 } 0343 0344 void KJotsWidget::setupActions() 0345 { 0346 KActionCollection *actionCollection = m_xmlGuiClient->actionCollection(); 0347 // Default Akonadi Notes actions 0348 m_actionManager = new StandardNoteActionManager(actionCollection, this); 0349 m_actionManager->createAllActions(); 0350 0351 actionCollection->setDefaultShortcut(m_actionManager->action(StandardActionManager::CreateCollection), 0352 QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_N)); 0353 0354 QAction *action; 0355 // Standard actions 0356 KStandardAction::preferences(this, &KJotsWidget::configure, actionCollection); 0357 0358 action = KStandardAction::next(this, [this](){ 0359 m_itemView->selectionModel()->select(previousNextEntity(m_itemView, +1), QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); 0360 }, actionCollection); 0361 actionCollection->setDefaultShortcut(action, QKeySequence(Qt::CTRL | Qt::Key_PageDown)); 0362 connect(this, &KJotsWidget::canGoNextPageChanged, action, &QAction::setEnabled); 0363 0364 action = KStandardAction::prior(this, [this](){ 0365 m_itemView->selectionModel()->select(previousNextEntity(m_itemView, -1), QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); 0366 }, actionCollection); 0367 actionCollection->setDefaultShortcut(action, QKeySequence(Qt::CTRL | Qt::Key_PageUp)); 0368 connect(this, &KJotsWidget::canGoPreviousPageChanged, action, &QAction::setEnabled); 0369 0370 KStandardAction::renameFile(this, [this](){ 0371 EntityTreeView *activeView; 0372 if (m_collectionView->hasFocus()) { 0373 activeView = m_collectionView; 0374 } else { 0375 activeView = m_itemView; 0376 } 0377 0378 const QModelIndexList rows = activeView->selectionModel()->selectedRows(); 0379 if (rows.size() != 1) { 0380 return; 0381 } 0382 activeView->edit(rows.first()); 0383 }, actionCollection); 0384 0385 action = KStandardAction::deleteFile(this, [this](){ 0386 if (m_collectionView->hasFocus()) { 0387 m_actionManager->action(StandardActionManager::DeleteCollections)->trigger(); 0388 } else { 0389 m_actionManager->action(StandardActionManager::DeleteItems)->trigger(); 0390 } 0391 }, actionCollection); 0392 // Default Shift+Delete is ambiguous and used both for cut and delete 0393 actionCollection->setDefaultShortcut(action, QKeySequence(Qt::CTRL | Qt::Key_Delete)); 0394 0395 action = KStandardAction::copy(this, [this](){ 0396 activeEditor()->copy(); 0397 }, actionCollection); 0398 connect(m_editor, &KJotsEdit::copyAvailable, action, &QAction::setEnabled); 0399 connect(m_browserWidget->browser(), &KJotsBrowser::copyAvailable, action, &QAction::setEnabled); 0400 action->setEnabled(false); 0401 0402 action = KStandardAction::selectAll(this, [this](){ 0403 activeEditor()->selectAll(); 0404 }, actionCollection); 0405 KStandardAction::find(this, [this](){ 0406 if (m_editorWidget->isVisible()) { 0407 m_editorWidget->slotFind(); 0408 } else { 0409 m_browserWidget->slotFind(); 0410 } 0411 }, actionCollection); 0412 KStandardAction::findNext(this, [this](){ 0413 if (m_editorWidget->isVisible()) { 0414 m_editorWidget->slotFindNext(); 0415 } else { 0416 m_browserWidget->slotFindNext(); 0417 } 0418 }, actionCollection); 0419 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0420 action = KStandardAction::replace(m_editorWidget, &KPIMTextEdit::RichTextEditorWidget::slotReplace, actionCollection); 0421 #else 0422 action = KStandardAction::replace(m_editorWidget, &TextCustomEditor::RichTextEditorWidget::slotReplace, actionCollection); 0423 #endif 0424 connect(m_stackedWidget, &QStackedWidget::currentChanged, this, [this, action](int index){ 0425 action->setEnabled(m_stackedWidget->widget(index) == m_editorWidget); 0426 }); 0427 0428 KStandardAction::print(this, &KJotsWidget::printSelection, actionCollection); 0429 KStandardAction::printPreview(this, &KJotsWidget::printPreviewSelection, actionCollection); 0430 0431 // Bookmarks actions 0432 auto *bookmarkMenu = actionCollection->add<KActionMenu>(QStringLiteral("bookmarks")); 0433 bookmarkMenu->setText(i18n("&Bookmarks")); 0434 auto bookmarks = new KJotsBookmarks(m_collectionView->selectionModel(), this); 0435 connect(bookmarks, &KJotsBookmarks::openLink, this, &KJotsWidget::openLink); 0436 #if QT_VERSION_MAJOR < 6 0437 m_bookmarkManager = KBookmarkManager::managerForFile( 0438 QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + 0439 QStringLiteral("/kjots/bookmarks.xml"), QStringLiteral("kjots")); 0440 auto bmm = new KBookmarkMenu(m_bookmarkManager, 0441 bookmarks, bookmarkMenu->menu()); 0442 #else 0443 m_bookmarkManager = new KBookmarkManager( 0444 QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + 0445 QStringLiteral("/kjots/bookmarks.xml"), this); 0446 auto bmm = new KBookmarkMenu(m_bookmarkManager, bookmarks, 0447 bookmarkMenu->menu()); 0448 #endif 0449 0450 // "Add bookmark" and "make text bold" actions have conflicting shortcuts 0451 // (ctrl + b) Make add_bookmark use ctrl+shift+b to resolve that. 0452 action = bmm->addBookmarkAction(); 0453 actionCollection->addAction(QStringLiteral("add_bookmark"), action); 0454 actionCollection->setDefaultShortcut(action, Qt::CTRL | Qt::SHIFT | Qt::Key_B); 0455 actionCollection->addAction(QStringLiteral("edit_bookmark"), bmm->editBookmarksAction()); 0456 actionCollection->addAction(QStringLiteral("add_bookmarks_list"), bmm->bookmarkTabsAsFolderAction()); 0457 0458 // Export actions 0459 auto *exportMenu = actionCollection->add<KActionMenu>(QStringLiteral("save_to")); 0460 exportMenu->setText(i18n("Export")); 0461 exportMenu->setIcon(QIcon::fromTheme(QStringLiteral("document-export"))); 0462 0463 action = actionCollection->addAction(QStringLiteral("save_to_ascii")); 0464 action->setText(i18n("To Text File...")); 0465 action->setIcon(QIcon::fromTheme(QStringLiteral("text-plain"))); 0466 connect(action, &QAction::triggered, this, [this](){ 0467 exportSelection(QStringLiteral("plain_text"), QStringLiteral("template.txt")); 0468 }); 0469 exportMenu->menu()->addAction(action); 0470 0471 action = actionCollection->addAction(QStringLiteral("save_to_html")); 0472 action->setText(i18n("To HTML File...")); 0473 action->setIcon(QIcon::fromTheme(QStringLiteral("text-html"))); 0474 connect(action, &QAction::triggered, this, [this](){ 0475 exportSelection(QStringLiteral("default"), QStringLiteral("template.html")); 0476 }); 0477 exportMenu->menu()->addAction(action); 0478 0479 // Move actions 0480 action = actionCollection->addAction(QStringLiteral("go_next_book")); 0481 action->setText(i18n("Next Book")); 0482 action->setIcon(QIcon::fromTheme(QStringLiteral("go-down"))); 0483 actionCollection->setDefaultShortcut(action, QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_PageDown)); 0484 connect(action, &QAction::triggered, this, [this](){ 0485 const QModelIndex idx = previousNextEntity(m_collectionView, +1); 0486 m_collectionView->selectionModel()->select(idx, QItemSelectionModel::SelectCurrent); 0487 m_collectionView->expand(idx); 0488 }); 0489 connect(this, &KJotsWidget::canGoNextBookChanged, action, &QAction::setEnabled); 0490 0491 action = actionCollection->addAction(QStringLiteral("go_prev_book")); 0492 action->setText(i18n("Previous Book")); 0493 action->setIcon(QIcon::fromTheme(QStringLiteral("go-up"))); 0494 actionCollection->setDefaultShortcut(action, QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_PageUp)); 0495 connect(action, &QAction::triggered, this, [this](){ 0496 const QModelIndex idx = previousNextEntity(m_collectionView, -1); 0497 m_collectionView->selectionModel()->select(idx, QItemSelectionModel::SelectCurrent); 0498 m_collectionView->expand(idx); 0499 }); 0500 connect(this, &KJotsWidget::canGoPreviousBookChanged, action, &QAction::setEnabled); 0501 0502 // View mode actions 0503 m_viewModeGroup = new QActionGroup(this); 0504 0505 action = new QAction(i18nc("@action:inmenu", "Two Columns"), m_viewModeGroup); 0506 action->setCheckable(true); 0507 action->setData(1); 0508 actionCollection->addAction(QStringLiteral("view_mode_two_columns"), action); 0509 0510 action = new QAction(i18nc("@action:inmenu", "Three Columns"), m_viewModeGroup); 0511 action->setCheckable(true); 0512 action->setData(2); 0513 actionCollection->addAction(QStringLiteral("view_mode_three_columns"), action); 0514 0515 connect(m_viewModeGroup, &QActionGroup::triggered, this, [this](QAction *action){ 0516 setViewMode(action->data().toInt()); 0517 }); 0518 } 0519 0520 void KJotsWidget::delayedInitialization() 0521 { 0522 // anySelectionActions are available when at least something is selected (i.e. editor/browser are not empty 0523 0524 KActionCollection *actionCollection = m_xmlGuiClient->actionCollection(); 0525 0526 // Actions for a single item selection. 0527 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0528 anySelectionActions = { actionCollection->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Find))), 0529 actionCollection->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Print))), 0530 #else 0531 anySelectionActions = { actionCollection->action(KStandardAction::name(KStandardAction::Find)), 0532 actionCollection->action(KStandardAction::name(KStandardAction::Print)), 0533 #endif 0534 actionCollection->action(QStringLiteral("save_to")) }; 0535 0536 updateMenu(); 0537 0538 // Load view mode and splitters 0539 setViewMode(0); 0540 } 0541 0542 inline QTextEdit *KJotsWidget::activeEditor() 0543 { 0544 if (m_browserWidget->isVisible()) { 0545 return m_browserWidget->browser(); 0546 } else { 0547 return m_editor; 0548 } 0549 } 0550 0551 void KJotsWidget::updateMenu() 0552 { 0553 const int collectionsSelected = m_collectionView->selectionModel()->selectedRows().count(); 0554 const int itemsSelected = m_itemView->selectionModel()->selectedRows().count(); 0555 const int selectionSize = itemsSelected + collectionsSelected; 0556 0557 // Actions available only when editor is shown 0558 m_editor->setEnableActions(itemsSelected == 1 && !m_editor->locked()); 0559 0560 // Rename is available only when single something is selected 0561 m_xmlGuiClient->actionCollection() 0562 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0563 ->action(QString::fromLatin1(KStandardAction::name(KStandardAction::RenameFile))) 0564 #else 0565 ->action(KStandardAction::name(KStandardAction::RenameFile)) 0566 #endif 0567 ->setEnabled((itemsSelected == 1) || (m_collectionView->hasFocus() && collectionsSelected == 1)); 0568 0569 // Actions available when at least something is shown 0570 for (QAction *action : std::as_const(anySelectionActions)) { 0571 action->setEnabled(selectionSize >= 1); 0572 } 0573 } 0574 0575 void KJotsWidget::configure() 0576 { 0577 if (KConfigDialog::showDialog(QStringLiteral("kjotssettings"))) { 0578 return; 0579 } 0580 auto* dialog = new KConfigDialog(this, QStringLiteral("kjotssettings"), KJotsSettings::self()); 0581 auto configMisc = new KJotsConfigMisc(dialog); 0582 #if KCMUTILS_VERSION < QT_VERSION_CHECK(5, 240, 0) 0583 dialog->addPage(configMisc, i18nc("@title:window config dialog page", "Misc"), QStringLiteral("preferences-other")); 0584 #else 0585 dialog->addPage(configMisc->widget(), i18nc("@title:window config dialog page", "Misc"), QStringLiteral("preferences-other")); 0586 #endif 0587 connect(dialog, &KConfigDialog::settingsChanged, this, &KJotsWidget::updateConfiguration); 0588 dialog->show(); 0589 } 0590 0591 void KJotsWidget::updateConfiguration() 0592 { 0593 if (KJotsSettings::autoSave()) { 0594 m_autosaveTimer->setInterval(KJotsSettings::autoSaveInterval() * 1000 * 60); 0595 m_autosaveTimer->start(); 0596 } else { 0597 m_autosaveTimer->stop(); 0598 } 0599 } 0600 0601 QString KJotsWidget::renderSelectionTo(const QString &theme, const QString &templ) 0602 { 0603 QList<QVariant> objectList; 0604 0605 const QModelIndexList selectedItems = m_itemView->selectionModel()->selectedRows(); 0606 if (selectedItems.count() > 0) { 0607 objectList.reserve(selectedItems.size()); 0608 std::transform(selectedItems.cbegin(), selectedItems.cend(), std::back_inserter(objectList), 0609 [](const QModelIndex &idx){ 0610 return idx.data(KJotsModel::GrantleeObjectRole); 0611 }); 0612 } else { 0613 const QModelIndexList selectedCollections = m_collectionView->selectionModel()->selectedRows(); 0614 objectList.reserve(selectedCollections.size()); 0615 std::transform(selectedCollections.cbegin(), selectedCollections.cend(), std::back_inserter(objectList), 0616 [](const QModelIndex &idx){ 0617 return idx.data(KJotsModel::GrantleeObjectRole); 0618 }); 0619 } 0620 QHash<QString, QVariant> hash = {{QStringLiteral("entities"), objectList}, 0621 {QStringLiteral("i18n_TABLE_OF_CONTENTS"), i18nc("Header for 'Table of contents' section of rendered output", "Table of contents")}}; 0622 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0623 Context c(hash); 0624 #else 0625 KTextTemplate::Context c(hash); 0626 #endif 0627 0628 const QString currentTheme = m_loader->themeName(); 0629 m_loader->setTheme(theme); 0630 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0631 Template t = m_templateEngine->loadByName(templ); 0632 #else 0633 KTextTemplate::Template t = m_templateEngine->loadByName(templ); 0634 #endif 0635 const QString result = t->render(&c); 0636 m_loader->setTheme(currentTheme); 0637 return result; 0638 } 0639 0640 QString KJotsWidget::renderSelectionToHtml() 0641 { 0642 return renderSelectionTo(QStringLiteral("default"), QStringLiteral("template.html")); 0643 } 0644 0645 void KJotsWidget::exportSelection(const QString &theme, const QString &templ) 0646 { 0647 // TODO: dialog captions & etc 0648 QString filename = QFileDialog::getSaveFileName(); 0649 if (filename.isEmpty()) { 0650 return; 0651 } 0652 QFile exportFile(filename); 0653 if (!exportFile.open(QIODevice::WriteOnly | QIODevice::Text)) { 0654 KMessageBox::error(this, i18n("<qt>Could not open \"%1\" for writing</qt>", filename)); 0655 return; 0656 } 0657 exportFile.write(renderSelectionTo(theme, templ).toUtf8()); 0658 exportFile.close(); 0659 } 0660 0661 std::unique_ptr<QPrinter> KJotsWidget::setupPrinter(QPrinter::PrinterMode mode) 0662 { 0663 auto printer = std::make_unique<QPrinter>(mode); 0664 printer->setDocName(QStringLiteral("KJots_Print")); 0665 printer->setCreator(QStringLiteral("KJots")); 0666 if (!activeEditor()->textCursor().selection().isEmpty()) { 0667 printer->setPrintRange(QPrinter::Selection); 0668 } 0669 return printer; 0670 } 0671 0672 void KJotsWidget::printPreviewSelection() 0673 { 0674 auto printer = setupPrinter(QPrinter::ScreenResolution); 0675 QPrintPreviewDialog previewdlg(printer.get(), this); 0676 connect(&previewdlg, &QPrintPreviewDialog::paintRequested, this, &KJotsWidget::print); 0677 previewdlg.exec(); 0678 } 0679 0680 void KJotsWidget::printSelection() 0681 { 0682 auto printer = setupPrinter(QPrinter::HighResolution); 0683 QPrintDialog printDialog(printer.get(), this); 0684 if (printDialog.exec() == QDialog::Accepted) { 0685 print(printer.get()); 0686 } 0687 } 0688 0689 void KJotsWidget::print(QPrinter *printer) 0690 { 0691 QTextDocument printDocument; 0692 if (printer->printRange() == QPrinter::Selection) { 0693 printDocument.setHtml(activeEditor()->textCursor().selection().toHtml()); 0694 } else { 0695 QString currentTheme = m_loader->themeName(); 0696 m_loader->setTheme(QStringLiteral("default")); 0697 printDocument.setHtml(renderSelectionToHtml()); 0698 m_loader->setTheme(currentTheme); 0699 } 0700 printDocument.print(printer); 0701 } 0702 0703 QModelIndex KJotsWidget::previousNextEntity(QTreeView *view, int step) 0704 { 0705 const QModelIndexList selection = view->selectionModel()->selectedRows(); 0706 if (selection.size() == 0) { 0707 return step > 0 ? view->model()->index(0, 0) : view->model()->index(view->model()->rowCount()-1, 0); 0708 } 0709 if (selection.size() != 1) { 0710 return {}; 0711 } 0712 return step > 0 ? view->indexBelow(selection.first()) : view->indexAbove(selection.first()); 0713 } 0714 0715 void KJotsWidget::renderSelection() 0716 { 0717 Q_EMIT canGoNextBookChanged(previousNextEntity(m_collectionView, +1).isValid()); 0718 Q_EMIT canGoNextPageChanged(previousNextEntity(m_itemView, +1).isValid()); 0719 Q_EMIT canGoPreviousBookChanged(previousNextEntity(m_collectionView, -1).isValid()); 0720 Q_EMIT canGoPreviousPageChanged(previousNextEntity(m_itemView, -1).isValid()); 0721 0722 const QModelIndexList selectedItems = m_itemView->selectionModel()->selectedRows(); 0723 0724 // If the selection is a single note, present it for editing... 0725 if (selectedItems.count() == 1) { 0726 if (m_editor->setModelIndex(selectedItems.first())) { 0727 m_stackedWidget->setCurrentWidget(m_editorWidget); 0728 return; 0729 } 0730 // If something went wrong, we show user the browser 0731 } 0732 // ... Otherwise, render the selection read-only. 0733 m_browserWidget->browser()->setHtml(renderSelectionToHtml()); 0734 m_stackedWidget->setCurrentWidget(m_browserWidget); 0735 } 0736 0737 void KJotsWidget::updateCaption() 0738 { 0739 QString caption; 0740 const QModelIndexList itemSelection = m_itemView->selectionModel()->selectedRows(); 0741 const QModelIndexList collectionSelection = m_collectionView->selectionModel()->selectedRows(); 0742 if (itemSelection.size() == 1) { 0743 caption = KJotsModel::itemPath(KJotsModel::etmIndex(itemSelection.first())); 0744 if (m_editor->modified()) { 0745 caption.append(QStringLiteral(" *")); 0746 } 0747 } else if (itemSelection.size() == 0 && collectionSelection.size() == 1) { 0748 caption = KJotsModel::itemPath(collectionSelection.first()); 0749 } else if (itemSelection.size() > 1 || collectionSelection.size() > 1) { 0750 caption = i18nc("@title:window", "Multiple selection"); 0751 } 0752 0753 Q_EMIT captionChanged(caption); 0754 } 0755 0756 bool KJotsWidget::queryClose() 0757 { 0758 // Saving the current note 0759 // We cannot use async interface (i.e. ETM) here 0760 // because we need to abort the close if something went wrong 0761 const QModelIndexList selection = m_itemView->selectionModel()->selectedRows(); 0762 if ((selection.size() == 1) && (m_editor->document()->isModified())) { 0763 QModelIndex idx = selection.first(); 0764 m_editor->prepareDocumentForSaving(); 0765 auto job = new ItemModifyJob(KJotsModel::updateItem(idx.data(EntityTreeModel::ItemRole).value<Item>(), m_editor->document())); 0766 if (!job->exec()) { 0767 int res = KMessageBox::warningContinueCancelDetailed(this, 0768 i18n("Unable to save the note.\n" 0769 "You can save your note to a local file using the \"File - Export\" menu,\n" 0770 "otherwise you will lose your changes!\n" 0771 "Do you want to close anyways?"), 0772 i18n("Close Document"), 0773 KStandardGuiItem::quit(), 0774 KStandardGuiItem::cancel(), 0775 QString(), 0776 KMessageBox::Notify, 0777 i18n("Error message:\n" 0778 "%1", job->errorString())); 0779 if (res == KMessageBox::Cancel) { 0780 return false; 0781 } 0782 } 0783 } 0784 0785 saveUIStates(); 0786 KJotsSettings::self()->save(); 0787 m_orderProxy->saveOrder(); 0788 0789 return true; 0790 } 0791 0792 void KJotsWidget::openLink(const QUrl &url) 0793 { 0794 if (url.scheme() == QStringLiteral("akonadi")) { 0795 QModelIndex idx = KJotsModel::modelIndexForUrl(m_kjotsModel, url); 0796 0797 // Trying to map it to collection view model 0798 QModelIndex colIdx = m_collectionModel->mapFromSource(idx); 0799 if (colIdx.isValid()) { 0800 colIdx = m_orderProxy->mapFromSource(colIdx); 0801 m_collectionView->selectionModel()->select(colIdx, QItemSelectionModel::SelectCurrent); 0802 m_itemView->selectionModel()->clearSelection(); 0803 } else { 0804 // Selecting parent collection 0805 QModelIndex parentCollectionIdx = EntityTreeModel::modelIndexForCollection(m_collectionView->model(), 0806 idx.data(EntityTreeModel::ParentCollectionRole).value<Collection>()); 0807 m_collectionView->selectionModel()->select(parentCollectionIdx, QItemSelectionModel::SelectCurrent); 0808 0809 // Mapping idx to item view model 0810 idx = m_collectionSelectionProxyModel->mapFromSource(idx); 0811 idx = m_itemModel->mapFromSource(idx); 0812 m_itemView->selectionModel()->select(idx, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); 0813 } 0814 } else { 0815 auto job = new KIO::OpenUrlJob(url, this); 0816 job->start(); 0817 } 0818 } 0819 0820 #include "moc_kjotswidget.cpp"