File indexing completed on 2024-04-14 04:53:18

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"