File indexing completed on 2024-05-05 05:51:26

0001 /* This file is part of the KDE project
0002    SPDX-FileCopyrightText: 2010 Thomas Fjellstrom <thomas@fjellstrom.ca>
0003 
0004    SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 // BEGIN Includes
0008 
0009 #include "katefiletreeplugin.h"
0010 #include "katefiletree.h"
0011 #include "katefiletreeconfigpage.h"
0012 #include "katefiletreemodel.h"
0013 #include "katefiletreeproxymodel.h"
0014 
0015 #include <ktexteditor/application.h>
0016 #include <ktexteditor/view.h>
0017 
0018 #include <KActionCollection>
0019 #include <KConfigGroup>
0020 #include <KLocalizedString>
0021 #include <KPluginFactory>
0022 #include <KXMLGUIFactory>
0023 #include <KXmlGuiWindow>
0024 
0025 #include <QAction>
0026 #include <QLayout>
0027 #include <QLineEdit>
0028 #include <QStyle>
0029 #include <QToolBar>
0030 
0031 #include "katefiletreedebug.h"
0032 
0033 // END Includes
0034 
0035 K_PLUGIN_FACTORY_WITH_JSON(KateFileTreeFactory, "katefiletreeplugin.json", registerPlugin<KateFileTreePlugin>();)
0036 
0037 Q_LOGGING_CATEGORY(FILETREE, "kate-filetree", QtWarningMsg)
0038 
0039 // BEGIN KateFileTreePlugin
0040 KateFileTreePlugin::KateFileTreePlugin(QObject *parent, const QVariantList &)
0041     : KTextEditor::Plugin(parent)
0042 {
0043 }
0044 
0045 KateFileTreePlugin::~KateFileTreePlugin()
0046 {
0047     m_settings.save();
0048 }
0049 
0050 QObject *KateFileTreePlugin::createView(KTextEditor::MainWindow *mainWindow)
0051 {
0052     auto view = new KateFileTreePluginView(mainWindow, this);
0053     m_views.append(view);
0054     return view;
0055 }
0056 
0057 void KateFileTreePlugin::viewDestroyed(QObject *view)
0058 {
0059     // do not access the view pointer, since it is partially destroyed already
0060     m_views.removeAll(static_cast<KateFileTreePluginView *>(view));
0061 }
0062 
0063 int KateFileTreePlugin::configPages() const
0064 {
0065     return 1;
0066 }
0067 
0068 KTextEditor::ConfigPage *KateFileTreePlugin::configPage(int number, QWidget *parent)
0069 {
0070     if (number != 0) {
0071         return nullptr;
0072     }
0073 
0074     KateFileTreeConfigPage *page = new KateFileTreeConfigPage(parent, this);
0075     return page;
0076 }
0077 
0078 const KateFileTreePluginSettings &KateFileTreePlugin::settings()
0079 {
0080     return m_settings;
0081 }
0082 
0083 void KateFileTreePlugin::applyConfig(bool shadingEnabled,
0084                                      const QColor &viewShade,
0085                                      const QColor &editShade,
0086                                      bool listMode,
0087                                      int sortRole,
0088                                      bool showFullPath,
0089                                      bool showToolbar,
0090                                      bool showCloseButton,
0091                                      bool middleClickToClose)
0092 {
0093     // save to settings
0094     m_settings.setShadingEnabled(shadingEnabled);
0095     m_settings.setViewShade(viewShade);
0096     m_settings.setEditShade(editShade);
0097 
0098     m_settings.setListMode(listMode);
0099     m_settings.setSortRole(sortRole);
0100     m_settings.setShowFullPathOnRoots(showFullPath);
0101     m_settings.setShowToolbar(showToolbar);
0102     m_settings.setShowCloseButton(showCloseButton);
0103     m_settings.middleClickToClose = middleClickToClose;
0104     m_settings.save();
0105 
0106     // update views
0107     for (KateFileTreePluginView *view : qAsConst(m_views)) {
0108         view->setHasLocalPrefs(false);
0109         view->model()->setShadingEnabled(shadingEnabled);
0110         view->model()->setViewShade(viewShade);
0111         view->model()->setEditShade(editShade);
0112         view->setListMode(listMode);
0113         view->proxy()->setSortRole(sortRole);
0114         view->tree()->setDragDropMode(sortRole == CustomSorting ? QAbstractItemView::InternalMove : QAbstractItemView::DragOnly);
0115         view->model()->setShowFullPathOnRoots(showFullPath);
0116         view->setToolbarVisible(showToolbar);
0117         view->tree()->setShowCloseButton(showCloseButton);
0118         view->tree()->setMiddleClickToClose(middleClickToClose);
0119     }
0120 }
0121 
0122 // END KateFileTreePlugin
0123 
0124 // BEGIN KateFileTreePluginView
0125 KateFileTreePluginView::KateFileTreePluginView(KTextEditor::MainWindow *mainWindow, KateFileTreePlugin *plug)
0126     : QObject(mainWindow)
0127     , m_plug(plug)
0128     , m_mainWindow(mainWindow)
0129 {
0130     KXMLGUIClient::setComponentName(QStringLiteral("katefiletree"), i18n("Documents"));
0131     setXMLFile(QStringLiteral("ui.rc"));
0132 
0133     m_toolView = mainWindow->createToolView(plug,
0134                                             QStringLiteral("kate_private_plugin_katefiletreeplugin"),
0135                                             KTextEditor::MainWindow::Left,
0136                                             QIcon::fromTheme(QStringLiteral("folder-documents-symbolic")),
0137                                             i18n("Documents"));
0138 
0139     // create toolbar
0140     m_toolbar = new QToolBar(m_toolView);
0141     m_toolbar->setMovable(false);
0142     m_toolbar->setToolButtonStyle(Qt::ToolButtonIconOnly);
0143     m_toolbar->setContextMenuPolicy(Qt::NoContextMenu);
0144     m_toolbar->layout()->setContentsMargins(0, 0, 0, 0);
0145 
0146     // ensure reasonable icons sizes, like e.g. the quick-open and co. icons
0147     // the normal toolbar sizes are TOO large, e.g. for scaled stuff even more!
0148     const int iconSize = m_toolView->style()->pixelMetric(QStyle::PM_ButtonIconSize, nullptr, m_toolView);
0149     m_toolbar->setIconSize(QSize(iconSize, iconSize));
0150 
0151     // create filetree
0152     m_fileTree = new KateFileTree(m_mainWindow, m_toolView);
0153     m_fileTree->setSortingEnabled(true);
0154     m_fileTree->setShowCloseButton(m_plug->settings().showCloseButton());
0155     m_fileTree->setMiddleClickToClose(m_plug->settings().middleClickToClose);
0156     m_fileTree->setProperty("_breeze_borders_sides", QVariant::fromValue(QFlags{Qt::TopEdge}));
0157 
0158     connect(m_fileTree, &KateFileTree::activateDocument, this, &KateFileTreePluginView::activateDocument);
0159     connect(m_fileTree, &KateFileTree::viewModeChanged, this, &KateFileTreePluginView::viewModeChanged);
0160     connect(m_fileTree, &KateFileTree::sortRoleChanged, this, &KateFileTreePluginView::sortRoleChanged);
0161 
0162     m_documentModel = new KateFileTreeModel(m_mainWindow, this);
0163     m_proxyModel = new KateFileTreeProxyModel(this);
0164     m_proxyModel->setSourceModel(m_documentModel);
0165     m_proxyModel->setDynamicSortFilter(true);
0166     m_proxyModel->setRecursiveFilteringEnabled(true);
0167 
0168     m_documentModel->setShowFullPathOnRoots(m_plug->settings().showFullPathOnRoots());
0169     m_documentModel->setShadingEnabled(m_plug->settings().shadingEnabled());
0170     m_documentModel->setViewShade(m_plug->settings().viewShade());
0171     m_documentModel->setEditShade(m_plug->settings().editShade());
0172 
0173     m_filter = new QLineEdit(m_toolView);
0174     m_filter->setPlaceholderText(QStringLiteral("Filter..."));
0175     m_filter->setClearButtonEnabled(true);
0176     m_filter->setProperty("_breeze_borders_sides", QVariant::fromValue(QFlags{Qt::TopEdge}));
0177     connect(m_filter, &QLineEdit::textChanged, this, [this](const QString &text) {
0178         m_proxyModel->setFilterRegularExpression(QRegularExpression(text, QRegularExpression::CaseInsensitiveOption));
0179         if (!text.isEmpty()) {
0180             QTimer::singleShot(100, m_fileTree, &QTreeView::expandAll);
0181         }
0182     });
0183 
0184     connect(KTextEditor::Editor::instance()->application(),
0185             &KTextEditor::Application::documentWillBeDeleted,
0186             m_documentModel,
0187             &KateFileTreeModel::documentClosed);
0188     connect(KTextEditor::Editor::instance()->application(), &KTextEditor::Application::documentCreated, this, &KateFileTreePluginView::documentOpened);
0189     connect(KTextEditor::Editor::instance()->application(), &KTextEditor::Application::documentWillBeDeleted, this, &KateFileTreePluginView::documentClosed);
0190 
0191     // delayed update for new documents to be more efficient if multiple ones are created at once
0192     m_documentsCreatedTimer.setSingleShot(true);
0193     m_documentsCreatedTimer.setInterval(0);
0194     connect(&m_documentsCreatedTimer, &QTimer::timeout, this, &KateFileTreePluginView::slotDocumentsCreated);
0195 
0196     connect(m_documentModel, &KateFileTreeModel::triggerViewChangeAfterNameChange, this, [this] {
0197         viewChanged();
0198     });
0199 
0200     m_fileTree->setModel(m_proxyModel);
0201     m_fileTree->setSelectionMode(QAbstractItemView::SingleSelection);
0202 
0203     connect(m_fileTree->selectionModel(), &QItemSelectionModel::currentChanged, m_fileTree, &KateFileTree::slotCurrentChanged);
0204 
0205     connect(mainWindow, &KTextEditor::MainWindow::viewChanged, this, &KateFileTreePluginView::viewChanged);
0206 
0207     auto mw = mainWindow->window();
0208     connect(mw, SIGNAL(widgetAdded(QWidget *)), this, SLOT(slotWidgetCreated(QWidget *)));
0209     connect(mw, SIGNAL(widgetRemoved(QWidget *)), this, SLOT(slotWidgetRemoved(QWidget *)));
0210 
0211     connect(m_fileTree, &KateFileTree::closeWidget, this, [this](QWidget *w) {
0212         auto mw = m_mainWindow->window();
0213         QMetaObject::invokeMethod(mw, "removeWidget", Q_ARG(QWidget *, w));
0214     });
0215     connect(m_fileTree, &KateFileTree::activateWidget, this, [this](QWidget *w) {
0216         auto mw = m_mainWindow->window();
0217         QMetaObject::invokeMethod(mw, "activateWidget", Q_ARG(QWidget *, w));
0218     });
0219 
0220     //
0221     // actions
0222     //
0223     setupActions();
0224 
0225     mainWindow->guiFactory()->addClient(this);
0226 
0227     setToolbarVisible(m_plug->settings().showToolbar());
0228 
0229     m_proxyModel->setSortRole(Qt::DisplayRole);
0230     m_fileTree->setDragDropMode(QAbstractItemView::DragOnly);
0231 
0232     m_proxyModel->sort(0, Qt::AscendingOrder);
0233     m_proxyModel->invalidate();
0234 }
0235 
0236 KateFileTreePluginView::~KateFileTreePluginView()
0237 {
0238     m_plug->viewDestroyed(this);
0239 
0240     m_mainWindow->guiFactory()->removeClient(this);
0241 
0242     // clean up tree and toolview
0243     delete m_fileTree->parent();
0244     // delete m_toolView;
0245     // and TreeModel
0246     delete m_documentModel;
0247 }
0248 
0249 void KateFileTreePluginView::setupActions()
0250 {
0251     auto aPrev = actionCollection()->addAction(QStringLiteral("filetree_prev_document"));
0252     aPrev->setText(i18n("Previous Document"));
0253     aPrev->setIcon(QIcon::fromTheme(QStringLiteral("go-up")));
0254     actionCollection()->setDefaultShortcut(aPrev, Qt::ALT | Qt::Key_Up);
0255     connect(aPrev, &QAction::triggered, m_fileTree, &KateFileTree::slotDocumentPrev);
0256 
0257     auto aNext = actionCollection()->addAction(QStringLiteral("filetree_next_document"));
0258     aNext->setText(i18n("Next Document"));
0259     aNext->setIcon(QIcon::fromTheme(QStringLiteral("go-down")));
0260     actionCollection()->setDefaultShortcut(aNext, Qt::ALT | Qt::Key_Down);
0261     connect(aNext, &QAction::triggered, m_fileTree, &KateFileTree::slotDocumentNext);
0262 
0263     auto aShowActive = actionCollection()->addAction(QStringLiteral("filetree_show_active_document"));
0264     aShowActive->setText(i18n("&Show Active Document"));
0265     aShowActive->setIcon(QIcon::fromTheme(QStringLiteral("folder-sync")));
0266     connect(aShowActive, &QAction::triggered, this, &KateFileTreePluginView::showActiveDocument);
0267 
0268     auto aSave = actionCollection()->addAction(QStringLiteral("filetree_save"), this, SLOT(slotDocumentSave()));
0269     aSave->setText(i18n("Save"));
0270     aSave->setToolTip(i18n("Save the current document"));
0271     aSave->setIcon(QIcon::fromTheme(QStringLiteral("document-save")));
0272 
0273     auto aSaveAs = actionCollection()->addAction(QStringLiteral("filetree_save_as"), this, SLOT(slotDocumentSaveAs()));
0274     aSaveAs->setText(i18n("Save As"));
0275     aSaveAs->setToolTip(i18n("Save the current document under a new name"));
0276     aSaveAs->setIcon(QIcon::fromTheme(QStringLiteral("document-save-as")));
0277 
0278     /**
0279      * add new & open, if hosting application has it
0280      */
0281     if (KXmlGuiWindow *parentClient = qobject_cast<KXmlGuiWindow *>(m_mainWindow->window())) {
0282         bool newOrOpen = false;
0283         if (auto a = parentClient->action(QStringLiteral("file_new"))) {
0284             m_toolbar->addAction(a);
0285             newOrOpen = true;
0286         }
0287         if (auto a = parentClient->action(QStringLiteral("file_open"))) {
0288             m_toolbar->addAction(a);
0289             newOrOpen = true;
0290         }
0291         if (newOrOpen) {
0292             m_toolbar->addSeparator();
0293         }
0294     }
0295 
0296     /**
0297      * add own actions
0298      */
0299     m_toolbar->addAction(aSave);
0300     m_toolbar->addAction(aSaveAs);
0301     m_toolbar->addSeparator();
0302     m_toolbar->addAction(aPrev);
0303     m_toolbar->addAction(aNext);
0304     m_toolbar->addAction(aShowActive);
0305 }
0306 
0307 KateFileTreeModel *KateFileTreePluginView::model() const
0308 {
0309     return m_documentModel;
0310 }
0311 
0312 KateFileTreeProxyModel *KateFileTreePluginView::proxy() const
0313 {
0314     return m_proxyModel;
0315 }
0316 
0317 KateFileTree *KateFileTreePluginView::tree() const
0318 {
0319     return m_fileTree;
0320 }
0321 
0322 void KateFileTreePluginView::documentOpened(KTextEditor::Document *doc)
0323 {
0324     // enqueue and start update timer to collapse updates
0325     m_documentsCreatedTimer.start();
0326     m_documentsCreated.append(doc);
0327 }
0328 
0329 void KateFileTreePluginView::documentClosed(KTextEditor::Document *doc)
0330 {
0331     m_documentsCreated.removeAll(doc);
0332     m_proxyModel->invalidate();
0333 }
0334 
0335 void KateFileTreePluginView::setToolbarVisible(bool visible)
0336 {
0337     m_toolbar->setVisible(visible);
0338 }
0339 
0340 void KateFileTreePluginView::viewChanged(KTextEditor::View *)
0341 {
0342     auto mw = m_mainWindow->window();
0343     QWidget *activeWidget = nullptr;
0344     QMetaObject::invokeMethod(mw, "activeWidget", Q_RETURN_ARG(QWidget *, activeWidget));
0345     if (!activeWidget) {
0346         return;
0347     }
0348 
0349     QModelIndex index;
0350     if (auto view = qobject_cast<KTextEditor::View *>(activeWidget)) {
0351         KTextEditor::Document *doc = view->document();
0352         index = m_proxyModel->docIndex(doc);
0353         // update the model on which doc is active
0354         m_documentModel->documentActivated(doc);
0355     } else {
0356         index = m_proxyModel->widgetIndex(activeWidget);
0357     }
0358 
0359     m_fileTree->selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
0360 
0361     m_fileTree->scrollTo(index);
0362 
0363     while (index != QModelIndex()) {
0364         m_fileTree->expand(index);
0365         index = index.parent();
0366     }
0367 }
0368 
0369 void KateFileTreePluginView::setListMode(bool listMode)
0370 {
0371     if (listMode) {
0372         m_documentModel->setListMode(true);
0373         m_fileTree->setRootIsDecorated(false);
0374     } else {
0375         m_documentModel->setListMode(false);
0376         m_fileTree->setRootIsDecorated(true);
0377     }
0378 
0379     m_proxyModel->sort(0, Qt::AscendingOrder);
0380     m_proxyModel->invalidate();
0381 }
0382 
0383 void KateFileTreePluginView::viewModeChanged(bool listMode)
0384 {
0385     setHasLocalPrefs(true);
0386     setListMode(listMode);
0387 }
0388 
0389 void KateFileTreePluginView::sortRoleChanged(int role)
0390 {
0391     setHasLocalPrefs(true);
0392     m_proxyModel->setSortRole(role);
0393     m_proxyModel->invalidate();
0394     m_fileTree->setDragDropMode(role == CustomSorting ? QAbstractItemView::InternalMove : QAbstractItemView::DragOnly);
0395 }
0396 
0397 void KateFileTreePluginView::activateDocument(KTextEditor::Document *doc)
0398 {
0399     m_mainWindow->activateView(doc);
0400 }
0401 
0402 void KateFileTreePluginView::showToolView()
0403 {
0404     m_mainWindow->showToolView(m_toolView);
0405     m_toolView->setFocus();
0406 }
0407 
0408 void KateFileTreePluginView::hideToolView()
0409 {
0410     m_mainWindow->hideToolView(m_toolView);
0411 }
0412 
0413 void KateFileTreePluginView::showActiveDocument()
0414 {
0415     // hack?
0416     viewChanged();
0417     // make the tool view show if it was hidden
0418     showToolView();
0419 }
0420 
0421 bool KateFileTreePluginView::hasLocalPrefs() const
0422 {
0423     return m_hasLocalPrefs;
0424 }
0425 
0426 void KateFileTreePluginView::setHasLocalPrefs(bool h)
0427 {
0428     m_hasLocalPrefs = h;
0429 }
0430 
0431 void KateFileTreePluginView::readSessionConfig(const KConfigGroup &g)
0432 {
0433     if (g.exists()) {
0434         m_hasLocalPrefs = true;
0435     } else {
0436         m_hasLocalPrefs = false;
0437     }
0438 
0439     // we chain to the global settings by using them as the defaults
0440     //  here in the session view config loading.
0441     const KateFileTreePluginSettings &defaults = m_plug->settings();
0442 
0443     bool listMode = g.readEntry("listMode", defaults.listMode());
0444 
0445     setListMode(listMode);
0446 
0447     int sortRole = g.readEntry("sortRole", defaults.sortRole());
0448     m_proxyModel->setSortRole(sortRole);
0449     m_fileTree->setDragDropMode(sortRole == CustomSorting ? QAbstractItemView::InternalMove : QAbstractItemView::DragOnly);
0450 }
0451 
0452 void KateFileTreePluginView::writeSessionConfig(KConfigGroup &g)
0453 {
0454     if (m_hasLocalPrefs) {
0455         g.writeEntry("listMode", QVariant(m_documentModel->listMode()));
0456         g.writeEntry("sortRole", int(m_proxyModel->sortRole()));
0457     } else {
0458         g.deleteEntry("listMode");
0459         g.deleteEntry("sortRole");
0460     }
0461 
0462     g.sync();
0463 }
0464 
0465 void KateFileTreePluginView::slotDocumentsCreated()
0466 {
0467     // handle potential multiple new documents
0468     m_documentModel->documentsOpened(m_documentsCreated);
0469     m_documentsCreated.clear();
0470     viewChanged();
0471 }
0472 
0473 void KateFileTreePluginView::slotDocumentSave() const
0474 {
0475     if (auto view = m_mainWindow->activeView()) {
0476         view->document()->documentSave();
0477     }
0478 }
0479 
0480 void KateFileTreePluginView::slotDocumentSaveAs() const
0481 {
0482     if (auto view = m_mainWindow->activeView()) {
0483         view->document()->documentSaveAs();
0484     }
0485 }
0486 
0487 void KateFileTreePluginView::slotWidgetCreated(QWidget *w)
0488 {
0489     m_documentModel->addWidget(w);
0490 }
0491 
0492 void KateFileTreePluginView::slotWidgetRemoved(QWidget *w)
0493 {
0494     m_documentModel->removeWidget(w);
0495 }
0496 
0497 // END KateFileTreePluginView
0498 
0499 #include "katefiletreeplugin.moc"
0500 #include "moc_katefiletreeplugin.cpp"