File indexing completed on 2024-05-12 09:01:09
0001 /* This file is part of the KDE libraries 0002 SPDX-FileCopyrightText: 2003 Stephan Binner <binner@kde.org> 0003 SPDX-FileCopyrightText: 2003 Zack Rusin <zack@kde.org> 0004 SPDX-FileCopyrightText: 2009 Urs Wolfer <uwolfer @ kde.org> 0005 0006 SPDX-License-Identifier: LGPL-2.0-or-later 0007 */ 0008 0009 #include "ktabwidget.h" 0010 0011 #include <QApplication> 0012 #include <QDragMoveEvent> 0013 #include <QDropEvent> 0014 #include <QMouseEvent> 0015 #include <QStyle> 0016 #include <QStyleOption> 0017 #include <QTextDocument> 0018 #include <QWheelEvent> 0019 #include <QList> 0020 0021 #include <ksharedconfig.h> 0022 #include <kiconloader.h> 0023 #include <kstringhandler.h> 0024 0025 #include "ktabbar.h" 0026 #include "konqdebug.h" 0027 0028 #include <kconfiggroup.h> 0029 0030 class Q_DECL_HIDDEN KTabWidget::Private 0031 { 0032 public: 0033 enum { 0034 ResizeEnabled = 0, 0035 ResizeDisabled, 0036 ResizeLater 0037 } m_resizeSuspend; 0038 0039 Private(KTabWidget *parent) 0040 : m_resizeSuspend(ResizeEnabled), 0041 m_parent(parent), 0042 m_automaticResizeTabs(false), 0043 m_tabBarHidden(false) 0044 { 0045 0046 KConfigGroup cg(KSharedConfig::openConfig(), "General"); 0047 m_maxLength = cg.readEntry("MaximumTabLength", 30); 0048 m_minLength = cg.readEntry("MinimumTabLength", 3); 0049 Q_ASSERT(m_maxLength >= m_minLength); 0050 m_currentTabLength = m_minLength; 0051 } 0052 0053 KTabWidget *m_parent; 0054 bool m_automaticResizeTabs; 0055 bool m_tabBarHidden; 0056 int m_maxLength; 0057 int m_minLength; 0058 int m_currentTabLength; 0059 0060 //holds the full names of the tab, otherwise all we 0061 //know about is the shortened name 0062 QStringList m_tabNames; 0063 0064 bool isEmptyTabbarSpace(const QPoint &) const; 0065 void resizeTabs(int changedTabIndex = -1); 0066 void updateTab(int index); 0067 void removeTab(int index); 0068 0069 void slotTabMoved(int from, int to); 0070 }; 0071 0072 bool KTabWidget::Private::isEmptyTabbarSpace(const QPoint &point) const 0073 { 0074 if (m_parent->count() == 0) { 0075 return true; 0076 } 0077 if (m_parent->tabBar()->isHidden()) { 0078 return false; 0079 } 0080 QSize size(m_parent->tabBar()->sizeHint()); 0081 if ((m_parent->tabPosition() == QTabWidget::North && point.y() < size.height()) || 0082 (m_parent->tabPosition() == QTabWidget::South && point.y() > (m_parent->height() - size.height()))) { 0083 0084 QWidget *rightcorner = m_parent->cornerWidget(Qt::TopRightCorner); 0085 if (rightcorner && rightcorner->isVisible()) { 0086 if (point.x() >= m_parent->width() - rightcorner->width()) { 0087 return false; 0088 } 0089 } 0090 0091 QWidget *leftcorner = m_parent->cornerWidget(Qt::TopLeftCorner); 0092 if (leftcorner && leftcorner->isVisible()) { 0093 if (point.x() <= leftcorner->width()) { 0094 return false; 0095 } 0096 } 0097 0098 for (int i = 0; i < m_parent->count(); ++i) 0099 if (m_parent->tabBar()->tabRect(i).contains(m_parent->tabBar()->mapFromParent(point))) { 0100 return false; 0101 } 0102 0103 return true; 0104 } 0105 0106 return false; 0107 } 0108 0109 void KTabWidget::Private::removeTab(int index) 0110 { 0111 // prevent cascading resize slowness, not to mention crashes due to tab count() 0112 // and m_tabNames.count() being out of sync! 0113 m_resizeSuspend = ResizeDisabled; 0114 0115 // Need to do this here, rather than in tabRemoved(). Calling 0116 // QTabWidget::removeTab() below may cause a relayout of the tab bar, which 0117 // will call resizeTabs() immediately. If m_automaticResizeTabs is true, 0118 // that will use the m_tabNames[] list before it has been updated to reflect 0119 // the new tab arrangement. See bug 190528. 0120 m_tabNames.removeAt(index); 0121 0122 m_parent->QTabWidget::removeTab(index); 0123 0124 const bool doResize = (m_resizeSuspend == ResizeLater) || m_automaticResizeTabs; 0125 m_resizeSuspend = ResizeEnabled; 0126 if (doResize) { 0127 resizeTabs(); 0128 } 0129 0130 } 0131 0132 void KTabWidget::Private::resizeTabs(int changeTabIndex) 0133 { 0134 if (m_resizeSuspend != ResizeEnabled) { 0135 m_resizeSuspend = ResizeLater; 0136 return; 0137 } 0138 0139 int newTabLength = m_maxLength; 0140 0141 if (m_automaticResizeTabs) { 0142 // Calculate new max length 0143 int lcw = 0, rcw = 0; 0144 0145 const int tabBarHeight = m_parent->tabBar()->sizeHint().height(); 0146 if (m_parent->cornerWidget(Qt::TopLeftCorner) && 0147 m_parent->cornerWidget(Qt::TopLeftCorner)->isVisible()) { 0148 lcw = qMax(m_parent->cornerWidget(Qt::TopLeftCorner)->width(), tabBarHeight); 0149 } 0150 if (m_parent->cornerWidget(Qt::TopRightCorner) && 0151 m_parent->cornerWidget(Qt::TopRightCorner)->isVisible()) { 0152 rcw = qMax(m_parent->cornerWidget(Qt::TopRightCorner)->width(), tabBarHeight); 0153 } 0154 0155 const int maxTabBarWidth = m_parent->width() - lcw - rcw; 0156 0157 // binary search for the best fitting tab title length; some wiggling was 0158 // required to make this behave in the face of rounding. 0159 int newTabLengthHi = m_maxLength + 1; 0160 int newTabLengthLo = m_minLength; 0161 int prevTabLengthMid = -1; 0162 while (true) { 0163 int newTabLengthMid = (newTabLengthHi + newTabLengthLo) / 2; 0164 if (prevTabLengthMid == newTabLengthMid) { 0165 // no change, we're stuck due to rounding. 0166 break; 0167 } 0168 prevTabLengthMid = newTabLengthMid; 0169 0170 if (m_parent->tabBarWidthForMaxChars(newTabLengthMid) > maxTabBarWidth) { 0171 newTabLengthHi = newTabLengthMid; 0172 } else { 0173 newTabLengthLo = newTabLengthMid; 0174 } 0175 } 0176 newTabLength = qMin(newTabLengthLo, m_maxLength); 0177 } 0178 0179 // Update hinted or all tabs 0180 if (m_currentTabLength != newTabLength) { 0181 m_currentTabLength = newTabLength; 0182 for (int i = 0; i < m_parent->count(); i++) { 0183 updateTab(i); 0184 } 0185 } else if (changeTabIndex != -1) { 0186 updateTab(changeTabIndex); 0187 } 0188 } 0189 0190 void KTabWidget::Private::updateTab(int index) 0191 { 0192 QString title = m_automaticResizeTabs ? m_tabNames[ index ] : m_parent->QTabWidget::tabText(index); 0193 m_parent->setTabToolTip(index, QString()); 0194 0195 if (title.length() > m_currentTabLength) { 0196 QString toolTipText = title; 0197 // Remove '&'s, which are indicators for keyboard shortcuts in tab titles. "&&" is replaced by '&'. 0198 for (int i = toolTipText.indexOf('&'); i >= 0 && i < toolTipText.length(); i = toolTipText.indexOf('&', i + 1)) { 0199 toolTipText.remove(i, 1); 0200 } 0201 0202 if (Qt::mightBeRichText(toolTipText)) { 0203 m_parent->setTabToolTip(index, toolTipText.toHtmlEscaped()); 0204 } else { 0205 m_parent->setTabToolTip(index, toolTipText); 0206 } 0207 } 0208 0209 title = KStringHandler::rsqueeze(title, m_currentTabLength).leftJustified(m_minLength, ' '); 0210 0211 if (m_parent->QTabWidget::tabText(index) != title) { 0212 m_parent->QTabWidget::setTabText(index, title); 0213 } 0214 } 0215 0216 void KTabWidget::Private::slotTabMoved(int from, int to) 0217 { 0218 /* called from Qt slot when Qt has moved the tab, so we only 0219 need to adjust the m_tabNames list */ 0220 if (m_automaticResizeTabs) { 0221 QString movedName = m_tabNames.takeAt(from); 0222 m_tabNames.insert(to, movedName); 0223 } 0224 } 0225 0226 KTabWidget::KTabWidget(QWidget *parent, Qt::WindowFlags flags) 0227 : QTabWidget(parent), 0228 d(new Private(this)) 0229 { 0230 setWindowFlags(flags); 0231 setTabBar(new KTabBar(this)); 0232 setObjectName("tabbar"); 0233 setAcceptDrops(true); 0234 0235 connect(tabBar(), SIGNAL(contextMenu(int,QPoint)), SLOT(contextMenu(int,QPoint))); 0236 connect(tabBar(), SIGNAL(tabDoubleClicked(int)), SLOT(mouseDoubleClick(int))); 0237 connect(tabBar(), SIGNAL(newTabRequest()), this, SIGNAL(mouseDoubleClick())); // #185487 0238 connect(tabBar(), SIGNAL(mouseMiddleClick(int)), SLOT(mouseMiddleClick(int))); 0239 connect(tabBar(), SIGNAL(initiateDrag(int)), SLOT(initiateDrag(int))); 0240 connect(tabBar(), SIGNAL(testCanDecode(const QDragMoveEvent*,bool&)), SIGNAL(testCanDecode(const QDragMoveEvent*,bool&))); 0241 connect(tabBar(), SIGNAL(receivedDropEvent(int,QDropEvent*)), SLOT(receivedDropEvent(int,QDropEvent*))); 0242 connect(tabBar(), SIGNAL(tabMoved(int,int)), SLOT(slotTabMoved(int,int))); 0243 } 0244 0245 KTabWidget::~KTabWidget() 0246 { 0247 delete d; 0248 } 0249 0250 int KTabWidget::tabBarWidthForMaxChars(int maxLength) 0251 { 0252 const int hframe = tabBar()->style()->pixelMetric(QStyle::PM_TabBarTabHSpace, nullptr, tabBar()); 0253 0254 const QFontMetrics fm = tabBar()->fontMetrics(); 0255 int x = 0; 0256 for (int i = 0; i < count(); ++i) { 0257 QString newTitle = d->m_tabNames.value(i); 0258 newTitle = KStringHandler::rsqueeze(newTitle, maxLength).leftJustified(d->m_minLength, ' '); 0259 0260 int lw = fm.boundingRect(newTitle).width(); 0261 int iw = 0; 0262 if (!tabBar()->tabIcon(i).isNull()) { 0263 iw = tabBar()->tabIcon(i).pixmap(style()->pixelMetric(QStyle::PM_SmallIconSize), QIcon::Normal).width() + 4; 0264 } 0265 if (tabsClosable()) { 0266 // FIXME: how to get the size of the close button directly from the tabBar()? 0267 iw += KIconLoader::SizeSmall * 3 / 2; 0268 } 0269 //TODO KF6: the third argument used to be a size whose width was the maximum between 0270 //lw + hframe + iw and QApplication::globalStrut().width(). Since QApplication::globalStrut() 0271 //has been removed in KF6, we assume it's 0: check whether this is correct or what should 0272 //be used in place of globalStrut 0273 x += (tabBar()->style()->sizeFromContents(QStyle::CT_TabBarTab, nullptr, 0274 QSize(lw + hframe + iw, 0), 0275 this)).width(); 0276 } 0277 0278 return x; 0279 } 0280 0281 QString KTabWidget::tabText(int index) const 0282 { 0283 if (d->m_automaticResizeTabs) { 0284 if (index >= 0 && index < count()) { 0285 if (index >= d->m_tabNames.count()) { 0286 // Ooops, the tab exists, but tabInserted wasn't called yet. 0287 // This can happen when inserting the first tab, 0288 // and calling tabText from slotCurrentChanged, 0289 // see KTabWidget_UnitTest. 0290 const_cast<KTabWidget *>(this)->tabInserted(index); 0291 } 0292 return d->m_tabNames[ index ]; 0293 } else { 0294 return QString(); 0295 } 0296 } else { 0297 return QTabWidget::tabText(index); 0298 } 0299 } 0300 0301 void KTabWidget::setTabText(int index, const QString &text) 0302 { 0303 if (text == tabText(index)) { 0304 return; 0305 } 0306 0307 if (d->m_automaticResizeTabs) { 0308 0309 tabBar()->setUpdatesEnabled(false); //no flicker 0310 0311 QTabWidget::setTabText(index, text); 0312 0313 if (index != -1) { 0314 if (index >= d->m_tabNames.count()) { 0315 qCWarning(KONQUEROR_LOG) << "setTabText(" << index << ") called but d->m_tabNames has only" << d->m_tabNames.count() << "entries"; 0316 while (index >= d->m_tabNames.count()) { 0317 d->m_tabNames.append(QString()); 0318 } 0319 } 0320 d->m_tabNames[ index ] = text; 0321 d->resizeTabs(index); 0322 } 0323 0324 tabBar()->setUpdatesEnabled(true); 0325 0326 } else { 0327 QTabWidget::setTabText(index, text); 0328 } 0329 } 0330 0331 void KTabWidget::dragEnterEvent(QDragEnterEvent *event) 0332 { 0333 if (d->isEmptyTabbarSpace(event->pos())) { 0334 bool accept = false; 0335 // The receivers of the testCanDecode() signal has to adjust 0336 // 'accept' accordingly. 0337 emit testCanDecode(event, accept); 0338 0339 event->setAccepted(accept); 0340 return; 0341 } 0342 0343 QTabWidget::dragEnterEvent(event); 0344 } 0345 0346 void KTabWidget::dragMoveEvent(QDragMoveEvent *event) 0347 { 0348 if (d->isEmptyTabbarSpace(event->pos())) { 0349 bool accept = false; 0350 // The receivers of the testCanDecode() signal has to adjust 0351 // 'accept' accordingly. 0352 emit testCanDecode(event, accept); 0353 0354 event->setAccepted(accept); 0355 return; 0356 } 0357 0358 QTabWidget::dragMoveEvent(event); 0359 } 0360 0361 void KTabWidget::dropEvent(QDropEvent *event) 0362 { 0363 if (d->isEmptyTabbarSpace(event->pos())) { 0364 emit(receivedDropEvent(event)); 0365 return; 0366 } 0367 0368 QTabWidget::dropEvent(event); 0369 } 0370 0371 #ifndef QT_NO_WHEELEVENT 0372 void KTabWidget::wheelEvent(QWheelEvent *event) 0373 { 0374 if (d->isEmptyTabbarSpace(event->position().toPoint())) { 0375 QCoreApplication::sendEvent(tabBar(), event); 0376 } else { 0377 QTabWidget::wheelEvent(event); 0378 } 0379 } 0380 0381 void KTabWidget::wheelDelta(int delta) 0382 { 0383 if (count() < 2) { 0384 return; 0385 } 0386 0387 int page = currentIndex(); 0388 if (delta < 0) { 0389 page = (page + 1) % count(); 0390 } else { 0391 page--; 0392 if (page < 0) { 0393 page = count() - 1; 0394 } 0395 } 0396 setCurrentIndex(page); 0397 } 0398 #endif 0399 0400 void KTabWidget::mouseDoubleClickEvent(QMouseEvent *event) 0401 { 0402 if (event->button() != Qt::LeftButton) { 0403 return; 0404 } 0405 0406 if (d->isEmptyTabbarSpace(event->pos())) { 0407 emit(mouseDoubleClick()); 0408 return; 0409 } 0410 0411 QTabWidget::mouseDoubleClickEvent(event); 0412 } 0413 0414 void KTabWidget::mousePressEvent(QMouseEvent *event) 0415 { 0416 if (event->button() == Qt::RightButton) { 0417 if (d->isEmptyTabbarSpace(event->pos())) { 0418 emit(contextMenu(mapToGlobal(event->pos()))); 0419 return; 0420 } 0421 } 0422 0423 QTabWidget::mousePressEvent(event); 0424 } 0425 0426 void KTabWidget::mouseReleaseEvent(QMouseEvent *event) 0427 { 0428 if (event->button() == Qt::MiddleButton) { 0429 if (d->isEmptyTabbarSpace(event->pos())) { 0430 emit(mouseMiddleClick()); 0431 return; 0432 } 0433 } 0434 0435 QTabWidget::mouseReleaseEvent(event); 0436 } 0437 0438 void KTabWidget::receivedDropEvent(int index, QDropEvent *event) 0439 { 0440 emit(receivedDropEvent(widget(index), event)); 0441 } 0442 0443 void KTabWidget::initiateDrag(int index) 0444 { 0445 emit(initiateDrag(widget(index))); 0446 } 0447 0448 void KTabWidget::contextMenu(int index, const QPoint &point) 0449 { 0450 emit(contextMenu(widget(index), point)); 0451 } 0452 0453 void KTabWidget::mouseDoubleClick(int index) 0454 { 0455 emit(mouseDoubleClick(widget(index))); 0456 } 0457 0458 void KTabWidget::mouseMiddleClick(int index) 0459 { 0460 emit(mouseMiddleClick(widget(index))); 0461 } 0462 0463 void KTabWidget::removeTab(int index) 0464 { 0465 if (d->m_automaticResizeTabs) { 0466 const bool wasUpdatesEnabled = updatesEnabled(); 0467 setUpdatesEnabled(false); 0468 d->removeTab(index); 0469 setUpdatesEnabled(wasUpdatesEnabled); 0470 } else { 0471 d->removeTab(index); 0472 } 0473 } 0474 0475 void KTabWidget::setAutomaticResizeTabs(bool enabled) 0476 { 0477 if (d->m_automaticResizeTabs == enabled) { 0478 return; 0479 } 0480 0481 setUpdatesEnabled(false); 0482 0483 d->m_automaticResizeTabs = enabled; 0484 if (enabled) { 0485 d->m_tabNames.clear(); 0486 for (int i = 0; i < count(); ++i) { 0487 d->m_tabNames.append(tabBar()->tabText(i)); 0488 } 0489 } else 0490 for (int i = 0; i < count(); ++i) { 0491 tabBar()->setTabText(i, d->m_tabNames[ i ]); 0492 } 0493 0494 d->resizeTabs(); 0495 0496 setUpdatesEnabled(true); 0497 } 0498 0499 bool KTabWidget::automaticResizeTabs() const 0500 { 0501 return d->m_automaticResizeTabs; 0502 } 0503 0504 void KTabWidget::resizeEvent(QResizeEvent *event) 0505 { 0506 QTabWidget::resizeEvent(event); 0507 d->resizeTabs(); 0508 } 0509 0510 void KTabWidget::tabInserted(int idx) 0511 { 0512 d->m_tabNames.insert(idx, tabBar()->tabText(idx)); 0513 } 0514 0515 #include "moc_ktabwidget.cpp"