File indexing completed on 2024-05-12 09:54:32

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