2 * SPDX-FileCopyrightText: 2007 Peter Penz <peter.penz19@gmail.com>
4 * SPDX-License-Identifier: GPL-2.0-or-later
7 #include "dolphinviewcontainer.h"
10 #include "admin/workerintegration.h"
11 #include "dolphin_compactmodesettings.h"
12 #include "dolphin_contentdisplaysettings.h"
13 #include "dolphin_detailsmodesettings.h"
14 #include "dolphin_generalsettings.h"
15 #include "dolphin_iconsmodesettings.h"
16 #include "dolphindebug.h"
17 #include "dolphinplacesmodelsingleton.h"
18 #include "filterbar/filterbar.h"
20 #include "kitemviews/kitemlistcontainer.h"
21 #include "search/bar.h"
22 #include "selectionmode/topbar.h"
23 #include "statusbar/dolphinstatusbar.h"
25 #include <KActionCollection>
26 #include <KApplicationTrader>
27 #include <KFileItemActions>
28 #include <KFilePlacesModel>
29 #include <KIO/JobUiDelegateFactory>
30 #include <KIO/OpenUrlJob>
31 #include <KLocalizedString>
32 #include <KMessageWidget>
33 #include <KProtocolManager>
35 #include <kio_version.h>
37 #ifndef QT_NO_ACCESSIBILITY
38 #include <QAccessible>
40 #include <QApplication>
41 #include <QDesktopServices>
43 #include <QGridLayout>
44 #include <QGuiApplication>
45 #include <QRegularExpression>
52 bool isSearchUrl(const QUrl
&url
)
54 return url
.scheme().contains(QLatin1String("search"));
57 // An overview of the widgets contained by this ViewContainer
58 struct LayoutStructure
{
61 int messageWidget
= 2;
62 int selectionModeTopBar
= 3;
64 int selectionModeBottomBar
= 5;
68 constexpr LayoutStructure positionFor
;
70 DolphinViewContainer::DolphinViewContainer(const QUrl
&url
, QWidget
*parent
)
72 , m_topLayout(nullptr)
73 , m_urlNavigator
{new DolphinUrlNavigator(url
)}
74 , m_urlNavigatorConnected
{nullptr}
75 , m_searchBar(nullptr)
76 , m_searchModeEnabled(false)
78 , m_authorizeToEnterFolderAction
{nullptr}
79 , m_messageWidget(nullptr)
80 , m_selectionModeTopBar
{nullptr}
82 , m_filterBar(nullptr)
83 , m_selectionModeBottomBar
{nullptr}
84 , m_statusBar(nullptr)
85 , m_statusBarTimer(nullptr)
86 , m_statusBarTimestamp()
87 , m_grabFocusOnUrlChange
{true}
91 m_topLayout
= new QGridLayout(this);
92 m_topLayout
->setSpacing(0);
93 m_topLayout
->setContentsMargins(0, 0, 0, 0);
95 m_messageWidget
= new KMessageWidget(this);
96 m_messageWidget
->setCloseButtonVisible(true);
97 m_messageWidget
->setPosition(KMessageWidget::Header
);
98 m_messageWidget
->hide();
100 #if !defined(Q_OS_WIN) && !defined(Q_OS_HAIKU)
102 // We must be logged in as the root user; show a big scary warning
103 showMessage(i18n("Running Dolphin as root can be dangerous. Please be careful."), KMessageWidget::Warning
);
107 // Initialize filter bar
108 m_filterBar
= new FilterBar(this);
109 m_filterBar
->setVisible(GeneralSettings::filterBar(), WithoutAnimation
);
111 connect(m_filterBar
, &FilterBar::filterChanged
, this, &DolphinViewContainer::setNameFilter
);
112 connect(m_filterBar
, &FilterBar::closeRequest
, this, &DolphinViewContainer::closeFilterBar
);
113 connect(m_filterBar
, &FilterBar::focusViewRequest
, this, &DolphinViewContainer::requestFocus
);
115 // Initialize the main view
116 m_view
= new DolphinView(url
, this);
117 connect(m_view
, &DolphinView::urlChanged
, m_filterBar
, &FilterBar::clearIfUnlocked
);
118 connect(m_view
, &DolphinView::urlChanged
, m_messageWidget
, &KMessageWidget::hide
);
119 // m_urlNavigator stays in sync with m_view's location changes and
120 // keeps track of them so going back and forth in the history works.
121 connect(m_view
, &DolphinView::urlChanged
, m_urlNavigator
.get(), &DolphinUrlNavigator::setLocationUrl
);
122 connect(m_urlNavigator
.get(), &DolphinUrlNavigator::urlChanged
, this, &DolphinViewContainer::slotUrlNavigatorLocationChanged
);
123 connect(m_urlNavigator
.get(), &DolphinUrlNavigator::urlAboutToBeChanged
, this, &DolphinViewContainer::slotUrlNavigatorLocationAboutToBeChanged
);
124 connect(m_urlNavigator
.get(), &DolphinUrlNavigator::urlSelectionRequested
, this, &DolphinViewContainer::slotUrlSelectionRequested
);
125 connect(m_view
, &DolphinView::writeStateChanged
, this, &DolphinViewContainer::writeStateChanged
);
126 connect(m_view
, &DolphinView::requestItemInfo
, this, &DolphinViewContainer::showItemInfo
);
127 connect(m_view
, &DolphinView::itemActivated
, this, &DolphinViewContainer::slotItemActivated
);
128 connect(m_view
, &DolphinView::fileMiddleClickActivated
, this, &DolphinViewContainer::slotfileMiddleClickActivated
);
129 connect(m_view
, &DolphinView::itemsActivated
, this, &DolphinViewContainer::slotItemsActivated
);
130 connect(m_view
, &DolphinView::redirection
, this, &DolphinViewContainer::redirect
);
131 connect(m_view
, &DolphinView::directoryLoadingStarted
, this, &DolphinViewContainer::slotDirectoryLoadingStarted
);
132 connect(m_view
, &DolphinView::directoryLoadingCompleted
, this, &DolphinViewContainer::slotDirectoryLoadingCompleted
);
133 connect(m_view
, &DolphinView::directoryLoadingCanceled
, this, &DolphinViewContainer::slotDirectoryLoadingCanceled
);
134 connect(m_view
, &DolphinView::itemCountChanged
, this, &DolphinViewContainer::delayedStatusBarUpdate
);
135 connect(m_view
, &DolphinView::selectionChanged
, this, &DolphinViewContainer::delayedStatusBarUpdate
);
136 connect(m_view
, &DolphinView::errorMessage
, this, &DolphinViewContainer::slotErrorMessageFromView
);
137 connect(m_view
, &DolphinView::urlIsFileError
, this, &DolphinViewContainer::slotUrlIsFileError
);
138 connect(m_view
, &DolphinView::activated
, this, &DolphinViewContainer::activate
);
139 connect(m_view
, &DolphinView::hiddenFilesShownChanged
, this, &DolphinViewContainer::slotHiddenFilesShownChanged
);
140 connect(m_view
, &DolphinView::sortHiddenLastChanged
, this, &DolphinViewContainer::slotSortHiddenLastChanged
);
141 connect(m_view
, &DolphinView::currentDirectoryRemoved
, this, &DolphinViewContainer::slotCurrentDirectoryRemoved
);
143 // Initialize status bar
144 m_statusBar
= new DolphinStatusBar(this);
145 m_statusBar
->setUrl(m_view
->url());
146 m_statusBar
->setZoomLevel(m_view
->zoomLevel());
147 connect(m_view
, &DolphinView::urlChanged
, m_statusBar
, &DolphinStatusBar::setUrl
);
148 connect(m_view
, &DolphinView::zoomLevelChanged
, m_statusBar
, &DolphinStatusBar::setZoomLevel
);
149 connect(m_view
, &DolphinView::infoMessage
, m_statusBar
, &DolphinStatusBar::setText
);
150 connect(m_view
, &DolphinView::operationCompletedMessage
, m_statusBar
, &DolphinStatusBar::setText
);
151 connect(m_view
, &DolphinView::statusBarTextChanged
, m_statusBar
, &DolphinStatusBar::setDefaultText
);
152 connect(m_view
, &DolphinView::statusBarTextChanged
, m_statusBar
, &DolphinStatusBar::resetToDefaultText
);
153 connect(m_view
, &DolphinView::directoryLoadingProgress
, m_statusBar
, [this](int percent
) {
154 m_statusBar
->showProgress(i18nc("@info:progress", "Loading folder…"), percent
);
156 connect(m_view
, &DolphinView::directorySortingProgress
, m_statusBar
, [this](int percent
) {
157 m_statusBar
->showProgress(i18nc("@info:progress", "Sorting…"), percent
);
159 connect(m_statusBar
, &DolphinStatusBar::stopPressed
, this, &DolphinViewContainer::stopDirectoryLoading
);
160 connect(m_statusBar
, &DolphinStatusBar::zoomLevelChanged
, this, &DolphinViewContainer::slotStatusBarZoomLevelChanged
);
161 connect(m_statusBar
, &DolphinStatusBar::showMessage
, this, [this](const QString
&message
, KMessageWidget::MessageType messageType
) {
162 showMessage(message
, messageType
);
164 connect(m_statusBar
, &DolphinStatusBar::widthUpdated
, this, &DolphinViewContainer::updateStatusBarGeometry
);
165 connect(m_statusBar
, &DolphinStatusBar::urlChanged
, this, &DolphinViewContainer::updateStatusBar
);
166 connect(this, &DolphinViewContainer::showFilterBarChanged
, this, &DolphinViewContainer::updateStatusBar
);
168 m_statusBarTimer
= new QTimer(this);
169 m_statusBarTimer
->setSingleShot(true);
170 m_statusBarTimer
->setInterval(300);
171 connect(m_statusBarTimer
, &QTimer::timeout
, this, &DolphinViewContainer::updateStatusBar
);
173 KIO::FileUndoManager
*undoManager
= KIO::FileUndoManager::self();
174 connect(undoManager
, &KIO::FileUndoManager::jobRecordingFinished
, this, &DolphinViewContainer::delayedStatusBarUpdate
);
176 m_topLayout
->addWidget(m_messageWidget
, positionFor
.messageWidget
, 0);
177 m_topLayout
->addWidget(m_view
, positionFor
.view
, 0);
178 m_topLayout
->addWidget(m_filterBar
, positionFor
.filterBar
, 0);
179 if (GeneralSettings::showStatusBar() == GeneralSettings::EnumShowStatusBar::FullWidth
) {
180 m_topLayout
->addWidget(m_statusBar
, positionFor
.statusBar
, 0);
182 connect(m_statusBar
, &DolphinStatusBar::modeUpdated
, this, [this]() {
183 const bool statusBarInLayout
= m_topLayout
->itemAtPosition(positionFor
.statusBar
, 0);
184 if (GeneralSettings::showStatusBar() == GeneralSettings::EnumShowStatusBar::FullWidth
) {
185 if (!statusBarInLayout
) {
186 m_topLayout
->addWidget(m_statusBar
, positionFor
.statusBar
, 0);
187 m_statusBar
->setUrl(m_view
->url());
190 if (statusBarInLayout
) {
191 m_topLayout
->removeWidget(m_statusBar
);
194 updateStatusBarGeometry();
196 m_statusBar
->setHidden(false);
198 setSearchBarVisible(isSearchUrl(url
));
200 // Update view as the ContentDisplaySettings change
201 // this happens here and not in DolphinView as DolphinviewContainer and DolphinView are not in the same build target ATM
202 connect(ContentDisplaySettings::self(), &KCoreConfigSkeleton::configChanged
, m_view
, &DolphinView::reload
);
204 KFilePlacesModel
*placesModel
= DolphinPlacesModelSingleton::instance().placesModel();
205 connect(placesModel
, &KFilePlacesModel::dataChanged
, this, &DolphinViewContainer::slotPlacesModelChanged
);
206 connect(placesModel
, &KFilePlacesModel::rowsInserted
, this, &DolphinViewContainer::slotPlacesModelChanged
);
207 connect(placesModel
, &KFilePlacesModel::rowsRemoved
, this, &DolphinViewContainer::slotPlacesModelChanged
);
209 QApplication::instance()->installEventFilter(this);
212 DolphinViewContainer::~DolphinViewContainer()
216 QUrl
DolphinViewContainer::url() const
218 return m_view
->url();
221 KFileItem
DolphinViewContainer::rootItem() const
223 return m_view
->rootItem();
226 void DolphinViewContainer::setActive(bool active
)
228 if (m_urlNavigatorConnected
) {
229 m_urlNavigatorConnected
->setActive(active
);
231 m_view
->setActive(active
);
234 bool DolphinViewContainer::isActive() const
236 return m_view
->isActive();
239 void DolphinViewContainer::setGrabFocusOnUrlChange(bool grabFocus
)
241 m_grabFocusOnUrlChange
= grabFocus
;
244 const DolphinStatusBar
*DolphinViewContainer::statusBar() const
249 DolphinStatusBar
*DolphinViewContainer::statusBar()
254 const DolphinUrlNavigator
*DolphinViewContainer::urlNavigator() const
256 return m_urlNavigatorConnected
;
259 DolphinUrlNavigator
*DolphinViewContainer::urlNavigator()
261 return m_urlNavigatorConnected
;
264 const DolphinUrlNavigator
*DolphinViewContainer::urlNavigatorInternalWithHistory() const
266 return m_urlNavigator
.get();
269 DolphinUrlNavigator
*DolphinViewContainer::urlNavigatorInternalWithHistory()
271 return m_urlNavigator
.get();
274 const DolphinView
*DolphinViewContainer::view() const
279 DolphinView
*DolphinViewContainer::view()
284 void DolphinViewContainer::connectUrlNavigator(DolphinUrlNavigator
*urlNavigator
)
286 Q_CHECK_PTR(urlNavigator
);
287 Q_ASSERT(!m_urlNavigatorConnected
);
288 Q_ASSERT(m_urlNavigator
.get() != urlNavigator
);
291 urlNavigator
->setLocationUrl(m_view
->url());
292 urlNavigator
->setShowHiddenFolders(m_view
->hiddenFilesShown());
293 urlNavigator
->setSortHiddenFoldersLast(m_view
->sortHiddenLast());
294 if (m_urlNavigatorVisualState
) {
295 urlNavigator
->setVisualState(*m_urlNavigatorVisualState
.get());
296 m_urlNavigatorVisualState
.reset();
298 urlNavigator
->setActive(isActive());
300 // Url changes are still done via m_urlNavigator.
301 connect(urlNavigator
, &DolphinUrlNavigator::urlChanged
, m_urlNavigator
.get(), &DolphinUrlNavigator::setLocationUrl
);
302 connect(urlNavigator
, &DolphinUrlNavigator::urlsDropped
, this, [=, this](const QUrl
&destination
, QDropEvent
*event
) {
303 m_view
->dropUrls(destination
, event
, urlNavigator
->dropWidget());
305 // Aside from these, only visual things need to be connected.
306 connect(m_view
, &DolphinView::urlChanged
, urlNavigator
, &DolphinUrlNavigator::setLocationUrl
);
307 connect(urlNavigator
, &DolphinUrlNavigator::activated
, this, &DolphinViewContainer::activate
);
308 connect(urlNavigator
, &DolphinUrlNavigator::requestToLoseFocus
, m_view
, [this]() {
312 urlNavigator
->setReadOnlyBadgeVisible(rootItem().isLocalFile() && !rootItem().isWritable());
314 m_urlNavigatorConnected
= urlNavigator
;
317 void DolphinViewContainer::disconnectUrlNavigator()
319 if (!m_urlNavigatorConnected
) {
323 disconnect(m_urlNavigatorConnected
, &DolphinUrlNavigator::urlChanged
, m_urlNavigator
.get(), &DolphinUrlNavigator::setLocationUrl
);
324 disconnect(m_urlNavigatorConnected
, &DolphinUrlNavigator::urlsDropped
, this, nullptr);
325 disconnect(m_view
, &DolphinView::urlChanged
, m_urlNavigatorConnected
, &DolphinUrlNavigator::setLocationUrl
);
326 disconnect(m_urlNavigatorConnected
, &DolphinUrlNavigator::activated
, this, &DolphinViewContainer::activate
);
327 disconnect(m_urlNavigatorConnected
, &DolphinUrlNavigator::requestToLoseFocus
, m_view
, nullptr);
329 m_urlNavigatorVisualState
= m_urlNavigatorConnected
->visualState();
330 m_urlNavigatorConnected
= nullptr;
333 void DolphinViewContainer::setSearchBarVisible(bool visible
)
336 if (isSearchBarVisible()) {
337 m_searchBar
->setVisible(false, WithAnimation
);
343 m_searchBar
= new Search::Bar(std::make_shared
<const Search::DolphinQuery
>(m_urlNavigator
->locationUrl(), QUrl
{} /** will be set below. */), this);
344 connect(m_searchBar
, &Search::Bar::urlChangeRequested
, this, [this](const QUrl
&url
) {
345 m_view
->setViewPropertiesContext(isSearchUrl(url
) ? QStringLiteral("search") : QString());
346 setGrabFocusOnUrlChange(false); // Prevent loss of focus while typing or refining a search.
348 setGrabFocusOnUrlChange(true);
350 connect(m_searchBar
, &Search::Bar::focusViewRequest
, this, &DolphinViewContainer::requestFocus
);
351 connect(m_searchBar
, &Search::Bar::showMessage
, this, [this](const QString
&message
, KMessageWidget::MessageType messageType
) {
352 showMessage(message
, messageType
);
355 &Search::Bar::showInstallationProgress
,
357 [this](const QString
¤tlyRunningTaskTitle
, int installationProgressPercent
) {
358 m_statusBar
->showProgress(currentlyRunningTaskTitle
, installationProgressPercent
, DolphinStatusBar::CancelLoading::Disallowed
);
360 connect(m_searchBar
, &Search::Bar::visibilityChanged
, this, &DolphinViewContainer::searchBarVisibilityChanged
);
361 m_topLayout
->addWidget(m_searchBar
, positionFor
.searchBar
, 0);
364 m_searchBar
->setVisible(true, WithAnimation
);
366 // The Search::Bar has been set visible but its state does not yet match with this view container or view.
367 // The view might for example already be searching because it was opened with a search URL. The Search::Bar needs to be updated to show the parameters of
368 // that search. And even if there is no search URL loaded in the view currently, we still need to figure out where the Search::Bar should be searching if
369 // the user starts a search from there. Let's figure out the search location in this method and let the DolphinQuery constructor figure out the rest from
370 // the current m_urlNavigator->locationUrl().
371 for (int i
= m_urlNavigator
->historyIndex(); i
< m_urlNavigator
->historySize(); i
++) {
372 QUrl url
= m_urlNavigator
->locationUrl(i
);
373 if (isSearchUrl(url
)) {
374 // The previous location was a search URL. Try to see if that search URL has a valid search path so we keep searching in the same location.
375 const auto searchPath
= Search::DolphinQuery(url
, QUrl
{}).searchPath(); // DolphinQuery is great at extracting the search path from a search URL.
376 if (searchPath
.isValid()) {
377 m_searchBar
->updateStateToMatch(std::make_shared
<const Search::DolphinQuery
>(m_urlNavigator
->locationUrl(), searchPath
));
380 } else if (url
.scheme() == QLatin1String("tags")) {
381 continue; // We avoid setting a tags url as the backup search path because a DolphinQuery constructed from a tags url will already search tagged
384 m_searchBar
->updateStateToMatch(std::make_shared
<const Search::DolphinQuery
>(m_urlNavigator
->locationUrl(), url
));
388 // We could not find any URL fit for searching in the history. This might happen because this view only ever loaded a search which searches "Everywhere"
389 // and therefore there is no specific search path to choose from. But the Search::Bar *needs* to know a search path because the user might switch from
390 // searching "Everywhere" to "Here" and it is everybody's guess what "Here" is supposed to mean in that context… We'll simply fall back to the user's home
391 // path for lack of a better option.
392 m_searchBar
->updateStateToMatch(std::make_shared
<const Search::DolphinQuery
>(m_urlNavigator
->locationUrl(), QUrl::fromUserInput(QDir::homePath())));
395 bool DolphinViewContainer::isSearchBarVisible() const
397 return m_searchBar
&& m_searchBar
->isVisible() && m_searchBar
->isEnabled();
400 void DolphinViewContainer::setFocusToSearchBar()
402 Q_ASSERT(isSearchBarVisible());
403 m_searchBar
->selectAll();
406 void DolphinViewContainer::setSelectionModeEnabled(bool enabled
, KActionCollection
*actionCollection
, SelectionMode::BottomBar::Contents bottomBarContents
)
408 const bool wasEnabled
= m_view
->selectionMode();
409 m_view
->setSelectionModeEnabled(enabled
);
413 return; // nothing to do here
415 Q_CHECK_PTR(m_selectionModeTopBar
); // there is no point in disabling selectionMode when it wasn't even enabled once.
416 Q_CHECK_PTR(m_selectionModeBottomBar
);
417 m_selectionModeTopBar
->setVisible(false, WithAnimation
);
418 m_selectionModeBottomBar
->setVisible(false, WithAnimation
);
419 Q_EMIT
selectionModeChanged(false);
421 if (!QApplication::focusWidget() || m_selectionModeTopBar
->isAncestorOf(QApplication::focusWidget())
422 || m_selectionModeBottomBar
->isAncestorOf(QApplication::focusWidget())) {
428 if (!m_selectionModeTopBar
) {
429 // Changing the location will disable selection mode.
430 connect(m_urlNavigator
.get(), &DolphinUrlNavigator::urlChanged
, this, [this]() {
431 setSelectionModeEnabled(false);
434 m_selectionModeTopBar
= new SelectionMode::TopBar(this); // will be created hidden
435 connect(m_selectionModeTopBar
, &SelectionMode::TopBar::selectionModeLeavingRequested
, this, [this]() {
436 setSelectionModeEnabled(false);
438 m_topLayout
->addWidget(m_selectionModeTopBar
, positionFor
.selectionModeTopBar
, 0);
441 if (!m_selectionModeBottomBar
) {
442 m_selectionModeBottomBar
= new SelectionMode::BottomBar(actionCollection
, this);
443 connect(m_view
, &DolphinView::selectionChanged
, this, [this](const KFileItemList
&selection
) {
444 m_selectionModeBottomBar
->slotSelectionChanged(selection
, m_view
->url());
446 connect(m_selectionModeBottomBar
, &SelectionMode::BottomBar::error
, this, &DolphinViewContainer::showErrorMessage
);
447 connect(m_selectionModeBottomBar
, &SelectionMode::BottomBar::selectionModeLeavingRequested
, this, [this]() {
448 setSelectionModeEnabled(false);
450 m_topLayout
->addWidget(m_selectionModeBottomBar
, positionFor
.selectionModeBottomBar
, 0);
452 m_selectionModeBottomBar
->resetContents(bottomBarContents
);
453 if (bottomBarContents
== SelectionMode::BottomBar::GeneralContents
) {
454 m_selectionModeBottomBar
->slotSelectionChanged(m_view
->selectedItems(), m_view
->url());
458 m_selectionModeTopBar
->setVisible(true, WithAnimation
);
459 m_selectionModeBottomBar
->setVisible(true, WithAnimation
);
460 Q_EMIT
selectionModeChanged(true);
464 bool DolphinViewContainer::isSelectionModeEnabled() const
466 const bool isEnabled
= m_view
->selectionMode();
468 // We can't assert that the bars are invisible only because the selection mode is disabled because the hide animation might still be playing.
469 && (!m_selectionModeBottomBar
|| !m_selectionModeBottomBar
->isEnabled() || !m_selectionModeBottomBar
->isVisible()
470 || m_selectionModeBottomBar
->contents() == SelectionMode::BottomBar::PasteContents
))
471 || (isEnabled
&& m_selectionModeTopBar
472 && m_selectionModeTopBar
->isVisible()
473 // The bottom bar is either visible or was hidden because it has nothing to show in GeneralContents mode e.g. because no items are selected.
474 && m_selectionModeBottomBar
475 && (m_selectionModeBottomBar
->isVisible() || m_selectionModeBottomBar
->contents() == SelectionMode::BottomBar::GeneralContents
)));
479 void DolphinViewContainer::slotSplitTabDisabled()
481 if (m_selectionModeBottomBar
) {
482 m_selectionModeBottomBar
->slotSplitTabDisabled();
486 void DolphinViewContainer::showMessage(const QString
&message
, KMessageWidget::MessageType messageType
, std::initializer_list
<QAction
*> buttonActions
)
488 if (message
.isEmpty()) {
492 m_messageWidget
->setText(message
);
494 // TODO: wrap at arbitrary character positions once QLabel can do this
495 // https://bugreports.qt.io/browse/QTBUG-1276
496 m_messageWidget
->setWordWrap(true);
497 m_messageWidget
->setMessageType(messageType
);
499 const QList
<QAction
*> previousMessageWidgetActions
= m_messageWidget
->actions();
500 for (auto action
: previousMessageWidgetActions
) {
501 m_messageWidget
->removeAction(action
);
503 for (QAction
*action
: buttonActions
) {
504 m_messageWidget
->addAction(action
);
507 m_messageWidget
->setWordWrap(false);
508 const int unwrappedWidth
= m_messageWidget
->sizeHint().width();
509 m_messageWidget
->setWordWrap(unwrappedWidth
> size().width());
511 if (m_messageWidget
->isVisible()) {
512 m_messageWidget
->hide();
514 m_messageWidget
->animatedShow();
516 #ifndef QT_NO_ACCESSIBILITY
517 if (QAccessible::isActive() && isActive()) {
518 // To announce the new message keyboard focus must be moved to the message label. However, we do not have direct access to the label that is internal
519 // to the KMessageWidget. Instead we setFocus() on the KMessageWidget and trust that it has set correct focus handling.
520 m_messageWidget
->setFocus();
525 void DolphinViewContainer::showProgress(const QString
¤tlyRunningTaskTitle
, int progressPercent
)
527 m_statusBar
->showProgress(currentlyRunningTaskTitle
, progressPercent
, DolphinStatusBar::CancelLoading::Disallowed
);
530 void DolphinViewContainer::readSettings()
532 // The startup settings should (only) get applied if they have been
533 // modified by the user. Otherwise keep the (possibly) different current
534 // setting of the filterbar.
535 if (GeneralSettings::modifiedStartupSettings()) {
536 setFilterBarVisible(GeneralSettings::filterBar());
539 m_view
->readSettings();
540 m_statusBar
->readSettings();
543 bool DolphinViewContainer::isFilterBarVisible() const
545 return m_filterBar
->isEnabled(); // Gets disabled in AnimatedHeightWidget while animating towards a hidden state.
548 QString
DolphinViewContainer::placesText() const
552 if (isSearchBarVisible() && m_searchBar
->isSearchConfigured()) {
553 text
= m_searchBar
->queryTitle();
555 text
= url().adjusted(QUrl::StripTrailingSlash
).fileName();
556 if (text
.isEmpty()) {
559 if (text
.isEmpty()) {
560 text
= url().scheme();
567 void DolphinViewContainer::reload()
570 m_messageWidget
->hide();
573 QString
DolphinViewContainer::captionWindowTitle() const
575 if (GeneralSettings::showFullPathInTitlebar() && (!isSearchBarVisible() || !m_searchBar
->isSearchConfigured())) {
576 if (!url().isLocalFile()) {
577 return url().adjusted(QUrl::StripTrailingSlash
).toString();
579 return url().adjusted(QUrl::StripTrailingSlash
).path();
581 return DolphinViewContainer::caption();
585 QString
DolphinViewContainer::caption() const
587 // see KUrlNavigatorPrivate::firstButtonText().
588 if (url().path().isEmpty() || url().path() == QLatin1Char('/')) {
589 QUrlQuery
query(url());
590 const QString title
= query
.queryItemValue(QStringLiteral("title"), QUrl::FullyDecoded
);
591 if (!title
.isEmpty()) {
596 if (isSearchBarVisible() && m_searchBar
->isSearchConfigured()) {
597 return m_searchBar
->queryTitle();
600 KFilePlacesModel
*placesModel
= DolphinPlacesModelSingleton::instance().placesModel();
602 QModelIndex url_index
= placesModel
->closestItem(url());
604 if (url_index
.isValid() && placesModel
->url(url_index
).matches(url(), QUrl::StripTrailingSlash
)) {
605 return placesModel
->text(url_index
);
608 if (!url().isLocalFile()) {
609 QUrl adjustedUrl
= url().adjusted(QUrl::StripTrailingSlash
);
611 if (!adjustedUrl
.fileName().isEmpty()) {
612 caption
= adjustedUrl
.fileName();
613 } else if (!adjustedUrl
.path().isEmpty() && adjustedUrl
.path() != "/") {
614 caption
= adjustedUrl
.path();
615 } else if (!adjustedUrl
.host().isEmpty()) {
616 caption
= adjustedUrl
.host();
618 caption
= adjustedUrl
.toString();
623 QString fileName
= url().adjusted(QUrl::StripTrailingSlash
).fileName();
624 if (fileName
.isEmpty()) {
631 void DolphinViewContainer::setUrl(const QUrl
&newUrl
)
633 if (newUrl
!= m_urlNavigator
->locationUrl()) {
634 m_urlNavigator
->setLocationUrl(newUrl
);
635 if (m_searchBar
&& !Search::isSupportedSearchScheme(newUrl
.scheme())) {
636 m_searchBar
->setSearchPath(newUrl
);
641 void DolphinViewContainer::setFilterBarVisible(bool visible
)
643 Q_ASSERT(m_filterBar
);
645 m_view
->hideToolTip(ToolTipManager::HideBehavior::Instantly
);
646 m_filterBar
->setVisible(true, WithAnimation
);
647 m_filterBar
->setFocus();
648 m_filterBar
->selectAll();
649 Q_EMIT
showFilterBarChanged(true);
655 void DolphinViewContainer::delayedStatusBarUpdate()
657 if (m_statusBarTimer
->isActive() && (m_statusBarTimestamp
.elapsed() > 2000)) {
658 // No update of the statusbar has been done during the last 2 seconds,
659 // although an update has been requested. Trigger an immediate update.
660 m_statusBarTimer
->stop();
663 // Invoke updateStatusBar() with a small delay. This assures that
664 // when a lot of delayedStatusBarUpdates() are done in a short time,
665 // no bottleneck is given.
666 m_statusBarTimer
->start();
670 void DolphinViewContainer::updateStatusBar()
672 m_statusBarTimestamp
.start();
673 m_view
->requestStatusBarText();
674 updateStatusBarGeometry();
677 void DolphinViewContainer::slotDirectoryLoadingStarted()
679 if (isSearchUrl(url())) {
680 // Search KIO-slaves usually don't provide any progress information. Give
681 // a hint to the user that a searching is done:
683 m_statusBar
->showProgress(i18nc("@info", "Searching…"), -1);
685 // Trigger an undetermined progress indication. The progress
686 // information in percent will be triggered by the percent() signal
687 // of the directory lister later.
688 m_statusBar
->showProgress(QString(), -1);
691 if (m_urlNavigatorConnected
) {
692 m_urlNavigatorConnected
->setReadOnlyBadgeVisible(false);
696 void DolphinViewContainer::slotDirectoryLoadingCompleted()
698 m_statusBar
->showProgress(QString(), 100);
700 if (isSearchUrl(url()) && m_view
->itemsCount() == 0) {
701 // The dir lister has been completed on a Baloo-URI and no items have been found. Instead
702 // of showing the default status bar information ("0 items") a more helpful information is given:
703 m_statusBar
->setText(i18nc("@info:status", "No items found."));
708 if (m_urlNavigatorConnected
) {
709 m_urlNavigatorConnected
->setReadOnlyBadgeVisible(rootItem().isLocalFile() && !rootItem().isWritable());
712 // Update admin bar visibility
713 if (m_view
->url().scheme() == QStringLiteral("admin")) {
715 m_adminBar
= new Admin::Bar(this);
716 m_topLayout
->addWidget(m_adminBar
, positionFor
.adminBar
, 0);
718 m_adminBar
->setVisible(true, WithAnimation
);
719 } else if (m_adminBar
) {
720 m_adminBar
->setVisible(false, WithAnimation
);
724 void DolphinViewContainer::slotDirectoryLoadingCanceled()
726 m_statusBar
->showProgress(QString(), 100);
727 m_statusBar
->setText(QString());
730 void DolphinViewContainer::slotUrlIsFileError(const QUrl
&url
)
732 const KFileItem
item(url
);
734 // Find out if the file can be opened in the view (for example, this is the
735 // case if the file is an archive). The mime type must be known for that.
736 item
.determineMimeType();
737 const QUrl
&folderUrl
= DolphinView::openItemAsFolderUrl(item
, true);
738 if (!folderUrl
.isEmpty()) {
741 slotItemActivated(item
);
745 void DolphinViewContainer::slotItemActivated(const KFileItem
&item
)
747 // It is possible to activate items on inactive views by
748 // drag & drop operations. Assure that activating an item always
749 // results in an active view.
750 m_view
->setActive(true);
752 const QUrl
&url
= DolphinView::openItemAsFolderUrl(item
, GeneralSettings::browseThroughArchives());
753 if (!url
.isEmpty()) {
754 const auto modifiers
= QGuiApplication::keyboardModifiers();
755 // keep in sync with KUrlNavigator::slotNavigatorButtonClicked
756 if (modifiers
& Qt::ControlModifier
&& modifiers
& Qt::ShiftModifier
) {
757 Q_EMIT
activeTabRequested(url
);
758 } else if (modifiers
& Qt::ControlModifier
) {
759 Q_EMIT
tabRequested(url
);
760 } else if (modifiers
& Qt::ShiftModifier
) {
761 Dolphin::openNewWindow({KFilePlacesModel::convertedUrl(url
)}, this);
768 KIO::OpenUrlJob
*job
= new KIO::OpenUrlJob(item
.targetUrl(), item
.mimetype());
769 // Auto*Warning*Handling, errors are put in a KMessageWidget by us in slotOpenUrlFinished.
770 job
->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoWarningHandlingEnabled
, this));
771 job
->setShowOpenOrExecuteDialog(true);
772 connect(job
, &KIO::OpenUrlJob::finished
, this, &DolphinViewContainer::slotOpenUrlFinished
);
776 void DolphinViewContainer::slotfileMiddleClickActivated(const KFileItem
&item
)
778 KService::List services
= KApplicationTrader::queryByMimeType(item
.mimetype());
780 int indexOfAppToOpenFileWith
= 1;
782 // executable scripts
783 auto mimeType
= item
.currentMimeType();
784 if (item
.isLocalFile() && mimeType
.inherits(QStringLiteral("application/x-executable")) && mimeType
.inherits(QStringLiteral("text/plain"))
785 && QFileInfo(item
.localPath()).isExecutable()) {
786 KConfigGroup
cfgGroup(KSharedConfig::openConfig(QStringLiteral("kiorc")), QStringLiteral("Executable scripts"));
787 const QString value
= cfgGroup
.readEntry("behaviourOnLaunch", "alwaysAsk");
789 // in case KIO::WidgetsOpenOrExecuteFileHandler::promptUserOpenOrExecute would not open the file
790 if (value
!= QLatin1String("open")) {
791 indexOfAppToOpenFileWith
= 0;
795 if (services
.length() >= indexOfAppToOpenFileWith
+ 1) {
796 auto service
= services
.at(indexOfAppToOpenFileWith
);
798 KIO::ApplicationLauncherJob
*job
= new KIO::ApplicationLauncherJob(service
, this);
799 job
->setUrls({item
.url()});
801 job
->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled
, this));
802 connect(job
, &KIO::OpenUrlJob::finished
, this, &DolphinViewContainer::slotOpenUrlFinished
);
805 // If no 2nd service available, try to open archives in new tabs, regardless of the "Open archives as folder" setting.
806 const QUrl
&url
= DolphinView::openItemAsFolderUrl(item
);
807 const auto modifiers
= QGuiApplication::keyboardModifiers();
808 if (!url
.isEmpty()) {
809 // keep in sync with KUrlNavigator::slotNavigatorButtonClicked
810 if (modifiers
& Qt::ShiftModifier
) {
811 Q_EMIT
activeTabRequested(url
);
813 Q_EMIT
tabRequested(url
);
819 void DolphinViewContainer::slotItemsActivated(const KFileItemList
&items
)
821 Q_ASSERT(items
.count() >= 2);
823 KFileItemActions
fileItemActions(this);
824 fileItemActions
.runPreferredApplications(items
);
827 void DolphinViewContainer::showItemInfo(const KFileItem
&item
)
830 m_statusBar
->resetToDefaultText();
832 m_statusBar
->setText(item
.getStatusBarInfo());
836 void DolphinViewContainer::closeFilterBar()
838 m_filterBar
->closeFilterBar();
840 Q_EMIT
showFilterBarChanged(false);
843 void DolphinViewContainer::clearFilterBar()
845 m_filterBar
->clearIfUnlocked();
848 void DolphinViewContainer::setNameFilter(const QString
&nameFilter
)
850 m_view
->hideToolTip(ToolTipManager::HideBehavior::Instantly
);
851 m_view
->setNameFilter(nameFilter
);
852 delayedStatusBarUpdate();
855 void DolphinViewContainer::activate()
860 void DolphinViewContainer::slotUrlNavigatorLocationAboutToBeChanged(const QUrl
&)
865 void DolphinViewContainer::slotUrlNavigatorLocationChanged(const QUrl
&url
)
867 if (m_urlNavigatorConnected
) {
868 m_urlNavigatorConnected
->slotReturnPressed();
871 if (KProtocolManager::supportsListing(url
)) {
872 if (isSearchUrl(url
)) {
873 setSearchBarVisible(true);
874 } else if (m_searchBar
&& m_searchBar
->isSearchConfigured()) {
875 // Hide the search bar because it shows an outdated search which the user does not care about anymore.
876 setSearchBarVisible(false);
880 tryRestoreViewState();
882 if (m_grabFocusOnUrlChange
&& isActive()) {
883 // When an URL has been entered, the view should get the focus.
884 // The focus must be requested asynchronously, as changing the URL might create
885 // a new view widget.
886 QTimer::singleShot(0, this, &DolphinViewContainer::requestFocus
);
888 } else if (KProtocolManager::isSourceProtocol(url
)) {
889 if (url
.scheme().startsWith(QLatin1String("http"))) {
890 showMessage(i18nc("@info:status", // krazy:exclude=qmethods
891 "Dolphin does not support web pages, the web browser has been launched"),
892 KMessageWidget::Information
);
894 showMessage(i18nc("@info:status", "Protocol not supported by Dolphin, default application has been launched"), KMessageWidget::Information
);
897 QDesktopServices::openUrl(url
);
898 redirect(QUrl(), m_urlNavigator
->locationUrl(1));
900 if (!url
.scheme().isEmpty()) {
901 showMessage(i18nc("@info:status", "Invalid protocol '%1'", url
.scheme()), KMessageWidget::Error
);
903 showMessage(i18nc("@info:status", "Invalid protocol"), KMessageWidget::Error
);
905 m_urlNavigator
->goBack();
909 void DolphinViewContainer::slotUrlSelectionRequested(const QUrl
&url
)
911 // We do not want to select any item here because there is no reason to assume that the user wants to edit the folder we are emerging from. BUG: 424723
913 m_view
->markUrlAsCurrent(url
); // makes the item scroll into view
916 void DolphinViewContainer::disableUrlNavigatorSelectionRequests()
918 disconnect(m_urlNavigator
.get(), &KUrlNavigator::urlSelectionRequested
, this, &DolphinViewContainer::slotUrlSelectionRequested
);
921 void DolphinViewContainer::enableUrlNavigatorSelectionRequests()
923 connect(m_urlNavigator
.get(), &KUrlNavigator::urlSelectionRequested
, this, &DolphinViewContainer::slotUrlSelectionRequested
);
926 void DolphinViewContainer::redirect(const QUrl
&oldUrl
, const QUrl
&newUrl
)
929 const bool block
= m_urlNavigator
->signalsBlocked();
930 m_urlNavigator
->blockSignals(true);
932 // Assure that the location state is reset for redirection URLs. This
933 // allows to skip redirection URLs when going back or forward in the
935 m_urlNavigator
->saveLocationState(QByteArray());
936 m_urlNavigator
->setLocationUrl(newUrl
);
937 setSearchBarVisible(isSearchUrl(newUrl
));
939 m_urlNavigator
->blockSignals(block
);
942 void DolphinViewContainer::requestFocus()
947 void DolphinViewContainer::stopDirectoryLoading()
949 m_view
->stopLoading();
950 m_statusBar
->showProgress(QString(), 100);
953 void DolphinViewContainer::slotStatusBarZoomLevelChanged(int zoomLevel
)
955 m_view
->setZoomLevel(zoomLevel
);
958 void DolphinViewContainer::slotErrorMessageFromView(const QString
&message
, const int kioErrorCode
)
960 if (kioErrorCode
== KIO::ERR_CANNOT_ENTER_DIRECTORY
&& m_view
->url().scheme() == QStringLiteral("file")
961 && KProtocolInfo::isKnownProtocol(QStringLiteral("admin")) && !rootItem().isReadable()) {
962 // Explain to users that they need authentication to see the folder contents.
963 if (!m_authorizeToEnterFolderAction
) { // This code is similar to parts of Admin::Bar::hideTheNextTimeAuthorizationExpires().
964 // We should not simply use the actAsAdminAction() itself here because that one always refers to the active view instead of this->m_view.
965 auto actAsAdminAction
= Admin::WorkerIntegration::FriendAccess::actAsAdminAction();
966 m_authorizeToEnterFolderAction
= new QAction
{actAsAdminAction
->icon(), actAsAdminAction
->text(), this};
967 m_authorizeToEnterFolderAction
->setToolTip(actAsAdminAction
->toolTip());
968 m_authorizeToEnterFolderAction
->setWhatsThis(actAsAdminAction
->whatsThis());
969 connect(m_authorizeToEnterFolderAction
, &QAction::triggered
, this, [this, actAsAdminAction
]() {
971 actAsAdminAction
->trigger();
974 showMessage(i18nc("@info", "Authorization required to enter this folder."), KMessageWidget::Error
, {m_authorizeToEnterFolderAction
});
977 Q_EMIT
showErrorMessage(message
);
980 void DolphinViewContainer::showErrorMessage(const QString
&message
)
982 showMessage(message
, KMessageWidget::Error
);
985 void DolphinViewContainer::slotPlacesModelChanged()
987 if (!GeneralSettings::showFullPathInTitlebar()) {
988 Q_EMIT
captionChanged();
992 void DolphinViewContainer::slotHiddenFilesShownChanged(bool showHiddenFiles
)
994 if (m_urlNavigatorConnected
) {
995 m_urlNavigatorConnected
->setShowHiddenFolders(showHiddenFiles
);
999 void DolphinViewContainer::slotSortHiddenLastChanged(bool hiddenLast
)
1001 if (m_urlNavigatorConnected
) {
1002 m_urlNavigatorConnected
->setSortHiddenFoldersLast(hiddenLast
);
1006 void DolphinViewContainer::slotCurrentDirectoryRemoved()
1008 const QString
location(url().toDisplayString(QUrl::PreferLocalFile
));
1009 if (url().isLocalFile()) {
1010 const QString dirPath
= url().toLocalFile();
1011 const QString newPath
= getNearestExistingAncestorOfPath(dirPath
);
1012 const QUrl newUrl
= QUrl::fromLocalFile(newPath
);
1013 // #473377: Delay changing the url to avoid modifying KCoreDirLister before KCoreDirListerCache::deleteDir() returns.
1014 QTimer::singleShot(0, this, [&, newUrl
, location
] {
1016 showMessage(xi18n("Current location changed, <filename>%1</filename> is no longer accessible.", location
), KMessageWidget::Warning
);
1019 showMessage(xi18n("Current location changed, <filename>%1</filename> is no longer accessible.", location
), KMessageWidget::Warning
);
1022 void DolphinViewContainer::slotOpenUrlFinished(KJob
*job
)
1024 if (job
->error() && job
->error() != KIO::ERR_USER_CANCELED
) {
1025 showErrorMessage(job
->errorString());
1029 void DolphinViewContainer::saveViewState()
1031 QByteArray locationState
;
1032 QDataStream
stream(&locationState
, QIODevice::WriteOnly
);
1033 m_view
->saveState(stream
);
1034 m_urlNavigator
->saveLocationState(locationState
);
1037 void DolphinViewContainer::tryRestoreViewState()
1039 QByteArray locationState
= m_urlNavigator
->locationState();
1040 if (!locationState
.isEmpty()) {
1041 QDataStream
stream(&locationState
, QIODevice::ReadOnly
);
1042 m_view
->restoreState(stream
);
1046 QString
DolphinViewContainer::getNearestExistingAncestorOfPath(const QString
&path
) const
1050 dir
.setPath(QDir::cleanPath(dir
.filePath(QStringLiteral(".."))));
1051 } while (!dir
.exists() && !dir
.isRoot());
1053 return dir
.exists() ? dir
.path() : QString
{};
1056 void DolphinViewContainer::updateStatusBarGeometry()
1061 if (GeneralSettings::showStatusBar() == GeneralSettings::EnumShowStatusBar::Small
) {
1062 QRect
statusBarRect(preferredSmallStatusBarGeometry());
1063 if (view()->layoutDirection() == Qt::RightToLeft
) {
1064 const int splitterWidth
= m_statusBar
->clippingAmount();
1065 statusBarRect
.setLeft(rect().width() - m_statusBar
->width() + splitterWidth
); // Add clipping amount.
1067 // Move statusbar to bottomLeft, or bottomRight with right-to-left-layout.
1068 m_statusBar
->setGeometry(statusBarRect
);
1069 // Add 1 due to how qrect coordinates work.
1070 m_view
->setStatusBarOffset(m_statusBar
->geometry().height() - m_statusBar
->clippingAmount() + 1);
1072 m_view
->setStatusBarOffset(0);
1076 bool DolphinViewContainer::eventFilter(QObject
*object
, QEvent
*event
)
1078 if (GeneralSettings::showStatusBar() == GeneralSettings::EnumShowStatusBar::Small
&& object
== m_view
) {
1079 switch (event
->type()) {
1080 case QEvent::Resize
: {
1081 m_statusBar
->updateWidthToContent();
1084 case QEvent::LayoutRequest
: {
1085 m_statusBar
->updateWidthToContent();
1095 QRect
DolphinViewContainer::preferredSmallStatusBarGeometry()
1097 // Add offset depending if horizontal scrollbar or filterbar is visible, we need to add 1 due to how QRect coordinates work.
1098 const int yPos
= m_view
->geometry().bottom() - m_view
->horizontalScrollBarHeight() - m_statusBar
->minimumHeight() + 1;
1099 QRect statusBarRect
= rect().adjusted(0, yPos
, 0, 0);
1100 return statusBarRect
;
1103 #include "moc_dolphinviewcontainer.cpp"