]> cloud.milkyroute.net Git - dolphin.git/blob - src/dolphintabwidget.cpp
Tabs: ensure to have folder icons for remote folders
[dolphin.git] / src / dolphintabwidget.cpp
1 /*
2 * SPDX-FileCopyrightText: 2014 Emmanuel Pescosta <emmanuelpescosta099@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
7 #include "dolphintabwidget.h"
8
9 #include "dolphin_generalsettings.h"
10 #include "dolphintabbar.h"
11 #include "dolphinviewcontainer.h"
12
13 #include <KConfigGroup>
14 #include <KShell>
15 #include <kio/global.h>
16 #include <KIO/CommandLauncherJob>
17 #include <KAcceleratorManager>
18
19 #include <QApplication>
20 #include <QDropEvent>
21
22 DolphinTabWidget::DolphinTabWidget(DolphinNavigatorsWidgetAction *navigatorsWidget, QWidget* parent) :
23 QTabWidget(parent),
24 m_lastViewedTab(nullptr),
25 m_navigatorsWidget{navigatorsWidget}
26 {
27 KAcceleratorManager::setNoAccel(this);
28
29 connect(this, &DolphinTabWidget::tabCloseRequested,
30 this, QOverload<int>::of(&DolphinTabWidget::closeTab));
31 connect(this, &DolphinTabWidget::currentChanged,
32 this, &DolphinTabWidget::currentTabChanged);
33
34 DolphinTabBar* tabBar = new DolphinTabBar(this);
35 connect(tabBar, &DolphinTabBar::openNewActivatedTab,
36 this, QOverload<int>::of(&DolphinTabWidget::openNewActivatedTab));
37 connect(tabBar, &DolphinTabBar::tabDropEvent,
38 this, &DolphinTabWidget::tabDropEvent);
39 connect(tabBar, &DolphinTabBar::tabDetachRequested,
40 this, &DolphinTabWidget::detachTab);
41 tabBar->hide();
42
43 setTabBar(tabBar);
44 setDocumentMode(true);
45 setElideMode(Qt::ElideRight);
46 setUsesScrollButtons(true);
47 }
48
49 DolphinTabPage* DolphinTabWidget::currentTabPage() const
50 {
51 return tabPageAt(currentIndex());
52 }
53
54 DolphinTabPage* DolphinTabWidget::nextTabPage() const
55 {
56 const int index = currentIndex() + 1;
57 return tabPageAt(index < count() ? index : 0);
58 }
59
60 DolphinTabPage* DolphinTabWidget::prevTabPage() const
61 {
62 const int index = currentIndex() - 1;
63 return tabPageAt(index >= 0 ? index : (count() - 1));
64 }
65
66 DolphinTabPage* DolphinTabWidget::tabPageAt(const int index) const
67 {
68 return static_cast<DolphinTabPage*>(widget(index));
69 }
70
71 void DolphinTabWidget::saveProperties(KConfigGroup& group) const
72 {
73 const int tabCount = count();
74 group.writeEntry("Tab Count", tabCount);
75 group.writeEntry("Active Tab Index", currentIndex());
76
77 for (int i = 0; i < tabCount; ++i) {
78 const DolphinTabPage* tabPage = tabPageAt(i);
79 group.writeEntry("Tab Data " % QString::number(i), tabPage->saveState());
80 }
81 }
82
83 void DolphinTabWidget::readProperties(const KConfigGroup& group)
84 {
85 const int tabCount = group.readEntry("Tab Count", 0);
86 for (int i = 0; i < tabCount; ++i) {
87 if (i >= count()) {
88 openNewActivatedTab();
89 }
90 const QByteArray state = group.readEntry("Tab Data " % QString::number(i), QByteArray());
91 tabPageAt(i)->restoreState(state);
92 }
93
94 const int index = group.readEntry("Active Tab Index", 0);
95 setCurrentIndex(index);
96 }
97
98 void DolphinTabWidget::refreshViews()
99 {
100 // Left-elision is better when showing full paths, since you care most
101 // about the current directory which is on the right
102 if (GeneralSettings::showFullPathInTitlebar()) {
103 setElideMode(Qt::ElideLeft);
104 } else {
105 setElideMode(Qt::ElideRight);
106 }
107
108 const int tabCount = count();
109 for (int i = 0; i < tabCount; ++i) {
110 tabBar()->setTabText(i, tabName(tabPageAt(i)));
111 tabPageAt(i)->refreshViews();
112 }
113 }
114
115 bool DolphinTabWidget::isUrlOpen(const QUrl &url) const
116 {
117 return indexByUrl(url).first >= 0;
118 }
119
120 void DolphinTabWidget::openNewActivatedTab()
121 {
122 std::unique_ptr<DolphinUrlNavigator::VisualState> oldNavigatorState;
123 if (currentTabPage()->primaryViewActive() || !m_navigatorsWidget->secondaryUrlNavigator()) {
124 oldNavigatorState = m_navigatorsWidget->primaryUrlNavigator()->visualState();
125 } else {
126 oldNavigatorState = m_navigatorsWidget->secondaryUrlNavigator()->visualState();
127 }
128
129 const DolphinViewContainer* oldActiveViewContainer = currentTabPage()->activeViewContainer();
130 Q_ASSERT(oldActiveViewContainer);
131
132 openNewActivatedTab(oldActiveViewContainer->url());
133
134 DolphinViewContainer* newActiveViewContainer = currentTabPage()->activeViewContainer();
135 Q_ASSERT(newActiveViewContainer);
136
137 // The URL navigator of the new tab should have the same editable state
138 // as the current tab
139 newActiveViewContainer->urlNavigator()->setVisualState(*oldNavigatorState.get());
140
141 // Always focus the new tab's view
142 newActiveViewContainer->view()->setFocus();
143 }
144
145 void DolphinTabWidget::openNewActivatedTab(const QUrl& primaryUrl, const QUrl& secondaryUrl)
146 {
147 openNewTab(primaryUrl, secondaryUrl);
148 if (GeneralSettings::openNewTabAfterLastTab()) {
149 setCurrentIndex(count() - 1);
150 } else {
151 setCurrentIndex(currentIndex() + 1);
152 }
153 }
154
155 void DolphinTabWidget::openNewTab(const QUrl& primaryUrl, const QUrl& secondaryUrl)
156 {
157 QWidget* focusWidget = QApplication::focusWidget();
158
159 DolphinTabPage* tabPage = new DolphinTabPage(primaryUrl, secondaryUrl, this);
160 tabPage->setActive(false);
161 connect(tabPage, &DolphinTabPage::activeViewChanged,
162 this, &DolphinTabWidget::activeViewChanged);
163 connect(tabPage, &DolphinTabPage::activeViewUrlChanged,
164 this, &DolphinTabWidget::tabUrlChanged);
165 int newTabIndex = -1;
166 if (!GeneralSettings::openNewTabAfterLastTab()) {
167 newTabIndex = currentIndex() + 1;
168 }
169 insertTab(newTabIndex, tabPage, QIcon() /* loaded in tabInserted */, tabName(tabPage));
170
171 if (focusWidget) {
172 // The DolphinViewContainer grabbed the keyboard focus. As the tab is opened
173 // in background, assure that the previous focused widget gets the focus back.
174 focusWidget->setFocus();
175 }
176 }
177
178 void DolphinTabWidget::openDirectories(const QList<QUrl>& dirs, bool splitView)
179 {
180 Q_ASSERT(dirs.size() > 0);
181
182 bool somethingWasAlreadyOpen = false;
183
184 QList<QUrl>::const_iterator it = dirs.constBegin();
185 while (it != dirs.constEnd()) {
186 const QUrl& primaryUrl = *(it++);
187 const QPair<int, bool> indexInfo = indexByUrl(primaryUrl);
188 const int index = indexInfo.first;
189 const bool isInPrimaryView = indexInfo.second;
190
191 // When the user asks for a URL that's already open, activate it instead
192 // of opening a second copy
193 if (index >= 0) {
194 somethingWasAlreadyOpen = true;
195 activateTab(index);
196 const auto tabPage = tabPageAt(index);
197 if (isInPrimaryView) {
198 tabPage->primaryViewContainer()->setActive(true);
199 } else {
200 tabPage->secondaryViewContainer()->setActive(true);
201 }
202 // BUG: 417230
203 // Required for updateViewState() call in openFiles() to work as expected
204 // If there is a selection, updateViewState() calls are effectively a no-op
205 tabPage->activeViewContainer()->view()->clearSelection();
206 } else if (splitView && (it != dirs.constEnd())) {
207 const QUrl& secondaryUrl = *(it++);
208 if (somethingWasAlreadyOpen) {
209 openNewTab(primaryUrl, secondaryUrl);
210 } else {
211 openNewActivatedTab(primaryUrl, secondaryUrl);
212 }
213 } else {
214 if (somethingWasAlreadyOpen) {
215 openNewTab(primaryUrl);
216 } else {
217 openNewActivatedTab(primaryUrl);
218 }
219 }
220 }
221 }
222
223 void DolphinTabWidget::openFiles(const QList<QUrl>& files, bool splitView)
224 {
225 Q_ASSERT(files.size() > 0);
226
227 // Get all distinct directories from 'files' and open a tab
228 // for each directory. If the "split view" option is enabled, two
229 // directories are shown inside one tab (see openDirectories()).
230 QList<QUrl> dirs;
231 for (const QUrl& url : files) {
232 const QUrl dir(url.adjusted(QUrl::RemoveFilename));
233 if (!dirs.contains(dir)) {
234 dirs.append(dir);
235 }
236 }
237
238 const int oldTabCount = count();
239 openDirectories(dirs, splitView);
240 const int tabCount = count();
241
242 // Select the files. Although the files can be split between several
243 // tabs, there is no need to split 'files' accordingly, as
244 // the DolphinView will just ignore invalid selections.
245 for (int i = 0; i < tabCount; ++i) {
246 DolphinTabPage* tabPage = tabPageAt(i);
247 tabPage->markUrlsAsSelected(files);
248 tabPage->markUrlAsCurrent(files.first());
249 if (i < oldTabCount) {
250 // Force selection of file if directory was already open, BUG: 417230
251 tabPage->activeViewContainer()->view()->updateViewState();
252 }
253 }
254 }
255
256 void DolphinTabWidget::closeTab()
257 {
258 closeTab(currentIndex());
259 }
260
261 void DolphinTabWidget::closeTab(const int index)
262 {
263 Q_ASSERT(index >= 0);
264 Q_ASSERT(index < count());
265
266 if (count() < 2) {
267 // Close Dolphin when closing the last tab.
268 parentWidget()->close();
269 return;
270 }
271
272 DolphinTabPage* tabPage = tabPageAt(index);
273 Q_EMIT rememberClosedTab(tabPage->activeViewContainer()->url(), tabPage->saveState());
274
275 removeTab(index);
276 tabPage->deleteLater();
277 }
278
279 void DolphinTabWidget::activateTab(const int index)
280 {
281 if (index < count()) {
282 setCurrentIndex(index);
283 }
284 }
285
286 void DolphinTabWidget::activateLastTab()
287 {
288 setCurrentIndex(count() - 1);
289 }
290
291 void DolphinTabWidget::activateNextTab()
292 {
293 const int index = currentIndex() + 1;
294 setCurrentIndex(index < count() ? index : 0);
295 }
296
297 void DolphinTabWidget::activatePrevTab()
298 {
299 const int index = currentIndex() - 1;
300 setCurrentIndex(index >= 0 ? index : (count() - 1));
301 }
302
303 void DolphinTabWidget::restoreClosedTab(const QByteArray& state)
304 {
305 openNewActivatedTab();
306 currentTabPage()->restoreState(state);
307 }
308
309 void DolphinTabWidget::copyToInactiveSplitView()
310 {
311 const DolphinTabPage* tabPage = tabPageAt(currentIndex());
312 DolphinViewContainer* activeViewContainer = currentTabPage()->activeViewContainer();
313 if (!tabPage->splitViewEnabled() || activeViewContainer->view()->selectedItems().isEmpty()) {
314 return;
315 }
316
317 if (tabPage->primaryViewActive()) {
318 // copy from left panel to right
319 activeViewContainer->view()->copySelectedItems(activeViewContainer->view()->selectedItems(), tabPage->secondaryViewContainer()->url());
320 } else {
321 // copy from right panel to left
322 activeViewContainer->view()->copySelectedItems(activeViewContainer->view()->selectedItems(), tabPage->primaryViewContainer()->url());
323 }
324 }
325
326 void DolphinTabWidget::moveToInactiveSplitView()
327 {
328 const DolphinTabPage* tabPage = tabPageAt(currentIndex());
329 DolphinViewContainer* activeViewContainer = currentTabPage()->activeViewContainer();
330 if (!tabPage->splitViewEnabled() || activeViewContainer->view()->selectedItems().isEmpty()) {
331 return;
332 }
333
334 if (tabPage->primaryViewActive()) {
335 // move from left panel to right
336 activeViewContainer->view()->moveSelectedItems(activeViewContainer->view()->selectedItems(), tabPage->secondaryViewContainer()->url());
337 } else {
338 // move from right panel to left
339 activeViewContainer->view()->moveSelectedItems(activeViewContainer->view()->selectedItems(), tabPage->primaryViewContainer()->url());
340 }
341 }
342
343 void DolphinTabWidget::detachTab(int index)
344 {
345 Q_ASSERT(index >= 0);
346
347 QStringList args;
348
349 const DolphinTabPage* tabPage = tabPageAt(index);
350 args << tabPage->primaryViewContainer()->url().url();
351 if (tabPage->splitViewEnabled()) {
352 args << tabPage->secondaryViewContainer()->url().url();
353 args << QStringLiteral("--split");
354 }
355 args << QStringLiteral("--new-window");
356
357 KIO::CommandLauncherJob *job = new KIO::CommandLauncherJob("dolphin", args, this);
358 job->setDesktopName(QStringLiteral("org.kde.dolphin"));
359 job->start();
360
361 closeTab(index);
362 }
363
364 void DolphinTabWidget::openNewActivatedTab(int index)
365 {
366 Q_ASSERT(index >= 0);
367 const DolphinTabPage* tabPage = tabPageAt(index);
368 openNewActivatedTab(tabPage->activeViewContainer()->url());
369 }
370
371 void DolphinTabWidget::tabDropEvent(int index, QDropEvent* event)
372 {
373 if (index >= 0) {
374 DolphinView* view = tabPageAt(index)->activeViewContainer()->view();
375 view->dropUrls(view->url(), event, view);
376 }
377 }
378
379 void DolphinTabWidget::tabUrlChanged(const QUrl& url)
380 {
381 const int index = indexOf(qobject_cast<QWidget*>(sender()));
382 if (index >= 0) {
383 tabBar()->setTabText(index, tabName(tabPageAt(index)));
384 tabBar()->setTabToolTip(index, url.toDisplayString(QUrl::PreferLocalFile));
385 if (tabBar()->isVisible()) {
386 // ensure the path url ends with a slash to have proper folder icon for remote folders
387 const QUrl pathUrl = QUrl(url.adjusted(QUrl::StripTrailingSlash).toString(QUrl::FullyEncoded).append("/"));
388 tabBar()->setTabIcon(index, QIcon::fromTheme(KIO::iconNameForUrl(pathUrl)));
389 } else {
390 // Mark as dirty, actually load once the tab bar actually gets shown
391 tabBar()->setTabIcon(index, QIcon());
392 }
393
394 // Emit the currentUrlChanged signal if the url of the current tab has been changed.
395 if (index == currentIndex()) {
396 Q_EMIT currentUrlChanged(url);
397 }
398 }
399 }
400
401 void DolphinTabWidget::currentTabChanged(int index)
402 {
403 DolphinTabPage *tabPage = tabPageAt(index);
404 if (tabPage == m_lastViewedTab) {
405 return;
406 }
407 if (m_lastViewedTab) {
408 m_lastViewedTab->disconnectNavigators();
409 m_lastViewedTab->setActive(false);
410 }
411 if (tabPage->splitViewEnabled() && !m_navigatorsWidget->secondaryUrlNavigator()) {
412 m_navigatorsWidget->createSecondaryUrlNavigator();
413 }
414 DolphinViewContainer* viewContainer = tabPage->activeViewContainer();
415 Q_EMIT activeViewChanged(viewContainer);
416 Q_EMIT currentUrlChanged(viewContainer->url());
417 tabPage->setActive(true);
418 tabPage->connectNavigators(m_navigatorsWidget);
419 m_navigatorsWidget->setSecondaryNavigatorVisible(tabPage->splitViewEnabled());
420 m_lastViewedTab = tabPage;
421 }
422
423 void DolphinTabWidget::tabInserted(int index)
424 {
425 QTabWidget::tabInserted(index);
426
427 if (count() > 1) {
428 // Resolve all pending tab icons
429 for (int i = 0; i < count(); ++i) {
430 const QUrl url = tabPageAt(i)->activeViewContainer()->url();
431 if (tabBar()->tabIcon(i).isNull()) {
432 // ensure the path url ends with a slash to have proper folder icon for remote folders
433 const QUrl pathUrl = QUrl(url.adjusted(QUrl::StripTrailingSlash).toString(QUrl::FullyEncoded).append("/"));
434 tabBar()->setTabIcon(i, QIcon::fromTheme(KIO::iconNameForUrl(pathUrl)));
435 }
436 if (tabBar()->tabToolTip(i).isEmpty()) {
437 tabBar()->setTabToolTip(index, url.toDisplayString(QUrl::PreferLocalFile));
438 }
439 }
440
441 tabBar()->show();
442 }
443
444 Q_EMIT tabCountChanged(count());
445 }
446
447 void DolphinTabWidget::tabRemoved(int index)
448 {
449 QTabWidget::tabRemoved(index);
450
451 // If only one tab is left, then remove the tab entry so that
452 // closing the last tab is not possible.
453 if (count() < 2) {
454 tabBar()->hide();
455 }
456
457 Q_EMIT tabCountChanged(count());
458 }
459
460 QString DolphinTabWidget::tabName(DolphinTabPage* tabPage) const
461 {
462 if (!tabPage) {
463 return QString();
464 }
465 QString name = tabPage->activeViewContainer()->caption();
466 // Make sure that a '&' inside the directory name is displayed correctly
467 // and not misinterpreted as a keyboard shortcut in QTabBar::setTabText()
468 return name.replace('&', QLatin1String("&&"));
469 }
470
471 QPair<int, bool> DolphinTabWidget::indexByUrl(const QUrl& url) const
472 {
473 for (int i = 0; i < count(); i++) {
474 const auto tabPage = tabPageAt(i);
475 if (url == tabPage->primaryViewContainer()->url()) {
476 return qMakePair(i, true);
477 }
478
479 if (tabPage->splitViewEnabled() && url == tabPage->secondaryViewContainer()->url()) {
480 return qMakePair(i, false);
481 }
482 }
483 return qMakePair(-1, false);
484 }