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