File indexing completed on 2024-04-28 13:45:13
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 }