Advance Wayland and KDE package bring-up

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-04-14 10:51:06 +01:00
parent 51f3c21121
commit cf12defd28
15214 changed files with 20594243 additions and 269 deletions
@@ -0,0 +1,153 @@
find_package(KF6Bookmarks ${KF_DEP_VERSION} REQUIRED)
configure_file(config-kiofilewidgets.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kiofilewidgets.h)
add_library(KF6KIOFileWidgets)
add_library(KF6::KIOFileWidgets ALIAS KF6KIOFileWidgets)
set_target_properties(KF6KIOFileWidgets PROPERTIES
VERSION ${KIO_VERSION}
SOVERSION ${KIO_SOVERSION}
EXPORT_NAME KIOFileWidgets
)
target_sources(KF6KIOFileWidgets PRIVATE
kfilemetapreview.cpp
kimagefilepreview.cpp
kpreviewwidgetbase.cpp
defaultviewadapter.cpp
kdiroperator.cpp
kdiroperatordetailview.cpp
kdiroperatoriconview.cpp
kdirsortfilterproxymodel.cpp #used in combination with kdirmodel.cpp
kencodingfiledialog.cpp
kfilebookmarkhandler.cpp
kfilecopytomenu.cpp
kfilecustomdialog.cpp
kfilefiltercombo.cpp
kfilewidget.cpp
kfilewidgetdocktitlebar.cpp
kfileplacesitem.cpp
kfileplacesmodel.cpp
kfileplacesview.cpp
kfileplaceeditdialog.cpp
kfilepreviewgenerator.cpp
knameandurlinputdialog.cpp
knewfilemenu.cpp
kurlnavigatordropdownbutton.cpp
kurlnavigatorbuttonbase.cpp
kurlnavigatorbutton.cpp
kurlnavigatorplacesselector.cpp
kurlnavigatorschemecombo.cpp
kurlnavigatortogglebutton.cpp
kurlnavigator.cpp
kurlnavigatormenu.cpp
kurlnavigatorpathselectoreventfilter.cpp
../new_file_templates/templates.qrc
)
ecm_qt_export_logging_category(
IDENTIFIER KFILEWIDGETS_LOG
CATEGORY_NAME kf.kio.filewidgets
OLD_CATEGORY_NAMES kf5.kio.filewidgets
DESCRIPTION "KFileWidgets (KIO)"
EXPORT KIO
)
ecm_qt_export_logging_category(
IDENTIFIER KIO_KFILEWIDGETS_FW
CATEGORY_NAME kf.kio.filewidgets.kfilewidget
OLD_CATEGORY_NAMES kf5.kio.filewidgets.kfilewidget
DEFAULT_SEVERITY Info
DESCRIPTION "KFileWidgets (KIO)"
EXPORT KIO
)
ecm_qt_declare_logging_category(KF6KIOFileWidgets
HEADER kfilefiltercombo_debug.h
IDENTIFIER KIO_KFILEWIDGETS_KFILEFILTERCOMBO
CATEGORY_NAME kf.kio.filewidgets.kfilefiltercombo
OLD_CATEGORY_NAMES kf5.kio.filewidgets.kfilefiltercombo
DEFAULT_SEVERITY Warning
DESCRIPTION "KFileFilterCombo (KIO)"
EXPORT KIO
)
ecm_qt_declare_logging_category(KF6KIOFileWidgets
HEADER kfilewidgets_debug.h
IDENTIFIER KFILEWIDGETS_LOG
CATEGORY_NAME kf.kio.filewidgets.kfilewidgets
OLD_CATEGORY_NAMES kf5.kio.filewidgets.kfilewidgets
DEFAULT_SEVERITY Warning
DESCRIPTION "KFileWidgets (KIO)"
EXPORT KIO
)
ecm_generate_export_header(KF6KIOFileWidgets
BASE_NAME KIOFileWidgets
GROUP_BASE_NAME KF
VERSION ${KF_VERSION}
USE_VERSION_HEADER
VERSION_BASE_NAME KIO
DEPRECATED_BASE_VERSION 0
DEPRECATION_VERSIONS 6.3
EXCLUDE_DEPRECATED_BEFORE_AND_AT ${EXCLUDE_DEPRECATED_BEFORE_AND_AT}
)
target_include_directories(KF6KIOFileWidgets INTERFACE "$<INSTALL_INTERFACE:${KDE_INSTALL_INCLUDEDIR_KF}/KIOFileWidgets>")
target_link_libraries(KF6KIOFileWidgets
PUBLIC
KF6::KIOWidgets
KF6::Bookmarks # in KFilePlacesModel's API
KF6::ItemViews # kdirsortfilterproxymodel
KF6::Solid # KFilePlacesModel/KFilePlacesView
PRIVATE
KF6::GuiAddons # KIconUtils
KF6::IconThemes # KIconLoader
KF6::IconWidgets # KIconButton
KF6::I18n
KF6::ConfigGui
KF6::BookmarksWidgets
KF6::ColorScheme
)
if (Qt6Core_VERSION VERSION_LESS 6.7.0)
target_link_libraries(KF6KIOFileWidgets PRIVATE Qt6::Core5Compat)
endif()
ecm_generate_headers(KIOFileWidgets_HEADERS
HEADER_NAMES
KAbstractViewAdapter
KImageFilePreview
KPreviewWidgetBase
KDirOperator
KDirSortFilterProxyModel
KFileCopyToMenu
KFileCustomDialog
KFileFilterCombo
KFilePlaceEditDialog
KFilePlacesModel
KFilePlacesView
KFilePreviewGenerator
KFileWidget
KUrlNavigator
KNewFileMenu
KNameAndUrlInputDialog
KEncodingFileDialog
REQUIRED_HEADERS KIOFileWidgets_HEADERS
)
install(TARGETS KF6KIOFileWidgets EXPORT KF6KIOTargets ${KF_INSTALL_TARGETS_DEFAULT_ARGS})
install(FILES
${KIOFileWidgets_HEADERS}
${CMAKE_CURRENT_BINARY_DIR}/kiofilewidgets_export.h
DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF}/KIOFileWidgets COMPONENT Devel)
# make available to ecm_add_qch in parent folder
set(KIOFileWidgets_QCH_SOURCES ${KIOFileWidgets_HEADERS} PARENT_SCOPE)
@@ -0,0 +1,36 @@
/** @mainpage File Selection Widgets
@section purpose Purpose
The KIOFileWidgets library provides the file selection dialog and its components.
@section desc Description
The file dialog is available to all kde applications by using the QFileDialog class.
The KDE QPA theme plugin uses a KFileDialog automatically.
The KIOFileWidgets library provides KFileWidget, which is the main widget in the file dialog, as well as
the KDirOperator widget which contains the file views, and other components of the file dialog.
Applications only need to link to KIOFileWidgets if they need direct access to KFileWidget
or KDirOperator or to the other widgets used by the file dialog, either in order to
modify the behavior of the file dialog, or to reuse its components into the application itself.
@authors
Stephan Kulow<br>
Carsten Pfeiffer<br>
Peter Penz<br>
Aaron J. Seigo<br>
and others
@maintainers
David Faure
@licenses
@lgpl
*/
// DOXYGEN_REFERENCES = KIOCore KIOWidgets
// DOXYGEN_SET_PROJECT_NAME = KIOFileWidgets
// vim:ts=4:sw=4:expandtab:filetype=doxygen
@@ -0,0 +1,35 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 1999 Stephan Kulow <coolo@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef DEFAULTS_KFILE_H
#define DEFAULTS_KFILE_H
#include <QLatin1String>
static const bool DefaultShowHidden = false;
static const bool DefaultDirsFirst = true;
static const bool DefaultHiddenFilesLast = false;
static const bool DefaultSortReversed = false;
static constexpr int DefaultRecentURLsNumber = 15;
static const bool DefaultAutoSelectExtChecked = true;
static const QLatin1String ConfigGroup("KFileDialog Settings");
static const QLatin1String RecentURLs("Recent URLs");
static const QLatin1String RecentFiles("Recent Files");
static const QLatin1String RecentURLsNumber("Maximum of recent URLs");
static const QLatin1String RecentFilesNumber("Maximum of recent files");
static const QLatin1String PathComboCompletionMode("PathCombo Completionmode");
static const QLatin1String LocationComboCompletionMode("LocationCombo Completionmode");
static const QLatin1String ShowSpeedbar("Show Speedbar");
static const QLatin1String SpeedbarWidth("Speedbar Width");
static const QLatin1String ShowBookmarks("Show Bookmarks");
static const QLatin1String AutoSelectExtChecked("Automatically select filename extension");
static const QLatin1String BreadcrumbNavigation("Breadcrumb Navigation");
static const QLatin1String ShowFullPath("Show Full Path");
static const QLatin1String PlacesIconsAutoresize("Places Icons Auto-resize");
static const QLatin1String PlacesIconsStaticSize("Places Icons Static Size");
#endif
@@ -0,0 +1,55 @@
/*
SPDX-FileCopyrightText: 2008 Peter Penz <peter.penz@gmx.at>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "defaultviewadapter_p.h"
#include <QAbstractItemView>
#include <QScrollBar>
namespace KIO
{
DefaultViewAdapter::DefaultViewAdapter(QAbstractItemView *view, QObject *parent)
: KAbstractViewAdapter(parent)
, m_view(view)
{
}
QAbstractItemModel *DefaultViewAdapter::model() const
{
return m_view->model();
}
QSize DefaultViewAdapter::iconSize() const
{
return m_view->iconSize();
}
QPalette DefaultViewAdapter::palette() const
{
return m_view->palette();
}
QRect DefaultViewAdapter::visibleArea() const
{
return m_view->viewport()->rect();
}
QRect DefaultViewAdapter::visualRect(const QModelIndex &index) const
{
return m_view->visualRect(index);
}
void DefaultViewAdapter::connect(Signal signal, QObject *receiver, const char *slot)
{
if (signal == ScrollBarValueChanged) {
QObject::connect(m_view->horizontalScrollBar(), SIGNAL(valueChanged(int)), receiver, slot);
QObject::connect(m_view->verticalScrollBar(), SIGNAL(valueChanged(int)), receiver, slot);
}
}
}
#include "moc_defaultviewadapter_p.cpp"
@@ -0,0 +1,38 @@
/*
SPDX-FileCopyrightText: 2008 Peter Penz <peter.penz@gmx.at>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef DEFAULTVIEWADAPTER_H
#define DEFAULTVIEWADAPTER_H
#include "kabstractviewadapter.h"
#include "kiofilewidgets_export.h"
class QAbstractItemView;
namespace KIO
{
/**
* Implementation of the view adapter for the default case when
* an instance of QAbstractItemView is used as view.
*/
class KIOFILEWIDGETS_EXPORT DefaultViewAdapter : public KAbstractViewAdapter
{
Q_OBJECT
public:
DefaultViewAdapter(QAbstractItemView *view, QObject *parent);
QAbstractItemModel *model() const override;
QSize iconSize() const override;
QPalette palette() const override;
QRect visibleArea() const override;
QRect visualRect(const QModelIndex &index) const override;
void connect(Signal signal, QObject *receiver, const char *slot) override;
private:
QAbstractItemView *m_view;
};
}
#endif
@@ -0,0 +1,83 @@
/*
SPDX-FileCopyrightText: 2008 Fredrik Höglund <fredrik@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KABSTRACTVIEWADAPTER_H
#define KABSTRACTVIEWADAPTER_H
#include "kiofilewidgets_export.h"
#include <QObject>
class QAbstractItemModel;
class QModelIndex;
class QPalette;
class QRect;
class QSize;
/*
* TODO KF6 Q_PROPERTY(QSize iconSize READ iconSize WRITE setIconSize NOTIFY iconSizeChanged)
* TODO KF6 virtual void setIconSize(const QSize &size);
* TODO KF6 iconSizeChanged();
*
* TODO KF6:
* KAbstractViewAdapter exists to allow KFilePreviewGenerator to be
* reused with new kinds of views. Unfortunately it doesn't cover
* all use cases that would be useful right now, in particular there
* are no change notifications for the properties it has getters for.
* This requires view implementations to e.g. call updateIcons() on
* the generator when the icon size changes, which means updating two
* entities (the generator and the adapter) instead of only one.
* In KF6 we should make iconSize a Q_PROPERTY with a virtual setter
* and a change notification signal, and make KFilePreviewGenerator
* listen to that signal.
* A related problem is that while the adapter is supposed to inter-
* face a view to the generator, it is sometimes the generator that
* is responsible for instantiating the adapter: KDirOperator in this
* framework uses the KFilePreviewGenerator constructor that doesn't
* take an adapter instance, which makes the generator instantiate a
* KIO::DefaultViewAdapter internally, which it doesn't expose to the
* outside. That means even when a setIconSize() is added,
* KDirOperator won't be able to call it on the adapter. This mis-
* design needs to be addressed as well so all change notifications
* can run through the adapter, also for the DefaultViewAdapter
* implementation (though for this specific example, perhaps Qt will
* one day give us a NOTIFY for QAbstractItemView::iconSize that the
* DefaultViewAdapter can use, obviating the need for KDirOperator
* to do anything except call setIconSize on its QAbstractItemView).
*/
/**
* @class KAbstractViewAdapter kabstractviewadapter.h <KAbstractViewAdapter>
*
* Interface used by KFilePreviewGenerator to generate previews
* for files. The interface allows KFilePreviewGenerator to be
* independent from the view implementation.
*/
class KIOFILEWIDGETS_EXPORT KAbstractViewAdapter : public QObject
{
public:
enum Signal {
ScrollBarValueChanged,
IconSizeChanged
};
KAbstractViewAdapter(QObject *parent)
: QObject(parent)
{
}
~KAbstractViewAdapter() override
{
}
virtual QAbstractItemModel *model() const = 0;
virtual QSize iconSize() const = 0;
virtual QPalette palette() const = 0;
virtual QRect visibleArea() const = 0;
virtual QRect visualRect(const QModelIndex &index) const = 0;
// TODO KF6 make this connect work with a PointerToMemberFunction/Functor
virtual void connect(Signal signal, QObject *receiver, const char *slot) = 0;
};
#endif
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,134 @@
/*
SPDX-FileCopyrightText: 2007 Peter Penz <peter.penz@gmx.at>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include "kdiroperatordetailview_p.h"
#include <kdirlister.h>
#include <kdirmodel.h>
#include <QApplication>
#include <QDragEnterEvent>
#include <QEvent>
#include <QHeaderView>
#include <QListView>
#include <QMimeData>
#include <QScrollBar>
KDirOperatorDetailView::KDirOperatorDetailView(QWidget *parent)
: QTreeView(parent)
, m_hideDetailColumns(false)
{
setRootIsDecorated(false);
setSortingEnabled(true);
setUniformRowHeights(true);
setDragDropMode(QListView::DragOnly);
setSelectionBehavior(QAbstractItemView::SelectRows);
setEditTriggers(QAbstractItemView::NoEditTriggers);
setVerticalScrollMode(QListView::ScrollPerPixel);
setHorizontalScrollMode(QListView::ScrollPerPixel);
const QFontMetrics metrics(viewport()->font());
const int singleStep = metrics.height() * QApplication::wheelScrollLines();
verticalScrollBar()->setSingleStep(singleStep);
horizontalScrollBar()->setSingleStep(singleStep);
}
KDirOperatorDetailView::~KDirOperatorDetailView()
{
}
bool KDirOperatorDetailView::setViewMode(KFile::FileView viewMode)
{
bool tree = false;
if (KFile::isDetailView(viewMode)) {
m_hideDetailColumns = false;
setAlternatingRowColors(true);
} else if (KFile::isTreeView(viewMode)) {
m_hideDetailColumns = true;
setAlternatingRowColors(false);
tree = true;
} else if (KFile::isDetailTreeView(viewMode)) {
m_hideDetailColumns = false;
setAlternatingRowColors(true);
tree = true;
} else {
return false;
}
setRootIsDecorated(tree);
setItemsExpandable(tree);
// This allows to have a horizontal scrollbar in case this view is used as
// a plain treeview instead of cutting off filenames, especially useful when
// using KDirOperator in horizontally limited parts of an app.
if (tree && m_hideDetailColumns) {
header()->setSectionResizeMode(QHeaderView::ResizeToContents);
} else {
header()->setSectionResizeMode(QHeaderView::Interactive);
}
return true;
}
void KDirOperatorDetailView::initViewItemOption(QStyleOptionViewItem *option) const
{
QTreeView::initViewItemOption(option);
option->textElideMode = Qt::ElideMiddle;
}
bool KDirOperatorDetailView::event(QEvent *event)
{
if (event->type() == QEvent::Polish) {
QHeaderView *headerView = header();
headerView->setSectionResizeMode(0, QHeaderView::Stretch);
headerView->setSectionResizeMode(1, QHeaderView::ResizeToContents);
headerView->setSectionResizeMode(2, QHeaderView::ResizeToContents);
headerView->setStretchLastSection(false);
headerView->setSectionsMovable(false);
setColumnHidden(KDirModel::Size, m_hideDetailColumns);
setColumnHidden(KDirModel::ModifiedTime, m_hideDetailColumns);
hideColumn(KDirModel::Type);
hideColumn(KDirModel::Permissions);
hideColumn(KDirModel::Owner);
hideColumn(KDirModel::Group);
} else if (event->type() == QEvent::UpdateRequest) {
// A wheel movement will scroll 4 items
if (model()->rowCount()) {
verticalScrollBar()->setSingleStep((sizeHintForRow(0) / 3) * 4);
}
}
return QTreeView::event(event);
}
void KDirOperatorDetailView::dragEnterEvent(QDragEnterEvent *event)
{
if (event->mimeData()->hasUrls()) {
event->acceptProposedAction();
}
}
void KDirOperatorDetailView::mousePressEvent(QMouseEvent *event)
{
QTreeView::mousePressEvent(event);
const QModelIndex index = indexAt(event->pos());
if (!index.isValid() || (index.column() != KDirModel::Name)) {
const Qt::KeyboardModifiers modifiers = QApplication::keyboardModifiers();
if (!(modifiers & Qt::ShiftModifier) && !(modifiers & Qt::ControlModifier)) {
clearSelection();
}
}
}
void KDirOperatorDetailView::currentChanged(const QModelIndex &current, const QModelIndex &previous)
{
QTreeView::currentChanged(current, previous);
}
#include "moc_kdiroperatordetailview_p.cpp"
@@ -0,0 +1,45 @@
/*
SPDX-FileCopyrightText: 2007 Peter Penz <peter.penz@gmx.at>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KDIROPERATORDETAILVIEW_P_H
#define KDIROPERATORDETAILVIEW_P_H
#include <QTreeView>
#include <kfile.h>
class QAbstractItemModel;
/**
* Default detail view for KDirOperator using
* custom resizing options and columns.
*/
class KDirOperatorDetailView : public QTreeView
{
Q_OBJECT
public:
explicit KDirOperatorDetailView(QWidget *parent = nullptr);
~KDirOperatorDetailView() override;
/**
* Displays either Detail, Tree or DetailTree modes.
*/
virtual bool setViewMode(KFile::FileView viewMode);
protected:
void initViewItemOption(QStyleOptionViewItem *option) const override;
bool event(QEvent *event) override;
void dragEnterEvent(QDragEnterEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
void currentChanged(const QModelIndex &current, const QModelIndex &previous) override;
private:
bool m_hideDetailColumns;
};
#endif
@@ -0,0 +1,150 @@
/*
SPDX-FileCopyrightText: 2007 Peter Penz <peter.penz@gmx.at>
SPDX-FileCopyrightText: 2019 Méven Car <meven.car@kdemail.net>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include "kdiroperatoriconview_p.h"
#include <QApplication>
#include <QDragEnterEvent>
#include <QMimeData>
#include <QScrollBar>
#include <KFileItemDelegate>
#include <KIconLoader>
KDirOperatorIconView::KDirOperatorIconView(QWidget *parent, QStyleOptionViewItem::Position aDecorationPosition)
: QListView(parent)
{
setViewMode(QListView::IconMode);
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));
decorationPosition = aDecorationPosition;
const QFontMetrics metrics(viewport()->font());
const int singleStep = metrics.height() * QApplication::wheelScrollLines();
verticalScrollBar()->setSingleStep(singleStep);
horizontalScrollBar()->setSingleStep(singleStep);
updateLayout();
connect(this, &QListView::iconSizeChanged, this, &KDirOperatorIconView::updateLayout);
}
KDirOperatorIconView::~KDirOperatorIconView()
{
}
void KDirOperatorIconView::resizeEvent(QResizeEvent *event)
{
Q_UNUSED(event);
updateLayout();
}
void KDirOperatorIconView::initViewItemOption(QStyleOptionViewItem *option) const
{
QListView::initViewItemOption(option);
option->showDecorationSelected = true;
option->textElideMode = Qt::ElideMiddle;
option->decorationPosition = decorationPosition;
if (option->decorationPosition == QStyleOptionViewItem::Left) {
option->displayAlignment = Qt::AlignLeft | Qt::AlignVCenter;
} else {
option->displayAlignment = Qt::AlignCenter;
}
}
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->angleDelta().y() != 0) {
QWheelEvent horizEvent(event->position(),
event->globalPosition(),
QPoint(event->pixelDelta().y(), 0),
QPoint(event->angleDelta().y(), 0),
event->buttons(),
event->modifiers(),
event->phase(),
event->inverted(),
event->source());
QApplication::sendEvent(horizontalScrollBar(), &horizEvent);
}
}
void KDirOperatorIconView::updateLayout()
{
if (decorationPosition == QStyleOptionViewItem::Position::Top) {
// Icons view
setFlow(QListView::LeftToRight);
const QFontMetrics metrics(viewport()->font());
const int height = iconSize().height() + metrics.height() * 2.5;
const int minWidth = qMax(height, metrics.height() * 5);
const int scrollBarWidth = 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 = contentsRect().width() - scrollBarWidth - 1 - 4 * 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);
setGridSize(itemSize);
KFileItemDelegate *delegate = qobject_cast<KFileItemDelegate *>(itemDelegate());
if (delegate) {
delegate->setMaximumSize(itemSize);
}
} else {
// compact view
setFlow(QListView::TopToBottom);
setGridSize(QSize());
KFileItemDelegate *delegate = qobject_cast<KFileItemDelegate *>(itemDelegate());
if (delegate) {
delegate->setMaximumSize(QSize());
}
}
}
void KDirOperatorIconView::setDecorationPosition(QStyleOptionViewItem::Position newDecorationPosition)
{
decorationPosition = newDecorationPosition;
updateLayout();
}
#include "moc_kdiroperatoriconview_p.cpp"
@@ -0,0 +1,39 @@
/*
SPDX-FileCopyrightText: 2007 Peter Penz <peter.penz@gmx.at>
SPDX-FileCopyrightText: 2019 Méven Car <meven.car@kdemail.net>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KDIROPERATORICONVIEW_P_H
#define KDIROPERATORICONVIEW_P_H
#include <QListView>
/**
* Default icon view for KDirOperator using
* custom view options.
*/
class KDirOperatorIconView : public QListView
{
Q_OBJECT
public:
KDirOperatorIconView(QWidget *parent = nullptr, QStyleOptionViewItem::Position decorationPosition = QStyleOptionViewItem::Position::Top);
~KDirOperatorIconView() override;
void setDecorationPosition(QStyleOptionViewItem::Position decorationPosition);
protected:
void initViewItemOption(QStyleOptionViewItem *option) const override;
void dragEnterEvent(QDragEnterEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
void wheelEvent(QWheelEvent *event) override;
void resizeEvent(QResizeEvent *event) override;
protected
Q_SLOT : void updateLayout();
private:
QStyleOptionViewItem::Position decorationPosition;
};
#endif // KDIROPERATORICONVIEW_P_H
@@ -0,0 +1,298 @@
/*
SPDX-FileCopyrightText: 2006 Peter Penz <peter.penz@gmx.at>
SPDX-FileCopyrightText: 2006 Dominic Battre <dominic@battre.de>
SPDX-FileCopyrightText: 2006 Martin Pool <mbp@canonical.com>
Separated from Dolphin by Nick Shaforostoff <shafff@ukr.net>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include "kdirsortfilterproxymodel.h"
#include "defaults-kfile.h"
#include <KConfigGroup>
#include <KLocalizedString>
#include <KSharedConfig>
#include <kdirmodel.h>
#include <kfileitem.h>
#include <QCollator>
class Q_DECL_HIDDEN KDirSortFilterProxyModel::KDirSortFilterProxyModelPrivate
{
public:
KDirSortFilterProxyModelPrivate();
int compare(const QString &, const QString &, Qt::CaseSensitivity caseSensitivity = Qt::CaseSensitive);
void slotNaturalSortingChanged();
bool m_sortFoldersFirst;
bool m_sortHiddenFilesLast;
bool m_naturalSorting;
QCollator m_collator;
};
KDirSortFilterProxyModel::KDirSortFilterProxyModelPrivate::KDirSortFilterProxyModelPrivate()
: m_sortFoldersFirst(true)
, m_sortHiddenFilesLast(DefaultHiddenFilesLast)
{
slotNaturalSortingChanged();
}
int KDirSortFilterProxyModel::KDirSortFilterProxyModelPrivate::compare(const QString &a, const QString &b, Qt::CaseSensitivity caseSensitivity)
{
int result;
if (m_naturalSorting) {
m_collator.setCaseSensitivity(caseSensitivity);
result = m_collator.compare(a, b);
} else {
result = QString::compare(a, b, caseSensitivity);
}
if (caseSensitivity == Qt::CaseSensitive || result != 0) {
// Only return the result, if the strings are not equal. If they are equal by a case insensitive
// comparison, still a deterministic sort order is required. A case sensitive
// comparison is done as fallback.
return result;
}
return QString::compare(a, b, Qt::CaseSensitive);
}
void KDirSortFilterProxyModel::KDirSortFilterProxyModelPrivate::slotNaturalSortingChanged()
{
KConfigGroup g(KSharedConfig::openConfig(), QStringLiteral("KDE"));
m_naturalSorting = g.readEntry("NaturalSorting", true);
m_collator.setNumericMode(m_naturalSorting);
}
KDirSortFilterProxyModel::KDirSortFilterProxyModel(QObject *parent)
: KCategorizedSortFilterProxyModel(parent)
, d(new KDirSortFilterProxyModelPrivate)
{
setDynamicSortFilter(true);
// sort by the user visible string for now
setSortCaseSensitivity(Qt::CaseInsensitive);
sort(KDirModel::Name, Qt::AscendingOrder);
}
Qt::DropActions KDirSortFilterProxyModel::supportedDragOptions() const
{
return Qt::CopyAction | Qt::MoveAction | Qt::LinkAction | Qt::IgnoreAction;
}
KDirSortFilterProxyModel::~KDirSortFilterProxyModel() = default;
bool KDirSortFilterProxyModel::hasChildren(const QModelIndex &parent) const
{
const QModelIndex sourceParent = mapToSource(parent);
return sourceModel()->hasChildren(sourceParent);
}
bool KDirSortFilterProxyModel::canFetchMore(const QModelIndex &parent) const
{
const QModelIndex sourceParent = mapToSource(parent);
return sourceModel()->canFetchMore(sourceParent);
}
int KDirSortFilterProxyModel::pointsForPermissions(const QFileInfo &info)
{
int points = 0;
const QFile::Permission permissionsCheck[] = {QFile::ReadUser,
QFile::WriteUser,
QFile::ExeUser,
QFile::ReadGroup,
QFile::WriteGroup,
QFile::ExeGroup,
QFile::ReadOther,
QFile::WriteOther,
QFile::ExeOther};
for (QFile::Permission perm : permissionsCheck) {
points += info.permission(perm) ? 1 : 0;
}
return points;
}
void KDirSortFilterProxyModel::setSortFoldersFirst(bool foldersFirst)
{
if (d->m_sortFoldersFirst == foldersFirst) {
return;
}
d->m_sortFoldersFirst = foldersFirst;
invalidate();
}
bool KDirSortFilterProxyModel::sortFoldersFirst() const
{
return d->m_sortFoldersFirst;
}
void KDirSortFilterProxyModel::setSortHiddenFilesLast(bool hiddenFilesLast)
{
if (d->m_sortHiddenFilesLast == hiddenFilesLast) {
return;
}
d->m_sortHiddenFilesLast = hiddenFilesLast;
invalidate();
}
bool KDirSortFilterProxyModel::sortHiddenFilesLast() const
{
return d->m_sortHiddenFilesLast;
}
bool KDirSortFilterProxyModel::subSortLessThan(const QModelIndex &left, const QModelIndex &right) const
{
KDirModel *dirModel = static_cast<KDirModel *>(sourceModel());
const KFileItem leftFileItem = dirModel->itemForIndex(left);
const KFileItem rightFileItem = dirModel->itemForIndex(right);
const bool isLessThan = (sortOrder() == Qt::AscendingOrder);
// Show hidden files and folders last
if (d->m_sortHiddenFilesLast) {
const bool leftItemIsHidden = leftFileItem.isHidden();
const bool rightItemIsHidden = rightFileItem.isHidden();
if (leftItemIsHidden && !rightItemIsHidden) {
return !isLessThan;
} else if (!leftItemIsHidden && rightItemIsHidden) {
return isLessThan;
}
}
// Folders go before files if the corresponding setting is set.
if (d->m_sortFoldersFirst) {
const bool leftItemIsDir = leftFileItem.isDir();
const bool rightItemIsDir = rightFileItem.isDir();
if (leftItemIsDir && !rightItemIsDir) {
return isLessThan;
} else if (!leftItemIsDir && rightItemIsDir) {
return !isLessThan;
}
}
switch (left.column()) {
case KDirModel::Name: {
int result = d->compare(leftFileItem.text(), rightFileItem.text(), sortCaseSensitivity());
if (result == 0) {
// KFileItem::text() may not be unique in case UDS_DISPLAY_NAME is used
result = d->compare(leftFileItem.name(sortCaseSensitivity() == Qt::CaseInsensitive),
rightFileItem.name(sortCaseSensitivity() == Qt::CaseInsensitive),
sortCaseSensitivity());
if (result == 0) {
// If KFileItem::text() is also not unique most probably a search protocol is used
// that allows showing the same file names from different directories
result = d->compare(leftFileItem.url().toString(), rightFileItem.url().toString(), sortCaseSensitivity());
}
}
return result < 0;
}
case KDirModel::Size: {
// If we have two folders, what we have to measure is the number of
// items that contains each other
if (leftFileItem.isDir() && rightFileItem.isDir()) {
QVariant leftValue = dirModel->data(left, KDirModel::ChildCountRole);
int leftCount = (leftValue.typeId() == QMetaType::Int) ? leftValue.toInt() : KDirModel::ChildCountUnknown;
QVariant rightValue = dirModel->data(right, KDirModel::ChildCountRole);
int rightCount = (rightValue.typeId() == QMetaType::Int) ? rightValue.toInt() : KDirModel::ChildCountUnknown;
// In the case they two have the same child items, we sort them by
// their names. So we have always everything ordered. We also check
// if we are taking in count their cases.
if (leftCount == rightCount) {
return d->compare(leftFileItem.text(), rightFileItem.text(), sortCaseSensitivity()) < 0;
}
// If one of them has unknown child items, place them on the end. If we
// were comparing two unknown childed items, the previous comparison
// sorted them by QCollator between them. This case is when we
// have an unknown childed item, and another known.
if (leftCount == KDirModel::ChildCountUnknown) {
return false;
}
if (rightCount == KDirModel::ChildCountUnknown) {
return true;
}
// If they had different number of items, we sort them depending
// on how many items had each other.
return leftCount < rightCount;
}
// If what we are measuring is two files and they have the same size,
// sort them by their file names.
if (leftFileItem.size() == rightFileItem.size()) {
return d->compare(leftFileItem.text(), rightFileItem.text(), sortCaseSensitivity()) < 0;
}
// If their sizes are different, sort them by their sizes, as expected.
return leftFileItem.size() < rightFileItem.size();
}
case KDirModel::ModifiedTime: {
QDateTime leftModifiedTime = leftFileItem.time(KFileItem::ModificationTime).toLocalTime();
QDateTime rightModifiedTime = rightFileItem.time(KFileItem::ModificationTime).toLocalTime();
if (leftModifiedTime == rightModifiedTime) {
return d->compare(leftFileItem.text(), rightFileItem.text(), sortCaseSensitivity()) < 0;
}
return leftModifiedTime < rightModifiedTime;
}
case KDirModel::Permissions: {
const int leftPermissions = leftFileItem.permissions();
const int rightPermissions = rightFileItem.permissions();
if (leftPermissions == rightPermissions) {
return d->compare(leftFileItem.text(), rightFileItem.text(), sortCaseSensitivity()) < 0;
}
return leftPermissions > rightPermissions;
}
case KDirModel::Owner: {
if (leftFileItem.user() == rightFileItem.user()) {
return d->compare(leftFileItem.text(), rightFileItem.text(), sortCaseSensitivity()) < 0;
}
return d->compare(leftFileItem.user(), rightFileItem.user()) < 0;
}
case KDirModel::Group: {
if (leftFileItem.group() == rightFileItem.group()) {
return d->compare(leftFileItem.text(), rightFileItem.text(), sortCaseSensitivity()) < 0;
}
return d->compare(leftFileItem.group(), rightFileItem.group()) < 0;
}
case KDirModel::Type: {
if (leftFileItem.mimetype() == rightFileItem.mimetype()) {
return d->compare(leftFileItem.text(), rightFileItem.text(), sortCaseSensitivity()) < 0;
}
return d->compare(leftFileItem.mimeComment(), rightFileItem.mimeComment()) < 0;
}
}
// We have set a SortRole and trust the ProxyModel to do
// the right thing for now.
return KCategorizedSortFilterProxyModel::subSortLessThan(left, right);
}
#include "moc_kdirsortfilterproxymodel.cpp"
@@ -0,0 +1,95 @@
/*
SPDX-FileCopyrightText: 2006 Peter Penz <peter.penz@gmx.at>
SPDX-FileCopyrightText: 2006 Dominic Battre <dominic@battre.de>
SPDX-FileCopyrightText: 2006 Martin Pool <mbp@canonical.com>
Separated from Dolphin by Nick Shaforostoff <shafff@ukr.net>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KDIRSORTFILTERPROXYMODEL_H
#define KDIRSORTFILTERPROXYMODEL_H
#include <QFileInfo>
#include <KCategorizedSortFilterProxyModel>
#include "kiofilewidgets_export.h"
#include <memory>
/**
* @class KDirSortFilterProxyModel kdirsortfilterproxymodel.h <KDirSortFilterProxyModel>
*
* @brief Acts as proxy model for KDirModel to sort and filter
* KFileItems.
*
* A natural sorting is done. This means that items like:
* - item_10.png
* - item_1.png
* - item_2.png
*
* are sorted like
* - item_1.png
* - item_2.png
* - item_10.png
*
* Don't use it with non-KDirModel derivatives.
*
* @author Dominic Battre, Martin Pool and Peter Penz
*/
class KIOFILEWIDGETS_EXPORT KDirSortFilterProxyModel : public KCategorizedSortFilterProxyModel
{
Q_OBJECT
public:
explicit KDirSortFilterProxyModel(QObject *parent = nullptr);
~KDirSortFilterProxyModel() override;
/** Reimplemented from QAbstractItemModel. Returns true for directories. */
bool hasChildren(const QModelIndex &parent = QModelIndex()) const override;
/**
* Reimplemented from QAbstractItemModel.
* Returns true for 'empty' directories so they can be populated later.
*/
bool canFetchMore(const QModelIndex &parent) const override;
/**
* Returns the permissions in "points". This is useful for sorting by
* permissions.
*/
static int pointsForPermissions(const QFileInfo &info);
/**
* Choose if files and folders are sorted separately (with folders first) or not.
*/
void setSortFoldersFirst(bool foldersFirst);
/**
* Returns if files and folders are sorted separately (with folders first) or not.
*/
bool sortFoldersFirst() const;
/**
* Sets a separate sorting with hidden files and folders last (true) or not (false).
* @since 5.95
*/
void setSortHiddenFilesLast(bool hiddenFilesLast);
bool sortHiddenFilesLast() const;
Qt::DropActions supportedDragOptions() const;
protected:
/**
* Reimplemented from KCategorizedSortFilterProxyModel.
*/
bool subSortLessThan(const QModelIndex &left, const QModelIndex &right) const override;
private:
class KDirSortFilterProxyModelPrivate;
std::unique_ptr<KDirSortFilterProxyModelPrivate> const d;
};
#endif
@@ -0,0 +1,267 @@
// -*- c++ -*-
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2003 Joseph Wenninger <jowenn@kde.org>
SPDX-FileCopyrightText: 2003 Andras Mantia <amantia@freemail.hu>
SPDX-FileCopyrightText: 2013 Teo Mrnjavac <teo@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kencodingfiledialog.h"
#include "kfilewidget.h"
#include <defaults-kfile.h>
#include <KConfigGroup>
#include <KLocalizedString>
#include <KSharedConfig>
#include <KWindowConfig>
#include <krecentdocument.h>
#include <QBoxLayout>
#include <QComboBox>
#include <QPushButton>
#include <QStringDecoder>
#if QT_VERSION < QT_VERSION_CHECK(6, 7, 0)
#include <QTextCodec>
#endif
struct KEncodingFileDialogPrivate {
KEncodingFileDialogPrivate()
: cfgGroup(KSharedConfig::openConfig(), ConfigGroup)
{
}
QComboBox *encoding;
KFileWidget *w;
KConfigGroup cfgGroup;
};
KEncodingFileDialog::KEncodingFileDialog(const QUrl &startDir,
const QString &encoding,
const QString &filter,
const QString &title,
QFileDialog::AcceptMode type,
QWidget *parent)
: QDialog(parent, Qt::Dialog)
, d(new KEncodingFileDialogPrivate)
{
d->w = new KFileWidget(startDir, this);
d->w->setFilters(KFileFilter::fromFilterString(filter));
if (type == QFileDialog::AcceptOpen) {
d->w->setOperationMode(KFileWidget::Opening);
} else {
d->w->setOperationMode(KFileWidget::Saving);
}
setWindowTitle(title);
// ops->clearHistory();
KWindowConfig::restoreWindowSize(windowHandle(), d->cfgGroup);
QBoxLayout *mainLayout = new QVBoxLayout(this);
mainLayout->addWidget(d->w);
d->w->okButton()->show();
connect(d->w->okButton(), &QAbstractButton::clicked, this, &KEncodingFileDialog::slotOk);
d->w->cancelButton()->show();
connect(d->w->cancelButton(), &QAbstractButton::clicked, this, &KEncodingFileDialog::slotCancel);
connect(d->w, &KFileWidget::accepted, this, &KEncodingFileDialog::accept);
d->encoding = new QComboBox(this);
d->w->setCustomWidget(i18n("Encoding:"), d->encoding);
d->encoding->clear();
QByteArray sEncoding = encoding.toUtf8();
#if QT_VERSION < QT_VERSION_CHECK(6, 7, 0)
const auto systemEncoding = QTextCodec::codecForLocale()->name();
#else
const auto systemEncoding = QStringDecoder(QStringDecoder::System).name();
#endif
if (sEncoding.isEmpty() || sEncoding == "System") {
sEncoding = systemEncoding;
}
#if QT_VERSION < QT_VERSION_CHECK(6, 7, 0)
auto encodings = QTextCodec::availableCodecs();
#else
auto encodings = QStringDecoder::availableCodecs();
#endif
std::sort(encodings.begin(), encodings.end(), [](auto &a, auto &b) {
return (a.compare(b, Qt::CaseInsensitive) < 0);
});
int insert = 0;
int system = 0;
bool foundRequested = false;
for (const auto &encoding : encodings) {
#if QT_VERSION < QT_VERSION_CHECK(6, 7, 0)
QTextCodec *codecForEnc = QTextCodec::codecForName(encoding);
if (codecForEnc) {
d->encoding->addItem(QString::fromUtf8(encoding));
auto codecName = codecForEnc->name();
if ((codecName == sEncoding) || (encoding == sEncoding)) {
d->encoding->setCurrentIndex(insert);
foundRequested = true;
}
if ((codecName == systemEncoding) || (encoding == systemEncoding)) {
system = insert;
}
insert++;
}
#else
d->encoding->addItem(encoding);
const auto codecName = QStringDecoder(encoding.toUtf8().constData()).name();
if ((codecName == sEncoding) || (encoding.toUtf8() == sEncoding)) {
d->encoding->setCurrentIndex(insert);
foundRequested = true;
}
if ((codecName == systemEncoding) || (encoding.toUtf8() == systemEncoding)) {
system = insert;
}
insert++;
#endif
}
if (!foundRequested) {
d->encoding->setCurrentIndex(system);
}
}
KEncodingFileDialog::~KEncodingFileDialog() = default;
QString KEncodingFileDialog::selectedEncoding() const
{
if (d->encoding) {
return d->encoding->currentText();
} else {
return QString();
}
}
KEncodingFileDialog::Result
KEncodingFileDialog::getOpenFileNameAndEncoding(const QString &encoding, const QUrl &startDir, const QString &filter, QWidget *parent, const QString &title)
{
KEncodingFileDialog dlg(startDir, encoding, filter, title.isNull() ? i18n("Open") : title, QFileDialog::AcceptOpen, parent);
dlg.d->w->setMode(KFile::File | KFile::LocalOnly);
dlg.exec();
Result res;
res.fileNames << dlg.d->w->selectedFile();
res.encoding = dlg.selectedEncoding();
return res;
}
KEncodingFileDialog::Result
KEncodingFileDialog::getOpenFileNamesAndEncoding(const QString &encoding, const QUrl &startDir, const QString &filter, QWidget *parent, const QString &title)
{
KEncodingFileDialog dlg(startDir, encoding, filter, title.isNull() ? i18n("Open") : title, QFileDialog::AcceptOpen, parent);
dlg.d->w->setMode(KFile::Files | KFile::LocalOnly);
dlg.exec();
Result res;
res.fileNames = dlg.d->w->selectedFiles();
res.encoding = dlg.selectedEncoding();
return res;
}
KEncodingFileDialog::Result
KEncodingFileDialog::getOpenUrlAndEncoding(const QString &encoding, const QUrl &startDir, const QString &filter, QWidget *parent, const QString &title)
{
KEncodingFileDialog dlg(startDir, encoding, filter, title.isNull() ? i18n("Open") : title, QFileDialog::AcceptOpen, parent);
dlg.d->w->setMode(KFile::File);
dlg.exec();
Result res;
res.URLs << dlg.d->w->selectedUrl();
res.encoding = dlg.selectedEncoding();
return res;
}
KEncodingFileDialog::Result
KEncodingFileDialog::getOpenUrlsAndEncoding(const QString &encoding, const QUrl &startDir, const QString &filter, QWidget *parent, const QString &title)
{
KEncodingFileDialog dlg(startDir, encoding, filter, title.isNull() ? i18n("Open") : title, QFileDialog::AcceptOpen, parent);
dlg.d->w->setMode(KFile::Files);
dlg.exec();
Result res;
res.URLs = dlg.d->w->selectedUrls();
res.encoding = dlg.selectedEncoding();
return res;
}
KEncodingFileDialog::Result
KEncodingFileDialog::getSaveFileNameAndEncoding(const QString &encoding, const QUrl &dir, const QString &filter, QWidget *parent, const QString &title)
{
KEncodingFileDialog dlg(dir, encoding, filter, title.isNull() ? i18n("Save As") : title, QFileDialog::AcceptSave, parent);
dlg.d->w->setMode(KFile::File);
dlg.exec();
QString filename = dlg.d->w->selectedFile();
if (!filename.isEmpty()) {
KRecentDocument::add(QUrl::fromLocalFile(filename));
}
Result res;
res.fileNames << filename;
res.encoding = dlg.selectedEncoding();
return res;
}
KEncodingFileDialog::Result
KEncodingFileDialog::getSaveUrlAndEncoding(const QString &encoding, const QUrl &dir, const QString &filter, QWidget *parent, const QString &title)
{
KEncodingFileDialog dlg(dir, encoding, filter, title.isNull() ? i18n("Save As") : title, QFileDialog::AcceptSave, parent);
dlg.d->w->setMode(KFile::File);
Result res;
if (dlg.exec() == QDialog::Accepted) {
QUrl url = dlg.d->w->selectedUrl();
if (url.isValid()) {
KRecentDocument::add(url);
}
res.URLs << url;
res.encoding = dlg.selectedEncoding();
}
return res;
}
QSize KEncodingFileDialog::sizeHint() const
{
return d->w->dialogSizeHint();
}
void KEncodingFileDialog::hideEvent(QHideEvent *e)
{
KWindowConfig::saveWindowSize(windowHandle(), d->cfgGroup, KConfigBase::Persistent);
QDialog::hideEvent(e);
}
void KEncodingFileDialog::accept()
{
d->w->accept();
QDialog::accept();
}
void KEncodingFileDialog::slotOk()
{
d->w->slotOk();
}
void KEncodingFileDialog::slotCancel()
{
d->w->slotCancel();
reject();
}
#include "moc_kencodingfiledialog.cpp"
@@ -0,0 +1,295 @@
// -*- c++ -*-
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2003 Joseph Wenninger <jowenn@kde.org>
SPDX-FileCopyrightText: 2003 Andras Mantia <amantia@freemail.hu>
SPDX-FileCopyrightText: 2013 Teo Mrnjavac <teo@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef __KENCODINGFILEDIALOG_H__
#define __KENCODINGFILEDIALOG_H__
#include "kiofilewidgets_export.h"
#include <QFileDialog>
#include <memory>
struct KEncodingFileDialogPrivate;
/**
* @class KEncodingFileDialog kencodingfiledialog.h <KEncodingFileDialog>
*
* Provides a user (and developer) friendly way to select files with support for
* choosing encoding.
* This class comes with a private constructor, the only way to show a file dialog
* is through its static methods.
*/
class KEncodingFileDialog : public QDialog
{
Q_OBJECT
public:
class KIOFILEWIDGETS_EXPORT Result
{
public:
QStringList fileNames;
QList<QUrl> URLs;
QString encoding;
};
/**
* Creates a modal file dialog and return the selected
* filename or an empty string if none was chosen additionally a chosen
* encoding value is returned.
*
* Note that with
* this method the user must select an existing filename.
*
* @param encoding The encoding shown in the encoding combo.
* @param startDir This can either be
* @li The URL of the directory to start in.
* @li QString() to start in the current working
* directory, or the last directory where a file has been
* selected.
* @li ':&lt;keyword&gt;' to start in the directory last used
* by a filedialog in the same application that specified
* the same keyword.
* @li '::&lt;keyword&gt;' to start in the directory last used
* by a filedialog in any application that specified the
* same keyword.
* @param filter A shell glob or a MIME type filter that specifies which files to display.
* see KFileFilter::KFileFilter(const QString &label, const QStringList &filePatterns, const QStringList &mimePatterns)
* for examples of patterns.
* @param parent The widget the dialog will be centered on initially.
* @param title The name of the dialog widget.
*/
static KIOFILEWIDGETS_EXPORT Result getOpenFileNameAndEncoding(const QString &encoding = QString(),
const QUrl &startDir = QUrl(),
const QString &filter = QString(),
QWidget *parent = nullptr,
const QString &title = QString());
/**
* Creates a modal file dialog and returns the selected encoding and the selected
* filenames or an empty list if none was chosen.
*
* Note that with
* this method the user must select an existing filename.
*
* @param encoding The encoding shown in the encoding combo.
* @param startDir This can either be
* @li The URL of the directory to start in.
* @li QString() to start in the current working
* directory, or the last directory where a file has been
* selected.
* @li ':&lt;keyword&gt;' to start in the directory last used
* by a filedialog in the same application that specified
* the same keyword.
* @li '::&lt;keyword&gt;' to start in the directory last used
* by a filedialog in any application that specified the
* same keyword.
* @param filter A shell glob or a MIME type filter that specifies which files to display.
* see KFileFilter::KFileFilter(const QString &label, const QStringList &filePatterns, const QStringList &mimePatterns)
* for examples of patterns.
* @param parent The widget the dialog will be centered on initially.
* @param title The name of the dialog widget.
*/
static KIOFILEWIDGETS_EXPORT Result getOpenFileNamesAndEncoding(const QString &encoding = QString(),
const QUrl &startDir = QUrl(),
const QString &filter = QString(),
QWidget *parent = nullptr,
const QString &title = QString());
/**
* Creates a modal file dialog and returns the selected encoding and
* URL or an empty string if none was chosen.
*
* Note that with
* this method the user must select an existing URL.
*
* @param encoding The encoding shown in the encoding combo.
* @param startDir This can either be
* @li The URL of the directory to start in.
* @li QString() to start in the current working
* directory, or the last directory where a file has been
* selected.
* @li ':&lt;keyword&gt;' to start in the directory last used
* by a filedialog in the same application that specified
* the same keyword.
* @li '::&lt;keyword&gt;' to start in the directory last used
* by a filedialog in any application that specified the
* same keyword.
* @param filter A shell glob or a MIME type filter that specifies which files to display.
* see KFileFilter::KFileFilter(const QString &label, const QStringList &filePatterns, const QStringList &mimePatterns)
* for examples of patterns.
* @param parent The widget the dialog will be centered on initially.
* @param title The name of the dialog widget.
*/
static KIOFILEWIDGETS_EXPORT Result getOpenUrlAndEncoding(const QString &encoding = QString(),
const QUrl &startDir = QUrl(),
const QString &filter = QString(),
QWidget *parent = nullptr,
const QString &title = QString());
/**
* Creates a modal file dialog and returns the selected encoding
* URLs or an empty list if none was chosen.
*
* Note that with
* this method the user must select an existing filename.
*
* @param encoding The encoding shown in the encoding combo.
* @param startDir This can either be
* @li The URL of the directory to start in.
* @li QString() to start in the current working
* directory, or the last directory where a file has been
* selected.
* @li ':&lt;keyword&gt;' to start in the directory last used
* by a filedialog in the same application that specified
* the same keyword.
* @li '::&lt;keyword&gt;' to start in the directory last used
* by a filedialog in any application that specified the
* same keyword.
* @param filter A shell glob or a MIME type filter that specifies which files to display.
* see KFileFilter::KFileFilter(const QString &label, const QStringList &filePatterns, const QStringList &mimePatterns)
* for examples of patterns.
* @param parent The widget the dialog will be centered on initially.
* @param title The name of the dialog widget.
*/
static KIOFILEWIDGETS_EXPORT Result getOpenUrlsAndEncoding(const QString &encoding = QString(),
const QUrl &startDir = QUrl(),
const QString &filter = QString(),
QWidget *parent = nullptr,
const QString &title = QString());
/**
* Creates a modal file dialog and returns the selected encoding and
* filename or an empty string if none was chosen.
*
* Note that with this
* method the user need not select an existing filename.
*
* @param encoding The encoding shown in the encoding combo.
* @param startDir This can either be
* @li The URL of the directory to start in.
* @li a relative path or a filename determining the
* directory to start in and the file to be selected.
* @li QString() to start in the current working
* directory, or the last directory where a file has been
* selected.
* @li ':&lt;keyword&gt;' to start in the directory last used
* by a filedialog in the same application that specified
* the same keyword.
* @li '::&lt;keyword&gt;' to start in the directory last used
* by a filedialog in any application that specified the
* same keyword.
* @param filter A shell glob or a MIME type filter that specifies which files to display.
* see KFileFilter::KFileFilter(const QString &label, const QStringList &filePatterns, const QStringList &mimePatterns)
* for examples of patterns.
* @param parent The widget the dialog will be centered on initially.
* @param title The name of the dialog widget.
*/
static KIOFILEWIDGETS_EXPORT Result getSaveFileNameAndEncoding(const QString &encoding = QString(),
const QUrl &startDir = QUrl(),
const QString &filter = QString(),
QWidget *parent = nullptr,
const QString &title = QString());
/**
* Creates a modal file dialog and returns the selected encoding and
* filename or an empty string if none was chosen.
*
* Note that with this
* method the user need not select an existing filename.
*
* @param encoding The encoding shown in the encoding combo.
* @param startDir This can either be
* @li The URL of the directory to start in.
* @li a relative path or a filename determining the
* directory to start in and the file to be selected.
* @li QString() to start in the current working
* directory, or the last directory where a file has been
* selected.
* @li ':&lt;keyword&gt;' to start in the directory last used
* by a filedialog in the same application that specified
* the same keyword.
* @li '::&lt;keyword&gt;' to start in the directory last used
* by a filedialog in any application that specified the
* same keyword.
* @param filter A shell glob or a MIME type filter that specifies which files to display.
* see KFileFilter::KFileFilter(const QString &label, const QStringList &filePatterns, const QStringList &mimePatterns)
* for examples of patterns.
* @param parent The widget the dialog will be centered on initially.
* @param title The name of the dialog widget.
*/
static KIOFILEWIDGETS_EXPORT Result getSaveUrlAndEncoding(const QString &encoding = QString(),
const QUrl &startDir = QUrl(),
const QString &filter = QString(),
QWidget *parent = nullptr,
const QString &title = QString());
QSize sizeHint() const override;
protected:
void hideEvent(QHideEvent *e) override;
private Q_SLOTS:
void accept() override;
void slotOk();
void slotCancel();
private:
/**
* Constructs a file dialog for text files with encoding selection possibility.
*
* @param startDir This can either be
* @li The URL of the directory to start in.
* @li QString() to start in the current working
* directory, or the last directory where a file has been
* selected.
* @li ':&lt;keyword&gt;' to start in the directory last used
* by a filedialog in the same application that specified
* the same keyword.
* @li '::&lt;keyword&gt;' to start in the directory last used
* by a filedialog in any application that specified the
* same keyword.
*
* @param encoding The encoding shown in the encoding combo. If it's
* QString(), the global default encoding will be shown.
*
* @param filter A shell glob or a MIME type filter that specifies which files to display.
* The preferred option is to set a list of MIME type names, see setMimeFilter() for details.
* Otherwise you can set the text to be displayed for the each glob, and
* provide multiple globs, see setFilters() for details.
*
* @param title The title of the dialog
*
* @param type This can either be
* @li QFileDialog::AcceptOpen (open dialog, the default setting)
* @li QFileDialog::AcceptSave
* @param parent The parent widget of this dialog
*/
KEncodingFileDialog(const QUrl &startDir = QUrl(),
const QString &encoding = QString(),
const QString &filter = QString(),
const QString &title = QString(),
QFileDialog::AcceptMode type = QFileDialog::AcceptOpen,
QWidget *parent = nullptr);
/**
* Destructs the file dialog.
*/
~KEncodingFileDialog() override;
/**
* @returns The selected encoding if the constructor with the encoding parameter was used, otherwise QString().
*/
QString selectedEncoding() const;
std::unique_ptr<KEncodingFileDialogPrivate> const d;
};
#endif
@@ -0,0 +1,64 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2002 Carsten Pfeiffer <pfeiffer@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include "kfilebookmarkhandler_p.h"
#include <stdio.h>
#include <stdlib.h>
#include <QMenu>
#include <QStandardPaths>
#include <kio/global.h>
#include "kfilewidget.h"
KFileBookmarkHandler::KFileBookmarkHandler(KFileWidget *widget)
: QObject(widget)
, KBookmarkOwner()
, m_widget(widget)
{
setObjectName(QStringLiteral("KFileBookmarkHandler"));
m_menu = new QMenu(widget);
m_menu->setObjectName(QStringLiteral("bookmark menu"));
QString file = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kfile/bookmarks.xml"));
if (file.isEmpty()) {
file = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kfile/bookmarks.xml");
}
m_bookmarkManager = new KBookmarkManager(file, this);
m_bookmarkMenu = new KBookmarkMenu(m_bookmarkManager, this, m_menu);
}
KFileBookmarkHandler::~KFileBookmarkHandler()
{
delete m_bookmarkMenu;
}
void KFileBookmarkHandler::openBookmark(const KBookmark &bm, Qt::MouseButtons, Qt::KeyboardModifiers)
{
Q_EMIT openUrl(bm.url().toString());
}
QUrl KFileBookmarkHandler::currentUrl() const
{
return m_widget->baseUrl();
}
QString KFileBookmarkHandler::currentTitle() const
{
return m_widget->baseUrl().toDisplayString();
}
QString KFileBookmarkHandler::currentIcon() const
{
return KIO::iconNameForUrl(currentUrl());
}
#include "moc_kfilebookmarkhandler_p.cpp"
@@ -0,0 +1,53 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2002 Carsten Pfeiffer <pfeiffer@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KFILEBOOKMARKHANDLER_H
#define KFILEBOOKMARKHANDLER_H
#include <KBookmarkManager>
#include <KBookmarkMenu>
#include <KBookmarkOwner>
class QMenu;
class KFileWidget;
class KFileBookmarkHandler : public QObject, public KBookmarkOwner
{
Q_OBJECT
public:
explicit KFileBookmarkHandler(KFileWidget *widget);
~KFileBookmarkHandler() override;
QMenu *popupMenu();
// KBookmarkOwner interface:
QString currentTitle() const override;
QUrl currentUrl() const override;
QString currentIcon() const override;
QMenu *menu() const
{
return m_menu;
}
public Q_SLOTS:
void openBookmark(const KBookmark &bm, Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers) override;
Q_SIGNALS:
void openUrl(const QString &url);
private:
void importOldBookmarks(const QString &path, KBookmarkManager *manager);
KFileWidget *m_widget;
QMenu *m_menu;
KBookmarkMenu *m_bookmarkMenu;
KBookmarkManager *m_bookmarkManager;
};
#endif // KFILEBOOKMARKHANDLER_H
@@ -0,0 +1,260 @@
/*
SPDX-FileCopyrightText: 2008, 2009, 2015 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "kfilecopytomenu.h"
#include "kfilecopytomenu_p.h"
#include <QAction>
#include <QDir>
#include <QFileDialog>
#include <QIcon>
#include <QMimeDatabase>
#include <QMimeType>
#include "../utils_p.h"
#include <KIO/CopyJob>
#include <KIO/FileUndoManager>
#include <KIO/JobUiDelegate>
#include <KJobWidgets>
#include <KLocalizedString>
#include <KSharedConfig>
#include <KStringHandler>
#ifdef Q_OS_WIN
#include "windows.h"
#endif
static constexpr int s_maxRecentDirs = 10; // Hardcoded max size
KFileCopyToMenuPrivate::KFileCopyToMenuPrivate(KFileCopyToMenu *qq, QWidget *parentWidget)
: q(qq)
, m_urls()
, m_parentWidget(parentWidget)
, m_readOnly(false)
, m_autoErrorHandling(false)
{
}
////
KFileCopyToMenu::KFileCopyToMenu(QWidget *parentWidget)
: QObject(parentWidget)
, d(new KFileCopyToMenuPrivate(this, parentWidget))
{
}
KFileCopyToMenu::~KFileCopyToMenu() = default;
void KFileCopyToMenu::setUrls(const QList<QUrl> &urls)
{
d->m_urls = urls;
}
void KFileCopyToMenu::setReadOnly(bool ro)
{
d->m_readOnly = ro;
}
void KFileCopyToMenu::setAutoErrorHandlingEnabled(bool b)
{
d->m_autoErrorHandling = b;
}
void KFileCopyToMenu::addActionsTo(QMenu *menu) const
{
QMenu *mainCopyMenu = new KFileCopyToMainMenu(menu, d.get(), Copy);
mainCopyMenu->setTitle(i18nc("@title:menu", "Copy To"));
mainCopyMenu->menuAction()->setObjectName(QStringLiteral("copyTo_submenu")); // for the unittest
menu->addMenu(mainCopyMenu);
if (!d->m_readOnly) {
QMenu *mainMoveMenu = new KFileCopyToMainMenu(menu, d.get(), Move);
mainMoveMenu->setTitle(i18nc("@title:menu", "Move To"));
mainMoveMenu->menuAction()->setObjectName(QStringLiteral("moveTo_submenu")); // for the unittest
menu->addMenu(mainMoveMenu);
}
}
////
KFileCopyToMainMenu::KFileCopyToMainMenu(QMenu *parent, KFileCopyToMenuPrivate *_d, MenuType menuType)
: QMenu(parent)
, m_menuType(menuType)
, m_actionGroup(static_cast<QWidget *>(nullptr))
, d(_d)
, m_recentDirsGroup(KSharedConfig::openConfig(), m_menuType == Copy ? QStringLiteral("kuick-copy") : QStringLiteral("kuick-move"))
{
connect(this, &KFileCopyToMainMenu::aboutToShow, this, &KFileCopyToMainMenu::slotAboutToShow);
connect(&m_actionGroup, &QActionGroup::triggered, this, &KFileCopyToMainMenu::slotTriggered);
}
void KFileCopyToMainMenu::slotAboutToShow()
{
clear();
KFileCopyToDirectoryMenu *subMenu;
// Home Folder
subMenu = new KFileCopyToDirectoryMenu(this, this, QDir::homePath());
subMenu->setTitle(i18nc("@title:menu", "Home Folder"));
subMenu->setIcon(QIcon::fromTheme(QStringLiteral("go-home")));
QAction *act = addMenu(subMenu);
act->setObjectName(QStringLiteral("home"));
// Root Folder
#ifndef Q_OS_WIN
subMenu = new KFileCopyToDirectoryMenu(this, this, QDir::rootPath());
subMenu->setTitle(i18nc("@title:menu", "Root Folder"));
subMenu->setIcon(QIcon::fromTheme(QStringLiteral("folder-red")));
act = addMenu(subMenu);
act->setObjectName(QStringLiteral("root"));
#else
const QFileInfoList drives = QDir::drives();
for (const QFileInfo &info : drives) {
QString driveIcon = QStringLiteral("drive-harddisk");
const uint type = GetDriveTypeW((wchar_t *)info.absoluteFilePath().utf16());
switch (type) {
case DRIVE_REMOVABLE:
driveIcon = QStringLiteral("drive-removable-media");
break;
case DRIVE_FIXED:
driveIcon = QStringLiteral("drive-harddisk");
break;
case DRIVE_REMOTE:
driveIcon = QStringLiteral("network-server");
break;
case DRIVE_CDROM:
driveIcon = QStringLiteral("drive-optical");
break;
case DRIVE_RAMDISK:
case DRIVE_UNKNOWN:
case DRIVE_NO_ROOT_DIR:
default:
driveIcon = QStringLiteral("drive-harddisk");
}
subMenu = new KFileCopyToDirectoryMenu(this, this, info.absoluteFilePath());
subMenu->setTitle(info.absoluteFilePath());
subMenu->setIcon(QIcon::fromTheme(driveIcon));
addMenu(subMenu);
}
#endif
// Browse... action, shows a file dialog
auto *browseAction = new QAction(i18nc("@action:inmenu in Copy To or Move To submenu", "Browse…"), this);
browseAction->setObjectName(QStringLiteral("browse"));
connect(browseAction, &QAction::triggered, this, &KFileCopyToMainMenu::slotBrowse);
addAction(browseAction);
addSeparator(); // Qt handles removing it automatically if it's last in the menu, nice.
// Recent Destinations
const QStringList recentDirs = m_recentDirsGroup.readPathEntry("Paths", QStringList());
for (const QString &recentDir : recentDirs) {
const QUrl url = QUrl::fromLocalFile(recentDir);
const QString text = KStringHandler::csqueeze(url.toDisplayString(QUrl::PreferLocalFile), 60); // shorten very long paths (#61386)
QAction *act = new QAction(text, this);
act->setObjectName(recentDir);
act->setData(url);
m_actionGroup.addAction(act);
addAction(act);
}
}
void KFileCopyToMainMenu::slotBrowse()
{
const QUrl dest = QFileDialog::getExistingDirectoryUrl(d->m_parentWidget ? d->m_parentWidget : this);
if (!dest.isEmpty()) {
copyOrMoveTo(dest);
}
}
void KFileCopyToMainMenu::slotTriggered(QAction *action)
{
const QUrl url = action->data().toUrl();
Q_ASSERT(!url.isEmpty());
copyOrMoveTo(url);
}
void KFileCopyToMainMenu::copyOrMoveTo(const QUrl &dest)
{
// Insert into the recent destinations list
QStringList recentDirs = m_recentDirsGroup.readPathEntry("Paths", QStringList());
const QString niceDest = dest.toDisplayString(QUrl::PreferLocalFile);
if (!recentDirs.contains(niceDest)) { // don't change position if already there, moving stuff is bad usability
recentDirs.prepend(niceDest);
if (recentDirs.size() > s_maxRecentDirs) {
recentDirs.erase(recentDirs.begin() + s_maxRecentDirs, recentDirs.end());
}
m_recentDirsGroup.writePathEntry("Paths", recentDirs);
}
// #199549: add a trailing slash to avoid unexpected results when the
// dest doesn't exist anymore: it was creating a file with the name of
// the now non-existing dest.
QUrl dirDest = dest;
Utils::appendSlashToPath(dirDest);
// And now let's do the copy or move -- with undo/redo support.
KIO::CopyJob *job = m_menuType == Copy ? KIO::copy(d->m_urls, dirDest) : KIO::move(d->m_urls, dirDest);
KIO::FileUndoManager::self()->recordCopyJob(job);
KJobWidgets::setWindow(job, d->m_parentWidget ? d->m_parentWidget : this);
if (job->uiDelegate()) {
job->uiDelegate()->setAutoErrorHandlingEnabled(d->m_autoErrorHandling);
}
connect(job, &KIO::CopyJob::result, this, [this](KJob *job) {
Q_EMIT d->q->error(job->error(), job->errorString());
});
}
////
KFileCopyToDirectoryMenu::KFileCopyToDirectoryMenu(QMenu *parent, KFileCopyToMainMenu *mainMenu, const QString &path)
: QMenu(parent)
, m_mainMenu(mainMenu)
, m_path(Utils::slashAppended(path))
{
connect(this, &KFileCopyToDirectoryMenu::aboutToShow, this, &KFileCopyToDirectoryMenu::slotAboutToShow);
}
void KFileCopyToDirectoryMenu::slotAboutToShow()
{
clear();
QAction *act = new QAction(m_mainMenu->menuType() == Copy ? i18nc("@title:menu", "Copy Here") : i18nc("@title:menu", "Move Here"), this);
act->setData(QUrl::fromLocalFile(m_path));
act->setEnabled(QFileInfo(m_path).isWritable());
m_mainMenu->actionGroup().addAction(act);
addAction(act);
addSeparator(); // Qt handles removing it automatically if it's last in the menu, nice.
// List directory
// All we need is sub folder names, their permissions, their icon.
// KDirLister or KIO::listDir would fetch much more info, and would be async,
// and we only care about local directories so we use QDir directly.
QDir dir(m_path);
const QStringList entries = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::LocaleAware);
const QMimeDatabase db;
const QMimeType dirMime = db.mimeTypeForName(QStringLiteral("inode/directory"));
for (const QString &subDir : entries) {
QString subPath = m_path + subDir;
KFileCopyToDirectoryMenu *subMenu = new KFileCopyToDirectoryMenu(this, m_mainMenu, subPath);
QString menuTitle(subDir);
// Replace '&' by "&&" to make sure that '&' inside the directory name is displayed
// correctly and not misinterpreted as an indicator for a keyboard shortcut
subMenu->setTitle(menuTitle.replace(QLatin1Char('&'), QLatin1String("&&")));
const QString iconName = dirMime.iconName();
subMenu->setIcon(QIcon::fromTheme(iconName));
if (QFileInfo(subPath).isSymLink()) {
QFont font = subMenu->menuAction()->font();
font.setItalic(true);
subMenu->menuAction()->setFont(font);
}
addMenu(subMenu);
}
}
#include "moc_kfilecopytomenu.cpp"
#include "moc_kfilecopytomenu_p.cpp"
@@ -0,0 +1,80 @@
/*
SPDX-FileCopyrightText: 2008, 2015 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#ifndef KFILECOPYTOMENU_H
#define KFILECOPYTOMENU_H
#include <QObject>
#include <QUrl>
#include <kiofilewidgets_export.h>
#include <memory>
class QMenu;
class KFileCopyToMenuPrivate;
/**
* @class KFileCopyToMenu kfilecopytomenu.h <KFileCopyToMenu>
*
* This class adds "Copy To" and "Move To" submenus to a popupmenu.
*/
class KIOFILEWIDGETS_EXPORT KFileCopyToMenu : public QObject
{
Q_OBJECT
public:
/**
* Creates a KFileCopyToMenu instance
* Note that this instance (and the widget) must stay alive for at least as
* long as the popupmenu; it has the slots for the actions created by addActionsTo.
*
* @param parentWidget parent widget for the file dialog and message boxes.
* The parentWidget also serves as a parent for this object.
*/
explicit KFileCopyToMenu(QWidget *parentWidget);
/**
* Destructor
*/
~KFileCopyToMenu() override;
/**
* Sets the URLs which the actions apply to.
*/
void setUrls(const QList<QUrl> &urls);
/**
* If setReadOnly(true) is called, the "Move To" submenu will not appear.
*/
void setReadOnly(bool ro);
/**
* Generate the actions and submenus, and adds them to the @p menu.
* All actions are created as children of the menu.
*/
void addActionsTo(QMenu *menu) const;
/**
* Enables or disables automatic error handling with message boxes.
* When called with true, a messagebox is shown in case of an error during a copy or move.
* When called with false, the application should connect to the error signal instead.
* Auto error handling is disabled by default.
*/
void setAutoErrorHandlingEnabled(bool b);
Q_SIGNALS:
/**
* Emitted when the copy or move job fails.
* @param errorCode the KIO job error code
* @param message the error message to show the user
*/
void error(int errorCode, const QString &message);
private:
std::unique_ptr<KFileCopyToMenuPrivate> const d;
};
#endif
@@ -0,0 +1,79 @@
/*
SPDX-FileCopyrightText: 2008, 2015 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#ifndef KFILECOPYTOMENU_P_H
#define KFILECOPYTOMENU_P_H
#include <KConfigGroup>
#include <QActionGroup>
#include <QMenu>
#include <QUrl>
class KFileCopyToMenuPrivate
{
public:
KFileCopyToMenuPrivate(KFileCopyToMenu *qq, QWidget *parentWidget);
KFileCopyToMenu *const q;
QList<QUrl> m_urls;
QWidget *m_parentWidget;
bool m_readOnly;
bool m_autoErrorHandling;
};
enum MenuType {
Copy,
Move
};
// The main menu, shown when opening "Copy To" or "Move To"
// It contains Home Folder, Root Folder, Browse, and recent destinations
class KFileCopyToMainMenu : public QMenu
{
Q_OBJECT
public:
KFileCopyToMainMenu(QMenu *parent, KFileCopyToMenuPrivate *d, MenuType menuType);
QActionGroup &actionGroup()
{
return m_actionGroup; // used by submenus
}
MenuType menuType() const
{
return m_menuType; // used by submenus
}
private Q_SLOTS:
void slotAboutToShow();
void slotBrowse();
void slotTriggered(QAction *action);
private:
void copyOrMoveTo(const QUrl &dest);
private:
MenuType m_menuType;
QActionGroup m_actionGroup;
KFileCopyToMenuPrivate *d; // this isn't our own d pointer, it's the one for the public class
KConfigGroup m_recentDirsGroup;
};
// The menu that lists a directory
class KFileCopyToDirectoryMenu : public QMenu
{
Q_OBJECT
public:
KFileCopyToDirectoryMenu(QMenu *parent, KFileCopyToMainMenu *mainMenu, const QString &path);
private Q_SLOTS:
void slotAboutToShow();
private:
KFileCopyToMainMenu *m_mainMenu;
QString m_path;
};
#endif
@@ -0,0 +1,93 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2017 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
Work sponsored by the LiMux project of the city of Munich
SPDX-License-Identifier: LGPL-2.0-only
*/
#include "kfilecustomdialog.h"
#include <QPushButton>
#include <QUrl>
#include <QVBoxLayout>
class KFileCustomDialogPrivate
{
public:
explicit KFileCustomDialogPrivate(KFileCustomDialog *qq)
: q(qq)
{
}
void init(const QUrl &startDir);
KFileWidget *mFileWidget = nullptr;
KFileCustomDialog *const q;
};
void KFileCustomDialogPrivate::init(const QUrl &startDir)
{
QVBoxLayout *mainLayout = new QVBoxLayout(q);
mainLayout->setObjectName(QStringLiteral("mainlayout"));
mFileWidget = new KFileWidget(startDir, q);
mFileWidget->setObjectName(QStringLiteral("filewidget"));
mainLayout->addWidget(mFileWidget);
mFileWidget->okButton()->show();
q->connect(mFileWidget->okButton(), &QPushButton::clicked, q, [this]() {
mFileWidget->slotOk();
});
mFileWidget->cancelButton()->show();
q->connect(mFileWidget->cancelButton(), &QPushButton::clicked, q, [this]() {
mFileWidget->slotCancel();
q->reject();
});
q->connect(mFileWidget, &KFileWidget::accepted, q, [this] {
q->accept();
});
}
KFileCustomDialog::KFileCustomDialog(QWidget *parent)
: QDialog(parent)
, d(new KFileCustomDialogPrivate(this))
{
d->init(QUrl());
}
KFileCustomDialog::KFileCustomDialog(const QUrl &startDir, QWidget *parent)
: QDialog(parent)
, d(new KFileCustomDialogPrivate(this))
{
d->init(startDir);
}
KFileCustomDialog::~KFileCustomDialog() = default;
void KFileCustomDialog::setUrl(const QUrl &url)
{
d->mFileWidget->setUrl(url);
}
void KFileCustomDialog::setCustomWidget(QWidget *widget)
{
d->mFileWidget->setCustomWidget(QString(), widget);
}
KFileWidget *KFileCustomDialog::fileWidget() const
{
return d->mFileWidget;
}
void KFileCustomDialog::setOperationMode(KFileWidget::OperationMode op)
{
d->mFileWidget->setOperationMode(op);
}
void KFileCustomDialog::accept()
{
d->mFileWidget->accept();
QDialog::accept();
}
#include "moc_kfilecustomdialog.cpp"
@@ -0,0 +1,93 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2017 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
Work sponsored by the LiMux project of the city of Munich
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KFILECUSTOMDIALOG_H
#define KFILECUSTOMDIALOG_H
#include "kfilewidget.h"
#include "kiofilewidgets_export.h"
#include <QDialog>
#include <memory>
class KFileWidget;
class KFileCustomDialogPrivate;
/**
* This class implement a custom file dialog.
* It uses a KFileWidget and allows the application to provide a custom widget.
* @since 5.42
*/
class KIOFILEWIDGETS_EXPORT KFileCustomDialog : public QDialog
{
Q_OBJECT
public:
/**
* Constructs a custom file dialog
*/
explicit KFileCustomDialog(QWidget *parent = nullptr);
/**
* Constructs a custom file dialog
* @param startDir see the KFileWidget constructor for documentation
* @since 5.67
*/
explicit KFileCustomDialog(const QUrl &startDir, QWidget *parent = nullptr);
~KFileCustomDialog() override;
/**
* Sets the directory to view.
*
* @param url URL to show.
*/
void setUrl(const QUrl &url);
/**
* 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 title "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);
/**
* @brief fileWidget
* @return the filewidget used inside this dialog
*/
KFileWidget *fileWidget() const;
/**
* 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(KFileWidget::OperationMode op);
public Q_SLOTS:
void accept() override;
private:
std::unique_ptr<KFileCustomDialogPrivate> const d;
};
#endif // KFILECUSTOMDIALOG_H
@@ -0,0 +1,258 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: Stephan Kulow <coolo@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kfilefiltercombo.h"
#include "kfilefilter.h"
#include "kfilefiltercombo_debug.h"
#include <KLocalizedString>
#include <QDebug>
#include <QEvent>
#include <QLineEdit>
#include <QMimeDatabase>
#include <config-kiofilewidgets.h>
#include <algorithm>
#include <utility>
class KFileFilterComboPrivate
{
public:
explicit KFileFilterComboPrivate(KFileFilterCombo *qq)
: q(qq)
{
}
void slotFilterChanged();
KFileFilterCombo *const q;
// when we have more than 3 mimefilters and no default-filter,
// we don't show the comments of all mimefilters in one line,
// instead we show "All supported files". We have to translate
// that back to the list of mimefilters in currentFilter() tho.
bool m_hasAllSupportedFiles = false;
// true when setMimeFilter was called
bool m_isMimeFilter = false;
QString m_lastFilter;
KFileFilter m_defaultFilter = KFileFilter::fromFilterString(i18nc("Default mime type filter that shows all file types", "*|All Files")).first();
QList<KFileFilter> m_filters;
bool m_allTypes;
};
KFileFilterCombo::KFileFilterCombo(QWidget *parent)
: KComboBox(true, parent)
, d(new KFileFilterComboPrivate(this))
{
setTrapReturnKey(true);
setInsertPolicy(QComboBox::NoInsert);
connect(this, &QComboBox::activated, this, &KFileFilterCombo::filterChanged);
connect(this, &KComboBox::returnPressed, this, &KFileFilterCombo::filterChanged);
connect(this, &KFileFilterCombo::filterChanged, this, [this]() {
d->slotFilterChanged();
});
d->m_allTypes = false;
}
KFileFilterCombo::~KFileFilterCombo() = default;
void KFileFilterCombo::setFilters(const QList<KFileFilter> &types, const KFileFilter &defaultFilter)
{
clear();
d->m_filters.clear();
QString delim = QStringLiteral(", ");
d->m_hasAllSupportedFiles = false;
bool hasAllFilesFilter = false;
QMimeDatabase db;
if (types.isEmpty()) {
d->m_filters = {d->m_defaultFilter};
addItem(d->m_defaultFilter.label());
d->m_lastFilter = currentText();
return;
}
d->m_allTypes = defaultFilter.isEmpty() && (types.count() > 1);
if (!types.isEmpty() && types.first().mimePatterns().isEmpty()) {
d->m_allTypes = false;
}
// If there's MIME types that have the same comment, we will show the extension
// in addition to the MIME type comment
QHash<QString, int> allTypeComments;
for (const KFileFilter &filter : types) {
allTypeComments[filter.label()] += 1;
}
for (const KFileFilter &filter : types) {
if (!filter.isValid()) {
continue;
}
const QStringList mimeTypes = filter.mimePatterns();
const bool isAllFileFilters = std::any_of(mimeTypes.cbegin(), mimeTypes.cend(), [&db](const QString &mimeTypeName) {
const QMimeType type = db.mimeTypeForName(mimeTypeName);
if (!type.isValid()) {
qCWarning(KIO_KFILEWIDGETS_KFILEFILTERCOMBO) << mimeTypeName << "is not a valid MIME type";
return false;
}
return type.name().startsWith(QLatin1String("all/")) || type.isDefault();
});
if (isAllFileFilters) {
hasAllFilesFilter = true;
continue;
}
if (allTypeComments.value(filter.label()) > 1) {
QStringList mimeSuffixes;
for (const QString &mimeTypeName : filter.mimePatterns()) {
const QMimeType type = db.mimeTypeForName(mimeTypeName);
mimeSuffixes << type.suffixes();
}
const QString label = i18nc("%1 is the mimetype name, %2 is the extensions", "%1 (%2)", filter.label(), mimeSuffixes.join(QLatin1String(", ")));
KFileFilter newFilter(label, filter.filePatterns(), filter.mimePatterns());
d->m_filters.append(newFilter);
addItem(newFilter.label());
} else {
d->m_filters.append(filter);
addItem(filter.label());
}
if (filter == defaultFilter) {
setCurrentIndex(count() - 1);
}
}
if (count() == 1) {
d->m_allTypes = false;
}
if (d->m_allTypes) {
QStringList allMimePatterns;
QStringList allFilePatterns;
for (const KFileFilter &filter : std::as_const(d->m_filters)) {
allMimePatterns << filter.mimePatterns();
allFilePatterns << filter.filePatterns();
}
KFileFilter allSupportedFilesFilter;
if (count() <= 3) { // show the MIME type comments of at max 3 types
QStringList allComments;
for (const KFileFilter &filter : std::as_const(d->m_filters)) {
allComments << filter.label();
}
allSupportedFilesFilter = KFileFilter(allComments.join(delim), allFilePatterns, allMimePatterns);
} else {
allSupportedFilesFilter = KFileFilter(i18n("All Supported Files"), allFilePatterns, allMimePatterns);
d->m_hasAllSupportedFiles = true;
}
insertItem(0, allSupportedFilesFilter.label());
d->m_filters.prepend(allSupportedFilesFilter);
setCurrentIndex(0);
}
if (hasAllFilesFilter) {
addItem(i18n("All Files"));
KFileFilter allFilter(i18n("All Files"), {}, {QStringLiteral("application/octet-stream")});
d->m_filters.append(allFilter);
if (defaultFilter == allFilter) {
setCurrentIndex(count() - 1);
}
}
d->m_lastFilter = currentText();
}
KFileFilter KFileFilterCombo::currentFilter() const
{
if (currentText() != itemText(currentIndex())) {
// The user edited the text
const QList<KFileFilter> filter = KFileFilter::fromFilterString(currentText());
if (!filter.isEmpty()) {
return filter.first();
} else {
return KFileFilter();
}
} else {
if (currentIndex() == -1) {
return KFileFilter();
}
return d->m_filters[currentIndex()];
}
}
bool KFileFilterCombo::showsAllTypes() const
{
return d->m_allTypes;
}
QList<KFileFilter> KFileFilterCombo::filters() const
{
return d->m_filters;
}
void KFileFilterCombo::setCurrentFilter(const KFileFilter &filter)
{
auto it = std::find(d->m_filters.cbegin(), d->m_filters.cend(), filter);
if (it == d->m_filters.cend()) {
qCWarning(KIO_KFILEWIDGETS_KFILEFILTERCOMBO) << "KFileFilterCombo::setCurrentFilter: Could not find file filter" << filter;
setCurrentIndex(-1);
Q_EMIT filterChanged();
return;
}
setCurrentIndex(std::distance(d->m_filters.cbegin(), it));
Q_EMIT filterChanged();
}
void KFileFilterComboPrivate::slotFilterChanged()
{
m_lastFilter = q->currentText();
}
bool KFileFilterCombo::eventFilter(QObject *o, QEvent *e)
{
if (o == lineEdit() && e->type() == QEvent::FocusOut) {
if (currentText() != d->m_lastFilter) {
Q_EMIT filterChanged();
}
}
return KComboBox::eventFilter(o, e);
}
void KFileFilterCombo::setDefaultFilter(const KFileFilter &filter)
{
d->m_defaultFilter = filter;
}
KFileFilter KFileFilterCombo::defaultFilter() const
{
return d->m_defaultFilter;
}
#include "moc_kfilefiltercombo.cpp"
@@ -0,0 +1,118 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: Stephan Kulow <coolo@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KFILEFILTERCOMBO_H
#define KFILEFILTERCOMBO_H
#include "kiofilewidgets_export.h"
#include <QStringList>
#include <KComboBox>
#include <KFileFilter>
class KFileFilterComboPrivate;
/**
* @class KFileFilterCombo kfilefiltercombo.h <KFileFilterCombo>
*
* File filter combo box.
*/
class KIOFILEWIDGETS_EXPORT KFileFilterCombo : public KComboBox
{
Q_OBJECT
public:
/**
* Creates a new filter combo box.
*
* @param parent The parent widget.
*/
explicit KFileFilterCombo(QWidget *parent = nullptr);
/**
* Destroys the filter combo box.
*/
~KFileFilterCombo() override;
/**
* Sets the filters to be used.
*
* @param filters each item in the list corresponds to one item in the combobox.
* Entries for "All files" and "All supported files" are added automatically as needed.
*
* @param defaultFilter if not empty this will be the by default active filter
*
* @since 6.0
*
*/
void setFilters(const QList<KFileFilter> &filters, const KFileFilter &defaultFilter = KFileFilter());
/**
* The currently selected/active filter.
*
* @since 6.0
*/
KFileFilter currentFilter() const;
/**
* The current filters.
*
* This is not necessarily the same as the list set by setFileFilters() since
* entries for "All files" and "All supported files" are added automatically as needed.
*
* @since 6.0
*/
QList<KFileFilter> filters() const;
/**
* This method allows to set a default-filter, that is used when an
* empty filter is set. Make sure you call this before calling
* setFileFilter().
*
* By default, this is set to match all files.
* @see defaultFileFilter
*
* @since 6.0
*/
void setDefaultFilter(const KFileFilter &filter);
/**
* @return the default filter, used when an empty filter is set.
* @see setDefaultFileFilter
*
* @since 6.0
*/
KFileFilter defaultFilter() const;
/**
* Sets the current filter. Filter must match one of the filter items
* passed before to this widget.
*
* @since 6.0
*/
void setCurrentFilter(const KFileFilter &filter);
/**
* @return true if the filter's first item is the list of all MIME types
*/
bool showsAllTypes() const;
protected:
bool eventFilter(QObject *, QEvent *) override;
Q_SIGNALS:
/**
* This signal is emitted whenever the filter has been changed.
*/
void filterChanged();
private:
std::unique_ptr<KFileFilterComboPrivate> const d;
};
#endif
@@ -0,0 +1,190 @@
/*
This file is part of the KDE project.
SPDX-FileCopyrightText: 2003 Carsten Pfeiffer <pfeiffer@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include "kfilemetapreview_p.h"
#include <QLayout>
#include <QMimeDatabase>
#include <KPluginFactory>
#include <QDebug>
#include <kimagefilepreview.h>
#include <kio/previewjob.h>
bool KFileMetaPreview::s_tryAudioPreview = true;
KFileMetaPreview::KFileMetaPreview(QWidget *parent)
: KPreviewWidgetBase(parent)
, haveAudioPreview(false)
{
QHBoxLayout *layout = new QHBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
m_stack = new QStackedWidget(this);
layout->addWidget(m_stack);
// ###
// m_previewProviders.setAutoDelete( true );
initPreviewProviders();
}
KFileMetaPreview::~KFileMetaPreview()
{
}
void KFileMetaPreview::initPreviewProviders()
{
qDeleteAll(m_previewProviders);
m_previewProviders.clear();
// hardcoded so far
// image previews
KImageFilePreview *imagePreview = new KImageFilePreview(m_stack);
(void)m_stack->addWidget(imagePreview);
m_stack->setCurrentWidget(imagePreview);
resize(imagePreview->sizeHint());
const QStringList mimeTypes = imagePreview->supportedMimeTypes();
QStringList::ConstIterator it = mimeTypes.begin();
for (; it != mimeTypes.end(); ++it) {
// qDebug(".... %s", (*it).toLatin1().constData());
m_previewProviders.insert(*it, imagePreview);
}
}
KPreviewWidgetBase *KFileMetaPreview::findExistingProvider(const QString &mimeType, const QMimeType &mimeInfo) const
{
KPreviewWidgetBase *provider = m_previewProviders.value(mimeType);
if (provider) {
return provider;
}
if (mimeInfo.isValid()) {
// check MIME type inheritance
const QStringList parentMimeTypes = mimeInfo.allAncestors();
for (const QString &parentMimeType : parentMimeTypes) {
provider = m_previewProviders.value(parentMimeType);
if (provider) {
return provider;
}
}
}
// ### MIME type may be image/* for example, try that
const int index = mimeType.indexOf(QLatin1Char('/'));
if (index > 0) {
provider = m_previewProviders.value(QStringView(mimeType).left(index + 1) + QLatin1Char('*'));
if (provider) {
return provider;
}
}
return nullptr;
}
KPreviewWidgetBase *KFileMetaPreview::previewProviderFor(const QString &mimeType)
{
QMimeDatabase db;
QMimeType mimeInfo = db.mimeTypeForName(mimeType);
// qDebug("### looking for: %s", mimeType.toLatin1().constData());
// often the first highlighted item, where we can be sure, there is no plugin
// (this "folders reflect icons" is a konq-specific thing, right?)
if (mimeInfo.inherits(QStringLiteral("inode/directory"))) {
return nullptr;
}
KPreviewWidgetBase *provider = findExistingProvider(mimeType, mimeInfo);
if (provider) {
return provider;
}
// qDebug("#### didn't find anything for: %s", mimeType.toLatin1().constData());
if (s_tryAudioPreview && !mimeType.startsWith(QLatin1String("text/")) && !mimeType.startsWith(QLatin1String("image/"))) {
if (!haveAudioPreview) {
KPreviewWidgetBase *audioPreview = createAudioPreview(m_stack);
if (audioPreview) {
haveAudioPreview = true;
(void)m_stack->addWidget(audioPreview);
const QStringList mimeTypes = audioPreview->supportedMimeTypes();
QStringList::ConstIterator it = mimeTypes.begin();
for (; it != mimeTypes.end(); ++it) {
// only add non already handled MIME types
if (m_previewProviders.constFind(*it) == m_previewProviders.constEnd()) {
m_previewProviders.insert(*it, audioPreview);
}
}
}
}
}
// with the new MIME types from the audio-preview, try again
provider = findExistingProvider(mimeType, mimeInfo);
if (provider) {
return provider;
}
// The logic in this code duplicates the logic in PreviewJob.
// But why do we need multiple KPreviewWidgetBase instances anyway?
return nullptr;
}
void KFileMetaPreview::showPreview(const QUrl &url)
{
QMimeDatabase db;
QMimeType mt = db.mimeTypeForUrl(url);
KPreviewWidgetBase *provider = previewProviderFor(mt.name());
if (provider) {
if (provider != m_stack->currentWidget()) { // stop the previous preview
clearPreview();
}
m_stack->setEnabled(true);
m_stack->setCurrentWidget(provider);
provider->showPreview(url);
} else {
clearPreview();
m_stack->setEnabled(false);
}
}
void KFileMetaPreview::clearPreview()
{
if (m_stack->currentWidget()) {
static_cast<KPreviewWidgetBase *>(m_stack->currentWidget())->clearPreview();
}
}
void KFileMetaPreview::addPreviewProvider(const QString &mimeType, KPreviewWidgetBase *provider)
{
m_previewProviders.insert(mimeType, provider);
}
void KFileMetaPreview::clearPreviewProviders()
{
for (auto it = m_previewProviders.cbegin(); it != m_previewProviders.cend(); ++it) {
m_stack->removeWidget(it.value());
}
qDeleteAll(m_previewProviders);
m_previewProviders.clear();
}
// static
KPreviewWidgetBase *KFileMetaPreview::createAudioPreview(QWidget *parent)
{
KPluginMetaData data(QStringLiteral("kfileaudiopreview"));
if (auto plugin = KPluginFactory::instantiatePlugin<KPreviewWidgetBase>(data, parent).plugin) {
plugin->setObjectName(QStringLiteral("kfileaudiopreview"));
return plugin;
} else {
s_tryAudioPreview = false;
return nullptr;
}
}
#include "moc_kfilemetapreview_p.cpp"
@@ -0,0 +1,52 @@
/*
This file is part of the KDE project.
SPDX-FileCopyrightText: 2003 Carsten Pfeiffer <pfeiffer@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KFILEMETAPREVIEW_H
#define KFILEMETAPREVIEW_H
#include <QHash>
#include <QMimeType>
#include <QStackedWidget>
#include <kpreviewwidgetbase.h>
// Internal, but exported for KDirOperator (kfile) and KPreviewProps (kdelibs4support)
class KIOFILEWIDGETS_EXPORT KFileMetaPreview : public KPreviewWidgetBase
{
Q_OBJECT
public:
explicit KFileMetaPreview(QWidget *parent);
~KFileMetaPreview() override;
virtual void addPreviewProvider(const QString &mimeType, KPreviewWidgetBase *provider);
virtual void clearPreviewProviders();
public Q_SLOTS:
void showPreview(const QUrl &url) override;
void clearPreview() override;
protected:
virtual KPreviewWidgetBase *previewProviderFor(const QString &mimeType);
private:
void initPreviewProviders();
KPreviewWidgetBase *findExistingProvider(const QString &mimeType, const QMimeType &mimeInfo) const;
QStackedWidget *m_stack;
QHash<QString, KPreviewWidgetBase *> m_previewProviders;
bool haveAudioPreview;
// may return 0L
static KPreviewWidgetBase *createAudioPreview(QWidget *parent);
static bool s_tryAudioPreview;
private:
class KFileMetaPreviewPrivate;
KFileMetaPreviewPrivate *d;
};
#endif // KFILEMETAPREVIEW_H
@@ -0,0 +1,209 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2001, 2002, 2003 Carsten Pfeiffer <pfeiffer@kde.org>
SPDX-FileCopyrightText: 2007 Kevin Ottens <ervin@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include "kfileplaceeditdialog.h"
#include <KAboutData>
#include <KConfig>
#include <KIconButton>
#include <KLineEdit> // For KUrlRequester::lineEdit()
#include <KLocalizedString>
#include <kio/global.h>
#include <kprotocolinfo.h>
#include <kurlrequester.h>
#include <QApplication>
#include <QCheckBox>
#include <QDialogButtonBox>
#include <QFormLayout>
#include <qdrawutil.h>
#include <KConfigGroup>
#include <qplatformdefs.h>
bool KFilePlaceEditDialog::getInformation(bool allowGlobal,
QUrl &url,
QString &label,
QString &icon,
bool isAddingNewPlace,
bool &appLocal,
int iconSize,
QWidget *parent)
{
KFilePlaceEditDialog *dialog = new KFilePlaceEditDialog(allowGlobal, url, label, icon, isAddingNewPlace, appLocal, iconSize, parent);
if (dialog->exec() == QDialog::Accepted) {
// set the return parameters
url = dialog->url();
label = dialog->label();
icon = dialog->icon();
appLocal = dialog->applicationLocal();
delete dialog;
return true;
}
delete dialog;
return false;
}
KFilePlaceEditDialog::KFilePlaceEditDialog(bool allowGlobal,
const QUrl &url,
const QString &label,
const QString &icon,
bool isAddingNewPlace,
bool appLocal,
int iconSize,
QWidget *parent)
: QDialog(parent)
, m_iconButton(nullptr)
{
if (isAddingNewPlace) {
setWindowTitle(i18n("Add Places Entry"));
} else {
setWindowTitle(i18n("Edit Places Entry"));
}
setModal(true);
QVBoxLayout *box = new QVBoxLayout(this);
QFormLayout *layout = new QFormLayout();
box->addLayout(layout);
QString whatsThisText = i18n(
"<qt>This is the text that will appear in the Places panel.<br /><br />"
"The label should consist of one or two words "
"that will help you remember what this entry refers to. "
"If you do not enter a label, it will be derived from "
"the location's URL.</qt>");
m_labelEdit = new QLineEdit(this);
layout->addRow(i18n("L&abel:"), m_labelEdit);
m_labelEdit->setText(label);
m_labelEdit->setPlaceholderText(i18n("Enter descriptive label here"));
m_labelEdit->setWhatsThis(whatsThisText);
layout->labelForField(m_labelEdit)->setWhatsThis(whatsThisText);
whatsThisText = i18n(
"<qt>This is the location associated with the entry. Any valid URL may be used. For example:<br /><br />"
"%1<br />http://www.kde.org<br />ftp://ftp.kde.org/pub/kde/stable<br /><br />"
"By clicking on the button next to the text edit box you can browse to an "
"appropriate URL.</qt>",
QDir::homePath());
m_urlEdit = new KUrlRequester(url, this);
m_urlEdit->setMode(KFile::Directory);
layout->addRow(i18n("&Location:"), m_urlEdit);
m_urlEdit->setWhatsThis(whatsThisText);
layout->labelForField(m_urlEdit)->setWhatsThis(whatsThisText);
// Room for at least 40 chars (average char width is half of height)
m_urlEdit->setMinimumWidth(m_urlEdit->fontMetrics().height() * (40 / 2));
whatsThisText = i18n(
"<qt>This is the icon that will appear in the Places panel.<br /><br />"
"Click on the button to select a different icon.</qt>");
m_iconButton = new KIconButton(this);
m_iconButton->setObjectName(QStringLiteral("icon button"));
m_iconButton->setIconSize(iconSize);
m_iconButton->setIconType(KIconLoader::NoGroup, KIconLoader::Place);
if (icon.isEmpty()) {
m_iconButton->setIcon(KIO::iconNameForUrl(url));
} else {
m_iconButton->setIcon(icon);
}
m_iconButton->setWhatsThis(whatsThisText);
if (url.scheme() == QLatin1String("trash")) {
// Since there are separate trash icons when it is empty/non-empty,
// the trash item's icon is made non-editable for simplicity
m_iconButton->hide();
// making the trash item's url editable misleads users into
// thinking that the actual trash location is configurable here
m_urlEdit->setDisabled(true);
} else {
layout->addRow(i18n("Choose an &icon:"), m_iconButton);
layout->labelForField(m_iconButton)->setWhatsThis(whatsThisText);
}
if (allowGlobal) {
QString appName;
appName = QGuiApplication::applicationDisplayName();
if (appName.isEmpty()) {
appName = QCoreApplication::applicationName();
}
m_appLocal = new QCheckBox(i18n("&Only show when using this application (%1)", appName), this);
m_appLocal->setChecked(appLocal);
m_appLocal->setWhatsThis(
i18n("<qt>Select this setting if you want this "
"entry to show only when using the current application (%1).<br /><br />"
"If this setting is not selected, the entry will be available in all "
"applications.</qt>",
appName));
box->addWidget(m_appLocal);
} else {
m_appLocal = nullptr;
}
connect(m_urlEdit->lineEdit(), &QLineEdit::textChanged, this, &KFilePlaceEditDialog::urlChanged);
if (!label.isEmpty()) {
// editing existing entry
m_labelEdit->setFocus();
} else {
// new entry
m_urlEdit->setFocus();
}
m_buttonBox = new QDialogButtonBox(this);
m_buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
connect(m_buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
box->addWidget(m_buttonBox);
}
KFilePlaceEditDialog::~KFilePlaceEditDialog()
{
}
void KFilePlaceEditDialog::urlChanged(const QString &text)
{
m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!text.isEmpty());
}
QUrl KFilePlaceEditDialog::url() const
{
return m_urlEdit->url();
}
QString KFilePlaceEditDialog::label() const
{
if (!m_labelEdit->text().isEmpty()) {
return m_labelEdit->text();
}
// derive descriptive label from the URL
QUrl url = m_urlEdit->url();
if (!url.fileName().isEmpty()) {
return url.fileName();
}
if (!url.host().isEmpty()) {
return url.host();
}
return url.scheme();
}
QString KFilePlaceEditDialog::icon() const
{
return m_iconButton->icon();
}
bool KFilePlaceEditDialog::applicationLocal() const
{
if (!m_appLocal) {
return true;
}
return m_appLocal->isChecked();
}
#include "moc_kfileplaceeditdialog.cpp"
@@ -0,0 +1,137 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2001, 2002, 2003 Carsten Pfeiffer <pfeiffer@kde.org>
SPDX-FileCopyrightText: 2007 Kevin Ottens <ervin@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KFILEPLACEEDITDIALOG_H
#define KFILEPLACEEDITDIALOG_H
#include "kiofilewidgets_export.h"
#include <KIconLoader>
#include <QDialog>
#include <QUrl>
class QCheckBox;
class QDialogButtonBox;
class QLineEdit;
class KIconButton;
class KUrlRequester;
/**
* @class KFilePlaceEditDialog kfileplaceeditdialog.h <KFilePlaceEditDialog>
*
* A dialog that allows editing entries of a KFilePlacesModel.
* The dialog offers to configure a given url, label and icon.
* See the class-method getInformation() for easy usage.
*
* @author Carsten Pfeiffer <pfeiffer@kde.org>
* @since 5.53
*/
class KIOFILEWIDGETS_EXPORT KFilePlaceEditDialog : public QDialog
{
Q_OBJECT
public:
/**
* A convenience method to show the dialog and retrieve all the
* properties via the given parameters. The parameters are used to
* initialize the dialog and then return the user-configured values.
*
* @p allowGlobal if you set this to true, the dialog will have a checkbox
* for the user to decide if he wants the entry to be
* available globally or just for the current application.
* @p url the url of the item
* @p label a short, translated description of the item
* @p icon an icon for the item
* @p appLocal tells whether the item should be local for this application
* or be available globally
* @p iconSize determines the size of the icon that is shown/selectable
* @p parent the parent-widget for the dialog
*
* If you leave the icon empty, the default icon for the given url will be
* used (KMimeType::pixmapForUrl()).
*/
static bool
getInformation(bool allowGlobal, QUrl &url, QString &label, QString &icon, bool isAddingNewPlace, bool &appLocal, int iconSize, QWidget *parent = nullptr);
/**
* Constructs a KFilePlaceEditDialog.
*
* @p allowGlobal if you set this to true, the dialog will have a checkbox
* for the user to decide if he wants the entry to be
* available globally or just for the current application.
* @p url the url of the item
* @p label a short, translated description of the item
* @p icon an icon for the item
* @p appLocal tells whether the item should be local for this application
* or be available globally
* @p iconSize determines the size of the icon that is shown/selectable
* @p parent the parent-widget for the dialog
*
* If you leave the icon empty, the default icon for the given url will be
* used (KMimeType::pixmapForUrl()).
*/
KFilePlaceEditDialog(bool allowGlobal,
const QUrl &url,
const QString &label,
const QString &icon,
bool isAddingNewPlace,
bool appLocal = true,
int iconSize = KIconLoader::SizeMedium,
QWidget *parent = nullptr);
/**
* Destroys the dialog.
*/
~KFilePlaceEditDialog() override;
/**
* @returns the configured url
*/
QUrl url() const;
/**
* @returns the configured label
*/
QString label() const;
/**
* @returns the configured icon
*/
QString icon() const;
/**
* @returns whether the item should be local to the application or global.
* If allowGlobal was set to false in the constructor, this will always
* return true.
*/
bool applicationLocal() const;
public Q_SLOTS:
void urlChanged(const QString &);
private:
/**
* The KUrlRequester used for editing the url
*/
KUrlRequester *m_urlEdit;
/**
* The QLineEdit used for editing the label
*/
QLineEdit *m_labelEdit;
/**
* The KIconButton to configure the icon
*/
KIconButton *m_iconButton;
/**
* The QCheckBox to modify the local/global setting
*/
QCheckBox *m_appLocal;
QDialogButtonBox *m_buttonBox;
};
#endif // KFILEPLACEEDITDIALOG_H
@@ -0,0 +1,631 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2007 Kevin Ottens <ervin@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include "kfileplacesitem_p.h"
#include <QDateTime>
#include <QDir>
#include <QIcon>
#include <KBookmarkManager>
#include <KConfig>
#include <KConfigGroup>
#include <KIconUtils>
#include <KLocalizedString>
#include <KMountPoint>
#include <kprotocolinfo.h>
#include <solid/block.h>
#include <solid/genericinterface.h>
#include <solid/networkshare.h>
#include <solid/opticaldisc.h>
#include <solid/opticaldrive.h>
#include <solid/portablemediaplayer.h>
#include <solid/storageaccess.h>
#include <solid/storagedrive.h>
#include <solid/storagevolume.h>
static bool isTrash(const KBookmark &bk)
{
return bk.url().toString() == QLatin1String("trash:/");
}
KFilePlacesItem::KFilePlacesItem(KBookmarkManager *manager, const QString &address, const QString &udi, KFilePlacesModel *parent)
: QObject(static_cast<QObject *>(parent))
, m_manager(manager)
, m_folderIsEmpty(true)
, m_isCdrom(false)
, m_isAccessible(false)
, m_isTeardownAllowed(false)
, m_isTeardownOverlayRecommended(false)
, m_isTeardownInProgress(false)
, m_isSetupInProgress(false)
, m_isEjectInProgress(false)
, m_isReadOnly(false)
{
updateDeviceInfo(udi);
setBookmark(m_manager->findByAddress(address));
if (udi.isEmpty() && m_bookmark.metaDataItem(QStringLiteral("ID")).isEmpty()) {
m_bookmark.setMetaDataItem(QStringLiteral("ID"), generateNewId());
} else if (udi.isEmpty()) {
if (isTrash(m_bookmark)) {
KConfig cfg(QStringLiteral("trashrc"), KConfig::SimpleConfig);
const KConfigGroup group = cfg.group(QStringLiteral("Status"));
m_folderIsEmpty = group.readEntry("Empty", true);
}
}
// Hide SSHFS network device mounted by kdeconnect, since we already have the kdeconnect:// place.
if (isDevice() && m_access && device().vendor() == QLatin1String("fuse.sshfs")) {
const QString storageFilePath = m_access->filePath();
// Not using findByPath() as it resolves symlinks, potentially blocking,
// but here we know we query for an existing actual mount point.
const auto mountPoints = KMountPoint::currentMountPoints();
auto it = std::find_if(mountPoints.cbegin(), mountPoints.cend(), [&storageFilePath](const KMountPoint::Ptr &mountPoint) {
return mountPoint->mountPoint() == storageFilePath;
});
if (it != mountPoints.cend()) {
if ((*it)->mountedFrom().startsWith(QLatin1String("kdeconnect@"))) {
// Hide only if the user never set the "Hide" checkbox on the device.
if (m_bookmark.metaDataItem(QStringLiteral("IsHidden")).isEmpty()) {
setHidden(true);
}
}
}
}
}
KFilePlacesItem::~KFilePlacesItem()
{
}
QString KFilePlacesItem::id() const
{
if (isDevice()) {
return bookmark().metaDataItem(QStringLiteral("UDI"));
} else {
return bookmark().metaDataItem(QStringLiteral("ID"));
}
}
bool KFilePlacesItem::hasSupportedScheme(const QStringList &schemes) const
{
if (schemes.isEmpty()) {
return true;
}
// StorageAccess is always local, doesn't need to be accessible to know this
if (m_access && schemes.contains(QLatin1String("file"))) {
return true;
}
if (m_networkShare && schemes.contains(m_networkShare->url().scheme())) {
return true;
}
if (m_player) {
const QStringList protocols = m_player->supportedProtocols();
for (const QString &protocol : protocols) {
if (schemes.contains(protocol)) {
return true;
}
}
}
return false;
}
bool KFilePlacesItem::isDevice() const
{
return !bookmark().metaDataItem(QStringLiteral("UDI")).isEmpty();
}
KFilePlacesModel::DeviceAccessibility KFilePlacesItem::deviceAccessibility() const
{
if (m_isTeardownInProgress || m_isEjectInProgress) {
return KFilePlacesModel::TeardownInProgress;
} else if (m_isSetupInProgress) {
return KFilePlacesModel::SetupInProgress;
} else if (m_isAccessible) {
return KFilePlacesModel::Accessible;
} else {
return KFilePlacesModel::SetupNeeded;
}
}
bool KFilePlacesItem::isTeardownAllowed() const
{
return m_isTeardownAllowed;
}
bool KFilePlacesItem::isTeardownOverlayRecommended() const
{
return m_isTeardownOverlayRecommended;
}
bool KFilePlacesItem::isEjectAllowed() const
{
return m_isCdrom;
}
KBookmark KFilePlacesItem::bookmark() const
{
return m_bookmark;
}
void KFilePlacesItem::setBookmark(const KBookmark &bookmark)
{
m_bookmark = bookmark;
if (m_device.isValid()) {
m_bookmark.setMetaDataItem(QStringLiteral("UDI"), m_device.udi());
if (m_volume && !m_volume->uuid().isEmpty()) {
m_bookmark.setMetaDataItem(QStringLiteral("uuid"), m_volume->uuid());
}
}
if (bookmark.metaDataItem(QStringLiteral("isSystemItem")) == QLatin1String("true")) {
// This context must stay as it is - the translated system bookmark names
// are created with 'KFile System Bookmarks' as their context, so this
// ensures the right string is picked from the catalog.
// (coles, 13th May 2009)
m_text = i18nc("KFile System Bookmarks", bookmark.text().toUtf8().data());
} else {
m_text = bookmark.text();
}
if (!isDevice()) {
const QString protocol = bookmark.url().scheme();
if (protocol == QLatin1String("timeline") || protocol == QLatin1String("recentlyused")) {
m_groupType = KFilePlacesModel::RecentlySavedType;
} else if (protocol.contains(QLatin1String("search"))) {
m_groupType = KFilePlacesModel::SearchForType;
} else if (protocol == QLatin1String("bluetooth") || protocol == QLatin1String("obexftp") || protocol == QLatin1String("kdeconnect")) {
m_groupType = KFilePlacesModel::DevicesType;
} else if (protocol == QLatin1String("tags")) {
m_groupType = KFilePlacesModel::TagsType;
} else if (protocol == QLatin1String("remote") || KProtocolInfo::protocolClass(protocol) != QLatin1String(":local")) {
m_groupType = KFilePlacesModel::RemoteType;
} else {
m_groupType = KFilePlacesModel::PlacesType;
}
} else {
if (m_drive && (m_drive->isHotpluggable() || m_drive->isRemovable())) {
m_groupType = KFilePlacesModel::RemovableDevicesType;
} else if (m_networkShare) {
m_groupType = KFilePlacesModel::RemoteType;
} else {
m_groupType = KFilePlacesModel::DevicesType;
}
}
switch (m_groupType) {
case KFilePlacesModel::PlacesType:
m_groupName = i18nc("@item", "Places");
break;
case KFilePlacesModel::RemoteType:
m_groupName = i18nc("@item", "Remote");
break;
case KFilePlacesModel::RecentlySavedType:
m_groupName = i18nc("@item The place group section name for recent dynamic lists", "Recent");
break;
case KFilePlacesModel::SearchForType:
m_groupName = i18nc("@item", "Search For");
break;
case KFilePlacesModel::DevicesType:
m_groupName = i18nc("@item", "Devices");
break;
case KFilePlacesModel::RemovableDevicesType:
m_groupName = i18nc("@item", "Removable Devices");
break;
case KFilePlacesModel::TagsType:
m_groupName = i18nc("@item", "Tags");
break;
case KFilePlacesModel::UnknownType:
Q_UNREACHABLE();
break;
}
}
Solid::Device KFilePlacesItem::device() const
{
return m_device;
}
QVariant KFilePlacesItem::data(int role) const
{
if (role == KFilePlacesModel::GroupRole) {
return QVariant(m_groupName);
} else if (role != KFilePlacesModel::HiddenRole && role != Qt::BackgroundRole && isDevice()) {
return deviceData(role);
} else {
return bookmarkData(role);
}
}
KFilePlacesModel::GroupType KFilePlacesItem::groupType() const
{
return m_groupType;
}
bool KFilePlacesItem::isHidden() const
{
return m_bookmark.metaDataItem(QStringLiteral("IsHidden")) == QLatin1String("true");
}
void KFilePlacesItem::setHidden(bool hide)
{
if (m_bookmark.isNull() || isHidden() == hide) {
return;
}
m_bookmark.setMetaDataItem(QStringLiteral("IsHidden"), hide ? QStringLiteral("true") : QStringLiteral("false"));
}
QVariant KFilePlacesItem::bookmarkData(int role) const
{
KBookmark b = bookmark();
if (b.isNull()) {
return QVariant();
}
switch (role) {
case Qt::DisplayRole:
return m_text;
case Qt::DecorationRole:
return QIcon::fromTheme(iconNameForBookmark(b));
case Qt::ToolTipRole: {
const KFilePlacesModel::GroupType type = groupType();
// Don't display technical gibberish in the URL, particularly search.
if (type != KFilePlacesModel::RecentlySavedType && type != KFilePlacesModel::SearchForType && type != KFilePlacesModel::TagsType) {
return b.url().toDisplayString(QUrl::PreferLocalFile);
}
return QString();
}
case Qt::BackgroundRole:
if (isHidden()) {
return QColor(Qt::lightGray);
} else {
return QVariant();
}
case KFilePlacesModel::UrlRole:
return b.url();
case KFilePlacesModel::SetupNeededRole:
return false;
case KFilePlacesModel::HiddenRole:
return isHidden();
case KFilePlacesModel::IconNameRole:
return iconNameForBookmark(b);
default:
return QVariant();
}
}
QVariant KFilePlacesItem::deviceData(int role) const
{
Solid::Device d = device();
if (d.isValid()) {
switch (role) {
case Qt::DisplayRole:
if (m_deviceDisplayName.isEmpty()) {
m_deviceDisplayName = d.displayName();
}
return m_deviceDisplayName;
case Qt::DecorationRole:
// qDebug() << "adding emblems" << m_emblems << "to device icon" << m_deviceIconName;
return KIconUtils::addOverlays(m_deviceIconName, m_emblems);
case Qt::ToolTipRole: {
if (m_access && m_isAccessible) {
// For loop devices, show backing file path rather than /dev/loop123.
QString mountedFrom = m_backingFile;
if (mountedFrom.isEmpty() && m_block) {
mountedFrom = m_block->device();
}
if (!mountedFrom.isEmpty()) {
return i18nc("@info:tooltip path (mounted from)", "%1 (from %2)", m_access->filePath(), mountedFrom);
}
} else if (!m_backingFile.isEmpty()) {
return m_backingFile;
} else if (m_block) {
return m_block->device();
}
return QString();
}
case KFilePlacesModel::UrlRole:
if (m_access) {
const QString path = m_access->filePath();
return path.isEmpty() ? QUrl() : QUrl::fromLocalFile(path);
} else if (m_disc && (m_disc->availableContent() & Solid::OpticalDisc::Audio) != 0) {
Solid::Block *block = d.as<Solid::Block>();
if (block) {
QString device = block->device();
return QUrl(QStringLiteral("audiocd:/?device=%1").arg(device));
}
// We failed to get the block device. Assume audiocd:/ can
// figure it out, but cannot handle multiple disc drives.
// See https://bugs.kde.org/show_bug.cgi?id=314544#c40
return QUrl(QStringLiteral("audiocd:/"));
} else if (m_player) {
const QStringList protocols = m_player->supportedProtocols();
if (!protocols.isEmpty()) {
const QString protocol = protocols.first();
if (protocol == QLatin1String("mtp")) {
return QUrl(QStringLiteral("%1:udi=%2").arg(protocol, d.udi()));
} else {
QUrl url;
url.setScheme(protocol);
url.setHost(d.udi().section(QLatin1Char('/'), -1));
url.setPath(QStringLiteral("/"));
return url;
}
}
return QVariant();
} else {
return QVariant();
}
case KFilePlacesModel::SetupNeededRole:
if (m_access) {
return !m_isAccessible;
} else {
return QVariant();
}
case KFilePlacesModel::TeardownAllowedRole:
if (m_access) {
return m_isTeardownAllowed;
} else {
return QVariant();
}
case KFilePlacesModel::EjectAllowedRole:
return m_isAccessible && m_isCdrom;
case KFilePlacesModel::TeardownOverlayRecommendedRole:
return m_isTeardownOverlayRecommended;
case KFilePlacesModel::DeviceAccessibilityRole:
return deviceAccessibility();
case KFilePlacesModel::FixedDeviceRole: {
if (m_drive != nullptr) {
return !m_drive->isHotpluggable() && !m_drive->isRemovable();
}
return true;
}
case KFilePlacesModel::CapacityBarRecommendedRole:
return m_isAccessible && !m_isCdrom && !m_networkShare && !m_isReadOnly;
case KFilePlacesModel::IconNameRole:
return m_deviceIconName;
default:
return QVariant();
}
} else {
return QVariant();
}
}
KBookmark KFilePlacesItem::createBookmark(KBookmarkManager *manager, const QString &label, const QUrl &url, const QString &iconName, KFilePlacesItem *after)
{
KBookmarkGroup root = manager->root();
if (root.isNull()) {
return KBookmark();
}
QString empty_icon = iconName;
if (url.toString() == QLatin1String("trash:/")) {
if (empty_icon.endsWith(QLatin1String("-full"))) {
empty_icon.chop(5);
} else if (empty_icon.isEmpty()) {
empty_icon = QStringLiteral("user-trash");
}
}
KBookmark bookmark = root.addBookmark(label, url, empty_icon);
bookmark.setMetaDataItem(QStringLiteral("ID"), generateNewId());
if (after) {
root.moveBookmark(bookmark, after->bookmark());
}
return bookmark;
}
KBookmark KFilePlacesItem::createSystemBookmark(KBookmarkManager *manager,
const char *untranslatedLabel,
const QUrl &url,
const QString &iconName,
const KBookmark &after)
{
KBookmark bookmark = createBookmark(manager, QString::fromUtf8(untranslatedLabel), url, iconName);
if (!bookmark.isNull()) {
bookmark.setMetaDataItem(QStringLiteral("isSystemItem"), QStringLiteral("true"));
}
if (!after.isNull()) {
manager->root().moveBookmark(bookmark, after);
}
return bookmark;
}
KBookmark KFilePlacesItem::createDeviceBookmark(KBookmarkManager *manager, const Solid::Device &device)
{
KBookmarkGroup root = manager->root();
if (root.isNull()) {
return KBookmark();
}
KBookmark bookmark = root.createNewSeparator();
bookmark.setMetaDataItem(QStringLiteral("UDI"), device.udi());
bookmark.setMetaDataItem(QStringLiteral("isSystemItem"), QStringLiteral("true"));
const auto storage = device.as<Solid::StorageVolume>();
if (storage) {
bookmark.setMetaDataItem(QStringLiteral("uuid"), storage->uuid());
}
return bookmark;
}
KBookmark KFilePlacesItem::createTagBookmark(KBookmarkManager *manager, const QString &tag)
{
// TODO: Currently KFilePlacesItem::setBookmark() only decides by the "isSystemItem" property
// if the label text should be looked up for translation. So there is a small risk that
// labelTexts which match existing untranslated system labels accidentally get translated.
KBookmark bookmark = createBookmark(manager, tag, QUrl(QLatin1String("tags:/") + tag), QStringLiteral("tag"));
if (!bookmark.isNull()) {
bookmark.setMetaDataItem(QStringLiteral("tag"), tag);
bookmark.setMetaDataItem(QStringLiteral("isSystemItem"), QStringLiteral("true"));
}
return bookmark;
}
QString KFilePlacesItem::generateNewId()
{
static int count = 0;
// return QString::number(count++);
return QString::number(QDateTime::currentSecsSinceEpoch()) + QLatin1Char('/') + QString::number(count++);
// return QString::number(QDateTime::currentSecsSinceEpoch())
// + '/' + QString::number(qrand());
}
bool KFilePlacesItem::updateDeviceInfo(const QString &udi)
{
if (m_device.udi() == udi) {
return false;
}
if (m_access) {
m_access->disconnect(this);
}
if (m_opticalDrive) {
m_opticalDrive->disconnect(this);
}
m_device = Solid::Device(udi);
if (m_device.isValid()) {
m_access = m_device.as<Solid::StorageAccess>();
m_volume = m_device.as<Solid::StorageVolume>();
m_block = m_device.as<Solid::Block>();
m_disc = m_device.as<Solid::OpticalDisc>();
m_player = m_device.as<Solid::PortableMediaPlayer>();
m_networkShare = m_device.as<Solid::NetworkShare>();
m_deviceIconName = m_device.icon();
m_emblems = m_device.emblems();
if (auto *genericIface = m_device.as<Solid::GenericInterface>()) {
m_backingFile = genericIface->property(QStringLiteral("BackingFile")).toString();
}
m_drive = nullptr;
m_opticalDrive = nullptr;
Solid::Device parentDevice = m_device;
while (parentDevice.isValid() && !m_drive) {
m_drive = parentDevice.as<Solid::StorageDrive>();
m_opticalDrive = parentDevice.as<Solid::OpticalDrive>();
parentDevice = parentDevice.parent();
}
if (m_access) {
connect(m_access.data(), &Solid::StorageAccess::setupRequested, this, [this] {
m_isSetupInProgress = true;
Q_EMIT itemChanged(id(), {KFilePlacesModel::DeviceAccessibilityRole});
});
connect(m_access.data(), &Solid::StorageAccess::setupDone, this, [this] {
m_isSetupInProgress = false;
Q_EMIT itemChanged(id(), {KFilePlacesModel::DeviceAccessibilityRole});
});
connect(m_access.data(), &Solid::StorageAccess::teardownRequested, this, [this] {
m_isTeardownInProgress = true;
Q_EMIT itemChanged(id(), {KFilePlacesModel::DeviceAccessibilityRole});
});
connect(m_access.data(), &Solid::StorageAccess::teardownDone, this, [this] {
m_isTeardownInProgress = false;
Q_EMIT itemChanged(id(), {KFilePlacesModel::DeviceAccessibilityRole});
});
connect(m_access.data(), &Solid::StorageAccess::accessibilityChanged, this, &KFilePlacesItem::onAccessibilityChanged);
onAccessibilityChanged(m_access->isAccessible());
}
if (m_opticalDrive) {
connect(m_opticalDrive.data(), &Solid::OpticalDrive::ejectRequested, this, [this] {
m_isEjectInProgress = true;
Q_EMIT itemChanged(id(), {KFilePlacesModel::DeviceAccessibilityRole});
});
connect(m_opticalDrive.data(), &Solid::OpticalDrive::ejectDone, this, [this] {
m_isEjectInProgress = false;
Q_EMIT itemChanged(id(), {KFilePlacesModel::DeviceAccessibilityRole});
});
}
} else {
m_access = nullptr;
m_volume = nullptr;
m_disc = nullptr;
m_player = nullptr;
m_drive = nullptr;
m_opticalDrive = nullptr;
m_networkShare = nullptr;
m_deviceIconName.clear();
m_emblems.clear();
}
return true;
}
void KFilePlacesItem::onAccessibilityChanged(bool isAccessible)
{
m_isAccessible = isAccessible;
m_isCdrom = m_device.is<Solid::OpticalDrive>() || m_opticalDrive || (m_volume && m_volume->fsType() == QLatin1String("iso9660"));
m_emblems = m_device.emblems();
if (auto generic = m_device.as<Solid::GenericInterface>()) {
// TODO add Solid API for this.
m_isReadOnly = generic->property(QStringLiteral("ReadOnly")).toBool();
}
m_isTeardownAllowed = isAccessible;
if (m_isTeardownAllowed) {
if (m_access->filePath() == QDir::rootPath()) {
m_isTeardownAllowed = false;
} else {
const auto homeDevice = Solid::Device::storageAccessFromPath(QDir::homePath());
const auto *homeAccess = homeDevice.as<Solid::StorageAccess>();
if (homeAccess && m_access->filePath() == homeAccess->filePath()) {
m_isTeardownAllowed = false;
}
}
}
m_isTeardownOverlayRecommended = m_isTeardownAllowed && !m_networkShare;
if (m_isTeardownOverlayRecommended) {
if (m_drive && !m_drive->isHotpluggable() && !m_drive->isRemovable()) {
m_isTeardownOverlayRecommended = false;
}
}
Q_EMIT itemChanged(id());
}
QString KFilePlacesItem::iconNameForBookmark(const KBookmark &bookmark) const
{
if (!m_folderIsEmpty && isTrash(bookmark)) {
return bookmark.icon() + QLatin1String("-full");
} else {
return bookmark.icon();
}
}
#include "moc_kfileplacesitem_p.cpp"
@@ -0,0 +1,114 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2007 Kevin Ottens <ervin@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KFILEPLACESITEM_P_H
#define KFILEPLACESITEM_P_H
#include "kfileplacesmodel.h"
#include <KBookmark>
#include <KBookmarkManager>
#include <QObject>
#include <QPointer>
#include <QStringList>
#include <QUrl>
#include <solid/device.h>
class KDirLister;
namespace Solid
{
class Block;
class StorageAccess;
class StorageVolume;
class StorageDrive;
class NetworkShare;
class OpticalDrive;
class OpticalDisc;
class PortableMediaPlayer;
}
class KFilePlacesItem : public QObject
{
Q_OBJECT
public:
KFilePlacesItem(KBookmarkManager *manager, const QString &address, const QString &udi, KFilePlacesModel *parent);
~KFilePlacesItem() override;
QString id() const;
bool isDevice() const;
KFilePlacesModel::DeviceAccessibility deviceAccessibility() const;
bool isTeardownAllowed() const;
bool isTeardownOverlayRecommended() const;
bool isEjectAllowed() const;
KBookmark bookmark() const;
void setBookmark(const KBookmark &bookmark);
Solid::Device device() const;
QVariant data(int role) const;
KFilePlacesModel::GroupType groupType() const;
bool isHidden() const;
void setHidden(bool hide);
bool hasSupportedScheme(const QStringList &schemes) const;
static KBookmark
createBookmark(KBookmarkManager *manager, const QString &label, const QUrl &url, const QString &iconName, KFilePlacesItem *after = nullptr);
/**
* @param untranslatedLabel text for label. If to be translated, should be set by kli18nc("KFile System Bookmarks", "Label text").untranslatedText().
*/
static KBookmark createSystemBookmark(KBookmarkManager *manager,
const char *untranslatedLabel,
const QUrl &url,
const QString &iconName,
const KBookmark &after = KBookmark());
static KBookmark createDeviceBookmark(KBookmarkManager *manager, const Solid::Device &device);
static KBookmark createTagBookmark(KBookmarkManager *manager, const QString &tag);
Q_SIGNALS:
void itemChanged(const QString &id, const QList<int> &roles = {});
private Q_SLOTS:
void onAccessibilityChanged(bool);
private:
QVariant bookmarkData(int role) const;
QVariant deviceData(int role) const;
QString iconNameForBookmark(const KBookmark &bookmark) const;
static QString generateNewId();
bool updateDeviceInfo(const QString &udi);
KBookmarkManager *m_manager;
KBookmark m_bookmark;
bool m_folderIsEmpty;
bool m_isCdrom;
bool m_isAccessible;
bool m_isTeardownAllowed;
bool m_isTeardownOverlayRecommended;
bool m_isTeardownInProgress;
bool m_isSetupInProgress;
bool m_isEjectInProgress;
bool m_isReadOnly;
QString m_text;
Solid::Device m_device;
QPointer<Solid::StorageAccess> m_access;
QPointer<Solid::StorageVolume> m_volume;
QPointer<Solid::StorageDrive> m_drive;
QPointer<Solid::Block> m_block;
QPointer<Solid::OpticalDrive> m_opticalDrive;
QPointer<Solid::OpticalDisc> m_disc;
QPointer<Solid::PortableMediaPlayer> m_player;
QPointer<Solid::NetworkShare> m_networkShare;
QString m_deviceIconName;
QStringList m_emblems;
QString m_backingFile;
KFilePlacesModel::GroupType m_groupType = KFilePlacesModel::UnknownType;
QString m_groupName;
mutable QString m_deviceDisplayName;
};
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,496 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2007 Kevin Ottens <ervin@kde.org>
SPDX-FileCopyrightText: 2007 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2023 Harald Sitter <sitter@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KFILEPLACESMODEL_H
#define KFILEPLACESMODEL_H
#include "kiofilewidgets_export.h"
#include <KBookmark>
#include <QAbstractItemModel>
#include <QUrl>
#include <solid/device.h>
#include <solid/solidnamespace.h>
#include <memory>
class KFilePlacesModelPrivate;
class KBookmarkManager;
class QMimeData;
class QAction;
/**
* @class KFilePlacesModel kfileplacesmodel.h <KFilePlacesModel>
*
* This class is a list view model. Each entry represents a "place"
* where user can access files. Only relevant when
* used with QListView or QTableView.
* @note This class is since 6.0 re-entrant
*/
class KIOFILEWIDGETS_EXPORT KFilePlacesModel : public QAbstractItemModel
{
Q_OBJECT
Q_PROPERTY(QStringList supportedSchemes READ supportedSchemes WRITE setSupportedSchemes NOTIFY supportedSchemesChanged)
public:
// Note: run printf "0x%08X\n" $(($RANDOM*$RANDOM))
// to define additional roles.
enum AdditionalRoles {
/** roleName is "url". @see url() */
UrlRole = 0x069CD12B,
/** roleName is "isHidden". @see isHidden() */
HiddenRole = 0x0741CAAC,
/** roleName is "isSetupNeeded". @see setupNeeded() */
SetupNeededRole = 0x059A935D,
/**
* Whether the place is a fixed device (neither hotpluggable nor removable).
* roleName is "isFixedDevice".
*/
FixedDeviceRole = 0x332896C1,
/**
* Whether the place should have its free space displayed in a capacity bar.
* roleName is "isCapacityBarRecommended".
*/
CapacityBarRecommendedRole = 0x1548C5C4,
/**
* The name of the group, for example "Remote" or "Devices". roleName is "group".
* @since 5.40
*/
GroupRole = 0x0a5b64ee,
/**
* roleName is "iconName".
* @see icon()
* @since 5.41
*/
IconNameRole = 0x00a45c00,
/** roleName is "isGroupHidden".
* @see isGroupHidden()
* @since 5.42
*/
GroupHiddenRole = 0x21a4b936,
/** roleName is "isTeardownAllowed".
* @see isTeardownAllowed().
* @since 5.91
*/
TeardownAllowedRole = 0x02533364,
/** roleName is "isEjectAllowed".
* @since 5.94.
*/
EjectAllowedRole = 0x0A16AC5B,
/**
* roleName is "isTeardownOverlayRecommended".
* @see isTeardownOverlayRecommended()
* @since 5.95
*/
TeardownOverlayRecommendedRole = 0x032EDCCE,
/**
* roleName is "deviceAccessibility".
* @see deviceAccessibility()
* @since 5.99
*/
DeviceAccessibilityRole = 0x023FFD93,
};
/**
* Describes the available group types used in this model.
* @since 5.42
*/
enum GroupType {
PlacesType, ///< "Places" section
RemoteType, ///< "Remote" section
RecentlySavedType, ///< "Recent" section
SearchForType, ///< "Search for" section
DevicesType, ///< "Devices" section
RemovableDevicesType, ///< "Removable Devices" section
UnknownType, ///< Unknown GroupType
TagsType, ///< "Tags" section. @since 5.54
};
Q_ENUM(GroupType)
enum DeviceAccessibility {
SetupNeeded,
SetupInProgress,
Accessible,
TeardownInProgress
};
Q_ENUM(DeviceAccessibility)
explicit KFilePlacesModel(QObject *parent = nullptr);
~KFilePlacesModel() override;
/**
* @return The URL of the place at index @p index.
*/
Q_INVOKABLE QUrl url(const QModelIndex &index) const;
/**
* @return Whether the place at index @p index needs to be mounted before it can be used.
*/
Q_INVOKABLE bool setupNeeded(const QModelIndex &index) const;
/**
* @return Whether the place is a device that can be unmounted, e.g. it is
* mounted but does not point at system Root or the user's Home directory.
*
* It does not indicate whether the teardown can succeed.
* @since 5.91
*/
Q_INVOKABLE bool isTeardownAllowed(const QModelIndex &index) const;
/**
* @return Whether the place is a device that can be ejected, e.g. it is
* a CD, DVD, etc.
*
* It does not indicate whether the eject can succeed.
* @since 5.94
*/
Q_INVOKABLE bool isEjectAllowed(const QModelIndex &index) const;
/**
* @return Whether showing an inline teardown button is recommended,
* e.g. when it is a removable drive.
*
* @since 5.95
**/
Q_INVOKABLE bool isTeardownOverlayRecommended(const QModelIndex &index) const;
/**
* @return Whether this device is currently accessible or being (un)mounted.
*
* @since 5.99
*/
Q_INVOKABLE KFilePlacesModel::DeviceAccessibility deviceAccessibility(const QModelIndex &index) const;
/**
* @return The icon of the place at index @p index.
*/
Q_INVOKABLE QIcon icon(const QModelIndex &index) const;
/**
* @return The user-visible text of the place at index @p index.
*/
Q_INVOKABLE QString text(const QModelIndex &index) const;
/**
* @return Whether the place at index @p index is hidden or is inside an hidden group.
*/
Q_INVOKABLE bool isHidden(const QModelIndex &index) const;
/**
* @return Whether the group type @p type is hidden.
* @since 5.42
*/
Q_INVOKABLE bool isGroupHidden(const GroupType type) const;
/**
* @return Whether the group of the place at index @p index is hidden.
* @since 5.42
*/
Q_INVOKABLE bool isGroupHidden(const QModelIndex &index) const;
/**
* @return Whether the place at index @p index is a device handled by Solid.
* @see deviceForIndex()
*/
Q_INVOKABLE bool isDevice(const QModelIndex &index) const;
/**
* @return The solid device of the place at index @p index, if it is a device. Otherwise a default Solid::Device() instance is returned.
* @see isDevice()
*/
Solid::Device deviceForIndex(const QModelIndex &index) const;
/**
* @return The KBookmark instance of the place at index @p index.
* If the index is not valid, a default KBookmark instance is returned.
*/
KBookmark bookmarkForIndex(const QModelIndex &index) const;
/**
* @return The KBookmark instance of the place with url @p searchUrl.
* If the bookmark corresponding to searchUrl is not found, a default KBookmark instance is returned.
* @since 5.63
*/
KBookmark bookmarkForUrl(const QUrl &searchUrl) const;
/**
* @return The group type of the place at index @p index.
* @since 5.42
*/
Q_INVOKABLE GroupType groupType(const QModelIndex &index) const;
/**
* @return The list of model indexes that have @ type as their group type.
* @see groupType()
* @since 5.42
*/
Q_INVOKABLE QModelIndexList groupIndexes(const GroupType type) const;
/**
* @return A QAction with a proper translated label that can be used to trigger the requestTeardown()
* method for the place at index @p index.
* @see requestTeardown()
*/
Q_INVOKABLE QAction *teardownActionForIndex(const QModelIndex &index) const;
/**
* @return A QAction with a proper translated label that can be used to trigger the requestEject()
* method for the place at index @p index.
* @see requestEject()
*/
Q_INVOKABLE QAction *ejectActionForIndex(const QModelIndex &index) const;
/**
* @return A QAction with a proper translated label that can be used to open a partitioning menu for the device. nullptr if not a device.
*/
Q_INVOKABLE QAction *partitionActionForIndex(const QModelIndex &index) const;
/**
* Unmounts the place at index @p index by triggering the teardown functionality of its Solid device.
* @see deviceForIndex()
*/
Q_INVOKABLE void requestTeardown(const QModelIndex &index);
/**
* Ejects the place at index @p index by triggering the eject functionality of its Solid device.
* @see deviceForIndex()
*/
Q_INVOKABLE void requestEject(const QModelIndex &index);
/**
* Mounts the place at index @p index by triggering the setup functionality of its Solid device.
* @see deviceForIndex()
*/
Q_INVOKABLE void requestSetup(const QModelIndex &index);
/**
* Adds a new place to the model.
* @param text The user-visible text for the place
* @param url The URL of the place. It will be stored in its QUrl::FullyEncoded string format.
* @param iconName The icon of the place
* @param appName If set as the value of QCoreApplication::applicationName(), will make the place visible only in this application.
*/
Q_INVOKABLE void addPlace(const QString &text, const QUrl &url, const QString &iconName = QString(), const QString &appName = QString());
/**
* Adds a new place to the model.
* @param text The user-visible text for the place
* @param url The URL of the place. It will be stored in its QUrl::FullyEncoded string format.
* @param iconName The icon of the place
* @param appName If set as the value of QCoreApplication::applicationName(), will make the place visible only in this application.
* @param after The index after which the new place will be added.
*/
Q_INVOKABLE void addPlace(const QString &text, const QUrl &url, const QString &iconName, const QString &appName, const QModelIndex &after);
/**
* Edits the place with index @p index.
* @param text The new user-visible text for the place
* @param url The new URL of the place
* @param iconName The new icon of the place
* @param appName The new application-local filter for the place (@see addPlace()).
*/
Q_INVOKABLE void
editPlace(const QModelIndex &index, const QString &text, const QUrl &url, const QString &iconName = QString(), const QString &appName = QString());
/**
* Deletes the place with index @p index from the model.
*/
Q_INVOKABLE void removePlace(const QModelIndex &index) const;
/**
* Changes the visibility of the place with index @p index, but only if the place is not inside an hidden group.
* @param hidden Whether the place should be hidden or visible.
* @see isGroupHidden()
*/
Q_INVOKABLE void setPlaceHidden(const QModelIndex &index, bool hidden);
/**
* Changes the visibility of the group with type @p type.
* @param hidden Whether the group should be hidden or visible.
* @see isGroupHidden()
* @since 5.42
*/
Q_INVOKABLE void setGroupHidden(const GroupType type, bool hidden);
/**
* @brief Move place at @p itemRow to a position before @p row
* @return Whether the place has been moved.
* @since 5.41
*/
Q_INVOKABLE bool movePlace(int itemRow, int row);
/**
* @return The number of hidden places in the model.
* @see isHidden()
*/
Q_INVOKABLE int hiddenCount() const;
/**
* @brief Get a visible data based on Qt role for the given index.
* Return the device information for the give index.
*
* @param index The QModelIndex which contains the row, column to fetch the data.
* @param role The Interview data role(ex: Qt::DisplayRole).
*
* @return the data for the given index and role.
*/
QVariant data(const QModelIndex &index, int role) const override;
/**
* @brief Get the children model index for the given row and column.
*/
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
/**
* @brief Get the parent QModelIndex for the given model child.
*/
QModelIndex parent(const QModelIndex &child) const override;
/// Reimplemented from QAbstractItemModel.
/// @see AdditionalRoles
QHash<int, QByteArray> roleNames() const override;
/**
* @brief Get the number of rows for a model index.
*/
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
/**
* @brief Get the number of columns for a model index.
*/
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
/**
* Returns the closest item for the URL \a url.
* The closest item is defined as item which is equal to
* the URL or at least is a parent URL. If there are more than
* one possible parent URL candidates, the item which covers
* the bigger range of the URL is returned.
*
* Example: the url is '/home/peter/Documents/Music'.
* Available items are:
* - /home/peter
* - /home/peter/Documents
*
* The returned item will the one for '/home/peter/Documents'.
*/
QModelIndex closestItem(const QUrl &url) const;
Qt::DropActions supportedDropActions() const override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
QStringList mimeTypes() const override;
QMimeData *mimeData(const QModelIndexList &indexes) const override;
bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override;
/**
* @brief Reload bookmark information
* @since 5.41
*/
Q_INVOKABLE void refresh() const;
/**
* @brief Converts the URL, which contains "virtual" URLs for system-items like
* "timeline:/lastmonth" into a Query-URL "timeline:/2017-10"
* that will be handled by the corresponding KIO worker.
* Virtual URLs for bookmarks are used to be independent from
* internal format changes.
* @param an url
* @return the converted URL, which can be handled by a KIO worker
* @since 5.41
*/
static QUrl convertedUrl(const QUrl &url);
/**
* 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);
/**
* 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;
Q_SIGNALS:
/**
* @p message An error message explaining what went wrong.
*/
void errorMessage(const QString &message);
/**
* Emitted after the Solid setup ends.
* @param success Whether the Solid setup has been successful.
* @see requestSetup()
*/
void setupDone(const QModelIndex &index, bool success);
/**
* Emitted after the teardown of a device ends.
*
* @note In case of an error, the @p errorMessage signal
* will also be emitted with a message describing the error.
*
* @param error Type of error that occurred, if any.
* @param errorData More information about the error, if any.
* @since 5.100
*/
void teardownDone(const QModelIndex &index, Solid::ErrorType error, const QVariant &errorData);
/**
* Emitted whenever the visibility of the group @p group changes.
* @param hidden The new visibility of the group.
* @see setGroupHidden()
* @since 5.42
*/
void groupHiddenChanged(KFilePlacesModel::GroupType group, bool hidden);
/**
* Called once the model has been reloaded
*
* @since 5.71
*/
void reloaded();
/**
* Emitted whenever the list of supported schemes has been changed
*
* @since 5.94
*/
void supportedSchemesChanged();
private:
friend class KFilePlacesModelPrivate;
std::unique_ptr<KFilePlacesModelPrivate> d;
};
#endif
@@ -0,0 +1,75 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2007 Kevin Ottens <ervin@kde.org>
SPDX-FileCopyrightText: 2007 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2023 Harald Sitter <sitter@kde.org>
SPDX-FileCopyrightText: 2023 Kai Uwe Broulik <kde@broulik.de>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KFILEPLACESMODEL_P_H
#define KFILEPLACESMODEL_P_H
#include <solid/predicate.h>
#include <solid/solidnamespace.h>
#include <QList>
#include <QMap>
#include <QPersistentModelIndex>
#include <QStringList>
class KBookmarkManager;
class KCoreDirLister;
class KFilePlacesItem;
class KFilePlacesModel;
class QUrl;
namespace Solid
{
class StorageAccess;
}
class KFilePlacesModelPrivate
{
public:
explicit KFilePlacesModelPrivate(KFilePlacesModel *qq);
KFilePlacesModel *const q;
static QString ignoreMimeType();
static QString internalMimeType(const KFilePlacesModel *model);
QList<KFilePlacesItem *> items;
QList<Solid::Device> availableDevices;
QMap<QObject *, QPersistentModelIndex> setupInProgress;
QMap<QObject *, QPersistentModelIndex> teardownInProgress;
QStringList supportedSchemes;
Solid::Predicate predicate;
KBookmarkManager *bookmarkManager;
const bool fileIndexingEnabled;
void reloadAndSignal();
QList<KFilePlacesItem *> loadBookmarkList();
int findNearestPosition(int source, int target);
QList<QString> tags;
const QString tagsUrlBase = QStringLiteral("tags:/");
KCoreDirLister *tagsLister = nullptr;
void initDeviceList();
void deviceAdded(const QString &udi);
void deviceRemoved(const QString &udi);
void itemChanged(const QString &udi, const QList<int> &roles);
void reloadBookmarks();
void storageSetupDone(Solid::ErrorType error, const QVariant &errorData, Solid::StorageAccess *sender);
void storageTeardownDone(const QString &filePath, Solid::ErrorType error, const QVariant &errorData, QObject *sender);
private:
bool isBalooUrl(const QUrl &url) const;
};
#endif // KFILEPLACESMODEL_P_H
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,192 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2007 Kevin Ottens <ervin@kde.org>
SPDX-FileCopyrightText: 2022 Kai Uwe Broulik <kde@broulik.de>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KFILEPLACESVIEW_H
#define KFILEPLACESVIEW_H
#include "kiofilewidgets_export.h"
#include <QListView>
#include <QUrl>
#include <functional>
#include <memory>
class QResizeEvent;
class QContextMenuEvent;
class KFilePlacesViewPrivate;
/**
* @class KFilePlacesView kfileplacesview.h <KFilePlacesView>
*
* This class allows to display a KFilePlacesModel.
*/
class KIOFILEWIDGETS_EXPORT KFilePlacesView : public QListView
{
Q_OBJECT
public:
explicit KFilePlacesView(QWidget *parent = nullptr);
~KFilePlacesView() override;
/**
* The teardown function signature. Custom teardown logic
* may be provided via the setTeardownFunction method.
* @since 5.91
*/
using TeardownFunction = std::function<void(const QModelIndex &)>;
/**
* Whether hidden places, if any, are currently shown.
* @since 5.91
*/
bool allPlacesShown() const;
/**
* If \a enabled is true, it is allowed dropping items
* above a place for e. g. copy or move operations. The application
* has to take care itself to perform the operation
* (see KFilePlacesView::urlsDropped()). If
* \a enabled is false, it is only possible adding items
* as additional place. Per default dropping on a place is
* disabled.
*/
void setDropOnPlaceEnabled(bool enabled);
bool isDropOnPlaceEnabled() const;
/**
* If \a delay (in ms) is greater than zero, the place will
* automatically be activated if an item is dragged over
* and held on top of a place for at least that duraton.
*
* @param delay Delay in ms, default is zero.
* @since 5.92
*/
void setDragAutoActivationDelay(int delay);
int dragAutoActivationDelay() const;
/**
* If \a enabled is true (the default), items will automatically resize
* themselves to fill the view.
*
*/
void setAutoResizeItemsEnabled(bool enabled);
bool isAutoResizeItemsEnabled() const;
/**
* Sets a custom function that will be called when teardown of
* a device (e.g.\ unmounting a drive) is requested.
* @since 5.91
*/
void setTeardownFunction(TeardownFunction teardownFunc);
QSize sizeHint() const override; // clazy:exclude=const-signal-or-slot
public Q_SLOTS:
void setUrl(const QUrl &url);
void setShowAll(bool showAll);
void setModel(QAbstractItemModel *model) override;
protected:
void keyPressEvent(QKeyEvent *event) override;
void contextMenuEvent(QContextMenuEvent *event) override;
void resizeEvent(QResizeEvent *event) override;
void showEvent(QShowEvent *event) override;
void hideEvent(QHideEvent *event) override;
void dragEnterEvent(QDragEnterEvent *event) override;
void dragLeaveEvent(QDragLeaveEvent *event) override;
void dragMoveEvent(QDragMoveEvent *event) override;
void dropEvent(QDropEvent *event) override;
void paintEvent(QPaintEvent *event) override;
void startDrag(Qt::DropActions supportedActions) override;
void mousePressEvent(QMouseEvent *event) override;
protected Q_SLOTS:
void rowsInserted(const QModelIndex &parent, int start, int end) override;
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList<int> &roles) override;
Q_SIGNALS:
/**
* Emitted when an item in the places view is clicked on with left mouse
* button with no modifier keys pressed.
*
* If a storage device needs to be mounted first, this signal is emitted once
* mounting has completed successfully.
*
* @param url The URL of the place
* @since 5.91
*/
void placeActivated(const QUrl &url);
/**
* Emitted when the URL \a url should be opened in a new inactive tab because
* the user clicked on a place with the middle mouse button or
* left-clicked with the Ctrl modifier pressed or selected "Open in New Tab"
* from the context menu.
*
* If a storage device needs to be mounted first, this signal is emitted once
* mounting has completed successfully.
* @since 5.91
*/
void tabRequested(const QUrl &url);
/**
* Emitted when the URL \a url should be opened in a new active tab because
* the user clicked on a place with the middle mouse button with
* the Shift modifier pressed or left-clicked with both the Ctrl and Shift
* modifiers pressed.
* If a storage device needs to be mounted first, this signal is emitted once
* mounting has completed successfully.
* @since 5.91
*/
void activeTabRequested(const QUrl &url);
/**
* Emitted when the URL \a url should be opened in a new window because
* the user left-clicked on a place with Shift modifier pressed or selected
* "Open in New Window" from the context menu.
*
* If a storage device needs to be mounted first, this signal is emitted once
* mounting has completed successfully.
* @since 5.91
*/
void newWindowRequested(const QUrl &url);
/**
* Emitted just before the context menu opens. This can be used to add additional
* application actions to the menu.
* @param index The model index of the place whose menu is about to open.
* @param menu The menu that will be opened.
* @since 5.91
*/
void contextMenuAboutToShow(const QModelIndex &index, QMenu *menu);
/**
* Emitted when allPlacesShown changes
* @since 5.91
*/
void allPlacesShownChanged(bool allPlacesShown);
void urlChanged(const QUrl &url);
/**
* Is emitted if items are dropped on the place \a dest.
* The application has to take care itself about performing the
* corresponding action like copying or moving.
*/
void urlsDropped(const QUrl &dest, QDropEvent *event, QWidget *parent);
private:
friend class KFilePlacesViewPrivate;
friend class KFilePlacesEventWatcher;
std::unique_ptr<KFilePlacesViewPrivate> const d;
};
#endif
@@ -0,0 +1,433 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2008 Rafael Fernández López <ereslibre@kde.org>
SPDX-FileCopyrightText: 2022 Kai Uwe Broulik <kde@broulik.de>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KFILEPLACESVIEW_P_H
#define KFILEPLACESVIEW_P_H
#include <KIO/FileSystemFreeSpaceJob>
#include <KIO/Global>
#include <QAbstractItemDelegate>
#include <QDateTime>
#include <QDeadlineTimer>
#include <QGestureEvent>
#include <QMouseEvent>
#include <QPointer>
#include <QScroller>
#include <QTimer>
#include <set>
class KFilePlacesView;
class QTimeLine;
struct PlaceFreeSpaceInfo {
QDeadlineTimer timeout;
KIO::filesize_t used = 0;
KIO::filesize_t size = 0;
QPointer<KIO::FileSystemFreeSpaceJob> job;
};
class KFilePlacesViewDelegate : public QAbstractItemDelegate
{
Q_OBJECT
public:
explicit KFilePlacesViewDelegate(KFilePlacesView *parent);
~KFilePlacesViewDelegate() override;
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
int iconSize() const;
void setIconSize(int newSize);
void paletteChange();
void addAppearingItem(const QModelIndex &index);
void setAppearingItemProgress(qreal value);
void addDisappearingItem(const QModelIndex &index);
void addDisappearingItemGroup(const QModelIndex &index);
void setDisappearingItemProgress(qreal value);
void setDeviceBusyAnimationRotation(qreal angle);
void setShowHoverIndication(bool show);
void setHoveredHeaderArea(const QModelIndex &index);
void setHoveredAction(const QModelIndex &index);
qreal contentsOpacity(const QModelIndex &index) const;
bool pointIsHeaderArea(const QPoint &pos) const;
bool pointIsTeardownAction(const QPoint &pos) const;
void startDrag();
int sectionHeaderHeight(const QModelIndex &index) const;
bool indexIsSectionHeader(const QModelIndex &index) const;
int actionIconSize() const;
void checkFreeSpace();
void checkFreeSpace(const QModelIndex &index) const;
void startPollingFreeSpace() const;
void stopPollingFreeSpace() const;
void clearFreeSpaceInfo();
protected:
bool helpEvent(QHelpEvent *event, QAbstractItemView *view, const QStyleOptionViewItem &option, const QModelIndex &index) override;
private:
QString groupNameFromIndex(const QModelIndex &index) const;
QModelIndex previousVisibleIndex(const QModelIndex &index) const;
void drawSectionHeader(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
QColor textColor(const QStyleOption &option) const;
QColor baseColor(const QStyleOption &option) const;
QColor mixedColor(const QColor &c1, const QColor &c2, int c1Percent) const;
KFilePlacesView *m_view;
int m_iconSize;
QList<QPersistentModelIndex> m_appearingItems;
qreal m_appearingHeightScale;
qreal m_appearingOpacity;
QList<QPersistentModelIndex> m_disappearingItems;
qreal m_disappearingHeightScale;
qreal m_disappearingOpacity;
qreal m_busyAnimationRotation = 0.0;
bool m_showHoverIndication;
QPersistentModelIndex m_hoveredHeaderArea;
QPersistentModelIndex m_hoveredAction;
mutable bool m_dragStarted;
QMap<QPersistentModelIndex, QTimeLine *> m_timeLineMap;
QMap<QTimeLine *, QPersistentModelIndex> m_timeLineInverseMap;
mutable QTimer m_pollFreeSpace;
mutable QMap<QPersistentModelIndex, PlaceFreeSpaceInfo> m_freeSpaceInfo;
mutable std::set<QPersistentModelIndex> m_elidedTexts;
// constructing KColorScheme is expensive, cache the negative color
mutable QColor m_warningCapacityBarColor;
};
class KFilePlacesEventWatcher : public QObject
{
Q_OBJECT
public:
explicit KFilePlacesEventWatcher(KFilePlacesView *parent = nullptr)
: QObject(parent)
, m_scroller(nullptr)
, q(parent)
, m_rubberBand(nullptr)
, m_isTouchEvent(false)
, m_mousePressed(false)
, m_tapAndHoldActive(false)
, m_lastMouseSource(Qt::MouseEventNotSynthesized)
{
m_rubberBand = new QRubberBand(QRubberBand::Rectangle, parent);
}
const QModelIndex hoveredHeaderAreaIndex()
{
return m_hoveredHeaderAreaIndex;
}
const QModelIndex hoveredActionIndex()
{
return m_hoveredActionIndex;
}
QScroller *m_scroller;
public Q_SLOTS:
void qScrollerStateChanged(const QScroller::State newState)
{
if (newState == QScroller::Inactive) {
m_isTouchEvent = false;
}
}
Q_SIGNALS:
void entryMiddleClicked(const QModelIndex &index);
void headerAreaEntered(const QModelIndex &index);
void headerAreaLeft(const QModelIndex &index);
void actionEntered(const QModelIndex &index);
void actionLeft(const QModelIndex &index);
void actionClicked(const QModelIndex &index);
void windowActivated();
void windowDeactivated();
void paletteChanged();
protected:
bool eventFilter(QObject *watched, QEvent *event) override
{
switch (event->type()) {
case QEvent::MouseMove: {
if (m_isTouchEvent && !m_tapAndHoldActive) {
return true;
}
m_tapAndHoldActive = false;
if (m_rubberBand->isVisible()) {
m_rubberBand->hide();
}
QAbstractItemView *view = qobject_cast<QAbstractItemView *>(watched->parent());
const QPoint pos = static_cast<QMouseEvent *>(event)->pos();
const QModelIndex index = view->indexAt(pos);
QModelIndex headerAreaIndex;
QModelIndex actionIndex;
if (index.isValid()) {
if (auto *delegate = qobject_cast<KFilePlacesViewDelegate *>(view->itemDelegate())) {
if (delegate->pointIsHeaderArea(pos)) {
headerAreaIndex = index;
} else if (delegate->pointIsTeardownAction(pos)) {
actionIndex = index;
}
}
}
if (headerAreaIndex != m_hoveredHeaderAreaIndex) {
if (m_hoveredHeaderAreaIndex.isValid()) {
Q_EMIT headerAreaLeft(m_hoveredHeaderAreaIndex);
}
m_hoveredHeaderAreaIndex = headerAreaIndex;
if (headerAreaIndex.isValid()) {
Q_EMIT headerAreaEntered(headerAreaIndex);
}
}
if (actionIndex != m_hoveredActionIndex) {
if (m_hoveredActionIndex.isValid()) {
Q_EMIT actionLeft(m_hoveredActionIndex);
}
m_hoveredActionIndex = actionIndex;
if (actionIndex.isValid()) {
Q_EMIT actionEntered(actionIndex);
}
}
break;
}
case QEvent::Leave:
if (m_hoveredHeaderAreaIndex.isValid()) {
Q_EMIT headerAreaLeft(m_hoveredHeaderAreaIndex);
}
m_hoveredHeaderAreaIndex = QModelIndex();
if (m_hoveredActionIndex.isValid()) {
Q_EMIT actionLeft(m_hoveredActionIndex);
}
m_hoveredActionIndex = QModelIndex();
break;
case QEvent::MouseButtonPress: {
QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
m_mousePressed = true;
m_lastMouseSource = mouseEvent->source();
if (m_isTouchEvent) {
return true;
}
onPressed(mouseEvent);
Q_FALLTHROUGH();
}
case QEvent::MouseButtonDblClick: {
// Prevent the selection clearing by clicking on the viewport directly
QAbstractItemView *view = qobject_cast<QAbstractItemView *>(watched->parent());
if (!view->indexAt(static_cast<QMouseEvent *>(event)->pos()).isValid()) {
return true;
}
break;
}
case QEvent::MouseButtonRelease: {
QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
if (mouseEvent->button() == Qt::LeftButton || mouseEvent->button() == Qt::MiddleButton) {
QAbstractItemView *view = qobject_cast<QAbstractItemView *>(watched->parent());
const QModelIndex index = view->indexAt(mouseEvent->pos());
if (mouseEvent->button() == Qt::LeftButton) {
if (m_clickedActionIndex.isValid()) {
if (auto *delegate = qobject_cast<KFilePlacesViewDelegate *>(view->itemDelegate())) {
if (delegate->pointIsTeardownAction(mouseEvent->pos())) {
if (m_clickedActionIndex == index) {
Q_EMIT actionClicked(m_clickedActionIndex);
// filter out, avoid QAbstractItemView::clicked being emitted
return true;
}
}
}
}
m_clickedActionIndex = index;
} else if (mouseEvent->button() == Qt::MiddleButton) {
if (m_middleClickedIndex.isValid() && m_middleClickedIndex == index) {
Q_EMIT entryMiddleClicked(m_middleClickedIndex);
}
m_middleClickedIndex = QPersistentModelIndex();
}
}
break;
}
case QEvent::WindowActivate:
Q_EMIT windowActivated();
break;
case QEvent::WindowDeactivate:
Q_EMIT windowDeactivated();
break;
case QEvent::PaletteChange:
Q_EMIT paletteChanged();
break;
case QEvent::TouchBegin: {
m_isTouchEvent = true;
m_mousePressed = false;
break;
}
case QEvent::Gesture: {
gestureEvent(static_cast<QGestureEvent *>(event));
event->accept();
return true;
}
default:
return false;
}
return false;
}
void onPressed(QMouseEvent *mouseEvent)
{
if (mouseEvent->button() == Qt::LeftButton || mouseEvent->button() == Qt::MiddleButton) {
QAbstractItemView *view = qobject_cast<QAbstractItemView *>(q);
const QModelIndex index = view->indexAt(mouseEvent->pos());
if (index.isValid()) {
if (mouseEvent->button() == Qt::LeftButton) {
if (auto *delegate = qobject_cast<KFilePlacesViewDelegate *>(view->itemDelegate())) {
if (delegate->pointIsTeardownAction(mouseEvent->pos())) {
m_clickedActionIndex = index;
}
}
} else if (mouseEvent->button() == Qt::MiddleButton) {
m_middleClickedIndex = index;
}
}
}
}
void gestureEvent(QGestureEvent *event)
{
if (QGesture *gesture = event->gesture(Qt::TapGesture)) {
tapTriggered(static_cast<QTapGesture *>(gesture));
}
if (QGesture *gesture = event->gesture(Qt::TapAndHoldGesture)) {
tapAndHoldTriggered(static_cast<QTapAndHoldGesture *>(gesture));
}
}
void tapAndHoldTriggered(QTapAndHoldGesture *tap)
{
if (tap->state() == Qt::GestureFinished) {
if (!m_mousePressed) {
return;
}
// the TapAndHold gesture is triggerable with the mouse and stylus, we don't want this
if (m_lastMouseSource == Qt::MouseEventNotSynthesized || !m_isTouchEvent) {
return;
}
m_tapAndHoldActive = true;
m_scroller->stop();
// simulate a mousePressEvent, to allow KFilePlacesView to select the items
const QPointF tapGlobalPos = tap->position(); // QTapAndHoldGesture::position is global
const QPointF tapViewportPos(q->viewport()->mapFromGlobal(tapGlobalPos));
QMouseEvent fakeMousePress(QEvent::MouseButtonPress, tapViewportPos, tapGlobalPos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
onPressed(&fakeMousePress);
q->mousePressEvent(&fakeMousePress);
const QPoint tapIndicatorSize(80, 80);
const QPoint pos(q->mapFromGlobal(tapGlobalPos.toPoint()));
const QRect tapIndicatorRect(pos - (tapIndicatorSize / 2), pos + (tapIndicatorSize / 2));
m_rubberBand->setGeometry(tapIndicatorRect.normalized());
m_rubberBand->show();
}
}
void tapTriggered(QTapGesture *tap)
{
static bool scrollerWasScrolling = false;
if (tap->state() == Qt::GestureStarted) {
m_tapAndHoldActive = false;
// if QScroller state is Scrolling or Dragging, the user makes the tap to stop the scrolling
auto const scrollerState = m_scroller->state();
if (scrollerState == QScroller::Scrolling || scrollerState == QScroller::Dragging) {
scrollerWasScrolling = true;
} else {
scrollerWasScrolling = false;
}
}
if (tap->state() == Qt::GestureFinished && !scrollerWasScrolling) {
m_isTouchEvent = false;
// with touch you can touch multiple widgets at the same time, but only one widget will get a mousePressEvent.
// we use this to select the right window
if (!m_mousePressed) {
return;
}
if (m_rubberBand->isVisible()) {
m_rubberBand->hide();
}
// simulate a mousePressEvent, to allow KFilePlacesView to select the items
const QPointF tapPosition = tap->position(); // QTapGesture::position is local
const QPointF globalTapPosition = q->mapToGlobal(tapPosition);
QMouseEvent fakeMousePress(QEvent::MouseButtonPress,
tapPosition,
globalTapPosition,
m_tapAndHoldActive ? Qt::RightButton : Qt::LeftButton,
m_tapAndHoldActive ? Qt::RightButton : Qt::LeftButton,
Qt::NoModifier);
onPressed(&fakeMousePress);
q->mousePressEvent(&fakeMousePress);
if (m_tapAndHoldActive) {
// simulate a contextMenuEvent
QContextMenuEvent fakeContextMenu(QContextMenuEvent::Mouse, tapPosition.toPoint(), globalTapPosition.toPoint());
q->contextMenuEvent(&fakeContextMenu);
}
m_tapAndHoldActive = false;
}
}
private:
QPersistentModelIndex m_hoveredHeaderAreaIndex;
QPersistentModelIndex m_middleClickedIndex;
QPersistentModelIndex m_hoveredActionIndex;
QPersistentModelIndex m_clickedActionIndex;
KFilePlacesView *const q;
QRubberBand *m_rubberBand;
bool m_isTouchEvent;
bool m_mousePressed;
bool m_tapAndHoldActive;
Qt::MouseEventSource m_lastMouseSource;
};
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,113 @@
/*
SPDX-FileCopyrightText: 2008-2009 Peter Penz <peter.penz@gmx.at>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KFILEPREVIEWGENERATOR_H
#define KFILEPREVIEWGENERATOR_H
#include "kiofilewidgets_export.h"
#include <QObject>
#include <memory>
class KAbstractViewAdapter;
class KDirModel;
class QAbstractItemView;
class QAbstractProxyModel;
class KFilePreviewGeneratorPrivate;
/**
* @class KFilePreviewGenerator kfilepreviewgenerator.h <KFilePreviewGenerator>
*
* @brief Generates previews for files of an item view.
*
* Per default a preview is generated for each item.
* Additionally the clipboard is checked for cut items.
* The icon state for cut items gets dimmed automatically.
*
* The following strategy is used when creating previews:
* - The previews for currently visible items are created before
* the previews for invisible items.
* - If the user changes the visible area by using the scrollbars,
* all pending previews get paused. As soon as the user stays
* on the same position for a short delay, the previews are
* resumed. Also in this case the previews for the visible items
* are generated first.
*
*/
class KIOFILEWIDGETS_EXPORT KFilePreviewGenerator : public QObject
{
Q_OBJECT
public:
/**
* @param parent Item view containing the file items where previews should
* be generated. It is mandatory that the item view specifies
* an icon size by QAbstractItemView::setIconSize() and that
* the model of the view (or the source model of the proxy model)
* is an instance of KDirModel. Otherwise no previews will be generated.
*/
KFilePreviewGenerator(QAbstractItemView *parent);
/** @internal */
KFilePreviewGenerator(KAbstractViewAdapter *parent, QAbstractProxyModel *model);
~KFilePreviewGenerator() override;
/**
* If \a show is set to true, a preview is generated for each item. If \a show
* is false, the MIME type icon of the item is shown instead. Per default showing
* the preview is turned on. Note that it is mandatory that the item view
* specifies an icon size by QAbstractItemView::setIconSize(), otherwise
* KFilePreviewGenerator::isPreviewShown() will always return false.
*/
void setPreviewShown(bool show);
bool isPreviewShown() const;
/**
* Sets the list of enabled thumbnail plugins.
* Per default all plugins enabled in the KConfigGroup "PreviewSettings"
* are used.
*
* Note that this method doesn't cause already generated previews
* to be regenerated.
*
* For a list of available plugins, call KIO::PreviewJob::availableThumbnailerPlugins().
*
* @see enabledPlugins
*/
void setEnabledPlugins(const QStringList &list);
/**
* Returns the list of enabled thumbnail plugins.
* @see setEnabledPlugins
*/
QStringList enabledPlugins() const;
public Q_SLOTS:
/**
* Updates the icons for all items. Usually it is only
* necessary to invoke this method when the icon size of the abstract item view
* has been changed by QAbstractItemView::setIconSize(). Note that this method
* should also be invoked if previews have been turned off, as the icons for
* cut items must be updated when the icon size has changed.
*/
void updateIcons();
/** Cancels all pending previews. */
void cancelPreviews();
private:
friend class KFilePreviewGeneratorPrivate;
std::unique_ptr<KFilePreviewGeneratorPrivate> const d;
Q_DISABLE_COPY(KFilePreviewGenerator)
Q_PRIVATE_SLOT(d, void pauseIconUpdates())
};
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,522 @@
// -*- c++ -*-
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 1997, 1998 Richard Moore <rich@kde.org>
SPDX-FileCopyrightText: 1998 Stephan Kulow <coolo@kde.org>
SPDX-FileCopyrightText: 1998 Daniel Grana <grana@ie.iwi.unibe.ch>
SPDX-FileCopyrightText: 2000, 2001 Carsten Pfeiffer <pfeiffer@kde.org>
SPDX-FileCopyrightText: 2001 Frerich Raabe <raabe@kde.org>
SPDX-FileCopyrightText: 2007 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2008 Rafael Fernández López <ereslibre@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KFILEWIDGET_H
#define KFILEWIDGET_H
#include "kfile.h"
#include "kiofilewidgets_export.h"
#include <QWidget>
#include <KFileFilter>
#include <memory>
class QUrl;
class QPushButton;
class KActionCollection;
class KFileWidgetPrivate;
class KUrlComboBox;
class KFileFilterCombo;
class KPreviewWidgetBase;
class QMimeType;
class KConfigGroup;
class KJob;
class KFileItem;
class KDirOperator;
/**
* @class KFileWidget kfilewidget.h <KFileWidget>
*
* 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:///&lt;keyword&gt; 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:///&lt;keyword&gt;/&lt;filename&gt;
* 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 Deprecated: A URL of the form @c kfiledialog:///&lt;keyword&gt;?global to start
* in the directory last used by a filedialog in any application that
* specified the same keyword.
* @li Deprecated: A URL of the form @c kfiledialog:///&lt;keyword&gt;/&lt;filename&gt;?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.
*
* @note Since 5.96, the "?global" syntax is deprecated, for lack of usage.
*
* @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<QUrl> 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);
/**
* 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 a list of URLs as preselected
*
* @see setSelectedUrl
* @since 5.75
*/
void setSelectedUrls(const QList<QUrl> &urls);
/**
* 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;
/**
* Set the filters to be used.
*
* Each item of the list corresponds to a selectable filter.
*
* Only one filter is active at a time.
*
* @param activeFilter the initially active filter
*
* @since 6.0
*
*/
void setFilters(const QList<KFileFilter> &filters, const KFileFilter &activeFilter = KFileFilter());
/**
* Returns the current filter as entered by the user or one of the
* predefined set via setFilters().
*
* @see setFilters()
* @see filterChanged()
*
* @since 6.0
*/
KFileFilter currentFilter() const;
/**
* Clears any MIME type or name filter. 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 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;
/**
* 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()
*/
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 title "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.
*/
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.
*/
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 setFilters().
*
* @param filter contains the new filter (only the extension part,
* not the explanation), i.e. "*.cpp" or "*.cpp *.cc".
*
* @see setFilters()
* @see currentFilter()
*
* @since 6.0
*/
void filterChanged(const KFileFilter &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
*/
KDirOperator *dirOperator();
#if KIOFILEWIDGETS_ENABLE_DEPRECATED_SINCE(6, 3)
/**
* reads the configuration for this widget from the given config group
* @param group the KConfigGroup to read from
*
* @deprecated since 6.3, no known use case.
*/
KIOFILEWIDGETS_DEPRECATED_VERSION(6, 3, "No known use case")
void readConfig(KConfigGroup &group);
#endif
private:
friend class KFileWidgetPrivate;
std::unique_ptr<KFileWidgetPrivate> const d;
};
#endif
@@ -0,0 +1,33 @@
/*
SPDX-FileCopyrightText: 2018 Kai Uwe Broulik <kde@privat.broulik.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kfilewidgetdocktitlebar_p.h"
#include <QStyle>
using namespace KDEPrivate;
KFileWidgetDockTitleBar::KFileWidgetDockTitleBar(QWidget *parent)
: QWidget(parent)
{
}
KFileWidgetDockTitleBar::~KFileWidgetDockTitleBar()
{
}
QSize KFileWidgetDockTitleBar::minimumSizeHint() const
{
const int border = style()->pixelMetric(QStyle::PM_DockWidgetTitleBarButtonMargin);
return QSize(border, border);
}
QSize KFileWidgetDockTitleBar::sizeHint() const
{
return minimumSizeHint();
}
#include "moc_kfilewidgetdocktitlebar_p.cpp"
@@ -0,0 +1,31 @@
/*
SPDX-FileCopyrightText: 2018 Kai Uwe Broulik <kde@privat.broulik.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KFILEWIDGETDOCKTITLEBAR_P_H
#define KFILEWIDGETDOCKTITLEBAR_P_H
#include <QWidget>
namespace KDEPrivate
{
/**
* @brief An empty title bar for the Places dock widget
*/
class KFileWidgetDockTitleBar : public QWidget
{
Q_OBJECT
public:
explicit KFileWidgetDockTitleBar(QWidget *parent);
~KFileWidgetDockTitleBar() override;
QSize minimumSizeHint() const override;
QSize sizeHint() const override;
};
} // namespace KDEPrivate
#endif // KFILEWIDGETDOCKTITLEBAR_P_H
@@ -0,0 +1,283 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2001 Martin R. Jones <mjones@kde.org>
SPDX-FileCopyrightText: 2001 Carsten Pfeiffer <pfeiffer@kde.org>
SPDX-FileCopyrightText: 2008 Rafael Fernández López <ereslibre@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include "kimagefilepreview.h"
#include <QCheckBox>
#include <QLabel>
#include <QPainter>
#include <QResizeEvent>
#include <QStyle>
#include <QTimeLine>
#include <QVBoxLayout>
#include <KConfig>
#include <KConfigGroup>
#include <KIconLoader>
#include <KLocalizedString>
#include <kfileitem.h>
#include <kio/previewjob.h>
/**** KImageFilePreview ****/
class KImageFilePreviewPrivate
{
public:
KImageFilePreviewPrivate(KImageFilePreview *qq)
: q(qq)
{
if (q->style()->styleHint(QStyle::SH_Widget_Animate, nullptr, q)) {
m_timeLine = new QTimeLine(150, q);
m_timeLine->setEasingCurve(QEasingCurve::InCurve);
m_timeLine->setDirection(QTimeLine::Forward);
m_timeLine->setFrameRange(0, 100);
}
}
void slotResult(KJob *);
void slotFailed(const KFileItem &);
void slotStepAnimation();
void slotFinished();
void slotActuallyClear();
KImageFilePreview *q = nullptr;
QUrl currentURL;
QUrl lastShownURL;
QLabel *imageLabel;
KIO::PreviewJob *m_job = nullptr;
QTimeLine *m_timeLine = nullptr;
QPixmap m_pmCurrent;
QPixmap m_pmTransition;
float m_pmCurrentOpacity = 1;
float m_pmTransitionOpacity = 0;
bool clear = true;
};
KImageFilePreview::KImageFilePreview(QWidget *parent)
: KPreviewWidgetBase(parent)
, d(new KImageFilePreviewPrivate(this))
{
QVBoxLayout *vb = new QVBoxLayout(this);
vb->setContentsMargins(0, 0, 0, 0);
d->imageLabel = new QLabel(this);
d->imageLabel->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
d->imageLabel->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding));
vb->addWidget(d->imageLabel);
setSupportedMimeTypes(KIO::PreviewJob::supportedMimeTypes());
setMinimumWidth(50);
if (d->m_timeLine) {
connect(d->m_timeLine, &QTimeLine::frameChanged, this, [this]() {
d->slotStepAnimation();
});
connect(d->m_timeLine, &QTimeLine::finished, this, [this]() {
d->slotFinished();
});
}
}
KImageFilePreview::~KImageFilePreview()
{
if (d->m_job) {
d->m_job->kill();
}
}
void KImageFilePreview::showPreview()
{
// Pass a copy since clearPreview() will clear currentURL
QUrl url = d->currentURL;
showPreview(url, true);
}
// called via KPreviewWidgetBase interface
void KImageFilePreview::showPreview(const QUrl &url)
{
showPreview(url, false);
}
void KImageFilePreview::showPreview(const QUrl &url, bool force)
{
/* clang-format off */
if (!url.isValid()
|| (d->lastShownURL.isValid()
&& url.matches(d->lastShownURL, QUrl::StripTrailingSlash)
&& d->currentURL.isValid())) {
return;
}
/* clang-format on*/
d->clear = false;
d->currentURL = url;
d->lastShownURL = url;
int w = d->imageLabel->contentsRect().width() - 4;
int h = d->imageLabel->contentsRect().height() - 4;
if (d->m_job) {
disconnect(d->m_job, nullptr, this, nullptr);
d->m_job->kill();
}
d->m_job = createJob(url, w, h);
if (force) { // explicitly requested previews shall always be generated!
d->m_job->setIgnoreMaximumSize(true);
}
connect(d->m_job, &KJob::result, this, [this](KJob *job) {
d->slotResult(job);
});
connect(d->m_job, &KIO::PreviewJob::gotPreview, this, &KImageFilePreview::gotPreview);
connect(d->m_job, &KIO::PreviewJob::failed, this, [this](const KFileItem &item) {
d->slotFailed(item);
});
}
void KImageFilePreview::resizeEvent(QResizeEvent *)
{
// Nothing to do, if no current preview
if (d->imageLabel->pixmap().isNull()) {
return;
}
clearPreview();
d->currentURL = QUrl(); // force this to actually happen
showPreview(d->lastShownURL);
}
QSize KImageFilePreview::sizeHint() const
{
return QSize(100, 200);
}
KIO::PreviewJob *KImageFilePreview::createJob(const QUrl &url, int w, int h)
{
if (!url.isValid()) {
return nullptr;
}
KFileItemList items;
items.append(KFileItem(url));
QStringList plugins = KIO::PreviewJob::availablePlugins();
KIO::PreviewJob *previewJob = KIO::filePreview(items, QSize(w, h), &plugins);
previewJob->setScaleType(KIO::PreviewJob::Scaled);
return previewJob;
}
void KImageFilePreview::gotPreview(const KFileItem &item, const QPixmap &pm)
{
if (item.url() != d->currentURL) { // Shouldn't happen
return;
}
if (d->m_timeLine) {
if (d->m_timeLine->state() == QTimeLine::Running) {
d->m_timeLine->setCurrentTime(0);
}
d->m_pmTransition = pm;
d->m_pmTransitionOpacity = 0;
d->m_pmCurrentOpacity = 1;
d->m_timeLine->setDirection(QTimeLine::Forward);
d->m_timeLine->start();
} else {
d->imageLabel->setPixmap(pm);
}
}
void KImageFilePreviewPrivate::slotFailed(const KFileItem &item)
{
if (item.isDir()) {
imageLabel->clear();
} else if (item.url() == currentURL) { // should always be the case
imageLabel->setPixmap(QIcon::fromTheme(QStringLiteral("image-missing")).pixmap(KIconLoader::SizeLarge, QIcon::Disabled));
}
}
void KImageFilePreviewPrivate::slotResult(KJob *job)
{
if (job == m_job) {
m_job = nullptr;
}
}
void KImageFilePreviewPrivate::slotStepAnimation()
{
const QSize currSize = m_pmCurrent.size();
const QSize transitionSize = m_pmTransition.size();
const int width = std::max(currSize.width(), transitionSize.width());
const int height = std::max(currSize.height(), transitionSize.height());
QPixmap pm(QSize(width, height));
pm.fill(Qt::transparent);
QPainter p(&pm);
p.setOpacity(m_pmCurrentOpacity);
// If we have a current pixmap
if (!m_pmCurrent.isNull()) {
p.drawPixmap(QPoint(((float)pm.size().width() - m_pmCurrent.size().width()) / 2.0, ((float)pm.size().height() - m_pmCurrent.size().height()) / 2.0),
m_pmCurrent);
}
if (!m_pmTransition.isNull()) {
p.setOpacity(m_pmTransitionOpacity);
p.drawPixmap(
QPoint(((float)pm.size().width() - m_pmTransition.size().width()) / 2.0, ((float)pm.size().height() - m_pmTransition.size().height()) / 2.0),
m_pmTransition);
}
p.end();
imageLabel->setPixmap(pm);
m_pmCurrentOpacity = qMax(m_pmCurrentOpacity - 0.4, 0.0); // krazy:exclude=qminmax
m_pmTransitionOpacity = qMin(m_pmTransitionOpacity + 0.4, 1.0); // krazy:exclude=qminmax
}
void KImageFilePreviewPrivate::slotFinished()
{
m_pmCurrent = m_pmTransition;
m_pmTransitionOpacity = 0;
m_pmCurrentOpacity = 1;
m_pmTransition = QPixmap();
// The animation might have lost some frames. Be sure that if the last one
// was dropped, the last image shown is the opaque one.
imageLabel->setPixmap(m_pmCurrent);
clear = false;
}
void KImageFilePreview::clearPreview()
{
if (d->m_job) {
d->m_job->kill();
d->m_job = nullptr;
}
if (d->clear || (d->m_timeLine && d->m_timeLine->state() == QTimeLine::Running)) {
return;
}
if (d->m_timeLine) {
d->m_pmTransition = QPixmap();
// If we add a previous preview then we run the animation
if (!d->m_pmCurrent.isNull()) {
d->m_timeLine->setCurrentTime(0);
d->m_timeLine->setDirection(QTimeLine::Backward);
d->m_timeLine->start();
}
d->currentURL.clear();
d->clear = true;
} else {
d->imageLabel->clear();
}
}
#include "moc_kimagefilepreview.cpp"
@@ -0,0 +1,82 @@
/*
This file is part of the KDE project.
SPDX-FileCopyrightText: 2001 Martin R. Jones <mjones@kde.org>
SPDX-FileCopyrightText: 2001 Carsten Pfeiffer <pfeiffer@kde.org>
SPDX-FileCopyrightText: 2008 Rafael Fernández López <ereslibre@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KIMAGEFILEPREVIEW_H
#define KIMAGEFILEPREVIEW_H
#include <QPixmap>
#include <QUrl>
#include <kpreviewwidgetbase.h>
#include <memory>
class KFileItem;
class KJob;
class KImageFilePreviewPrivate;
namespace KIO
{
class PreviewJob;
}
/**
* @class KImageFilePreview kimagefilepreview.h <KImageFilePreview>
*
* Image preview widget for the file dialog.
*/
class KIOFILEWIDGETS_EXPORT KImageFilePreview : public KPreviewWidgetBase
{
Q_OBJECT
public:
/**
* Creates a new image file preview.
*
* @param parent The parent widget.
*/
explicit KImageFilePreview(QWidget *parent = nullptr);
/**
* Destroys the image file preview.
*/
~KImageFilePreview() override;
/**
* Returns the size hint for this widget.
*/
QSize sizeHint() const override;
public Q_SLOTS:
/**
* Shows a preview for the given @p url.
*/
void showPreview(const QUrl &url) override;
/**
* Clears the preview.
*/
void clearPreview() override;
protected Q_SLOTS:
void showPreview();
void showPreview(const QUrl &url, bool force);
virtual void gotPreview(const KFileItem &, const QPixmap &);
protected:
void resizeEvent(QResizeEvent *event) override;
virtual KIO::PreviewJob *createJob(const QUrl &url, int width, int height);
private:
std::unique_ptr<KImageFilePreviewPrivate> const d;
Q_DISABLE_COPY(KImageFilePreview)
};
#endif // KIMAGEFILEPREVIEW_H
@@ -0,0 +1,139 @@
/*
SPDX-FileCopyrightText: 1998, 2008, 2009 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "knameandurlinputdialog.h"
#include <KLineEdit> // For KUrlRequester::lineEdit()
#include <QDialogButtonBox>
#include <QFormLayout>
#include <QVBoxLayout>
#include <kprotocolmanager.h>
#include <kurlrequester.h>
class KNameAndUrlInputDialogPrivate
{
public:
explicit KNameAndUrlInputDialogPrivate(KNameAndUrlInputDialog *qq)
: m_fileNameEdited(false)
, q(qq)
{
}
void slotNameTextChanged(const QString &);
void slotURLTextChanged(const QString &);
/**
* The line edit widget for the fileName
*/
QLineEdit *m_leName;
/**
* The URL requester for the URL :)
*/
KUrlRequester *m_urlRequester;
/**
* True if the filename was manually edited.
*/
bool m_fileNameEdited;
QDialogButtonBox *m_buttonBox;
KNameAndUrlInputDialog *const q;
};
KNameAndUrlInputDialog::KNameAndUrlInputDialog(const QString &nameLabel, const QString &urlLabel, const QUrl &startDir, QWidget *parent)
: QDialog(parent)
, d(new KNameAndUrlInputDialogPrivate(this))
{
QVBoxLayout *topLayout = new QVBoxLayout(this);
QFormLayout *formLayout = new QFormLayout;
formLayout->setContentsMargins(0, 0, 0, 0);
// First line: filename
d->m_leName = new QLineEdit(this);
d->m_leName->setMinimumWidth(d->m_leName->sizeHint().width() * 3);
d->m_leName->setSelection(0, d->m_leName->text().length()); // autoselect
connect(d->m_leName, &QLineEdit::textChanged, this, [this](const QString &text) {
d->slotNameTextChanged(text);
});
formLayout->addRow(nameLabel, d->m_leName);
// Second line: url
d->m_urlRequester = new KUrlRequester(this);
d->m_urlRequester->setStartDir(startDir);
d->m_urlRequester->setMode(KFile::File | KFile::Directory);
d->m_urlRequester->setMinimumWidth(d->m_urlRequester->sizeHint().width() * 3);
connect(d->m_urlRequester->lineEdit(), &QLineEdit::textChanged, this, [this](const QString &text) {
d->slotURLTextChanged(text);
});
formLayout->addRow(urlLabel, d->m_urlRequester);
topLayout->addLayout(formLayout);
d->m_buttonBox = new QDialogButtonBox(this);
d->m_buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
connect(d->m_buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(d->m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
topLayout->addWidget(d->m_buttonBox);
d->m_fileNameEdited = false;
d->m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!d->m_leName->text().isEmpty() && !d->m_urlRequester->url().isEmpty());
d->m_leName->setFocus();
}
KNameAndUrlInputDialog::~KNameAndUrlInputDialog() = default;
QUrl KNameAndUrlInputDialog::url() const
{
return d->m_urlRequester->url();
}
QString KNameAndUrlInputDialog::urlText() const
{
return d->m_urlRequester->text();
}
QString KNameAndUrlInputDialog::name() const
{
return d->m_leName->text();
}
void KNameAndUrlInputDialogPrivate::slotNameTextChanged(const QString &)
{
m_fileNameEdited = true;
m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!m_leName->text().isEmpty() && !m_urlRequester->url().isEmpty());
}
void KNameAndUrlInputDialogPrivate::slotURLTextChanged(const QString &)
{
if (!m_fileNameEdited) {
// use URL as default value for the filename
// (we copy only its filename if protocol supports listing,
// but for HTTP we don't want tons of index.html links)
QUrl url(m_urlRequester->url());
if (KProtocolManager::supportsListing(url) && !url.fileName().isEmpty()) {
m_leName->setText(url.fileName());
} else {
m_leName->setText(url.toString());
}
m_fileNameEdited = false; // slotNameTextChanged set it to true erroneously
}
m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!m_leName->text().isEmpty() && !m_urlRequester->url().isEmpty());
}
void KNameAndUrlInputDialog::setSuggestedName(const QString &name)
{
d->m_leName->setText(name);
d->m_urlRequester->setFocus();
}
void KNameAndUrlInputDialog::setSuggestedUrl(const QUrl &url)
{
d->m_urlRequester->setUrl(url);
}
#include "moc_knameandurlinputdialog.cpp"
@@ -0,0 +1,71 @@
/*
SPDX-FileCopyrightText: 1998, 2008, 2009 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#ifndef KNAMEANDURLINPUTDIALOG_H
#define KNAMEANDURLINPUTDIALOG_H
#include "kiofilewidgets_export.h"
#include <QDialog>
#include <memory>
class KUrlRequester;
class KNameAndUrlInputDialogPrivate;
class QUrl;
/**
* @class KNameAndUrlInputDialog knameandurlinputdialog.h <KNameAndUrlInputDialog>
*
* Dialog to ask for a name (e.g.\ filename) and a URL
* Basically a merge of KLineEditDlg and KUrlRequesterDlg ;)
* @author David Faure <faure@kde.org>
*/
class KIOFILEWIDGETS_EXPORT KNameAndUrlInputDialog : public QDialog
{
Q_OBJECT
public:
/**
* @param nameLabel label for the name field
* @param urlLabel label for the URL requester
* @param startDir start directory for the URL requester (optional)
* @param parent parent widget
*/
KNameAndUrlInputDialog(const QString &nameLabel, const QString &urlLabel, const QUrl &startDir, QWidget *parent);
/**
* Destructor.
*/
~KNameAndUrlInputDialog() override;
/**
* Pre-fill the name lineedit.
*/
void setSuggestedName(const QString &name);
/**
* Pre-fill the URL requester.
*/
void setSuggestedUrl(const QUrl &url);
/**
* @return the name the user entered
*/
QString name() const;
/**
* @return the URL the user entered
*/
QUrl url() const;
/**
* @return the URL the user entered, as plain text.
* This is only useful for creating relative symlinks.
* @since 5.25
*/
QString urlText() const;
private:
std::unique_ptr<KNameAndUrlInputDialogPrivate> const d;
};
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,242 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 1998-2009 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2003 Sven Leiber <s.leiber@web.de>
SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only
*/
#ifndef KNEWFILEMENU_H
#define KNEWFILEMENU_H
#include "kiofilewidgets_export.h"
#include <KActionMenu>
#include <QUrl>
#include <memory>
class KJob;
class KActionCollection;
class KNewFileMenuPrivate;
/**
* @class KNewFileMenu knewfilemenu.h <KNewFileMenu>
*
* The 'Create New' submenu, for creating files using templates
* (e.g.\ "new HTML file") and directories.
*
* The same instance can be used by both for the File menu and the RMB popup menu,
* in a file manager. This is also used in the file dialog's RMB menu.
*
* To use this class, you need to connect aboutToShow() of the File menu
* with slotCheckUpToDate() and to call slotCheckUpToDate() before showing
* the RMB popupmenu.
*
* KNewFileMenu automatically updates the list of templates shown if installed templates
* are added/updated/deleted.
*
* @author Björn Ruberg <bjoern@ruberg-wegener.de>
* Made dialogs working asynchronously
* @author David Faure <faure@kde.org>
* Ideas and code for the new template handling mechanism ('link' desktop files)
* from Christoph Pickart <pickart@iam.uni-bonn.de>
*/
class KIOFILEWIDGETS_EXPORT KNewFileMenu : public KActionMenu
{
Q_OBJECT
public:
/**
* Constructor.
*
* @param parent the parent object, for ownership.
* If the parent object is a widget, it will also be used as the parent widget
* for any dialogs that this class might show. Otherwise, call setParentWidget.
*
* @since 5.100
*/
KNewFileMenu(QObject *parent);
/**
* Destructor.
* KNewMenu uses internally a globally shared cache, so that multiple instances
* of it don't need to parse the installed templates multiple times. Therefore
* you can safely create and delete KNewMenu instances without a performance issue.
*/
~KNewFileMenu() override;
/**
* Returns the modality of dialogs
*/
bool isModal() const;
/**
* Sets the modality of dialogs created by KNewFile. Set to false if you do not want to block
* your application window when entering a new directory name i.e.
*/
void setModal(bool modality);
/**
* Sets a parent widget for the dialogs shown by KNewFileMenu.
* This is strongly recommended, for apps with a main window.
*/
void setParentWidget(QWidget *parentWidget);
/**
* Set the working directory.
* Files will be created relative to this directory.
* @since 5.97.
*/
void setWorkingDirectory(const QUrl &directory);
/**
* Returns the working directory.
* Files will be created relative to this directory.
* @since 5.97.
*/
QUrl workingDirectory() const;
/**
* Only show the files in a given set of MIME types.
* This is useful in specialized applications (while file managers, on
* the other hand, want to show all MIME types).
*/
void setSupportedMimeTypes(const QStringList &mime);
/**
* Returns the MIME types set in supportedMimeTypes()
*/
QStringList supportedMimeTypes() const;
/**
* Whether on not the dialog should emit `selectExistingDir` when trying to create an exist directory
*
* default: false
*
* @since 5.76
*/
void setSelectDirWhenAlreadyExist(bool b);
/**
* Use this to set a shortcut for the "New Folder" action.
*
* The shortcut is copied from @param action.
*
* @since 5.100
*/
void setNewFolderShortcutAction(QAction *action);
/**
* Use this to set a shortcut for the new file action.
*
* The shortcut is copied from @param action.
*
* @since 5.100
*/
void setNewFileShortcutAction(QAction *action);
/**
* Use this to check if namejob for new directory creation still running.
* Namejob is what spawns the new directory dialog, which can be slow in,
* for example, network folders.
*
* @since 6.2
*/
bool isCreateDirectoryRunning();
/**
* Use this to check if the file creation process is still running.
* @since 6.2
*/
bool isCreateFileRunning();
public Q_SLOTS:
/**
* Checks if updating the list is necessary
* IMPORTANT : Call this in the slot for aboutToShow.
*/
void checkUpToDate();
/**
* Call this to create a new directory as if the user had done it using
* a popupmenu. This is useful to make sure that creating a directory with
* a key shortcut (e.g. F10) triggers the exact same code as when using
* the New menu.
* Requirements: since 5.97 call setWorkingDirectory first (for older releases call setPopupFiles first), and keep this KNewFileMenu instance
* alive (the mkdir is async).
*/
void createDirectory();
/**
* Call this to create a new file as if the user had done it using
* a popupmenu. This is useful to make sure that creating a directory with
* a key shortcut (e.g. Shift-F10) triggers the exact same code as when using
* the New menu.
* Requirements: since 5.97 call setWorkingDirectory first (for older releases call setPopupFiles first), and keep this KNewFileMenu instance
* alive (the copy is async).
* @since 5.53
*/
void createFile();
Q_SIGNALS:
/**
* Emitted once the creation job for file @p url has been started
* @since 6.2
*/
void fileCreationStarted(const QUrl &url);
/**
* Emitted once the file (or symlink) @p url has been successfully created
*/
void fileCreated(const QUrl &url);
/**
* Emitted once the creation for file @p url has been rejected
* @since 6.2
*/
void fileCreationRejected(const QUrl &url);
/**
* Emitted once the creation job for directory @p url has been started
* @since 6.2
*/
void directoryCreationStarted(const QUrl &url);
/**
* Emitted once the directory @p url has been successfully created
*/
void directoryCreated(const QUrl &url);
/**
* Emitted once the creation for directory @p url has been rejected
* @since 6.2
*/
void directoryCreationRejected(const QUrl &url);
/**
* Emitted when trying to create a new directory that has the same name as
* an existing one, so that KDirOperator can select the existing item in
* the view (in case the user wants to use that directory instead of creating
* a new one).
*
* @since 5.76
*/
void selectExistingDir(const QUrl &url);
protected Q_SLOTS:
/**
* Called when the job that copied the template has finished.
* This method is virtual so that error handling can be reimplemented.
* Make sure to call the base class slotResult when !job->error() though.
*/
virtual void slotResult(KJob *job);
private:
friend class KNewFileMenuPrivate;
std::unique_ptr<KNewFileMenuPrivate> const d;
};
#endif
@@ -0,0 +1,34 @@
/*
This file is part of the KDE project.
SPDX-FileCopyrightText: 2003 Carsten Pfeiffer <pfeiffer@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include "kpreviewwidgetbase.h"
class Q_DECL_HIDDEN KPreviewWidgetBase::KPreviewWidgetBasePrivate
{
public:
QStringList supportedMimeTypes;
};
KPreviewWidgetBase::KPreviewWidgetBase(QWidget *parent)
: QWidget(parent)
, d(new KPreviewWidgetBasePrivate)
{
}
KPreviewWidgetBase::~KPreviewWidgetBase() = default;
void KPreviewWidgetBase::setSupportedMimeTypes(const QStringList &mimeTypes)
{
d->supportedMimeTypes = mimeTypes;
}
QStringList KPreviewWidgetBase::supportedMimeTypes() const
{
return d->supportedMimeTypes;
}
#include "moc_kpreviewwidgetbase.cpp"
@@ -0,0 +1,78 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2001 Frerich Raabe <raabe@kde.org>
SPDX-FileCopyrightText: 2003 Carsten Pfeiffer <pfeiffer@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef __KPREVIEWWIDGETBASE_H__
#define __KPREVIEWWIDGETBASE_H__
#include <QWidget>
#include "kiofilewidgets_export.h"
#include <memory>
class QUrl;
/**
* @class KPreviewWidgetBase kpreviewwidgetbase.h <KPreviewWidgetBase>
*
* Abstract baseclass for all preview widgets which shall be used via
* KFileDialog::setPreviewWidget(const KPreviewWidgetBase *).
* Ownership will be transferred to KFileDialog, so you have to create
* the preview with "new" and let KFileDialog delete it.
*
* Just derive your custom preview widget from KPreviewWidgetBase and implement
* all the pure virtual methods. The slot showPreview(const QUrl &) is called
* every time the file selection changes.
*
* @short Abstract baseclass for all preview widgets.
* @author Frerich Raabe <raabe@kde.org>
*/
class KIOFILEWIDGETS_EXPORT KPreviewWidgetBase : public QWidget
{
Q_OBJECT
public:
/**
* Constructor. Construct the user interface of your preview widget here
* and pass the KFileDialog this preview widget is going to be used in as
* the parent.
*
* @param parent The KFileDialog this preview widget is going to be used in
*/
explicit KPreviewWidgetBase(QWidget *parent);
~KPreviewWidgetBase() override;
QStringList supportedMimeTypes() const;
public Q_SLOTS:
/**
* This slot is called every time the user selects another file in the
* file dialog. Implement the stuff necessary to reflect the change here.
*
* @param url The URL of the currently selected file.
*/
virtual void showPreview(const QUrl &url) = 0;
/**
* Reimplement this to clear the preview. This is called when e.g. the
* selection is cleared or when multiple selections exist, or the directory
* is changed.
*/
virtual void clearPreview() = 0;
protected:
void setSupportedMimeTypes(const QStringList &mimeTypes);
private:
class KPreviewWidgetBasePrivate;
std::unique_ptr<KPreviewWidgetBasePrivate> const d;
Q_DISABLE_COPY(KPreviewWidgetBase)
};
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,481 @@
/*
SPDX-FileCopyrightText: 2006-2010 Peter Penz <peter.penz@gmx.at>
SPDX-FileCopyrightText: 2006 Aaron J. Seigo <aseigo@kde.org>
SPDX-FileCopyrightText: 2007 Kevin Ottens <ervin@kde.org>
SPDX-FileCopyrightText: 2007 Urs Wolfer <uwolfer @ kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KURLNAVIGATOR_H
#define KURLNAVIGATOR_H
#include "kiofilewidgets_export.h"
#include <QByteArray>
#include <QUrl>
#include <QWidget>
#include <memory>
class QMouseEvent;
class KFilePlacesModel;
class KUrlComboBox;
class KUrlNavigatorPrivate;
/**
* @class KUrlNavigator kurlnavigator.h <KUrlNavigator>
*
* @brief Widget that allows to navigate through the paths of an URL.
*
* The URL navigator offers two modes:
* - Editable: The URL of the location is editable inside an editor.
* By pressing RETURN the URL will get activated.
* - Non editable ("breadcrumb view"): The URL of the location is represented by
* a number of buttons, where each button represents a path
* of the URL. By clicking on a button the path will get
* activated. This mode also supports drag and drop of items.
*
* The mode can be changed by clicking on the empty area of the URL navigator.
* It is recommended that the application remembers the setting
* or allows to configure the default mode (see KUrlNavigator::setUrlEditable()).
*
* The URL navigator remembers the URL history during navigation and allows to go
* back and forward within this history.
*
* In the non editable mode ("breadcrumb view") it can be configured whether
* the full path should be shown. It is recommended that the application
* remembers the setting or allows to configure the default mode (see
* KUrlNavigator::setShowFullPath()).
*
* The typical usage of the KUrlNavigator is:
* - Create an instance providing a places model and an URL.
* - Create an instance of QAbstractItemView which shows the content of the URL
* given by the URL navigator.
* - Connect to the signal KUrlNavigator::urlChanged() and synchronize the content of
* QAbstractItemView with the URL given by the URL navigator.
*
* It is recommended, that the application remembers the state of the QAbstractItemView
* when the URL has been changed. This allows to restore the view state when going back in history.
* KUrlNavigator offers support for remembering the view state:
* - The signal urlAboutToBeChanged() will be emitted before the URL change takes places.
* This allows the application to store the view state by KUrlNavigator::saveLocationState().
* - The signal urlChanged() will be emitted after the URL change took place. This allows
* the application to restore the view state by getting the values from
* KUrlNavigator::locationState().
*/
class KIOFILEWIDGETS_EXPORT KUrlNavigator : public QWidget
{
Q_OBJECT
public:
/** @since 4.5 */
KUrlNavigator(QWidget *parent = nullptr);
/**
* @param placesModel Model for the places which are selectable inside a
* menu. A place can be a bookmark or a device. If it is 0,
* no places selector is displayed.
* @param url URL which is used for the navigation or editing.
* @param parent Parent widget.
*/
KUrlNavigator(KFilePlacesModel *placesModel, const QUrl &url, QWidget *parent);
~KUrlNavigator() override;
/**
* @return URL of the location given by the \a historyIndex. If \a historyIndex
* is smaller than 0, the URL of the current location is returned.
* @since 4.5
*/
QUrl locationUrl(int historyIndex = -1) const;
/**
* Saves the location state described by \a state for the current location. It is recommended
* that at least the scroll position of a view is remembered and restored when traversing
* through the history. Saving the location state should be done when the signal
* KUrlNavigator::urlAboutToBeChanged() has been emitted. Restoring the location state (see
* KUrlNavigator::locationState()) should be done when the signal KUrlNavigator::urlChanged()
* has been emitted.
*
* Example:
* \code
* QByteArray state;
* QDataStream data(&state, QIODevice::WriteOnly);
* data << QPoint(x, y);
* data << ...;
* ...
* urlNavigator->saveLocationState(state);
* \endcode
*
*/
void saveLocationState(const QByteArray &state);
/**
* @return Location state given by \a historyIndex. If \a historyIndex
* is smaller than 0, the state of the current location is returned.
* @see KUrlNavigator::saveLocationState()
* @since 4.5
*/
QByteArray locationState(int historyIndex = -1) const;
/**
* Goes back one step in the URL history. The signals
* KUrlNavigator::urlAboutToBeChanged(), KUrlNavigator::urlChanged() and
* KUrlNavigator::historyChanged() are emitted if true is returned. False is returned
* if the beginning of the history has already been reached and hence going back was
* not possible. The history index (see KUrlNavigator::historyIndex()) is
* increased by one if the operation was successful.
*/
bool goBack();
/**
* Goes forward one step in the URL history. The signals
* KUrlNavigator::urlAboutToBeChanged(), KUrlNavigator::urlChanged() and
* KUrlNavigator::historyChanged() are emitted if true is returned. False is returned
* if the end of the history has already been reached and hence going forward
* was not possible. The history index (see KUrlNavigator::historyIndex()) is
* decreased by one if the operation was successful.
*/
bool goForward();
/**
* Goes up one step of the URL path and remembers the old path
* in the history. The signals KUrlNavigator::urlAboutToBeChanged(),
* KUrlNavigator::urlChanged() and KUrlNavigator::historyChanged() are
* emitted if true is returned. False is returned if going up was not
* possible as the root has been reached.
*/
bool goUp();
/**
* Goes to the home URL and remembers the old URL in the history.
* The signals KUrlNavigator::urlAboutToBeChanged(), KUrlNavigator::urlChanged()
* and KUrlNavigator::historyChanged() are emitted.
*
* @see KUrlNavigator::setHomeUrl()
*/
// KDE5: Remove the home-property. It is sufficient to invoke
// KUrlNavigator::setLocationUrl(homeUrl) on application-side.
void goHome();
/**
* Sets the home URL used by KUrlNavigator::goHome(). If no
* home URL is set, the default home path of the user is used.
*/
// KDE5: Remove the home-property. It is sufficient to invoke
// KUrlNavigator::setLocationUrl(homeUrl) on application-side.
void setHomeUrl(const QUrl &url);
QUrl homeUrl() const;
/**
* Allows to edit the URL of the navigation bar if \a editable
* is true, and sets the focus accordingly.
* If \a editable is false, each part of
* the URL is presented by a button for a fast navigation ("breadcrumb view").
*/
void setUrlEditable(bool editable);
/**
* @return True, if the URL is editable within a line editor.
* If false is returned, each part of the URL is presented by a button
* for fast navigation ("breadcrumb view").
*/
bool isUrlEditable() const;
/**
* Shows the full path of the URL even if a place represents a part of the URL.
* Assuming that a place called "Pictures" uses the URL /home/user/Pictures.
* An URL like /home/user/Pictures/2008 is shown as [Pictures] > [2008]
* in the breadcrumb view, if showing the full path is turned off. If
* showing the full path is turned on, the URL is shown
* as [/] > [home] > [Pictures] > [2008].
*/
void setShowFullPath(bool show);
/**
* @return True, if the full path of the URL should be shown in the breadcrumb view.
* @since 4.2
*/
bool showFullPath() const;
/**
* Set the URL navigator to the active mode, if \a active
* is true. The active mode is default. The inactive mode only differs
* visually from the active mode, no change of the behavior is given.
*
* Using the URL navigator in the inactive mode is useful when having split views,
* where the inactive view is indicated by an inactive URL
* navigator visually.
*/
void setActive(bool active);
/**
* @return True, if the URL navigator is in the active mode.
* @see KUrlNavigator::setActive()
*/
bool isActive() const;
/**
* Sets the places selector visible, if \a visible is true.
* The places selector allows to select the places provided
* by the places model passed in the constructor. Per default
* the places selector is visible.
*/
void setPlacesSelectorVisible(bool visible);
/** @return True, if the places selector is visible. */
bool isPlacesSelectorVisible() const;
/**
* @return The currently entered, but not accepted URL.
* It is possible that the returned URL is not valid.
*/
QUrl uncommittedUrl() const;
/**
* @return The amount of locations in the history. The data for each
* location can be retrieved by KUrlNavigator::locationUrl() and
* KUrlNavigator::locationState().
*/
int historySize() const;
/**
* @return The history index of the current location, where
* 0 <= history index < KUrlNavigator::historySize(). 0 is the most
* recent history entry.
*/
int historyIndex() const;
/**
* @return The used editor when the navigator is in the edit mode
* @see KUrlNavigator::setUrlEditable()
*/
KUrlComboBox *editor() const;
/**
* Set the URL schemes that the navigator should allow navigating to.
*
* If the passed list is empty, all schemes are supported. Examples for
* schemes are @c "file" or @c "ftp".
*
* @sa QFileDialog::setSupportedSchemes
* @since 5.103
*/
void setSupportedSchemes(const QStringList &schemes);
/**
* Returns the URL schemes that the navigator should allow navigating to.
*
* If the returned list is empty, all schemes are supported.
*
* @sa QFileDialog::supportedSchemes
* @since 5.103
*/
QStringList supportedSchemes() const;
/**
* The child widget that received the QDropEvent when dropping on the URL
* navigator. You can pass this widget to KJobWidgets::setWindow()
* if you need to show a drop menu with KIO::drop().
* @return Child widget that has received the last drop event, or nullptr if
* nothing has been dropped yet on the URL navigator.
* @since 5.37
* @see KIO::drop()
*/
QWidget *dropWidget() const;
/**
* Sets whether to show hidden folders in the subdirectories popup.
* @since 5.87
*/
void setShowHiddenFolders(bool showHiddenFolders);
/**
* Returns whether to show hidden folders in the subdirectories popup.
* @since 5.87
*/
bool showHiddenFolders() const;
/**
* Sets whether to sort hidden folders in the subdirectories popup last.
* @since 5.87
*/
void setSortHiddenFoldersLast(bool sortHiddenFoldersLast);
/**
* Returns whether to sort hidden folders in the subdirectories popup last.
* @since 5.87
*/
bool sortHiddenFoldersLast() const;
/**
* Puts \a widget to the right of the breadcrumb.
*
* KUrlNavigator takes ownership over \a widget. Any existing badge widget is deleted.
*
* NOTE: There is no limit to the size of the badge widget. If your badge widget is taller than other
* controls in KUrlNavigator, then the whole KUrlNavigator will be resized to accommodate it. Also,
* KUrlNavigator has fixed minimumWidth of 100, so if your badge widget is too wide, it might be clipped
* when the space is tight. You might want to call KUrlNavigator::setMinimumWidth() with a larger value
* in that case.
* In general, it is recommended to keep the badge widget small and not expanding, to avoid layout issues.
* @since 6.2
*/
void setBadgeWidget(QWidget *widget);
/**
* Returns the badge widget set by setBadgeWidget(). If setBadgeWidget() hasn't been called, returns nullptr.
* @since 6.2
*/
QWidget *badgeWidget() const;
public Q_SLOTS:
/**
* Sets the location to \a url. The old URL is added to the history.
* The signals KUrlNavigator::urlAboutToBeChanged(), KUrlNavigator::urlChanged()
* and KUrlNavigator::historyChanged() are emitted. Use
* KUrlNavigator::locationUrl() to read the location.
*/
void setLocationUrl(const QUrl &url);
/**
* Activates the URL navigator (KUrlNavigator::isActive() will return true)
* and emits the signal KUrlNavigator::activated().
* @see KUrlNavigator::setActive()
*/
void requestActivation();
#if !defined(K_DOXYGEN)
// KDE5: Remove and listen for focus-signal instead
void setFocus();
#endif
Q_SIGNALS:
/**
* Is emitted, if the URL navigator has been activated by
* an user interaction
* @see KUrlNavigator::setActive()
*/
void activated();
/**
* Is emitted, if the location URL has been changed e. g. by
* the user.
* @see KUrlNavigator::setUrl()
*/
void urlChanged(const QUrl &url);
/**
* Is emitted, before the location URL is going to be changed to \a newUrl.
* The signal KUrlNavigator::urlChanged() will be emitted after the change
* has been done. Connecting to this signal is useful to save the state
* of a view with KUrlNavigator::saveLocationState().
*/
void urlAboutToBeChanged(const QUrl &newUrl);
/**
* Is emitted, if the editable state for the URL has been changed
* (see KUrlNavigator::setUrlEditable()).
*/
void editableStateChanged(bool editable);
/**
* Is emitted, if the history has been changed. Usually
* the history is changed if a new URL has been selected.
*/
void historyChanged();
/**
* Is emitted if a dropping has been done above the destination
* \a destination. The receiver must accept the drop event if
* the dropped data can be handled.
*/
void urlsDropped(const QUrl &destination, QDropEvent *event);
/**
* This signal is emitted when the Return or Enter key is pressed.
*/
void returnPressed();
/**
* Is emitted if the URL \a url should be opened in a new inactive tab because
* the user clicked on a breadcrumb with the middle mouse button or
* left-clicked with the ctrl modifier pressed or pressed return with
* the alt modifier pressed.
*/
void tabRequested(const QUrl &url);
/**
* Is emitted if the URL \a url should be opened in a new active tab because
* the user clicked on a breadcrumb with the middle mouse button with
* the shift modifier pressed or left-clicked with both the ctrl and shift
* modifiers pressed or pressed return with both the alt and shift modifiers
* pressed.
* @since 5.89
*/
void activeTabRequested(const QUrl &url);
/**
* Is emitted if the URL \a url should be opened in a new window because
* the user left-clicked on a breadcrumb with the shift modifier pressed
* or pressed return with the shift modifier pressed.
* @since 5.89
*/
void newWindowRequested(const QUrl &url);
/**
* When the URL is changed and the new URL (e.g.\ /home/user1/)
* is a parent of the previous URL (e.g.\ /home/user1/data/stuff),
* then this signal is emitted and \p url is set to the child
* directory of the new URL which is an ancestor of the old URL
* (in the example paths this would be /home/user1/data/).
* This signal allows file managers to pre-select the directory
* that the user is navigating up from.
* @since 5.37.0
*/
void urlSelectionRequested(const QUrl &url);
protected:
#if !defined(K_DOXYGEN)
/**
* If the Escape key is pressed, the navigation bar should switch
* to the breadcrumb view.
* @see QWidget::keyPressEvent()
*/
void keyPressEvent(QKeyEvent *event) override;
/**
* Reimplemented for internal purposes.
*/
void keyReleaseEvent(QKeyEvent *event) override;
/**
* Paste the clipboard content as URL, if the middle mouse
* button has been clicked.
* @see QWidget::mouseReleaseEvent()
*/
void mouseReleaseEvent(QMouseEvent *event) override;
/**
* Reimplemented to activate on middle mousse button click
*/
void mousePressEvent(QMouseEvent *event) override;
void resizeEvent(QResizeEvent *event) override;
void wheelEvent(QWheelEvent *event) override;
bool eventFilter(QObject *watched, QEvent *event) override;
#endif
private:
friend class KUrlNavigatorPrivate;
std::unique_ptr<KUrlNavigatorPrivate> const d;
Q_DISABLE_COPY(KUrlNavigator)
};
#endif
@@ -0,0 +1,709 @@
/*
SPDX-FileCopyrightText: 2006 Peter Penz <peter.penz@gmx.at>
SPDX-FileCopyrightText: 2006 Aaron J. Seigo <aseigo@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kurlnavigatorbutton_p.h"
#include "../utils_p.h"
#include "kurlnavigator.h"
#include "kurlnavigatormenu_p.h"
#include <kio/listjob.h>
#include <kio/statjob.h>
#include <KLocalizedString>
#include <KStringHandler>
#include <QCollator>
#include <QKeyEvent>
#include <QMimeData>
#include <QPainter>
#include <QStyleOption>
#include <QTimer>
namespace KDEPrivate
{
QPointer<KUrlNavigatorMenu> KUrlNavigatorButton::m_subDirsMenu;
KUrlNavigatorButton::KUrlNavigatorButton(const QUrl &url, KUrlNavigator *parent)
: KUrlNavigatorButtonBase(parent)
, m_hoverArrow(false)
, m_pendingTextChange(false)
, m_replaceButton(false)
, m_showMnemonic(false)
, m_wheelSteps(0)
, m_url(url)
, m_subDir()
, m_openSubDirsTimer(nullptr)
, m_subDirsJob(nullptr)
{
setAcceptDrops(true);
setUrl(url);
setMouseTracking(true);
m_openSubDirsTimer = new QTimer(this);
m_openSubDirsTimer->setSingleShot(true);
m_openSubDirsTimer->setInterval(300);
connect(m_openSubDirsTimer, &QTimer::timeout, this, &KUrlNavigatorButton::startSubDirsJob);
connect(this, &QAbstractButton::pressed, this, &KUrlNavigatorButton::requestSubDirs);
}
KUrlNavigatorButton::~KUrlNavigatorButton()
{
}
void KUrlNavigatorButton::setUrl(const QUrl &url)
{
m_url = url;
// Doing a text-resolving with KIO::stat() for all non-local
// URLs leads to problems for protocols where a limit is given for
// the number of parallel connections. A black-list
// is given where KIO::stat() should not be used:
static const QSet<QString> protocolBlacklist = QSet<QString>{
QStringLiteral("nfs"),
QStringLiteral("fish"),
QStringLiteral("ftp"),
QStringLiteral("sftp"),
QStringLiteral("smb"),
QStringLiteral("webdav"),
QStringLiteral("mtp"),
};
const bool startTextResolving = m_url.isValid() && !m_url.isLocalFile() && !protocolBlacklist.contains(m_url.scheme());
if (startTextResolving) {
m_pendingTextChange = true;
KIO::StatJob *job = KIO::stat(m_url, KIO::HideProgressInfo);
connect(job, &KJob::result, this, &KUrlNavigatorButton::statFinished);
Q_EMIT startedTextResolving();
} else {
setText(m_url.fileName().replace(QLatin1Char('&'), QLatin1String("&&")));
}
}
QUrl KUrlNavigatorButton::url() const
{
return m_url;
}
void KUrlNavigatorButton::setText(const QString &text)
{
QString adjustedText = text;
if (adjustedText.isEmpty()) {
adjustedText = m_url.scheme();
}
// Assure that the button always consists of one line
adjustedText.remove(QLatin1Char('\n'));
KUrlNavigatorButtonBase::setText(adjustedText);
updateMinimumWidth();
// Assure that statFinished() does not overwrite a text that has been
// set by a client of the URL navigator button
m_pendingTextChange = false;
}
void KUrlNavigatorButton::setActiveSubDirectory(const QString &subDir)
{
m_subDir = subDir;
// We use a different (bold) font on active, so the size hint changes
updateGeometry();
update();
}
QString KUrlNavigatorButton::activeSubDirectory() const
{
return m_subDir;
}
QSize KUrlNavigatorButton::sizeHint() const
{
QFont adjustedFont(font());
adjustedFont.setBold(m_subDir.isEmpty());
// the minimum size is textWidth + arrowWidth() + 2 * BorderWidth; for the
// preferred size we add the BorderWidth 2 times again for having an uncluttered look
const int width = QFontMetrics(adjustedFont).size(Qt::TextSingleLine, plainText()).width() + arrowWidth() + 4 * BorderWidth;
return QSize(width, KUrlNavigatorButtonBase::sizeHint().height());
}
void KUrlNavigatorButton::setShowMnemonic(bool show)
{
if (m_showMnemonic != show) {
m_showMnemonic = show;
update();
}
}
bool KUrlNavigatorButton::showMnemonic() const
{
return m_showMnemonic;
}
void KUrlNavigatorButton::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event);
QPainter painter(this);
QFont adjustedFont(font());
adjustedFont.setBold(m_subDir.isEmpty());
painter.setFont(adjustedFont);
int buttonWidth = width();
int preferredWidth = sizeHint().width();
if (preferredWidth < minimumWidth()) {
preferredWidth = minimumWidth();
}
if (buttonWidth > preferredWidth) {
buttonWidth = preferredWidth;
}
const int buttonHeight = height();
const QColor fgColor = foregroundColor();
drawHoverBackground(&painter);
int textLeft = 0;
int textWidth = buttonWidth;
const bool leftToRight = (layoutDirection() == Qt::LeftToRight);
if (!m_subDir.isEmpty()) {
// draw arrow
const int arrowSize = arrowWidth();
const int arrowX = leftToRight ? (buttonWidth - arrowSize) - BorderWidth : BorderWidth;
const int arrowY = (buttonHeight - arrowSize) / 2;
QStyleOption option;
option.initFrom(this);
option.rect = QRect(arrowX, arrowY, arrowSize, arrowSize);
option.palette = palette();
option.palette.setColor(QPalette::Text, fgColor);
option.palette.setColor(QPalette::WindowText, fgColor);
option.palette.setColor(QPalette::ButtonText, fgColor);
if (m_hoverArrow) {
// highlight the background of the arrow to indicate that the directories
// popup can be opened by a mouse click
QColor hoverColor = palette().color(QPalette::HighlightedText);
hoverColor.setAlpha(96);
painter.setPen(Qt::NoPen);
painter.setBrush(hoverColor);
int hoverX = arrowX;
if (!leftToRight) {
hoverX -= BorderWidth;
}
painter.drawRect(QRect(hoverX, 0, arrowSize + BorderWidth, buttonHeight));
}
if (leftToRight) {
style()->drawPrimitive(QStyle::PE_IndicatorArrowRight, &option, &painter, this);
} else {
style()->drawPrimitive(QStyle::PE_IndicatorArrowLeft, &option, &painter, this);
textLeft += arrowSize + 2 * BorderWidth;
}
textWidth -= arrowSize + 2 * BorderWidth;
}
painter.setPen(fgColor);
const bool clipped = isTextClipped();
const QRect textRect(textLeft, 0, textWidth, buttonHeight);
if (clipped) {
QColor bgColor = fgColor;
bgColor.setAlpha(0);
QLinearGradient gradient(textRect.topLeft(), textRect.topRight());
if (leftToRight) {
gradient.setColorAt(0.8, fgColor);
gradient.setColorAt(1.0, bgColor);
} else {
gradient.setColorAt(0.0, bgColor);
gradient.setColorAt(0.2, fgColor);
}
QPen pen;
pen.setBrush(QBrush(gradient));
painter.setPen(pen);
}
int textFlags = clipped ? Qt::AlignVCenter : Qt::AlignCenter;
if (m_showMnemonic) {
textFlags |= Qt::TextShowMnemonic;
painter.drawText(textRect, textFlags, text());
} else {
painter.drawText(textRect, textFlags, plainText());
}
}
void KUrlNavigatorButton::enterEvent(QEnterEvent *event)
{
KUrlNavigatorButtonBase::enterEvent(event);
// if the text is clipped due to a small window width, the text should
// be shown as tooltip
if (isTextClipped()) {
setToolTip(plainText());
}
}
void KUrlNavigatorButton::leaveEvent(QEvent *event)
{
KUrlNavigatorButtonBase::leaveEvent(event);
setToolTip(QString());
if (m_hoverArrow) {
m_hoverArrow = false;
update();
}
}
void KUrlNavigatorButton::keyPressEvent(QKeyEvent *event)
{
switch (event->key()) {
case Qt::Key_Enter:
case Qt::Key_Return:
Q_EMIT navigatorButtonActivated(m_url, Qt::LeftButton, event->modifiers());
break;
case Qt::Key_Down:
case Qt::Key_Space:
startSubDirsJob();
break;
default:
KUrlNavigatorButtonBase::keyPressEvent(event);
}
}
void KUrlNavigatorButton::dropEvent(QDropEvent *event)
{
if (event->mimeData()->hasUrls()) {
setDisplayHintEnabled(DraggedHint, true);
Q_EMIT urlsDroppedOnNavButton(m_url, event);
setDisplayHintEnabled(DraggedHint, false);
update();
}
}
void KUrlNavigatorButton::dragEnterEvent(QDragEnterEvent *event)
{
if (event->mimeData()->hasUrls()) {
setDisplayHintEnabled(DraggedHint, true);
event->acceptProposedAction();
update();
}
}
void KUrlNavigatorButton::dragMoveEvent(QDragMoveEvent *event)
{
QRect rect = event->answerRect();
if (isAboveArrow(rect.center().x())) {
m_hoverArrow = true;
update();
if (m_subDirsMenu == nullptr) {
requestSubDirs();
} else if (m_subDirsMenu->parent() != this) {
m_subDirsMenu->close();
m_subDirsMenu->deleteLater();
m_subDirsMenu = nullptr;
requestSubDirs();
}
} else {
if (m_openSubDirsTimer->isActive()) {
cancelSubDirsRequest();
}
if (m_subDirsMenu) {
m_subDirsMenu->deleteLater();
m_subDirsMenu = nullptr;
}
m_hoverArrow = false;
update();
}
}
void KUrlNavigatorButton::dragLeaveEvent(QDragLeaveEvent *event)
{
KUrlNavigatorButtonBase::dragLeaveEvent(event);
m_hoverArrow = false;
setDisplayHintEnabled(DraggedHint, false);
update();
}
void KUrlNavigatorButton::mousePressEvent(QMouseEvent *event)
{
if (isAboveArrow(qRound(event->position().x())) && (event->button() == Qt::LeftButton)) {
// the mouse is pressed above the [>] button
startSubDirsJob();
}
KUrlNavigatorButtonBase::mousePressEvent(event);
}
void KUrlNavigatorButton::mouseReleaseEvent(QMouseEvent *event)
{
if (!isAboveArrow(qRound(event->position().x())) || (event->button() != Qt::LeftButton)) {
// the mouse has been released above the text area and not
// above the [>] button
Q_EMIT navigatorButtonActivated(m_url, event->button(), event->modifiers());
cancelSubDirsRequest();
}
KUrlNavigatorButtonBase::mouseReleaseEvent(event);
}
void KUrlNavigatorButton::mouseMoveEvent(QMouseEvent *event)
{
KUrlNavigatorButtonBase::mouseMoveEvent(event);
const bool hoverArrow = isAboveArrow(qRound(event->position().x()));
if (hoverArrow != m_hoverArrow) {
m_hoverArrow = hoverArrow;
update();
}
}
void KUrlNavigatorButton::wheelEvent(QWheelEvent *event)
{
if (event->angleDelta().y() != 0) {
m_wheelSteps = event->angleDelta().y() / 120;
m_replaceButton = true;
startSubDirsJob();
}
KUrlNavigatorButtonBase::wheelEvent(event);
}
void KUrlNavigatorButton::requestSubDirs()
{
if (!m_openSubDirsTimer->isActive() && (m_subDirsJob == nullptr)) {
m_openSubDirsTimer->start();
}
}
void KUrlNavigatorButton::startSubDirsJob()
{
if (m_subDirsJob != nullptr) {
return;
}
const QUrl url = m_replaceButton ? KIO::upUrl(m_url) : m_url;
const KUrlNavigator *urlNavigator = qobject_cast<KUrlNavigator *>(parent());
Q_ASSERT(urlNavigator);
m_subDirsJob =
KIO::listDir(url, KIO::HideProgressInfo, urlNavigator->showHiddenFolders() ? KIO::ListJob::ListFlag::IncludeHidden : KIO::ListJob::ListFlags{});
m_subDirs.clear(); // just to be ++safe
connect(m_subDirsJob, &KIO::ListJob::entries, this, &KUrlNavigatorButton::addEntriesToSubDirs);
if (m_replaceButton) {
connect(m_subDirsJob, &KJob::result, this, &KUrlNavigatorButton::replaceButton);
} else {
connect(m_subDirsJob, &KJob::result, this, &KUrlNavigatorButton::openSubDirsMenu);
}
}
void KUrlNavigatorButton::addEntriesToSubDirs(KIO::Job *job, const KIO::UDSEntryList &entries)
{
Q_ASSERT(job == m_subDirsJob);
Q_UNUSED(job);
for (const KIO::UDSEntry &entry : entries) {
if (entry.isDir()) {
const QString name = entry.stringValue(KIO::UDSEntry::UDS_NAME);
QString displayName = entry.stringValue(KIO::UDSEntry::UDS_DISPLAY_NAME);
if (displayName.isEmpty()) {
displayName = name;
}
if (name != QLatin1String(".") && name != QLatin1String("..")) {
m_subDirs.push_back({name, displayName});
}
}
}
}
void KUrlNavigatorButton::slotUrlsDropped(QAction *action, QDropEvent *event)
{
const int result = action->data().toInt();
QUrl url(m_url);
url.setPath(Utils::concatPaths(url.path(), m_subDirs.at(result).name));
Q_EMIT urlsDroppedOnNavButton(url, event);
}
void KUrlNavigatorButton::slotMenuActionClicked(QAction *action, Qt::MouseButton button)
{
const int result = action->data().toInt();
QUrl url(m_url);
url.setPath(Utils::concatPaths(url.path(), m_subDirs.at(result).name));
Q_EMIT navigatorButtonActivated(url, button, Qt::NoModifier);
}
void KUrlNavigatorButton::statFinished(KJob *job)
{
if (m_pendingTextChange) {
m_pendingTextChange = false;
const KIO::UDSEntry entry = static_cast<KIO::StatJob *>(job)->statResult();
QString name = entry.stringValue(KIO::UDSEntry::UDS_DISPLAY_NAME);
if (name.isEmpty()) {
name = m_url.fileName();
}
setText(name);
Q_EMIT finishedTextResolving();
}
}
/**
* Helper struct for sorting folder names
*/
struct FolderNameNaturalLessThan {
FolderNameNaturalLessThan(bool sortHiddenLast)
: m_sortHiddenLast(sortHiddenLast)
{
m_collator.setCaseSensitivity(Qt::CaseInsensitive);
m_collator.setNumericMode(true);
}
bool operator()(const KUrlNavigatorButton::SubDirInfo &a, const KUrlNavigatorButton::SubDirInfo &b)
{
if (m_sortHiddenLast) {
const bool isHiddenA = a.name.startsWith(QLatin1Char('.'));
const bool isHiddenB = b.name.startsWith(QLatin1Char('.'));
if (isHiddenA && !isHiddenB) {
return false;
}
if (!isHiddenA && isHiddenB) {
return true;
}
}
return m_collator.compare(a.name, b.name) < 0;
}
private:
QCollator m_collator;
bool m_sortHiddenLast;
};
void KUrlNavigatorButton::openSubDirsMenu(KJob *job)
{
Q_ASSERT(job == m_subDirsJob);
m_subDirsJob = nullptr;
if (job->error() || m_subDirs.empty()) {
// clear listing
return;
}
const KUrlNavigator *urlNavigator = qobject_cast<KUrlNavigator *>(parent());
Q_ASSERT(urlNavigator);
FolderNameNaturalLessThan less(urlNavigator->showHiddenFolders() && urlNavigator->sortHiddenFoldersLast());
std::sort(m_subDirs.begin(), m_subDirs.end(), less);
setDisplayHintEnabled(PopupActiveHint, true);
update(); // ensure the button is drawn highlighted
if (m_subDirsMenu != nullptr) {
m_subDirsMenu->close();
m_subDirsMenu->deleteLater();
m_subDirsMenu = nullptr;
}
m_subDirsMenu = new KUrlNavigatorMenu(this);
initMenu(m_subDirsMenu, 0);
const bool leftToRight = (layoutDirection() == Qt::LeftToRight);
const int popupX = leftToRight ? width() - arrowWidth() - BorderWidth : 0;
const QPoint popupPos = parentWidget()->mapToGlobal(geometry().bottomLeft() + QPoint(popupX, 0));
QPointer<QObject> guard(this);
m_subDirsMenu->exec(popupPos);
// If 'this' has been deleted in the menu's nested event loop, we have to return
// immediately because any access to a member variable might cause a crash.
if (!guard) {
return;
}
m_subDirs.clear();
delete m_subDirsMenu;
m_subDirsMenu = nullptr;
setDisplayHintEnabled(PopupActiveHint, false);
}
void KUrlNavigatorButton::replaceButton(KJob *job)
{
Q_ASSERT(job == m_subDirsJob);
m_subDirsJob = nullptr;
m_replaceButton = false;
if (job->error() || m_subDirs.empty()) {
return;
}
const KUrlNavigator *urlNavigator = qobject_cast<KUrlNavigator *>(parent());
Q_ASSERT(urlNavigator);
FolderNameNaturalLessThan less(urlNavigator->showHiddenFolders() && urlNavigator->sortHiddenFoldersLast());
std::sort(m_subDirs.begin(), m_subDirs.end(), less);
// Get index of the directory that is shown currently in the button
const QString currentDir = m_url.fileName();
int currentIndex = 0;
const int subDirsCount = m_subDirs.size();
while (currentIndex < subDirsCount) {
if (m_subDirs[currentIndex].name == currentDir) {
break;
}
++currentIndex;
}
// Adjust the index by respecting the wheel steps and
// trigger a replacing of the button content
int targetIndex = currentIndex - m_wheelSteps;
if (targetIndex < 0) {
targetIndex = 0;
} else if (targetIndex >= subDirsCount) {
targetIndex = subDirsCount - 1;
}
QUrl url(KIO::upUrl(m_url));
url.setPath(Utils::concatPaths(url.path(), m_subDirs[targetIndex].name));
Q_EMIT navigatorButtonActivated(url, Qt::LeftButton, Qt::NoModifier);
m_subDirs.clear();
}
void KUrlNavigatorButton::cancelSubDirsRequest()
{
m_openSubDirsTimer->stop();
if (m_subDirsJob != nullptr) {
m_subDirsJob->kill();
m_subDirsJob = nullptr;
}
}
QString KUrlNavigatorButton::plainText() const
{
// Replace all "&&" by '&' and remove all single
// '&' characters
const QString source = text();
const int sourceLength = source.length();
QString dest;
dest.resize(sourceLength);
int sourceIndex = 0;
int destIndex = 0;
while (sourceIndex < sourceLength) {
if (source.at(sourceIndex) == QLatin1Char('&')) {
++sourceIndex;
if (sourceIndex >= sourceLength) {
break;
}
}
dest[destIndex] = source.at(sourceIndex);
++sourceIndex;
++destIndex;
}
dest.resize(destIndex);
return dest;
}
int KUrlNavigatorButton::arrowWidth() const
{
// if there isn't arrow then return 0
int width = 0;
if (!m_subDir.isEmpty()) {
width = height() / 2;
if (width < 4) {
width = 4;
}
}
return width;
}
bool KUrlNavigatorButton::isAboveArrow(int x) const
{
const bool leftToRight = (layoutDirection() == Qt::LeftToRight);
return leftToRight ? (x >= width() - arrowWidth()) : (x < arrowWidth());
}
bool KUrlNavigatorButton::isTextClipped() const
{
int availableWidth = width() - 2 * BorderWidth;
if (!m_subDir.isEmpty()) {
availableWidth -= arrowWidth() - BorderWidth;
}
QFont adjustedFont(font());
adjustedFont.setBold(m_subDir.isEmpty());
return QFontMetrics(adjustedFont).size(Qt::TextSingleLine, plainText()).width() >= availableWidth;
}
void KUrlNavigatorButton::updateMinimumWidth()
{
const int oldMinWidth = minimumWidth();
int minWidth = sizeHint().width();
if (minWidth < 40) {
minWidth = 40;
} else if (minWidth > 150) {
// don't let an overlong path name waste all the URL navigator space
minWidth = 150;
}
if (oldMinWidth != minWidth) {
setMinimumWidth(minWidth);
}
}
void KUrlNavigatorButton::initMenu(KUrlNavigatorMenu *menu, int startIndex)
{
connect(menu, &KUrlNavigatorMenu::mouseButtonClicked, this, &KUrlNavigatorButton::slotMenuActionClicked);
connect(menu, &KUrlNavigatorMenu::urlsDropped, this, &KUrlNavigatorButton::slotUrlsDropped);
// So that triggering a menu item with the keyboard works
connect(menu, &QMenu::triggered, this, [this](QAction *act) {
slotMenuActionClicked(act, Qt::LeftButton);
});
menu->setLayoutDirection(Qt::LeftToRight);
const int maxIndex = startIndex + 30; // Don't show more than 30 items in a menu
const int subDirsSize = m_subDirs.size();
const int lastIndex = std::min(subDirsSize - 1, maxIndex);
for (int i = startIndex; i <= lastIndex; ++i) {
const auto &[subDirName, subDirDisplayName] = m_subDirs[i];
QString text = KStringHandler::csqueeze(subDirDisplayName, 60);
text.replace(QLatin1Char('&'), QLatin1String("&&"));
QAction *action = new QAction(text, this);
if (m_subDir == subDirName) {
QFont font(action->font());
font.setBold(true);
action->setFont(font);
}
action->setData(i);
menu->addAction(action);
}
if (subDirsSize > maxIndex) {
// If too much items are shown, move them into a sub menu
menu->addSeparator();
KUrlNavigatorMenu *subDirsMenu = new KUrlNavigatorMenu(menu);
subDirsMenu->setTitle(i18nc("@action:inmenu", "More"));
initMenu(subDirsMenu, maxIndex);
menu->addMenu(subDirsMenu);
}
}
} // namespace KDEPrivate
#include "moc_kurlnavigatorbutton_p.cpp"
@@ -0,0 +1,194 @@
/*
SPDX-FileCopyrightText: 2006 Peter Penz <peter.penz@gmx.at>
SPDX-FileCopyrightText: 2006 Aaron J. Seigo <aseigo@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KURLNAVIGATORBUTTON_P_H
#define KURLNAVIGATORBUTTON_P_H
#include "kurlnavigatorbuttonbase_p.h"
#include "kurlnavigatormenu_p.h"
#include <kio/global.h>
#include <kio/udsentry.h>
#include <QPointer>
#include <QUrl>
class KJob;
class QDropEvent;
class QPaintEvent;
namespace KIO
{
class ListJob;
class Job;
}
namespace KDEPrivate
{
/**
* @brief Button of the URL navigator which contains one part of an URL.
*
* It is possible to drop a various number of items to an UrlNavigatorButton. In this case
* a context menu is opened where the user must select whether he wants
* to copy, move or link the dropped items to the URL part indicated by
* the button.
*/
class KUrlNavigatorButton : public KUrlNavigatorButtonBase
{
Q_OBJECT
Q_PROPERTY(QString plainText READ plainText) // for the unittest
public:
explicit KUrlNavigatorButton(const QUrl &url, KUrlNavigator *parent);
~KUrlNavigatorButton() override;
void setUrl(const QUrl &url);
QUrl url() const;
/* Implementation note: QAbstractButton::setText() is not virtual,
* but KUrlNavigatorButton needs to adjust the minimum size when
* the text has been changed. KUrlNavigatorButton::setText() hides
* QAbstractButton::setText() which is not nice, but sufficient for
* the usage in KUrlNavigator.
*/
void setText(const QString &text);
/**
* Sets the name of the sub directory that should be marked when
* opening the sub directories popup.
*/
void setActiveSubDirectory(const QString &subDir);
QString activeSubDirectory() const;
/** @see QWidget::sizeHint() */
QSize sizeHint() const override;
void setShowMnemonic(bool show);
bool showMnemonic() const;
struct SubDirInfo {
QString name;
QString displayName;
};
Q_SIGNALS:
/**
* Emitted when URLs are dropped on the KUrlNavigatorButton associated with
* the URL @p destination.
*/
void urlsDroppedOnNavButton(const QUrl &destination, QDropEvent *event);
void navigatorButtonActivated(const QUrl &url, Qt::MouseButton button, Qt::KeyboardModifiers modifiers);
/**
* Is emitted, if KUrlNavigatorButton::setUrl() cannot resolve
* the text synchronously and KUrlNavigator::text() will return
* an empty string in this case. The signal finishedTextResolving() is
* emitted, as soon as the text has been resolved.
*/
void startedTextResolving();
/**
* Is emitted, if the asynchronous resolving of the text has
* been finished (see startTextResolving()).
* KUrlNavigatorButton::text() contains the resolved text.
*/
void finishedTextResolving();
protected:
void enterEvent(QEnterEvent *event) override;
void paintEvent(QPaintEvent *event) override;
void leaveEvent(QEvent *event) override;
void keyPressEvent(QKeyEvent *event) override;
void dropEvent(QDropEvent *event) override;
void dragEnterEvent(QDragEnterEvent *event) override;
void dragMoveEvent(QDragMoveEvent *event) override;
void dragLeaveEvent(QDragLeaveEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void wheelEvent(QWheelEvent *event) override;
private Q_SLOTS:
/**
* Requests to load the sub-directories after a short delay.
* startSubDirsJob() is invoked if the delay is exceeded.
*/
void requestSubDirs();
/**
* Starts to load the sub directories asynchronously. The directories
* are stored in m_subDirs by addEntriesToSubDirs().
*/
void startSubDirsJob();
/**
* Adds the entries from the sub-directories job to m_subDirs. The entries
* will be shown if the job has been finished in openSubDirsMenu() or
* replaceButton().
*/
void addEntriesToSubDirs(KIO::Job *job, const KIO::UDSEntryList &entries);
/**
* Is called after the sub-directories job has been finished and opens a menu
* showing all sub directories.
*/
void openSubDirsMenu(KJob *job);
/**
* Is called after the sub-directories job has been finished and replaces
* the button content by the current sub directory (triggered by
* the scroll wheel).
*/
void replaceButton(KJob *job);
void slotUrlsDropped(QAction *action, QDropEvent *event);
/**
* Is called, if an action of a sub-menu has been triggered by
* a click.
*/
void slotMenuActionClicked(QAction *action, Qt::MouseButton button);
void statFinished(KJob *);
private:
/**
* Cancels any request done by requestSubDirs().
*/
void cancelSubDirsRequest();
/**
* @return Text without mnemonic characters.
*/
QString plainText() const;
int arrowWidth() const;
bool isAboveArrow(int x) const;
bool isTextClipped() const;
void updateMinimumWidth();
void initMenu(KUrlNavigatorMenu *menu, int startIndex);
private:
bool m_hoverArrow;
bool m_pendingTextChange;
bool m_replaceButton;
bool m_showMnemonic;
int m_wheelSteps;
QUrl m_url;
QString m_subDir;
QTimer *m_openSubDirsTimer;
static QPointer<KUrlNavigatorMenu> m_subDirsMenu;
KIO::ListJob *m_subDirsJob;
std::vector<SubDirInfo> m_subDirs;
};
} // namespace KDEPrivate
#endif
@@ -0,0 +1,130 @@
/*
SPDX-FileCopyrightText: 2006-2010 Peter Penz <peter.penz@gmx.at>
SPDX-FileCopyrightText: 2006 Aaron J. Seigo <aseigo@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kurlnavigatorbuttonbase_p.h"
#include <KLocalizedString>
#include <KUrlNavigator>
#include <QStyle>
#include <QStyleOptionViewItem>
namespace KDEPrivate
{
KUrlNavigatorButtonBase::KUrlNavigatorButtonBase(KUrlNavigator *parent)
: QPushButton(parent)
, m_active(true)
, m_displayHint(0)
{
setFocusPolicy(Qt::TabFocus);
setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed);
setMinimumHeight(parent->minimumHeight());
setAttribute(Qt::WA_LayoutUsesWidgetRect);
connect(this, &KUrlNavigatorButtonBase::pressed, parent, &KUrlNavigator::requestActivation);
}
KUrlNavigatorButtonBase::~KUrlNavigatorButtonBase()
{
}
void KUrlNavigatorButtonBase::setActive(bool active)
{
if (m_active != active) {
m_active = active;
update();
}
}
bool KUrlNavigatorButtonBase::isActive() const
{
return m_active;
}
void KUrlNavigatorButtonBase::setDisplayHintEnabled(DisplayHint hint, bool enable)
{
if (enable) {
m_displayHint = m_displayHint | hint;
} else {
m_displayHint = m_displayHint & ~hint;
}
update();
}
bool KUrlNavigatorButtonBase::isDisplayHintEnabled(DisplayHint hint) const
{
return (m_displayHint & hint) > 0;
}
void KUrlNavigatorButtonBase::focusInEvent(QFocusEvent *event)
{
setDisplayHintEnabled(EnteredHint, true);
QPushButton::focusInEvent(event);
}
void KUrlNavigatorButtonBase::focusOutEvent(QFocusEvent *event)
{
setDisplayHintEnabled(EnteredHint, false);
QPushButton::focusOutEvent(event);
}
void KUrlNavigatorButtonBase::enterEvent(QEnterEvent *event)
{
QPushButton::enterEvent(event);
setDisplayHintEnabled(EnteredHint, true);
update();
}
void KUrlNavigatorButtonBase::leaveEvent(QEvent *event)
{
QPushButton::leaveEvent(event);
setDisplayHintEnabled(EnteredHint, false);
update();
}
void KUrlNavigatorButtonBase::drawHoverBackground(QPainter *painter)
{
const bool isHighlighted = isDisplayHintEnabled(EnteredHint) || isDisplayHintEnabled(DraggedHint) || isDisplayHintEnabled(PopupActiveHint);
QColor backgroundColor = isHighlighted ? palette().color(QPalette::Highlight) : Qt::transparent;
if (!m_active && isHighlighted) {
backgroundColor.setAlpha(128);
}
if (backgroundColor != Qt::transparent) {
// TODO: the backgroundColor should be applied to the style
QStyleOptionViewItem option;
option.initFrom(this);
option.state = QStyle::State_Enabled | QStyle::State_MouseOver;
option.viewItemPosition = QStyleOptionViewItem::OnlyOne;
style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, this);
}
}
QColor KUrlNavigatorButtonBase::foregroundColor() const
{
const bool isHighlighted = isDisplayHintEnabled(EnteredHint) || isDisplayHintEnabled(DraggedHint) || isDisplayHintEnabled(PopupActiveHint);
QColor foregroundColor = palette().color(foregroundRole());
int alpha = m_active ? 255 : 128;
if (!m_active && !isHighlighted) {
alpha -= alpha / 4;
}
foregroundColor.setAlpha(alpha);
return foregroundColor;
}
void KUrlNavigatorButtonBase::activate()
{
setActive(true);
}
} // namespace KDEPrivate
#include "moc_kurlnavigatorbuttonbase_p.cpp"
@@ -0,0 +1,81 @@
/*
SPDX-FileCopyrightText: 2006-2010 Peter Penz <peter.penz@gmx.at>
SPDX-FileCopyrightText: 2006 Aaron J. Seigo <aseigo@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KURLNAVIGATORBUTTONBASE_P_H
#define KURLNAVIGATORBUTTONBASE_P_H
#include <QColor>
#include <QPushButton>
class QUrl;
class QEvent;
class KUrlNavigator;
namespace KDEPrivate
{
/**
* @brief Base class for buttons of the URL navigator.
*
* Buttons of the URL navigator offer an active/inactive
* state and custom display hints.
*/
class KUrlNavigatorButtonBase : public QPushButton
{
Q_OBJECT
public:
explicit KUrlNavigatorButtonBase(KUrlNavigator *parent);
~KUrlNavigatorButtonBase() override;
/**
* When having several URL navigator instances, it is important
* to provide a visual difference to indicate which URL navigator
* is active (usecase: split view in Dolphin). The activation state
* is independent from the focus or hover state.
* Per default the URL navigator button is marked as active.
*/
void setActive(bool active);
bool isActive() const;
protected:
enum DisplayHint {
EnteredHint = 1,
DraggedHint = 2,
PopupActiveHint = 4,
};
enum {
BorderWidth = 2
};
void setDisplayHintEnabled(DisplayHint hint, bool enable);
bool isDisplayHintEnabled(DisplayHint hint) const;
void focusInEvent(QFocusEvent *event) override;
void focusOutEvent(QFocusEvent *event) override;
void enterEvent(QEnterEvent *event) override;
void leaveEvent(QEvent *event) override;
void drawHoverBackground(QPainter *painter);
/** Returns the foreground color by respecting the current display hint. */
QColor foregroundColor() const;
private Q_SLOTS:
/** Invokes setActive(true). */
void activate();
private:
bool m_active;
int m_displayHint;
};
} // namespace KDEPrivate
#endif
@@ -0,0 +1,74 @@
/*
SPDX-FileCopyrightText: 2006 Peter Penz <peter.penz@gmx.at>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kurlnavigator.h"
#include "kurlnavigatordropdownbutton_p.h"
#include <KLocalizedString>
#include <QKeyEvent>
#include <QPainter>
#include <QStyleOption>
namespace KDEPrivate
{
KUrlNavigatorDropDownButton::KUrlNavigatorDropDownButton(KUrlNavigator *parent)
: KUrlNavigatorButtonBase(parent)
{
setText(i18nc("@action:button opening a list of locations", "Go to Location on Path"));
}
KUrlNavigatorDropDownButton::~KUrlNavigatorDropDownButton()
{
}
QSize KUrlNavigatorDropDownButton::sizeHint() const
{
QSize size = KUrlNavigatorButtonBase::sizeHint();
size.setWidth(size.height() / 2);
return size;
}
void KUrlNavigatorDropDownButton::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event);
QPainter painter(this);
drawHoverBackground(&painter);
const QColor fgColor = foregroundColor();
QStyleOption option;
option.initFrom(this);
option.rect = QRect(0, 0, width(), height());
option.palette = palette();
option.palette.setColor(QPalette::Text, fgColor);
option.palette.setColor(QPalette::WindowText, fgColor);
option.palette.setColor(QPalette::ButtonText, fgColor);
if (layoutDirection() == Qt::LeftToRight) {
style()->drawPrimitive(QStyle::PE_IndicatorArrowRight, &option, &painter, this);
} else {
style()->drawPrimitive(QStyle::PE_IndicatorArrowLeft, &option, &painter, this);
}
}
void KUrlNavigatorDropDownButton::keyPressEvent(QKeyEvent *event)
{
switch (event->key()) {
case Qt::Key_Enter:
case Qt::Key_Return:
case Qt::Key_Down:
Q_EMIT clicked();
break;
default:
KUrlNavigatorButtonBase::keyPressEvent(event);
}
}
} // namespace KDEPrivate
#include "moc_kurlnavigatordropdownbutton_p.cpp"
@@ -0,0 +1,42 @@
/*
SPDX-FileCopyrightText: 2006 Peter Penz <peter.penz@gmx.at>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KURLNAVIGATORDROPDOWNBUTTON_P_H
#define KURLNAVIGATORDROPDOWNBUTTON_P_H
#include "kurlnavigatorbuttonbase_p.h"
class KUrlNavigator;
namespace KDEPrivate
{
/**
* @brief Button of the URL navigator which offers a drop down menu
* of the hidden portion of the path.
*
* The button will only be shown if the width of the URL navigator is
* too small to show the whole path or if some part of the path is
* expected to be a known location like "home".
*/
class KUrlNavigatorDropDownButton : public KUrlNavigatorButtonBase
{
Q_OBJECT
public:
explicit KUrlNavigatorDropDownButton(KUrlNavigator *parent);
~KUrlNavigatorDropDownButton() override;
/** @see QWidget::sizeHint() */
QSize sizeHint() const override;
protected:
void keyPressEvent(QKeyEvent *event) override;
void paintEvent(QPaintEvent *event) override;
};
} // namespace KDEPrivate
#endif
@@ -0,0 +1,87 @@
/*
SPDX-FileCopyrightText: 2009 Rahman Duran <rahman.duran@gmail.com>
SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "kurlnavigatormenu_p.h"
#include <QApplication>
#include <QKeyEvent>
#include <QMimeData>
namespace KDEPrivate
{
KUrlNavigatorMenu::KUrlNavigatorMenu(QWidget *parent)
: QMenu(parent)
, m_initialMousePosition(QCursor::pos())
, m_mouseMoved(false)
{
setAcceptDrops(true);
setMouseTracking(true);
}
KUrlNavigatorMenu::~KUrlNavigatorMenu()
{
}
void KUrlNavigatorMenu::dragEnterEvent(QDragEnterEvent *event)
{
if (event->mimeData()->hasUrls()) {
event->acceptProposedAction();
}
}
void KUrlNavigatorMenu::dragMoveEvent(QDragMoveEvent *event)
{
const QPointF eventPosition = event->position();
const QPointF globalEventPosition = mapToGlobal(eventPosition);
QMouseEvent mouseEvent(QMouseEvent(QEvent::MouseMove, eventPosition, globalEventPosition, Qt::LeftButton, event->buttons(), event->modifiers()));
mouseMoveEvent(&mouseEvent);
}
void KUrlNavigatorMenu::dropEvent(QDropEvent *event)
{
QAction *action = actionAt(event->position().toPoint());
if (action != nullptr) {
Q_EMIT urlsDropped(action, event);
}
}
void KUrlNavigatorMenu::mouseMoveEvent(QMouseEvent *event)
{
if (!m_mouseMoved) {
QPoint moveDistance = mapToGlobal(event->pos()) - m_initialMousePosition;
m_mouseMoved = (moveDistance.manhattanLength() >= QApplication::startDragDistance());
}
// Don't pass the event to the base class until we consider
// that the mouse has moved. This prevents menu items from
// being highlighted too early.
if (m_mouseMoved) {
QMenu::mouseMoveEvent(event);
}
}
void KUrlNavigatorMenu::mouseReleaseEvent(QMouseEvent *event)
{
Qt::MouseButton btn = event->button();
// Since menu is opened on mouse press, we may receive
// the corresponding mouse release event. Let's ignore
// it unless mouse was moved.
if (m_mouseMoved || (btn != Qt::LeftButton)) {
QAction *action = actionAt(event->pos());
if (action != nullptr) {
Q_EMIT mouseButtonClicked(action, btn);
// Prevent QMenu default activation, in case
// triggered signal is used
setActiveAction(nullptr);
}
QMenu::mouseReleaseEvent(event);
}
m_mouseMoved = true;
}
} // namespace KDEPrivate
#include "moc_kurlnavigatormenu_p.cpp"
@@ -0,0 +1,54 @@
/*
SPDX-FileCopyrightText: 2009 Rahman Duran <rahman.duran@gmail.com>
SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#ifndef KURLNAVIGATORMENU_P_H
#define KURLNAVIGATORMENU_P_H
#include <QMenu>
namespace KDEPrivate
{
/**
* @brief Provides drop-down menus for the URL navigator.
*
* The implementation extends QMenu with drag & drop support.
*
* @internal
*/
class KUrlNavigatorMenu : public QMenu
{
Q_OBJECT
public:
explicit KUrlNavigatorMenu(QWidget *parent);
~KUrlNavigatorMenu() override;
Q_SIGNALS:
/**
* Is emitted when drop event occurs.
*/
void urlsDropped(QAction *action, QDropEvent *event);
/**
* Is emitted, if the action \p action has been clicked.
*/
void mouseButtonClicked(QAction *action, Qt::MouseButton button);
protected:
void dragEnterEvent(QDragEnterEvent *event) override;
void dragMoveEvent(QDragMoveEvent *event) override;
void dropEvent(QDropEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
private:
const QPoint m_initialMousePosition;
bool m_mouseMoved;
};
} // namespace KDEPrivate
#endif
@@ -0,0 +1,46 @@
/*
SPDX-FileCopyrightText: 2018 Kai Uwe Broulik <kde@privat.broulik.de>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "kurlnavigatorpathselectoreventfilter_p.h"
#include <QEvent>
#include <QMenu>
#include <QMouseEvent>
using namespace KDEPrivate;
KUrlNavigatorPathSelectorEventFilter::KUrlNavigatorPathSelectorEventFilter(QObject *parent)
: QObject(parent)
{
}
KUrlNavigatorPathSelectorEventFilter::~KUrlNavigatorPathSelectorEventFilter()
{
}
bool KUrlNavigatorPathSelectorEventFilter::eventFilter(QObject *watched, QEvent *event)
{
if (event->type() == QEvent::MouseButtonRelease) {
QMouseEvent *me = static_cast<QMouseEvent *>(event);
if (me->button() == Qt::MiddleButton) {
if (QMenu *menu = qobject_cast<QMenu *>(watched)) {
if (QAction *action = menu->activeAction()) {
const QUrl url(action->data().toString());
if (url.isValid()) {
menu->close();
Q_EMIT tabRequested(url);
return true;
}
}
}
}
}
return QObject::eventFilter(watched, event);
}
#include "moc_kurlnavigatorpathselectoreventfilter_p.cpp"
@@ -0,0 +1,31 @@
/*
SPDX-FileCopyrightText: 2018 Kai Uwe Broulik <kde@privat.broulik.de>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#ifndef KURLNAVIGATORPATHSELECTOREVENTFILTER_P_H
#define KURLNAVIGATORPATHSELECTOREVENTFILTER_P_H
#include <QObject>
namespace KDEPrivate
{
class KUrlNavigatorPathSelectorEventFilter : public QObject
{
Q_OBJECT
public:
explicit KUrlNavigatorPathSelectorEventFilter(QObject *parent);
~KUrlNavigatorPathSelectorEventFilter() override;
Q_SIGNALS:
void tabRequested(const QUrl &url);
protected:
bool eventFilter(QObject *watched, QEvent *event) override;
};
} // namespace KDEPrivate
#endif
@@ -0,0 +1,280 @@
/*
SPDX-FileCopyrightText: 2006 Peter Penz <peter.penz@gmx.at>
SPDX-FileCopyrightText: 2007 Kevin Ottens <ervin@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kurlnavigatorplacesselector_p.h"
#include <KProtocolInfo>
#include <KUrlMimeData>
#include <kfileplacesmodel.h>
#include <QDragEnterEvent>
#include <QDragLeaveEvent>
#include <QDropEvent>
#include <QMenu>
#include <QMimeData>
#include <QMimeDatabase>
#include <QMouseEvent>
#include <QPainter>
#include <QPixmap>
#include <QStyle>
namespace KDEPrivate
{
KUrlNavigatorPlacesSelector::KUrlNavigatorPlacesSelector(KUrlNavigator *parent, KFilePlacesModel *placesModel)
: KUrlNavigatorButtonBase(parent)
, m_selectedItem(-1)
, m_placesModel(placesModel)
{
connect(m_placesModel, &KFilePlacesModel::reloaded, this, [this] {
updateSelection(m_selectedUrl);
});
m_placesMenu = new QMenu(this);
m_placesMenu->installEventFilter(this);
connect(m_placesMenu, &QMenu::aboutToShow, this, &KUrlNavigatorPlacesSelector::updateMenu);
connect(m_placesMenu, &QMenu::triggered, this, [this](QAction *action) {
activatePlace(action, &KUrlNavigatorPlacesSelector::placeActivated);
});
setMenu(m_placesMenu);
setAcceptDrops(true);
}
KUrlNavigatorPlacesSelector::~KUrlNavigatorPlacesSelector()
{
}
void KUrlNavigatorPlacesSelector::updateMenu()
{
m_placesMenu->clear();
// Submenus have to be deleted explicitly (QTBUG-11070)
for (QObject *obj : QObjectList(m_placesMenu->children())) {
delete qobject_cast<QMenu *>(obj); // Noop for nullptr
}
QString previousGroup;
QMenu *subMenu = nullptr;
const int rowCount = m_placesModel->rowCount();
for (int i = 0; i < rowCount; ++i) {
QModelIndex index = m_placesModel->index(i, 0);
if (m_placesModel->isHidden(index)) {
continue;
}
QAction *placeAction = new QAction(m_placesModel->icon(index), m_placesModel->text(index), m_placesMenu);
placeAction->setData(i);
const QString &groupName = index.data(KFilePlacesModel::GroupRole).toString();
if (previousGroup.isEmpty()) { // Skip first group heading.
previousGroup = groupName;
}
// Put all subsequent categories into a submenu.
if (previousGroup != groupName) {
QAction *subMenuAction = new QAction(groupName, m_placesMenu);
subMenu = new QMenu(m_placesMenu);
subMenu->installEventFilter(this);
subMenuAction->setMenu(subMenu);
m_placesMenu->addAction(subMenuAction);
previousGroup = groupName;
}
if (subMenu) {
subMenu->addAction(placeAction);
} else {
m_placesMenu->addAction(placeAction);
}
if (i == m_selectedItem) {
setIcon(m_placesModel->icon(index));
}
}
const QModelIndex index = m_placesModel->index(m_selectedItem, 0);
if (QAction *teardown = m_placesModel->teardownActionForIndex(index)) {
m_placesMenu->addSeparator();
teardown->setParent(m_placesMenu);
m_placesMenu->addAction(teardown);
}
}
void KUrlNavigatorPlacesSelector::updateSelection(const QUrl &url)
{
const QModelIndex index = m_placesModel->closestItem(url);
if (index.isValid()) {
m_selectedItem = index.row();
m_selectedUrl = url;
setIcon(m_placesModel->icon(index));
} else {
m_selectedItem = -1;
// No bookmark has been found which matches to the given Url.
// Show the protocol's icon as pixmap for indication, if available:
QIcon icon;
if (!url.scheme().isEmpty()) {
if (const QString iconName = KProtocolInfo::icon(url.scheme()); !iconName.isEmpty()) {
icon = QIcon::fromTheme(iconName);
}
}
if (icon.isNull()) {
icon = QIcon::fromTheme(QStringLiteral("folder"));
}
setIcon(icon);
}
}
QUrl KUrlNavigatorPlacesSelector::selectedPlaceUrl() const
{
const QModelIndex index = m_placesModel->index(m_selectedItem, 0);
return index.isValid() ? m_placesModel->url(index) : QUrl();
}
QString KUrlNavigatorPlacesSelector::selectedPlaceText() const
{
const QModelIndex index = m_placesModel->index(m_selectedItem, 0);
return index.isValid() ? m_placesModel->text(index) : QString();
}
QSize KUrlNavigatorPlacesSelector::sizeHint() const
{
const int height = KUrlNavigatorButtonBase::sizeHint().height();
return QSize(height, height);
}
void KUrlNavigatorPlacesSelector::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event);
QPainter painter(this);
drawHoverBackground(&painter);
// draw icon
const QPixmap pixmap = icon().pixmap(QSize(22, 22).expandedTo(iconSize()), QIcon::Normal);
style()->drawItemPixmap(&painter, rect(), Qt::AlignCenter, pixmap);
}
void KUrlNavigatorPlacesSelector::dragEnterEvent(QDragEnterEvent *event)
{
if (event->mimeData()->hasUrls()) {
setDisplayHintEnabled(DraggedHint, true);
event->acceptProposedAction();
update();
}
}
void KUrlNavigatorPlacesSelector::dragLeaveEvent(QDragLeaveEvent *event)
{
KUrlNavigatorButtonBase::dragLeaveEvent(event);
setDisplayHintEnabled(DraggedHint, false);
update();
}
void KUrlNavigatorPlacesSelector::dropEvent(QDropEvent *event)
{
setDisplayHintEnabled(DraggedHint, false);
update();
QMimeDatabase db;
const QList<QUrl> urlList = KUrlMimeData::urlsFromMimeData(event->mimeData());
for (const QUrl &url : urlList) {
QMimeType mimetype = db.mimeTypeForUrl(url);
if (mimetype.inherits(QStringLiteral("inode/directory"))) {
m_placesModel->addPlace(url.fileName(), url);
}
}
}
void KUrlNavigatorPlacesSelector::mouseReleaseEvent(QMouseEvent *event)
{
if (event->button() == Qt::MiddleButton && geometry().contains(event->pos())) {
Q_EMIT tabRequested(KFilePlacesModel::convertedUrl(m_placesModel->url(m_placesModel->index(m_selectedItem, 0))));
event->accept();
return;
}
KUrlNavigatorButtonBase::mouseReleaseEvent(event);
}
void KUrlNavigatorPlacesSelector::activatePlace(QAction *action, ActivationSignal activationSignal)
{
Q_ASSERT(action != nullptr);
if (action->data().toString() == QLatin1String("teardownAction")) {
QModelIndex index = m_placesModel->index(m_selectedItem, 0);
m_placesModel->requestTeardown(index);
return;
}
QModelIndex index = m_placesModel->index(action->data().toInt(), 0);
m_lastClickedIndex = QPersistentModelIndex();
m_lastActivationSignal = nullptr;
if (m_placesModel->setupNeeded(index)) {
connect(m_placesModel, &KFilePlacesModel::setupDone, this, &KUrlNavigatorPlacesSelector::onStorageSetupDone);
m_lastClickedIndex = index;
m_lastActivationSignal = activationSignal;
m_placesModel->requestSetup(index);
return;
} else if (index.isValid()) {
if (activationSignal == &KUrlNavigatorPlacesSelector::placeActivated) {
m_selectedItem = index.row();
setIcon(m_placesModel->icon(index));
}
const QUrl url = KFilePlacesModel::convertedUrl(m_placesModel->url(index));
/*Q_EMIT*/ std::invoke(activationSignal, this, url);
}
}
void KUrlNavigatorPlacesSelector::onStorageSetupDone(const QModelIndex &index, bool success)
{
disconnect(m_placesModel, &KFilePlacesModel::setupDone, this, &KUrlNavigatorPlacesSelector::onStorageSetupDone);
if (m_lastClickedIndex == index) {
if (success) {
if (m_lastActivationSignal == &KUrlNavigatorPlacesSelector::placeActivated) {
m_selectedItem = index.row();
setIcon(m_placesModel->icon(index));
}
const QUrl url = KFilePlacesModel::convertedUrl(m_placesModel->url(index));
/*Q_EMIT*/ std::invoke(m_lastActivationSignal, this, url);
}
m_lastClickedIndex = QPersistentModelIndex();
m_lastActivationSignal = nullptr;
}
}
bool KUrlNavigatorPlacesSelector::eventFilter(QObject *watched, QEvent *event)
{
if (auto *menu = qobject_cast<QMenu *>(watched)) {
if (event->type() == QEvent::MouseButtonRelease) {
QMouseEvent *me = static_cast<QMouseEvent *>(event);
if (me->button() == Qt::MiddleButton) {
if (QAction *action = menu->activeAction()) {
m_placesMenu->close(); // always close top menu
activatePlace(action, &KUrlNavigatorPlacesSelector::tabRequested);
return true;
}
}
}
}
return KUrlNavigatorButtonBase::eventFilter(watched, event);
}
} // namespace KDEPrivate
#include "moc_kurlnavigatorplacesselector_p.cpp"
@@ -0,0 +1,111 @@
/*
SPDX-FileCopyrightText: 2006 Peter Penz <peter.penz@gmx.at>
SPDX-FileCopyrightText: 2007 Kevin Ottens <ervin@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KURLNAVIGATORPLACESSELECTOR_P_H
#define KURLNAVIGATORPLACESSELECTOR_P_H
#include "kurlnavigatorbuttonbase_p.h"
#include <QUrl>
#include <QPersistentModelIndex>
class KFilePlacesModel;
class QMenu;
namespace KDEPrivate
{
/**
* @brief Allows to select a bookmark from a popup menu.
*
* The icon from the current selected bookmark is shown
* inside the bookmark selector.
*
* @see KUrlNavigator
* @internal
*/
class KUrlNavigatorPlacesSelector : public KUrlNavigatorButtonBase
{
Q_OBJECT
public:
/**
* @param parent Parent widget where the bookmark selector
* is embedded into.
*/
KUrlNavigatorPlacesSelector(KUrlNavigator *parent, KFilePlacesModel *placesModel);
~KUrlNavigatorPlacesSelector() override;
using ActivationSignal = void (KUrlNavigatorPlacesSelector::*)(const QUrl &);
/**
* Updates the selection dependent from the given URL \a url. The
* URL must not match exactly to one of the available bookmarks:
* The bookmark which is equal to the URL or at least is a parent URL
* is selected. If there are more than one possible parent URL candidates,
* the bookmark which covers the bigger range of the URL is selected.
*/
void updateSelection(const QUrl &url);
/** Returns the selected bookmark. */
QUrl selectedPlaceUrl() const;
/** Returns the selected bookmark. */
QString selectedPlaceText() const;
/** @see QWidget::sizeHint() */
QSize sizeHint() const override;
Q_SIGNALS:
/**
* Is send when a bookmark has been activated by the user.
* @param url URL of the selected place.
*/
void placeActivated(const QUrl &url);
/**
* Is sent when a bookmark was middle clicked by the user
* and thus should be opened in a new tab.
*/
void tabRequested(const QUrl &url);
protected:
/**
* Draws the icon of the selected Url as content of the Url
* selector.
*/
void paintEvent(QPaintEvent *event) override;
void dragEnterEvent(QDragEnterEvent *event) override;
void dragLeaveEvent(QDragLeaveEvent *event) override;
void dropEvent(QDropEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
bool eventFilter(QObject *watched, QEvent *event) override;
private Q_SLOTS:
/**
* Updates the selected index and the icon to the bookmark
* which is indicated by the triggered action \a action.
*/
void activatePlace(QAction *action, ActivationSignal activationSignal);
void updateMenu();
void onStorageSetupDone(const QModelIndex &index, bool success);
private:
int m_selectedItem;
QPersistentModelIndex m_lastClickedIndex;
ActivationSignal m_lastActivationSignal = nullptr;
QMenu *m_placesMenu;
KFilePlacesModel *m_placesModel;
QUrl m_selectedUrl;
};
} // namespace KDEPrivate
#endif
@@ -0,0 +1,215 @@
/*
SPDX-FileCopyrightText: 2006 Aaron J. Seigo <aseigo@kde.org>
SPDX-FileCopyrightText: 2009 Peter Penz <peter.penz@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kurlnavigatorschemecombo_p.h"
#include <QAction>
#include <QMenu>
#include <QPaintEvent>
#include <QPainter>
#include <QStyleOption>
#include <KLocalizedString>
#include <kprotocolinfo.h>
#include <kprotocolmanager.h>
#include <kurlnavigator.h>
namespace
{
const int ArrowSize = 10;
}
namespace KDEPrivate
{
KUrlNavigatorSchemeCombo::KUrlNavigatorSchemeCombo(const QString &scheme, KUrlNavigator *parent)
: KUrlNavigatorButtonBase(parent)
, m_menu(nullptr)
, m_schemes()
, m_categories()
{
m_menu = new QMenu(this);
connect(m_menu, &QMenu::triggered, this, &KUrlNavigatorSchemeCombo::setSchemeFromMenu);
setText(scheme);
setMenu(m_menu);
}
void KUrlNavigatorSchemeCombo::setSupportedSchemes(const QStringList &schemes)
{
m_schemes = schemes;
m_menu->clear();
for (const QString &scheme : schemes) {
QAction *action = m_menu->addAction(scheme);
action->setData(scheme);
}
}
QSize KUrlNavigatorSchemeCombo::sizeHint() const
{
const QSize size = KUrlNavigatorButtonBase::sizeHint();
int width = fontMetrics().boundingRect(KLocalizedString::removeAcceleratorMarker(text())).width();
width += (3 * BorderWidth) + ArrowSize;
return QSize(width, size.height());
}
void KUrlNavigatorSchemeCombo::setScheme(const QString &scheme)
{
setText(scheme);
}
QString KUrlNavigatorSchemeCombo::currentScheme() const
{
return text();
}
void KUrlNavigatorSchemeCombo::showEvent(QShowEvent *event)
{
KUrlNavigatorButtonBase::showEvent(event);
if (!event->spontaneous() && m_schemes.isEmpty()) {
m_schemes = KProtocolInfo::protocols();
auto it = std::remove_if(m_schemes.begin(), m_schemes.end(), [](const QString &s) {
QUrl url;
url.setScheme(s);
return !KProtocolManager::supportsListing(url);
});
m_schemes.erase(it, m_schemes.end());
std::sort(m_schemes.begin(), m_schemes.end());
updateMenu();
}
}
void KUrlNavigatorSchemeCombo::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event);
QPainter painter(this);
const int buttonWidth = width();
const int buttonHeight = height();
drawHoverBackground(&painter);
const QColor fgColor = foregroundColor();
painter.setPen(fgColor);
// draw arrow
const int arrowX = buttonWidth - ArrowSize - BorderWidth;
const int arrowY = (buttonHeight - ArrowSize) / 2;
QStyleOption option;
option.rect = QRect(arrowX, arrowY, ArrowSize, ArrowSize);
option.palette = palette();
option.palette.setColor(QPalette::Text, fgColor);
option.palette.setColor(QPalette::WindowText, fgColor);
option.palette.setColor(QPalette::ButtonText, fgColor);
style()->drawPrimitive(QStyle::PE_IndicatorArrowDown, &option, &painter, this);
// draw text
const int textWidth = arrowX - (2 * BorderWidth);
int alignment = Qt::AlignCenter | Qt::TextShowMnemonic;
if (!style()->styleHint(QStyle::SH_UnderlineShortcut, &option, this)) {
alignment |= Qt::TextHideMnemonic;
}
style()->drawItemText(&painter, QRect(BorderWidth, 0, textWidth, buttonHeight), alignment, option.palette, isEnabled(), text());
}
void KUrlNavigatorSchemeCombo::setSchemeFromMenu(QAction *action)
{
const QString scheme = action->data().toString();
setText(scheme);
Q_EMIT activated(scheme);
}
void KUrlNavigatorSchemeCombo::updateMenu()
{
initializeCategories();
std::sort(m_schemes.begin(), m_schemes.end());
// move all schemes into the corresponding category of 'items'
QList<QString> items[CategoryCount];
for (const QString &scheme : std::as_const(m_schemes)) {
if (m_categories.contains(scheme)) {
const SchemeCategory category = m_categories.value(scheme);
items[category].append(scheme);
} else {
items[OtherCategory].append(scheme);
}
}
// Create the menu that includes all entries from 'items'. The categories
// CoreCategory and PlacesCategory are placed at the top level, the remaining
// categories are placed in sub menus.
QMenu *menu = m_menu;
for (int category = 0; category < CategoryCount; ++category) {
if (!items[category].isEmpty()) {
switch (category) {
case DevicesCategory:
menu = m_menu->addMenu(i18nc("@item:inmenu", "Devices"));
break;
case SubversionCategory:
menu = m_menu->addMenu(i18nc("@item:inmenu", "Subversion"));
break;
case OtherCategory:
menu = m_menu->addMenu(i18nc("@item:inmenu", "Other"));
break;
case CoreCategory:
case PlacesCategory:
default:
break;
}
for (const QString &scheme : std::as_const(items[category])) {
QAction *action = menu->addAction(scheme);
action->setData(scheme);
}
if (menu == m_menu) {
menu->addSeparator();
}
}
}
}
void KUrlNavigatorSchemeCombo::initializeCategories()
{
if (m_categories.isEmpty()) {
m_categories.insert(QStringLiteral("file"), CoreCategory);
m_categories.insert(QStringLiteral("ftp"), CoreCategory);
m_categories.insert(QStringLiteral("fish"), CoreCategory);
m_categories.insert(QStringLiteral("nfs"), CoreCategory);
m_categories.insert(QStringLiteral("sftp"), CoreCategory);
m_categories.insert(QStringLiteral("smb"), CoreCategory);
m_categories.insert(QStringLiteral("webdav"), CoreCategory);
m_categories.insert(QStringLiteral("desktop"), PlacesCategory);
m_categories.insert(QStringLiteral("fonts"), PlacesCategory);
m_categories.insert(QStringLiteral("programs"), PlacesCategory);
m_categories.insert(QStringLiteral("settings"), PlacesCategory);
m_categories.insert(QStringLiteral("trash"), PlacesCategory);
m_categories.insert(QStringLiteral("floppy"), DevicesCategory);
m_categories.insert(QStringLiteral("camera"), DevicesCategory);
m_categories.insert(QStringLiteral("remote"), DevicesCategory);
m_categories.insert(QStringLiteral("svn"), SubversionCategory);
m_categories.insert(QStringLiteral("svn+file"), SubversionCategory);
m_categories.insert(QStringLiteral("svn+http"), SubversionCategory);
m_categories.insert(QStringLiteral("svn+https"), SubversionCategory);
m_categories.insert(QStringLiteral("svn+ssh"), SubversionCategory);
}
}
} // namespace KDEPrivate
#include "moc_kurlnavigatorschemecombo_p.cpp"
@@ -0,0 +1,73 @@
/*
SPDX-FileCopyrightText: 2006 Aaron J. Seigo <aseigo@kde.org>
SPDX-FileCopyrightText: 2009 Peter Penz <peter.penz@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KURLNAVIGATORSCHEMECOMBO_P_H
#define KURLNAVIGATORSCHEMECOMBO_P_H
#include "kurlnavigatorbuttonbase_p.h"
#include <QHash>
class QMenu;
namespace KDEPrivate
{
/**
* @brief A combobox listing available schemes.
*
* The widget is used by the URL navigator for offering the available
* schemes for non-local URLs.
*
* @see KUrlNavigator
*/
class KUrlNavigatorSchemeCombo : public KUrlNavigatorButtonBase
{
Q_OBJECT
public:
explicit KUrlNavigatorSchemeCombo(const QString &scheme, KUrlNavigator *parent = nullptr);
QString currentScheme() const;
void setSupportedSchemes(const QStringList &schemes);
QSize sizeHint() const override;
public Q_SLOTS:
void setScheme(const QString &scheme);
Q_SIGNALS:
void activated(const QString &scheme);
protected:
void showEvent(QShowEvent *event) override;
void paintEvent(QPaintEvent *event) override;
private Q_SLOTS:
void setSchemeFromMenu(QAction *action);
private:
void updateMenu();
void initializeCategories();
enum SchemeCategory {
CoreCategory,
PlacesCategory,
DevicesCategory,
SubversionCategory,
OtherCategory,
CategoryCount, // mandatory last entry
};
QMenu *m_menu;
QStringList m_schemes;
QHash<QString, SchemeCategory> m_categories;
};
} // namespace KDEPrivate
#endif
@@ -0,0 +1,99 @@
/*
SPDX-FileCopyrightText: 2006 Peter Penz <peter.penz@gmx.at>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kurlnavigatortogglebutton_p.h"
#include <KIconLoader>
#include <KLocalizedString>
#include <QPaintEvent>
#include <QPainter>
#include <QStyle>
namespace KDEPrivate
{
static constexpr int s_iconSize = KIconLoader::SizeSmallMedium;
KUrlNavigatorToggleButton::KUrlNavigatorToggleButton(KUrlNavigator *parent)
: KUrlNavigatorButtonBase(parent)
{
setCheckable(true);
connect(this, &QAbstractButton::toggled, this, &KUrlNavigatorToggleButton::updateToolTip);
connect(this, &QAbstractButton::clicked, this, &KUrlNavigatorToggleButton::updateCursor);
#ifndef QT_NO_ACCESSIBILITY
setAccessibleName(i18n("Edit mode"));
#endif
updateToolTip();
}
KUrlNavigatorToggleButton::~KUrlNavigatorToggleButton()
{
}
QSize KUrlNavigatorToggleButton::sizeHint() const
{
QSize size = KUrlNavigatorButtonBase::sizeHint();
size.setWidth(qMax(s_iconSize, iconSize().width()) + 4);
return size;
}
void KUrlNavigatorToggleButton::enterEvent(QEnterEvent *event)
{
KUrlNavigatorButtonBase::enterEvent(event);
updateCursor();
}
void KUrlNavigatorToggleButton::leaveEvent(QEvent *event)
{
KUrlNavigatorButtonBase::leaveEvent(event);
setCursor(Qt::ArrowCursor);
}
void KUrlNavigatorToggleButton::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
painter.setClipRect(event->rect());
const int buttonWidth = width();
const int buttonHeight = height();
if (isChecked()) {
drawHoverBackground(&painter);
if (m_pixmap.isNull() || m_pixmap.devicePixelRatioF() != devicePixelRatioF()) {
const QSize tickIconSize = QSize(s_iconSize, s_iconSize).expandedTo(iconSize());
m_pixmap = QIcon::fromTheme(QStringLiteral("dialog-ok")).pixmap(tickIconSize, devicePixelRatioF());
}
style()->drawItemPixmap(&painter, rect(), Qt::AlignCenter, m_pixmap);
} else if (isDisplayHintEnabled(EnteredHint)) {
painter.setPen(Qt::NoPen);
painter.setBrush(palette().color(foregroundRole()));
const int verticalGap = 4;
const int caretWidth = 2;
const int x = (layoutDirection() == Qt::LeftToRight) ? 0 : buttonWidth - caretWidth;
painter.drawRect(x, verticalGap, caretWidth, buttonHeight - 2 * verticalGap);
}
}
void KUrlNavigatorToggleButton::updateToolTip()
{
if (isChecked()) {
setToolTip(i18n("Click for Location Navigation"));
} else {
setToolTip(i18n("Click to Edit Location"));
}
}
void KUrlNavigatorToggleButton::updateCursor()
{
setCursor(isChecked() ? Qt::ArrowCursor : Qt::IBeamCursor);
}
} // namespace KDEPrivate
#include "moc_kurlnavigatortogglebutton_p.cpp"
@@ -0,0 +1,47 @@
/*
SPDX-FileCopyrightText: 2006 Peter Penz <peter.penz@gmx.at>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KURLNAVIGATORTOGGLEBUTTON_P_H
#define KURLNAVIGATORTOGGLEBUTTON_P_H
#include "kurlnavigatorbuttonbase_p.h"
#include <QPixmap>
namespace KDEPrivate
{
/**
* @brief Represents the button of the URL navigator to switch to
* the editable mode.
*
* A cursor is shown when hovering the button.
*/
class KUrlNavigatorToggleButton : public KUrlNavigatorButtonBase
{
Q_OBJECT
public:
explicit KUrlNavigatorToggleButton(KUrlNavigator *parent);
~KUrlNavigatorToggleButton() override;
/** @see QWidget::sizeHint() */
QSize sizeHint() const override;
protected:
void enterEvent(QEnterEvent *event) override;
void leaveEvent(QEvent *event) override;
void paintEvent(QPaintEvent *event) override;
private Q_SLOTS:
void updateToolTip();
void updateCursor();
private:
QPixmap m_pixmap;
};
} // namespace KDEPrivate
#endif