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