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"