File indexing completed on 2024-04-21 16:33:20

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     Based on original code from Sebastian Trueg <trueg@kde.org>
0007 
0008     SPDX-License-Identifier: GPL-2.0-or-later
0009 */
0010 
0011 #include "paneltabbar.h"
0012 
0013 #include "../icon.h"
0014 #include "../krglobal.h"
0015 #include "Panel/listpanel.h"
0016 #include "Panel/panelfunc.h"
0017 #include "abstractpanelmanager.h"
0018 #include "compat.h"
0019 #include "defaults.h"
0020 #include "tabactions.h"
0021 
0022 // QtCore
0023 #include <QEvent>
0024 // QtGui
0025 #include <QFontMetrics>
0026 #include <QMouseEvent>
0027 #include <QResizeEvent>
0028 // QtWidgets
0029 #include <QAction>
0030 #include <QMenu>
0031 
0032 #include <KConfigCore/KSharedConfig>
0033 #include <KI18n/KLocalizedString>
0034 #include <KWidgetsAddons/KActionMenu>
0035 
0036 static const int sDragEnterDelay = 500; // msec
0037 
0038 PanelTabBar::PanelTabBar(QWidget *parent, TabActions *actions)
0039     : QTabBar(parent)
0040     , _maxTabLength(0)
0041     , _tabClicked(false)
0042     , _tabDoubleClicked(false)
0043     , _draggingTab(false)
0044     , _dragTabIndex(-1)
0045 {
0046     const KConfigGroup cfg(krConfig, "Look&Feel");
0047     const bool expandingTabs = cfg.readEntry("Expanding Tabs", true);
0048     const bool showCloseButtons = cfg.readEntry("Show Close Tab Buttons", true);
0049     _doubleClickClose = cfg.readEntry("Close Tab By Double Click", false);
0050 
0051     _panelActionMenu = new KActionMenu(i18n("Panel"), this);
0052 
0053     setAcceptDrops(true);
0054     setExpanding(expandingTabs);
0055     setTabsClosable(showCloseButtons);
0056 
0057     insertAction(actions->actNewTab);
0058     insertAction(actions->actLockTab);
0059     insertAction(actions->actPinTab);
0060     insertAction(actions->actDupTab);
0061     insertAction(actions->actMoveTabToOtherSide);
0062     insertAction(actions->actMoveTabToLeft);
0063     insertAction(actions->actMoveTabToRight);
0064     insertAction(actions->actCloseTab);
0065     insertAction(actions->actUndoCloseTab);
0066     insertAction(actions->actCloseInactiveTabs);
0067     insertAction(actions->actCloseDuplicatedTabs);
0068 
0069     setMovable(true); // enable drag'n'drop
0070     _dragTimer = new QTimer(this);
0071     _dragTimer->setSingleShot(true);
0072     _dragTimer->setInterval(sDragEnterDelay);
0073     connect(_dragTimer, &QTimer::timeout, this, [=]() {
0074         if (_dragTabIndex != -1 && _dragTabIndex != currentIndex()) {
0075             setCurrentIndex(_dragTabIndex);
0076         }
0077         _dragTabIndex = -1;
0078     });
0079 
0080     setShape(QTabBar::TriangularSouth);
0081 }
0082 
0083 void PanelTabBar::insertAction(QAction *action)
0084 {
0085     _panelActionMenu->addAction(action);
0086 }
0087 
0088 int PanelTabBar::addPanel(ListPanel *panel, bool setCurrent, int insertIndex)
0089 {
0090     // If the position where to place the new tab is not specified,
0091     // take the settings into account
0092     if (insertIndex == -1) {
0093         insertIndex = KConfigGroup(krConfig, "Look&Feel").readEntry("Insert Tabs After Current", false) ? currentIndex() + 1 : count();
0094     }
0095 
0096     QUrl virtualPath = panel->virtualPath();
0097     panel->setPinnedUrl(virtualPath);
0098     const QString text = squeeze(virtualPath);
0099     // In the help about `insertTab()` it's written that it inserts a new tab at
0100     // position `index`. If `index` is out of range, the new tab is appened. Returns
0101     // the new tab's index
0102     insertIndex = insertTab(insertIndex, text);
0103 
0104     setTabData(insertIndex, QVariant(reinterpret_cast<long long>(panel)));
0105 
0106     setIcon(insertIndex, panel);
0107 
0108     // make sure all tabs lengths are correct
0109     layoutTabs();
0110 
0111     if (setCurrent)
0112         setCurrentIndex(insertIndex);
0113 
0114     return insertIndex;
0115 }
0116 
0117 ListPanel *PanelTabBar::getPanel(int tabIdx)
0118 {
0119     QVariant v = tabData(tabIdx);
0120     if (v.isNull())
0121         return nullptr;
0122     return (ListPanel *)v.toLongLong();
0123 }
0124 
0125 void PanelTabBar::changePanel(int tabIdx, ListPanel *panel)
0126 {
0127     setTabData(tabIdx, QVariant((long long)panel));
0128 }
0129 
0130 ListPanel *PanelTabBar::removePanel(int index, ListPanel *&panelToDelete)
0131 {
0132     panelToDelete = getPanel(index); // old panel to kill later
0133     disconnect(panelToDelete, nullptr, this, nullptr);
0134 
0135     removeTab(index);
0136     layoutTabs();
0137 
0138     return getPanel(currentIndex());
0139 }
0140 
0141 ListPanel *PanelTabBar::removeCurrentPanel(ListPanel *&panelToDelete)
0142 {
0143     return removePanel(currentIndex(), panelToDelete);
0144 }
0145 
0146 void PanelTabBar::updateTab(ListPanel *panel)
0147 {
0148     // find which is the correct tab
0149     for (int i = 0; i < count(); i++) {
0150         if ((ListPanel *)tabData(i).toLongLong() == panel) {
0151             setPanelTextToTab(i, panel);
0152             setIcon(i, panel);
0153             break;
0154         }
0155     }
0156 }
0157 
0158 void PanelTabBar::duplicateTab()
0159 {
0160     int id = currentIndex();
0161     emit newTab(((ListPanel *)tabData(id).toLongLong())->virtualPath());
0162 }
0163 
0164 void PanelTabBar::setIcon(int index, ListPanel *panel)
0165 {
0166     Icon tabIcon;
0167     if (panel->isLocked()) {
0168         tabIcon = Icon("lock");
0169     } else if (panel->isPinned()) {
0170         tabIcon = Icon("pin");
0171     }
0172     setTabIcon(index, tabIcon);
0173 }
0174 
0175 QString PanelTabBar::squeeze(const QUrl &url, int tabIndex)
0176 {
0177     const QString longText = url.isEmpty() ? i18n("[invalid]") : url.isLocalFile() ? url.path() : url.toDisplayString();
0178     if (tabIndex >= 0)
0179         setTabToolTip(tabIndex, longText);
0180 
0181     const KConfigGroup group(krConfig, "Look&Feel");
0182     const bool showLongNames = group.readEntry("Fullpath Tab Names", _FullPathTabNames);
0183 
0184     QString text;
0185     if (!showLongNames) {
0186         const QString scheme = url.scheme().isEmpty() || url.isLocalFile() ? "" : (url.scheme() + ':');
0187         const QString host = url.host().isEmpty() ? "" : (url.host() + ':');
0188         const QString name = url.isLocalFile() && url.fileName().isEmpty() ? "/" : url.fileName();
0189         text = scheme + host + name;
0190     } else {
0191         text = longText;
0192     }
0193 
0194     if (text.isEmpty())
0195         text = i18nc("invalid URL path", "?");
0196 
0197     // set the real max length
0198     QFontMetrics fm(fontMetrics());
0199     _maxTabLength = (dynamic_cast<QWidget *>(parent())->width() - (6 * fm.horizontalAdvance("W"))) / fm.horizontalAdvance("W");
0200     // each tab gets a fair share of the max tab length
0201     const int effectiveTabLength = _maxTabLength / (count() == 0 ? 1 : count());
0202     const int labelWidth = fm.horizontalAdvance("W") * effectiveTabLength;
0203     const int textWidth = fm.horizontalAdvance(text);
0204     if (textWidth <= labelWidth)
0205         return text;
0206 
0207     // squeeze text - start with the dots only
0208     QString squeezedText = "...";
0209     int squeezedWidth = fm.horizontalAdvance(squeezedText);
0210 
0211     int letters = text.length() * (labelWidth - squeezedWidth) / textWidth / 2;
0212     if (labelWidth < squeezedWidth)
0213         letters = 1;
0214     squeezedText = text.left(letters) + "..." + text.right(letters);
0215     squeezedWidth = fm.horizontalAdvance(squeezedText);
0216 
0217     if (squeezedWidth < labelWidth) {
0218         // we estimated too short
0219         // add letters while text < label
0220         do {
0221             letters++;
0222             squeezedText = text.left(letters) + "..." + text.right(letters);
0223             squeezedWidth = fm.horizontalAdvance(squeezedText);
0224         } while (squeezedWidth < labelWidth);
0225         letters--;
0226         squeezedText = text.left(letters) + "..." + text.right(letters);
0227     } else if (squeezedWidth > labelWidth) {
0228         // we estimated too long
0229         // remove letters while text > label
0230         do {
0231             letters--;
0232             squeezedText = text.left(letters) + "..." + text.right(letters);
0233             squeezedWidth = fm.horizontalAdvance(squeezedText);
0234         } while (letters && squeezedWidth > labelWidth);
0235     }
0236 
0237     return squeezedText;
0238 }
0239 
0240 void PanelTabBar::resizeEvent(QResizeEvent *e)
0241 {
0242     QTabBar::resizeEvent(e);
0243 
0244     layoutTabs();
0245 }
0246 
0247 void PanelTabBar::mouseMoveEvent(QMouseEvent *e)
0248 {
0249     QTabBar::mouseMoveEvent(e);
0250     if (_tabClicked) {
0251         _draggingTab = true;
0252         emit draggingTab(e);
0253     }
0254 }
0255 
0256 void PanelTabBar::mousePressEvent(QMouseEvent *e)
0257 {
0258     int clickedTabIndex = tabAt(e->pos());
0259 
0260     // don't handle clicks outside of tabs
0261     if (clickedTabIndex < 0) {
0262         QTabBar::mousePressEvent(e);
0263         return;
0264     }
0265 
0266     bool isActiveTab = currentIndex() == clickedTabIndex;
0267     KrPanel *tabPane = getPanel(clickedTabIndex)->manager()->currentPanel();
0268     KrPanel *activePane = ACTIVE_PANEL;
0269 
0270     _tabClicked = true;
0271 
0272     // activate the pane with the clicked tab unless it's active already
0273     if (tabPane && tabPane != activePane) {
0274         // slotFocusOnMe() is an expensive call, make it only when necessary
0275         // If current tab is not active, the QTabBar::mousePressEvent will trigger
0276         // current tab index change signal and the slot will call slotFocusOnMe().
0277         // This way we avoid extra calls.
0278         if (isActiveTab)
0279             tabPane->gui->slotFocusOnMe();
0280         else
0281             emit tabPane->gui->activate();
0282     }
0283 
0284     if (e->button() == Qt::RightButton) {
0285         if (!isActiveTab)
0286             setCurrentIndex(clickedTabIndex);
0287 
0288         // show the popup menu
0289         _panelActionMenu->menu()->popup(e->globalPos());
0290     } else if (e->button() == Qt::LeftButton && !_tabDoubleClicked) {
0291         bool isDuplicationEvent = false;
0292 
0293         if (e->modifiers() == Qt::ControlModifier) {
0294             KConfigGroup group(krConfig, "Look&Feel");
0295             if (group.readEntry("Duplicate Tab Click", "disabled") == "ctrl_click") {
0296                 isDuplicationEvent = true;
0297             }
0298         } else if (e->modifiers() == Qt::AltModifier) {
0299             KConfigGroup group(krConfig, "Look&Feel");
0300             if (group.readEntry("Duplicate Tab Click", "disabled") == "alt_click") {
0301                 isDuplicationEvent = true;
0302             }
0303         }
0304 
0305         if (isDuplicationEvent) {
0306             // Duplicate only the active tab, otherwise dismiss the click,
0307             // because an inactive tab may not be properly initialized
0308             // and duplication will be incomplete in this case.
0309             if (isActiveTab)
0310                 emit duplicateCurrentTab();
0311             else
0312                 return;
0313         }
0314     } else if (e->button() == Qt::MidButton) {
0315         if (!isActiveTab)
0316             setCurrentIndex(clickedTabIndex);
0317 
0318         // close the current tab
0319         emit closeCurrentTab();
0320     }
0321 
0322     QTabBar::mousePressEvent(e);
0323 }
0324 
0325 void PanelTabBar::mouseDoubleClickEvent(QMouseEvent *e)
0326 {
0327     _tabDoubleClicked = true;
0328 
0329     if (!_doubleClickClose) {
0330         QTabBar::mouseDoubleClickEvent(e);
0331         return;
0332     }
0333 
0334     int clickedTabIndex = tabAt(e->pos());
0335 
0336     if (-1 == clickedTabIndex) { // clicked on nothing ...
0337         QTabBar::mouseDoubleClickEvent(e);
0338         return;
0339     }
0340 
0341     _tabClicked = true;
0342 
0343     // close the current tab
0344     if (e->button() == Qt::LeftButton && e->modifiers() == Qt::NoModifier) {
0345         emit closeCurrentTab();
0346     }
0347     QTabBar::mouseDoubleClickEvent(e);
0348 }
0349 
0350 void PanelTabBar::mouseReleaseEvent(QMouseEvent *e)
0351 {
0352     QTabBar::mouseReleaseEvent(e);
0353     if (_draggingTab)
0354         emit draggingTabFinished(e);
0355     _draggingTab = false;
0356     _tabClicked = false;
0357     _tabDoubleClicked = false;
0358 }
0359 
0360 void PanelTabBar::dragEnterEvent(QDragEnterEvent *e)
0361 {
0362     e->accept();
0363     handleDragEvent(tabAt(e->pos()));
0364     QTabBar::dragEnterEvent(e);
0365 }
0366 
0367 void PanelTabBar::dragLeaveEvent(QDragLeaveEvent *)
0368 {
0369     handleDragEvent(-1);
0370 }
0371 
0372 void PanelTabBar::dragMoveEvent(QDragMoveEvent *e)
0373 {
0374     e->ignore();
0375     handleDragEvent(tabAt(e->pos()));
0376     QTabBar::dragMoveEvent(e);
0377 }
0378 
0379 void PanelTabBar::handleDragEvent(int tabIndex)
0380 {
0381     if (_dragTabIndex == tabIndex)
0382         return;
0383 
0384     _dragTabIndex = tabIndex;
0385     if (_dragTabIndex == -1) {
0386         _dragTimer->stop();
0387     } else {
0388         _dragTimer->start();
0389     }
0390 }
0391 
0392 void PanelTabBar::layoutTabs()
0393 {
0394     for (int i = 0; i < count(); i++) {
0395         setPanelTextToTab(i, (ListPanel *)tabData(i).toLongLong());
0396     }
0397 }
0398 
0399 void PanelTabBar::setPanelTextToTab(int tabIndex, ListPanel *panel)
0400 {
0401     // update tab text from pinnedUrl in case the tab is pinned
0402     if (panel->isPinned()) {
0403         setTabText(tabIndex, squeeze(panel->pinnedUrl(), tabIndex));
0404     } else {
0405         setTabText(tabIndex, squeeze(panel->virtualPath(), tabIndex));
0406     }
0407 }