File indexing completed on 2024-04-28 05:52:03
0001 /* 0002 SPDX-FileCopyrightText: 2002 Shie Erlich <erlich@users.sourceforge.net> 0003 SPDX-FileCopyrightText: 2002 Rafi Yanai <yanai@users.sourceforge.net> 0004 SPDX-FileCopyrightText: 2004-2022 Krusader Krew <https://krusader.org> 0005 0006 SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include "panelmanager.h" 0010 0011 #include "Panel/PanelView/krview.h" 0012 #include "Panel/PanelView/krviewfactory.h" 0013 #include "Panel/listpanel.h" 0014 #include "Panel/panelfunc.h" 0015 #include "defaults.h" 0016 #include "icon.h" 0017 #include "kractions.h" 0018 #include "krmainwindow.h" 0019 #include "krusaderview.h" 0020 #include "tabactions.h" 0021 0022 #include <assert.h> 0023 0024 // QtGui 0025 #include <QImage> 0026 // QtWidgets 0027 #include <QGridLayout> 0028 #include <QMenu> 0029 #include <QStackedWidget> 0030 #include <QToolButton> 0031 0032 #include <KConfigCore/KConfig> 0033 #include <KI18n/KLocalizedString> 0034 0035 PanelManager::PanelManager(QWidget *parent, KrMainWindow *mainWindow, bool left) 0036 : QWidget(parent) 0037 , _otherManager(nullptr) 0038 , _actions(mainWindow->tabActions()) 0039 , _layout(nullptr) 0040 , _left(left) 0041 , _currentPanel(nullptr) 0042 { 0043 _layout = new QGridLayout(this); 0044 _layout->setContentsMargins(0, 0, 0, 0); 0045 _layout->setSpacing(0); 0046 _stack = new QStackedWidget(this); 0047 0048 // new tab button 0049 _newTab = new QToolButton(this); 0050 _newTab->setAutoRaise(true); 0051 _newTab->setText(i18n("Open a new tab")); 0052 _newTab->setToolTip(i18n("Open a new tab")); 0053 _newTab->setIcon(Icon("tab-new")); 0054 _newTab->adjustSize(); 0055 connect(_newTab, &QToolButton::clicked, this, &PanelManager::slotNewTabButton); 0056 0057 // tab-bar 0058 _tabbar = new PanelTabBar(this, _actions); 0059 connect(_tabbar, &PanelTabBar::currentChanged, this, &PanelManager::slotCurrentTabChanged); 0060 connect(_tabbar, &PanelTabBar::tabCloseRequested, this, QOverload<int>::of(&PanelManager::slotCloseTab)); 0061 connect(_tabbar, &PanelTabBar::closeCurrentTab, this, QOverload<>::of(&PanelManager::slotCloseTab)); 0062 connect(_tabbar, &PanelTabBar::duplicateCurrentTab, this, &PanelManager::slotDuplicateTabLMB, Qt::QueuedConnection); 0063 connect(_tabbar, &PanelTabBar::newTab, this, [=](const QUrl &url) { 0064 slotNewTab(url); 0065 }); 0066 connect(_tabbar, &PanelTabBar::draggingTab, this, &PanelManager::slotDraggingTab); 0067 connect(_tabbar, &PanelTabBar::draggingTabFinished, this, &PanelManager::slotDraggingTabFinished); 0068 0069 auto *tabbarLayout = new QHBoxLayout; 0070 tabbarLayout->setSpacing(0); 0071 tabbarLayout->setContentsMargins(0, 0, 0, 0); 0072 0073 tabbarLayout->addWidget(_tabbar); 0074 tabbarLayout->addWidget(_newTab); 0075 0076 _layout->addWidget(_stack, 0, 0); 0077 _layout->addLayout(tabbarLayout, 1, 0); 0078 0079 updateTabbarPos(); 0080 0081 setLayout(_layout); 0082 0083 addPanel(true); 0084 0085 tabsCountChanged(); 0086 } 0087 0088 void PanelManager::tabsCountChanged() 0089 { 0090 const KConfigGroup cfg(krConfig, "Look&Feel"); 0091 const bool showTabbar = _tabbar->count() > 1 || cfg.readEntry("Show Tab Bar On Single Tab", true); 0092 const bool showNewButton = showTabbar && cfg.readEntry("Show New Tab Button", true); 0093 const bool showCloseButtons = showTabbar && cfg.readEntry("Show Close Tab Buttons", true); 0094 0095 _tabbar->setVisible(showTabbar); 0096 _newTab->setVisible(showNewButton); 0097 0098 // disable close button if only 1 tab is left 0099 _tabbar->setTabsClosable(showCloseButtons && _tabbar->count() > 1); 0100 0101 _actions->refreshActions(); 0102 } 0103 0104 void PanelManager::activate() 0105 { 0106 assert(sender() == (currentPanel()->gui)); 0107 emit setActiveManager(this); 0108 _actions->refreshActions(); 0109 } 0110 0111 void PanelManager::slotCurrentTabChanged(int index) 0112 { 0113 ListPanel *panel = _tabbar->getPanel(index); 0114 0115 if (!panel || panel == _currentPanel) 0116 return; 0117 0118 ListPanel *previousPanel = _currentPanel; 0119 _currentPanel = panel; 0120 0121 _stack->setCurrentWidget(_currentPanel); 0122 0123 if (previousPanel) { 0124 previousPanel->slotFocusOnMe(false); // FIXME - necessary ? 0125 } 0126 _currentPanel->slotFocusOnMe(this == ACTIVE_MNG); 0127 0128 emit pathChanged(panel); 0129 0130 if (otherManager()) { 0131 otherManager()->currentPanel()->otherPanelChanged(); 0132 } 0133 0134 // go back to pinned url if tab is pinned and switched back to active 0135 if (panel->isPinned()) { 0136 panel->func->openUrl(panel->pinnedUrl()); 0137 } 0138 } 0139 0140 ListPanel *PanelManager::createPanel(const KConfigGroup &cfg) 0141 { 0142 ListPanel *p = new ListPanel(_stack, this, cfg); 0143 connectPanel(p); 0144 return p; 0145 } 0146 0147 void PanelManager::connectPanel(ListPanel *p) 0148 { 0149 connect(p, &ListPanel::activate, this, &PanelManager::activate); 0150 connect(p, &ListPanel::pathChanged, this, [=]() { 0151 emit pathChanged(p); 0152 }); 0153 connect(p, &ListPanel::pathChanged, this, [=]() { 0154 _tabbar->updateTab(p); 0155 }); 0156 } 0157 0158 void PanelManager::disconnectPanel(ListPanel *p) 0159 { 0160 disconnect(p, &ListPanel::activate, this, nullptr); 0161 disconnect(p, &ListPanel::pathChanged, this, nullptr); 0162 } 0163 0164 ListPanel *PanelManager::addPanel(bool setCurrent, const KConfigGroup &cfg, int insertIndex) 0165 { 0166 // create the panel and add it into the widgetstack 0167 ListPanel *p = createPanel(cfg); 0168 _stack->addWidget(p); 0169 0170 // now, create the corresponding tab 0171 int index = _tabbar->addPanel(p, setCurrent, insertIndex); 0172 tabsCountChanged(); 0173 0174 if (setCurrent) 0175 slotCurrentTabChanged(index); 0176 0177 return p; 0178 } 0179 0180 ListPanel *PanelManager::duplicatePanel(const KConfigGroup &cfg, KrPanel *nextTo, int insertIndex) 0181 { 0182 // Search for the position where the passed panel is 0183 if (insertIndex == -1) { 0184 int quantOfPanels = _tabbar->count(); 0185 for (int i = 0; i < quantOfPanels; i++) { 0186 if (_tabbar->getPanel(i) == nextTo) { 0187 insertIndex = i + 1; 0188 break; 0189 } 0190 } 0191 } 0192 0193 return addPanel(true, cfg, insertIndex); 0194 } 0195 0196 void PanelManager::saveSettings(KConfigGroup config, bool saveHistory) 0197 { 0198 config.writeEntry("ActiveTab", activeTab()); 0199 0200 KConfigGroup grpTabs(&config, "Tabs"); 0201 foreach (const QString &grpTab, grpTabs.groupList()) 0202 grpTabs.deleteGroup(grpTab); 0203 0204 for (int i = 0; i < _tabbar->count(); i++) { 0205 ListPanel *panel = _tabbar->getPanel(i); 0206 KConfigGroup grpTab(&grpTabs, "Tab" + QString::number(i)); 0207 panel->saveSettings(grpTab, saveHistory); 0208 } 0209 } 0210 0211 void PanelManager::loadSettings(KConfigGroup config) 0212 { 0213 KConfigGroup grpTabs(&config, "Tabs"); 0214 int numTabsOld = _tabbar->count(); 0215 int numTabsNew = grpTabs.groupList().count(); 0216 0217 for (int i = 0; i < numTabsNew; i++) { 0218 KConfigGroup grpTab(&grpTabs, "Tab" + QString::number(i)); 0219 // TODO workaround for bug 371453. Remove this when bug is fixed 0220 if (grpTab.keyList().isEmpty()) 0221 continue; 0222 0223 ListPanel *panel = i < numTabsOld ? _tabbar->getPanel(i) : addPanel(false, grpTab, i); 0224 panel->restoreSettings(grpTab); 0225 _tabbar->updateTab(panel); 0226 } 0227 0228 for (int i = numTabsOld - 1; i >= numTabsNew && i > 0; i--) 0229 slotCloseTab(i); 0230 0231 setActiveTab(config.readEntry("ActiveTab", 0)); 0232 0233 // this is needed so that all tab labels get updated 0234 layoutTabs(); 0235 } 0236 0237 void PanelManager::layoutTabs() 0238 { 0239 // delayed url refreshes may be pending - 0240 // delay the layout too so it happens after them 0241 QTimer::singleShot(0, _tabbar, &PanelTabBar::layoutTabs); 0242 } 0243 0244 KrPanel *PanelManager::currentPanel() const 0245 { 0246 return _currentPanel; 0247 } 0248 0249 void PanelManager::moveTabToOtherSide() 0250 { 0251 if (tabCount() < 2) 0252 return; 0253 0254 ListPanel *p; 0255 _tabbar->removeCurrentPanel(p); 0256 _stack->removeWidget(p); 0257 disconnectPanel(p); 0258 0259 p->reparent(_otherManager->_stack, _otherManager); 0260 _otherManager->connectPanel(p); 0261 _otherManager->_stack->addWidget(p); 0262 _otherManager->_tabbar->addPanel(p, true); 0263 0264 _otherManager->tabsCountChanged(); 0265 tabsCountChanged(); 0266 0267 p->slotFocusOnMe(); 0268 } 0269 0270 void PanelManager::moveTabToLeft() 0271 { 0272 // don't move the leftmost tab - also skip a single tab, always leftmost 0273 if (_tabbar->currentIndex() == 0) 0274 return; 0275 0276 _tabbar->moveTab(_tabbar->currentIndex(), _tabbar->currentIndex() - 1); 0277 } 0278 0279 void PanelManager::moveTabToRight() 0280 { 0281 // don't move the rightmost tab - also skip a single tab, always rightmost 0282 if (_tabbar->currentIndex() == tabCount() - 1) 0283 return; 0284 0285 _tabbar->moveTab(_tabbar->currentIndex(), _tabbar->currentIndex() + 1); 0286 } 0287 0288 void PanelManager::slotNewTab(const QUrl &url, bool setCurrent, int insertIndex) 0289 { 0290 ListPanel *p = addPanel(setCurrent, KConfigGroup(), insertIndex); 0291 p->start(url); 0292 } 0293 0294 void PanelManager::slotNewTabButton() 0295 { 0296 KConfigGroup group(krConfig, "Look&Feel"); 0297 int insertIndex = group.readEntry("Insert Tabs After Current", false) ? _tabbar->currentIndex() + 1 : _tabbar->count(); 0298 0299 if (group.readEntry("New Tab Button Duplicates", false)) { 0300 slotDuplicateTab(currentPanel()->virtualPath(), currentPanel(), insertIndex); 0301 _currentPanel->slotFocusOnMe(); 0302 } else { 0303 slotNewTab(insertIndex); 0304 } 0305 } 0306 0307 void PanelManager::slotNewTab(int insertIndex) 0308 { 0309 slotNewTab(QUrl::fromLocalFile(QDir::home().absolutePath()), true, insertIndex); 0310 _currentPanel->slotFocusOnMe(); 0311 } 0312 0313 void PanelManager::slotDuplicateTab(const QUrl &url, KrPanel *nextTo, int insertIndex) 0314 { 0315 ListPanel *p = duplicatePanel(KConfigGroup(), nextTo, insertIndex); 0316 if (nextTo && nextTo->gui) { 0317 // We duplicate tab settings by writing original settings to a temporary 0318 // group and making the new tab read settings from it. Duplicating 0319 // settings directly would add too much complexity. 0320 QString grpName = "PanelManager_" + QString::number(qApp->applicationPid()); 0321 krConfig->deleteGroup(grpName); // make sure the group is empty 0322 KConfigGroup cfg(krConfig, grpName); 0323 0324 nextTo->gui->saveSettings(cfg, true); 0325 // reset undesired duplicated settings 0326 cfg.writeEntry("Properties", 0); 0327 p->restoreSettings(cfg); 0328 krConfig->deleteGroup(grpName); 0329 } 0330 p->start(url); 0331 } 0332 0333 void PanelManager::slotDuplicateTabLMB() 0334 { 0335 slotDuplicateTab(currentPanel()->virtualPath(), currentPanel()); 0336 } 0337 0338 void PanelManager::slotCloseTab() 0339 { 0340 slotCloseTab(_tabbar->currentIndex()); 0341 } 0342 0343 void PanelManager::slotCloseTab(int index) 0344 { 0345 if (_tabbar->count() <= 1) /* if this is the last tab don't close it */ 0346 return; 0347 0348 // Back up some data that will be useful if the user wants to 0349 // undo the closing of the tab 0350 QByteArray backupData; 0351 QDataStream tabStream(&backupData, QIODevice::WriteOnly); // In order to serialize data 0352 tabStream << _left; 0353 tabStream << index; 0354 ListPanel *panel = static_cast<ListPanel *>(currentPanel()); 0355 const QUrl urlTab = panel->virtualPath(); 0356 tabStream << urlTab; 0357 tabStream << panel->getProperties(); 0358 tabStream << panel->pinnedUrl(); 0359 tabStream << panel->view->selectedUrls(); 0360 0361 QAction *actReopenTab = KrActions::actClosedTabsMenu->updateAfterClosingATab(urlTab, backupData, _actions); 0362 0363 // Save settings of the tab. Note: The code is 0364 // based on the one of slotDuplicateTab() 0365 QString grpName = QString("closedTab_%1").arg(reinterpret_cast<qulonglong>(actReopenTab)); 0366 krConfig->deleteGroup(grpName); // make sure the group is empty 0367 KConfigGroup cfg(krConfig, grpName); 0368 panel->gui->saveSettings(cfg, true); 0369 // reset undesired duplicated settings 0370 cfg.writeEntry("Properties", 0); 0371 0372 _tabbar->removePanel(index, panel); // this automatically changes the current panel 0373 0374 _stack->removeWidget(panel); 0375 deletePanel(panel); 0376 tabsCountChanged(); 0377 } 0378 0379 void PanelManager::slotUndoCloseTab() 0380 { 0381 const int fixedMenuEntries = KrActions::actClosedTabsMenu->quantFixedMenuEntries; 0382 Q_ASSERT(KrActions::actClosedTabsMenu->menu()->actions().size() > fixedMenuEntries); 0383 // Performs the same action as when clicking on that menu item 0384 KrActions::actClosedTabsMenu->slotTriggered(KrActions::actClosedTabsMenu->menu()->actions().at(fixedMenuEntries)); 0385 } 0386 0387 void PanelManager::undoCloseTab(const QAction *action) 0388 { 0389 QDataStream tabStream(action->data().toByteArray()); 0390 // Deserialize data 0391 bool closedInTheLeftPan; 0392 tabStream >> closedInTheLeftPan; 0393 int insertIndex; 0394 tabStream >> insertIndex; 0395 QUrl urlClosedTab; 0396 tabStream >> urlClosedTab; 0397 int tabProperties; 0398 tabStream >> tabProperties; 0399 QUrl pinnedUrl; 0400 tabStream >> pinnedUrl; 0401 QList<QUrl> selectedUrls; 0402 tabStream >> selectedUrls; 0403 0404 // This variable points to the PanelManager where the closed tab is going to be restored 0405 PanelManager *whereToUndo = closedInTheLeftPan ? LEFT_MNG : RIGHT_MNG; 0406 0407 MAIN_VIEW->slotSetActiveManager(whereToUndo); 0408 // Open a new tab where to apply the planned changes 0409 whereToUndo->slotNewTab(urlClosedTab, true, insertIndex); 0410 0411 // Restore settings of the tab. Note: The code is 0412 // based on the one of slotDuplicateTab() 0413 QString grpName = QString("closedTab_%1").arg(reinterpret_cast<qulonglong>(action)); 0414 KConfigGroup cfg(krConfig, grpName); 0415 ListPanel *panel = static_cast<ListPanel *>(whereToUndo->currentPanel()); 0416 panel->restoreSettings(cfg); 0417 krConfig->deleteGroup(grpName); 0418 panel->setProperties(tabProperties); 0419 panel->setPinnedUrl(pinnedUrl); 0420 // Note: In `void PanelManager::layoutTabs()` there was a similar `QTimer::singleShot(` 0421 // and a comment about it: "delayed url refreshes may be pending - delay the layout too 0422 // so it happens after them" 0423 QTimer::singleShot(1, this, [=] { 0424 panel->view->setSelectionUrls(selectedUrls); 0425 }); 0426 } 0427 0428 void PanelManager::delAllClosedTabs() 0429 { 0430 const int quantFixedMenuEntries = KrActions::actClosedTabsMenu->quantFixedMenuEntries; 0431 RecentlyClosedTabsMenu *closedTabsMenu = KrActions::actClosedTabsMenu; 0432 if (closedTabsMenu) { 0433 const int quantActions = closedTabsMenu->menu()->actions().size(); 0434 // Remove the actions (and related information) that follow the 0435 // fixed menu entries 0436 for (int x = quantActions - 1; x >= quantFixedMenuEntries; x--) { 0437 QAction *action = closedTabsMenu->menu()->actions().at(x); 0438 delClosedTab(action); 0439 } 0440 } 0441 } 0442 0443 void PanelManager::delClosedTab(QAction *action) 0444 { 0445 // Delete the settings of the closed tab. 0446 // Note: The code is based on the one of slotCloseTab() 0447 QString grpName = QString("closedTab_%1").arg(reinterpret_cast<qulonglong>(action)); 0448 krConfig->deleteGroup(grpName); 0449 0450 // Remove the menu entry and the rest of its information 0451 KrActions::actClosedTabsMenu->removeAction(action); 0452 } 0453 0454 void PanelManager::updateTabbarPos() 0455 { 0456 KConfigGroup group(krConfig, "Look&Feel"); 0457 if (group.readEntry("Tab Bar Position", "bottom") == "top") { 0458 _layout->addWidget(_stack, 2, 0); 0459 _tabbar->setShape(QTabBar::RoundedNorth); 0460 } else { 0461 _layout->addWidget(_stack, 0, 0); 0462 _tabbar->setShape(QTabBar::RoundedSouth); 0463 } 0464 } 0465 0466 int PanelManager::activeTab() 0467 { 0468 return _tabbar->currentIndex(); 0469 } 0470 0471 void PanelManager::setActiveTab(int index) 0472 { 0473 _tabbar->setCurrentIndex(index); 0474 } 0475 0476 void PanelManager::slotRecreatePanels() 0477 { 0478 updateTabbarPos(); 0479 0480 for (int i = 0; i != _tabbar->count(); i++) { 0481 QString grpName = "PanelManager_" + QString::number(qApp->applicationPid()); 0482 krConfig->deleteGroup(grpName); // make sure the group is empty 0483 KConfigGroup cfg(krConfig, grpName); 0484 0485 ListPanel *oldPanel = _tabbar->getPanel(i); 0486 oldPanel->view->setFileIconSize(oldPanel->view->defaultFileIconSize()); 0487 oldPanel->saveSettings(cfg, true); 0488 disconnect(oldPanel); 0489 0490 ListPanel *newPanel = createPanel(cfg); 0491 _stack->insertWidget(i, newPanel); 0492 _tabbar->changePanel(i, newPanel); 0493 0494 if (_currentPanel == oldPanel) { 0495 _currentPanel = newPanel; 0496 _stack->setCurrentWidget(_currentPanel); 0497 } 0498 0499 _stack->removeWidget(oldPanel); 0500 deletePanel(oldPanel); 0501 0502 newPanel->restoreSettings(cfg); 0503 0504 _tabbar->updateTab(newPanel); 0505 0506 krConfig->deleteGroup(grpName); 0507 } 0508 tabsCountChanged(); 0509 _currentPanel->slotFocusOnMe(this == ACTIVE_MNG); 0510 emit pathChanged(_currentPanel); 0511 } 0512 0513 void PanelManager::slotNextTab() 0514 { 0515 int currTab = _tabbar->currentIndex(); 0516 int nextInd = (currTab == _tabbar->count() - 1 ? 0 : currTab + 1); 0517 _tabbar->setCurrentIndex(nextInd); 0518 } 0519 0520 void PanelManager::slotPreviousTab() 0521 { 0522 int currTab = _tabbar->currentIndex(); 0523 int nextInd = (currTab == 0 ? _tabbar->count() - 1 : currTab - 1); 0524 _tabbar->setCurrentIndex(nextInd); 0525 } 0526 0527 void PanelManager::reloadConfig() 0528 { 0529 for (int i = 0; i < _tabbar->count(); i++) { 0530 ListPanel *panel = _tabbar->getPanel(i); 0531 if (panel) { 0532 panel->func->refresh(); 0533 } 0534 } 0535 } 0536 0537 void PanelManager::deletePanel(ListPanel *p) 0538 { 0539 disconnect(p); 0540 delete p; 0541 } 0542 0543 void PanelManager::slotCloseInactiveTabs() 0544 { 0545 int i = 0; 0546 while (i < _tabbar->count()) { 0547 if (i == activeTab()) 0548 i++; 0549 else 0550 slotCloseTab(i); 0551 } 0552 } 0553 0554 void PanelManager::slotCloseDuplicatedTabs() 0555 { 0556 int i = 0; 0557 while (i < _tabbar->count() - 1) { 0558 ListPanel *panel1 = _tabbar->getPanel(i); 0559 if (panel1 != nullptr) { 0560 for (int j = i + 1; j < _tabbar->count(); j++) { 0561 ListPanel *panel2 = _tabbar->getPanel(j); 0562 if (panel2 != nullptr && panel1->virtualPath().matches(panel2->virtualPath(), QUrl::StripTrailingSlash)) { 0563 if (j == activeTab()) { 0564 slotCloseTab(i); 0565 i--; 0566 break; 0567 } else { 0568 slotCloseTab(j); 0569 j--; 0570 } 0571 } 0572 } 0573 } 0574 i++; 0575 } 0576 } 0577 0578 int PanelManager::findTab(QUrl url) 0579 { 0580 url.setPath(QDir::cleanPath(url.path())); 0581 for (int i = 0; i < _tabbar->count(); i++) { 0582 if (_tabbar->getPanel(i)) { 0583 QUrl panelUrl = _tabbar->getPanel(i)->virtualPath(); 0584 panelUrl.setPath(QDir::cleanPath(panelUrl.path())); 0585 if (panelUrl.matches(url, QUrl::StripTrailingSlash)) 0586 return i; 0587 } 0588 } 0589 return -1; 0590 } 0591 0592 void PanelManager::slotLockTab() 0593 { 0594 ListPanel *panel = _currentPanel; 0595 panel->gui->setTabState(panel->gui->isLocked() ? ListPanel::TabState::DEFAULT : ListPanel::TabState::LOCKED); 0596 _actions->refreshActions(); 0597 _tabbar->updateTab(panel); 0598 } 0599 0600 void PanelManager::slotPinTab() 0601 { 0602 ListPanel *panel = _currentPanel; 0603 panel->gui->setTabState(panel->gui->isPinned() ? ListPanel::TabState::DEFAULT : ListPanel::TabState::PINNED); 0604 if (panel->gui->isPinned()) { 0605 QUrl virtualPath = panel->virtualPath(); 0606 panel->setPinnedUrl(virtualPath); 0607 } 0608 _actions->refreshActions(); 0609 _tabbar->updateTab(panel); 0610 } 0611 0612 void PanelManager::newTabs(const QStringList &urls) 0613 { 0614 for (int i = 0; i < urls.count(); i++) 0615 slotNewTab(QUrl::fromUserInput(urls[i], QString(), QUrl::AssumeLocalFile)); 0616 }