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