]> cloud.milkyroute.net Git - dolphin.git/blob - src/dolphinviewcontainer.cpp
ce91dbfe85a52cc5f337d3660628bfcdda4e812b
[dolphin.git] / src / dolphinviewcontainer.cpp
1 /*
2 * SPDX-FileCopyrightText: 2007 Peter Penz <peter.penz19@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
7 #include "dolphinviewcontainer.h"
8
9 #include "admin/bar.h"
10 #include "dolphin_compactmodesettings.h"
11 #include "dolphin_contentdisplaysettings.h"
12 #include "dolphin_detailsmodesettings.h"
13 #include "dolphin_generalsettings.h"
14 #include "dolphin_iconsmodesettings.h"
15 #include "dolphindebug.h"
16 #include "dolphinplacesmodelsingleton.h"
17 #include "filterbar/filterbar.h"
18 #include "global.h"
19 #include "search/dolphinsearchbox.h"
20 #include "selectionmode/topbar.h"
21 #include "statusbar/dolphinstatusbar.h"
22
23 #include <KActionCollection>
24 #include <KApplicationTrader>
25 #include <KFileItemActions>
26 #include <KFilePlacesModel>
27 #include <KIO/JobUiDelegateFactory>
28 #include <KIO/OpenUrlJob>
29 #include <KLocalizedString>
30 #include <KMessageWidget>
31 #include <KProtocolManager>
32 #include <KShell>
33 #include <kio_version.h>
34
35 #include <QApplication>
36 #include <QDesktopServices>
37 #include <QDropEvent>
38 #include <QGridLayout>
39 #include <QGuiApplication>
40 #include <QRegularExpression>
41 #include <QTimer>
42 #include <QUrl>
43
44 // An overview of the widgets contained by this ViewContainer
45 struct LayoutStructure {
46 int searchBox = 0;
47 int adminBar = 1;
48 int messageWidget = 2;
49 int selectionModeTopBar = 3;
50 int view = 4;
51 int selectionModeBottomBar = 5;
52 int filterBar = 6;
53 int statusBar = 7;
54 };
55 constexpr LayoutStructure positionFor;
56
57 DolphinViewContainer::DolphinViewContainer(const QUrl &url, QWidget *parent)
58 : QWidget(parent)
59 , m_topLayout(nullptr)
60 , m_urlNavigator{new DolphinUrlNavigator(url)}
61 , m_urlNavigatorConnected{nullptr}
62 , m_searchBox(nullptr)
63 , m_searchModeEnabled(false)
64 , m_adminBar{nullptr}
65 , m_messageWidget(nullptr)
66 , m_selectionModeTopBar{nullptr}
67 , m_view(nullptr)
68 , m_filterBar(nullptr)
69 , m_selectionModeBottomBar{nullptr}
70 , m_statusBar(nullptr)
71 , m_statusBarTimer(nullptr)
72 , m_statusBarTimestamp()
73 , m_autoGrabFocus(true)
74 {
75 hide();
76
77 m_topLayout = new QGridLayout(this);
78 m_topLayout->setSpacing(0);
79 m_topLayout->setContentsMargins(0, 0, 0, 0);
80
81 m_searchBox = new DolphinSearchBox(this);
82 m_searchBox->setVisible(false, WithoutAnimation);
83 connect(m_searchBox, &DolphinSearchBox::activated, this, &DolphinViewContainer::activate);
84 connect(m_searchBox, &DolphinSearchBox::openRequest, this, &DolphinViewContainer::openSearchBox);
85 connect(m_searchBox, &DolphinSearchBox::closeRequest, this, &DolphinViewContainer::closeSearchBox);
86 connect(m_searchBox, &DolphinSearchBox::searchRequest, this, &DolphinViewContainer::startSearching);
87 connect(m_searchBox, &DolphinSearchBox::focusViewRequest, this, &DolphinViewContainer::requestFocus);
88 m_searchBox->setWhatsThis(xi18nc("@info:whatsthis findbar",
89 "<para>This helps you find files and folders. Enter a <emphasis>"
90 "search term</emphasis> and specify search settings with the "
91 "buttons at the bottom:<list><item>Filename/Content: "
92 "Does the item you are looking for contain the search terms "
93 "within its filename or its contents?<nl/>The contents of images, "
94 "audio files and videos will not be searched.</item><item>"
95 "From Here/Everywhere: Do you want to search in this "
96 "folder and its sub-folders or everywhere?</item><item>"
97 "More Options: Click this to search by media type, access "
98 "time or rating.</item><item>More Search Tools: Install other "
99 "means to find an item.</item></list></para>"));
100
101 m_messageWidget = new KMessageWidget(this);
102 m_messageWidget->setCloseButtonVisible(true);
103 m_messageWidget->setPosition(KMessageWidget::Header);
104 m_messageWidget->hide();
105
106 #if !defined(Q_OS_WIN) && !defined(Q_OS_HAIKU)
107 if (getuid() == 0) {
108 // We must be logged in as the root user; show a big scary warning
109 showMessage(i18n("Running Dolphin as root can be dangerous. Please be careful."), KMessageWidget::Warning);
110 }
111 #endif
112
113 // Initialize filter bar
114 m_filterBar = new FilterBar(this);
115 m_filterBar->setVisible(GeneralSettings::filterBar(), WithoutAnimation);
116
117 connect(m_filterBar, &FilterBar::filterChanged, this, &DolphinViewContainer::setNameFilter);
118 connect(m_filterBar, &FilterBar::closeRequest, this, &DolphinViewContainer::closeFilterBar);
119 connect(m_filterBar, &FilterBar::focusViewRequest, this, &DolphinViewContainer::requestFocus);
120
121 // Initialize the main view
122 m_view = new DolphinView(url, this);
123 connect(m_view, &DolphinView::urlChanged, m_filterBar, &FilterBar::clearIfUnlocked);
124 connect(m_view, &DolphinView::urlChanged, m_messageWidget, &KMessageWidget::hide);
125 // m_urlNavigator stays in sync with m_view's location changes and
126 // keeps track of them so going back and forth in the history works.
127 connect(m_view, &DolphinView::urlChanged, m_urlNavigator.get(), &DolphinUrlNavigator::setLocationUrl);
128 connect(m_urlNavigator.get(), &DolphinUrlNavigator::urlChanged, this, &DolphinViewContainer::slotUrlNavigatorLocationChanged);
129 connect(m_urlNavigator.get(), &DolphinUrlNavigator::urlAboutToBeChanged, this, &DolphinViewContainer::slotUrlNavigatorLocationAboutToBeChanged);
130 connect(m_urlNavigator.get(), &DolphinUrlNavigator::urlSelectionRequested, this, &DolphinViewContainer::slotUrlSelectionRequested);
131 connect(m_view, &DolphinView::writeStateChanged, this, &DolphinViewContainer::writeStateChanged);
132 connect(m_view, &DolphinView::requestItemInfo, this, &DolphinViewContainer::showItemInfo);
133 connect(m_view, &DolphinView::itemActivated, this, &DolphinViewContainer::slotItemActivated);
134 connect(m_view, &DolphinView::fileMiddleClickActivated, this, &DolphinViewContainer::slotfileMiddleClickActivated);
135 connect(m_view, &DolphinView::itemsActivated, this, &DolphinViewContainer::slotItemsActivated);
136 connect(m_view, &DolphinView::redirection, this, &DolphinViewContainer::redirect);
137 connect(m_view, &DolphinView::directoryLoadingStarted, this, &DolphinViewContainer::slotDirectoryLoadingStarted);
138 connect(m_view, &DolphinView::directoryLoadingCompleted, this, &DolphinViewContainer::slotDirectoryLoadingCompleted);
139 connect(m_view, &DolphinView::directoryLoadingCanceled, this, &DolphinViewContainer::slotDirectoryLoadingCanceled);
140 connect(m_view, &DolphinView::itemCountChanged, this, &DolphinViewContainer::delayedStatusBarUpdate);
141 connect(m_view, &DolphinView::selectionChanged, this, &DolphinViewContainer::delayedStatusBarUpdate);
142 connect(m_view, &DolphinView::errorMessage, this, &DolphinViewContainer::showErrorMessage);
143 connect(m_view, &DolphinView::urlIsFileError, this, &DolphinViewContainer::slotUrlIsFileError);
144 connect(m_view, &DolphinView::activated, this, &DolphinViewContainer::activate);
145 connect(m_view, &DolphinView::hiddenFilesShownChanged, this, &DolphinViewContainer::slotHiddenFilesShownChanged);
146 connect(m_view, &DolphinView::sortHiddenLastChanged, this, &DolphinViewContainer::slotSortHiddenLastChanged);
147 connect(m_view, &DolphinView::currentDirectoryRemoved, this, &DolphinViewContainer::slotCurrentDirectoryRemoved);
148 connect(m_view, &DolphinView::urlChanged, this, &DolphinViewContainer::updateAdminBarVisibility);
149
150 // Initialize status bar
151 m_statusBar = new DolphinStatusBar(this);
152 m_statusBar->setUrl(m_view->url());
153 m_statusBar->setZoomLevel(m_view->zoomLevel());
154 connect(m_view, &DolphinView::urlChanged, m_statusBar, &DolphinStatusBar::setUrl);
155 connect(m_view, &DolphinView::zoomLevelChanged, m_statusBar, &DolphinStatusBar::setZoomLevel);
156 connect(m_view, &DolphinView::infoMessage, m_statusBar, &DolphinStatusBar::setText);
157 connect(m_view, &DolphinView::operationCompletedMessage, m_statusBar, &DolphinStatusBar::setText);
158 connect(m_view, &DolphinView::statusBarTextChanged, m_statusBar, &DolphinStatusBar::setDefaultText);
159 connect(m_view, &DolphinView::statusBarTextChanged, m_statusBar, &DolphinStatusBar::resetToDefaultText);
160 connect(m_view, &DolphinView::directoryLoadingProgress, m_statusBar, [this](int percent) {
161 m_statusBar->showProgress(i18nc("@info:progress", "Loading folder…"), percent);
162 });
163 connect(m_view, &DolphinView::directorySortingProgress, m_statusBar, [this](int percent) {
164 m_statusBar->showProgress(i18nc("@info:progress", "Sorting…"), percent);
165 });
166 connect(m_statusBar, &DolphinStatusBar::stopPressed, this, &DolphinViewContainer::stopDirectoryLoading);
167 connect(m_statusBar, &DolphinStatusBar::zoomLevelChanged, this, &DolphinViewContainer::slotStatusBarZoomLevelChanged);
168
169 m_statusBarTimer = new QTimer(this);
170 m_statusBarTimer->setSingleShot(true);
171 m_statusBarTimer->setInterval(300);
172 connect(m_statusBarTimer, &QTimer::timeout, this, &DolphinViewContainer::updateStatusBar);
173
174 KIO::FileUndoManager *undoManager = KIO::FileUndoManager::self();
175 connect(undoManager, &KIO::FileUndoManager::jobRecordingFinished, this, &DolphinViewContainer::delayedStatusBarUpdate);
176
177 m_topLayout->addWidget(m_searchBox, positionFor.searchBox, 0);
178 m_topLayout->addWidget(m_messageWidget, positionFor.messageWidget, 0);
179 m_topLayout->addWidget(m_view, positionFor.view, 0);
180 m_topLayout->addWidget(m_filterBar, positionFor.filterBar, 0);
181 m_topLayout->addWidget(m_statusBar, positionFor.statusBar, 0);
182
183 setSearchModeEnabled(isSearchUrl(url));
184 updateAdminBarVisibility(url);
185
186 // Update view as the ContentDisplaySettings change
187 // this happens here and not in DolphinView as DolphinviewContainer and DolphinView are not in the same build target ATM
188 connect(ContentDisplaySettings::self(), &KCoreConfigSkeleton::configChanged, m_view, &DolphinView::reload);
189
190 KFilePlacesModel *placesModel = DolphinPlacesModelSingleton::instance().placesModel();
191 connect(placesModel, &KFilePlacesModel::dataChanged, this, &DolphinViewContainer::slotPlacesModelChanged);
192 connect(placesModel, &KFilePlacesModel::rowsInserted, this, &DolphinViewContainer::slotPlacesModelChanged);
193 connect(placesModel, &KFilePlacesModel::rowsRemoved, this, &DolphinViewContainer::slotPlacesModelChanged);
194
195 connect(this, &DolphinViewContainer::searchModeEnabledChanged, this, &DolphinViewContainer::captionChanged);
196 }
197
198 DolphinViewContainer::~DolphinViewContainer()
199 {
200 }
201
202 QUrl DolphinViewContainer::url() const
203 {
204 return m_view->url();
205 }
206
207 KFileItem DolphinViewContainer::rootItem() const
208 {
209 return m_view->rootItem();
210 }
211
212 void DolphinViewContainer::setActive(bool active)
213 {
214 m_searchBox->setActive(active);
215 if (m_urlNavigatorConnected) {
216 m_urlNavigatorConnected->setActive(active);
217 }
218 m_view->setActive(active);
219 }
220
221 bool DolphinViewContainer::isActive() const
222 {
223 return m_view->isActive();
224 }
225
226 void DolphinViewContainer::setAutoGrabFocus(bool grab)
227 {
228 m_autoGrabFocus = grab;
229 }
230
231 bool DolphinViewContainer::autoGrabFocus() const
232 {
233 return m_autoGrabFocus;
234 }
235
236 QString DolphinViewContainer::currentSearchText() const
237 {
238 return m_searchBox->text();
239 }
240
241 const DolphinStatusBar *DolphinViewContainer::statusBar() const
242 {
243 return m_statusBar;
244 }
245
246 DolphinStatusBar *DolphinViewContainer::statusBar()
247 {
248 return m_statusBar;
249 }
250
251 const DolphinUrlNavigator *DolphinViewContainer::urlNavigator() const
252 {
253 return m_urlNavigatorConnected;
254 }
255
256 DolphinUrlNavigator *DolphinViewContainer::urlNavigator()
257 {
258 return m_urlNavigatorConnected;
259 }
260
261 const DolphinUrlNavigator *DolphinViewContainer::urlNavigatorInternalWithHistory() const
262 {
263 return m_urlNavigator.get();
264 }
265
266 DolphinUrlNavigator *DolphinViewContainer::urlNavigatorInternalWithHistory()
267 {
268 return m_urlNavigator.get();
269 }
270
271 const DolphinView *DolphinViewContainer::view() const
272 {
273 return m_view;
274 }
275
276 DolphinView *DolphinViewContainer::view()
277 {
278 return m_view;
279 }
280
281 void DolphinViewContainer::connectUrlNavigator(DolphinUrlNavigator *urlNavigator)
282 {
283 Q_CHECK_PTR(urlNavigator);
284 Q_ASSERT(!m_urlNavigatorConnected);
285 Q_ASSERT(m_urlNavigator.get() != urlNavigator);
286 Q_CHECK_PTR(m_view);
287
288 urlNavigator->setLocationUrl(m_view->url());
289 urlNavigator->setShowHiddenFolders(m_view->hiddenFilesShown());
290 urlNavigator->setSortHiddenFoldersLast(m_view->sortHiddenLast());
291 if (m_urlNavigatorVisualState) {
292 urlNavigator->setVisualState(*m_urlNavigatorVisualState.get());
293 m_urlNavigatorVisualState.reset();
294 }
295 urlNavigator->setActive(isActive());
296
297 // Url changes are still done via m_urlNavigator.
298 connect(urlNavigator, &DolphinUrlNavigator::urlChanged, m_urlNavigator.get(), &DolphinUrlNavigator::setLocationUrl);
299 connect(urlNavigator, &DolphinUrlNavigator::urlsDropped, this, [=](const QUrl &destination, QDropEvent *event) {
300 m_view->dropUrls(destination, event, urlNavigator->dropWidget());
301 });
302 // Aside from these, only visual things need to be connected.
303 connect(m_view, &DolphinView::urlChanged, urlNavigator, &DolphinUrlNavigator::setLocationUrl);
304 connect(urlNavigator, &DolphinUrlNavigator::activated, this, &DolphinViewContainer::activate);
305
306 urlNavigator->setReadOnlyBadgeVisible(rootItem().isLocalFile() && !rootItem().isWritable());
307
308 m_urlNavigatorConnected = urlNavigator;
309 }
310
311 void DolphinViewContainer::disconnectUrlNavigator()
312 {
313 if (!m_urlNavigatorConnected) {
314 return;
315 }
316
317 disconnect(m_urlNavigatorConnected, &DolphinUrlNavigator::urlChanged, m_urlNavigator.get(), &DolphinUrlNavigator::setLocationUrl);
318 disconnect(m_urlNavigatorConnected, &DolphinUrlNavigator::urlsDropped, this, nullptr);
319 disconnect(m_view, &DolphinView::urlChanged, m_urlNavigatorConnected, &DolphinUrlNavigator::setLocationUrl);
320 disconnect(m_urlNavigatorConnected, &DolphinUrlNavigator::activated, this, &DolphinViewContainer::activate);
321
322 m_urlNavigatorVisualState = m_urlNavigatorConnected->visualState();
323 m_urlNavigatorConnected = nullptr;
324 }
325
326 void DolphinViewContainer::setSelectionModeEnabled(bool enabled, KActionCollection *actionCollection, SelectionMode::BottomBar::Contents bottomBarContents)
327 {
328 const bool wasEnabled = m_view->selectionMode();
329 m_view->setSelectionModeEnabled(enabled);
330
331 if (!enabled) {
332 if (!wasEnabled) {
333 return; // nothing to do here
334 }
335 Q_CHECK_PTR(m_selectionModeTopBar); // there is no point in disabling selectionMode when it wasn't even enabled once.
336 Q_CHECK_PTR(m_selectionModeBottomBar);
337 m_selectionModeTopBar->setVisible(false, WithAnimation);
338 m_selectionModeBottomBar->setVisible(false, WithAnimation);
339 Q_EMIT selectionModeChanged(false);
340
341 if (!QApplication::focusWidget() || m_selectionModeTopBar->isAncestorOf(QApplication::focusWidget())
342 || m_selectionModeBottomBar->isAncestorOf(QApplication::focusWidget())) {
343 m_view->setFocus();
344 }
345 return;
346 }
347
348 if (!m_selectionModeTopBar) {
349 // Changing the location will disable selection mode.
350 connect(m_urlNavigator.get(), &DolphinUrlNavigator::urlChanged, this, [this]() {
351 setSelectionModeEnabled(false);
352 });
353
354 m_selectionModeTopBar = new SelectionMode::TopBar(this); // will be created hidden
355 connect(m_selectionModeTopBar, &SelectionMode::TopBar::selectionModeLeavingRequested, this, [this]() {
356 setSelectionModeEnabled(false);
357 });
358 m_topLayout->addWidget(m_selectionModeTopBar, positionFor.selectionModeTopBar, 0);
359 }
360
361 if (!m_selectionModeBottomBar) {
362 m_selectionModeBottomBar = new SelectionMode::BottomBar(actionCollection, this);
363 connect(m_view, &DolphinView::selectionChanged, this, [this](const KFileItemList &selection) {
364 m_selectionModeBottomBar->slotSelectionChanged(selection, m_view->url());
365 });
366 connect(m_selectionModeBottomBar, &SelectionMode::BottomBar::error, this, &DolphinViewContainer::showErrorMessage);
367 connect(m_selectionModeBottomBar, &SelectionMode::BottomBar::selectionModeLeavingRequested, this, [this]() {
368 setSelectionModeEnabled(false);
369 });
370 m_topLayout->addWidget(m_selectionModeBottomBar, positionFor.selectionModeBottomBar, 0);
371 }
372 m_selectionModeBottomBar->resetContents(bottomBarContents);
373 if (bottomBarContents == SelectionMode::BottomBar::GeneralContents) {
374 m_selectionModeBottomBar->slotSelectionChanged(m_view->selectedItems(), m_view->url());
375 }
376
377 if (!wasEnabled) {
378 m_selectionModeTopBar->setVisible(true, WithAnimation);
379 m_selectionModeBottomBar->setVisible(true, WithAnimation);
380 Q_EMIT selectionModeChanged(true);
381 }
382 }
383
384 bool DolphinViewContainer::isSelectionModeEnabled() const
385 {
386 const bool isEnabled = m_view->selectionMode();
387 Q_ASSERT((!isEnabled
388 // We can't assert that the bars are invisible only because the selection mode is disabled because the hide animation might still be playing.
389 && (!m_selectionModeBottomBar || !m_selectionModeBottomBar->isEnabled() || !m_selectionModeBottomBar->isVisible()
390 || m_selectionModeBottomBar->contents() == SelectionMode::BottomBar::PasteContents))
391 || (isEnabled && m_selectionModeTopBar
392 && m_selectionModeTopBar->isVisible()
393 // 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.
394 && m_selectionModeBottomBar
395 && (m_selectionModeBottomBar->isVisible() || m_selectionModeBottomBar->contents() == SelectionMode::BottomBar::GeneralContents)));
396 return isEnabled;
397 }
398
399 void DolphinViewContainer::slotSplitTabDisabled()
400 {
401 if (m_selectionModeBottomBar) {
402 m_selectionModeBottomBar->slotSplitTabDisabled();
403 }
404 }
405
406 void DolphinViewContainer::showMessage(const QString &message, KMessageWidget::MessageType messageType)
407 {
408 if (message.isEmpty()) {
409 return;
410 }
411
412 m_messageWidget->setText(message);
413
414 // TODO: wrap at arbitrary character positions once QLabel can do this
415 // https://bugreports.qt.io/browse/QTBUG-1276
416 m_messageWidget->setWordWrap(true);
417 m_messageWidget->setMessageType(messageType);
418
419 m_messageWidget->setWordWrap(false);
420 const int unwrappedWidth = m_messageWidget->sizeHint().width();
421 m_messageWidget->setWordWrap(unwrappedWidth > size().width());
422
423 if (m_messageWidget->isVisible()) {
424 m_messageWidget->hide();
425 }
426 m_messageWidget->animatedShow();
427 }
428
429 void DolphinViewContainer::readSettings()
430 {
431 // The startup settings should (only) get applied if they have been
432 // modified by the user. Otherwise keep the (possibly) different current
433 // setting of the filterbar.
434 if (GeneralSettings::modifiedStartupSettings()) {
435 setFilterBarVisible(GeneralSettings::filterBar());
436 }
437
438 m_view->readSettings();
439 m_statusBar->readSettings();
440 }
441
442 bool DolphinViewContainer::isFilterBarVisible() const
443 {
444 return m_filterBar->isEnabled(); // Gets disabled in AnimatedHeightWidget while animating towards a hidden state.
445 }
446
447 void DolphinViewContainer::setSearchModeEnabled(bool enabled)
448 {
449 m_searchBox->setVisible(enabled, WithAnimation);
450
451 if (enabled) {
452 const QUrl &locationUrl = m_urlNavigator->locationUrl();
453 m_searchBox->fromSearchUrl(locationUrl);
454 }
455
456 if (enabled == isSearchModeEnabled()) {
457 if (enabled && !m_searchBox->hasFocus()) {
458 m_searchBox->setFocus();
459 m_searchBox->selectAll();
460 }
461 return;
462 }
463
464 if (!enabled) {
465 m_view->setViewPropertiesContext(QString());
466
467 // Restore the URL for the URL navigator. If Dolphin has been
468 // started with a search-URL, the home URL is used as fallback.
469 QUrl url = m_searchBox->searchPath();
470 if (url.isEmpty() || !url.isValid() || isSearchUrl(url)) {
471 url = Dolphin::homeUrl();
472 }
473 m_urlNavigatorConnected->setLocationUrl(url);
474 }
475
476 m_searchModeEnabled = enabled;
477
478 Q_EMIT searchModeEnabledChanged(enabled);
479 }
480
481 bool DolphinViewContainer::isSearchModeEnabled() const
482 {
483 return m_searchModeEnabled;
484 }
485
486 QString DolphinViewContainer::placesText() const
487 {
488 QString text;
489
490 if (isSearchModeEnabled()) {
491 text = i18n("Search for %1 in %2", m_searchBox->text(), m_searchBox->searchPath().fileName());
492 } else {
493 text = url().adjusted(QUrl::StripTrailingSlash).fileName();
494 if (text.isEmpty()) {
495 text = url().host();
496 }
497 if (text.isEmpty()) {
498 text = url().scheme();
499 }
500 }
501
502 return text;
503 }
504
505 void DolphinViewContainer::reload()
506 {
507 view()->reload();
508 m_messageWidget->hide();
509 }
510
511 QString DolphinViewContainer::captionWindowTitle() const
512 {
513 if (GeneralSettings::showFullPathInTitlebar() && !isSearchModeEnabled()) {
514 if (!url().isLocalFile()) {
515 return url().adjusted(QUrl::StripTrailingSlash).toString();
516 }
517 return url().adjusted(QUrl::StripTrailingSlash).path();
518 } else {
519 return DolphinViewContainer::caption();
520 }
521 }
522
523 QString DolphinViewContainer::caption() const
524 {
525 if (isSearchModeEnabled()) {
526 if (currentSearchText().isEmpty()) {
527 return i18n("Search");
528 } else {
529 return i18n("Search for %1", currentSearchText());
530 }
531 }
532
533 KFilePlacesModel *placesModel = DolphinPlacesModelSingleton::instance().placesModel();
534 const QString pattern = url().adjusted(QUrl::StripTrailingSlash).toString(QUrl::FullyEncoded).append("/?");
535 const auto &matchedPlaces =
536 placesModel->match(placesModel->index(0, 0), KFilePlacesModel::UrlRole, QRegularExpression::anchoredPattern(pattern), 1, Qt::MatchRegularExpression);
537
538 if (!matchedPlaces.isEmpty()) {
539 return placesModel->text(matchedPlaces.first());
540 }
541
542 if (!url().isLocalFile()) {
543 QUrl adjustedUrl = url().adjusted(QUrl::StripTrailingSlash);
544 QString caption;
545 if (!adjustedUrl.fileName().isEmpty()) {
546 caption = adjustedUrl.fileName();
547 } else if (!adjustedUrl.path().isEmpty() && adjustedUrl.path() != "/") {
548 caption = adjustedUrl.path();
549 } else if (!adjustedUrl.host().isEmpty()) {
550 caption = adjustedUrl.host();
551 } else {
552 caption = adjustedUrl.toString();
553 }
554 return caption;
555 }
556
557 QString fileName = url().adjusted(QUrl::StripTrailingSlash).fileName();
558 if (fileName.isEmpty()) {
559 fileName = '/';
560 }
561
562 return fileName;
563 }
564
565 void DolphinViewContainer::setUrl(const QUrl &newUrl)
566 {
567 if (newUrl != m_urlNavigator->locationUrl()) {
568 m_urlNavigator->setLocationUrl(newUrl);
569 }
570 }
571
572 void DolphinViewContainer::setFilterBarVisible(bool visible)
573 {
574 Q_ASSERT(m_filterBar);
575 if (visible) {
576 m_view->hideToolTip(ToolTipManager::HideBehavior::Instantly);
577 m_filterBar->setVisible(true, WithAnimation);
578 m_filterBar->setFocus();
579 m_filterBar->selectAll();
580 } else {
581 closeFilterBar();
582 }
583 }
584
585 void DolphinViewContainer::delayedStatusBarUpdate()
586 {
587 if (m_statusBarTimer->isActive() && (m_statusBarTimestamp.elapsed() > 2000)) {
588 // No update of the statusbar has been done during the last 2 seconds,
589 // although an update has been requested. Trigger an immediate update.
590 m_statusBarTimer->stop();
591 updateStatusBar();
592 } else {
593 // Invoke updateStatusBar() with a small delay. This assures that
594 // when a lot of delayedStatusBarUpdates() are done in a short time,
595 // no bottleneck is given.
596 m_statusBarTimer->start();
597 }
598 }
599
600 void DolphinViewContainer::updateStatusBar()
601 {
602 m_statusBarTimestamp.start();
603 m_view->requestStatusBarText();
604 }
605
606 void DolphinViewContainer::slotDirectoryLoadingStarted()
607 {
608 if (isSearchUrl(url())) {
609 // Search KIO-slaves usually don't provide any progress information. Give
610 // a hint to the user that a searching is done:
611 updateStatusBar();
612 m_statusBar->showProgress(i18nc("@info", "Searching…"), -1);
613 } else {
614 // Trigger an undetermined progress indication. The progress
615 // information in percent will be triggered by the percent() signal
616 // of the directory lister later.
617 m_statusBar->showProgress(QString(), -1);
618 }
619
620 if (m_urlNavigatorConnected) {
621 m_urlNavigatorConnected->setReadOnlyBadgeVisible(false);
622 }
623 }
624
625 void DolphinViewContainer::slotDirectoryLoadingCompleted()
626 {
627 m_statusBar->showProgress(QString(), 100);
628
629 if (isSearchUrl(url()) && m_view->itemsCount() == 0) {
630 // The dir lister has been completed on a Baloo-URI and no items have been found. Instead
631 // of showing the default status bar information ("0 items") a more helpful information is given:
632 m_statusBar->setText(i18nc("@info:status", "No items found."));
633 } else {
634 updateStatusBar();
635 }
636
637 if (m_urlNavigatorConnected) {
638 m_urlNavigatorConnected->setReadOnlyBadgeVisible(rootItem().isLocalFile() && !rootItem().isWritable());
639 }
640 }
641
642 void DolphinViewContainer::slotDirectoryLoadingCanceled()
643 {
644 m_statusBar->showProgress(QString(), 100);
645 m_statusBar->setText(QString());
646 }
647
648 void DolphinViewContainer::slotUrlIsFileError(const QUrl &url)
649 {
650 const KFileItem item(url);
651
652 // Find out if the file can be opened in the view (for example, this is the
653 // case if the file is an archive). The mime type must be known for that.
654 item.determineMimeType();
655 const QUrl &folderUrl = DolphinView::openItemAsFolderUrl(item, true);
656 if (!folderUrl.isEmpty()) {
657 setUrl(folderUrl);
658 } else {
659 slotItemActivated(item);
660 }
661 }
662
663 void DolphinViewContainer::slotItemActivated(const KFileItem &item)
664 {
665 // It is possible to activate items on inactive views by
666 // drag & drop operations. Assure that activating an item always
667 // results in an active view.
668 m_view->setActive(true);
669
670 const QUrl &url = DolphinView::openItemAsFolderUrl(item, GeneralSettings::browseThroughArchives());
671 if (!url.isEmpty()) {
672 const auto modifiers = QGuiApplication::keyboardModifiers();
673 // keep in sync with KUrlNavigator::slotNavigatorButtonClicked
674 if (modifiers & Qt::ControlModifier && modifiers & Qt::ShiftModifier) {
675 Q_EMIT activeTabRequested(url);
676 } else if (modifiers & Qt::ControlModifier) {
677 Q_EMIT tabRequested(url);
678 } else if (modifiers & Qt::ShiftModifier) {
679 Dolphin::openNewWindow({KFilePlacesModel::convertedUrl(url)}, this);
680 } else {
681 setUrl(url);
682 }
683 return;
684 }
685
686 KIO::OpenUrlJob *job = new KIO::OpenUrlJob(item.targetUrl(), item.mimetype());
687 // Auto*Warning*Handling, errors are put in a KMessageWidget by us in slotOpenUrlFinished.
688 job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoWarningHandlingEnabled, this));
689 job->setShowOpenOrExecuteDialog(true);
690 connect(job, &KIO::OpenUrlJob::finished, this, &DolphinViewContainer::slotOpenUrlFinished);
691 job->start();
692 }
693
694 void DolphinViewContainer::slotfileMiddleClickActivated(const KFileItem &item)
695 {
696 KService::List services = KApplicationTrader::queryByMimeType(item.mimetype());
697
698 int indexOfAppToOpenFileWith = 1;
699
700 // executable scripts
701 auto mimeType = item.currentMimeType();
702 if (item.isLocalFile() && mimeType.inherits(QStringLiteral("application/x-executable")) && mimeType.inherits(QStringLiteral("text/plain"))
703 && QFileInfo(item.localPath()).isExecutable()) {
704 KConfigGroup cfgGroup(KSharedConfig::openConfig(QStringLiteral("kiorc")), QStringLiteral("Executable scripts"));
705 const QString value = cfgGroup.readEntry("behaviourOnLaunch", "alwaysAsk");
706
707 // in case KIO::WidgetsOpenOrExecuteFileHandler::promptUserOpenOrExecute would not open the file
708 if (value != QLatin1String("open")) {
709 indexOfAppToOpenFileWith = 0;
710 }
711 }
712
713 if (services.length() >= indexOfAppToOpenFileWith + 1) {
714 auto service = services.at(indexOfAppToOpenFileWith);
715
716 KIO::ApplicationLauncherJob *job = new KIO::ApplicationLauncherJob(service, this);
717 job->setUrls({item.url()});
718
719 job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, this));
720 connect(job, &KIO::OpenUrlJob::finished, this, &DolphinViewContainer::slotOpenUrlFinished);
721 job->start();
722 }
723 }
724
725 void DolphinViewContainer::slotItemsActivated(const KFileItemList &items)
726 {
727 Q_ASSERT(items.count() >= 2);
728
729 KFileItemActions fileItemActions(this);
730 fileItemActions.runPreferredApplications(items);
731 }
732
733 void DolphinViewContainer::showItemInfo(const KFileItem &item)
734 {
735 if (item.isNull()) {
736 m_statusBar->resetToDefaultText();
737 } else {
738 m_statusBar->setText(item.getStatusBarInfo());
739 }
740 }
741
742 void DolphinViewContainer::updateAdminBarVisibility(const QUrl &url)
743 {
744 if (url.scheme() == QStringLiteral("admin")) {
745 if (!m_adminBar) {
746 m_adminBar = new Admin::Bar(this);
747 m_topLayout->addWidget(m_adminBar, positionFor.adminBar, 0);
748 connect(m_adminBar, &Admin::Bar::activated, this, &DolphinViewContainer::activate);
749 }
750 m_adminBar->setVisible(true, WithAnimation);
751 } else if (m_adminBar) {
752 m_adminBar->setVisible(false, WithAnimation);
753 }
754 }
755
756 void DolphinViewContainer::closeFilterBar()
757 {
758 m_filterBar->closeFilterBar();
759 m_view->setFocus();
760 Q_EMIT showFilterBarChanged(false);
761 }
762
763 void DolphinViewContainer::clearFilterBar()
764 {
765 m_filterBar->clearIfUnlocked();
766 }
767
768 void DolphinViewContainer::setNameFilter(const QString &nameFilter)
769 {
770 m_view->hideToolTip(ToolTipManager::HideBehavior::Instantly);
771 m_view->setNameFilter(nameFilter);
772 delayedStatusBarUpdate();
773 }
774
775 void DolphinViewContainer::activate()
776 {
777 setActive(true);
778 }
779
780 void DolphinViewContainer::slotUrlNavigatorLocationAboutToBeChanged(const QUrl &)
781 {
782 saveViewState();
783 }
784
785 void DolphinViewContainer::slotUrlNavigatorLocationChanged(const QUrl &url)
786 {
787 if (m_urlNavigatorConnected) {
788 m_urlNavigatorConnected->slotReturnPressed();
789 }
790
791 if (KProtocolManager::supportsListing(url)) {
792 const bool searchBoxInitialized = isSearchModeEnabled() && m_searchBox->text().isEmpty();
793 setSearchModeEnabled(isSearchUrl(url) || searchBoxInitialized);
794
795 m_view->setUrl(url);
796 tryRestoreViewState();
797
798 if (m_autoGrabFocus && isActive() && !isSearchModeEnabled()) {
799 // When an URL has been entered, the view should get the focus.
800 // The focus must be requested asynchronously, as changing the URL might create
801 // a new view widget.
802 QTimer::singleShot(0, this, &DolphinViewContainer::requestFocus);
803 }
804 } else if (KProtocolManager::isSourceProtocol(url)) {
805 if (url.scheme().startsWith(QLatin1String("http"))) {
806 showMessage(i18nc("@info:status", // krazy:exclude=qmethods
807 "Dolphin does not support web pages, the web browser has been launched"),
808 KMessageWidget::Information);
809 } else {
810 showMessage(i18nc("@info:status", "Protocol not supported by Dolphin, default application has been launched"), KMessageWidget::Information);
811 }
812
813 QDesktopServices::openUrl(url);
814 redirect(QUrl(), m_urlNavigator->locationUrl(1));
815 } else {
816 if (!url.scheme().isEmpty()) {
817 showMessage(i18nc("@info:status", "Invalid protocol '%1'", url.scheme()), KMessageWidget::Error);
818 } else {
819 showMessage(i18nc("@info:status", "Invalid protocol"), KMessageWidget::Error);
820 }
821 m_urlNavigator->goBack();
822 }
823 }
824
825 void DolphinViewContainer::slotUrlSelectionRequested(const QUrl &url)
826 {
827 // 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
828
829 m_view->markUrlAsCurrent(url); // makes the item scroll into view
830 }
831
832 void DolphinViewContainer::disableUrlNavigatorSelectionRequests()
833 {
834 disconnect(m_urlNavigator.get(), &KUrlNavigator::urlSelectionRequested, this, &DolphinViewContainer::slotUrlSelectionRequested);
835 }
836
837 void DolphinViewContainer::enableUrlNavigatorSelectionRequests()
838 {
839 connect(m_urlNavigator.get(), &KUrlNavigator::urlSelectionRequested, this, &DolphinViewContainer::slotUrlSelectionRequested);
840 }
841
842 void DolphinViewContainer::redirect(const QUrl &oldUrl, const QUrl &newUrl)
843 {
844 Q_UNUSED(oldUrl)
845 const bool block = m_urlNavigator->signalsBlocked();
846 m_urlNavigator->blockSignals(true);
847
848 // Assure that the location state is reset for redirection URLs. This
849 // allows to skip redirection URLs when going back or forward in the
850 // URL history.
851 m_urlNavigator->saveLocationState(QByteArray());
852 m_urlNavigator->setLocationUrl(newUrl);
853 setSearchModeEnabled(isSearchUrl(newUrl));
854
855 m_urlNavigator->blockSignals(block);
856 }
857
858 void DolphinViewContainer::requestFocus()
859 {
860 m_view->setFocus();
861 }
862
863 void DolphinViewContainer::startSearching()
864 {
865 Q_CHECK_PTR(m_urlNavigatorConnected);
866 const QUrl url = m_searchBox->urlForSearching();
867 if (url.isValid() && !url.isEmpty()) {
868 m_view->setViewPropertiesContext(QStringLiteral("search"));
869 m_urlNavigatorConnected->setLocationUrl(url);
870 }
871 }
872
873 void DolphinViewContainer::openSearchBox()
874 {
875 setSearchModeEnabled(true);
876 }
877
878 void DolphinViewContainer::closeSearchBox()
879 {
880 setSearchModeEnabled(false);
881 }
882
883 void DolphinViewContainer::stopDirectoryLoading()
884 {
885 m_view->stopLoading();
886 m_statusBar->showProgress(QString(), 100);
887 }
888
889 void DolphinViewContainer::slotStatusBarZoomLevelChanged(int zoomLevel)
890 {
891 m_view->setZoomLevel(zoomLevel);
892 }
893
894 void DolphinViewContainer::showErrorMessage(const QString &message)
895 {
896 showMessage(message, KMessageWidget::Error);
897 }
898
899 void DolphinViewContainer::slotPlacesModelChanged()
900 {
901 if (!GeneralSettings::showFullPathInTitlebar() && !isSearchModeEnabled()) {
902 Q_EMIT captionChanged();
903 }
904 }
905
906 void DolphinViewContainer::slotHiddenFilesShownChanged(bool showHiddenFiles)
907 {
908 if (m_urlNavigatorConnected) {
909 m_urlNavigatorConnected->setShowHiddenFolders(showHiddenFiles);
910 }
911 }
912
913 void DolphinViewContainer::slotSortHiddenLastChanged(bool hiddenLast)
914 {
915 if (m_urlNavigatorConnected) {
916 m_urlNavigatorConnected->setSortHiddenFoldersLast(hiddenLast);
917 }
918 }
919
920 void DolphinViewContainer::slotCurrentDirectoryRemoved()
921 {
922 const QString location(url().toDisplayString(QUrl::PreferLocalFile));
923 if (url().isLocalFile()) {
924 const QString dirPath = url().toLocalFile();
925 const QString newPath = getNearestExistingAncestorOfPath(dirPath);
926 const QUrl newUrl = QUrl::fromLocalFile(newPath);
927 setUrl(newUrl);
928 }
929
930 showMessage(xi18n("Current location changed, <filename>%1</filename> is no longer accessible.", location), KMessageWidget::Warning);
931 }
932
933 void DolphinViewContainer::slotOpenUrlFinished(KJob *job)
934 {
935 if (job->error() && job->error() != KIO::ERR_USER_CANCELED) {
936 showErrorMessage(job->errorString());
937 }
938 }
939
940 bool DolphinViewContainer::isSearchUrl(const QUrl &url) const
941 {
942 return url.scheme().contains(QLatin1String("search"));
943 }
944
945 void DolphinViewContainer::saveViewState()
946 {
947 QByteArray locationState;
948 QDataStream stream(&locationState, QIODevice::WriteOnly);
949 m_view->saveState(stream);
950 m_urlNavigator->saveLocationState(locationState);
951 }
952
953 void DolphinViewContainer::tryRestoreViewState()
954 {
955 QByteArray locationState = m_urlNavigator->locationState();
956 if (!locationState.isEmpty()) {
957 QDataStream stream(&locationState, QIODevice::ReadOnly);
958 m_view->restoreState(stream);
959 }
960 }
961
962 QString DolphinViewContainer::getNearestExistingAncestorOfPath(const QString &path) const
963 {
964 QDir dir(path);
965 do {
966 dir.setPath(QDir::cleanPath(dir.filePath(QStringLiteral(".."))));
967 } while (!dir.exists() && !dir.isRoot());
968
969 return dir.exists() ? dir.path() : QString{};
970 }
971
972 #include "moc_dolphinviewcontainer.cpp"