diff --git a/src/filewidgets/kdiroperator.cpp b/src/filewidgets/kdiroperator.cpp index 6e5d7797..20494b65 100644 --- a/src/filewidgets/kdiroperator.cpp +++ b/src/filewidgets/kdiroperator.cpp @@ -1,2688 +1,2690 @@ /* This file is part of the KDE libraries Copyright (C) 1999,2000 Stephan Kulow 1999,2000,2001,2002,2003 Carsten Pfeiffer This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kdiroperator.h" #include #include #include "kdirmodel.h" #include "kdiroperatordetailview_p.h" #include "kdirsortfilterproxymodel.h" #include "kfileitem.h" #include "kfilemetapreview_p.h" #include "kpreviewwidgetbase.h" #include "knewfilemenu.h" #include "../pathhelpers_p.h" #include #include // ConfigGroup, DefaultShowHidden, DefaultDirsFirst, DefaultSortReversed #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include template class QHash; // QDir::SortByMask is not only undocumented, it also omits QDir::Type which is another // sorting mode. static const int QDirSortMask = QDir::SortByMask | QDir::Type; /** * Default icon view for KDirOperator using * custom view options. */ class KDirOperatorIconView : public QListView { Q_OBJECT public: KDirOperatorIconView(KDirOperator *dirOperator, QWidget *parent = nullptr); virtual ~KDirOperatorIconView(); protected: QStyleOptionViewItem viewOptions() const override; void dragEnterEvent(QDragEnterEvent *event) override; void mousePressEvent(QMouseEvent *event) override; void wheelEvent(QWheelEvent *event) override; private: KDirOperator *ops; }; KDirOperatorIconView::KDirOperatorIconView(KDirOperator *dirOperator, QWidget *parent) : QListView(parent), ops(dirOperator) { setViewMode(QListView::IconMode); setFlow(QListView::TopToBottom); setResizeMode(QListView::Adjust); setSpacing(0); setMovement(QListView::Static); setDragDropMode(QListView::DragOnly); setVerticalScrollMode(QListView::ScrollPerPixel); setHorizontalScrollMode(QListView::ScrollPerPixel); setEditTriggers(QAbstractItemView::NoEditTriggers); setWordWrap(true); setIconSize(QSize(KIconLoader::SizeSmall, KIconLoader::SizeSmall)); } KDirOperatorIconView::~KDirOperatorIconView() { } QStyleOptionViewItem KDirOperatorIconView::viewOptions() const { QStyleOptionViewItem viewOptions = QListView::viewOptions(); viewOptions.showDecorationSelected = true; viewOptions.decorationPosition = ops->decorationPosition(); if (viewOptions.decorationPosition == QStyleOptionViewItem::Left) { viewOptions.displayAlignment = Qt::AlignLeft | Qt::AlignVCenter; } else { viewOptions.displayAlignment = Qt::AlignCenter; } return viewOptions; } void KDirOperatorIconView::dragEnterEvent(QDragEnterEvent *event) { if (event->mimeData()->hasUrls()) { event->acceptProposedAction(); } } void KDirOperatorIconView::mousePressEvent(QMouseEvent *event) { if (!indexAt(event->pos()).isValid()) { const Qt::KeyboardModifiers modifiers = QApplication::keyboardModifiers(); if (!(modifiers & Qt::ShiftModifier) && !(modifiers & Qt::ControlModifier)) { clearSelection(); } } QListView::mousePressEvent(event); } void KDirOperatorIconView::wheelEvent(QWheelEvent *event) { QListView::wheelEvent(event); // apply the vertical wheel event to the horizontal scrollbar, as // the items are aligned from left to right if (event->orientation() == Qt::Vertical) { QWheelEvent horizEvent(event->pos(), event->delta(), event->buttons(), event->modifiers(), Qt::Horizontal); QApplication::sendEvent(horizontalScrollBar(), &horizEvent); } } void KDirOperator::keyPressEvent(QKeyEvent *e) { if (!(e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter)) { QWidget::keyPressEvent(e); + } else { + emit keyEnterReturnPressed(); } } class Q_DECL_HIDDEN KDirOperator::Private { public: explicit Private(KDirOperator *parent); ~Private(); enum InlinePreviewState { ForcedToFalse = 0, ForcedToTrue, NotForced }; // private methods bool checkPreviewInternal() const; void checkPath(const QString &txt, bool takeFiles = false); bool openUrl(const QUrl &url, KDirLister::OpenUrlFlags flags = KDirLister::NoFlags); int sortColumn() const; Qt::SortOrder sortOrder() const; void updateSorting(QDir::SortFlags sort); static bool isReadable(const QUrl &url); bool isSchemeSupported(const QString &scheme) const; KFile::FileView allViews(); // private slots void _k_slotDetailedView(); void _k_slotSimpleView(); void _k_slotTreeView(); void _k_slotDetailedTreeView(); void _k_slotToggleHidden(bool); void _k_togglePreview(bool); void _k_toggleInlinePreviews(bool); void _k_slotOpenFileManager(); void _k_slotSortByName(); void _k_slotSortBySize(); void _k_slotSortByDate(); void _k_slotSortByType(); void _k_slotSortReversed(bool doReverse); void _k_slotToggleDirsFirst(); void _k_slotToggleIgnoreCase(); void _k_slotStarted(); void _k_slotProgress(int); void _k_slotShowProgress(); void _k_slotIOFinished(); void _k_slotCanceled(); void _k_slotRedirected(const QUrl &); void _k_slotProperties(); void _k_slotActivated(const QModelIndex &); void _k_slotSelectionChanged(); void _k_openContextMenu(const QPoint &); void _k_triggerPreview(const QModelIndex &); void _k_showPreview(); void _k_slotSplitterMoved(int, int); void _k_assureVisibleSelection(); void _k_synchronizeSortingState(int, Qt::SortOrder); void _k_slotChangeDecorationPosition(); void _k_slotExpandToUrl(const QModelIndex &); void _k_slotItemsChanged(); void _k_slotDirectoryCreated(const QUrl &); void updateListViewGrid(); int iconSizeForViewType(QAbstractItemView *itemView) const; // private members KDirOperator * const parent; QStack backStack; ///< Contains all URLs you can reach with the back button. QStack forwardStack; ///< Contains all URLs you can reach with the forward button. QModelIndex lastHoveredIndex; KDirLister *dirLister; QUrl currUrl; KCompletion completion; KCompletion dirCompletion; bool completeListDirty; QDir::SortFlags sorting; QStyleOptionViewItem::Position decorationPosition; QSplitter *splitter; QAbstractItemView *itemView; KDirModel *dirModel; KDirSortFilterProxyModel *proxyModel; KFileItemList pendingMimeTypes; // the enum KFile::FileView as an int int viewKind; int defaultView; KFile::Modes mode; QProgressBar *progressBar; KPreviewWidgetBase *preview; QUrl previewUrl; int previewWidth; bool dirHighlighting; bool onlyDoubleClickSelectsFiles; QString lastURL; // used for highlighting a directory on cdUp QTimer *progressDelayTimer; int dropOptions; KActionMenu *actionMenu; KActionCollection *actionCollection; KNewFileMenu *newFileMenu; KConfigGroup *configGroup; KFilePreviewGenerator *previewGenerator; bool showPreviews; int iconsZoom; bool isSaving; KActionMenu *decorationMenu; KToggleAction *leftAction; QList itemsToBeSetAsCurrent; bool shouldFetchForItems; InlinePreviewState inlinePreviewState; QStringList supportedSchemes; }; KDirOperator::Private::Private(KDirOperator *_parent) : parent(_parent), dirLister(nullptr), decorationPosition(QStyleOptionViewItem::Left), splitter(nullptr), itemView(nullptr), dirModel(nullptr), proxyModel(nullptr), progressBar(nullptr), preview(nullptr), previewUrl(), previewWidth(0), dirHighlighting(false), onlyDoubleClickSelectsFiles(!qApp->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick)), progressDelayTimer(nullptr), dropOptions(0), actionMenu(nullptr), actionCollection(nullptr), newFileMenu(nullptr), configGroup(nullptr), previewGenerator(nullptr), showPreviews(false), iconsZoom(0), isSaving(false), decorationMenu(nullptr), leftAction(nullptr), shouldFetchForItems(false), inlinePreviewState(NotForced) { } KDirOperator::Private::~Private() { delete itemView; itemView = nullptr; // TODO: // if (configGroup) { // itemView->writeConfig(configGroup); // } qDeleteAll(backStack); qDeleteAll(forwardStack); delete preview; preview = nullptr; delete proxyModel; proxyModel = nullptr; delete dirModel; dirModel = nullptr; dirLister = nullptr; // deleted by KDirModel delete configGroup; configGroup = nullptr; delete progressDelayTimer; progressDelayTimer = nullptr; } KDirOperator::KDirOperator(const QUrl &_url, QWidget *parent) : QWidget(parent), d(new Private(this)) { d->splitter = new QSplitter(this); d->splitter->setChildrenCollapsible(false); connect(d->splitter, SIGNAL(splitterMoved(int,int)), this, SLOT(_k_slotSplitterMoved(int,int))); d->preview = nullptr; d->mode = KFile::File; d->viewKind = KFile::Simple; if (_url.isEmpty()) { // no dir specified -> current dir QString strPath = QDir::currentPath(); strPath.append(QLatin1Char('/')); d->currUrl = QUrl::fromLocalFile(strPath); } else { d->currUrl = _url; if (d->currUrl.scheme().isEmpty()) { d->currUrl.setScheme(QStringLiteral("file")); } QString path = d->currUrl.path(); if (!path.endsWith(QLatin1Char('/'))) { path.append(QLatin1Char('/')); // make sure we have a trailing slash! } d->currUrl.setPath(path); } // We set the direction of this widget to LTR, since even on RTL desktops // viewing directory listings in RTL mode makes people's head explode. // Is this the correct place? Maybe it should be in some lower level widgets...? setLayoutDirection(Qt::LeftToRight); setDirLister(new KDirLister()); connect(&d->completion, &KCompletion::match, this, &KDirOperator::slotCompletionMatch); d->progressBar = new QProgressBar(this); d->progressBar->setObjectName(QStringLiteral("d->progressBar")); d->progressBar->adjustSize(); d->progressBar->move(2, height() - d->progressBar->height() - 2); d->progressDelayTimer = new QTimer(this); d->progressDelayTimer->setObjectName(QStringLiteral("d->progressBar delay timer")); connect(d->progressDelayTimer, SIGNAL(timeout()), SLOT(_k_slotShowProgress())); d->completeListDirty = false; // action stuff setupActions(); setupMenu(); d->sorting = QDir::NoSort; //so updateSorting() doesn't think nothing has changed d->updateSorting(QDir::Name | QDir::DirsFirst); setFocusPolicy(Qt::WheelFocus); } KDirOperator::~KDirOperator() { resetCursor(); disconnect(d->dirLister, nullptr, this, nullptr); delete d; } void KDirOperator::setSorting(QDir::SortFlags spec) { d->updateSorting(spec); } QDir::SortFlags KDirOperator::sorting() const { return d->sorting; } bool KDirOperator::isRoot() const { #ifdef Q_OS_WIN if (url().isLocalFile()) { const QString path = url().toLocalFile(); if (path.length() == 3) { return (path[0].isLetter() && path[1] == QLatin1Char(':') && path[2] == QLatin1Char('/')); } return false; } else #endif return url().path() == QLatin1String("/"); } KDirLister *KDirOperator::dirLister() const { return d->dirLister; } void KDirOperator::resetCursor() { if (qApp) { QApplication::restoreOverrideCursor(); } d->progressBar->hide(); } void KDirOperator::sortByName() { d->updateSorting((d->sorting & ~QDirSortMask) | QDir::Name); } void KDirOperator::sortBySize() { d->updateSorting((d->sorting & ~QDirSortMask) | QDir::Size); } void KDirOperator::sortByDate() { d->updateSorting((d->sorting & ~QDirSortMask) | QDir::Time); } void KDirOperator::sortByType() { d->updateSorting((d->sorting & ~QDirSortMask) | QDir::Type); } void KDirOperator::sortReversed() { // toggle it, hence the inversion of current state d->_k_slotSortReversed(!(d->sorting & QDir::Reversed)); } void KDirOperator::toggleDirsFirst() { d->_k_slotToggleDirsFirst(); } void KDirOperator::toggleIgnoreCase() { if (d->proxyModel != nullptr) { Qt::CaseSensitivity cs = d->proxyModel->sortCaseSensitivity(); cs = (cs == Qt::CaseSensitive) ? Qt::CaseInsensitive : Qt::CaseSensitive; d->proxyModel->setSortCaseSensitivity(cs); } } void KDirOperator::updateSelectionDependentActions() { const bool hasSelection = (d->itemView != nullptr) && d->itemView->selectionModel()->hasSelection(); d->actionCollection->action(QStringLiteral("trash"))->setEnabled(hasSelection); d->actionCollection->action(QStringLiteral("delete"))->setEnabled(hasSelection); d->actionCollection->action(QStringLiteral("properties"))->setEnabled(hasSelection); } void KDirOperator::setPreviewWidget(KPreviewWidgetBase *w) { const bool showPreview = (w != nullptr); if (showPreview) { d->viewKind = (d->viewKind | KFile::PreviewContents); } else { d->viewKind = (d->viewKind & ~KFile::PreviewContents); } delete d->preview; d->preview = w; if (w) { d->splitter->addWidget(w); } KToggleAction *previewAction = static_cast(d->actionCollection->action(QStringLiteral("preview"))); previewAction->setEnabled(showPreview); previewAction->setChecked(showPreview); setView(static_cast(d->viewKind)); } KFileItemList KDirOperator::selectedItems() const { KFileItemList itemList; if (d->itemView == nullptr) { return itemList; } const QItemSelection selection = d->proxyModel->mapSelectionToSource(d->itemView->selectionModel()->selection()); const QModelIndexList indexList = selection.indexes(); foreach (const QModelIndex &index, indexList) { KFileItem item = d->dirModel->itemForIndex(index); if (!item.isNull()) { itemList.append(item); } } return itemList; } bool KDirOperator::isSelected(const KFileItem &item) const { if ((item.isNull()) || (d->itemView == nullptr)) { return false; } const QModelIndex dirIndex = d->dirModel->indexForItem(item); const QModelIndex proxyIndex = d->proxyModel->mapFromSource(dirIndex); return d->itemView->selectionModel()->isSelected(proxyIndex); } int KDirOperator::numDirs() const { return (d->dirLister == nullptr) ? 0 : d->dirLister->directories().count(); } int KDirOperator::numFiles() const { return (d->dirLister == nullptr) ? 0 : d->dirLister->items().count() - numDirs(); } KCompletion *KDirOperator::completionObject() const { return const_cast(&d->completion); } KCompletion *KDirOperator::dirCompletionObject() const { return const_cast(&d->dirCompletion); } KActionCollection *KDirOperator::actionCollection() const { return d->actionCollection; } KFile::FileView KDirOperator::Private::allViews() { return static_cast(KFile::Simple | KFile::Detail | KFile::Tree | KFile::DetailTree); } void KDirOperator::Private::_k_slotDetailedView() { KFile::FileView view = static_cast((viewKind & ~allViews()) | KFile::Detail); parent->setView(view); } void KDirOperator::Private::_k_slotSimpleView() { KFile::FileView view = static_cast((viewKind & ~allViews()) | KFile::Simple); parent->setView(view); } void KDirOperator::Private::_k_slotTreeView() { KFile::FileView view = static_cast((viewKind & ~allViews()) | KFile::Tree); parent->setView(view); } void KDirOperator::Private::_k_slotDetailedTreeView() { KFile::FileView view = static_cast((viewKind & ~allViews()) | KFile::DetailTree); parent->setView(view); } void KDirOperator::Private::_k_slotToggleHidden(bool show) { dirLister->setShowingDotFiles(show); parent->updateDir(); _k_assureVisibleSelection(); } void KDirOperator::Private::_k_togglePreview(bool on) { if (on) { viewKind = viewKind | KFile::PreviewContents; if (preview == nullptr) { preview = new KFileMetaPreview(parent); actionCollection->action(QStringLiteral("preview"))->setChecked(true); splitter->addWidget(preview); } preview->show(); QMetaObject::invokeMethod(parent, "_k_assureVisibleSelection", Qt::QueuedConnection); if (itemView != nullptr) { const QModelIndex index = itemView->selectionModel()->currentIndex(); if (index.isValid()) { _k_triggerPreview(index); } } } else if (preview != nullptr) { viewKind = viewKind & ~KFile::PreviewContents; preview->hide(); } } void KDirOperator::Private::_k_toggleInlinePreviews(bool show) { if (showPreviews == show) { return; } showPreviews = show; if (!previewGenerator) { return; } previewGenerator->setPreviewShown(show); } void KDirOperator::Private::_k_slotOpenFileManager() { const KFileItemList list = parent->selectedItems(); if (list.isEmpty()) { KIO::highlightInFileManager({currUrl.adjusted(QUrl::StripTrailingSlash)}); } else { KIO::highlightInFileManager(list.urlList()); } } void KDirOperator::Private::_k_slotSortByName() { parent->sortByName(); } void KDirOperator::Private::_k_slotSortBySize() { parent->sortBySize(); } void KDirOperator::Private::_k_slotSortByDate() { parent->sortByDate(); } void KDirOperator::Private::_k_slotSortByType() { parent->sortByType(); } void KDirOperator::Private::_k_slotSortReversed(bool doReverse) { QDir::SortFlags s = sorting & ~QDir::Reversed; if (doReverse) { s |= QDir::Reversed; } updateSorting(s); } void KDirOperator::Private::_k_slotToggleDirsFirst() { QDir::SortFlags s = (sorting ^ QDir::DirsFirst); updateSorting(s); } void KDirOperator::Private::_k_slotToggleIgnoreCase() { // TODO: port to Qt4's QAbstractItemView /*if ( !d->fileView ) return; QDir::SortFlags sorting = d->fileView->sorting(); if ( !KFile::isSortCaseInsensitive( sorting ) ) d->fileView->setSorting( sorting | QDir::IgnoreCase ); else d->fileView->setSorting( sorting & ~QDir::IgnoreCase ); d->sorting = d->fileView->sorting();*/ } void KDirOperator::mkdir() { d->newFileMenu->setPopupFiles(QList() << url()); d->newFileMenu->setViewShowsHiddenFiles(showHiddenFiles()); d->newFileMenu->createDirectory(); } bool KDirOperator::mkdir(const QString &directory, bool enterDirectory) { // Creates "directory", relative to the current directory (d->currUrl). // The given path may contain any number directories, existent or not. // They will all be created, if possible. // TODO: very similar to KDirSelectDialog::Private::slotMkdir bool writeOk = false; bool exists = false; QUrl folderurl(d->currUrl); const QStringList dirs = directory.split(QLatin1Char('/'), QString::SkipEmptyParts); QStringList::ConstIterator it = dirs.begin(); for (; it != dirs.end(); ++it) { folderurl.setPath(concatPaths(folderurl.path(), *it)); if (folderurl.isLocalFile()) { exists = QFile::exists(folderurl.toLocalFile()); } else { KIO::StatJob *job = KIO::stat(folderurl); KJobWidgets::setWindow(job, this); job->setDetails(0); //We only want to know if it exists, 0 == that. job->setSide(KIO::StatJob::DestinationSide); exists = job->exec(); } if (!exists) { KIO::Job *job = KIO::mkdir(folderurl); KJobWidgets::setWindow(job, this); writeOk = job->exec(); } } if (exists) { // url was already existent KMessageBox::sorry(d->itemView, i18n("A file or folder named %1 already exists.", folderurl.toDisplayString(QUrl::PreferLocalFile))); } else if (!writeOk) { KMessageBox::sorry(d->itemView, i18n("You do not have permission to " "create that folder.")); } else if (enterDirectory) { setUrl(folderurl, true); } return writeOk; } KIO::DeleteJob *KDirOperator::del(const KFileItemList &items, QWidget *parent, bool ask, bool showProgress) { if (items.isEmpty()) { KMessageBox::information(parent, i18n("You did not select a file to delete."), i18n("Nothing to Delete")); return nullptr; } if (parent == nullptr) { parent = this; } const QList urls = items.urlList(); bool doIt = !ask; if (ask) { KIO::JobUiDelegate uiDelegate; uiDelegate.setWindow(parent); doIt = uiDelegate.askDeleteConfirmation(urls, KIO::JobUiDelegate::Delete, KIO::JobUiDelegate::DefaultConfirmation); } if (doIt) { KIO::JobFlags flags = showProgress ? KIO::DefaultFlags : KIO::HideProgressInfo; KIO::DeleteJob *job = KIO::del(urls, flags); KJobWidgets::setWindow(job, this); job->uiDelegate()->setAutoErrorHandlingEnabled(true); return job; } return nullptr; } void KDirOperator::deleteSelected() { const KFileItemList list = selectedItems(); if (!list.isEmpty()) { del(list, this); } } KIO::CopyJob *KDirOperator::trash(const KFileItemList &items, QWidget *parent, bool ask, bool showProgress) { if (items.isEmpty()) { KMessageBox::information(parent, i18n("You did not select a file to trash."), i18n("Nothing to Trash")); return nullptr; } const QList urls = items.urlList(); bool doIt = !ask; if (ask) { KIO::JobUiDelegate uiDelegate; uiDelegate.setWindow(parent); doIt = uiDelegate.askDeleteConfirmation(urls, KIO::JobUiDelegate::Trash, KIO::JobUiDelegate::DefaultConfirmation); } if (doIt) { KIO::JobFlags flags = showProgress ? KIO::DefaultFlags : KIO::HideProgressInfo; KIO::CopyJob *job = KIO::trash(urls, flags); KJobWidgets::setWindow(job, this); job->uiDelegate()->setAutoErrorHandlingEnabled(true); return job; } return nullptr; } KFilePreviewGenerator *KDirOperator::previewGenerator() const { return d->previewGenerator; } void KDirOperator::setInlinePreviewShown(bool show) { d->inlinePreviewState = show ? Private::ForcedToTrue : Private::ForcedToFalse; } bool KDirOperator::isInlinePreviewShown() const { return d->showPreviews; } int KDirOperator::iconsZoom() const { return d->iconsZoom; } void KDirOperator::setIsSaving(bool isSaving) { d->isSaving = isSaving; } bool KDirOperator::isSaving() const { return d->isSaving; } void KDirOperator::trashSelected() { if (d->itemView == nullptr) { return; } if (QApplication::keyboardModifiers() & Qt::ShiftModifier) { deleteSelected(); return; } const KFileItemList list = selectedItems(); if (!list.isEmpty()) { trash(list, this); } } void KDirOperator::setIconsZoom(int _value) { if (d->iconsZoom == _value) { return; } int value = _value; value = qMin(100, value); value = qMax(0, value); d->iconsZoom = value; if (!d->previewGenerator) { return; } const int maxSize = KIconLoader::SizeEnormous - KIconLoader::SizeSmall; const int val = (maxSize * value / 100) + KIconLoader::SizeSmall; d->itemView->setIconSize(QSize(val, val)); d->updateListViewGrid(); d->previewGenerator->updatePreviews(); emit currentIconSizeChanged(value); } void KDirOperator::close() { resetCursor(); d->pendingMimeTypes.clear(); d->completion.clear(); d->dirCompletion.clear(); d->completeListDirty = true; d->dirLister->stop(); } void KDirOperator::Private::checkPath(const QString &, bool /*takeFiles*/) // SLOT { #if 0 // copy the argument in a temporary string QString text = _txt; // it's unlikely to happen, that at the beginning are spaces, but // for the end, it happens quite often, I guess. text = text.trimmed(); // if the argument is no URL (the check is quite fragil) and it's // no absolute path, we add the current directory to get a correct url if (text.find(':') < 0 && text[0] != '/') { text.insert(0, d->currUrl); } // in case we have a selection defined and someone patched the file- // name, we check, if the end of the new name is changed. if (!selection.isNull()) { int position = text.lastIndexOf('/'); ASSERT(position >= 0); // we already inserted the current d->dirLister in case QString filename = text.mid(position + 1, text.length()); if (filename != selection) { selection.clear(); } } QUrl u(text); // I have to take care of entered URLs bool filenameEntered = false; if (u.isLocalFile()) { // the empty path is kind of a hack KFileItem i("", u.toLocalFile()); if (i.isDir()) { setUrl(text, true); } else { if (takeFiles) if (acceptOnlyExisting && !i.isFile()) { warning("you entered an invalid URL"); } else { filenameEntered = true; } } } else { setUrl(text, true); } if (filenameEntered) { filename_ = u.url(); emit fileSelected(filename_); QApplication::restoreOverrideCursor(); accept(); } #endif // qDebug() << "TODO KDirOperator::checkPath()"; } void KDirOperator::setUrl(const QUrl &_newurl, bool clearforward) { QUrl newurl; if (!_newurl.isValid()) { newurl = QUrl::fromLocalFile(QDir::homePath()); } else { newurl = _newurl.adjusted(QUrl::NormalizePathSegments); } if (!newurl.path().isEmpty() && !newurl.path().endsWith(QLatin1Char('/'))) { newurl.setPath(newurl.path() + QLatin1Char('/')); } // already set if (newurl.matches(d->currUrl, QUrl::StripTrailingSlash)) { return; } if (!d->isSchemeSupported(newurl.scheme())) return; if (!Private::isReadable(newurl)) { // maybe newurl is a file? check its parent directory newurl = newurl.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); if (newurl.matches(d->currUrl, QUrl::StripTrailingSlash)) { return; // parent is current dir, nothing to do (fixes #173454, too) } KIO::StatJob *job = KIO::stat(newurl); KJobWidgets::setWindow(job, this); bool res = job->exec(); KIO::UDSEntry entry = job->statResult(); KFileItem i(entry, newurl); if ((!res || !Private::isReadable(newurl)) && i.isDir()) { resetCursor(); KMessageBox::error(d->itemView, i18n("The specified folder does not exist " "or was not readable.")); return; } else if (!i.isDir()) { return; } } if (clearforward) { // autodelete should remove this one d->backStack.push(new QUrl(d->currUrl)); qDeleteAll(d->forwardStack); d->forwardStack.clear(); } d->lastURL = d->currUrl.toString(QUrl::StripTrailingSlash); d->currUrl = newurl; pathChanged(); emit urlEntered(newurl); // enable/disable actions QAction *forwardAction = d->actionCollection->action(QStringLiteral("forward")); forwardAction->setEnabled(!d->forwardStack.isEmpty()); QAction *backAction = d->actionCollection->action(QStringLiteral("back")); backAction->setEnabled(!d->backStack.isEmpty()); QAction *upAction = d->actionCollection->action(QStringLiteral("up")); upAction->setEnabled(!isRoot()); d->openUrl(newurl); } void KDirOperator::updateDir() { QApplication::setOverrideCursor(Qt::WaitCursor); d->dirLister->emitChanges(); QApplication::restoreOverrideCursor(); } void KDirOperator::rereadDir() { pathChanged(); d->openUrl(d->currUrl, KDirLister::Reload); } bool KDirOperator::Private::isSchemeSupported(const QString &scheme) const { return supportedSchemes.isEmpty() || supportedSchemes.contains(scheme); } bool KDirOperator::Private::openUrl(const QUrl &url, KDirLister::OpenUrlFlags flags) { const bool result = KProtocolManager::supportsListing(url) && isSchemeSupported(url.scheme()) && dirLister->openUrl(url, flags); if (!result) { // in that case, neither completed() nor canceled() will be emitted by KDL _k_slotCanceled(); } return result; } int KDirOperator::Private::sortColumn() const { int column = KDirModel::Name; if (KFile::isSortByDate(sorting)) { column = KDirModel::ModifiedTime; } else if (KFile::isSortBySize(sorting)) { column = KDirModel::Size; } else if (KFile::isSortByType(sorting)) { column = KDirModel::Type; } else { Q_ASSERT(KFile::isSortByName(sorting)); } return column; } Qt::SortOrder KDirOperator::Private::sortOrder() const { return (sorting & QDir::Reversed) ? Qt::DescendingOrder : Qt::AscendingOrder; } void KDirOperator::Private::updateSorting(QDir::SortFlags sort) { // qDebug() << "changing sort flags from" << sorting << "to" << sort; if (sort == sorting) { return; } if ((sorting ^ sort) & QDir::DirsFirst) { // The "Folders First" setting has been changed. // We need to make sure that the files and folders are really re-sorted. // Without the following intermediate "fake resorting", // QSortFilterProxyModel::sort(int column, Qt::SortOrder order) // would do nothing because neither the column nor the sort order have been changed. Qt::SortOrder tmpSortOrder = (sortOrder() == Qt::AscendingOrder ? Qt::DescendingOrder : Qt::AscendingOrder); proxyModel->sort(sortOrder(), tmpSortOrder); proxyModel->setSortFoldersFirst(sort & QDir::DirsFirst); } sorting = sort; parent->updateSortActions(); proxyModel->sort(sortColumn(), sortOrder()); // TODO: The headers from QTreeView don't take care about a sorting // change of the proxy model hence they must be updated the manually. // This is done here by a qobject_cast, but it would be nicer to: // - provide a signal 'sortingChanged()' // - connect KDirOperatorDetailView() with this signal and update the // header internally QTreeView *treeView = qobject_cast(itemView); if (treeView != nullptr) { QHeaderView *headerView = treeView->header(); headerView->blockSignals(true); headerView->setSortIndicator(sortColumn(), sortOrder()); headerView->blockSignals(false); } _k_assureVisibleSelection(); } // Protected void KDirOperator::pathChanged() { if (d->itemView == nullptr) { return; } d->pendingMimeTypes.clear(); //d->fileView->clear(); TODO d->completion.clear(); d->dirCompletion.clear(); // it may be, that we weren't ready at this time QApplication::restoreOverrideCursor(); // when KIO::Job emits finished, the slot will restore the cursor QApplication::setOverrideCursor(Qt::WaitCursor); if (!Private::isReadable(d->currUrl)) { KMessageBox::error(d->itemView, i18n("The specified folder does not exist " "or was not readable.")); if (d->backStack.isEmpty()) { home(); } else { back(); } } } void KDirOperator::Private::_k_slotRedirected(const QUrl &newURL) { currUrl = newURL; pendingMimeTypes.clear(); completion.clear(); dirCompletion.clear(); completeListDirty = true; emit parent->urlEntered(newURL); } // Code pinched from kfm then hacked void KDirOperator::back() { if (d->backStack.isEmpty()) { return; } d->forwardStack.push(new QUrl(d->currUrl)); QUrl *s = d->backStack.pop(); setUrl(*s, false); delete s; } // Code pinched from kfm then hacked void KDirOperator::forward() { if (d->forwardStack.isEmpty()) { return; } d->backStack.push(new QUrl(d->currUrl)); QUrl *s = d->forwardStack.pop(); setUrl(*s, false); delete s; } QUrl KDirOperator::url() const { return d->currUrl; } void KDirOperator::cdUp() { // Allow /d/c// to go up to /d/ instead of /d/c/ QUrl tmp(d->currUrl.adjusted(QUrl::NormalizePathSegments)); setUrl(tmp.resolved(QUrl(QStringLiteral(".."))), true); } void KDirOperator::home() { setUrl(QUrl::fromLocalFile(QDir::homePath()), true); } void KDirOperator::clearFilter() { d->dirLister->setNameFilter(QString()); d->dirLister->clearMimeFilter(); checkPreviewSupport(); } void KDirOperator::setNameFilter(const QString &filter) { d->dirLister->setNameFilter(filter); checkPreviewSupport(); } QString KDirOperator::nameFilter() const { return d->dirLister->nameFilter(); } void KDirOperator::setMimeFilter(const QStringList &mimetypes) { d->dirLister->setMimeFilter(mimetypes); checkPreviewSupport(); } QStringList KDirOperator::mimeFilter() const { return d->dirLister->mimeFilters(); } void KDirOperator::setNewFileMenuSupportedMimeTypes(const QStringList &mimeTypes) { d->newFileMenu->setSupportedMimeTypes(mimeTypes); } QStringList KDirOperator::newFileMenuSupportedMimeTypes() const { return d->newFileMenu->supportedMimeTypes(); } bool KDirOperator::checkPreviewSupport() { KToggleAction *previewAction = static_cast(d->actionCollection->action(QStringLiteral("preview"))); bool hasPreviewSupport = false; KConfigGroup cg(KSharedConfig::openConfig(), ConfigGroup); if (cg.readEntry("Show Default Preview", true)) { hasPreviewSupport = d->checkPreviewInternal(); } previewAction->setEnabled(hasPreviewSupport); return hasPreviewSupport; } void KDirOperator::activatedMenu(const KFileItem &item, const QPoint &pos) { updateSelectionDependentActions(); d->newFileMenu->setPopupFiles(QList() << item.url()); d->newFileMenu->setViewShowsHiddenFiles(showHiddenFiles()); d->newFileMenu->checkUpToDate(); emit contextMenuAboutToShow(item, d->actionMenu->menu()); d->actionMenu->menu()->exec(pos); } void KDirOperator::changeEvent(QEvent *event) { QWidget::changeEvent(event); } bool KDirOperator::eventFilter(QObject *watched, QEvent *event) { Q_UNUSED(watched); // If we are not hovering any items, check if there is a current index // set. In that case, we show the preview of that item. switch (event->type()) { case QEvent::MouseMove: { if (d->preview && !d->preview->isHidden()) { const QModelIndex hoveredIndex = d->itemView->indexAt(d->itemView->viewport()->mapFromGlobal(QCursor::pos())); if (d->lastHoveredIndex == hoveredIndex) { return QWidget::eventFilter(watched, event); } d->lastHoveredIndex = hoveredIndex; const QModelIndex focusedIndex = d->itemView->selectionModel() ? d->itemView->selectionModel()->currentIndex() : QModelIndex(); if (!hoveredIndex.isValid() && focusedIndex.isValid() && d->itemView->selectionModel()->isSelected(focusedIndex) && (d->lastHoveredIndex != focusedIndex)) { const QModelIndex sourceFocusedIndex = d->proxyModel->mapToSource(focusedIndex); const KFileItem item = d->dirModel->itemForIndex(sourceFocusedIndex); if (!item.isNull()) { d->preview->showPreview(item.url()); } } } } break; case QEvent::MouseButtonRelease: { if (d->preview != nullptr && !d->preview->isHidden()) { const QModelIndex hoveredIndex = d->itemView->indexAt(d->itemView->viewport()->mapFromGlobal(QCursor::pos())); const QModelIndex focusedIndex = d->itemView->selectionModel() ? d->itemView->selectionModel()->currentIndex() : QModelIndex(); if (((!focusedIndex.isValid()) || !d->itemView->selectionModel()->isSelected(focusedIndex)) && (!hoveredIndex.isValid())) { d->preview->clearPreview(); } } QMouseEvent *mouseEvent = static_cast(event); if (mouseEvent) { switch (mouseEvent->button()) { case Qt::BackButton: back(); break; case Qt::ForwardButton: forward(); break; default: break; } } } break; case QEvent::Wheel: { QWheelEvent *evt = static_cast(event); if (evt->modifiers() & Qt::ControlModifier) { if (evt->delta() > 0) { setIconsZoom(d->iconsZoom + 10); } else { setIconsZoom(d->iconsZoom - 10); } return true; } } break; default: break; } return QWidget::eventFilter(watched, event); } bool KDirOperator::Private::checkPreviewInternal() const { const QStringList supported = KIO::PreviewJob::supportedMimeTypes(); // no preview support for directories? if (parent->dirOnlyMode() && supported.indexOf(QStringLiteral("inode/directory")) == -1) { return false; } QStringList mimeTypes = dirLister->mimeFilters(); const QStringList nameFilter = dirLister->nameFilter().split(QLatin1Char(' '), QString::SkipEmptyParts); QMimeDatabase db; if (mimeTypes.isEmpty() && nameFilter.isEmpty() && !supported.isEmpty()) { return true; } else { QRegExp r; r.setPatternSyntax(QRegExp::Wildcard); // the "mimetype" can be "image/*" if (!mimeTypes.isEmpty()) { QStringList::ConstIterator it = supported.begin(); for (; it != supported.end(); ++it) { r.setPattern(*it); QStringList result = mimeTypes.filter(r); if (!result.isEmpty()) { // matches! -> we want previews return true; } } } if (!nameFilter.isEmpty()) { // find the mimetypes of all the filter-patterns QStringList::const_iterator it1 = nameFilter.begin(); for (; it1 != nameFilter.end(); ++it1) { if ((*it1) == QLatin1String("*")) { return true; } QMimeType mt = db.mimeTypeForFile(*it1, QMimeDatabase::MatchExtension /*fast mode, no file contents exist*/); if (!mt.isValid()) { continue; } QString mime = mt.name(); // the "mimetypes" we get from the PreviewJob can be "image/*" // so we need to check in wildcard mode QStringList::ConstIterator it2 = supported.begin(); for (; it2 != supported.end(); ++it2) { r.setPattern(*it2); if (r.indexIn(mime) != -1) { return true; } } } } } return false; } QAbstractItemView *KDirOperator::createView(QWidget *parent, KFile::FileView viewKind) { QAbstractItemView *itemView = nullptr; if (KFile::isDetailView(viewKind) || KFile::isTreeView(viewKind) || KFile::isDetailTreeView(viewKind)) { KDirOperatorDetailView *detailView = new KDirOperatorDetailView(parent); detailView->setViewMode(viewKind); itemView = detailView; } else { itemView = new KDirOperatorIconView(this, parent); } return itemView; } void KDirOperator::setAcceptDrops(bool b) { // TODO: //if (d->fileView) // d->fileView->widget()->setAcceptDrops(b); QWidget::setAcceptDrops(b); } void KDirOperator::setDropOptions(int options) { d->dropOptions = options; // TODO: //if (d->fileView) // d->fileView->setDropOptions(options); } void KDirOperator::setView(KFile::FileView viewKind) { bool preview = (KFile::isPreviewInfo(viewKind) || KFile::isPreviewContents(viewKind)); if (viewKind == KFile::Default) { if (KFile::isDetailView((KFile::FileView)d->defaultView)) { viewKind = KFile::Detail; } else if (KFile::isTreeView((KFile::FileView)d->defaultView)) { viewKind = KFile::Tree; } else if (KFile::isDetailTreeView((KFile::FileView)d->defaultView)) { viewKind = KFile::DetailTree; } else { viewKind = KFile::Simple; } const KFile::FileView defaultViewKind = static_cast(d->defaultView); preview = (KFile::isPreviewInfo(defaultViewKind) || KFile::isPreviewContents(defaultViewKind)) && d->actionCollection->action(QStringLiteral("preview"))->isEnabled(); } d->viewKind = static_cast(viewKind); viewKind = static_cast(d->viewKind); QAbstractItemView *newView = createView(this, viewKind); setView(newView); d->_k_togglePreview(preview); } KFile::FileView KDirOperator::viewMode() const { return static_cast(d->viewKind); } QAbstractItemView *KDirOperator::view() const { return d->itemView; } KFile::Modes KDirOperator::mode() const { return d->mode; } void KDirOperator::setMode(KFile::Modes mode) { if (d->mode == mode) { return; } d->mode = mode; d->dirLister->setDirOnlyMode(dirOnlyMode()); // reset the view with the different mode if (d->itemView != nullptr) { setView(static_cast(d->viewKind)); } } void KDirOperator::setView(QAbstractItemView *view) { if (view == d->itemView) { return; } // TODO: do a real timer and restart it after that d->pendingMimeTypes.clear(); const bool listDir = (d->itemView == nullptr); if (d->mode & KFile::Files) { view->setSelectionMode(QAbstractItemView::ExtendedSelection); } else { view->setSelectionMode(QAbstractItemView::SingleSelection); } QItemSelectionModel *selectionModel = nullptr; if ((d->itemView != nullptr) && d->itemView->selectionModel()->hasSelection()) { // remember the selection of the current item view and apply this selection // to the new view later const QItemSelection selection = d->itemView->selectionModel()->selection(); selectionModel = new QItemSelectionModel(d->proxyModel, this); selectionModel->select(selection, QItemSelectionModel::Select); } setFocusProxy(nullptr); delete d->itemView; d->itemView = view; d->itemView->setModel(d->proxyModel); setFocusProxy(d->itemView); view->viewport()->installEventFilter(this); KFileItemDelegate *delegate = new KFileItemDelegate(d->itemView); d->itemView->setItemDelegate(delegate); d->itemView->viewport()->setAttribute(Qt::WA_Hover); d->itemView->setContextMenuPolicy(Qt::CustomContextMenu); d->itemView->setMouseTracking(true); //d->itemView->setDropOptions(d->dropOptions); // first push our settings to the view, then listen for changes from the view QTreeView *treeView = qobject_cast(d->itemView); if (treeView) { QHeaderView *headerView = treeView->header(); headerView->setSortIndicator(d->sortColumn(), d->sortOrder()); connect(headerView, SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), this, SLOT(_k_synchronizeSortingState(int,Qt::SortOrder))); } connect(d->itemView, SIGNAL(activated(QModelIndex)), this, SLOT(_k_slotActivated(QModelIndex))); connect(d->itemView, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(_k_openContextMenu(QPoint))); connect(d->itemView, SIGNAL(entered(QModelIndex)), this, SLOT(_k_triggerPreview(QModelIndex))); updateViewActions(); d->splitter->insertWidget(0, d->itemView); d->splitter->resize(size()); d->itemView->show(); if (listDir) { QApplication::setOverrideCursor(Qt::WaitCursor); d->openUrl(d->currUrl); } if (selectionModel != nullptr) { d->itemView->setSelectionModel(selectionModel); QMetaObject::invokeMethod(this, "_k_assureVisibleSelection", Qt::QueuedConnection); } connect(d->itemView->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(_k_triggerPreview(QModelIndex))); connect(d->itemView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(_k_slotSelectionChanged())); // if we cannot cast it to a QListView, disable the "Icon Position" menu. Note that this check // needs to be done here, and not in createView, since we can be set an external view d->decorationMenu->setEnabled(qobject_cast(d->itemView)); d->shouldFetchForItems = qobject_cast(view); if (d->shouldFetchForItems) { connect(d->dirModel, SIGNAL(expand(QModelIndex)), this, SLOT(_k_slotExpandToUrl(QModelIndex))); } else { d->itemsToBeSetAsCurrent.clear(); } const bool previewForcedToTrue = d->inlinePreviewState == Private::ForcedToTrue; const bool previewShown = d->inlinePreviewState == Private::NotForced ? d->showPreviews : previewForcedToTrue; d->previewGenerator = new KFilePreviewGenerator(d->itemView); const int maxSize = KIconLoader::SizeEnormous - KIconLoader::SizeSmall; const int val = (maxSize * d->iconsZoom / 100) + KIconLoader::SizeSmall; d->itemView->setIconSize(previewForcedToTrue ? QSize(KIconLoader::SizeHuge, KIconLoader::SizeHuge) : QSize(val, val)); d->previewGenerator->setPreviewShown(previewShown); d->actionCollection->action(QStringLiteral("inline preview"))->setChecked(previewShown); // ensure we change everything needed d->_k_slotChangeDecorationPosition(); emit viewChanged(view); const int zoom = previewForcedToTrue ? (KIconLoader::SizeHuge - KIconLoader::SizeSmall + 1) * 100 / maxSize : d->iconSizeForViewType(view); // this will make d->iconsZoom be updated, since setIconsZoom slot will be called emit currentIconSizeChanged(zoom); } void KDirOperator::setDirLister(KDirLister *lister) { if (lister == d->dirLister) { // sanity check return; } delete d->dirModel; d->dirModel = nullptr; delete d->proxyModel; d->proxyModel = nullptr; //delete d->dirLister; // deleted by KDirModel already, which took ownership d->dirLister = lister; d->dirModel = new KDirModel(); d->dirModel->setDirLister(d->dirLister); d->dirModel->setDropsAllowed(KDirModel::DropOnDirectory); d->shouldFetchForItems = qobject_cast(d->itemView); if (d->shouldFetchForItems) { connect(d->dirModel, SIGNAL(expand(QModelIndex)), this, SLOT(_k_slotExpandToUrl(QModelIndex))); } else { d->itemsToBeSetAsCurrent.clear(); } d->proxyModel = new KDirSortFilterProxyModel(this); d->proxyModel->setSourceModel(d->dirModel); d->dirLister->setDelayedMimeTypes(true); QWidget *mainWidget = topLevelWidget(); d->dirLister->setMainWindow(mainWidget); // qDebug() << "mainWidget=" << mainWidget; connect(d->dirLister, SIGNAL(percent(int)), SLOT(_k_slotProgress(int))); connect(d->dirLister, SIGNAL(started(QUrl)), SLOT(_k_slotStarted())); connect(d->dirLister, SIGNAL(completed()), SLOT(_k_slotIOFinished())); connect(d->dirLister, SIGNAL(canceled()), SLOT(_k_slotCanceled())); connect(d->dirLister, SIGNAL(redirection(QUrl)), SLOT(_k_slotRedirected(QUrl))); connect(d->dirLister, SIGNAL(newItems(KFileItemList)), SLOT(_k_slotItemsChanged())); connect(d->dirLister, SIGNAL(itemsDeleted(KFileItemList)), SLOT(_k_slotItemsChanged())); connect(d->dirLister, SIGNAL(itemsFilteredByMime(KFileItemList)), SLOT(_k_slotItemsChanged())); connect(d->dirLister, SIGNAL(clear()), SLOT(_k_slotItemsChanged())); } void KDirOperator::selectDir(const KFileItem &item) { setUrl(item.targetUrl(), true); } void KDirOperator::selectFile(const KFileItem &item) { QApplication::restoreOverrideCursor(); emit fileSelected(item); } void KDirOperator::highlightFile(const KFileItem &item) { if ((d->preview != nullptr && !d->preview->isHidden()) && !item.isNull()) { d->preview->showPreview(item.url()); } emit fileHighlighted(item); } void KDirOperator::setCurrentItem(const QUrl &url) { // qDebug(); KFileItem item = d->dirLister->findByUrl(url); if (d->shouldFetchForItems && item.isNull()) { d->itemsToBeSetAsCurrent << url; d->dirModel->expandToUrl(url); return; } setCurrentItem(item); } void KDirOperator::setCurrentItem(const KFileItem &item) { // qDebug(); if (!d->itemView) { return; } QItemSelectionModel *selModel = d->itemView->selectionModel(); if (selModel) { selModel->clear(); if (!item.isNull()) { const QModelIndex dirIndex = d->dirModel->indexForItem(item); const QModelIndex proxyIndex = d->proxyModel->mapFromSource(dirIndex); selModel->setCurrentIndex(proxyIndex, QItemSelectionModel::Select); } } } void KDirOperator::setCurrentItems(const QList &urls) { // qDebug(); if (!d->itemView) { return; } KFileItemList itemList; foreach (const QUrl &url, urls) { KFileItem item = d->dirLister->findByUrl(url); if (d->shouldFetchForItems && item.isNull()) { d->itemsToBeSetAsCurrent << url; d->dirModel->expandToUrl(url); continue; } itemList << item; } setCurrentItems(itemList); } void KDirOperator::setCurrentItems(const KFileItemList &items) { // qDebug(); if (d->itemView == nullptr) { return; } QItemSelectionModel *selModel = d->itemView->selectionModel(); if (selModel) { selModel->clear(); QModelIndex proxyIndex; foreach (const KFileItem &item, items) { if (!item.isNull()) { const QModelIndex dirIndex = d->dirModel->indexForItem(item); proxyIndex = d->proxyModel->mapFromSource(dirIndex); selModel->select(proxyIndex, QItemSelectionModel::Select); } } if (proxyIndex.isValid()) { selModel->setCurrentIndex(proxyIndex, QItemSelectionModel::NoUpdate); } } } QString KDirOperator::makeCompletion(const QString &string) { if (string.isEmpty()) { d->itemView->selectionModel()->clear(); return QString(); } prepareCompletionObjects(); return d->completion.makeCompletion(string); } QString KDirOperator::makeDirCompletion(const QString &string) { if (string.isEmpty()) { d->itemView->selectionModel()->clear(); return QString(); } prepareCompletionObjects(); return d->dirCompletion.makeCompletion(string); } void KDirOperator::prepareCompletionObjects() { if (d->itemView == nullptr) { return; } if (d->completeListDirty) { // create the list of all possible completions const KFileItemList itemList = d->dirLister->items(); foreach (const KFileItem &item, itemList) { d->completion.addItem(item.name()); if (item.isDir()) { d->dirCompletion.addItem(item.name()); } } d->completeListDirty = false; } } void KDirOperator::slotCompletionMatch(const QString &match) { QUrl url(match); if (url.isRelative()) { url = d->currUrl.resolved(url); } setCurrentItem(url); emit completion(match); } void KDirOperator::setupActions() { d->actionCollection = new KActionCollection(this); d->actionCollection->setObjectName(QStringLiteral("KDirOperator::actionCollection")); d->actionMenu = new KActionMenu(i18n("Menu"), this); d->actionCollection->addAction(QStringLiteral("popupMenu"), d->actionMenu); QAction *upAction = d->actionCollection->addAction(KStandardAction::Up, QStringLiteral("up"), this, SLOT(cdUp())); upAction->setText(i18n("Parent Folder")); d->actionCollection->addAction(KStandardAction::Back, QStringLiteral("back"), this, SLOT(back())); d->actionCollection->addAction(KStandardAction::Forward, QStringLiteral("forward"), this, SLOT(forward())); QAction *homeAction = d->actionCollection->addAction(KStandardAction::Home, QStringLiteral("home"), this, SLOT(home())); homeAction->setText(i18n("Home Folder")); QAction *reloadAction = d->actionCollection->addAction(KStandardAction::Redisplay, QStringLiteral("reload"), this, SLOT(rereadDir())); reloadAction->setText(i18n("Reload")); reloadAction->setShortcuts(KStandardShortcut::shortcut(KStandardShortcut::Reload)); QAction *mkdirAction = new QAction(i18n("New Folder..."), this); d->actionCollection->addAction(QStringLiteral("mkdir"), mkdirAction); mkdirAction->setIcon(QIcon::fromTheme(QStringLiteral("folder-new"))); connect(mkdirAction, SIGNAL(triggered(bool)), this, SLOT(mkdir())); QAction *trash = new QAction(i18n("Move to Trash"), this); d->actionCollection->addAction(QStringLiteral("trash"), trash); trash->setIcon(QIcon::fromTheme(QStringLiteral("user-trash"))); trash->setShortcut(Qt::Key_Delete); connect(trash, &QAction::triggered, this, &KDirOperator::trashSelected); QAction *action = new QAction(i18n("Delete"), this); d->actionCollection->addAction(QStringLiteral("delete"), action); action->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete"))); action->setShortcut(Qt::SHIFT + Qt::Key_Delete); connect(action, &QAction::triggered, this, &KDirOperator::deleteSelected); // the sort menu actions KActionMenu *sortMenu = new KActionMenu(i18n("Sorting"), this); sortMenu->setIcon(QIcon::fromTheme(QStringLiteral("view-sort"))); sortMenu->setDelayed(false); d->actionCollection->addAction(QStringLiteral("sorting menu"), sortMenu); KToggleAction *byNameAction = new KToggleAction(i18n("Sort by Name"), this); d->actionCollection->addAction(QStringLiteral("by name"), byNameAction); connect(byNameAction, SIGNAL(triggered(bool)), this, SLOT(_k_slotSortByName())); KToggleAction *bySizeAction = new KToggleAction(i18n("Sort by Size"), this); d->actionCollection->addAction(QStringLiteral("by size"), bySizeAction); connect(bySizeAction, SIGNAL(triggered(bool)), this, SLOT(_k_slotSortBySize())); KToggleAction *byDateAction = new KToggleAction(i18n("Sort by Date"), this); d->actionCollection->addAction(QStringLiteral("by date"), byDateAction); connect(byDateAction, SIGNAL(triggered(bool)), this, SLOT(_k_slotSortByDate())); KToggleAction *byTypeAction = new KToggleAction(i18n("Sort by Type"), this); d->actionCollection->addAction(QStringLiteral("by type"), byTypeAction); connect(byTypeAction, SIGNAL(triggered(bool)), this, SLOT(_k_slotSortByType())); KToggleAction *descendingAction = new KToggleAction(i18n("Descending"), this); d->actionCollection->addAction(QStringLiteral("descending"), descendingAction); connect(descendingAction, SIGNAL(triggered(bool)), this, SLOT(_k_slotSortReversed(bool))); KToggleAction *dirsFirstAction = new KToggleAction(i18n("Folders First"), this); d->actionCollection->addAction(QStringLiteral("dirs first"), dirsFirstAction); connect(dirsFirstAction, SIGNAL(triggered(bool)), this, SLOT(_k_slotToggleDirsFirst())); QActionGroup *sortGroup = new QActionGroup(this); byNameAction->setActionGroup(sortGroup); bySizeAction->setActionGroup(sortGroup); byDateAction->setActionGroup(sortGroup); byTypeAction->setActionGroup(sortGroup); d->decorationMenu = new KActionMenu(i18n("Icon Position"), this); d->actionCollection->addAction(QStringLiteral("decoration menu"), d->decorationMenu); d->leftAction = new KToggleAction(i18n("Next to File Name"), this); d->actionCollection->addAction(QStringLiteral("decorationAtLeft"), d->leftAction); connect(d->leftAction, SIGNAL(triggered(bool)), this, SLOT(_k_slotChangeDecorationPosition())); KToggleAction *topAction = new KToggleAction(i18n("Above File Name"), this); d->actionCollection->addAction(QStringLiteral("decorationAtTop"), topAction); connect(topAction, SIGNAL(triggered(bool)), this, SLOT(_k_slotChangeDecorationPosition())); d->decorationMenu->addAction(d->leftAction); d->decorationMenu->addAction(topAction); QActionGroup *decorationGroup = new QActionGroup(this); d->leftAction->setActionGroup(decorationGroup); topAction->setActionGroup(decorationGroup); KToggleAction *shortAction = new KToggleAction(i18n("Short View"), this); d->actionCollection->addAction(QStringLiteral("short view"), shortAction); shortAction->setIcon(QIcon::fromTheme(QStringLiteral("view-list-icons"))); connect(shortAction, SIGNAL(triggered()), SLOT(_k_slotSimpleView())); KToggleAction *detailedAction = new KToggleAction(i18n("Detailed View"), this); d->actionCollection->addAction(QStringLiteral("detailed view"), detailedAction); detailedAction->setIcon(QIcon::fromTheme(QStringLiteral("view-list-details"))); connect(detailedAction, SIGNAL(triggered()), SLOT(_k_slotDetailedView())); KToggleAction *treeAction = new KToggleAction(i18n("Tree View"), this); d->actionCollection->addAction(QStringLiteral("tree view"), treeAction); treeAction->setIcon(QIcon::fromTheme(QStringLiteral("view-list-tree"))); connect(treeAction, SIGNAL(triggered()), SLOT(_k_slotTreeView())); KToggleAction *detailedTreeAction = new KToggleAction(i18n("Detailed Tree View"), this); d->actionCollection->addAction(QStringLiteral("detailed tree view"), detailedTreeAction); detailedTreeAction->setIcon(QIcon::fromTheme(QStringLiteral("view-list-tree"))); connect(detailedTreeAction, SIGNAL(triggered()), SLOT(_k_slotDetailedTreeView())); QActionGroup *viewGroup = new QActionGroup(this); shortAction->setActionGroup(viewGroup); detailedAction->setActionGroup(viewGroup); treeAction->setActionGroup(viewGroup); detailedTreeAction->setActionGroup(viewGroup); KToggleAction *showHiddenAction = new KToggleAction(i18n("Show Hidden Files"), this); d->actionCollection->addAction(QStringLiteral("show hidden"), showHiddenAction); showHiddenAction->setShortcuts({Qt::ALT + Qt::Key_Period, Qt::CTRL + Qt::Key_H, Qt::Key_F8}); connect(showHiddenAction, SIGNAL(toggled(bool)), SLOT(_k_slotToggleHidden(bool))); KToggleAction *previewAction = new KToggleAction(i18n("Show Aside Preview"), this); d->actionCollection->addAction(QStringLiteral("preview"), previewAction); previewAction->setShortcut(Qt::Key_F11); connect(previewAction, SIGNAL(toggled(bool)), SLOT(_k_togglePreview(bool))); KToggleAction *inlinePreview = new KToggleAction(QIcon::fromTheme(QStringLiteral("view-preview")), i18n("Show Preview"), this); d->actionCollection->addAction(QStringLiteral("inline preview"), inlinePreview); inlinePreview->setShortcut(Qt::Key_F12); connect(inlinePreview, SIGNAL(toggled(bool)), SLOT(_k_toggleInlinePreviews(bool))); QAction *fileManager = new QAction(i18n("Open Containing Folder"), this); d->actionCollection->addAction(QStringLiteral("file manager"), fileManager); fileManager->setIcon(QIcon::fromTheme(QStringLiteral("system-file-manager"))); connect(fileManager, SIGNAL(triggered()), SLOT(_k_slotOpenFileManager())); action = new QAction(i18n("Properties"), this); d->actionCollection->addAction(QStringLiteral("properties"), action); action->setIcon(QIcon::fromTheme(QStringLiteral("document-properties"))); action->setShortcut(Qt::ALT + Qt::Key_Return); connect(action, SIGNAL(triggered(bool)), this, SLOT(_k_slotProperties())); // the view menu actions KActionMenu *viewMenu = new KActionMenu(i18n("&View"), this); d->actionCollection->addAction(QStringLiteral("view menu"), viewMenu); viewMenu->addAction(shortAction); viewMenu->addAction(detailedAction); // Comment following lines to hide the extra two modes viewMenu->addAction(treeAction); viewMenu->addAction(detailedTreeAction); // TODO: QAbstractItemView does not offer an action collection. Provide // an interface to add a custom action collection. d->newFileMenu = new KNewFileMenu(d->actionCollection, QStringLiteral("new"), this); connect(d->newFileMenu, SIGNAL(directoryCreated(QUrl)), this, SLOT(_k_slotDirectoryCreated(QUrl))); d->actionCollection->addAssociatedWidget(this); foreach (QAction *action, d->actionCollection->actions()) { action->setShortcutContext(Qt::WidgetWithChildrenShortcut); } } void KDirOperator::setupMenu() { setupMenu(SortActions | ViewActions | FileActions); } void KDirOperator::setupMenu(int whichActions) { // first fill the submenus (sort and view) KActionMenu *sortMenu = static_cast(d->actionCollection->action(QStringLiteral("sorting menu"))); sortMenu->menu()->clear(); sortMenu->addAction(d->actionCollection->action(QStringLiteral("by name"))); sortMenu->addAction(d->actionCollection->action(QStringLiteral("by size"))); sortMenu->addAction(d->actionCollection->action(QStringLiteral("by date"))); sortMenu->addAction(d->actionCollection->action(QStringLiteral("by type"))); sortMenu->addSeparator(); sortMenu->addAction(d->actionCollection->action(QStringLiteral("descending"))); sortMenu->addAction(d->actionCollection->action(QStringLiteral("dirs first"))); // now plug everything into the popupmenu d->actionMenu->menu()->clear(); if (whichActions & NavActions) { d->actionMenu->addAction(d->actionCollection->action(QStringLiteral("up"))); d->actionMenu->addAction(d->actionCollection->action(QStringLiteral("back"))); d->actionMenu->addAction(d->actionCollection->action(QStringLiteral("forward"))); d->actionMenu->addAction(d->actionCollection->action(QStringLiteral("home"))); d->actionMenu->addSeparator(); } if (whichActions & FileActions) { d->actionMenu->addAction(d->actionCollection->action(QStringLiteral("new"))); if (d->currUrl.isLocalFile() && !(QApplication::keyboardModifiers() & Qt::ShiftModifier)) { d->actionMenu->addAction(d->actionCollection->action(QStringLiteral("trash"))); } KConfigGroup cg(KSharedConfig::openConfig(), QStringLiteral("KDE")); const bool del = !d->currUrl.isLocalFile() || (QApplication::keyboardModifiers() & Qt::ShiftModifier) || cg.readEntry("ShowDeleteCommand", false); if (del) { d->actionMenu->addAction(d->actionCollection->action(QStringLiteral("delete"))); } d->actionMenu->addSeparator(); } if (whichActions & SortActions) { d->actionMenu->addAction(sortMenu); if (!(whichActions & ViewActions)) { d->actionMenu->addSeparator(); } } if (whichActions & ViewActions) { d->actionMenu->addAction(d->actionCollection->action(QStringLiteral("view menu"))); d->actionMenu->addAction(d->actionCollection->action(QStringLiteral("reload"))); d->actionMenu->addSeparator(); } if (whichActions & FileActions) { d->actionMenu->addAction(d->actionCollection->action(QStringLiteral("file manager"))); d->actionMenu->addAction(d->actionCollection->action(QStringLiteral("properties"))); } } void KDirOperator::updateSortActions() { if (KFile::isSortByName(d->sorting)) { d->actionCollection->action(QStringLiteral("by name"))->setChecked(true); } else if (KFile::isSortByDate(d->sorting)) { d->actionCollection->action(QStringLiteral("by date"))->setChecked(true); } else if (KFile::isSortBySize(d->sorting)) { d->actionCollection->action(QStringLiteral("by size"))->setChecked(true); } else if (KFile::isSortByType(d->sorting)) { d->actionCollection->action(QStringLiteral("by type"))->setChecked(true); } d->actionCollection->action(QStringLiteral("descending"))->setChecked(d->sorting & QDir::Reversed); d->actionCollection->action(QStringLiteral("dirs first"))->setChecked(d->sorting & QDir::DirsFirst); } void KDirOperator::updateViewActions() { KFile::FileView fv = static_cast(d->viewKind); //QAction *separateDirs = d->actionCollection->action("separate dirs"); //separateDirs->setChecked(KFile::isSeparateDirs(fv) && // separateDirs->isEnabled()); d->actionCollection->action(QStringLiteral("short view"))->setChecked(KFile::isSimpleView(fv)); d->actionCollection->action(QStringLiteral("detailed view"))->setChecked(KFile::isDetailView(fv)); d->actionCollection->action(QStringLiteral("tree view"))->setChecked(KFile::isTreeView(fv)); d->actionCollection->action(QStringLiteral("detailed tree view"))->setChecked(KFile::isDetailTreeView(fv)); } void KDirOperator::readConfig(const KConfigGroup &configGroup) { d->defaultView = 0; QString viewStyle = configGroup.readEntry("View Style", "Simple"); if (viewStyle == QLatin1String("Detail")) { d->defaultView |= KFile::Detail; } else if (viewStyle == QLatin1String("Tree")) { d->defaultView |= KFile::Tree; } else if (viewStyle == QLatin1String("DetailTree")) { d->defaultView |= KFile::DetailTree; } else { d->defaultView |= KFile::Simple; } //if (configGroup.readEntry(QLatin1String("Separate Directories"), // DefaultMixDirsAndFiles)) { // d->defaultView |= KFile::SeparateDirs; //} if (configGroup.readEntry(QStringLiteral("Show Preview"), false)) { d->defaultView |= KFile::PreviewContents; } d->previewWidth = configGroup.readEntry(QStringLiteral("Preview Width"), 100); if (configGroup.readEntry(QStringLiteral("Show hidden files"), DefaultShowHidden)) { d->actionCollection->action(QStringLiteral("show hidden"))->setChecked(true); d->dirLister->setShowingDotFiles(true); } QDir::SortFlags sorting = QDir::Name; if (configGroup.readEntry(QStringLiteral("Sort directories first"), DefaultDirsFirst)) { sorting |= QDir::DirsFirst; } QString name = QStringLiteral("Name"); QString sortBy = configGroup.readEntry(QStringLiteral("Sort by"), name); if (sortBy == name) { sorting |= QDir::Name; } else if (sortBy == QLatin1String("Size")) { sorting |= QDir::Size; } else if (sortBy == QLatin1String("Date")) { sorting |= QDir::Time; } else if (sortBy == QLatin1String("Type")) { sorting |= QDir::Type; } if (configGroup.readEntry(QStringLiteral("Sort reversed"), DefaultSortReversed)) { sorting |= QDir::Reversed; } d->updateSorting(sorting); if (d->inlinePreviewState == Private::NotForced) { d->showPreviews = configGroup.readEntry(QStringLiteral("Show Inline Previews"), true); } QStyleOptionViewItem::Position pos = (QStyleOptionViewItem::Position) configGroup.readEntry(QStringLiteral("Decoration position"), (int) QStyleOptionViewItem::Left); setDecorationPosition(pos); } void KDirOperator::writeConfig(KConfigGroup &configGroup) { QString sortBy = QStringLiteral("Name"); if (KFile::isSortBySize(d->sorting)) { sortBy = QStringLiteral("Size"); } else if (KFile::isSortByDate(d->sorting)) { sortBy = QStringLiteral("Date"); } else if (KFile::isSortByType(d->sorting)) { sortBy = QStringLiteral("Type"); } configGroup.writeEntry(QStringLiteral("Sort by"), sortBy); configGroup.writeEntry(QStringLiteral("Sort reversed"), d->actionCollection->action(QStringLiteral("descending"))->isChecked()); configGroup.writeEntry(QStringLiteral("Sort directories first"), d->actionCollection->action(QStringLiteral("dirs first"))->isChecked()); // don't save the preview when an application specific preview is in use. bool appSpecificPreview = false; if (d->preview) { KFileMetaPreview *tmp = dynamic_cast(d->preview); appSpecificPreview = (tmp == nullptr); } if (!appSpecificPreview) { KToggleAction *previewAction = static_cast(d->actionCollection->action(QStringLiteral("preview"))); if (previewAction->isEnabled()) { bool hasPreview = previewAction->isChecked(); configGroup.writeEntry(QStringLiteral("Show Preview"), hasPreview); if (hasPreview) { // remember the width of the preview widget QList sizes = d->splitter->sizes(); Q_ASSERT(sizes.count() == 2); configGroup.writeEntry(QStringLiteral("Preview Width"), sizes[1]); } } } configGroup.writeEntry(QStringLiteral("Show hidden files"), d->actionCollection->action(QStringLiteral("show hidden"))->isChecked()); KFile::FileView fv = static_cast(d->viewKind); QString style; if (KFile::isDetailView(fv)) { style = QStringLiteral("Detail"); } else if (KFile::isSimpleView(fv)) { style = QStringLiteral("Simple"); } else if (KFile::isTreeView(fv)) { style = QStringLiteral("Tree"); } else if (KFile::isDetailTreeView(fv)) { style = QStringLiteral("DetailTree"); } configGroup.writeEntry(QStringLiteral("View Style"), style); if (d->inlinePreviewState == Private::NotForced) { configGroup.writeEntry(QStringLiteral("Show Inline Previews"), d->showPreviews); if (qobject_cast(d->itemView)) { configGroup.writeEntry(QStringLiteral("listViewIconSize"), d->iconsZoom); } else { configGroup.writeEntry(QStringLiteral("detailedViewIconSize"), d->iconsZoom); } } configGroup.writeEntry(QStringLiteral("Decoration position"), (int) d->decorationPosition); } void KDirOperator::resizeEvent(QResizeEvent *) { // resize the splitter and assure that the width of // the preview widget is restored QList sizes = d->splitter->sizes(); const bool hasPreview = (sizes.count() == 2); d->splitter->resize(size()); sizes = d->splitter->sizes(); const bool restorePreviewWidth = hasPreview && (d->previewWidth != sizes[1]); if (restorePreviewWidth) { const int availableWidth = sizes[0] + sizes[1]; sizes[0] = availableWidth - d->previewWidth; sizes[1] = d->previewWidth; d->splitter->setSizes(sizes); } if (hasPreview) { d->previewWidth = sizes[1]; } if (d->progressBar->parent() == this) { // might be reparented into a statusbar d->progressBar->move(2, height() - d->progressBar->height() - 2); } d->updateListViewGrid(); } void KDirOperator::setOnlyDoubleClickSelectsFiles(bool enable) { d->onlyDoubleClickSelectsFiles = enable; // TODO: port to QAbstractItemModel //if (d->itemView != 0) { // d->itemView->setOnlyDoubleClickSelectsFiles(enable); //} } bool KDirOperator::onlyDoubleClickSelectsFiles() const { return d->onlyDoubleClickSelectsFiles; } void KDirOperator::Private::_k_slotStarted() { progressBar->setValue(0); // delay showing the progressbar for one second progressDelayTimer->setSingleShot(true); progressDelayTimer->start(1000); } void KDirOperator::Private::_k_slotShowProgress() { progressBar->raise(); progressBar->show(); QApplication::flush(); } void KDirOperator::Private::_k_slotProgress(int percent) { progressBar->setValue(percent); // we have to redraw this as fast as possible if (progressBar->isVisible()) { QApplication::flush(); } } void KDirOperator::Private::_k_slotIOFinished() { progressDelayTimer->stop(); _k_slotProgress(100); progressBar->hide(); emit parent->finishedLoading(); parent->resetCursor(); if (preview) { preview->clearPreview(); } } void KDirOperator::Private::_k_slotCanceled() { emit parent->finishedLoading(); parent->resetCursor(); } QProgressBar *KDirOperator::progressBar() const { return d->progressBar; } void KDirOperator::clearHistory() { qDeleteAll(d->backStack); d->backStack.clear(); d->actionCollection->action(QStringLiteral("back"))->setEnabled(false); qDeleteAll(d->forwardStack); d->forwardStack.clear(); d->actionCollection->action(QStringLiteral("forward"))->setEnabled(false); } void KDirOperator::setEnableDirHighlighting(bool enable) { d->dirHighlighting = enable; } bool KDirOperator::dirHighlighting() const { return d->dirHighlighting; } bool KDirOperator::dirOnlyMode() const { return dirOnlyMode(d->mode); } bool KDirOperator::dirOnlyMode(uint mode) { return ((mode & KFile::Directory) && (mode & (KFile::File | KFile::Files)) == 0); } void KDirOperator::Private::_k_slotProperties() { if (itemView == nullptr) { return; } const KFileItemList list = parent->selectedItems(); if (!list.isEmpty()) { KPropertiesDialog dialog(list, parent); dialog.exec(); } } void KDirOperator::Private::_k_slotActivated(const QModelIndex &index) { const QModelIndex dirIndex = proxyModel->mapToSource(index); KFileItem item = dirModel->itemForIndex(dirIndex); const Qt::KeyboardModifiers modifiers = QApplication::keyboardModifiers(); if (item.isNull() || (modifiers & Qt::ShiftModifier) || (modifiers & Qt::ControlModifier)) { return; } if (item.isDir()) { parent->selectDir(item); } else { parent->selectFile(item); } } void KDirOperator::Private::_k_slotSelectionChanged() { if (itemView == nullptr) { return; } // In the multiselection mode each selection change is indicated by // emitting a null item. Also when the selection has been cleared, a // null item must be emitted. const bool multiSelectionMode = (itemView->selectionMode() == QAbstractItemView::ExtendedSelection); const bool hasSelection = itemView->selectionModel()->hasSelection(); if (multiSelectionMode || !hasSelection) { KFileItem nullItem; parent->highlightFile(nullItem); } else { const KFileItem selectedItem = parent->selectedItems().constFirst(); parent->highlightFile(selectedItem); } } void KDirOperator::Private::_k_openContextMenu(const QPoint &pos) { const QModelIndex proxyIndex = itemView->indexAt(pos); const QModelIndex dirIndex = proxyModel->mapToSource(proxyIndex); KFileItem item = dirModel->itemForIndex(dirIndex); if (item.isNull()) { return; } parent->activatedMenu(item, QCursor::pos()); } void KDirOperator::Private::_k_triggerPreview(const QModelIndex &index) { if ((preview != nullptr && !preview->isHidden()) && index.isValid() && (index.column() == KDirModel::Name)) { const QModelIndex dirIndex = proxyModel->mapToSource(index); const KFileItem item = dirModel->itemForIndex(dirIndex); if (item.isNull()) { return; } if (!item.isDir()) { previewUrl = item.url(); _k_showPreview(); } else { preview->clearPreview(); } } } void KDirOperator::Private::_k_showPreview() { if (preview != nullptr) { preview->showPreview(previewUrl); } } void KDirOperator::Private::_k_slotSplitterMoved(int, int) { const QList sizes = splitter->sizes(); if (sizes.count() == 2) { // remember the width of the preview widget (see KDirOperator::resizeEvent()) previewWidth = sizes[1]; } } void KDirOperator::Private::_k_assureVisibleSelection() { if (itemView == nullptr) { return; } QItemSelectionModel *selModel = itemView->selectionModel(); if (selModel->hasSelection()) { const QModelIndex index = selModel->currentIndex(); itemView->scrollTo(index, QAbstractItemView::EnsureVisible); _k_triggerPreview(index); } } void KDirOperator::Private::_k_synchronizeSortingState(int logicalIndex, Qt::SortOrder order) { QDir::SortFlags newSort = sorting & ~(QDirSortMask | QDir::Reversed); switch (logicalIndex) { case KDirModel::Name: newSort |= QDir::Name; break; case KDirModel::Size: newSort |= QDir::Size; break; case KDirModel::ModifiedTime: newSort |= QDir::Time; break; case KDirModel::Type: newSort |= QDir::Type; break; default: Q_ASSERT(false); } if (order == Qt::DescendingOrder) { newSort |= QDir::Reversed; } updateSorting(newSort); QMetaObject::invokeMethod(parent, "_k_assureVisibleSelection", Qt::QueuedConnection); } void KDirOperator::Private::_k_slotChangeDecorationPosition() { if (!itemView) { return; } QListView *view = qobject_cast(itemView); if (!view) { return; } const bool leftChecked = actionCollection->action(QStringLiteral("decorationAtLeft"))->isChecked(); if (leftChecked) { decorationPosition = QStyleOptionViewItem::Left; view->setFlow(QListView::TopToBottom); } else { decorationPosition = QStyleOptionViewItem::Top; view->setFlow(QListView::LeftToRight); } updateListViewGrid(); itemView->update(); } void KDirOperator::Private::_k_slotExpandToUrl(const QModelIndex &index) { QTreeView *treeView = qobject_cast(itemView); if (!treeView) { return; } const KFileItem item = dirModel->itemForIndex(index); if (item.isNull()) { return; } if (!item.isDir()) { const QModelIndex proxyIndex = proxyModel->mapFromSource(index); QList::Iterator it = itemsToBeSetAsCurrent.begin(); while (it != itemsToBeSetAsCurrent.end()) { const QUrl url = *it; if (url.matches(item.url(), QUrl::StripTrailingSlash) || url.isParentOf(item.url())) { const KFileItem _item = dirLister->findByUrl(url); if (!_item.isNull() && _item.isDir()) { const QModelIndex _index = dirModel->indexForItem(_item); const QModelIndex _proxyIndex = proxyModel->mapFromSource(_index); treeView->expand(_proxyIndex); // if we have expanded the last parent of this item, select it if (item.url().adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash) == url.adjusted(QUrl::StripTrailingSlash)) { treeView->selectionModel()->select(proxyIndex, QItemSelectionModel::Select); } } it = itemsToBeSetAsCurrent.erase(it); } else { ++it; } } } else if (!itemsToBeSetAsCurrent.contains(item.url())) { itemsToBeSetAsCurrent << item.url(); } } void KDirOperator::Private::_k_slotItemsChanged() { completeListDirty = true; } void KDirOperator::Private::updateListViewGrid() { if (!itemView) { return; } QListView *view = qobject_cast(itemView); if (!view) { return; } const bool leftChecked = actionCollection->action(QStringLiteral("decorationAtLeft"))->isChecked(); if (leftChecked) { view->setGridSize(QSize()); KFileItemDelegate *delegate = qobject_cast(view->itemDelegate()); if (delegate) { delegate->setMaximumSize(QSize()); } } else { const QFontMetrics metrics(itemView->viewport()->font()); const int height = itemView->iconSize().height() + metrics.height() * 2.5; const int minWidth = qMax(height, metrics.height() * 5); const int scrollBarWidth = itemView->verticalScrollBar()->sizeHint().width(); // Subtract 1 px to prevent flickering when resizing the window // For Oxygen a column is missing after showing the dialog without resizing it, // therefore subtract 4 more (scaled) pixels const int viewPortWidth = itemView->contentsRect().width() - scrollBarWidth - 1 - 4 * itemView->devicePixelRatioF(); const int itemsInRow = qMax(1, viewPortWidth / minWidth); const int remainingWidth = viewPortWidth - (minWidth * itemsInRow); const int width = minWidth + (remainingWidth / itemsInRow); const QSize itemSize(width, height); view->setGridSize(itemSize); KFileItemDelegate *delegate = qobject_cast(view->itemDelegate()); if (delegate) { delegate->setMaximumSize(itemSize); } } } int KDirOperator::Private::iconSizeForViewType(QAbstractItemView *itemView) const { if (!itemView || !configGroup) { return 0; } if (qobject_cast(itemView)) { return configGroup->readEntry("listViewIconSize", 0); } else { return configGroup->readEntry("detailedViewIconSize", 0); } } void KDirOperator::setViewConfig(KConfigGroup &configGroup) { delete d->configGroup; d->configGroup = new KConfigGroup(configGroup); } KConfigGroup *KDirOperator::viewConfigGroup() const { return d->configGroup; } void KDirOperator::setShowHiddenFiles(bool s) { d->actionCollection->action(QStringLiteral("show hidden"))->setChecked(s); } bool KDirOperator::showHiddenFiles() const { return d->actionCollection->action(QStringLiteral("show hidden"))->isChecked(); } QStyleOptionViewItem::Position KDirOperator::decorationPosition() const { return d->decorationPosition; } void KDirOperator::setDecorationPosition(QStyleOptionViewItem::Position position) { d->decorationPosition = position; const bool decorationAtLeft = d->decorationPosition == QStyleOptionViewItem::Left; d->actionCollection->action(QStringLiteral("decorationAtLeft"))->setChecked(decorationAtLeft); d->actionCollection->action(QStringLiteral("decorationAtTop"))->setChecked(!decorationAtLeft); } bool KDirOperator::Private::isReadable(const QUrl &url) { if (!url.isLocalFile()) { return true; // what else can we say? } return QDir(url.toLocalFile()).isReadable(); } void KDirOperator::Private::_k_slotDirectoryCreated(const QUrl &url) { parent->setUrl(url, true); } void KDirOperator::setSupportedSchemes(const QStringList &schemes) { d->supportedSchemes = schemes; rereadDir(); } QStringList KDirOperator::supportedSchemes() const { return d->supportedSchemes; } #include "moc_kdiroperator.cpp" #include "kdiroperator.moc" diff --git a/src/filewidgets/kdiroperator.h b/src/filewidgets/kdiroperator.h index 9b045c83..0528ef26 100644 --- a/src/filewidgets/kdiroperator.h +++ b/src/filewidgets/kdiroperator.h @@ -1,955 +1,961 @@ // -*- c++ -*- /* This file is part of the KDE libraries Copyright (C) 1999 Stephan Kulow 2000,2001 Carsten Pfeiffer This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDIROPERATOR_H_ #define KDIROPERATOR_H_ #include #include #include #include #include #include #include #include #include "kiofilewidgets_export.h" class QAbstractItemView; class QModelIndex; class QProgressBar; class KActionCollection; class KActionMenu; class KConfigGroup; class KDirLister; class KPreviewWidgetBase; class KFilePreviewGenerator; namespace KIO { class CopyJob; class DeleteJob; } /** * @class KDirOperator kdiroperator.h * * This widget works as a network transparent filebrowser. You specify a URL * to display and this url will be loaded via KDirLister. The user can * browse through directories, highlight and select files, delete or rename * files. * * It supports different views, e.g. a detailed view (see KFileDetailView), * a simple icon view (see KFileIconView), a combination of two views, * separating directories and files ( KCombiView). * * Additionally, a preview view is available (see KFilePreview), which can * show either a simple or detailed view and additionally a preview widget * (see setPreviewWidget()). KImageFilePreview is one implementation * of a preview widget, that displays previews for all supported filetypes * utilizing KIO::PreviewJob. * * Currently, those classes don't support Drag&Drop out of the box -- there * you have to use your own view-classes. You can use some DnD-aware views * from Björn Sahlström until they will be integrated * into this library. See http://devel-home.kde.org/~pfeiffer/DnD-classes.tar.gz * * This widget is the one used in the KFileWidget. * * Basic usage is like this: * \code * KDirOperator *op = new KDirOperator(QUrl("file:///home/gis"), this); * // some signals you might be interested in * connect(op, SIGNAL(urlEntered(const QUrl&)), * SLOT(urlEntered(const QUrl&))); * connect(op, SIGNAL(fileHighlighted(const KFileItem &)), * SLOT(fileHighlighted(const KFileItem &))); * connect(op, SIGNAL(fileSelected(const KFileItem &)), * SLOT(fileSelected(const KFileItem &))); * connect(op, SIGNAL(finishedLoading()), * SLOT(slotLoadingFinished())); * * KConfigGroup grp(KSharedConfig::openConfig(),"Your KDiroperator ConfigGroup" ); * op->readConfig( &grp); * op->setView(KFile::Default); * \endcode * * This will create a childwidget of 'this' showing the directory contents * of /home/gis in the default-view. The view is determined by the readConfig() * call, which will read the KDirOperator settings, the user left your program * with (and which you saved with op->writeConfig()). * * @short A widget for displaying files and browsing directories. * @author Stephan Kulow , Carsten Pfeiffer */ class KIOFILEWIDGETS_EXPORT KDirOperator : public QWidget { Q_OBJECT public: /** * The various action types. These values can be or'd together */ enum ActionType { SortActions = 1, ViewActions = 2, NavActions = 4, FileActions = 8, AllActions = 15 }; /** * Constructs the KDirOperator with no initial view. As the views are * configurable, call readConfig() to load the user's configuration * and then setView to explicitly set a view. * * This constructor doesn't start loading the url, setView will do it. */ explicit KDirOperator(const QUrl &urlName = QUrl(), QWidget *parent = nullptr); /** * Destroys the KDirOperator. */ virtual ~KDirOperator(); /** * Enables/disables showing hidden files. */ virtual void setShowHiddenFiles(bool s); /** * @returns true when hidden files are shown or false otherwise. */ bool showHiddenFiles() const; /** * Stops loading immediately. You don't need to call this, usually. */ void close(); /** * Sets a filter like "*.cpp *.h *.o". Only files matching that filter * will be shown. * * @see KDirLister::setNameFilter * @see nameFilter */ void setNameFilter(const QString &filter); /** * @returns the current namefilter. * @see setNameFilter */ QString nameFilter() const; /** * Sets a list of mimetypes as filter. Only files of those mimetypes * will be shown. * * Example: * \code * QStringList filter; * filter << "text/html" << "image/png" << "inode/directory"; * dirOperator->setMimefilter( filter ); * \endcode * * Node: Without the mimetype inode/directory, only files would be shown. * Call updateDir() to apply it. * * @see KDirLister::setMimeFilter * @see mimeFilter */ void setMimeFilter(const QStringList &mimetypes); /** * @returns the current mime filter. */ QStringList mimeFilter() const; /** * Only show the files in a given set of mimetypes. * This is useful in specialized applications (while file managers, on * the other hand, want to show all mimetypes). Internally uses * KNewFileMenu::setSupportedMimeTypes * * Example: * \code * QStringList mimeTypes; * mimeTypes << "text/html" << "inode/directory"; * dirOperator->setNewFileMenuSupportedMimeTypes(mimeTypes); * \endcode * * Note: If the list is empty, all options will be shown. Otherwise, * without the mimetype inode/directory, only file options will be shown. * * @see KNewFileMenu::setSupportedMimeTypes * @see newFileMenuSupportedMimeTypes * @since 4.5 */ void setNewFileMenuSupportedMimeTypes(const QStringList &mime); /** * @returns the current Supported Mimes Types. * @since 4.5 */ QStringList newFileMenuSupportedMimeTypes() const; /** * Clears both the namefilter and mimetype filter, so that all files and * directories will be shown. Call updateDir() to apply it. * * @see setMimeFilter * @see setNameFilter */ void clearFilter(); /** * @returns the current url */ QUrl url() const; /** * Sets a new url to list. * @param clearforward specifies whether the "forward" history should be cleared. * @param url the URL to set */ virtual void setUrl(const QUrl &url, bool clearforward); /** * Clears the current selection and attempts to set @p url * the current url file. */ void setCurrentItem(const QUrl &url); /** * Clears the current selection and attempts to set @p item * as the current item. */ void setCurrentItem(const KFileItem &item); /** * Clears the current selection and attempts to set @p urls * the current url files. * @since 4.2 */ void setCurrentItems(const QList &urls); /** * Clears the current selection and attempts to set @p items * as the current items. * @since 4.2 */ void setCurrentItems(const KFileItemList &items); /** * Sets a new view to be used for showing and browsing files. * Note: this will read the current url() to fill the view. * * @see KFileTreeView * @see view */ virtual void setView(QAbstractItemView *view); /** * @returns the currently used view. * @see setView */ QAbstractItemView *view() const; /** * Sets one of the predefined fileviews. * @see KFile::FileView */ virtual void setView(KFile::FileView viewKind); /** * Returns the current view mode. * @returns KFile::FileView * @see KFile::FileView * @since 5.0 */ KFile::FileView viewMode() const; /** * Sets the way to sort files and directories. */ void setSorting(QDir::SortFlags); /** * @returns the current way of sorting files and directories */ QDir::SortFlags sorting() const; /** * @returns true if we are displaying the root directory of the current url */ bool isRoot() const; /** * @returns the object listing the directory */ KDirLister *dirLister() const; /** * @returns the progress widget, that is shown during directory listing. * You can for example reparent() it to put it into a statusbar. */ QProgressBar *progressBar() const; /** * Sets the listing/selection mode for the views, an OR'ed combination of * @li File * @li Directory * @li Files * @li ExistingOnly * @li LocalOnly * * You cannot mix File and Files of course, as the former means * single-selection mode, the latter multi-selection. */ virtual void setMode(KFile::Modes m); /** * @returns the listing/selection mode. */ KFile::Modes mode() const; /** * Sets a preview-widget to be shown next to the file-view. * The ownership of @p w is transferred to KDirOperator, so don't * delete it yourself! */ virtual void setPreviewWidget(KPreviewWidgetBase *w); /** * @returns a list of all currently selected items. If there is no view, * or there are no selected items, an empty list is returned. */ KFileItemList selectedItems() const; /** * @returns true if @p item is currently selected, or false otherwise. */ bool isSelected(const KFileItem &item) const; /** * @returns the number of directories in the currently listed url. * Returns 0 if there is no view. */ int numDirs() const; /** * @returns the number of files in the currently listed url. * Returns 0 if there is no view. */ int numFiles() const; /** * @returns a KCompletion object, containing all filenames and * directories of the current directory/URL. * You can use it to insert it into a KLineEdit or KComboBox * Note: it will only contain files, after prepareCompletionObjects() * has been called. It will be implicitly called from makeCompletion() * or makeDirCompletion() */ KCompletion *completionObject() const; /** * @returns a KCompletion object, containing only all directories of the * current directory/URL. * You can use it to insert it into a KLineEdit or KComboBox * Note: it will only contain directories, after * prepareCompletionObjects() has been called. It will be implicitly * called from makeCompletion() or makeDirCompletion() */ KCompletion *dirCompletionObject() const; /** * an accessor to a collection of all available Actions. The actions * are static, they will be there all the time (no need to connect to * the signals KActionCollection::inserted() or removed(). * * There are the following actions: * * @li popupMenu : an ActionMenu presenting a popupmenu with all actions * @li up : changes to the parent directory * @li back : goes back to the previous directory * @li forward : goes forward in the history * @li home : changes to the user's home directory * @li reload : reloads the current directory * @li mkdir : opens a dialog box to create a directory * @li delete : deletes the selected files/directories * @li sorting menu : an ActionMenu containing all sort-options * @li by name : sorts by name * @li by size : sorts by size * @li by date : sorts by date * @li by type : sorts by type * @li descending : reverses the sort order * @li view menu : an ActionMenu containing all actions concerning the view * @li short view : shows a simple fileview * @li detailed view : shows a detailed fileview (dates, permissions ,...) * @li show hidden : shows hidden files * @li preview : shows a preview next to the fileview * @li properties : shows a KPropertiesDialog for the selected files * * The short and detailed view are in an exclusive group. The sort-by * actions are in an exclusive group as well. Also the "separate dirs", * "preview" and "single" actions are in an exclusive group. * * You can e.g. use * \code * actionCollection()->action( "up" )->plug( someToolBar ); * \endcode * to add a button into a toolbar, which makes the dirOperator change to * its parent directory. * * @returns all available Actions */ KActionCollection *actionCollection() const; /** * Sets the config object and the to be used group in KDirOperator. This * will be used to store the view's configuration. * If you don't set this, the views cannot save and restore their * configuration. * * Usually you call this right after KDirOperator creation so that the view * instantiation can make use of it already. * * Note that KDirOperator does NOT take ownership of that object (typically * it's KSharedConfig::openConfig() anyway. * * You must not delete the KConfig or KConfigGroup object (and master config object) before * either deleting the KDirOperator or calling setViewConfig(0); or something like that * * @see viewConfig * @see viewConfigGroup */ virtual void setViewConfig(KConfigGroup &configGroup); /** * @returns the group set by setViewConfig configuration. */ KConfigGroup *viewConfigGroup() const; /** * Reads the default settings for a view, i.e. the default KFile::FileView. * Also reads the sorting and whether hidden files should be shown. * Note: the default view will not be set - you have to call * \code * setView( KFile::Default ) * \endcode * to apply it. * * @see setView * @see setViewConfig * @see writeConfig */ virtual void readConfig(const KConfigGroup &configGroup); /** * Saves the current settings like sorting, simple or detailed view. * * @see readConfig * @see setViewConfig */ virtual void writeConfig(KConfigGroup &configGroup); /** * This toggles between double/single click file and directory selection mode. * When argument is true, files and directories are highlighted with single click and * selected (executed) with double click. * * NOTE: this currently has no effect. * * The default follows the single/double click system setting. */ void setOnlyDoubleClickSelectsFiles(bool enable); /** * @returns whether files (not directories) should only be select()ed by * double-clicks. * @see setOnlyDoubleClickSelectsFiles */ bool onlyDoubleClickSelectsFiles() const; /** * Creates the given directory/url. If it is a relative path, * it will be completed with the current directory. * If enterDirectory is true, the directory will be entered after a * successful operation. If unsuccessful, a messagebox will be presented * to the user. * @returns true if the directory could be created. */ virtual bool mkdir(const QString &directory, bool enterDirectory = true); /** * Starts and returns a KIO::DeleteJob to delete the given @p items. * * @param items the list of items to be deleted * @param parent the parent widget used for the confirmation dialog * @param ask specifies whether a confirmation dialog should be shown * @param showProgress passed to the DeleteJob to show a progress dialog */ virtual KIO::DeleteJob *del(const KFileItemList &items, QWidget *parent = nullptr, bool ask = true, bool showProgress = true); /** * Clears the forward and backward history. */ void clearHistory(); /** * When going up in the directory hierarchy, KDirOperator can highlight * the directory that was just left. * * I.e. when you go from /home/gis/src to /home/gis, the item "src" will * be made the current item. * * Default is off. */ virtual void setEnableDirHighlighting(bool enable); /** * @returns whether the last directory will be made the current item * when going up in the directory hierarchy. * * Default is false. */ bool dirHighlighting() const; /** * @returns true if we are in directory-only mode, that is, no files are * shown. */ bool dirOnlyMode() const; static bool dirOnlyMode(uint mode); /** * Sets up the action menu. * @param whichActions is an value of OR'd ActionTypes that controls which actions to show in the action menu */ void setupMenu(int whichActions); /** * Reimplemented - allow dropping of files if @p b is true * @param b true if the widget should allow dropping of files */ virtual void setAcceptDrops(bool b); /** * Sets the options for dropping files. * CURRENTLY NOT IMPLEMENTED */ virtual void setDropOptions(int options); /** * Starts and returns a KIO::CopyJob to trash the given @p items. * * @param items the list of items to be trashed * @param parent the parent widget used for the confirmation dialog * @param ask specifies whether a confirmation dialog should be shown * @param showProgress passed to the CopyJob to show a progress dialog */ virtual KIO::CopyJob *trash(const KFileItemList &items, QWidget *parent, bool ask = true, bool showProgress = true); /** * Returns the preview generator for the current view. * @since 4.2 */ KFilePreviewGenerator *previewGenerator() const; /** * Forces the inline previews to be shown or hidden, depending on @p show. * * @param show Whether to show inline previews or not. * @since 4.2 */ void setInlinePreviewShown(bool show); /** * Returns the position where icons are shown relative to the labels * of file items in the icon view. * @since 4.2.3 */ QStyleOptionViewItem::Position decorationPosition() const; /** * Sets the position where icons shall be shown relative to the labels * of file items in the icon view. * @since 4.2.3 */ void setDecorationPosition(QStyleOptionViewItem::Position position); /** * Returns whether the inline previews are shown or not. * @since 4.2 */ bool isInlinePreviewShown() const; /** * Returns the icon zoom. * @since 4.2 */ int iconsZoom() const; /** * If the system is set up to trigger items on single click, if @p isSaving * is true, we will force to double click to accept. * @note this is false by default * @since 4.2 */ void setIsSaving(bool isSaving); /** * Returns whether KDirOperator will force a double click to accept. * @note this is false by default * @since 4.2 */ bool isSaving() const; /** * Returns the URL schemes that the file widget should allow navigating to. * * If the returned list is empty, all schemes are supported. * * @sa QFileDialog::supportedSchemes * @since 5.43 */ QStringList supportedSchemes() const; protected: /** * A view factory for creating predefined fileviews. Called internally by setView, * but you can also call it directly. Reimplement this if you depend on self defined fileviews. * @param parent is the QWidget to be set as parent * @param viewKind is the predefined view to be set, note: this can be several ones OR:ed together * @returns the created view * @see KFile::FileView * @see setView */ virtual QAbstractItemView *createView(QWidget *parent, KFile::FileView viewKind); /** * Sets a custom KDirLister to list directories. * The KDirOperator takes ownership of the given KDirLister. */ virtual void setDirLister(KDirLister *lister); void resizeEvent(QResizeEvent *event) override; void keyPressEvent(QKeyEvent *event) override; /** * Sets up all the actions. Called from the constructor, you usually * better not call this. */ void setupActions(); /** * Updates the sorting-related actions to comply with the current sorting * @see sorting */ void updateSortActions(); /** * Updates the view-related actions to comply with the current * KFile::FileView */ void updateViewActions(); /** * Sets up the context-menu with all the necessary actions. Called from the * constructor, you usually don't need to call this. */ void setupMenu(); /** * Synchronizes the completion objects with the entries of the * currently listed url. * * Automatically called from makeCompletion() and * makeDirCompletion() */ void prepareCompletionObjects(); /** * Checks if there support from KIO::PreviewJob for the currently * shown files, taking mimeFilter() and nameFilter() into account * Enables/disables the preview-action accordingly. */ bool checkPreviewSupport(); /** * Called upon right-click to activate the popupmenu. */ virtual void activatedMenu(const KFileItem &item, const QPoint &pos); void changeEvent(QEvent *event) override; bool eventFilter(QObject *watched, QEvent *event) override; public Q_SLOTS: /** * Goes one step back in the history and opens that url. */ virtual void back(); /** * Goes one step forward in the history and opens that url. */ virtual void forward(); /** * Enters the home directory. */ virtual void home(); /** * Goes one directory up from the current url. */ virtual void cdUp(); /** * to update the view after changing the settings */ void updateDir(); /** * Re-reads the current url. */ virtual void rereadDir(); /** * Opens a dialog to create a new directory. */ virtual void mkdir(); /** * Deletes the currently selected files/directories. */ virtual void deleteSelected(); /** * Enables/disables actions that are selection dependent. Call this e.g. * when you are about to show a popup menu using some of KDirOperators * actions. */ void updateSelectionDependentActions(); /** * Tries to complete the given string (only completes files). */ QString makeCompletion(const QString &); /** * Tries to complete the given string (only completes directories). */ QString makeDirCompletion(const QString &); /** * Trashes the currently selected files/directories. * * This function used to take activation reason and keyboard modifiers, * in order to call deleteSelected() if the user wanted to delete. * Instead, call deleteSelected(). * * FIXME KAction Port: link deleteSelected() up correctly */ virtual void trashSelected(); /** * Notifies that the icons size should change. @p value is an int ranged from 0 to 100. * 100 means KIconLoader::SizeEnormous. * @since 4.2 */ void setIconsZoom(int value); /** * Set the URL schemes that the file widget should allow navigating to. * * If the returned list is empty, all schemes are supported. Examples for * schemes are @c "file" or @c "ftp". * * @sa QFileDialog::setSupportedSchemes * @since 5.43 */ void setSupportedSchemes(const QStringList &schemes); protected Q_SLOTS: /** * Restores the normal cursor after showing the busy-cursor. Also hides * the progressbar. */ void resetCursor(); /** * Called after setUrl() to load the directory, update the history, * etc. */ void pathChanged(); /** * Enters the directory specified by the given @p item. */ virtual void selectDir(const KFileItem &item); /** * Emits fileSelected( item ) */ void selectFile(const KFileItem &item); /** * Emits fileHighlighted(item) */ void highlightFile(const KFileItem &item); /** * Changes sorting to sort by name */ void sortByName(); /** * Changes sorting to sort by size */ void sortBySize(); /** * Changes sorting to sort by date */ void sortByDate(); /** * Changes sorting to sort by date */ void sortByType(); /** * Changes sorting to reverse sorting */ void sortReversed(); /** * Toggles showing directories first / having them sorted like files. */ void toggleDirsFirst(); /** * Toggles case sensitive / case insensitive sorting */ void toggleIgnoreCase(); /** * Tries to make the given @p match as current item in the view and emits * completion( match ) */ void slotCompletionMatch(const QString &match); Q_SIGNALS: void urlEntered(const QUrl &); void updateInformation(int files, int dirs); void completion(const QString &); void finishedLoading(); /** * Emitted whenever the current fileview is changed, either by an explicit * call to setView() or by the user selecting a different view thru * the GUI. */ void viewChanged(QAbstractItemView *newView); /** * Emitted when a file is highlighted or generally the selection changes in * multiselection mode. In the latter case, @p item is a null KFileItem. * You can access the selected items with selectedItems(). */ void fileHighlighted(const KFileItem &item); void dirActivated(const KFileItem &item); void fileSelected(const KFileItem &item); /** * Emitted when files are dropped. Dropping files is disabled by * default. You need to enable it with setAcceptDrops() * @param item the item on which the drop occurred or 0. * @param event the drop event itself. * @param urls the urls that where dropped. */ void dropped(const KFileItem &item, QDropEvent *event, const QList &urls); /** * Emitted just before the context menu is shown, allows users to * extend the menu with custom actions. * * @param item the file on which the context menu was invoked * @param menu the context menu, pre-populated with the file-management actions * @since 4.2 */ void contextMenuAboutToShow(const KFileItem &item, QMenu *menu); /** * Will notify that the icon size has changed. Since we save the icon size depending * on the view type (list view or a different kind of view), a call to setView() can * trigger this signal to be emitted. * @since 4.2 */ void currentIconSizeChanged(int size); + /** + * Triggered when the user hit Enter/Return + * @since 5.57 + */ + void keyEnterReturnPressed(); + private: class Private; Private *const d; Q_PRIVATE_SLOT(d, void _k_slotDetailedView()) Q_PRIVATE_SLOT(d, void _k_slotSimpleView()) Q_PRIVATE_SLOT(d, void _k_slotTreeView()) Q_PRIVATE_SLOT(d, void _k_slotDetailedTreeView()) Q_PRIVATE_SLOT(d, void _k_slotToggleHidden(bool)) Q_PRIVATE_SLOT(d, void _k_togglePreview(bool)) Q_PRIVATE_SLOT(d, void _k_toggleInlinePreviews(bool)) Q_PRIVATE_SLOT(d, void _k_slotOpenFileManager()) Q_PRIVATE_SLOT(d, void _k_slotSortByName()) Q_PRIVATE_SLOT(d, void _k_slotSortBySize()) Q_PRIVATE_SLOT(d, void _k_slotSortByDate()) Q_PRIVATE_SLOT(d, void _k_slotSortByType()) Q_PRIVATE_SLOT(d, void _k_slotSortReversed(bool)) Q_PRIVATE_SLOT(d, void _k_slotToggleDirsFirst()) Q_PRIVATE_SLOT(d, void _k_slotToggleIgnoreCase()) Q_PRIVATE_SLOT(d, void _k_slotStarted()) Q_PRIVATE_SLOT(d, void _k_slotProgress(int)) Q_PRIVATE_SLOT(d, void _k_slotShowProgress()) Q_PRIVATE_SLOT(d, void _k_slotIOFinished()) Q_PRIVATE_SLOT(d, void _k_slotCanceled()) Q_PRIVATE_SLOT(d, void _k_slotRedirected(const QUrl &)) Q_PRIVATE_SLOT(d, void _k_slotProperties()) Q_PRIVATE_SLOT(d, void _k_slotActivated(const QModelIndex &)) Q_PRIVATE_SLOT(d, void _k_slotSelectionChanged()) Q_PRIVATE_SLOT(d, void _k_openContextMenu(const QPoint &)) Q_PRIVATE_SLOT(d, void _k_triggerPreview(const QModelIndex &)) Q_PRIVATE_SLOT(d, void _k_showPreview()) Q_PRIVATE_SLOT(d, void _k_slotSplitterMoved(int, int)) Q_PRIVATE_SLOT(d, void _k_assureVisibleSelection()) Q_PRIVATE_SLOT(d, void _k_synchronizeSortingState(int, Qt::SortOrder)) Q_PRIVATE_SLOT(d, void _k_slotChangeDecorationPosition()) Q_PRIVATE_SLOT(d, void _k_slotExpandToUrl(const QModelIndex &)) Q_PRIVATE_SLOT(d, void _k_slotItemsChanged()) Q_PRIVATE_SLOT(d, void _k_slotDirectoryCreated(const QUrl &)) }; #endif diff --git a/src/filewidgets/kfilewidget.cpp b/src/filewidgets/kfilewidget.cpp index c08c0f54..2b5c9fd9 100644 --- a/src/filewidgets/kfilewidget.cpp +++ b/src/filewidgets/kfilewidget.cpp @@ -1,2911 +1,2923 @@ // -*- c++ -*- /* This file is part of the KDE libraries Copyright (C) 1997, 1998 Richard Moore 1998 Stephan Kulow 1998 Daniel Grana 1999,2000,2001,2002,2003 Carsten Pfeiffer 2003 Clarence Dang 2007 David Faure 2008 Rafael Fernández López This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kfilewidget.h" #include "../pathhelpers_p.h" #include "kfileplacesview.h" #include "kfileplacesmodel.h" #include "kfilebookmarkhandler_p.h" #include "kurlcombobox.h" #include "kurlnavigator.h" #include "kfilepreviewgenerator.h" #include "kfilewidgetdocktitlebar_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class KFileWidgetPrivate { public: explicit KFileWidgetPrivate(KFileWidget *widget) : q(widget), boxLayout(nullptr), placesDock(nullptr), placesView(nullptr), placesViewSplitter(nullptr), placesViewWidth(-1), labeledCustomWidget(nullptr), bottomCustomWidget(nullptr), autoSelectExtCheckBox(nullptr), operationMode(KFileWidget::Opening), bookmarkHandler(nullptr), toolbar(nullptr), locationEdit(nullptr), ops(nullptr), filterWidget(nullptr), autoSelectExtChecked(false), keepLocation(false), hasView(false), hasDefaultFilter(false), inAccept(false), dummyAdded(false), confirmOverwrite(false), differentHierarchyLevelItemsEntered(false), iconSizeSlider(nullptr), zoomOutAction(nullptr), zoomInAction(nullptr) { } ~KFileWidgetPrivate() { delete bookmarkHandler; // Should be deleted before ops! delete ops; } void updateLocationWhatsThis(); void updateAutoSelectExtension(); void initSpeedbar(); void setPlacesViewSplitterSizes(); void setLafBoxColumnWidth(); void initGUI(); void readViewConfig(); void writeViewConfig(); void setNonExtSelection(); void setLocationText(const QUrl &); void setLocationText(const QList &); void appendExtension(QUrl &url); void updateLocationEditExtension(const QString &); void updateFilter(); QList &parseSelectedUrls(); /** * Parses the string "line" for files. If line doesn't contain any ", the * whole line will be interpreted as one file. If the number of " is odd, * an empty list will be returned. Otherwise, all items enclosed in " " * will be returned as correct urls. */ QList tokenize(const QString &line) const; /** * Reads the recent used files and inserts them into the location combobox */ void readRecentFiles(); /** * Saves the entries from the location combobox. */ void saveRecentFiles(); /** * called when an item is highlighted/selected in multiselection mode. * handles setting the locationEdit. */ void multiSelectionChanged(); /** * Returns the absolute version of the URL specified in locationEdit. */ QUrl getCompleteUrl(const QString &) const; /** * Sets the dummy entry on the history combo box. If the dummy entry * already exists, it is overwritten with this information. */ void setDummyHistoryEntry(const QString &text, const QPixmap &icon = QPixmap(), bool usePreviousPixmapIfNull = true); /** * Removes the dummy entry of the history combo box. */ void removeDummyHistoryEntry(); /** * Asks for overwrite confirmation using a KMessageBox and returns * true if the user accepts. * * @since 4.2 */ bool toOverwrite(const QUrl &); // private slots void _k_slotLocationChanged(const QString &); void _k_urlEntered(const QUrl &); void _k_enterUrl(const QUrl &); void _k_enterUrl(const QString &); void _k_locationAccepted(const QString &); void _k_slotFilterChanged(); void _k_fileHighlighted(const KFileItem &); void _k_fileSelected(const KFileItem &); void _k_slotLoadingFinished(); void _k_fileCompletion(const QString &); void _k_toggleSpeedbar(bool); void _k_toggleBookmarks(bool); void _k_slotAutoSelectExtClicked(); void _k_placesViewSplitterMoved(int, int); void _k_activateUrlNavigator(); void _k_zoomOutIconsSize(); void _k_zoomInIconsSize(); void _k_slotIconSizeSliderMoved(int); void _k_slotIconSizeChanged(int); void _k_slotViewDoubleClicked(const QModelIndex&); + void _k_slotViewKeyEnterReturnPressed(); void addToRecentDocuments(); QString locationEditCurrentText() const; /** * KIO::NetAccess::mostLocalUrl local replacement. * This method won't show any progress dialogs for stating, since * they are very annoying when stating. */ QUrl mostLocalUrl(const QUrl &url); void setInlinePreviewShown(bool show); KFileWidget * const q; // the last selected url QUrl url; // the selected filenames in multiselection mode -- FIXME QString filenames; // now following all kind of widgets, that I need to rebuild // the geometry management QBoxLayout *boxLayout; QGridLayout *lafBox; QVBoxLayout *vbox; QLabel *locationLabel; QWidget *opsWidget; QWidget *pathSpacer; QLabel *filterLabel; KUrlNavigator *urlNavigator; QPushButton *okButton, *cancelButton; QDockWidget *placesDock; KFilePlacesView *placesView; QSplitter *placesViewSplitter; // caches the places view width. This value will be updated when the splitter // is moved. This allows us to properly set a value when the dialog itself // is resized int placesViewWidth; QWidget *labeledCustomWidget; QWidget *bottomCustomWidget; // Automatically Select Extension stuff QCheckBox *autoSelectExtCheckBox; QString extension; // current extension for this filter QList statJobs; QList urlList; //the list of selected urls KFileWidget::OperationMode operationMode; // The file class used for KRecentDirs QString fileClass; KFileBookmarkHandler *bookmarkHandler; KActionMenu *bookmarkButton; KToolBar *toolbar; KUrlComboBox *locationEdit; KDirOperator *ops; KFileFilterCombo *filterWidget; QTimer filterDelayTimer; KFilePlacesModel *model; // whether or not the _user_ has checked the above box bool autoSelectExtChecked : 1; // indicates if the location edit should be kept or cleared when changing // directories bool keepLocation : 1; // the KDirOperators view is set in KFileWidget::show(), so to avoid // setting it again and again, we have this nice little boolean :) bool hasView : 1; bool hasDefaultFilter : 1; // necessary for the operationMode bool autoDirectoryFollowing : 1; bool inAccept : 1; // true between beginning and end of accept() bool dummyAdded : 1; // if the dummy item has been added. This prevents the combo from having a // blank item added when loaded bool confirmOverwrite : 1; bool differentHierarchyLevelItemsEntered; QSlider *iconSizeSlider; QAction *zoomOutAction; QAction *zoomInAction; // The group which stores app-specific settings. These settings are recent // files and urls. Visual settings (view mode, sorting criteria...) are not // app-specific and are stored in kdeglobals KConfigGroup configGroup; }; Q_GLOBAL_STATIC(QUrl, lastDirectory) // to set the start path static const char autocompletionWhatsThisText[] = I18N_NOOP("While typing in the text area, you may be presented " "with possible matches. " "This feature can be controlled by clicking with the right mouse button " "and selecting a preferred mode from the Text Completion menu."); // returns true if the string contains ":/" sequence, where is at least 2 alpha chars static bool containsProtocolSection(const QString &string) { int len = string.length(); static const char prot[] = ":/"; for (int i = 0; i < len;) { i = string.indexOf(QLatin1String(prot), i); if (i == -1) { return false; } int j = i - 1; for (; j >= 0; j--) { const QChar &ch(string[j]); if (ch.toLatin1() == 0 || !ch.isLetter()) { break; } if (ch.isSpace() && (i - j - 1) >= 2) { return true; } } if (j < 0 && i >= 2) { return true; // at least two letters before ":/" } i += 3; // skip : and / and one char } return false; } // this string-to-url conversion function handles relative paths, full paths and URLs // without the http-prepending that QUrl::fromUserInput does. static QUrl urlFromString(const QString& str) { if (QDir::isAbsolutePath(str)) { return QUrl::fromLocalFile(str); } QUrl url(str); if (url.isRelative()) { url.clear(); url.setPath(str); } return url; } KFileWidget::KFileWidget(const QUrl &_startDir, QWidget *parent) : QWidget(parent), d(new KFileWidgetPrivate(this)) { QUrl startDir(_startDir); // qDebug() << "startDir" << startDir; QString filename; d->okButton = new QPushButton(this); KGuiItem::assign(d->okButton, KStandardGuiItem::ok()); d->okButton->setDefault(true); d->cancelButton = new QPushButton(this); KGuiItem::assign(d->cancelButton, KStandardGuiItem::cancel()); // The dialog shows them d->okButton->hide(); d->cancelButton->hide(); d->opsWidget = new QWidget(this); QVBoxLayout *opsWidgetLayout = new QVBoxLayout(d->opsWidget); opsWidgetLayout->setContentsMargins(0, 0, 0, 0); opsWidgetLayout->setSpacing(0); //d->toolbar = new KToolBar(this, true); d->toolbar = new KToolBar(d->opsWidget, true); d->toolbar->setObjectName(QStringLiteral("KFileWidget::toolbar")); d->toolbar->setMovable(false); opsWidgetLayout->addWidget(d->toolbar); d->model = new KFilePlacesModel(this); // Resolve this now so that a 'kfiledialog:' URL, if specified, // does not get inserted into the urlNavigator history. d->url = getStartUrl(startDir, d->fileClass, filename); startDir = d->url; // Don't pass startDir to the KUrlNavigator at this stage: as well as // the above, it may also contain a file name which should not get // inserted in that form into the old-style navigation bar history. // Wait until the KIO::stat has been done later. // // The stat cannot be done before this point, bug 172678. d->urlNavigator = new KUrlNavigator(d->model, QUrl(), d->opsWidget); //d->toolbar); d->urlNavigator->setPlacesSelectorVisible(false); opsWidgetLayout->addWidget(d->urlNavigator); QUrl u; KUrlComboBox *pathCombo = d->urlNavigator->editor(); #ifdef Q_OS_WIN #if 0 foreach (const QFileInfo &drive, QFSFileEngine::drives()) { u = QUrl::fromLocalFile(drive.filePath()); pathCombo->addDefaultUrl(u, KIO::pixmapForUrl(u, 0, KIconLoader::Small), i18n("Drive: %1", u.toLocalFile())); } #else #pragma message("QT5 PORT") #endif #else u = QUrl::fromLocalFile(QDir::rootPath()); pathCombo->addDefaultUrl(u, KIO::pixmapForUrl(u, 0, KIconLoader::Small), u.toLocalFile()); #endif u = QUrl::fromLocalFile(QDir::homePath()); pathCombo->addDefaultUrl(u, KIO::pixmapForUrl(u, 0, KIconLoader::Small), u.toLocalFile()); QUrl docPath = QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)); if (u.adjusted(QUrl::StripTrailingSlash) != docPath.adjusted(QUrl::StripTrailingSlash) && QDir(docPath.toLocalFile()).exists()) { pathCombo->addDefaultUrl(docPath, KIO::pixmapForUrl(docPath, 0, KIconLoader::Small), docPath.toLocalFile()); } u = QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::DesktopLocation)); pathCombo->addDefaultUrl(u, KIO::pixmapForUrl(u, 0, KIconLoader::Small), u.toLocalFile()); d->ops = new KDirOperator(QUrl(), d->opsWidget); d->ops->setObjectName(QStringLiteral("KFileWidget::ops")); d->ops->setIsSaving(d->operationMode == Saving); opsWidgetLayout->addWidget(d->ops); connect(d->ops, SIGNAL(urlEntered(QUrl)), SLOT(_k_urlEntered(QUrl))); connect(d->ops, SIGNAL(fileHighlighted(KFileItem)), SLOT(_k_fileHighlighted(KFileItem))); connect(d->ops, SIGNAL(fileSelected(KFileItem)), SLOT(_k_fileSelected(KFileItem))); connect(d->ops, SIGNAL(finishedLoading()), SLOT(_k_slotLoadingFinished())); + connect(d->ops, SIGNAL(keyEnterReturnPressed()), + SLOT(_k_slotViewKeyEnterReturnPressed())); d->ops->setupMenu(KDirOperator::SortActions | KDirOperator::FileActions | KDirOperator::ViewActions); KActionCollection *coll = d->ops->actionCollection(); coll->addAssociatedWidget(this); // add nav items to the toolbar // // NOTE: The order of the button icons here differs from that // found in the file manager and web browser, but has been discussed // and agreed upon on the kde-core-devel mailing list: // // http://lists.kde.org/?l=kde-core-devel&m=116888382514090&w=2 coll->action(QStringLiteral("up"))->setWhatsThis(i18n("Click this button to enter the parent folder.

" "For instance, if the current location is file:/home/konqi clicking this " "button will take you to file:/home.
")); coll->action(QStringLiteral("back"))->setWhatsThis(i18n("Click this button to move backwards one step in the browsing history.")); coll->action(QStringLiteral("forward"))->setWhatsThis(i18n("Click this button to move forward one step in the browsing history.")); coll->action(QStringLiteral("reload"))->setWhatsThis(i18n("Click this button to reload the contents of the current location.")); coll->action(QStringLiteral("mkdir"))->setShortcut(QKeySequence(Qt::Key_F10)); coll->action(QStringLiteral("mkdir"))->setWhatsThis(i18n("Click this button to create a new folder.")); QAction *goToNavigatorAction = coll->addAction(QStringLiteral("gotonavigator"), this, SLOT(_k_activateUrlNavigator())); goToNavigatorAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_L)); KToggleAction *showSidebarAction = new KToggleAction(i18n("Show Places Navigation Panel"), this); coll->addAction(QStringLiteral("toggleSpeedbar"), showSidebarAction); showSidebarAction->setShortcut(QKeySequence(Qt::Key_F9)); connect(showSidebarAction, SIGNAL(toggled(bool)), SLOT(_k_toggleSpeedbar(bool))); KToggleAction *showBookmarksAction = new KToggleAction(i18n("Show Bookmarks"), this); coll->addAction(QStringLiteral("toggleBookmarks"), showBookmarksAction); connect(showBookmarksAction, SIGNAL(toggled(bool)), SLOT(_k_toggleBookmarks(bool))); KActionMenu *menu = new KActionMenu(QIcon::fromTheme(QStringLiteral("configure")), i18n("Options"), this); coll->addAction(QStringLiteral("extra menu"), menu); menu->setWhatsThis(i18n("This is the preferences menu for the file dialog. " "Various options can be accessed from this menu including:
    " "
  • how files are sorted in the list
  • " "
  • types of view, including icon and list
  • " "
  • showing of hidden files
  • " "
  • the Places navigation panel
  • " "
  • file previews
  • " "
  • separating folders from files
")); menu->addAction(coll->action(QStringLiteral("view menu"))); menu->addSeparator(); menu->addAction(coll->action(QStringLiteral("decoration menu"))); menu->addSeparator(); menu->addAction(coll->action(QStringLiteral("show hidden"))); menu->addAction(showSidebarAction); menu->addAction(showBookmarksAction); coll->action(QStringLiteral("inline preview")); menu->addAction(coll->action(QStringLiteral("preview"))); menu->setDelayed(false); connect(menu->menu(), &QMenu::aboutToShow, d->ops, &KDirOperator::updateSelectionDependentActions); d->iconSizeSlider = new QSlider(this); d->iconSizeSlider->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed); d->iconSizeSlider->setMinimumWidth(40); d->iconSizeSlider->setOrientation(Qt::Horizontal); d->iconSizeSlider->setMinimum(0); d->iconSizeSlider->setMaximum(100); d->iconSizeSlider->installEventFilter(this); connect(d->iconSizeSlider, &QAbstractSlider::valueChanged, d->ops, &KDirOperator::setIconsZoom); connect(d->iconSizeSlider, SIGNAL(valueChanged(int)), this, SLOT(_k_slotIconSizeChanged(int))); connect(d->iconSizeSlider, SIGNAL(sliderMoved(int)), this, SLOT(_k_slotIconSizeSliderMoved(int))); connect(d->ops, &KDirOperator::currentIconSizeChanged, [this](int value) { d->iconSizeSlider->setValue(value); d->zoomOutAction->setDisabled(value <= d->iconSizeSlider->minimum()); d->zoomInAction->setDisabled(value >= d->iconSizeSlider->maximum()); }); d->zoomOutAction = new QAction(QIcon::fromTheme(QStringLiteral("file-zoom-out")), i18n("Zoom out"), this); connect(d->zoomOutAction, SIGNAL(triggered()), SLOT(_k_zoomOutIconsSize())); d->zoomInAction = new QAction(QIcon::fromTheme(QStringLiteral("file-zoom-in")), i18n("Zoom in"), this); connect(d->zoomInAction, SIGNAL(triggered()), SLOT(_k_zoomInIconsSize())); QWidget *midSpacer = new QWidget(this); midSpacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); d->toolbar->addAction(coll->action(QStringLiteral("back"))); d->toolbar->addAction(coll->action(QStringLiteral("forward"))); d->toolbar->addAction(coll->action(QStringLiteral("up"))); d->toolbar->addAction(coll->action(QStringLiteral("reload"))); d->toolbar->addSeparator(); d->toolbar->addAction(coll->action(QStringLiteral("inline preview"))); d->toolbar->addAction(coll->action(QStringLiteral("sorting menu"))); d->toolbar->addWidget(midSpacer); d->toolbar->addAction(d->zoomOutAction); d->toolbar->addWidget(d->iconSizeSlider); d->toolbar->addAction(d->zoomInAction); d->toolbar->addSeparator(); d->toolbar->addAction(coll->action(QStringLiteral("mkdir"))); d->toolbar->addAction(menu); d->toolbar->setToolButtonStyle(Qt::ToolButtonIconOnly); d->toolbar->setMovable(false); KUrlCompletion *pathCompletionObj = new KUrlCompletion(KUrlCompletion::DirCompletion); pathCombo->setCompletionObject(pathCompletionObj); pathCombo->setAutoDeleteCompletionObject(true); connect(d->urlNavigator, SIGNAL(urlChanged(QUrl)), this, SLOT(_k_enterUrl(QUrl))); connect(d->urlNavigator, &KUrlNavigator::returnPressed, d->ops, QOverload<>::of(&QWidget::setFocus)); QString whatsThisText; // the Location label/edit d->locationLabel = new QLabel(i18n("&Name:"), this); d->locationEdit = new KUrlComboBox(KUrlComboBox::Files, true, this); d->locationEdit->installEventFilter(this); // Properly let the dialog be resized (to smaller). Otherwise we could have // huge dialogs that can't be resized to smaller (it would be as big as the longest // item in this combo box). (ereslibre) d->locationEdit->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLength); connect(d->locationEdit, SIGNAL(editTextChanged(QString)), SLOT(_k_slotLocationChanged(QString))); d->updateLocationWhatsThis(); d->locationLabel->setBuddy(d->locationEdit); KUrlCompletion *fileCompletionObj = new KUrlCompletion(KUrlCompletion::FileCompletion); d->locationEdit->setCompletionObject(fileCompletionObj); d->locationEdit->setAutoDeleteCompletionObject(true); connect(fileCompletionObj, SIGNAL(match(QString)), SLOT(_k_fileCompletion(QString))); connect(d->locationEdit, SIGNAL(returnPressed(QString)), this, SLOT(_k_locationAccepted(QString))); // the Filter label/edit whatsThisText = i18n("This is the filter to apply to the file list. " "File names that do not match the filter will not be shown.

" "You may select from one of the preset filters in the " "drop down menu, or you may enter a custom filter " "directly into the text area.

" "Wildcards such as * and ? are allowed.

"); d->filterLabel = new QLabel(i18n("&Filter:"), this); d->filterLabel->setWhatsThis(whatsThisText); d->filterWidget = new KFileFilterCombo(this); // Properly let the dialog be resized (to smaller). Otherwise we could have // huge dialogs that can't be resized to smaller (it would be as big as the longest // item in this combo box). (ereslibre) d->filterWidget->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLength); d->filterWidget->setWhatsThis(whatsThisText); d->filterLabel->setBuddy(d->filterWidget); connect(d->filterWidget, SIGNAL(filterChanged()), SLOT(_k_slotFilterChanged())); d->filterDelayTimer.setSingleShot(true); d->filterDelayTimer.setInterval(300); connect(d->filterWidget, &QComboBox::editTextChanged, &d->filterDelayTimer, QOverload<>::of(&QTimer::start)); connect(&d->filterDelayTimer, SIGNAL(timeout()), SLOT(_k_slotFilterChanged())); // the Automatically Select Extension checkbox // (the text, visibility etc. is set in updateAutoSelectExtension(), which is called by readConfig()) d->autoSelectExtCheckBox = new QCheckBox(this); const int spacingHint = style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); d->autoSelectExtCheckBox->setStyleSheet(QStringLiteral("QCheckBox { padding-top: %1px; }").arg(spacingHint)); connect(d->autoSelectExtCheckBox, SIGNAL(clicked()), SLOT(_k_slotAutoSelectExtClicked())); d->initGUI(); // activate GM // read our configuration KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group(config, ConfigGroup); readConfig(group); coll->action(QStringLiteral("inline preview"))->setChecked(d->ops->isInlinePreviewShown()); d->iconSizeSlider->setValue(d->ops->iconsZoom()); KFilePreviewGenerator *pg = d->ops->previewGenerator(); if (pg) { coll->action(QStringLiteral("inline preview"))->setChecked(pg->isPreviewShown()); } // getStartUrl() above will have resolved the startDir parameter into // a directory and file name in the two cases: (a) where it is a // special "kfiledialog:" URL, or (b) where it is a plain file name // only without directory or protocol. For any other startDir // specified, it is not possible to resolve whether there is a file name // present just by looking at the URL; the only way to be sure is // to stat it. bool statRes = false; if (filename.isEmpty()) { KIO::StatJob *statJob = KIO::stat(startDir, KIO::HideProgressInfo); KJobWidgets::setWindow(statJob, this); statRes = statJob->exec(); // qDebug() << "stat of" << startDir << "-> statRes" << statRes << "isDir" << statJob->statResult().isDir(); if (!statRes || !statJob->statResult().isDir()) { filename = startDir.fileName(); startDir = startDir.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); // qDebug() << "statJob -> startDir" << startDir << "filename" << filename; } } d->ops->setUrl(startDir, true); d->urlNavigator->setLocationUrl(startDir); if (d->placesView) { d->placesView->setUrl(startDir); } // We have a file name either explicitly specified, or have checked that // we could stat it and it is not a directory. Set it. if (!filename.isEmpty()) { QLineEdit *lineEdit = d->locationEdit->lineEdit(); // qDebug() << "selecting filename" << filename; if (statRes) { d->setLocationText(QUrl(filename)); } else { lineEdit->setText(filename); // Preserve this filename when clicking on the view (cf _k_fileHighlighted) lineEdit->setModified(true); } lineEdit->selectAll(); } d->locationEdit->setFocus(); } KFileWidget::~KFileWidget() { KSharedConfig::Ptr config = KSharedConfig::openConfig(); config->sync(); delete d; } void KFileWidget::setLocationLabel(const QString &text) { d->locationLabel->setText(text); } void KFileWidget::setFilter(const QString &filter) { int pos = filter.indexOf(QLatin1Char('/')); // Check for an un-escaped '/', if found // interpret as a MIME filter. if (pos > 0 && filter[pos - 1] != QLatin1Char('\\')) { QStringList filters = filter.split(QLatin1Char(' '), QString::SkipEmptyParts); setMimeFilter(filters); return; } // Strip the escape characters from // escaped '/' characters. QString copy(filter); for (pos = 0; (pos = copy.indexOf(QStringLiteral("\\/"), pos)) != -1; ++pos) { copy.remove(pos, 1); } d->ops->clearFilter(); d->filterWidget->setFilter(copy); d->ops->setNameFilter(d->filterWidget->currentFilter()); d->ops->updateDir(); d->hasDefaultFilter = false; d->filterWidget->setEditable(true); d->updateAutoSelectExtension(); } QString KFileWidget::currentFilter() const { return d->filterWidget->currentFilter(); } void KFileWidget::setMimeFilter(const QStringList &mimeTypes, const QString &defaultType) { d->filterWidget->setMimeFilter(mimeTypes, defaultType); QStringList types = d->filterWidget->currentFilter().split(QLatin1Char(' '), QString::SkipEmptyParts); //QStringList::split(" ", d->filterWidget->currentFilter()); types.append(QStringLiteral("inode/directory")); d->ops->clearFilter(); d->ops->setMimeFilter(types); d->hasDefaultFilter = !defaultType.isEmpty(); d->filterWidget->setEditable(!d->hasDefaultFilter || d->operationMode != Saving); d->updateAutoSelectExtension(); } void KFileWidget::clearFilter() { d->filterWidget->setFilter(QString()); d->ops->clearFilter(); d->hasDefaultFilter = false; d->filterWidget->setEditable(true); d->updateAutoSelectExtension(); } QString KFileWidget::currentMimeFilter() const { int i = d->filterWidget->currentIndex(); if (d->filterWidget->showsAllTypes() && i == 0) { return QString(); // The "all types" item has no mimetype } return d->filterWidget->filters().at(i); } QMimeType KFileWidget::currentFilterMimeType() { QMimeDatabase db; return db.mimeTypeForName(currentMimeFilter()); } void KFileWidget::setPreviewWidget(KPreviewWidgetBase *w) { d->ops->setPreviewWidget(w); d->ops->clearHistory(); d->hasView = true; } QUrl KFileWidgetPrivate::getCompleteUrl(const QString &_url) const { // qDebug() << "got url " << _url; const QString url = KShell::tildeExpand(_url); QUrl u; if (QDir::isAbsolutePath(url)) { u = QUrl::fromLocalFile(url); } else { QUrl relativeUrlTest(ops->url()); relativeUrlTest.setPath(concatPaths(relativeUrlTest.path(), url)); if (!ops->dirLister()->findByUrl(relativeUrlTest).isNull() || !KProtocolInfo::isKnownProtocol(relativeUrlTest)) { u = relativeUrlTest; } else { // Try to preserve URLs if they have a scheme (for example, // "https://example.com/foo.txt") and otherwise resolve relative // paths to absolute ones (e.g. "foo.txt" -> "file:///tmp/foo.txt"). u = QUrl(url); if (u.isRelative()) { u = relativeUrlTest; } } } return u; } QSize KFileWidget::sizeHint() const { int fontSize = fontMetrics().height(); const QSize goodSize(48 * fontSize, 30 * fontSize); const QSize screenSize = QApplication::desktop()->availableGeometry(this).size(); const QSize minSize(screenSize / 2); const QSize maxSize(screenSize * qreal(0.9)); return (goodSize.expandedTo(minSize).boundedTo(maxSize)); } static QString relativePathOrUrl(const QUrl &baseUrl, const QUrl &url); // Called by KFileDialog void KFileWidget::slotOk() { // qDebug() << "slotOk\n"; const QString locationEditCurrentText(KShell::tildeExpand(d->locationEditCurrentText())); QList locationEditCurrentTextList(d->tokenize(locationEditCurrentText)); KFile::Modes mode = d->ops->mode(); // if there is nothing to do, just return from here if (locationEditCurrentTextList.isEmpty()) { return; } // Make sure that one of the modes was provided if (!((mode & KFile::File) || (mode & KFile::Directory) || (mode & KFile::Files))) { mode |= KFile::File; // qDebug() << "No mode() provided"; } // if we are on file mode, and the list of provided files/folder is greater than one, inform // the user about it if (locationEditCurrentTextList.count() > 1) { if (mode & KFile::File) { KMessageBox::sorry(this, i18n("You can only select one file"), i18n("More than one file provided")); return; } /** * Logic of the next part of code (ends at "end multi relative urls"). * * We allow for instance to be at "/" and insert '"home/foo/bar.txt" "boot/grub/menu.lst"'. * Why we need to support this ? Because we provide tree views, which aren't plain. * * Now, how does this logic work. It will get the first element on the list (with no filename), * following the previous example say "/home/foo" and set it as the top most url. * * After this, it will iterate over the rest of items and check if this URL (topmost url) * contains the url being iterated. * * As you might have guessed it will do "/home/foo" against "/boot/grub" (again stripping * filename), and a false will be returned. Then we upUrl the top most url, resulting in * "/home" against "/boot/grub", what will again return false, so we upUrl again. Now we * have "/" against "/boot/grub", what returns true for us, so we can say that the closest * common ancestor of both is "/". * * This example has been written for 2 urls, but this works for any number of urls. */ if (!d->differentHierarchyLevelItemsEntered) { // avoid infinite recursion. running this int start = 0; QUrl topMostUrl; KIO::StatJob *statJob = nullptr; bool res = false; // we need to check for a valid first url, so in theory we only iterate one time over // this loop. However it can happen that the user did // "home/foo/nonexistantfile" "boot/grub/menu.lst", so we look for a good first // candidate. while (!res && start < locationEditCurrentTextList.count()) { topMostUrl = locationEditCurrentTextList.at(start); statJob = KIO::stat(topMostUrl, KIO::HideProgressInfo); KJobWidgets::setWindow(statJob, this); res = statJob->exec(); start++; } Q_ASSERT(statJob); // if this is not a dir, strip the filename. after this we have an existent and valid // dir (we stated correctly the file). if (!statJob->statResult().isDir()) { topMostUrl = topMostUrl.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); } // now the funny part. for the rest of filenames, go and look for the closest ancestor // of all them. for (int i = start; i < locationEditCurrentTextList.count(); ++i) { QUrl currUrl = locationEditCurrentTextList.at(i); KIO::StatJob *statJob = KIO::stat(currUrl, KIO::HideProgressInfo); KJobWidgets::setWindow(statJob, this); int res = statJob->exec(); if (res) { // again, we don't care about filenames if (!statJob->statResult().isDir()) { currUrl = currUrl.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); } // iterate while this item is contained on the top most url while (!topMostUrl.matches(currUrl, QUrl::StripTrailingSlash) && !topMostUrl.isParentOf(currUrl)) { topMostUrl = KIO::upUrl(topMostUrl); } } } // now recalculate all paths for them being relative in base of the top most url QStringList stringList; stringList.reserve(locationEditCurrentTextList.count()); for (int i = 0; i < locationEditCurrentTextList.count(); ++i) { Q_ASSERT(topMostUrl.isParentOf(locationEditCurrentTextList[i])); stringList << relativePathOrUrl(topMostUrl, locationEditCurrentTextList[i]); } d->ops->setUrl(topMostUrl, true); const bool signalsBlocked = d->locationEdit->lineEdit()->blockSignals(true); d->locationEdit->lineEdit()->setText(QStringLiteral("\"%1\"").arg(stringList.join(QStringLiteral("\" \"")))); d->locationEdit->lineEdit()->blockSignals(signalsBlocked); d->differentHierarchyLevelItemsEntered = true; slotOk(); return; } /** * end multi relative urls */ } else if (!locationEditCurrentTextList.isEmpty()) { // if we are on file or files mode, and we have an absolute url written by // the user, convert it to relative if (!locationEditCurrentText.isEmpty() && !(mode & KFile::Directory) && (QDir::isAbsolutePath(locationEditCurrentText) || containsProtocolSection(locationEditCurrentText))) { QString fileName; QUrl url = urlFromString(locationEditCurrentText); if (d->operationMode == Opening) { KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo); KJobWidgets::setWindow(statJob, this); int res = statJob->exec(); if (res) { if (!statJob->statResult().isDir()) { fileName = url.fileName(); url = url.adjusted(QUrl::RemoveFilename); // keeps trailing slash } else { if (!url.path().endsWith(QLatin1Char('/'))) { url.setPath(url.path() + QLatin1Char('/')); } } } } else { const QUrl directory = url.adjusted(QUrl::RemoveFilename); //Check if the folder exists KIO::StatJob *statJob = KIO::stat(directory, KIO::HideProgressInfo); KJobWidgets::setWindow(statJob, this); int res = statJob->exec(); if (res) { if (statJob->statResult().isDir()) { url = url.adjusted(QUrl::StripTrailingSlash); fileName = url.fileName(); url = url.adjusted(QUrl::RemoveFilename); } } } d->ops->setUrl(url, true); const bool signalsBlocked = d->locationEdit->lineEdit()->blockSignals(true); d->locationEdit->lineEdit()->setText(fileName); d->locationEdit->lineEdit()->blockSignals(signalsBlocked); slotOk(); return; } } // restore it d->differentHierarchyLevelItemsEntered = false; // locationEditCurrentTextList contains absolute paths // this is the general loop for the File and Files mode. Obviously we know // that the File mode will iterate only one time here bool directoryMode = (mode & KFile::Directory); bool onlyDirectoryMode = directoryMode && !(mode & KFile::File) && !(mode & KFile::Files); QList::ConstIterator it = locationEditCurrentTextList.constBegin(); bool filesInList = false; while (it != locationEditCurrentTextList.constEnd()) { QUrl url(*it); if (d->operationMode == Saving && !directoryMode) { d->appendExtension(url); } d->url = url; KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo); KJobWidgets::setWindow(statJob, this); int res = statJob->exec(); if (!KUrlAuthorized::authorizeUrlAction(QStringLiteral("open"), QUrl(), url)) { QString msg = KIO::buildErrorString(KIO::ERR_ACCESS_DENIED, d->url.toDisplayString()); KMessageBox::error(this, msg); return; } // if we are on local mode, make sure we haven't got a remote base url if ((mode & KFile::LocalOnly) && !d->mostLocalUrl(d->url).isLocalFile()) { KMessageBox::sorry(this, i18n("You can only select local files"), i18n("Remote files not accepted")); return; } const auto &supportedSchemes = d->model->supportedSchemes(); if (!supportedSchemes.isEmpty() && !supportedSchemes.contains(d->url.scheme())) { KMessageBox::sorry(this, i18np("The selected URL uses an unsupported scheme. " "Please use the following scheme: %2", "The selected URL uses an unsupported scheme. " "Please use one of the following schemes: %2", supportedSchemes.size(), supportedSchemes.join(QLatin1String(", "))), i18n("Unsupported URL scheme")); return; } // if we are given a folder when not on directory mode, let's get into it if (res && !directoryMode && statJob->statResult().isDir()) { // check if we were given more than one folder, in that case we don't know to which one // cd ++it; while (it != locationEditCurrentTextList.constEnd()) { QUrl checkUrl(*it); KIO::StatJob *checkStatJob = KIO::stat(checkUrl, KIO::HideProgressInfo); KJobWidgets::setWindow(checkStatJob, this); bool res = checkStatJob->exec(); if (res && checkStatJob->statResult().isDir()) { KMessageBox::sorry(this, i18n("More than one folder has been selected and this dialog does not accept folders, so it is not possible to decide which one to enter. Please select only one folder to list it."), i18n("More than one folder provided")); return; } else if (res) { filesInList = true; } ++it; } if (filesInList) { KMessageBox::information(this, i18n("At least one folder and one file has been selected. Selected files will be ignored and the selected folder will be listed"), i18n("Files and folders selected")); } d->ops->setUrl(url, true); const bool signalsBlocked = d->locationEdit->lineEdit()->blockSignals(true); d->locationEdit->lineEdit()->setText(QString()); d->locationEdit->lineEdit()->blockSignals(signalsBlocked); return; } else if (res && onlyDirectoryMode && !statJob->statResult().isDir()) { // if we are given a file when on directory only mode, reject it return; } else if (!(mode & KFile::ExistingOnly) || res) { // if we don't care about ExistingOnly flag, add the file even if // it doesn't exist. If we care about it, don't add it to the list if (!onlyDirectoryMode || (res && statJob->statResult().isDir())) { d->urlList << url; } filesInList = true; } else { KMessageBox::sorry(this, i18n("The file \"%1\" could not be found", url.toDisplayString(QUrl::PreferLocalFile)), i18n("Cannot open file")); return; // do not emit accepted() if we had ExistingOnly flag and stat failed } if ((d->operationMode == Saving) && d->confirmOverwrite && !d->toOverwrite(url)) { return; } ++it; } // if we have reached this point and we didn't return before, that is because // we want this dialog to be accepted emit accepted(); } void KFileWidget::accept() { d->inAccept = true; // parseSelectedUrls() checks that *lastDirectory() = d->ops->url(); if (!d->fileClass.isEmpty()) { KRecentDirs::add(d->fileClass, d->ops->url().toString()); } // clear the topmost item, we insert it as full path later on as item 1 d->locationEdit->setItemText(0, QString()); const QList list = selectedUrls(); QList::const_iterator it = list.begin(); int atmost = d->locationEdit->maxItems(); //don't add more items than necessary for (; it != list.end() && atmost > 0; ++it) { const QUrl &url = *it; // we strip the last slash (-1) because KUrlComboBox does that as well // when operating in file-mode. If we wouldn't , dupe-finding wouldn't // work. QString file = url.isLocalFile() ? url.toLocalFile() : url.toDisplayString(); // remove dupes for (int i = 1; i < d->locationEdit->count(); i++) { if (d->locationEdit->itemText(i) == file) { d->locationEdit->removeItem(i--); break; } } //FIXME I don't think this works correctly when the KUrlComboBox has some default urls. //KUrlComboBox should provide a function to add an url and rotate the existing ones, keeping //track of maxItems, and we shouldn't be able to insert items as we please. d->locationEdit->insertItem(1, file); atmost--; } d->writeViewConfig(); d->saveRecentFiles(); d->addToRecentDocuments(); if (!(mode() & KFile::Files)) { // single selection emit fileSelected(d->url); } d->ops->close(); } void KFileWidgetPrivate::_k_fileHighlighted(const KFileItem &i) { if ((!i.isNull() && i.isDir()) || (locationEdit->hasFocus() && !locationEdit->currentText().isEmpty())) { // don't disturb return; } const bool modified = locationEdit->lineEdit()->isModified(); if (!(ops->mode() & KFile::Files)) { if (i.isNull()) { if (!modified) { setLocationText(QUrl()); } return; } url = i.url(); if (!locationEdit->hasFocus()) { // don't disturb while editing setLocationText(url); } emit q->fileHighlighted(url); } else { multiSelectionChanged(); emit q->selectionChanged(); } locationEdit->lineEdit()->setModified(false); // When saving, and when double-click mode is being used, highlight the // filename after a file is single-clicked so the user has a chance to quickly // rename it if desired // Note that double-clicking will override this and overwrite regardless of // single/double click mouse setting (see _k_slotViewDoubleClicked() ) if (operationMode == KFileWidget::Saving) { locationEdit->setFocus(); } } void KFileWidgetPrivate::_k_fileSelected(const KFileItem &i) { if (!i.isNull() && i.isDir()) { return; } if (!(ops->mode() & KFile::Files)) { if (i.isNull()) { setLocationText(QUrl()); return; } setLocationText(i.url()); } else { multiSelectionChanged(); emit q->selectionChanged(); } // Same as above in _k_fileHighlighted(), but for single-click mode if (operationMode == KFileWidget::Saving) { locationEdit->setFocus(); } else { q->slotOk(); } } // I know it's slow to always iterate thru the whole filelist // (d->ops->selectedItems()), but what can we do? void KFileWidgetPrivate::multiSelectionChanged() { if (locationEdit->hasFocus() && !locationEdit->currentText().isEmpty()) { // don't disturb return; } const KFileItemList list = ops->selectedItems(); if (list.isEmpty()) { setLocationText(QUrl()); return; } setLocationText(list.urlList()); } void KFileWidgetPrivate::setDummyHistoryEntry(const QString &text, const QPixmap &icon, bool usePreviousPixmapIfNull) { // setCurrentItem() will cause textChanged() being emitted, // so slotLocationChanged() will be called. Make sure we don't clear // the KDirOperator's view-selection in there QObject::disconnect(locationEdit, SIGNAL(editTextChanged(QString)), q, SLOT(_k_slotLocationChanged(QString))); bool dummyExists = dummyAdded; int cursorPosition = locationEdit->lineEdit()->cursorPosition(); if (dummyAdded) { if (!icon.isNull()) { locationEdit->setItemIcon(0, icon); locationEdit->setItemText(0, text); } else { if (!usePreviousPixmapIfNull) { locationEdit->setItemIcon(0, QPixmap()); } locationEdit->setItemText(0, text); } } else { if (!text.isEmpty()) { if (!icon.isNull()) { locationEdit->insertItem(0, icon, text); } else { if (!usePreviousPixmapIfNull) { locationEdit->insertItem(0, QPixmap(), text); } else { locationEdit->insertItem(0, text); } } dummyAdded = true; dummyExists = true; } } if (dummyExists && !text.isEmpty()) { locationEdit->setCurrentIndex(0); } locationEdit->lineEdit()->setCursorPosition(cursorPosition); QObject::connect(locationEdit, SIGNAL(editTextChanged(QString)), q, SLOT(_k_slotLocationChanged(QString))); } void KFileWidgetPrivate::removeDummyHistoryEntry() { if (!dummyAdded) { return; } // setCurrentItem() will cause textChanged() being emitted, // so slotLocationChanged() will be called. Make sure we don't clear // the KDirOperator's view-selection in there QObject::disconnect(locationEdit, SIGNAL(editTextChanged(QString)), q, SLOT(_k_slotLocationChanged(QString))); if (locationEdit->count()) { locationEdit->removeItem(0); } locationEdit->setCurrentIndex(-1); dummyAdded = false; QObject::connect(locationEdit, SIGNAL(editTextChanged(QString)), q, SLOT(_k_slotLocationChanged(QString))); } void KFileWidgetPrivate::setLocationText(const QUrl &url) { if (!url.isEmpty()) { QPixmap mimeTypeIcon = KIconLoader::global()->loadMimeTypeIcon(KIO::iconNameForUrl(url), KIconLoader::Small); if (!url.isRelative()) { const QUrl directory = url.adjusted(QUrl::RemoveFilename); if (!directory.path().isEmpty()) { q->setUrl(directory, false); } else { q->setUrl(url, false); } } setDummyHistoryEntry(url.fileName(), mimeTypeIcon); } else { removeDummyHistoryEntry(); } if (operationMode == KFileWidget::Saving) { setNonExtSelection(); } } static QString relativePathOrUrl(const QUrl &baseUrl, const QUrl &url) { if (baseUrl.isParentOf(url)) { const QString basePath(QDir::cleanPath(baseUrl.path())); QString relPath(QDir::cleanPath(url.path())); relPath.remove(0, basePath.length()); if (relPath.startsWith(QLatin1Char('/'))) { relPath.remove(0, 1); } return relPath; } else { return url.toDisplayString(); } } void KFileWidgetPrivate::setLocationText(const QList &urlList) { const QUrl currUrl = ops->url(); if (urlList.count() > 1) { QString urls; foreach (const QUrl &url, urlList) { urls += QStringLiteral("\"%1\"").arg(relativePathOrUrl(currUrl, url)) + QLatin1Char(' '); } urls.chop(1); setDummyHistoryEntry(urls, QPixmap(), false); } else if (urlList.count() == 1) { const QPixmap mimeTypeIcon = KIconLoader::global()->loadMimeTypeIcon(KIO::iconNameForUrl(urlList[0]), KIconLoader::Small); setDummyHistoryEntry(relativePathOrUrl(currUrl, urlList[0]), mimeTypeIcon); } else { removeDummyHistoryEntry(); } if (operationMode == KFileWidget::Saving) { setNonExtSelection(); } } void KFileWidgetPrivate::updateLocationWhatsThis() { QString whatsThisText; if (operationMode == KFileWidget::Saving) { whatsThisText = QLatin1String("") + i18n("This is the name to save the file as.") + i18n(autocompletionWhatsThisText); } else if (ops->mode() & KFile::Files) { whatsThisText = QLatin1String("") + i18n("This is the list of files to open. More than " "one file can be specified by listing several " "files, separated by spaces.") + i18n(autocompletionWhatsThisText); } else { whatsThisText = QLatin1String("") + i18n("This is the name of the file to open.") + i18n(autocompletionWhatsThisText); } locationLabel->setWhatsThis(whatsThisText); locationEdit->setWhatsThis(whatsThisText); } void KFileWidgetPrivate::initSpeedbar() { if (placesDock) { return; } placesDock = new QDockWidget(i18nc("@title:window", "Places"), q); placesDock->setFeatures(QDockWidget::NoDockWidgetFeatures); placesDock->setTitleBarWidget(new KDEPrivate::KFileWidgetDockTitleBar(placesDock)); placesView = new KFilePlacesView(placesDock); placesView->setModel(model); placesView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); placesView->setObjectName(QStringLiteral("url bar")); QObject::connect(placesView, SIGNAL(urlChanged(QUrl)), q, SLOT(_k_enterUrl(QUrl))); // need to set the current url of the urlbar manually (not via urlEntered() // here, because the initial url of KDirOperator might be the same as the // one that will be set later (and then urlEntered() won't be emitted). // TODO: KDE5 ### REMOVE THIS when KDirOperator's initial URL (in the c'tor) is gone. placesView->setUrl(url); placesDock->setWidget(placesView); placesViewSplitter->insertWidget(0, placesDock); // initialize the size of the splitter placesViewWidth = configGroup.readEntry(SpeedbarWidth, placesView->sizeHint().width()); // Needed for when the dialog is shown with the places panel initially hidden setPlacesViewSplitterSizes(); QObject::connect(placesDock, SIGNAL(visibilityChanged(bool)), q, SLOT(_k_toggleSpeedbar(bool))); } void KFileWidgetPrivate::setPlacesViewSplitterSizes() { if (placesViewWidth > 0) { QList sizes = placesViewSplitter->sizes(); sizes[0] = placesViewWidth; sizes[1] = q->width() - placesViewWidth - placesViewSplitter->handleWidth(); placesViewSplitter->setSizes(sizes); } } void KFileWidgetPrivate::setLafBoxColumnWidth() { // In order to perfectly align the filename widget with KDirOperator's icon view // - placesViewWidth needs to account for the size of the splitter handle // - the lafBox grid layout spacing should only affect the label, but not the line edit const int adjustment = placesViewSplitter->handleWidth() - lafBox->horizontalSpacing(); lafBox->setColumnMinimumWidth(0, placesViewWidth + adjustment); } void KFileWidgetPrivate::initGUI() { delete boxLayout; // deletes all sub layouts boxLayout = new QVBoxLayout(q); boxLayout->setContentsMargins(0, 0, 0, 0); // no additional margin to the already existing placesViewSplitter = new QSplitter(q); placesViewSplitter->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); placesViewSplitter->setChildrenCollapsible(false); boxLayout->addWidget(placesViewSplitter); QObject::connect(placesViewSplitter, SIGNAL(splitterMoved(int,int)), q, SLOT(_k_placesViewSplitterMoved(int,int))); placesViewSplitter->insertWidget(0, opsWidget); vbox = new QVBoxLayout(); vbox->setContentsMargins(0, 0, 0, 0); boxLayout->addLayout(vbox); lafBox = new QGridLayout(); lafBox->addWidget(locationLabel, 0, 0, Qt::AlignVCenter | Qt::AlignRight); lafBox->addWidget(locationEdit, 0, 1, Qt::AlignVCenter); lafBox->addWidget(okButton, 0, 2, Qt::AlignVCenter); lafBox->addWidget(filterLabel, 1, 0, Qt::AlignVCenter | Qt::AlignRight); lafBox->addWidget(filterWidget, 1, 1, Qt::AlignVCenter); lafBox->addWidget(cancelButton, 1, 2, Qt::AlignVCenter); lafBox->setColumnStretch(1, 4); vbox->addLayout(lafBox); // add the Automatically Select Extension checkbox vbox->addWidget(autoSelectExtCheckBox); q->setTabOrder(ops, autoSelectExtCheckBox); q->setTabOrder(autoSelectExtCheckBox, locationEdit); q->setTabOrder(locationEdit, filterWidget); q->setTabOrder(filterWidget, okButton); q->setTabOrder(okButton, cancelButton); q->setTabOrder(cancelButton, urlNavigator); q->setTabOrder(urlNavigator, ops); } void KFileWidgetPrivate::_k_slotFilterChanged() { // qDebug(); filterDelayTimer.stop(); QString filter = filterWidget->currentFilter(); ops->clearFilter(); if (filter.contains(QLatin1Char('/'))) { QStringList types = filter.split(QLatin1Char(' '), QString::SkipEmptyParts); types.prepend(QStringLiteral("inode/directory")); ops->setMimeFilter(types); } else if (filter.contains(QLatin1Char('*')) || filter.contains(QLatin1Char('?')) || filter.contains(QLatin1Char('['))) { ops->setNameFilter(filter); } else { ops->setNameFilter(QLatin1Char('*') + filter.replace(QLatin1Char(' '), QLatin1Char('*')) + QLatin1Char('*')); } updateAutoSelectExtension(); ops->updateDir(); emit q->filterChanged(filter); } void KFileWidget::setUrl(const QUrl &url, bool clearforward) { // qDebug(); d->ops->setUrl(url, clearforward); } // Protected void KFileWidgetPrivate::_k_urlEntered(const QUrl &url) { // qDebug(); QString filename = locationEditCurrentText(); KUrlComboBox *pathCombo = urlNavigator->editor(); if (pathCombo->count() != 0) { // little hack pathCombo->setUrl(url); } bool blocked = locationEdit->blockSignals(true); if (keepLocation) { QUrl currentUrl = urlFromString(filename); locationEdit->changeUrl(0, QIcon::fromTheme(KIO::iconNameForUrl(currentUrl)), currentUrl); locationEdit->lineEdit()->setModified(true); } locationEdit->blockSignals(blocked); urlNavigator->setLocationUrl(url); // is trigged in ctor before completion object is set KUrlCompletion *completion = dynamic_cast(locationEdit->completionObject()); if (completion) { completion->setDir(url); } if (placesView) { placesView->setUrl(url); } } void KFileWidgetPrivate::_k_locationAccepted(const QString &url) { Q_UNUSED(url); // qDebug(); q->slotOk(); } void KFileWidgetPrivate::_k_enterUrl(const QUrl &url) { // qDebug(); // append '/' if needed: url combo does not add it // tokenize() expects it because it uses QUrl::adjusted(QUrl::RemoveFilename) QUrl u(url); if (!u.path().isEmpty() && !u.path().endsWith(QLatin1Char('/'))) { u.setPath(u.path() + QLatin1Char('/')); } q->setUrl(u); // We need to check window()->focusWidget() instead of locationEdit->hasFocus // because when the window is showing up locationEdit // may still not have focus but it'll be the one that will have focus when the window // gets it and we don't want to steal its focus either if (q->window()->focusWidget() != locationEdit) { ops->setFocus(); } } void KFileWidgetPrivate::_k_enterUrl(const QString &url) { // qDebug(); _k_enterUrl(urlFromString(KUrlCompletion::replacedPath(url, true, true))); } bool KFileWidgetPrivate::toOverwrite(const QUrl &url) { // qDebug(); KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo); KJobWidgets::setWindow(statJob, q); bool res = statJob->exec(); if (res) { int ret = KMessageBox::warningContinueCancel(q, i18n("The file \"%1\" already exists. Do you wish to overwrite it?", url.fileName()), i18n("Overwrite File?"), KStandardGuiItem::overwrite(), KStandardGuiItem::cancel(), QString(), KMessageBox::Notify | KMessageBox::Dangerous); if (ret != KMessageBox::Continue) { return false; } return true; } return true; } #ifndef KIOFILEWIDGETS_NO_DEPRECATED void KFileWidget::setSelection(const QString &url) { // qDebug() << "setSelection " << url; if (url.isEmpty()) { return; } QUrl u = d->getCompleteUrl(url); if (!u.isValid()) { // Relative path was treated as URL, but it was found to be invalid. qWarning() << url << " is not a correct argument for setSelection!"; return; } setSelectedUrl(urlFromString(url)); } #endif void KFileWidget::setSelectedUrl(const QUrl &url) { // Honor protocols that do not support directory listing if (!url.isRelative() && !KProtocolManager::supportsListing(url)) { return; } d->setLocationText(url); } void KFileWidgetPrivate::_k_slotLoadingFinished() { const QString currentText = locationEdit->currentText(); if (currentText.isEmpty()) { return; } ops->blockSignals(true); QUrl u(ops->url()); if (currentText.startsWith(QLatin1Char('/'))) u.setPath(currentText); else u.setPath(concatPaths(ops->url().path(), currentText)); ops->setCurrentItem(u); ops->blockSignals(false); } void KFileWidgetPrivate::_k_fileCompletion(const QString &match) { // qDebug(); if (match.isEmpty() || locationEdit->currentText().contains(QLatin1Char('"'))) { return; } const QUrl url = urlFromString(match); const QPixmap pix = KIconLoader::global()->loadMimeTypeIcon(KIO::iconNameForUrl(url), KIconLoader::Small); setDummyHistoryEntry(locationEdit->currentText(), pix, !locationEdit->currentText().isEmpty()); } void KFileWidgetPrivate::_k_slotLocationChanged(const QString &text) { // qDebug(); locationEdit->lineEdit()->setModified(true); if (text.isEmpty() && ops->view()) { ops->view()->clearSelection(); } if (text.isEmpty()) { removeDummyHistoryEntry(); } else { setDummyHistoryEntry(text); } if (!locationEdit->lineEdit()->text().isEmpty()) { const QList urlList(tokenize(text)); ops->setCurrentItems(urlList); } updateFilter(); } QUrl KFileWidget::selectedUrl() const { // qDebug(); if (d->inAccept) { return d->url; } else { return QUrl(); } } QList KFileWidget::selectedUrls() const { // qDebug(); QList list; if (d->inAccept) { if (d->ops->mode() & KFile::Files) { list = d->parseSelectedUrls(); } else { list.append(d->url); } } return list; } QList &KFileWidgetPrivate::parseSelectedUrls() { // qDebug(); if (filenames.isEmpty()) { return urlList; } urlList.clear(); if (filenames.contains(QLatin1Char('/'))) { // assume _one_ absolute filename QUrl u; if (containsProtocolSection(filenames)) { u = QUrl(filenames); } else { u.setPath(filenames); } if (u.isValid()) { urlList.append(u); } else KMessageBox::error(q, i18n("The chosen filenames do not\n" "appear to be valid."), i18n("Invalid Filenames")); } else { urlList = tokenize(filenames); } filenames.clear(); // indicate that we parsed that one return urlList; } // FIXME: current implementation drawback: a filename can't contain quotes QList KFileWidgetPrivate::tokenize(const QString &line) const { // qDebug(); QList urls; QUrl u(ops->url()); if (!u.path().endsWith(QLatin1Char('/'))) { u.setPath(u.path() + QLatin1Char('/')); } QString name; const int count = line.count(QLatin1Char('"')); if (count == 0) { // no " " -> assume one single file if (!QDir::isAbsolutePath(line)) { u = u.adjusted(QUrl::RemoveFilename); u.setPath(u.path() + line); if (u.isValid()) { urls.append(u); } } else { urls << QUrl::fromLocalFile(line); } return urls; } int start = 0; int index1 = -1, index2 = -1; while (true) { index1 = line.indexOf(QLatin1Char('"'), start); index2 = line.indexOf(QLatin1Char('"'), index1 + 1); if (index1 < 0 || index2 < 0) { break; } // get everything between the " " name = line.mid(index1 + 1, index2 - index1 - 1); // since we use setPath we need to do this under a temporary url QUrl _u(u); QUrl currUrl(name); if (!QDir::isAbsolutePath(currUrl.url())) { _u = _u.adjusted(QUrl::RemoveFilename); _u.setPath(_u.path() + name); } else { // we allow to insert various absolute paths like: // "/home/foo/bar.txt" "/boot/grub/menu.lst" _u = currUrl; } if (_u.isValid()) { urls.append(_u); } start = index2 + 1; } return urls; } QString KFileWidget::selectedFile() const { // qDebug(); if (d->inAccept) { const QUrl url = d->mostLocalUrl(d->url); if (url.isLocalFile()) { return url.toLocalFile(); } else { KMessageBox::sorry(const_cast(this), i18n("You can only select local files."), i18n("Remote Files Not Accepted")); } } return QString(); } QStringList KFileWidget::selectedFiles() const { // qDebug(); QStringList list; if (d->inAccept) { if (d->ops->mode() & KFile::Files) { const QList urls = d->parseSelectedUrls(); QList::const_iterator it = urls.begin(); while (it != urls.end()) { QUrl url = d->mostLocalUrl(*it); if (url.isLocalFile()) { list.append(url.toLocalFile()); } ++it; } } else { // single-selection mode if (d->url.isLocalFile()) { list.append(d->url.toLocalFile()); } } } return list; } QUrl KFileWidget::baseUrl() const { return d->ops->url(); } void KFileWidget::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); if (d->placesDock) { // we don't want our places dock actually changing size when we resize // and qt doesn't make it easy to enforce such a thing with QSplitter d->setPlacesViewSplitterSizes(); } } void KFileWidget::showEvent(QShowEvent *event) { if (!d->hasView) { // delayed view-creation Q_ASSERT(d); Q_ASSERT(d->ops); d->ops->setView(KFile::Default); d->ops->view()->setSizePolicy(QSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum)); d->hasView = true; connect(d->ops->view(), SIGNAL(doubleClicked(QModelIndex)), this, SLOT(_k_slotViewDoubleClicked(QModelIndex))); } d->ops->clearHistory(); QWidget::showEvent(event); } bool KFileWidget::eventFilter(QObject *watched, QEvent *event) { const bool res = QWidget::eventFilter(watched, event); QKeyEvent *keyEvent = dynamic_cast(event); if (watched == d->iconSizeSlider && keyEvent) { if (keyEvent->key() == Qt::Key_Left || keyEvent->key() == Qt::Key_Up || keyEvent->key() == Qt::Key_Right || keyEvent->key() == Qt::Key_Down) { d->_k_slotIconSizeSliderMoved(d->iconSizeSlider->value()); } } else if (watched == d->locationEdit && event->type() == QEvent::KeyPress) { if (keyEvent->modifiers() & Qt::AltModifier) { switch (keyEvent->key()) { case Qt::Key_Up: d->ops->actionCollection()->action(QStringLiteral("up"))->trigger(); break; case Qt::Key_Left: d->ops->actionCollection()->action(QStringLiteral("back"))->trigger(); break; case Qt::Key_Right: d->ops->actionCollection()->action(QStringLiteral("forward"))->trigger(); break; default: break; } } } return res; } void KFileWidget::setMode(KFile::Modes m) { // qDebug(); d->ops->setMode(m); if (d->ops->dirOnlyMode()) { d->filterWidget->setDefaultFilter(i18n("*|All Folders")); } else { d->filterWidget->setDefaultFilter(i18n("*|All Files")); } d->updateAutoSelectExtension(); } KFile::Modes KFileWidget::mode() const { return d->ops->mode(); } void KFileWidgetPrivate::readViewConfig() { ops->setViewConfig(configGroup); ops->readConfig(configGroup); KUrlComboBox *combo = urlNavigator->editor(); autoDirectoryFollowing = configGroup.readEntry(AutoDirectoryFollowing, DefaultDirectoryFollowing); KCompletion::CompletionMode cm = (KCompletion::CompletionMode) configGroup.readEntry(PathComboCompletionMode, static_cast(KCompletion::CompletionPopup)); if (cm != KCompletion::CompletionPopup) { combo->setCompletionMode(cm); } cm = (KCompletion::CompletionMode) configGroup.readEntry(LocationComboCompletionMode, static_cast(KCompletion::CompletionPopup)); if (cm != KCompletion::CompletionPopup) { locationEdit->setCompletionMode(cm); } // show or don't show the speedbar _k_toggleSpeedbar(configGroup.readEntry(ShowSpeedbar, true)); // show or don't show the bookmarks _k_toggleBookmarks(configGroup.readEntry(ShowBookmarks, false)); // does the user want Automatically Select Extension? autoSelectExtChecked = configGroup.readEntry(AutoSelectExtChecked, DefaultAutoSelectExtChecked); updateAutoSelectExtension(); // should the URL navigator use the breadcrumb navigation? urlNavigator->setUrlEditable(!configGroup.readEntry(BreadcrumbNavigation, true)); // should the URL navigator show the full path? urlNavigator->setShowFullPath(configGroup.readEntry(ShowFullPath, false)); int w1 = q->minimumSize().width(); int w2 = toolbar->sizeHint().width(); if (w1 < w2) { q->setMinimumWidth(w2); } } void KFileWidgetPrivate::writeViewConfig() { // these settings are global settings; ALL instances of the file dialog // should reflect them. // There is no way to tell KFileOperator::writeConfig() to write to // kdeglobals so we write settings to a temporary config group then copy // them all to kdeglobals KConfig tmp(QString(), KConfig::SimpleConfig); KConfigGroup tmpGroup(&tmp, ConfigGroup); KUrlComboBox *pathCombo = urlNavigator->editor(); //saveDialogSize( tmpGroup, KConfigGroup::Persistent | KConfigGroup::Global ); tmpGroup.writeEntry(PathComboCompletionMode, static_cast(pathCombo->completionMode())); tmpGroup.writeEntry(LocationComboCompletionMode, static_cast(locationEdit->completionMode())); const bool showSpeedbar = placesDock && !placesDock->isHidden(); tmpGroup.writeEntry(ShowSpeedbar, showSpeedbar); if (placesViewWidth > 0) { tmpGroup.writeEntry(SpeedbarWidth, placesViewWidth); } tmpGroup.writeEntry(ShowBookmarks, bookmarkHandler != nullptr); tmpGroup.writeEntry(AutoSelectExtChecked, autoSelectExtChecked); tmpGroup.writeEntry(BreadcrumbNavigation, !urlNavigator->isUrlEditable()); tmpGroup.writeEntry(ShowFullPath, urlNavigator->showFullPath()); ops->writeConfig(tmpGroup); // Copy saved settings to kdeglobals tmpGroup.copyTo(&configGroup, KConfigGroup::Persistent | KConfigGroup::Global); } void KFileWidgetPrivate::readRecentFiles() { // qDebug(); QObject::disconnect(locationEdit, SIGNAL(editTextChanged(QString)), q, SLOT(_k_slotLocationChanged(QString))); locationEdit->setMaxItems(configGroup.readEntry(RecentFilesNumber, DefaultRecentURLsNumber)); locationEdit->setUrls(configGroup.readPathEntry(RecentFiles, QStringList()), KUrlComboBox::RemoveBottom); locationEdit->setCurrentIndex(-1); QObject::connect(locationEdit, SIGNAL(editTextChanged(QString)), q, SLOT(_k_slotLocationChanged(QString))); KUrlComboBox *combo = urlNavigator->editor(); combo->setUrls(configGroup.readPathEntry(RecentURLs, QStringList()), KUrlComboBox::RemoveTop); combo->setMaxItems(configGroup.readEntry(RecentURLsNumber, DefaultRecentURLsNumber)); combo->setUrl(ops->url()); // since we delayed this moment, initialize the directory of the completion object to // our current directory (that was very probably set on the constructor) KUrlCompletion *completion = dynamic_cast(locationEdit->completionObject()); if (completion) { completion->setDir(ops->url()); } } void KFileWidgetPrivate::saveRecentFiles() { // qDebug(); configGroup.writePathEntry(RecentFiles, locationEdit->urls()); KUrlComboBox *pathCombo = urlNavigator->editor(); configGroup.writePathEntry(RecentURLs, pathCombo->urls()); } QPushButton *KFileWidget::okButton() const { return d->okButton; } QPushButton *KFileWidget::cancelButton() const { return d->cancelButton; } // Called by KFileDialog void KFileWidget::slotCancel() { d->writeViewConfig(); d->ops->close(); } void KFileWidget::setKeepLocation(bool keep) { d->keepLocation = keep; } bool KFileWidget::keepsLocation() const { return d->keepLocation; } void KFileWidget::setOperationMode(OperationMode mode) { // qDebug(); d->operationMode = mode; d->keepLocation = (mode == Saving); d->filterWidget->setEditable(!d->hasDefaultFilter || mode != Saving); if (mode == Opening) { // don't use KStandardGuiItem::open() here which has trailing ellipsis! d->okButton->setText(i18n("&Open")); d->okButton->setIcon(QIcon::fromTheme(QStringLiteral("document-open"))); // hide the new folder actions...usability team says they shouldn't be in open file dialog actionCollection()->removeAction(actionCollection()->action(QStringLiteral("mkdir"))); } else if (mode == Saving) { KGuiItem::assign(d->okButton, KStandardGuiItem::save()); d->setNonExtSelection(); } else { KGuiItem::assign(d->okButton, KStandardGuiItem::ok()); } d->updateLocationWhatsThis(); d->updateAutoSelectExtension(); if (d->ops) { d->ops->setIsSaving(mode == Saving); } } KFileWidget::OperationMode KFileWidget::operationMode() const { return d->operationMode; } void KFileWidgetPrivate::_k_slotAutoSelectExtClicked() { // qDebug() << "slotAutoSelectExtClicked(): " // << autoSelectExtCheckBox->isChecked() << endl; // whether the _user_ wants it on/off autoSelectExtChecked = autoSelectExtCheckBox->isChecked(); // update the current filename's extension updateLocationEditExtension(extension /* extension hasn't changed */); } void KFileWidgetPrivate::_k_placesViewSplitterMoved(int pos, int index) { // qDebug(); // we need to record the size of the splitter when the splitter changes size // so we can keep the places box the right size! if (placesDock && index == 1) { placesViewWidth = pos; // qDebug() << "setting lafBox minwidth to" << placesViewWidth; setLafBoxColumnWidth(); } } void KFileWidgetPrivate::_k_activateUrlNavigator() { // qDebug(); QLineEdit* lineEdit = urlNavigator->editor()->lineEdit(); // If the text field currently has focus and everything is selected, // pressing the keyboard shortcut returns the whole thing to breadcrumb mode if (urlNavigator->isUrlEditable() && lineEdit->hasFocus() && lineEdit->selectedText() == lineEdit->text() ) { urlNavigator->setUrlEditable(false); } else { urlNavigator->setUrlEditable(true); urlNavigator->setFocus(); lineEdit->selectAll(); } } void KFileWidgetPrivate::_k_zoomOutIconsSize() { const int currValue = ops->iconsZoom(); const int futValue = qMax(0, currValue - 10); iconSizeSlider->setValue(futValue); _k_slotIconSizeSliderMoved(futValue); } void KFileWidgetPrivate::_k_zoomInIconsSize() { const int currValue = ops->iconsZoom(); const int futValue = qMin(100, currValue + 10); iconSizeSlider->setValue(futValue); _k_slotIconSizeSliderMoved(futValue); } void KFileWidgetPrivate::_k_slotIconSizeChanged(int _value) { int maxSize = KIconLoader::SizeEnormous - KIconLoader::SizeSmall; int value = (maxSize * _value / 100) + KIconLoader::SizeSmall; switch (value) { case KIconLoader::SizeSmall: case KIconLoader::SizeSmallMedium: case KIconLoader::SizeMedium: case KIconLoader::SizeLarge: case KIconLoader::SizeHuge: case KIconLoader::SizeEnormous: iconSizeSlider->setToolTip(i18n("Icon size: %1 pixels (standard size)", value)); break; default: iconSizeSlider->setToolTip(i18n("Icon size: %1 pixels", value)); break; } } void KFileWidgetPrivate::_k_slotIconSizeSliderMoved(int _value) { // Force this to be called in case this slot is called first on the // slider move. _k_slotIconSizeChanged(_value); QPoint global(iconSizeSlider->rect().topLeft()); global.ry() += iconSizeSlider->height() / 2; QHelpEvent toolTipEvent(QEvent::ToolTip, QPoint(0, 0), iconSizeSlider->mapToGlobal(global)); QApplication::sendEvent(iconSizeSlider, &toolTipEvent); } void KFileWidgetPrivate::_k_slotViewDoubleClicked(const QModelIndex &index) { // double clicking to save should only work on files if (operationMode == KFileWidget::Saving && index.isValid() && ops->selectedItems().constFirst().isFile()) { q->slotOk(); } } +void KFileWidgetPrivate::_k_slotViewKeyEnterReturnPressed() +{ + // an enter/return event occured in the view + // when we are saving one file and there is no selection in the view (otherwise we get an activated event) + if (operationMode == KFileWidget::Saving && (ops->mode() & KFile::File) && ops->selectedItems().isEmpty()) { + q->slotOk(); + } +} + static QString getExtensionFromPatternList(const QStringList &patternList) { // qDebug(); QString ret; // qDebug() << "\tgetExtension " << patternList; QStringList::ConstIterator patternListEnd = patternList.end(); for (QStringList::ConstIterator it = patternList.begin(); it != patternListEnd; ++it) { // qDebug() << "\t\ttry: \'" << (*it) << "\'"; // is this pattern like "*.BMP" rather than useless things like: // // README // *. // *.* // *.JP*G // *.JP? // *.[Jj][Pp][Gg] if ((*it).startsWith(QLatin1String("*.")) && (*it).length() > 2 && (*it).indexOf(QLatin1Char('*'), 2) < 0 && (*it).indexOf(QLatin1Char('?'), 2) < 0 && (*it).indexOf(QLatin1Char('['), 2) < 0 && (*it).indexOf(QLatin1Char(']'), 2) < 0) { ret = (*it).mid(1); break; } } return ret; } static QString stripUndisplayable(const QString &string) { QString ret = string; ret.remove(QLatin1Char(':')); ret = KLocalizedString::removeAcceleratorMarker(ret); return ret; } //QString KFileWidget::currentFilterExtension() //{ // return d->extension; //} void KFileWidgetPrivate::updateAutoSelectExtension() { if (!autoSelectExtCheckBox) { return; } QMimeDatabase db; // // Figure out an extension for the Automatically Select Extension thing // (some Windows users apparently don't know what to do when confronted // with a text file called "COPYING" but do know what to do with // COPYING.txt ...) // // qDebug() << "Figure out an extension: "; QString lastExtension = extension; extension.clear(); // Automatically Select Extension is only valid if the user is _saving_ a _file_ if ((operationMode == KFileWidget::Saving) && (ops->mode() & KFile::File)) { // // Get an extension from the filter // QString filter = filterWidget->currentFilter(); if (!filter.isEmpty()) { // if the currently selected filename already has an extension which // is also included in the currently allowed extensions, keep it // otherwise use the default extension QString currentExtension = db.suffixForFileName(locationEditCurrentText()); if (currentExtension.isEmpty()) { currentExtension = locationEditCurrentText().section(QLatin1Char('.'), -1, -1); } // qDebug() << "filter:" << filter << "locationEdit:" << locationEditCurrentText() << "currentExtension:" << currentExtension; QString defaultExtension; QStringList extensionList; // e.g. "*.cpp" if (filter.indexOf(QLatin1Char('/')) < 0) { extensionList = filter.split(QLatin1Char(' '), QString::SkipEmptyParts); defaultExtension = getExtensionFromPatternList(extensionList); } // e.g. "text/html" else { QMimeType mime = db.mimeTypeForName(filter); if (mime.isValid()) { extensionList = mime.globPatterns(); defaultExtension = mime.preferredSuffix(); if (!defaultExtension.isEmpty()) { defaultExtension.prepend(QLatin1Char('.')); } } } if ((!currentExtension.isEmpty() && extensionList.contains(QLatin1String("*.") + currentExtension)) || filter == QStringLiteral("application/octet-stream")) { extension = QLatin1Char('.') + currentExtension; } else { extension = defaultExtension; } // qDebug() << "List:" << extensionList << "auto-selected extension:" << extension; } // // GUI: checkbox // QString whatsThisExtension; if (!extension.isEmpty()) { // remember: sync any changes to the string with below autoSelectExtCheckBox->setText(i18n("Automatically select filename e&xtension (%1)", extension)); whatsThisExtension = i18n("the extension %1", extension); autoSelectExtCheckBox->setEnabled(true); autoSelectExtCheckBox->setChecked(autoSelectExtChecked); } else { // remember: sync any changes to the string with above autoSelectExtCheckBox->setText(i18n("Automatically select filename e&xtension")); whatsThisExtension = i18n("a suitable extension"); autoSelectExtCheckBox->setChecked(false); autoSelectExtCheckBox->setEnabled(false); } const QString locationLabelText = stripUndisplayable(locationLabel->text()); autoSelectExtCheckBox->setWhatsThis(QLatin1String("") + i18n( "This option enables some convenient features for " "saving files with extensions:
" "
    " "
  1. Any extension specified in the %1 text " "area will be updated if you change the file type " "to save in.
    " "
  2. " "
  3. If no extension is specified in the %2 " "text area when you click " "Save, %3 will be added to the end of the " "filename (if the filename does not already exist). " "This extension is based on the file type that you " "have chosen to save in.
    " "
    " "If you do not want KDE to supply an extension for the " "filename, you can either turn this option off or you " "can suppress it by adding a period (.) to the end of " "the filename (the period will be automatically " "removed)." "
  4. " "
" "If unsure, keep this option enabled as it makes your " "files more manageable." , locationLabelText, locationLabelText, whatsThisExtension) + QLatin1String("
") ); autoSelectExtCheckBox->show(); // update the current filename's extension updateLocationEditExtension(lastExtension); } // Automatically Select Extension not valid else { autoSelectExtCheckBox->setChecked(false); autoSelectExtCheckBox->hide(); } } // Updates the extension of the filename specified in d->locationEdit if the // Automatically Select Extension feature is enabled. // (this prevents you from accidentally saving "file.kwd" as RTF, for example) void KFileWidgetPrivate::updateLocationEditExtension(const QString &lastExtension) { if (!autoSelectExtCheckBox->isChecked() || extension.isEmpty()) { return; } QString urlStr = locationEditCurrentText(); if (urlStr.isEmpty()) { return; } QUrl url = getCompleteUrl(urlStr); // qDebug() << "updateLocationEditExtension (" << url << ")"; const int fileNameOffset = urlStr.lastIndexOf(QLatin1Char('/')) + 1; QString fileName = urlStr.mid(fileNameOffset); const int dot = fileName.lastIndexOf(QLatin1Char('.')); const int len = fileName.length(); if (dot > 0 && // has an extension already and it's not a hidden file // like ".hidden" (but we do accept ".hidden.ext") dot != len - 1 // and not deliberately suppressing extension ) { // exists? KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo); KJobWidgets::setWindow(statJob, q); bool result = statJob->exec(); if (result) { // qDebug() << "\tfile exists"; if (statJob->statResult().isDir()) { // qDebug() << "\tisDir - won't alter extension"; return; } // --- fall through --- } // // try to get rid of the current extension // // catch "double extensions" like ".tar.gz" if (lastExtension.length() && fileName.endsWith(lastExtension)) { fileName.chop(lastExtension.length()); } else if (extension.length() && fileName.endsWith(extension)) { fileName.chop(extension.length()); } // can only handle "single extensions" else { fileName.truncate(dot); } // add extension const QString newText = urlStr.leftRef(fileNameOffset) + fileName + extension; if (newText != locationEditCurrentText()) { locationEdit->setItemText(locationEdit->currentIndex(), newText); locationEdit->lineEdit()->setModified(true); } } } // Updates the filter if the extension of the filename specified in d->locationEdit is changed // (this prevents you from accidently saving "file.kwd" as RTF, for example) void KFileWidgetPrivate::updateFilter() { // qDebug(); if ((operationMode == KFileWidget::Saving) && (ops->mode() & KFile::File)) { QString urlStr = locationEditCurrentText(); if (urlStr.isEmpty()) { return; } if (filterWidget->isMimeFilter()) { QMimeDatabase db; QMimeType mime = db.mimeTypeForFile(urlStr, QMimeDatabase::MatchExtension); if (mime.isValid() && !mime.isDefault()) { if (filterWidget->currentFilter() != mime.name() && filterWidget->filters().indexOf(mime.name()) != -1) { filterWidget->setCurrentFilter(mime.name()); } } } else { QString filename = urlStr.mid(urlStr.lastIndexOf(QLatin1Char('/')) + 1); // only filename foreach (const QString &filter, filterWidget->filters()) { QStringList patterns = filter.left(filter.indexOf(QLatin1Char('|'))).split(QLatin1Char(' '), QString::SkipEmptyParts); // '*.foo *.bar|Foo type' -> '*.foo', '*.bar' foreach (const QString &p, patterns) { QRegExp rx(p); rx.setPatternSyntax(QRegExp::Wildcard); if (rx.exactMatch(filename)) { if (p != QLatin1String("*")) { // never match the catch-all filter filterWidget->setCurrentFilter(filter); } return; // do not repeat, could match a later filter } } } } } } // applies only to a file that doesn't already exist void KFileWidgetPrivate::appendExtension(QUrl &url) { // qDebug(); if (!autoSelectExtCheckBox->isChecked() || extension.isEmpty()) { return; } QString fileName = url.fileName(); if (fileName.isEmpty()) { return; } // qDebug() << "appendExtension(" << url << ")"; const int len = fileName.length(); const int dot = fileName.lastIndexOf(QLatin1Char('.')); const bool suppressExtension = (dot == len - 1); const bool unspecifiedExtension = (dot <= 0); // don't KIO::Stat if unnecessary if (!(suppressExtension || unspecifiedExtension)) { return; } // exists? KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo); KJobWidgets::setWindow(statJob, q); bool res = statJob->exec(); if (res) { // qDebug() << "\tfile exists - won't append extension"; return; } // suppress automatically append extension? if (suppressExtension) { // // Strip trailing dot // This allows lazy people to have autoSelectExtCheckBox->isChecked // but don't want a file extension to be appended // e.g. "README." will make a file called "README" // // If you really want a name like "README.", then type "README.." // and the trailing dot will be removed (or just stop being lazy and // turn off this feature so that you can type "README.") // // qDebug() << "\tstrip trailing dot"; QString path = url.path(); path.chop(1); url.setPath(path); } // evilmatically append extension :) if the user hasn't specified one else if (unspecifiedExtension) { // qDebug() << "\tappending extension \'" << extension << "\'..."; url = url.adjusted(QUrl::RemoveFilename); // keeps trailing slash url.setPath(url.path() + fileName + extension); // qDebug() << "\tsaving as \'" << url << "\'"; } } // adds the selected files/urls to 'recent documents' void KFileWidgetPrivate::addToRecentDocuments() { int m = ops->mode(); int atmost = KRecentDocument::maximumItems(); //don't add more than we need. KRecentDocument::add() is pretty slow if (m & KFile::LocalOnly) { const QStringList files = q->selectedFiles(); QStringList::ConstIterator it = files.begin(); for (; it != files.end() && atmost > 0; ++it) { KRecentDocument::add(QUrl::fromLocalFile(*it)); atmost--; } } else { // urls const QList urls = q->selectedUrls(); QList::ConstIterator it = urls.begin(); for (; it != urls.end() && atmost > 0; ++it) { if ((*it).isValid()) { KRecentDocument::add(*it); atmost--; } } } } KUrlComboBox *KFileWidget::locationEdit() const { return d->locationEdit; } KFileFilterCombo *KFileWidget::filterWidget() const { return d->filterWidget; } KActionCollection *KFileWidget::actionCollection() const { return d->ops->actionCollection(); } void KFileWidgetPrivate::_k_toggleSpeedbar(bool show) { if (show) { initSpeedbar(); placesDock->show(); setLafBoxColumnWidth(); // check to see if they have a home item defined, if not show the home button QUrl homeURL; homeURL.setPath(QDir::homePath()); KFilePlacesModel *model = static_cast(placesView->model()); for (int rowIndex = 0; rowIndex < model->rowCount(); rowIndex++) { QModelIndex index = model->index(rowIndex, 0); QUrl url = model->url(index); if (homeURL.matches(url, QUrl::StripTrailingSlash)) { toolbar->removeAction(ops->actionCollection()->action(QStringLiteral("home"))); break; } } } else { if (q->sender() == placesDock && placesDock && placesDock->isVisibleTo(q)) { // we didn't *really* go away! the dialog was simply hidden or // we changed virtual desktops or ... return; } if (placesDock) { placesDock->hide(); } QAction *homeAction = ops->actionCollection()->action(QStringLiteral("home")); QAction *reloadAction = ops->actionCollection()->action(QStringLiteral("reload")); if (!toolbar->actions().contains(homeAction)) { toolbar->insertAction(reloadAction, homeAction); } // reset the lafbox to not follow the width of the splitter lafBox->setColumnMinimumWidth(0, 0); } static_cast(q->actionCollection()->action(QStringLiteral("toggleSpeedbar")))->setChecked(show); // if we don't show the places panel, at least show the places menu urlNavigator->setPlacesSelectorVisible(!show); } void KFileWidgetPrivate::_k_toggleBookmarks(bool show) { if (show) { if (bookmarkHandler) { return; } bookmarkHandler = new KFileBookmarkHandler(q); q->connect(bookmarkHandler, SIGNAL(openUrl(QString)), SLOT(_k_enterUrl(QString))); bookmarkButton = new KActionMenu(QIcon::fromTheme(QStringLiteral("bookmarks")), i18n("Bookmarks"), q); bookmarkButton->setDelayed(false); q->actionCollection()->addAction(QStringLiteral("bookmark"), bookmarkButton); bookmarkButton->setMenu(bookmarkHandler->menu()); bookmarkButton->setWhatsThis(i18n("This button allows you to bookmark specific locations. " "Click on this button to open the bookmark menu where you may add, " "edit or select a bookmark.

" "These bookmarks are specific to the file dialog, but otherwise operate " "like bookmarks elsewhere in KDE.
")); toolbar->addAction(bookmarkButton); } else if (bookmarkHandler) { delete bookmarkHandler; bookmarkHandler = nullptr; delete bookmarkButton; bookmarkButton = nullptr; } static_cast(q->actionCollection()->action(QStringLiteral("toggleBookmarks")))->setChecked(show); } // static, overloaded QUrl KFileWidget::getStartUrl(const QUrl &startDir, QString &recentDirClass) { QString fileName; // result discarded return getStartUrl(startDir, recentDirClass, fileName); } // static, overloaded QUrl KFileWidget::getStartUrl(const QUrl &startDir, QString &recentDirClass, QString &fileName) { recentDirClass.clear(); fileName.clear(); QUrl ret; bool useDefaultStartDir = startDir.isEmpty(); if (!useDefaultStartDir) { if (startDir.scheme() == QLatin1String("kfiledialog")) { // The startDir URL with this protocol may be in the format: // directory() fileName() // 1. kfiledialog:///keyword "/" keyword // 2. kfiledialog:///keyword?global "/" keyword // 3. kfiledialog:///keyword/ "/" keyword // 4. kfiledialog:///keyword/?global "/" keyword // 5. kfiledialog:///keyword/filename /keyword filename // 6. kfiledialog:///keyword/filename?global /keyword filename QString keyword; QString urlDir = startDir.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).path(); QString urlFile = startDir.fileName(); if (urlDir == QLatin1String("/")) { // '1'..'4' above keyword = urlFile; fileName.clear(); } else { // '5' or '6' above keyword = urlDir.mid(1); fileName = urlFile; } if (startDir.query() == QLatin1String("global")) { recentDirClass = QStringLiteral("::%1").arg(keyword); } else { recentDirClass = QStringLiteral(":%1").arg(keyword); } ret = QUrl::fromLocalFile(KRecentDirs::dir(recentDirClass)); } else { // not special "kfiledialog" URL // "foo.png" only gives us a file name, the default start dir will be used. // "file:foo.png" (from KHTML/webkit, due to fromPath()) means the same // (and is the reason why we don't just use QUrl::isRelative()). // In all other cases (startDir contains a directory path, or has no // fileName for us anyway, such as smb://), startDir is indeed a dir url. if (!startDir.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).path().isEmpty() || startDir.fileName().isEmpty()) { // can use start directory ret = startDir; // will be checked by stat later // If we won't be able to list it (e.g. http), then use default if (!KProtocolManager::supportsListing(ret)) { useDefaultStartDir = true; fileName = startDir.fileName(); } } else { // file name only fileName = startDir.fileName(); useDefaultStartDir = true; } } } if (useDefaultStartDir) { if (lastDirectory()->isEmpty()) { *lastDirectory() = QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)); const QUrl home(QUrl::fromLocalFile(QDir::homePath())); // if there is no docpath set (== home dir), we prefer the current // directory over it. We also prefer the homedir when our CWD is // different from our homedirectory or when the document dir // does not exist if (lastDirectory()->adjusted(QUrl::StripTrailingSlash) == home.adjusted(QUrl::StripTrailingSlash) || QDir::currentPath() != QDir::homePath() || !QDir(lastDirectory()->toLocalFile()).exists()) { *lastDirectory() = QUrl::fromLocalFile(QDir::currentPath()); } } ret = *lastDirectory(); } // qDebug() << "for" << startDir << "->" << ret << "recentDirClass" << recentDirClass << "fileName" << fileName; return ret; } void KFileWidget::setStartDir(const QUrl &directory) { if (directory.isValid()) { *lastDirectory() = directory; } } void KFileWidgetPrivate::setNonExtSelection() { // Enhanced rename: Don't highlight the file extension. QString filename = locationEditCurrentText(); QMimeDatabase db; QString extension = db.suffixForFileName(filename); if (!extension.isEmpty()) { locationEdit->lineEdit()->setSelection(0, filename.length() - extension.length() - 1); } else { int lastDot = filename.lastIndexOf(QLatin1Char('.')); if (lastDot > 0) { locationEdit->lineEdit()->setSelection(0, lastDot); } else { locationEdit->lineEdit()->selectAll(); } } } KToolBar *KFileWidget::toolBar() const { return d->toolbar; } void KFileWidget::setCustomWidget(QWidget *widget) { delete d->bottomCustomWidget; d->bottomCustomWidget = widget; // add it to the dialog, below the filter list box. // Change the parent so that this widget is a child of the main widget d->bottomCustomWidget->setParent(this); d->vbox->addWidget(d->bottomCustomWidget); //d->vbox->addSpacing(3); // can't do this every time... // FIXME: This should adjust the tab orders so that the custom widget // comes after the Cancel button. The code appears to do this, but the result // somehow screws up the tab order of the file path combo box. Not a major // problem, but ideally the tab order with a custom widget should be // the same as the order without one. setTabOrder(d->cancelButton, d->bottomCustomWidget); setTabOrder(d->bottomCustomWidget, d->urlNavigator); } void KFileWidget::setCustomWidget(const QString &text, QWidget *widget) { delete d->labeledCustomWidget; d->labeledCustomWidget = widget; QLabel *label = new QLabel(text, this); label->setAlignment(Qt::AlignRight); d->lafBox->addWidget(label, 2, 0, Qt::AlignVCenter); d->lafBox->addWidget(widget, 2, 1, Qt::AlignVCenter); } KDirOperator *KFileWidget::dirOperator() { return d->ops; } void KFileWidget::readConfig(KConfigGroup &group) { d->configGroup = group; d->readViewConfig(); d->readRecentFiles(); } QString KFileWidgetPrivate::locationEditCurrentText() const { return QDir::fromNativeSeparators(locationEdit->currentText()); } QUrl KFileWidgetPrivate::mostLocalUrl(const QUrl &url) { if (url.isLocalFile()) { return url; } KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo); KJobWidgets::setWindow(statJob, q); bool res = statJob->exec(); if (!res) { return url; } const QString path = statJob->statResult().stringValue(KIO::UDSEntry::UDS_LOCAL_PATH); if (!path.isEmpty()) { QUrl newUrl; newUrl.setPath(path); return newUrl; } return url; } void KFileWidgetPrivate::setInlinePreviewShown(bool show) { ops->setInlinePreviewShown(show); } void KFileWidget::setConfirmOverwrite(bool enable) { d->confirmOverwrite = enable; } void KFileWidget::setInlinePreviewShown(bool show) { d->setInlinePreviewShown(show); } QSize KFileWidget::dialogSizeHint() const { int fontSize = fontMetrics().height(); QSize goodSize(48 * fontSize, 30 * fontSize); QSize screenSize = QApplication::desktop()->availableGeometry(this).size(); QSize minSize(screenSize / 2); QSize maxSize(screenSize * qreal(0.9)); return (goodSize.expandedTo(minSize).boundedTo(maxSize)); } void KFileWidget::setViewMode(KFile::FileView mode) { d->ops->setView(mode); d->hasView = true; } void KFileWidget::setSupportedSchemes(const QStringList &schemes) { d->model->setSupportedSchemes(schemes); d->ops->setSupportedSchemes(schemes); d->urlNavigator->setCustomProtocols(schemes); } QStringList KFileWidget::supportedSchemes() const { return d->model->supportedSchemes(); } #include "moc_kfilewidget.cpp" diff --git a/src/filewidgets/kfilewidget.h b/src/filewidgets/kfilewidget.h index 3a44dc15..eb17417d 100644 --- a/src/filewidgets/kfilewidget.h +++ b/src/filewidgets/kfilewidget.h @@ -1,618 +1,619 @@ // -*- c++ -*- /* This file is part of the KDE libraries Copyright (C) 1997, 1998 Richard Moore 1998 Stephan Kulow 1998 Daniel Grana 2000,2001 Carsten Pfeiffer 2001 Frerich Raabe 2007 David Faure 2008 Rafael Fernández López This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KFILEWIDGET_H #define KFILEWIDGET_H #include "kiofilewidgets_export.h" #include "kfile.h" #include class QUrl; class QPushButton; class KActionCollection; class KToolBar; class KFileWidgetPrivate; class KUrlComboBox; class KFileFilterCombo; class KPreviewWidgetBase; class QMimeType; class KConfigGroup; class KJob; class KFileItem; class KDirOperator; /** * @class KFileWidget kfilewidget.h * * File selector widget. * * This is the contents of the KDE file dialog, without the actual QDialog around it. * It can be embedded directly into applications. */ class KIOFILEWIDGETS_EXPORT KFileWidget : public QWidget { Q_OBJECT public: /** * Constructs a file selector widget. * * @param startDir This can either be: * @li An empty URL (QUrl()) to start in the current working directory, * or the last directory where a file has been selected. * @li The path or URL of a starting directory. * @li An initial file name to select, with the starting directory being * the current working directory or the last directory where a file * has been selected. * @li The path or URL of a file, specifying both the starting directory and * an initially selected file name. * @li A URL of the form @c kfiledialog:///<keyword> to start in the * directory last used by a filedialog in the same application that * specified the same keyword. * @li A URL of the form @c kfiledialog:///<keyword>/<filename> * to start in the directory last used by a filedialog in the same * application that specified the same keyword, and to initially * select the specified filename. * @li A URL of the form @c kfiledialog:///<keyword>?global to start * in the directory last used by a filedialog in any application that * specified the same keyword. * @li A URL of the form @c kfiledialog:///<keyword>/<filename>?global * to start in the directory last used by a filedialog in any * application that specified the same keyword, and to initially * select the specified filename. * * @param parent The parent widget of this widget * */ explicit KFileWidget(const QUrl &startDir, QWidget *parent = nullptr); /** * Destructor */ ~KFileWidget() override; /** * Defines some default behavior of the filedialog. * E.g. in mode @p Opening and @p Saving, the selected files/urls will * be added to the "recent documents" list. The Saving mode also implies * setKeepLocation() being set. * * @p Other means that no default actions are performed. * * @see setOperationMode * @see operationMode */ enum OperationMode { Other = 0, Opening, Saving }; /** * @returns The selected fully qualified filename. */ QUrl selectedUrl() const; /** * @returns The list of selected URLs. */ QList selectedUrls() const; /** * @returns the currently shown directory. */ QUrl baseUrl() const; /** * Returns the full path of the selected file in the local filesystem. * (Local files only) */ QString selectedFile() const; /** * Returns a list of all selected local files. */ QStringList selectedFiles() const; /** * Sets the directory to view. * * @param url URL to show. * @param clearforward Indicates whether the forward queue * should be cleared. */ void setUrl(const QUrl &url, bool clearforward = true); #if !defined(KIOFILEWIDGETS_NO_DEPRECATED) && !defined(DOXYGEN_SHOULD_SKIP_THIS) /** * Sets the file to preselect to @p pathOrUrl * * This method handles absolute paths (on Unix, but probably not correctly on Windows) * and absolute URLs as strings (but for those you should use setSelectedUrl instead). * * This method does not work with relative paths (filenames) * (it would misinterpret a ':' or a '#' in the filename). * * @deprecated since 5.33, use setSelectedUrl instead, after ensuring that * construct the QUrl correctly (e.g. use fromLocalFile for local paths). */ KIOFILEWIDGETS_DEPRECATED void setSelection(const QString &pathOrUrl); #endif /** * Sets the URL to preselect to @p url * * This method handles absolute URLs (remember to use fromLocalFile for local paths). * It also handles relative URLs, which you should construct like this: * QUrl relativeUrl; relativeUrl.setPath(fileName); * * @since 5.33 */ void setSelectedUrl(const QUrl &url); /** * Sets the operational mode of the filedialog to @p Saving, @p Opening * or @p Other. This will set some flags that are specific to loading * or saving files. E.g. setKeepLocation() makes mostly sense for * a save-as dialog. So setOperationMode( KFileWidget::Saving ); sets * setKeepLocation for example. * * The mode @p Saving, together with a default filter set via * setMimeFilter() will make the filter combobox read-only. * * The default mode is @p Opening. * * Call this method right after instantiating KFileWidget. * * @see operationMode * @see KFileWidget::OperationMode */ void setOperationMode(OperationMode); /** * @returns the current operation mode, Opening, Saving or Other. Default * is Other. * * @see operationMode * @see KFileWidget::OperationMode */ OperationMode operationMode() const; /** * Sets whether the filename/url should be kept when changing directories. * This is for example useful when having a predefined filename where * the full path for that file is searched. * * This is implicitly set when operationMode() is KFileWidget::Saving * * getSaveFileName() and getSaveUrl() set this to true by default, so that * you can type in the filename and change the directory without having * to type the name again. */ void setKeepLocation(bool keep); /** * @returns whether the contents of the location edit are kept when * changing directories. */ bool keepsLocation() const; /** * Sets the filter to be used to @p filter. * * You can set more * filters for the user to select separated by '\n'. Every * filter entry is defined through namefilter|text to display. * If no | is found in the expression, just the namefilter is * shown. Examples: * * \code * kfile->setFilter("*.cpp|C++ Source Files\n*.h|Header files"); * kfile->setFilter("*.cpp"); * kfile->setFilter("*.cpp|Sources (*.cpp)"); * kfile->setFilter("*.cpp|" + i18n("Sources (*.cpp)")); * kfile->setFilter("*.cpp *.cc *.C|C++ Source Files\n*.h *.H|Header files"); * \endcode * * Note: The text to display is not parsed in any way. So, if you * want to show the suffix to select by a specific filter, you must * repeat it. * * If the filter contains an unescaped '/', a mimetype-filter is assumed. * If you would like a '/' visible in your filter it can be escaped with * a '\'. You can specify multiple mimetypes like this (separated with * space): * * \code * kfile->setFilter( "image/png text/html text/plain" ); * kfile->setFilter( "*.cue|CUE\\/BIN Files (*.cue)" ); * \endcode * * @see filterChanged * @see setMimeFilter */ void setFilter(const QString &filter); /** * Returns the current filter as entered by the user or one of the * predefined set via setFilter(). * * @see setFilter() * @see filterChanged() */ QString currentFilter() const; /** * Returns the mimetype for the desired output format. * * This is only valid if setFilterMimeType() has been called * previously. * * @see setFilterMimeType() */ QMimeType currentFilterMimeType(); /** * Sets the filter up to specify the output type. * * @param types a list of mimetypes that can be used as output format * @param defaultType the default mimetype to use as output format, if any. * If @p defaultType is set, it will be set as the current item. * Otherwise, a first item showing all the mimetypes will be created. * Typically, @p defaultType should be empty for loading and set for saving. * * Do not use in conjunction with setFilter() */ void setMimeFilter(const QStringList &types, const QString &defaultType = QString()); /** * The mimetype for the desired output format. * * This is only valid if setMimeFilter() has been called * previously. * * @see setMimeFilter() */ QString currentMimeFilter() const; /** * Clears any mime- or namefilter. Does not reload the directory. */ void clearFilter(); /** * Adds a preview widget and enters the preview mode. * * In this mode the dialog is split and the right part contains your * preview widget. * * Ownership is transferred to KFileWidget. You need to create the * preview-widget with "new", i.e. on the heap. * * @param w The widget to be used for the preview. */ void setPreviewWidget(KPreviewWidgetBase *w); /** * Sets the mode of the dialog. * * The mode is defined as (in kfile.h): * \code * enum Mode { * File = 1, * Directory = 2, * Files = 4, * ExistingOnly = 8, * LocalOnly = 16 * }; * \endcode * You can OR the values, e.g. * \code * KFile::Modes mode = KFile::Files | * KFile::ExistingOnly | * KFile::LocalOnly ); * setMode( mode ); * \endcode */ void setMode(KFile::Modes m); /** * Returns the mode of the filedialog. * @see setMode() */ KFile::Modes mode() const; /** * Sets the text to be displayed in front of the selection. * * The default is "Location". * Most useful if you want to make clear what * the location is used for. */ void setLocationLabel(const QString &text); /** * Returns a pointer to the toolbar. * */ KToolBar *toolBar() const; /** * @returns a pointer to the OK-Button in the filedialog. * Note that the button is hidden and unconnected when using KFileWidget alone; * KFileDialog shows it and connects to it. */ QPushButton *okButton() const; /** * @returns a pointer to the Cancel-Button in the filedialog. * Note that the button is hidden and unconnected when using KFileWidget alone; * KFileDialog shows it and connects to it. */ QPushButton *cancelButton() const; /** * @returns the combobox used to type the filename or full location of the file. */ KUrlComboBox *locationEdit() const; /** * @returns the combobox that contains the filters */ KFileFilterCombo *filterWidget() const; /** * @returns a pointer to the action collection, holding all the used * KActions. */ KActionCollection *actionCollection() const; /** * This method implements the logic to determine the user's default directory * to be listed. E.g. the documents directory, home directory or a recently * used directory. * @param startDir A URL specifying the initial directory, or using the * @c kfiledialog:/// syntax to specify a last used * directory. If this URL specifies a file name, it is * ignored. Refer to the KFileWidget::KFileWidget() * documentation for the @c kfiledialog:/// URL syntax. * @param recentDirClass If the @c kfiledialog:/// syntax is used, this * will return the string to be passed to KRecentDirs::dir() and * KRecentDirs::add(). * @return The URL that should be listed by default (e.g. by KFileDialog or * KDirSelectDialog). * @see KFileWidget::KFileWidget() */ static QUrl getStartUrl(const QUrl &startDir, QString &recentDirClass); /** * Similar to getStartUrl(const QUrl& startDir,QString& recentDirClass), * but allows both the recent start directory keyword and a suggested file name * to be returned. * @param startDir A URL specifying the initial directory and/or filename, * or using the @c kfiledialog:/// syntax to specify a * last used location. * Refer to the KFileWidget::KFileWidget() * documentation for the @c kfiledialog:/// URL syntax. * @param recentDirClass If the @c kfiledialog:/// syntax is used, this * will return the string to be passed to KRecentDirs::dir() and * KRecentDirs::add(). * @param fileName The suggested file name, if specified as part of the * @p StartDir URL. * @return The URL that should be listed by default (e.g. by KFileDialog or * KDirSelectDialog). * * @see KFileWidget::KFileWidget() * @since 4.3 */ static QUrl getStartUrl(const QUrl &startDir, QString &recentDirClass, QString &fileName); /** * @internal * Used by KDirSelectDialog to share the dialog's start directory. */ static void setStartDir(const QUrl &directory); /** * Set a custom widget that should be added to the file dialog. * @param widget A widget, or a widget of widgets, for displaying custom * data in the file widget. This can be used, for example, to * display a check box with the caption "Open as read-only". * When creating this widget, you don't need to specify a parent, * since the widget's parent will be set automatically by KFileWidget. */ void setCustomWidget(QWidget *widget); /** * Sets a custom widget that should be added below the location and the filter * editors. * @param text Label of the custom widget, which is displayed below the labels * "Location:" and "Filter:". * @param widget Any kind of widget, but preferable a combo box or a line editor * to be compliant with the location and filter layout. * When creating this widget, you don't need to specify a parent, * since the widget's parent will be set automatically by KFileWidget. */ void setCustomWidget(const QString &text, QWidget *widget); /** * Sets whether the user should be asked for confirmation * when an overwrite might occur. * * @param enable Set this to true to enable checking. * @since 4.2 */ void setConfirmOverwrite(bool enable); /** * Forces the inline previews to be shown or hidden, depending on @p show. * * @param show Whether to show inline previews or not. * @since 4.2 */ void setInlinePreviewShown(bool show); /** * Provides a size hint, useful for dialogs that embed the widget. * * @return a QSize, calculated to be optimal for a dialog. * @since 5.0 */ QSize dialogSizeHint() const; /** * Sets how the view should be displayed. * * @see KFile::FileView * @since 5.0 */ void setViewMode(KFile::FileView mode); /** * Reimplemented */ QSize sizeHint() const override; /** * Set the URL schemes that the file widget should allow navigating to. * * If the returned list is empty, all schemes are supported. * * @sa QFileDialog::setSupportedSchemes * @since 5.43 */ void setSupportedSchemes(const QStringList &schemes); /** * Returns the URL schemes that the file widget should allow navigating to. * * If the returned list is empty, all schemes are supported. Examples for * schemes are @c "file" or @c "ftp". * * @sa QFileDialog::supportedSchemes * @since 5.43 */ QStringList supportedSchemes() const; public Q_SLOTS: /** * Called when clicking ok (when this widget is used in KFileDialog) * Might or might not call accept(). */ void slotOk(); void accept(); void slotCancel(); protected: void resizeEvent(QResizeEvent *event) override; void showEvent(QShowEvent *event) override; bool eventFilter(QObject *watched, QEvent *event) override; Q_SIGNALS: /** * Emitted when the user selects a file. It is only emitted in single- * selection mode. The best way to get notified about selected file(s) * is to connect to the okClicked() signal inherited from KDialog * and call selectedFile(), selectedFiles(), * selectedUrl() or selectedUrls(). * * \since 4.4 */ void fileSelected(const QUrl &); /** * Emitted when the user highlights a file. * \since 4.4 */ void fileHighlighted(const QUrl &); /** * Emitted when the user highlights one or more files in multiselection mode. * * Note: fileHighlighted() or fileSelected() are @em not * emitted in multiselection mode. You may use selectedItems() to * ask for the current highlighted items. * @see fileSelected */ void selectionChanged(); /** * Emitted when the filter changed, i.e. the user entered an own filter * or chose one of the predefined set via setFilter(). * * @param filter contains the new filter (only the extension part, * not the explanation), i.e. "*.cpp" or "*.cpp *.cc". * * @see setFilter() * @see currentFilter() */ void filterChanged(const QString &filter); /** * Emitted by slotOk() (directly or asynchronously) once everything has * been done. Should be used by the caller to call accept(). */ void accepted(); public: /** * @returns the KDirOperator used to navigate the filesystem * @since 4.3 */ KDirOperator *dirOperator(); /** * reads the configuration for this widget from the given config group * @param group the KConfigGroup to read from * @since 4.4 */ void readConfig(KConfigGroup &group); private: friend class KFileWidgetPrivate; KFileWidgetPrivate *const d; Q_PRIVATE_SLOT(d, void _k_slotLocationChanged(const QString &)) Q_PRIVATE_SLOT(d, void _k_urlEntered(const QUrl &)) Q_PRIVATE_SLOT(d, void _k_enterUrl(const QUrl &)) Q_PRIVATE_SLOT(d, void _k_enterUrl(const QString &)) Q_PRIVATE_SLOT(d, void _k_locationAccepted(const QString &)) Q_PRIVATE_SLOT(d, void _k_slotFilterChanged()) Q_PRIVATE_SLOT(d, void _k_fileHighlighted(const KFileItem &)) Q_PRIVATE_SLOT(d, void _k_fileSelected(const KFileItem &)) Q_PRIVATE_SLOT(d, void _k_slotLoadingFinished()) Q_PRIVATE_SLOT(d, void _k_fileCompletion(const QString &)) Q_PRIVATE_SLOT(d, void _k_toggleSpeedbar(bool)) Q_PRIVATE_SLOT(d, void _k_toggleBookmarks(bool)) Q_PRIVATE_SLOT(d, void _k_slotAutoSelectExtClicked()) Q_PRIVATE_SLOT(d, void _k_placesViewSplitterMoved(int, int)) Q_PRIVATE_SLOT(d, void _k_activateUrlNavigator()) Q_PRIVATE_SLOT(d, void _k_zoomOutIconsSize()) Q_PRIVATE_SLOT(d, void _k_zoomInIconsSize()) Q_PRIVATE_SLOT(d, void _k_slotIconSizeSliderMoved(int)) Q_PRIVATE_SLOT(d, void _k_slotIconSizeChanged(int)) Q_PRIVATE_SLOT(d, void _k_slotViewDoubleClicked(const QModelIndex&)) + Q_PRIVATE_SLOT(d, void _k_slotViewKeyEnterReturnPressed()) }; #endif diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b9ea79f4..02812e2f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,44 +1,45 @@ include(ECMMarkAsTest) find_package(KF5XmlGui ${KF5_DEP_VERSION} REQUIRED) remove_definitions(-DQT_NO_CAST_FROM_ASCII) remove_definitions(-DQT_NO_CAST_FROM_BYTEARRAY) macro(KIOWIDGETS_EXECUTABLE_TESTS) foreach(_testname ${ARGN}) add_executable(${_testname} ${_testname}.cpp) target_link_libraries(${_testname} KF5::KIOCore KF5::KIOWidgets KF5::KIOFileWidgets Qt5::Test KF5::WidgetsAddons KF5::IconThemes) ecm_mark_as_test(${_testname}) endforeach() endmacro(KIOWIDGETS_EXECUTABLE_TESTS) KIOWIDGETS_EXECUTABLE_TESTS( getalltest kruntest kioslavetest kopenwithtest kencodingfiledialogtest_gui kdirmodeltest_gui kdirlistertest_gui kfilecustomdialogtest_gui previewtest kurlrequestertest_gui kpropertiesdialogtest kmountpoint_debug listjobtest listrecursivetest kionetrctest ksycocaupdatetest udsentrybenchmark kurlnavigatortest_gui kprotocolinfo_dumper kfilewidgettest_gui + kfilewidgettest_saving_gui runapplication ) diff --git a/tests/kfilewidgettest_saving_gui.cpp b/tests/kfilewidgettest_saving_gui.cpp new file mode 100644 index 00000000..3fad286c --- /dev/null +++ b/tests/kfilewidgettest_saving_gui.cpp @@ -0,0 +1,58 @@ +/* This file is part of the KDE libraries + Copyright (C) 2019 Méven Car + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + QApplication app(argc, argv); + + KFileWidget* fileWidget = new KFileWidget(QUrl(QStringLiteral("kfiledialog:///SaveDialog")), nullptr); + fileWidget->setOperationMode(KFileWidget::Saving); + fileWidget->setMode(KFile::File); + fileWidget->setAttribute(Qt::WA_DeleteOnClose); + + fileWidget->okButton()->show(); + fileWidget->cancelButton()->show(); + app.connect(fileWidget->okButton(), &QPushButton::clicked, fileWidget, &KFileWidget::slotOk); + app.connect(fileWidget->cancelButton(), &QPushButton::clicked, fileWidget, [&app, fileWidget]() { + qDebug() << "canceled"; + fileWidget->slotCancel(); + app.exit(); + }); + + app.connect(fileWidget, &KFileWidget::accepted, fileWidget, [&app, fileWidget]() { + qDebug() << "accepted"; + fileWidget->accept(); + qDebug() << fileWidget->selectedFile(); + qDebug() << fileWidget->selectedUrl(); + qDebug() << fileWidget->selectedFiles(); + qDebug() << fileWidget->selectedUrls(); + app.exit(); + }); + + fileWidget->show(); + + return app.exec(); +} +