diff options
Diffstat (limited to 'src/views')
| -rw-r--r-- | src/views/dolphinview.cpp | 89 | ||||
| -rw-r--r-- | src/views/dolphinview.h | 13 | ||||
| -rw-r--r-- | src/views/dolphinviewactionhandler.cpp | 13 | ||||
| -rw-r--r-- | src/views/versioncontrol/kversioncontrolplugin.cpp | 5 | ||||
| -rw-r--r-- | src/views/versioncontrol/kversioncontrolplugin.h | 7 | ||||
| -rw-r--r-- | src/views/versioncontrol/versioncontrolobserver.cpp | 99 | ||||
| -rw-r--r-- | src/views/versioncontrol/versioncontrolobserver.h | 7 |
7 files changed, 156 insertions, 77 deletions
diff --git a/src/views/dolphinview.cpp b/src/views/dolphinview.cpp index 32e962459..d03b75ddd 100644 --- a/src/views/dolphinview.cpp +++ b/src/views/dolphinview.cpp @@ -48,7 +48,9 @@ #include <QApplication> #include <QClipboard> #include <QDropEvent> +#include <QGraphicsOpacityEffect> #include <QGraphicsSceneDragDropEvent> +#include <QLabel> #include <QMenu> #include <QMimeDatabase> #include <QPixmapCache> @@ -65,6 +67,7 @@ DolphinView::DolphinView(const QUrl& url, QWidget* parent) : m_assureVisibleCurrentIndex(false), m_isFolderWritable(true), m_dragging(false), + m_loading(false), m_url(url), m_viewPropertiesContext(), m_mode(DolphinView::IconsView), @@ -82,7 +85,8 @@ DolphinView::DolphinView(const QUrl& url, QWidget* parent) : m_clearSelectionBeforeSelectingNewItems(false), m_markFirstNewlySelectedItemAsCurrent(false), m_versionControlObserver(nullptr), - m_twoClicksRenamingTimer(nullptr) + m_twoClicksRenamingTimer(nullptr), + m_placeholderLabel(nullptr) { m_topLayout = new QVBoxLayout(this); m_topLayout->setSpacing(0); @@ -120,6 +124,28 @@ DolphinView::DolphinView(const QUrl& url, QWidget* parent) : connect(m_container->horizontalScrollBar(), &QScrollBar::valueChanged, this, [=] { hideToolTip(); }); connect(m_container->verticalScrollBar(), &QScrollBar::valueChanged, this, [=] { hideToolTip(); }); + // Show some placeholder text for empty folders + // This is made using a heavily-modified QLabel rather than a KTitleWidget + // because KTitleWidget can't be told to turn off mouse-selectable text + m_placeholderLabel = new QLabel(this); + QFont placeholderLabelFont; + // To match the size of a level 2 Heading/KTitleWidget + placeholderLabelFont.setPointSize(qRound(placeholderLabelFont.pointSize() * 1.3)); + m_placeholderLabel->setFont(placeholderLabelFont); + m_placeholderLabel->setTextInteractionFlags(Qt::NoTextInteraction); + m_placeholderLabel->setWordWrap(true); + m_placeholderLabel->setAlignment(Qt::AlignCenter); + // Match opacity of QML placeholder label component + auto *effect = new QGraphicsOpacityEffect(m_placeholderLabel); + effect->setOpacity(0.5); + m_placeholderLabel->setGraphicsEffect(effect); + // Set initial text and visibility + updatePlaceholderLabel(); + + auto *centeringLayout = new QVBoxLayout(m_container); + centeringLayout->addWidget(m_placeholderLabel); + centeringLayout->setAlignment(m_placeholderLabel, Qt::AlignCenter); + controller->setSelectionBehavior(KItemListController::MultiSelection); connect(controller, &KItemListController::itemActivated, this, &DolphinView::slotItemActivated); connect(controller, &KItemListController::itemsActivated, this, &DolphinView::slotItemsActivated); @@ -140,7 +166,7 @@ DolphinView::DolphinView(const QUrl& url, QWidget* parent) : connect(m_model, &KFileItemModel::directoryLoadingStarted, this, &DolphinView::slotDirectoryLoadingStarted); connect(m_model, &KFileItemModel::directoryLoadingCompleted, this, &DolphinView::slotDirectoryLoadingCompleted); - connect(m_model, &KFileItemModel::directoryLoadingCanceled, this, &DolphinView::directoryLoadingCanceled); + connect(m_model, &KFileItemModel::directoryLoadingCanceled, this, &DolphinView::slotDirectoryLoadingCanceled); connect(m_model, &KFileItemModel::directoryLoadingProgress, this, &DolphinView::directoryLoadingProgress); connect(m_model, &KFileItemModel::directorySortingProgress, this, &DolphinView::directorySortingProgress); connect(m_model, &KFileItemModel::itemsChanged, @@ -152,6 +178,9 @@ DolphinView::DolphinView(const QUrl& url, QWidget* parent) : connect(m_model, &KFileItemModel::directoryRedirection, this, &DolphinView::slotDirectoryRedirection); connect(m_model, &KFileItemModel::urlIsFileError, this, &DolphinView::urlIsFileError); + connect(this, &DolphinView::itemCountChanged, + this, &DolphinView::updatePlaceholderLabel); + m_view->installEventFilter(this); connect(m_view, &DolphinItemListView::sortOrderChanged, this, &DolphinView::slotSortOrderChangedByHeader); @@ -915,7 +944,7 @@ void DolphinView::slotItemsActivated(const KItemSet& indexes) const QUrl& url = openItemAsFolderUrl(item); if (!url.isEmpty()) { // Open folders in new tabs - Q_EMIT tabRequested(url, DolphinTabWidget::AfterLastTab); + Q_EMIT tabRequested(url); } else { items.append(item); } @@ -933,9 +962,9 @@ void DolphinView::slotItemMiddleClicked(int index) const KFileItem& item = m_model->fileItem(index); const QUrl& url = openItemAsFolderUrl(item); if (!url.isEmpty()) { - Q_EMIT tabRequested(url, DolphinTabWidget::AfterCurrentTab); + Q_EMIT tabRequested(url); } else if (isTabsForFilesEnabled()) { - Q_EMIT tabRequested(item.url(), DolphinTabWidget::AfterCurrentTab); + Q_EMIT tabRequested(item.url()); } } @@ -1594,6 +1623,9 @@ void DolphinView::slotRenamingResult(KJob* job) void DolphinView::slotDirectoryLoadingStarted() { + m_loading = true; + updatePlaceholderLabel(); + // Disable the writestate temporary until it can be determined in a fast way // in DolphinView::slotDirectoryLoadingCompleted() if (m_isFolderWritable) { @@ -1606,15 +1638,30 @@ void DolphinView::slotDirectoryLoadingStarted() void DolphinView::slotDirectoryLoadingCompleted() { + m_loading = false; + // Update the view-state. This has to be done asynchronously // because the view might not be in its final state yet. QTimer::singleShot(0, this, &DolphinView::updateViewState); + // Update the placeholder label in case we found that the folder was empty + // after loading it + Q_EMIT directoryLoadingCompleted(); + updatePlaceholderLabel(); updateWritableState(); } +void DolphinView::slotDirectoryLoadingCanceled() +{ + m_loading = false; + + updatePlaceholderLabel(); + + Q_EMIT directoryLoadingCanceled(); +} + void DolphinView::slotItemsChanged() { m_assureVisibleCurrentIndex = false; @@ -1976,3 +2023,35 @@ void DolphinView::slotSwipeUp() { Q_EMIT goUpRequested(); } + +void DolphinView::updatePlaceholderLabel() +{ + if (m_loading || itemsCount() > 0) { + m_placeholderLabel->setVisible(false); + return; + } + + if (!nameFilter().isEmpty()) { + m_placeholderLabel->setText(i18n("No items matching the filter")); + } else if (m_url.scheme() == QLatin1String("baloosearch") || m_url.scheme() == QLatin1String("filenamesearch")) { + m_placeholderLabel->setText(i18n("No items matching the search")); + } else if (m_url.scheme() == QLatin1String("trash")) { + m_placeholderLabel->setText(i18n("Trash is empty")); + } else if (m_url.scheme() == QLatin1String("tags")) { + m_placeholderLabel->setText(i18n("No tags")); + } else if (m_url.scheme() == QLatin1String("recentlyused")) { + m_placeholderLabel->setText(i18n("No recently used items")); + } else if (m_url.scheme() == QLatin1String("smb")) { + m_placeholderLabel->setText(i18n("No shared folders found")); + } else if (m_url.scheme() == QLatin1String("network")) { + m_placeholderLabel->setText(i18n("No relevant network resources found")); + } else if (m_url.scheme() == QLatin1String("mtp")) { + m_placeholderLabel->setText(i18n("No MTP-compatible devices found")); + } else if (m_url.scheme() == QLatin1String("bluetooth")) { + m_placeholderLabel->setText(i18n("No Bluetooth devices found")); + } else { + m_placeholderLabel->setText(i18n("Folder is empty")); + } + + m_placeholderLabel->setVisible(true); +} diff --git a/src/views/dolphinview.h b/src/views/dolphinview.h index 1d0ebe0fe..d0285da85 100644 --- a/src/views/dolphinview.h +++ b/src/views/dolphinview.h @@ -32,6 +32,7 @@ class KItemSet; class ToolTipManager; class VersionControlObserver; class ViewProperties; +class QLabel; class QGraphicsSceneDragDropEvent; class QRegularExpression; @@ -432,7 +433,7 @@ signals: /** * Is emitted if a new tab should be opened for the URL \a url. */ - void tabRequested(const QUrl& url, DolphinTabWidget::TabPlacement tabPlacement); + void tabRequested(const QUrl& url); /** * Is emitted if the view mode (IconsView, DetailsView, @@ -689,6 +690,12 @@ private slots: void slotDirectoryLoadingCompleted(); /** + * Invoked when the file item model indicates that the loading of a directory has + * been canceled. + */ + void slotDirectoryLoadingCanceled(); + + /** * Is invoked when items of KFileItemModel have been changed. */ void slotItemsChanged(); @@ -804,6 +811,8 @@ private: void abortTwoClicksRenaming(); + void updatePlaceholderLabel(); + private: void updatePalette(); @@ -813,6 +822,7 @@ private: bool m_isFolderWritable; bool m_dragging; // True if a dragging is done. Required to be able to decide whether a // tooltip may be shown when hovering an item. + bool m_loading; QUrl m_url; QString m_viewPropertiesContext; @@ -841,6 +851,7 @@ private: QTimer* m_twoClicksRenamingTimer; QUrl m_twoClicksRenamingItemUrl; + QLabel* m_placeholderLabel; // For unit tests friend class TestBase; diff --git a/src/views/dolphinviewactionhandler.cpp b/src/views/dolphinviewactionhandler.cpp index f3e3f95a3..14c1e96c2 100644 --- a/src/views/dolphinviewactionhandler.cpp +++ b/src/views/dolphinviewactionhandler.cpp @@ -11,6 +11,7 @@ #include "kitemviews/kfileitemmodel.h" #include "settings/viewpropertiesdialog.h" #include "views/zoomlevelinfo.h" +#include "kconfig_version.h" #ifdef HAVE_BALOO #include <Baloo/IndexerConfig> @@ -78,7 +79,7 @@ void DolphinViewActionHandler::createActions() // KNewFileMenu takes care of the GUI stuff. QAction* newDirAction = m_actionCollection->addAction(QStringLiteral("create_dir")); newDirAction->setText(i18nc("@action", "Create Folder...")); - m_actionCollection->setDefaultShortcut(newDirAction, Qt::Key_F10); + m_actionCollection->setDefaultShortcuts(newDirAction, KStandardShortcut::createFolder()); newDirAction->setIcon(QIcon::fromTheme(QStringLiteral("folder-new"))); newDirAction->setEnabled(false); // Will be enabled in slotWriteStateChanged(bool) if the current URL is writable connect(newDirAction, &QAction::triggered, this, &DolphinViewActionHandler::createDirectoryTriggered); @@ -139,7 +140,7 @@ void DolphinViewActionHandler::createActions() "You can configure advanced options there like managing " "read- and write-permissions.")); propertiesAction->setIcon(QIcon::fromTheme(QStringLiteral("document-properties"))); - m_actionCollection->setDefaultShortcuts(propertiesAction, {Qt::ALT + Qt::Key_Return, Qt::ALT + Qt::Key_Enter}); + m_actionCollection->setDefaultShortcuts(propertiesAction, {Qt::ALT | Qt::Key_Return, Qt::ALT | Qt::Key_Enter}); connect(propertiesAction, &QAction::triggered, this, &DolphinViewActionHandler::slotProperties); QAction *copyPathAction = m_actionCollection->addAction( QStringLiteral("copy_location") ); @@ -197,7 +198,7 @@ void DolphinViewActionHandler::createActions() zoomResetAction->setToolTip(i18n("Zoom To Default")); zoomResetAction->setWhatsThis(i18nc("@info:whatsthis zoom reset", "This resets the icon size to default.")); zoomResetAction->setIcon(QIcon::fromTheme(QStringLiteral("zoom-original"))); - m_actionCollection->setDefaultShortcuts(zoomResetAction, {Qt::CTRL + Qt::Key_0}); + m_actionCollection->setDefaultShortcuts(zoomResetAction, {Qt::CTRL | Qt::Key_0}); connect(zoomResetAction, &QAction::triggered, this, &DolphinViewActionHandler::zoomReset); QAction* zoomOutAction = KStandardAction::zoomOut(this, @@ -567,7 +568,7 @@ KToggleAction* DolphinViewActionHandler::iconsModeAction() KToggleAction* iconsView = m_actionCollection->add<KToggleAction>(QStringLiteral("icons")); iconsView->setText(i18nc("@action:inmenu View Mode", "Icons")); iconsView->setToolTip(i18nc("@info", "Icons view mode")); - m_actionCollection->setDefaultShortcut(iconsView, Qt::CTRL + Qt::Key_1); + m_actionCollection->setDefaultShortcut(iconsView, Qt::CTRL | Qt::Key_1); iconsView->setIcon(QIcon::fromTheme(QStringLiteral("view-list-icons"))); iconsView->setData(QVariant::fromValue(DolphinView::IconsView)); return iconsView; @@ -578,7 +579,7 @@ KToggleAction* DolphinViewActionHandler::compactModeAction() KToggleAction* iconsView = m_actionCollection->add<KToggleAction>(QStringLiteral("compact")); iconsView->setText(i18nc("@action:inmenu View Mode", "Compact")); iconsView->setToolTip(i18nc("@info", "Compact view mode")); - m_actionCollection->setDefaultShortcut(iconsView, Qt::CTRL + Qt::Key_2); + m_actionCollection->setDefaultShortcut(iconsView, Qt::CTRL | Qt::Key_2); iconsView->setIcon(QIcon::fromTheme(QStringLiteral("view-list-details"))); // TODO: discuss with Oxygen-team the wrong (?) name iconsView->setData(QVariant::fromValue(DolphinView::CompactView)); return iconsView; @@ -589,7 +590,7 @@ KToggleAction* DolphinViewActionHandler::detailsModeAction() KToggleAction* detailsView = m_actionCollection->add<KToggleAction>(QStringLiteral("details")); detailsView->setText(i18nc("@action:inmenu View Mode", "Details")); detailsView->setToolTip(i18nc("@info", "Details view mode")); - m_actionCollection->setDefaultShortcut(detailsView, Qt::CTRL + Qt::Key_3); + m_actionCollection->setDefaultShortcut(detailsView, Qt::CTRL | Qt::Key_3); detailsView->setIcon(QIcon::fromTheme(QStringLiteral("view-list-tree"))); detailsView->setData(QVariant::fromValue(DolphinView::DetailsView)); return detailsView; diff --git a/src/views/versioncontrol/kversioncontrolplugin.cpp b/src/views/versioncontrol/kversioncontrolplugin.cpp index 2e1a4468b..9cbf0eb5b 100644 --- a/src/views/versioncontrol/kversioncontrolplugin.cpp +++ b/src/views/versioncontrol/kversioncontrolplugin.cpp @@ -15,3 +15,8 @@ KVersionControlPlugin::KVersionControlPlugin(QObject* parent) : KVersionControlPlugin::~KVersionControlPlugin() { } + +QString KVersionControlPlugin::localRepositoryRoot(const QString &/*directory*/) const +{ + return QString(); +} diff --git a/src/views/versioncontrol/kversioncontrolplugin.h b/src/views/versioncontrol/kversioncontrolplugin.h index 0de305d14..aeac5ad29 100644 --- a/src/views/versioncontrol/kversioncontrolplugin.h +++ b/src/views/versioncontrol/kversioncontrolplugin.h @@ -144,6 +144,12 @@ public: virtual QString fileName() const = 0; /** + * Returns the path of the local repository root for the versionned directory + * Returns an emtpy QString when directory is not part of a working copy + */ + virtual QString localRepositoryRoot(const QString& directory) const; + + /** * Is invoked whenever the version control * information will get retrieved for the directory * \p directory. It is assured that the directory @@ -177,6 +183,7 @@ public: * @return List of actions that are available for the out of version control * items \p items. It's opposed to the \p versionedActions. Common usage * is for clone/checkout actions. + * @since 21.04 */ virtual QList<QAction*> outOfVersionControlActions(const KFileItemList& items) const = 0; diff --git a/src/views/versioncontrol/versioncontrolobserver.cpp b/src/views/versioncontrol/versioncontrolobserver.cpp index c66c639c8..cf5be3c91 100644 --- a/src/views/versioncontrol/versioncontrolobserver.cpp +++ b/src/views/versioncontrol/versioncontrolobserver.cpp @@ -21,7 +21,6 @@ VersionControlObserver::VersionControlObserver(QObject* parent) : QObject(parent), m_pendingItemStatesUpdate(false), - m_versionedDirectory(false), m_silentUpdate(false), m_view(nullptr), m_model(nullptr), @@ -113,8 +112,8 @@ QList<QAction*> VersionControlObserver::actions(const KFileItemList& items) cons return m_plugin->versionControlActions(items); } else { QList<QAction*> actions; - for (const auto &plugin : qAsConst(m_plugins)) { - actions << plugin.first->outOfVersionControlActions(items); + for (const QPointer<KVersionControlPlugin> &plugin : qAsConst(m_plugins)) { + actions << plugin->outOfVersionControlActions(items); } return actions; } @@ -155,23 +154,23 @@ void VersionControlObserver::verifyDirectory() return; } - m_plugin = searchPlugin(rootItem.url()); - if (m_plugin) { - if (!m_versionedDirectory) { - m_versionedDirectory = true; + if (m_plugin != nullptr) { + if (!rootItem.url().path().startsWith(m_localRepoRoot) || !QFile::exists(m_localRepoRoot + '/' + m_plugin->fileName())) { + m_plugin = nullptr; - // The directory is versioned. Assume that the user will further browse through - // versioned directories and decrease the verification timer. - m_dirVerificationTimer->setInterval(100); + // The directory is not versioned. Reset the verification timer to a higher + // value, so that browsing through non-versioned directories is not slown down + // by an immediate verification. + m_dirVerificationTimer->setInterval(500); + } else { + // View was versionned but should not be anymore + updateItemStates(); } + } else if ((m_plugin = searchPlugin(rootItem.url()))) { + // The directory is versioned. Assume that the user will further browse through + // versioned directories and decrease the verification timer. + m_dirVerificationTimer->setInterval(100); updateItemStates(); - } else if (m_versionedDirectory) { - m_versionedDirectory = false; - - // The directory is not versioned. Reset the verification timer to a higher - // value, so that browsing through non-versioned directories is not slown down - // by an immediate verification. - m_dirVerificationTimer->setInterval(500); } } @@ -273,7 +272,7 @@ int VersionControlObserver::createItemStatesList(QMap<QString, QVector<ItemState return index - firstIndex; // number of processed items } -KVersionControlPlugin* VersionControlObserver::searchPlugin(const QUrl& directory) +void VersionControlObserver::initPlugins() { if (!m_pluginsInitialized) { // No searching for plugins has been done yet. Query the KServiceTypeTrader for @@ -294,65 +293,41 @@ KVersionControlPlugin* VersionControlObserver::searchPlugin(const QUrl& director connect(plugin, &KVersionControlPlugin::operationCompletedMessage, this, &VersionControlObserver::operationCompletedMessage); - m_plugins.append( qMakePair(plugin, plugin->fileName()) ); + m_plugins.append(plugin); } } } m_pluginsInitialized = true; } +} - if (m_plugins.empty()) { - // A searching for plugins has already been done, but no - // plugins are installed - return nullptr; - } +KVersionControlPlugin* VersionControlObserver::searchPlugin(const QUrl& directory) +{ + initPlugins(); - // We use the number of upUrl() calls to find the best matching plugin - // for the given directory. The smaller value, the better it is (0 is best). - KVersionControlPlugin* bestPlugin = nullptr; - int bestScore = INT_MAX; + // Verify whether the current directory is under a version system + for (const QPointer<KVersionControlPlugin> &plugin : qAsConst(m_plugins)) { + if (!plugin) { + continue; + } - // Verify whether the current directory contains revision information - // like .svn, .git, ... - for (const auto &it : qAsConst(m_plugins)) { - const QString fileName = directory.path() + '/' + it.second; + // first naively check if we are at working copy root + const QString fileName = directory.path() + '/' + plugin->fileName(); if (QFile::exists(fileName)) { - // The score of this plugin is 0 (best), so we can just return this plugin, - // instead of going through the plugin scoring procedure, we can't find a better one ;) - return it.first; + m_localRepoRoot = directory.path(); + return plugin; } - - // Version control systems like Git provide the version information - // file only in the root directory. Check whether the version information file can - // be found in one of the parent directories. For performance reasons this - // step is only done, if the previous directory was marked as versioned by - // m_versionedDirectory. Drawback: Until e. g. Git is recognized, the root directory - // must be shown at least once. - if (m_versionedDirectory) { - QUrl dirUrl(directory); - QUrl upUrl = KIO::upUrl(dirUrl); - int upUrlCounter = 1; - while ((upUrlCounter < bestScore) && (upUrl != dirUrl)) { - const QString fileName = dirUrl.path() + '/' + it.second; - if (QFile::exists(fileName)) { - if (upUrlCounter < bestScore) { - bestPlugin = it.first; - bestScore = upUrlCounter; - } - break; - } - dirUrl = upUrl; - upUrl = KIO::upUrl(dirUrl); - ++upUrlCounter; - } + const QString root = plugin->localRepositoryRoot(directory.path()); + if (!root.isEmpty()) { + m_localRepoRoot = root; + return plugin; } } - - return bestPlugin; + return nullptr; } bool VersionControlObserver::isVersionControlled() const { - return m_versionedDirectory && m_plugin; + return m_plugin != nullptr; } diff --git a/src/views/versioncontrol/versioncontrolobserver.h b/src/views/versioncontrol/versioncontrolobserver.h index 89c047148..5f425fe85 100644 --- a/src/views/versioncontrol/versioncontrolobserver.h +++ b/src/views/versioncontrol/versioncontrolobserver.h @@ -101,7 +101,6 @@ private slots: private: typedef QPair<KFileItem, KVersionControlPlugin::ItemVersion> ItemState; - typedef QPair<KVersionControlPlugin*, QString> VCSPlugin; void updateItemStates(); @@ -133,10 +132,12 @@ private: bool isVersionControlled() const; private: + void initPlugins(); + bool m_pendingItemStatesUpdate; - bool m_versionedDirectory; bool m_silentUpdate; // if true, no messages will be send during the update // of version states + QString m_localRepoRoot; DolphinView* m_view; KFileItemModel* m_model; @@ -145,7 +146,7 @@ private: bool m_pluginsInitialized; KVersionControlPlugin* m_plugin; - QList<VCSPlugin> m_plugins; + QList<QPointer<KVersionControlPlugin>> m_plugins; UpdateItemStatesThread* m_updateItemStatesThread; friend class UpdateItemStatesThread; |
