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

0001 /* This file is part of the KDE project
0002    SPDX-FileCopyrightText: 2001 Christoph Cullmann <cullmann@kde.org>
0003    SPDX-FileCopyrightText: 2001 Joseph Wenninger <jowenn@kde.org>
0004    SPDX-FileCopyrightText: 2001 Anders Lund <anders.lund@lund.tdcadsl.dk>
0005    SPDX-FileCopyrightText: 2007 Mirko Stocker <me@misto.ch>
0006    SPDX-FileCopyrightText: 2009 Dominik Haumann <dhaumann kde org>
0007 
0008    SPDX-License-Identifier: LGPL-2.0-only
0009 */
0010 
0011 // BEGIN Includes
0012 #include "katefilebrowser.h"
0013 
0014 #include "katebookmarkhandler.h"
0015 #include "katefileactions.h"
0016 
0017 #include <ktexteditor/document.h>
0018 #include <ktexteditor/view.h>
0019 
0020 #include <KActionCollection>
0021 #include <KActionMenu>
0022 #include <KApplicationTrader>
0023 #include <KConfigGroup>
0024 #include <KFilePlacesModel>
0025 #include <KHistoryComboBox>
0026 #include <KLocalizedString>
0027 #include <KMessageBox>
0028 #include <KSharedConfig>
0029 #include <KToolBar>
0030 #include <KUrlNavigator>
0031 #include <kwidgetsaddons_version.h>
0032 
0033 #include <QAbstractItemView>
0034 #include <QAction>
0035 #include <QDir>
0036 #include <QLineEdit>
0037 #include <QStyle>
0038 #include <QVBoxLayout>
0039 
0040 // END Includes
0041 
0042 KateFileBrowser::KateFileBrowser(KTextEditor::MainWindow *mainWindow, QWidget *parent)
0043     : QWidget(parent)
0044     , m_mainWindow(mainWindow)
0045 {
0046     QVBoxLayout *mainLayout = new QVBoxLayout(this);
0047     mainLayout->setContentsMargins(0, 0, 0, 0);
0048     mainLayout->setSpacing(0);
0049 
0050     m_toolbar = new KToolBar(this);
0051     m_toolbar->setMovable(false);
0052     m_toolbar->setToolButtonStyle(Qt::ToolButtonIconOnly);
0053     m_toolbar->setContextMenuPolicy(Qt::NoContextMenu);
0054     m_toolbar->layout()->setContentsMargins(0, 0, 0, 0);
0055 
0056     // ensure reasonable icons sizes, like e.g. the quick-open and co. icons
0057     // the normal toolbar sizes are TOO large, e.g. for scaled stuff even more!
0058     const int iconSize = style()->pixelMetric(QStyle::PM_ButtonIconSize, nullptr, this);
0059     m_toolbar->setIconSize(QSize(iconSize, iconSize));
0060 
0061     mainLayout->addWidget(m_toolbar);
0062 
0063     // includes some actions, but not hooked into the shortcut dialog atm
0064     m_actionCollection = new KActionCollection(this);
0065     m_actionCollection->addAssociatedWidget(this);
0066 
0067     KFilePlacesModel *model = new KFilePlacesModel(this);
0068     m_urlNavigator = new KUrlNavigator(model, QUrl::fromLocalFile(QDir::homePath()), this);
0069     connect(m_urlNavigator, &KUrlNavigator::urlChanged, this, &KateFileBrowser::updateDirOperator);
0070     mainLayout->addWidget(m_urlNavigator);
0071 
0072     auto separator = new QFrame(this);
0073     separator->setFrameShape(QFrame::HLine);
0074     separator->setEnabled(false);
0075     mainLayout->addWidget(separator);
0076 
0077     m_dirOperator = new KDirOperator(QUrl(), this);
0078     // Default to a view with only one column since columns are auto-sized
0079     m_dirOperator->setViewMode(KFile::Tree);
0080     m_dirOperator->view()->setSelectionMode(QAbstractItemView::ExtendedSelection);
0081     m_dirOperator->setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding));
0082     mainLayout->addWidget(m_dirOperator);
0083 
0084     // Mime filter for the KDirOperator
0085     QStringList filter;
0086     filter << QStringLiteral("text/html") << QStringLiteral("inode/directory");
0087     filter << QStringLiteral("application/x-zerosize");
0088     m_dirOperator->setNewFileMenuSupportedMimeTypes(filter);
0089 
0090     setFocusProxy(m_dirOperator);
0091     connect(m_dirOperator, &KDirOperator::viewChanged, this, &KateFileBrowser::selectorViewChanged);
0092     connect(m_urlNavigator, &KUrlNavigator::returnPressed, m_dirOperator, static_cast<void (KDirOperator::*)()>(&KDirOperator::setFocus));
0093 
0094     // now all actions exist in dir operator and we can use them in the toolbar
0095     setupActions();
0096     setupToolbar();
0097 
0098     m_filter = new KHistoryComboBox(true, this);
0099     m_filter->setMaxCount(10);
0100     m_filter->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed));
0101     m_filter->lineEdit()->setPlaceholderText(i18n("Search"));
0102     m_filter->setProperty("_breeze_borders_sides", QVariant::fromValue(QFlags{Qt::TopEdge}));
0103     mainLayout->addWidget(m_filter);
0104 
0105     connect(m_filter, &KHistoryComboBox::editTextChanged, this, &KateFileBrowser::slotFilterChange);
0106     connect(m_filter, static_cast<void (KHistoryComboBox::*)(const QString &)>(&KHistoryComboBox::returnPressed), m_filter, &KHistoryComboBox::addToHistory);
0107     connect(m_filter,
0108             static_cast<void (KHistoryComboBox::*)(const QString &)>(&KHistoryComboBox::returnPressed),
0109             m_dirOperator,
0110             static_cast<void (KDirOperator::*)()>(&KDirOperator::setFocus));
0111     connect(m_dirOperator, &KDirOperator::urlEntered, this, &KateFileBrowser::updateUrlNavigator);
0112 
0113     // Connect the bookmark handler
0114     connect(m_bookmarkHandler, &KateBookmarkHandler::openUrl, this, static_cast<void (KateFileBrowser::*)(const QString &)>(&KateFileBrowser::setDir));
0115 
0116     m_filter->setWhatsThis(i18n("Enter a name filter to limit which files are displayed."));
0117 
0118     connect(m_dirOperator, &KDirOperator::fileSelected, this, &KateFileBrowser::fileSelected);
0119     connect(m_mainWindow, &KTextEditor::MainWindow::viewChanged, this, &KateFileBrowser::autoSyncFolder);
0120 
0121     connect(m_dirOperator, &KDirOperator::contextMenuAboutToShow, this, &KateFileBrowser::contextMenuAboutToShow);
0122 
0123     // Ensure highlight current document also works after directory change
0124     connect(m_dirOperator, &KDirOperator::finishedLoading, this, [this] {
0125         if (m_highlightCurrentFile->isChecked() && m_autoSyncFolder->isChecked()) {
0126             if (const auto u = activeDocumentUrl(); u.isValid()) {
0127                 m_dirOperator->setCurrentItem(u);
0128             }
0129         }
0130     });
0131 }
0132 
0133 KateFileBrowser::~KateFileBrowser()
0134 {
0135 }
0136 // END Constructor/Destructor
0137 
0138 // BEGIN Public Methods
0139 void KateFileBrowser::setupToolbar()
0140 {
0141     KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("filebrowser"));
0142     QStringList actions = config.readEntry("toolbar actions", QStringList());
0143     if (actions.isEmpty()) { // default toolbar
0144         actions << QStringLiteral("back") << QStringLiteral("forward") << QStringLiteral("bookmarks") << QStringLiteral("sync_dir")
0145                 << QStringLiteral("configure");
0146     }
0147 
0148     // remove all actions from the toolbar (there should be none)
0149     m_toolbar->clear();
0150 
0151     // now add all actions to the toolbar
0152     for (const QString &it : qAsConst(actions)) {
0153         QAction *ac = nullptr;
0154         if (it.isEmpty()) {
0155             continue;
0156         }
0157         if (it == QLatin1String("bookmarks") || it == QLatin1String("sync_dir") || it == QLatin1String("configure")) {
0158             ac = actionCollection()->action(it);
0159         } else {
0160             ac = m_dirOperator->action(actionFromName(it));
0161         }
0162 
0163         if (ac) {
0164             m_toolbar->addAction(ac);
0165         }
0166     }
0167 }
0168 
0169 void KateFileBrowser::readSessionConfig(const KConfigGroup &cg)
0170 {
0171     m_dirOperator->readConfig(cg);
0172     m_dirOperator->setViewMode(KFile::Default);
0173 
0174     m_urlNavigator->setLocationUrl(cg.readEntry("location", QUrl::fromLocalFile(QDir::homePath())));
0175     setDir(cg.readEntry("location", QUrl::fromLocalFile(QDir::homePath())));
0176     m_autoSyncFolder->setChecked(cg.readEntry("auto sync folder", true));
0177     m_highlightCurrentFile->setChecked(cg.readEntry("highlight current file", true));
0178     m_highlightCurrentFile->setEnabled(m_autoSyncFolder->isChecked());
0179     m_filter->setHistoryItems(cg.readEntry("filter history", QStringList()), true);
0180 }
0181 
0182 void KateFileBrowser::writeSessionConfig(KConfigGroup &cg)
0183 {
0184     m_dirOperator->writeConfig(cg);
0185 
0186     cg.writeEntry("location", m_urlNavigator->locationUrl().url());
0187     cg.writeEntry("auto sync folder", m_autoSyncFolder->isChecked());
0188     cg.writeEntry("auto sync folder", m_autoSyncFolder->isChecked());
0189     cg.writeEntry("highlight current file", m_highlightCurrentFile->isChecked());
0190     cg.writeEntry("filter history", m_filter->historyItems());
0191 }
0192 
0193 KDirOperator::Action KateFileBrowser::actionFromName(const QString &name)
0194 {
0195     if (name == QLatin1String("up")) {
0196         return KDirOperator::Up;
0197     } else if (name == QLatin1String("back")) {
0198         return KDirOperator::Back;
0199     } else if (name == QLatin1String("forward")) {
0200         return KDirOperator::Forward;
0201     } else if (name == QLatin1String("home")) {
0202         return KDirOperator::Home;
0203     } else if (name == QLatin1String("reload")) {
0204         return KDirOperator::Reload;
0205     } else if (name == QLatin1String("mkdir")) {
0206         return KDirOperator::NewFolder;
0207     } else if (name == QLatin1String("delete")) {
0208         return KDirOperator::Delete;
0209     } else if (name == QLatin1String("short view")) {
0210         return KDirOperator::ShortView;
0211     } else if (name == QLatin1String("detailed view")) {
0212         return KDirOperator::DetailedView;
0213     } else if (name == QLatin1String("tree view")) {
0214         return KDirOperator::TreeView;
0215     } else if (name == QLatin1String("detailed tree view")) {
0216         return KDirOperator::DetailedTreeView;
0217     } else if (name == QLatin1String("show hidden")) {
0218         return KDirOperator::ShowHiddenFiles;
0219     } else {
0220         qWarning() << "Unknown KDirOperator action:" << name;
0221     }
0222 
0223     return {};
0224 }
0225 
0226 // END Public Methods
0227 
0228 // BEGIN Public Slots
0229 
0230 void KateFileBrowser::slotFilterChange(const QString &nf)
0231 {
0232     QString f = nf.trimmed();
0233     const bool empty = f.isEmpty() || f == QLatin1String("*");
0234 
0235     if (empty) {
0236         m_dirOperator->clearFilter();
0237     } else {
0238         // unless the user explicitly used wild card terms, turn filter into partial matching one
0239         // given user expectations with the narrow-as-you-type style of the UI
0240         QStringList filters = f.split(QLatin1Char(' '), Qt::SkipEmptyParts);
0241         for (QString &filter : filters) {
0242             if (filter.contains(QLatin1Char('*')) || filter.contains(QLatin1Char('?')) || filter.contains(QLatin1Char('['))) {
0243                 continue;
0244             }
0245             filter = QLatin1Char('*') + filter + QLatin1Char('*');
0246         }
0247         m_dirOperator->setNameFilter(filters.join(QLatin1Char(' ')));
0248     }
0249 
0250     m_dirOperator->updateDir();
0251 }
0252 
0253 bool kateFileSelectorIsReadable(const QUrl &url)
0254 {
0255     if (!url.isLocalFile()) {
0256         return true; // what else can we say?
0257     }
0258 
0259     QDir dir(url.toLocalFile());
0260     return dir.exists();
0261 }
0262 
0263 void KateFileBrowser::setDir(const QUrl &u)
0264 {
0265     QUrl newurl;
0266 
0267     if (!u.isValid()) {
0268         newurl = QUrl::fromLocalFile(QDir::homePath());
0269     } else {
0270         newurl = u;
0271     }
0272 
0273     QString path(newurl.path());
0274     if (!path.endsWith(QLatin1Char('/'))) {
0275         path += QLatin1Char('/');
0276     }
0277     newurl.setPath(path);
0278 
0279     if (!kateFileSelectorIsReadable(newurl)) {
0280         newurl.setPath(newurl.path() + QStringLiteral("../"));
0281         newurl = newurl.adjusted(QUrl::NormalizePathSegments);
0282     }
0283 
0284     if (!kateFileSelectorIsReadable(newurl)) {
0285         newurl = QUrl::fromLocalFile(QDir::homePath());
0286     }
0287 
0288     m_dirOperator->setUrl(newurl, true);
0289 }
0290 
0291 void KateFileBrowser::contextMenuAboutToShow(const KFileItem &item, QMenu *menu)
0292 {
0293     if (m_openWithMenu == nullptr) {
0294         m_openWithMenu = new KateFileBrowserOpenWithMenu(i18nc("@action:inmenu", "Open With"), this);
0295         m_openWithMenu->setIcon(QIcon::fromTheme(QStringLiteral("system-run")));
0296         menu->insertMenu(menu->actions().at(1), m_openWithMenu);
0297         menu->insertSeparator(menu->actions().at(2));
0298         connect(m_openWithMenu, &QMenu::aboutToShow, this, &KateFileBrowser::fixOpenWithMenu);
0299         connect(m_openWithMenu, &QMenu::triggered, this, &KateFileBrowser::openWithMenuAction);
0300     }
0301     m_openWithMenu->setItem(item);
0302 }
0303 
0304 void KateFileBrowser::fixOpenWithMenu()
0305 {
0306     KateFileBrowserOpenWithMenu *menu = static_cast<KateFileBrowserOpenWithMenu *>(sender());
0307     menu->clear();
0308 
0309     // get a list of appropriate services.
0310     QMimeType mime = menu->item().determineMimeType();
0311 
0312     QAction *a = nullptr;
0313     const KService::List offers = KApplicationTrader::queryByMimeType(mime.name());
0314     // for each one, insert a menu item...
0315     for (const auto &service : offers) {
0316         if (service->name() == QLatin1String("Kate")) {
0317             continue;
0318         }
0319         a = menu->addAction(QIcon::fromTheme(service->icon()), service->name());
0320         a->setData(QVariant(QList<QString>({service->entryPath(), menu->item().url().toString()})));
0321     }
0322     // append "Other..." to call the KDE "open with" dialog.
0323     a = menu->addAction(i18n("&Other..."));
0324     a->setData(QVariant(QList<QString>({QString(), menu->item().url().toString()})));
0325 }
0326 
0327 void KateFileBrowser::openWithMenuAction(QAction *a)
0328 {
0329     const QString application = a->data().toStringList().first();
0330     const QString fileName = a->data().toStringList().last();
0331 
0332     a->setData(application);
0333     KateFileActions::showOpenWithMenu(this, QUrl(fileName), a);
0334 }
0335 // END Public Slots
0336 
0337 // BEGIN Private Slots
0338 
0339 void KateFileBrowser::fileSelected(const KFileItem & /*file*/)
0340 {
0341     openSelectedFiles();
0342 }
0343 
0344 void KateFileBrowser::openSelectedFiles()
0345 {
0346     const KFileItemList list = m_dirOperator->selectedItems();
0347 
0348     if (list.count() > 20) {
0349         if (KMessageBox::questionTwoActions(
0350                 this,
0351                 i18np("You are trying to open 1 file, are you sure?", "You are trying to open %1 files, are you sure?", list.count()),
0352                 {},
0353                 KGuiItem(i18nc("@action:button", "Open All Files"), QStringLiteral("document-open")),
0354                 KStandardGuiItem::cancel())
0355             == KMessageBox::SecondaryAction) {
0356             return;
0357         }
0358     }
0359 
0360     for (const KFileItem &item : list) {
0361         m_mainWindow->openUrl(item.url());
0362     }
0363 
0364     m_dirOperator->view()->selectionModel()->clear();
0365 }
0366 
0367 void KateFileBrowser::updateDirOperator(const QUrl &u)
0368 {
0369     m_dirOperator->setUrl(u, true);
0370 }
0371 
0372 void KateFileBrowser::updateUrlNavigator(const QUrl &u)
0373 {
0374     m_urlNavigator->setLocationUrl(u);
0375 }
0376 
0377 void KateFileBrowser::setActiveDocumentDir()
0378 {
0379     QUrl u = activeDocumentUrl();
0380     if (!u.isEmpty()) {
0381         setDir(KIO::upUrl(u));
0382         if (m_highlightCurrentFile->isChecked() && m_autoSyncFolder->isChecked()) {
0383             m_dirOperator->setCurrentItem(u);
0384         }
0385     }
0386 }
0387 
0388 void KateFileBrowser::autoSyncFolder()
0389 {
0390     if (m_autoSyncFolder->isChecked()) {
0391         setActiveDocumentDir();
0392     }
0393 }
0394 
0395 void KateFileBrowser::selectorViewChanged(QAbstractItemView *newView)
0396 {
0397     newView->setSelectionMode(QAbstractItemView::ExtendedSelection);
0398 }
0399 
0400 // END Private Slots
0401 
0402 // BEGIN Protected
0403 
0404 QUrl KateFileBrowser::activeDocumentUrl()
0405 {
0406     KTextEditor::View *v = m_mainWindow->activeView();
0407     if (v) {
0408         return v->document()->url();
0409     }
0410     return QUrl();
0411 }
0412 
0413 void KateFileBrowser::setupActions()
0414 {
0415     // bookmarks action!
0416     KActionMenu *acmBookmarks = new KActionMenu(QIcon::fromTheme(QStringLiteral("bookmarks")), i18n("Bookmarks"), this);
0417     acmBookmarks->setPopupMode(QToolButton::InstantPopup);
0418     m_bookmarkHandler = new KateBookmarkHandler(this, acmBookmarks->menu());
0419     acmBookmarks->setShortcutContext(Qt::WidgetWithChildrenShortcut);
0420 
0421     // action for synchronizing the dir operator with the current document path
0422     QAction *syncFolder = new QAction(this);
0423     syncFolder->setShortcutContext(Qt::WidgetWithChildrenShortcut);
0424     syncFolder->setText(i18n("Current Document Folder"));
0425     syncFolder->setIcon(QIcon::fromTheme(QStringLiteral("system-switch-user")));
0426     connect(syncFolder, &QAction::triggered, this, &KateFileBrowser::setActiveDocumentDir);
0427 
0428     m_actionCollection->addAction(QStringLiteral("sync_dir"), syncFolder);
0429     m_actionCollection->addAction(QStringLiteral("bookmarks"), acmBookmarks);
0430 
0431     // section for settings menu
0432     KActionMenu *optionsMenu = new KActionMenu(QIcon::fromTheme(QStringLiteral("configure")), i18n("Options"), this);
0433     optionsMenu->setPopupMode(QToolButton::InstantPopup);
0434     optionsMenu->addAction(m_dirOperator->action(KDirOperator::ShortView));
0435     optionsMenu->addAction(m_dirOperator->action(KDirOperator::DetailedView));
0436     optionsMenu->addAction(m_dirOperator->action(KDirOperator::TreeView));
0437     optionsMenu->addAction(m_dirOperator->action(KDirOperator::DetailedTreeView));
0438     optionsMenu->addSeparator();
0439     optionsMenu->addAction(m_dirOperator->action(KDirOperator::ShowHiddenFiles));
0440 
0441     // action for synchronising the dir operator with the current document path...
0442     m_autoSyncFolder = new QAction(this);
0443     m_autoSyncFolder->setCheckable(true);
0444     m_autoSyncFolder->setText(i18n("Automatically synchronize with current document"));
0445     m_autoSyncFolder->setChecked(true);
0446     m_autoSyncFolder->setIcon(QIcon::fromTheme(QStringLiteral("system-switch-user")));
0447     optionsMenu->addAction(m_autoSyncFolder);
0448     // ...and his buddy who depend on him...
0449     m_highlightCurrentFile = new QAction(this);
0450     m_highlightCurrentFile->setCheckable(true);
0451     m_highlightCurrentFile->setText(i18n("Highlight current file"));
0452     m_highlightCurrentFile->setChecked(true);
0453     optionsMenu->addAction(m_highlightCurrentFile);
0454     // ...needs some special handling in case of user action
0455     connect(m_highlightCurrentFile, &QAction::triggered, this, [this] {
0456         m_dirOperator->view()->clearSelection();
0457         autoSyncFolder();
0458     });
0459     connect(m_autoSyncFolder, &QAction::triggered, this, [this](bool enabled) {
0460         m_dirOperator->view()->clearSelection();
0461         m_highlightCurrentFile->setEnabled(enabled);
0462         autoSyncFolder();
0463     });
0464 
0465     m_actionCollection->addAction(QStringLiteral("configure"), optionsMenu);
0466 
0467     //
0468     // Remove all shortcuts due to shortcut clashes (e.g. F5: reload, Ctrl+B: bookmark)
0469     // BUGS: #188954, #236368
0470     //
0471     const auto actions = m_actionCollection->actions();
0472     for (QAction *a : actions) {
0473         a->setShortcut(QKeySequence());
0474     }
0475     const auto dirActions = m_dirOperator->allActions();
0476     for (QAction *a : dirActions) {
0477         a->setShortcut(QKeySequence());
0478     }
0479 }
0480 // END Protected
0481 
0482 #include "moc_katefilebrowser.cpp"