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::readSettings()
527 // The startup settings should (only) get applied if they have been
528 // modified by the user. Otherwise keep the (possibly) different current
529 // setting of the filterbar.
530 if (GeneralSettings::modifiedStartupSettings()) {
531 setFilterBarVisible(GeneralSettings::filterBar());
534 m_view
->readSettings();
535 m_statusBar
->readSettings();
538 bool DolphinViewContainer::isFilterBarVisible() const
540 return m_filterBar
->isEnabled(); // Gets disabled in AnimatedHeightWidget while animating towards a hidden state.
543 QString
DolphinViewContainer::placesText() const
547 if (isSearchBarVisible() && m_searchBar
->isSearchConfigured()) {
548 text
= m_searchBar
->queryTitle();
550 text
= url().adjusted(QUrl::StripTrailingSlash
).fileName();
551 if (text
.isEmpty()) {
554 if (text
.isEmpty()) {
555 text
= url().scheme();
562 void DolphinViewContainer::reload()
565 m_messageWidget
->hide();
568 QString
DolphinViewContainer::captionWindowTitle() const
570 if (GeneralSettings::showFullPathInTitlebar() && (!isSearchBarVisible() || !m_searchBar
->isSearchConfigured())) {
571 if (!url().isLocalFile()) {
572 return url().adjusted(QUrl::StripTrailingSlash
).toString();
574 return url().adjusted(QUrl::StripTrailingSlash
).path();
576 return DolphinViewContainer::caption();
580 QString
DolphinViewContainer::caption() const
582 // see KUrlNavigatorPrivate::firstButtonText().
583 if (url().path().isEmpty() || url().path() == QLatin1Char('/')) {
584 QUrlQuery
query(url());
585 const QString title
= query
.queryItemValue(QStringLiteral("title"), QUrl::FullyDecoded
);
586 if (!title
.isEmpty()) {
591 if (isSearchBarVisible() && m_searchBar
->isSearchConfigured()) {
592 return m_searchBar
->queryTitle();
595 KFilePlacesModel
*placesModel
= DolphinPlacesModelSingleton::instance().placesModel();
597 QModelIndex url_index
= placesModel
->closestItem(url());
599 if (url_index
.isValid() && placesModel
->url(url_index
).matches(url(), QUrl::StripTrailingSlash
)) {
600 return placesModel
->text(url_index
);
603 if (!url().isLocalFile()) {
604 QUrl adjustedUrl
= url().adjusted(QUrl::StripTrailingSlash
);
606 if (!adjustedUrl
.fileName().isEmpty()) {
607 caption
= adjustedUrl
.fileName();
608 } else if (!adjustedUrl
.path().isEmpty() && adjustedUrl
.path() != "/") {
609 caption
= adjustedUrl
.path();
610 } else if (!adjustedUrl
.host().isEmpty()) {
611 caption
= adjustedUrl
.host();
613 caption
= adjustedUrl
.toString();
618 QString fileName
= url().adjusted(QUrl::StripTrailingSlash
).fileName();
619 if (fileName
.isEmpty()) {
626 void DolphinViewContainer::setUrl(const QUrl
&newUrl
)
628 if (newUrl
!= m_urlNavigator
->locationUrl()) {
629 m_urlNavigator
->setLocationUrl(newUrl
);
630 if (m_searchBar
&& !Search::isSupportedSearchScheme(newUrl
.scheme())) {
631 m_searchBar
->setSearchPath(newUrl
);
636 void DolphinViewContainer::setFilterBarVisible(bool visible
)
638 Q_ASSERT(m_filterBar
);
640 m_view
->hideToolTip(ToolTipManager::HideBehavior::Instantly
);
641 m_filterBar
->setVisible(true, WithAnimation
);
642 m_filterBar
->setFocus();
643 m_filterBar
->selectAll();
644 Q_EMIT
showFilterBarChanged(true);
650 void DolphinViewContainer::delayedStatusBarUpdate()
652 if (m_statusBarTimer
->isActive() && (m_statusBarTimestamp
.elapsed() > 2000)) {
653 // No update of the statusbar has been done during the last 2 seconds,
654 // although an update has been requested. Trigger an immediate update.
655 m_statusBarTimer
->stop();
658 // Invoke updateStatusBar() with a small delay. This assures that
659 // when a lot of delayedStatusBarUpdates() are done in a short time,
660 // no bottleneck is given.
661 m_statusBarTimer
->start();
665 void DolphinViewContainer::updateStatusBar()
667 m_statusBarTimestamp
.start();
668 m_view
->requestStatusBarText();
669 updateStatusBarGeometry();
672 void DolphinViewContainer::slotDirectoryLoadingStarted()
674 if (isSearchUrl(url())) {
675 // Search KIO-slaves usually don't provide any progress information. Give
676 // a hint to the user that a searching is done:
678 m_statusBar
->showProgress(i18nc("@info", "Searching…"), -1);
680 // Trigger an undetermined progress indication. The progress
681 // information in percent will be triggered by the percent() signal
682 // of the directory lister later.
683 m_statusBar
->showProgress(QString(), -1);
686 if (m_urlNavigatorConnected
) {
687 m_urlNavigatorConnected
->setReadOnlyBadgeVisible(false);
691 void DolphinViewContainer::slotDirectoryLoadingCompleted()
693 m_statusBar
->showProgress(QString(), 100);
695 if (isSearchUrl(url()) && m_view
->itemsCount() == 0) {
696 // The dir lister has been completed on a Baloo-URI and no items have been found. Instead
697 // of showing the default status bar information ("0 items") a more helpful information is given:
698 m_statusBar
->setText(i18nc("@info:status", "No items found."));
703 if (m_urlNavigatorConnected
) {
704 m_urlNavigatorConnected
->setReadOnlyBadgeVisible(rootItem().isLocalFile() && !rootItem().isWritable());
707 // Update admin bar visibility
708 if (m_view
->url().scheme() == QStringLiteral("admin")) {
710 m_adminBar
= new Admin::Bar(this);
711 m_topLayout
->addWidget(m_adminBar
, positionFor
.adminBar
, 0);
713 m_adminBar
->setVisible(true, WithAnimation
);
714 } else if (m_adminBar
) {
715 m_adminBar
->setVisible(false, WithAnimation
);
719 void DolphinViewContainer::slotDirectoryLoadingCanceled()
721 m_statusBar
->showProgress(QString(), 100);
722 m_statusBar
->setText(QString());
725 void DolphinViewContainer::slotUrlIsFileError(const QUrl
&url
)
727 const KFileItem
item(url
);
729 // Find out if the file can be opened in the view (for example, this is the
730 // case if the file is an archive). The mime type must be known for that.
731 item
.determineMimeType();
732 const QUrl
&folderUrl
= DolphinView::openItemAsFolderUrl(item
, true);
733 if (!folderUrl
.isEmpty()) {
736 slotItemActivated(item
);
740 void DolphinViewContainer::slotItemActivated(const KFileItem
&item
)
742 // It is possible to activate items on inactive views by
743 // drag & drop operations. Assure that activating an item always
744 // results in an active view.
745 m_view
->setActive(true);
747 const QUrl
&url
= DolphinView::openItemAsFolderUrl(item
, GeneralSettings::browseThroughArchives());
748 if (!url
.isEmpty()) {
749 const auto modifiers
= QGuiApplication::keyboardModifiers();
750 // keep in sync with KUrlNavigator::slotNavigatorButtonClicked
751 if (modifiers
& Qt::ControlModifier
&& modifiers
& Qt::ShiftModifier
) {
752 Q_EMIT
activeTabRequested(url
);
753 } else if (modifiers
& Qt::ControlModifier
) {
754 Q_EMIT
tabRequested(url
);
755 } else if (modifiers
& Qt::ShiftModifier
) {
756 Dolphin::openNewWindow({KFilePlacesModel::convertedUrl(url
)}, this);
763 KIO::OpenUrlJob
*job
= new KIO::OpenUrlJob(item
.targetUrl(), item
.mimetype());
764 // Auto*Warning*Handling, errors are put in a KMessageWidget by us in slotOpenUrlFinished.
765 job
->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoWarningHandlingEnabled
, this));
766 job
->setShowOpenOrExecuteDialog(true);
767 connect(job
, &KIO::OpenUrlJob::finished
, this, &DolphinViewContainer::slotOpenUrlFinished
);
771 void DolphinViewContainer::slotfileMiddleClickActivated(const KFileItem
&item
)
773 KService::List services
= KApplicationTrader::queryByMimeType(item
.mimetype());
775 int indexOfAppToOpenFileWith
= 1;
777 // executable scripts
778 auto mimeType
= item
.currentMimeType();
779 if (item
.isLocalFile() && mimeType
.inherits(QStringLiteral("application/x-executable")) && mimeType
.inherits(QStringLiteral("text/plain"))
780 && QFileInfo(item
.localPath()).isExecutable()) {
781 KConfigGroup
cfgGroup(KSharedConfig::openConfig(QStringLiteral("kiorc")), QStringLiteral("Executable scripts"));
782 const QString value
= cfgGroup
.readEntry("behaviourOnLaunch", "alwaysAsk");
784 // in case KIO::WidgetsOpenOrExecuteFileHandler::promptUserOpenOrExecute would not open the file
785 if (value
!= QLatin1String("open")) {
786 indexOfAppToOpenFileWith
= 0;
790 if (services
.length() >= indexOfAppToOpenFileWith
+ 1) {
791 auto service
= services
.at(indexOfAppToOpenFileWith
);
793 KIO::ApplicationLauncherJob
*job
= new KIO::ApplicationLauncherJob(service
, this);
794 job
->setUrls({item
.url()});
796 job
->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled
, this));
797 connect(job
, &KIO::OpenUrlJob::finished
, this, &DolphinViewContainer::slotOpenUrlFinished
);
800 // If no 2nd service available, try to open archives in new tabs, regardless of the "Open archives as folder" setting.
801 const QUrl
&url
= DolphinView::openItemAsFolderUrl(item
);
802 const auto modifiers
= QGuiApplication::keyboardModifiers();
803 if (!url
.isEmpty()) {
804 // keep in sync with KUrlNavigator::slotNavigatorButtonClicked
805 if (modifiers
& Qt::ShiftModifier
) {
806 Q_EMIT
activeTabRequested(url
);
808 Q_EMIT
tabRequested(url
);
814 void DolphinViewContainer::slotItemsActivated(const KFileItemList
&items
)
816 Q_ASSERT(items
.count() >= 2);
818 KFileItemActions
fileItemActions(this);
819 fileItemActions
.runPreferredApplications(items
);
822 void DolphinViewContainer::showItemInfo(const KFileItem
&item
)
825 m_statusBar
->resetToDefaultText();
827 m_statusBar
->setText(item
.getStatusBarInfo());
831 void DolphinViewContainer::closeFilterBar()
833 m_filterBar
->closeFilterBar();
835 Q_EMIT
showFilterBarChanged(false);
838 void DolphinViewContainer::clearFilterBar()
840 m_filterBar
->clearIfUnlocked();
843 void DolphinViewContainer::setNameFilter(const QString
&nameFilter
)
845 m_view
->hideToolTip(ToolTipManager::HideBehavior::Instantly
);
846 m_view
->setNameFilter(nameFilter
);
847 delayedStatusBarUpdate();
850 void DolphinViewContainer::activate()
855 void DolphinViewContainer::slotUrlNavigatorLocationAboutToBeChanged(const QUrl
&)
860 void DolphinViewContainer::slotUrlNavigatorLocationChanged(const QUrl
&url
)
862 if (m_urlNavigatorConnected
) {
863 m_urlNavigatorConnected
->slotReturnPressed();
866 if (KProtocolManager::supportsListing(url
)) {
867 if (isSearchUrl(url
)) {
868 setSearchBarVisible(true);
869 } else if (m_searchBar
&& m_searchBar
->isSearchConfigured()) {
870 // Hide the search bar because it shows an outdated search which the user does not care about anymore.
871 setSearchBarVisible(false);
875 tryRestoreViewState();
877 if (m_grabFocusOnUrlChange
&& isActive()) {
878 // When an URL has been entered, the view should get the focus.
879 // The focus must be requested asynchronously, as changing the URL might create
880 // a new view widget.
881 QTimer::singleShot(0, this, &DolphinViewContainer::requestFocus
);
883 } else if (KProtocolManager::isSourceProtocol(url
)) {
884 if (url
.scheme().startsWith(QLatin1String("http"))) {
885 showMessage(i18nc("@info:status", // krazy:exclude=qmethods
886 "Dolphin does not support web pages, the web browser has been launched"),
887 KMessageWidget::Information
);
889 showMessage(i18nc("@info:status", "Protocol not supported by Dolphin, default application has been launched"), KMessageWidget::Information
);
892 QDesktopServices::openUrl(url
);
893 redirect(QUrl(), m_urlNavigator
->locationUrl(1));
895 if (!url
.scheme().isEmpty()) {
896 showMessage(i18nc("@info:status", "Invalid protocol '%1'", url
.scheme()), KMessageWidget::Error
);
898 showMessage(i18nc("@info:status", "Invalid protocol"), KMessageWidget::Error
);
900 m_urlNavigator
->goBack();
904 void DolphinViewContainer::slotUrlSelectionRequested(const QUrl
&url
)
906 // 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
908 m_view
->markUrlAsCurrent(url
); // makes the item scroll into view
911 void DolphinViewContainer::disableUrlNavigatorSelectionRequests()
913 disconnect(m_urlNavigator
.get(), &KUrlNavigator::urlSelectionRequested
, this, &DolphinViewContainer::slotUrlSelectionRequested
);
916 void DolphinViewContainer::enableUrlNavigatorSelectionRequests()
918 connect(m_urlNavigator
.get(), &KUrlNavigator::urlSelectionRequested
, this, &DolphinViewContainer::slotUrlSelectionRequested
);
921 void DolphinViewContainer::redirect(const QUrl
&oldUrl
, const QUrl
&newUrl
)
924 const bool block
= m_urlNavigator
->signalsBlocked();
925 m_urlNavigator
->blockSignals(true);
927 // Assure that the location state is reset for redirection URLs. This
928 // allows to skip redirection URLs when going back or forward in the
930 m_urlNavigator
->saveLocationState(QByteArray());
931 m_urlNavigator
->setLocationUrl(newUrl
);
932 setSearchBarVisible(isSearchUrl(newUrl
));
934 m_urlNavigator
->blockSignals(block
);
937 void DolphinViewContainer::requestFocus()
942 void DolphinViewContainer::stopDirectoryLoading()
944 m_view
->stopLoading();
945 m_statusBar
->showProgress(QString(), 100);
948 void DolphinViewContainer::slotStatusBarZoomLevelChanged(int zoomLevel
)
950 m_view
->setZoomLevel(zoomLevel
);
953 void DolphinViewContainer::slotErrorMessageFromView(const QString
&message
, const int kioErrorCode
)
955 if (kioErrorCode
== KIO::ERR_CANNOT_ENTER_DIRECTORY
&& m_view
->url().scheme() == QStringLiteral("file")
956 && KProtocolInfo::isKnownProtocol(QStringLiteral("admin")) && !rootItem().isReadable()) {
957 // Explain to users that they need authentication to see the folder contents.
958 if (!m_authorizeToEnterFolderAction
) { // This code is similar to parts of Admin::Bar::hideTheNextTimeAuthorizationExpires().
959 // We should not simply use the actAsAdminAction() itself here because that one always refers to the active view instead of this->m_view.
960 auto actAsAdminAction
= Admin::WorkerIntegration::FriendAccess::actAsAdminAction();
961 m_authorizeToEnterFolderAction
= new QAction
{actAsAdminAction
->icon(), actAsAdminAction
->text(), this};
962 m_authorizeToEnterFolderAction
->setToolTip(actAsAdminAction
->toolTip());
963 m_authorizeToEnterFolderAction
->setWhatsThis(actAsAdminAction
->whatsThis());
964 connect(m_authorizeToEnterFolderAction
, &QAction::triggered
, this, [this, actAsAdminAction
]() {
966 actAsAdminAction
->trigger();
969 showMessage(i18nc("@info", "Authorization required to enter this folder."), KMessageWidget::Error
, {m_authorizeToEnterFolderAction
});
972 Q_EMIT
showErrorMessage(message
);
975 void DolphinViewContainer::showErrorMessage(const QString
&message
)
977 showMessage(message
, KMessageWidget::Error
);
980 void DolphinViewContainer::slotPlacesModelChanged()
982 if (!GeneralSettings::showFullPathInTitlebar()) {
983 Q_EMIT
captionChanged();
987 void DolphinViewContainer::slotHiddenFilesShownChanged(bool showHiddenFiles
)
989 if (m_urlNavigatorConnected
) {
990 m_urlNavigatorConnected
->setShowHiddenFolders(showHiddenFiles
);
994 void DolphinViewContainer::slotSortHiddenLastChanged(bool hiddenLast
)
996 if (m_urlNavigatorConnected
) {
997 m_urlNavigatorConnected
->setSortHiddenFoldersLast(hiddenLast
);
1001 void DolphinViewContainer::slotCurrentDirectoryRemoved()
1003 const QString
location(url().toDisplayString(QUrl::PreferLocalFile
));
1004 if (url().isLocalFile()) {
1005 const QString dirPath
= url().toLocalFile();
1006 const QString newPath
= getNearestExistingAncestorOfPath(dirPath
);
1007 const QUrl newUrl
= QUrl::fromLocalFile(newPath
);
1008 // #473377: Delay changing the url to avoid modifying KCoreDirLister before KCoreDirListerCache::deleteDir() returns.
1009 QTimer::singleShot(0, this, [&, newUrl
, location
] {
1011 showMessage(xi18n("Current location changed, <filename>%1</filename> is no longer accessible.", location
), KMessageWidget::Warning
);
1014 showMessage(xi18n("Current location changed, <filename>%1</filename> is no longer accessible.", location
), KMessageWidget::Warning
);
1017 void DolphinViewContainer::slotOpenUrlFinished(KJob
*job
)
1019 if (job
->error() && job
->error() != KIO::ERR_USER_CANCELED
) {
1020 showErrorMessage(job
->errorString());
1024 void DolphinViewContainer::saveViewState()
1026 QByteArray locationState
;
1027 QDataStream
stream(&locationState
, QIODevice::WriteOnly
);
1028 m_view
->saveState(stream
);
1029 m_urlNavigator
->saveLocationState(locationState
);
1032 void DolphinViewContainer::tryRestoreViewState()
1034 QByteArray locationState
= m_urlNavigator
->locationState();
1035 if (!locationState
.isEmpty()) {
1036 QDataStream
stream(&locationState
, QIODevice::ReadOnly
);
1037 m_view
->restoreState(stream
);
1041 QString
DolphinViewContainer::getNearestExistingAncestorOfPath(const QString
&path
) const
1045 dir
.setPath(QDir::cleanPath(dir
.filePath(QStringLiteral(".."))));
1046 } while (!dir
.exists() && !dir
.isRoot());
1048 return dir
.exists() ? dir
.path() : QString
{};
1051 void DolphinViewContainer::updateStatusBarGeometry()
1056 if (GeneralSettings::showStatusBar() == GeneralSettings::EnumShowStatusBar::Small
) {
1057 QRect
statusBarRect(preferredSmallStatusBarGeometry());
1058 if (view()->layoutDirection() == Qt::RightToLeft
) {
1059 const int splitterWidth
= m_statusBar
->clippingAmount();
1060 statusBarRect
.setLeft(rect().width() - m_statusBar
->width() + splitterWidth
); // Add clipping amount.
1062 // Move statusbar to bottomLeft, or bottomRight with right-to-left-layout.
1063 m_statusBar
->setGeometry(statusBarRect
);
1064 // Add 1 due to how qrect coordinates work.
1065 m_view
->setStatusBarOffset(m_statusBar
->geometry().height() - m_statusBar
->clippingAmount() + 1);
1067 m_view
->setStatusBarOffset(0);
1071 bool DolphinViewContainer::eventFilter(QObject
*object
, QEvent
*event
)
1073 if (GeneralSettings::showStatusBar() == GeneralSettings::EnumShowStatusBar::Small
&& object
== m_view
) {
1074 switch (event
->type()) {
1075 case QEvent::Resize
: {
1076 m_statusBar
->updateWidthToContent();
1079 case QEvent::LayoutRequest
: {
1080 m_statusBar
->updateWidthToContent();
1090 QRect
DolphinViewContainer::preferredSmallStatusBarGeometry()
1092 // Adjust to clipping, we need to add 1 due to how QRects coordinates work.
1093 int clipAdjustment
= m_statusBar
->clippingAmount() + 1;
1094 // Add offset depending if horizontal scrollbar or filterbar is visible.
1095 const int yPos
= m_view
->geometry().bottom() - m_view
->horizontalScrollBarHeight() - m_statusBar
->minimumHeight() + clipAdjustment
;
1096 QRect statusBarRect
= rect().adjusted(-clipAdjustment
, yPos
, 0, 0);
1097 return statusBarRect
;
1100 #include "moc_dolphinviewcontainer.cpp"