diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -58,6 +58,8 @@ konqframevisitor.cpp konqframestatusbar.cpp konqframecontainer.cpp + ktabbar.cpp + ktabwidget.cpp konqtabs.cpp konqactions.cpp konqsessiondlg.cpp diff --git a/src/konqtabs.h b/src/konqtabs.h --- a/src/konqtabs.h +++ b/src/konqtabs.h @@ -25,7 +25,7 @@ #include "konqframe.h" #include "konqframecontainer.h" -#include +#include "ktabwidget.h" #include #include diff --git a/src/konqtabs.cpp b/src/konqtabs.cpp --- a/src/konqtabs.cpp +++ b/src/konqtabs.cpp @@ -45,7 +45,7 @@ #include #include #include -#include +#include "ktabbar.h" //################################################################### @@ -526,7 +526,7 @@ void KonqFrameTabs::slotCurrentChanged(int index) { const KColorScheme colorScheme(QPalette::Active, KColorScheme::Window); - setTabTextColor(index, colorScheme.foreground(KColorScheme::NormalText).color()); + tabBar()->setTabTextColor(index, colorScheme.foreground(KColorScheme::NormalText).color()); KonqFrameBase *currentFrame = tabAt(index); if (currentFrame && !m_pViewManager->isLoadingProfile()) { @@ -584,7 +584,7 @@ color = colorScheme.foreground(KColorScheme::NormalText).color(); } } - setTabTextColor(pos, color); + tabBar()->setTabTextColor(pos, color); } void KonqFrameTabs::replaceChildFrame(KonqFrameBase *oldFrame, KonqFrameBase *newFrame) diff --git a/src/ktabbar.h b/src/ktabbar.h new file mode 100644 --- /dev/null +++ b/src/ktabbar.h @@ -0,0 +1,111 @@ +/* This file is part of the KDE libraries + Copyright (C) 2003 Stephan Binner + Copyright (C) 2003 Zack Rusin + Copyright (C) 2009 Urs Wolfer + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KTABBAR_H +#define KTABBAR_H + +#include + +/** + * A QTabBar with extended features. + * + */ +class KTabBar : public QTabBar +{ + Q_OBJECT + +public: + /** + * Creates a new tab bar. + * + * @param parent The parent widget. + */ + explicit KTabBar(QWidget *parent = nullptr); + + /** + * Destroys the tab bar. + */ + virtual ~KTabBar(); + +Q_SIGNALS: + /** + * A right mouse button click was performed over the tab with the @param index. + * The signal is emitted on the press of the mouse button. + */ + void contextMenu(int index, const QPoint &globalPos); + /** + * A right mouse button click was performed over the empty area on the tab bar. + * The signal is emitted on the press of the mouse button. + */ + void emptyAreaContextMenu(const QPoint &globalPos); + /** + * A double left mouse button click was performed over the tab with the @param index. + * The signal is emitted on the second press of the mouse button, before the release. + */ + void tabDoubleClicked(int index); + /** + * A double left mouse button click was performed over the empty area on the tab bar. + * The signal is emitted on the second press of the mouse button, before the release. + */ + void newTabRequest(); + /** + * A double middle mouse button click was performed over the tab with the @param index. + * The signal is emitted on the release of the mouse button. + */ + void mouseMiddleClick(int index); + void initiateDrag(int); + void testCanDecode(const QDragMoveEvent *, bool &); + void receivedDropEvent(int, QDropEvent *); + /** + * Used internally by KTabBar's/KTabWidget's middle-click tab moving mechanism. + * Tells the KTabWidget which owns the KTabBar to move a tab. + */ + void moveTab(int, int); +#ifndef QT_NO_WHEELEVENT + void wheelDelta(int); +#endif + +protected: + void mouseDoubleClickEvent(QMouseEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; +#ifndef QT_NO_WHEELEVENT + void wheelEvent(QWheelEvent *event) override; +#endif + + void dragEnterEvent(QDragEnterEvent *event) override; + void dragMoveEvent(QDragMoveEvent *event) override; + void dropEvent(QDropEvent *event) override; + + void tabLayoutChange() override; + +private Q_SLOTS: + void activateDragSwitchTab(); + +private: + int selectTab(const QPoint &pos) const; + +private: + class Private; + Private *const d; +}; + +#endif diff --git a/src/ktabbar.cpp b/src/ktabbar.cpp new file mode 100644 --- /dev/null +++ b/src/ktabbar.cpp @@ -0,0 +1,231 @@ +/* This file is part of the KDE libraries + Copyright (C) 2003 Stephan Binner + Copyright (C) 2003 Zack Rusin + Copyright (C) 2009 Urs Wolfer + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "ktabbar.h" + +#include +#include +#include +#include + +class Q_DECL_HIDDEN KTabBar::Private +{ +public: + Private() + : + mDragSwitchTab(-1), + mActivateDragSwitchTabTimer(nullptr), + mMiddleMouseTabMoveInProgress(false) + { + } + + QPoint mDragStart; + int mDragSwitchTab; + QTimer *mActivateDragSwitchTabTimer; + + bool mMiddleMouseTabMoveInProgress : 1; + +}; + +KTabBar::KTabBar(QWidget *parent) + : QTabBar(parent), + d(new Private) +{ + setAcceptDrops(true); + setMouseTracking(true); + + d->mActivateDragSwitchTabTimer = new QTimer(this); + d->mActivateDragSwitchTabTimer->setSingleShot(true); + connect(d->mActivateDragSwitchTabTimer, SIGNAL(timeout()), SLOT(activateDragSwitchTab())); +} + +KTabBar::~KTabBar() +{ + delete d; +} + +void KTabBar::mouseDoubleClickEvent(QMouseEvent *event) +{ + if (event->button() != Qt::LeftButton) { + return; + } + + int tab = selectTab(event->pos()); + + if (tab == -1) { + emit newTabRequest(); + } else { + emit tabDoubleClicked(tab); + } + + QTabBar::mouseDoubleClickEvent(event); +} + +void KTabBar::mousePressEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) { + d->mDragStart = event->pos(); + } else if (event->button() == Qt::RightButton) { + int tab = selectTab(event->pos()); + if (tab != -1) { + emit contextMenu(tab, mapToGlobal(event->pos())); + } else { + emit emptyAreaContextMenu(mapToGlobal(event->pos())); + } + return; + } + + QTabBar::mousePressEvent(event); +} + +void KTabBar::mouseMoveEvent(QMouseEvent *event) +{ + if (event->buttons() == Qt::LeftButton && !isMovable()) { + int tab = selectTab(event->pos()); + if (d->mDragSwitchTab && tab != d->mDragSwitchTab) { + d->mActivateDragSwitchTabTimer->stop(); + d->mDragSwitchTab = 0; + } + + int delay = QApplication::startDragDistance(); + QPoint newPos = event->pos(); + if (newPos.x() > d->mDragStart.x() + delay || newPos.x() < d->mDragStart.x() - delay || + newPos.y() > d->mDragStart.y() + delay || newPos.y() < d->mDragStart.y() - delay) { + if (tab != -1) { + emit initiateDrag(tab); + return; + } + } + } + + QTabBar::mouseMoveEvent(event); +} + +void KTabBar::activateDragSwitchTab() +{ + int tab = selectTab(mapFromGlobal(QCursor::pos())); + if (tab != -1 && d->mDragSwitchTab == tab) { + setCurrentIndex(d->mDragSwitchTab); + } + + d->mDragSwitchTab = 0; +} + +void KTabBar::dragEnterEvent(QDragEnterEvent *event) +{ + int tab = selectTab(event->pos()); + if (tab != -1) { + bool accept = false; + // The receivers of the testCanDecode() signal has to adjust + // 'accept' accordingly. + emit testCanDecode(event, accept); + if (accept && tab != currentIndex()) { + d->mDragSwitchTab = tab; + d->mActivateDragSwitchTabTimer->start(QApplication::doubleClickInterval() * 2); + } + + event->setAccepted(accept); + return; + } + + QTabBar::dragEnterEvent(event); +} + +void KTabBar::dragMoveEvent(QDragMoveEvent *event) +{ + int tab = selectTab(event->pos()); + if (tab != -1) { + bool accept = false; + // The receivers of the testCanDecode() signal has to adjust + // 'accept' accordingly. + emit testCanDecode(event, accept); + if (accept && tab != currentIndex()) { + d->mDragSwitchTab = tab; + d->mActivateDragSwitchTabTimer->start(QApplication::doubleClickInterval() * 2); + } + + event->setAccepted(accept); + return; + } + + QTabBar::dragMoveEvent(event); +} + +void KTabBar::dropEvent(QDropEvent *event) +{ + int tab = selectTab(event->pos()); + if (tab != -1) { + d->mActivateDragSwitchTabTimer->stop(); + d->mDragSwitchTab = 0; + emit receivedDropEvent(tab, event); + return; + } + + QTabBar::dropEvent(event); +} + +#ifndef QT_NO_WHEELEVENT +void KTabBar::wheelEvent(QWheelEvent *event) +{ + if (!(event->orientation() == Qt::Horizontal)) { + if (receivers(SIGNAL(wheelDelta(int)))) { + emit(wheelDelta(event->delta())); + return; + } + int lastIndex = count() - 1; + //Set an invalid index as base case + int targetIndex = -1; + bool forward = event->delta() < 0; + if (forward && lastIndex == currentIndex()) { + targetIndex = 0; + } else if (!forward && 0 == currentIndex()) { + targetIndex = lastIndex; + } + //Will not move when targetIndex is invalid + setCurrentIndex(targetIndex); + //If it has not moved yet (targetIndex == -1), or if it moved but current tab is disabled + if (targetIndex != currentIndex() || !isTabEnabled(targetIndex)) { + QTabBar::wheelEvent(event); + } + event->accept(); + } else { + event->ignore(); + } +} +#endif + +void KTabBar::tabLayoutChange() +{ + d->mActivateDragSwitchTabTimer->stop(); + d->mDragSwitchTab = 0; +} + +int KTabBar::selectTab(const QPoint &pos) const +{ + const int tabCount = count(); + for (int i = 0; i < tabCount; ++i) + if (tabRect(i).contains(pos)) { + return i; + } + + return -1; +} + diff --git a/src/ktabwidget.h b/src/ktabwidget.h new file mode 100644 --- /dev/null +++ b/src/ktabwidget.h @@ -0,0 +1,188 @@ +/* This file is part of the KDE libraries + Copyright (C) 2003 Stephan Binner + Copyright (C) 2003 Zack Rusin + Copyright (C) 2009 Urs Wolfer + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KTABWIDGET_H +#define KTABWIDGET_H + +#include + +class QTab; + +/** + * \brief A widget containing multiple tabs + * + * It extends the Qt QTabWidget, providing extra features such as automatic resizing of tabs, and moving tabs. + * + * See also the QTabWidget documentation. + */ +class KTabWidget : public QTabWidget +{ + Q_OBJECT + Q_PROPERTY(bool automaticResizeTabs READ automaticResizeTabs WRITE setAutomaticResizeTabs) + +public: + + /** + * Creates a new tab widget. + * + * @param parent The parent widgets. + * @param flags The Qt window flags @see QWidget. + */ + explicit KTabWidget(QWidget *parent = nullptr, Qt::WindowFlags flags = nullptr); + + /** + * Destroys the tab widget. + */ + virtual ~KTabWidget(); + + /** + * Returns true if calling setTitle() will resize tabs + * to the width of the tab bar. + */ + bool automaticResizeTabs() const; + + /** + * Reimplemented for internal reasons. + */ + QString tabText(int) const; // but it's not virtual... + + /** + * Reimplemented for internal reasons. + */ + void setTabText(int, const QString &); + +public Q_SLOTS: + /** + * Move a widget's tab from first to second specified index and emit + * signal movedTab( int, int ) afterwards. + */ + virtual void moveTab(int, int); + + /** + * Removes the widget, reimplemented for + * internal reasons (keeping labels in sync). + */ + virtual void removeTab(int index); // but it's not virtual in QTabWidget... + + /** + * If \a enable is true, tabs will be resized to the width of the tab bar. + * + * Does not work reliably with "QTabWidget* foo=new KTabWidget()" and if + * you change tabs via the tabbar or by accessing tabs directly. + */ + void setAutomaticResizeTabs(bool enable); + +Q_SIGNALS: + /** + * Connect to this and set accept to true if you can and want to decode the event. + */ + void testCanDecode(const QDragMoveEvent *e, bool &accept /* result */); + + /** + * Received an event in the empty space beside tabbar. Usually creates a new tab. + * This signal is only possible after testCanDecode and positive accept result. + */ + void receivedDropEvent(QDropEvent *); + + /** + * Received an drop event on given widget's tab. + * This signal is only possible after testCanDecode and positive accept result. + */ + void receivedDropEvent(QWidget *, QDropEvent *); + + /** + * Request to start a drag operation on the given tab. + */ + void initiateDrag(QWidget *); + + /** + * The right mouse button was pressed over empty space besides tabbar. + */ + void contextMenu(const QPoint &); + + /** + * The right mouse button was pressed over a widget. + */ + void contextMenu(QWidget *, const QPoint &); + + /** + * A double left mouse button click was performed over empty space besides tabbar. + * The signal is emitted on the second press of the mouse button, before the release. + */ + void mouseDoubleClick(); + + /** + * A double left mouse button click was performed over the widget. + * The signal is emitted on the second press of the mouse button, before the release. + */ + void mouseDoubleClick(QWidget *); + + /** + * A middle mouse button click was performed over empty space besides tabbar. + * The signal is emitted on the release of the mouse button. + */ + void mouseMiddleClick(); + + /** + * A middle mouse button click was performed over the widget. + * The signal is emitted on the release of the mouse button. + */ + void mouseMiddleClick(QWidget *); + + /** + * The close button of a widget's tab was clicked. This signal is + * only possible after you have called setCloseButtonEnabled( true ). + */ + void closeRequest(QWidget *); + +protected: + void mouseDoubleClickEvent(QMouseEvent *) override; + void mousePressEvent(QMouseEvent *) override; + void mouseReleaseEvent(QMouseEvent *) override; + void dragEnterEvent(QDragEnterEvent *) override; + void dragMoveEvent(QDragMoveEvent *) override; + void dropEvent(QDropEvent *) override; + int tabBarWidthForMaxChars(int); +#ifndef QT_NO_WHEELEVENT + void wheelEvent(QWheelEvent *) override; +#endif + void resizeEvent(QResizeEvent *) override; + void tabInserted(int) override; + +protected Q_SLOTS: + virtual void receivedDropEvent(int, QDropEvent *); + virtual void initiateDrag(int); + virtual void contextMenu(int, const QPoint &); + virtual void mouseDoubleClick(int); + virtual void mouseMiddleClick(int); + virtual void closeRequest(int); +#ifndef QT_NO_WHEELEVENT + virtual void wheelDelta(int); +#endif + +private: + class Private; + Private *const d; + + Q_PRIVATE_SLOT(d, void slotTabMoved(int, int)) +}; + +#endif diff --git a/src/ktabwidget.cpp b/src/ktabwidget.cpp new file mode 100644 --- /dev/null +++ b/src/ktabwidget.cpp @@ -0,0 +1,524 @@ +/* This file is part of the KDE libraries + Copyright (C) 2003 Stephan Binner + Copyright (C) 2003 Zack Rusin + Copyright (C) 2009 Urs Wolfer + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "ktabwidget.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "ktabbar.h" + +#include + +class Q_DECL_HIDDEN KTabWidget::Private +{ +public: + enum { + ResizeEnabled = 0, + ResizeDisabled, + ResizeLater + } m_resizeSuspend; + + Private(KTabWidget *parent) + : m_resizeSuspend(ResizeEnabled), + m_parent(parent), + m_automaticResizeTabs(false), + m_tabBarHidden(false) + { + + KConfigGroup cg(KSharedConfig::openConfig(), "General"); + m_maxLength = cg.readEntry("MaximumTabLength", 30); + m_minLength = cg.readEntry("MinimumTabLength", 3); + Q_ASSERT(m_maxLength >= m_minLength); + m_currentTabLength = m_minLength; + } + + KTabWidget *m_parent; + bool m_automaticResizeTabs; + bool m_tabBarHidden; + int m_maxLength; + int m_minLength; + int m_currentTabLength; + + //holds the full names of the tab, otherwise all we + //know about is the shortened name + QStringList m_tabNames; + + bool isEmptyTabbarSpace(const QPoint &) const; + void resizeTabs(int changedTabIndex = -1); + void updateTab(int index); + void removeTab(int index); + + void slotTabMoved(int from, int to); +}; + +bool KTabWidget::Private::isEmptyTabbarSpace(const QPoint &point) const +{ + if (m_parent->count() == 0) { + return true; + } + if (m_parent->tabBar()->isHidden()) { + return false; + } + QSize size(m_parent->tabBar()->sizeHint()); + if ((m_parent->tabPosition() == QTabWidget::North && point.y() < size.height()) || + (m_parent->tabPosition() == QTabWidget::South && point.y() > (m_parent->height() - size.height()))) { + + QWidget *rightcorner = m_parent->cornerWidget(Qt::TopRightCorner); + if (rightcorner && rightcorner->isVisible()) { + if (point.x() >= m_parent->width() - rightcorner->width()) { + return false; + } + } + + QWidget *leftcorner = m_parent->cornerWidget(Qt::TopLeftCorner); + if (leftcorner && leftcorner->isVisible()) { + if (point.x() <= leftcorner->width()) { + return false; + } + } + + for (int i = 0; i < m_parent->count(); ++i) + if (m_parent->tabBar()->tabRect(i).contains(m_parent->tabBar()->mapFromParent(point))) { + return false; + } + + return true; + } + + return false; +} + +void KTabWidget::Private::removeTab(int index) +{ + // prevent cascading resize slowness, not to mention crashes due to tab count() + // and m_tabNames.count() being out of sync! + m_resizeSuspend = ResizeDisabled; + + // Need to do this here, rather than in tabRemoved(). Calling + // QTabWidget::removeTab() below may cause a relayout of the tab bar, which + // will call resizeTabs() immediately. If m_automaticResizeTabs is true, + // that will use the m_tabNames[] list before it has been updated to reflect + // the new tab arrangement. See bug 190528. + m_tabNames.removeAt(index); + + m_parent->QTabWidget::removeTab(index); + + const bool doResize = (m_resizeSuspend == ResizeLater) || m_automaticResizeTabs; + m_resizeSuspend = ResizeEnabled; + if (doResize) { + resizeTabs(); + } + +} + +void KTabWidget::Private::resizeTabs(int changeTabIndex) +{ + if (m_resizeSuspend != ResizeEnabled) { + m_resizeSuspend = ResizeLater; + return; + } + + int newTabLength = m_maxLength; + + if (m_automaticResizeTabs) { + // Calculate new max length + int lcw = 0, rcw = 0; + + const int tabBarHeight = m_parent->tabBar()->sizeHint().height(); + if (m_parent->cornerWidget(Qt::TopLeftCorner) && + m_parent->cornerWidget(Qt::TopLeftCorner)->isVisible()) { + lcw = qMax(m_parent->cornerWidget(Qt::TopLeftCorner)->width(), tabBarHeight); + } + if (m_parent->cornerWidget(Qt::TopRightCorner) && + m_parent->cornerWidget(Qt::TopRightCorner)->isVisible()) { + rcw = qMax(m_parent->cornerWidget(Qt::TopRightCorner)->width(), tabBarHeight); + } + + const int maxTabBarWidth = m_parent->width() - lcw - rcw; + + // binary search for the best fitting tab title length; some wiggling was + // required to make this behave in the face of rounding. + int newTabLengthHi = m_maxLength + 1; + int newTabLengthLo = m_minLength; + int prevTabLengthMid = -1; + while (true) { + int newTabLengthMid = (newTabLengthHi + newTabLengthLo) / 2; + if (prevTabLengthMid == newTabLengthMid) { + // no change, we're stuck due to rounding. + break; + } + prevTabLengthMid = newTabLengthMid; + + if (m_parent->tabBarWidthForMaxChars(newTabLengthMid) > maxTabBarWidth) { + newTabLengthHi = newTabLengthMid; + } else { + newTabLengthLo = newTabLengthMid; + } + } + newTabLength = qMin(newTabLengthLo, m_maxLength); + } + + // Update hinted or all tabs + if (m_currentTabLength != newTabLength) { + m_currentTabLength = newTabLength; + for (int i = 0; i < m_parent->count(); i++) { + updateTab(i); + } + } else if (changeTabIndex != -1) { + updateTab(changeTabIndex); + } +} + +void KTabWidget::Private::updateTab(int index) +{ + QString title = m_automaticResizeTabs ? m_tabNames[ index ] : m_parent->QTabWidget::tabText(index); + m_parent->setTabToolTip(index, QString()); + + if (title.length() > m_currentTabLength) { + QString toolTipText = title; + // Remove '&'s, which are indicators for keyboard shortcuts in tab titles. "&&" is replaced by '&'. + for (int i = toolTipText.indexOf('&'); i >= 0 && i < toolTipText.length(); i = toolTipText.indexOf('&', i + 1)) { + toolTipText.remove(i, 1); + } + + if (Qt::mightBeRichText(toolTipText)) { + m_parent->setTabToolTip(index, toolTipText.toHtmlEscaped()); + } else { + m_parent->setTabToolTip(index, toolTipText); + } + } + + title = KStringHandler::rsqueeze(title, m_currentTabLength).leftJustified(m_minLength, ' '); + + if (m_parent->QTabWidget::tabText(index) != title) { + m_parent->QTabWidget::setTabText(index, title); + } +} + +void KTabWidget::Private::slotTabMoved(int from, int to) +{ + /* called from Qt slot when Qt has moved the tab, so we only + need to adjust the m_tabNames list */ + if (m_automaticResizeTabs) { + QString movedName = m_tabNames.takeAt(from); + m_tabNames.insert(to, movedName); + } +} + +KTabWidget::KTabWidget(QWidget *parent, Qt::WindowFlags flags) + : QTabWidget(parent), + d(new Private(this)) +{ + setWindowFlags(flags); + setTabBar(new KTabBar(this)); + setObjectName("tabbar"); + setAcceptDrops(true); + + connect(tabBar(), SIGNAL(contextMenu(int,QPoint)), SLOT(contextMenu(int,QPoint))); + connect(tabBar(), SIGNAL(tabDoubleClicked(int)), SLOT(mouseDoubleClick(int))); + connect(tabBar(), SIGNAL(newTabRequest()), this, SIGNAL(mouseDoubleClick())); // #185487 + connect(tabBar(), SIGNAL(mouseMiddleClick(int)), SLOT(mouseMiddleClick(int))); + connect(tabBar(), SIGNAL(initiateDrag(int)), SLOT(initiateDrag(int))); + connect(tabBar(), SIGNAL(testCanDecode(const QDragMoveEvent*,bool&)), SIGNAL(testCanDecode(const QDragMoveEvent*,bool&))); + connect(tabBar(), SIGNAL(receivedDropEvent(int,QDropEvent*)), SLOT(receivedDropEvent(int,QDropEvent*))); + connect(tabBar(), SIGNAL(tabMoved(int,int)), SLOT(slotTabMoved(int,int))); +} + +KTabWidget::~KTabWidget() +{ + delete d; +} + +int KTabWidget::tabBarWidthForMaxChars(int maxLength) +{ + const int hframe = tabBar()->style()->pixelMetric(QStyle::PM_TabBarTabHSpace, nullptr, tabBar()); + + const QFontMetrics fm = tabBar()->fontMetrics(); + int x = 0; + for (int i = 0; i < count(); ++i) { + QString newTitle = d->m_tabNames.value(i); + newTitle = KStringHandler::rsqueeze(newTitle, maxLength).leftJustified(d->m_minLength, ' '); + + int lw = fm.boundingRect(newTitle).width(); + int iw = 0; + if (!tabBar()->tabIcon(i).isNull()) { + iw = tabBar()->tabIcon(i).pixmap(style()->pixelMetric(QStyle::PM_SmallIconSize), QIcon::Normal).width() + 4; + } + if (tabsClosable()) { + // FIXME: how to get the size of the close button directly from the tabBar()? + iw += KIconLoader::SizeSmall * 3 / 2; + } + x += (tabBar()->style()->sizeFromContents(QStyle::CT_TabBarTab, nullptr, + QSize(qMax(lw + hframe + iw, QApplication::globalStrut().width()), 0), + this)).width(); + } + + return x; +} + +QString KTabWidget::tabText(int index) const +{ + if (d->m_automaticResizeTabs) { + if (index >= 0 && index < count()) { + if (index >= d->m_tabNames.count()) { + // Ooops, the tab exists, but tabInserted wasn't called yet. + // This can happen when inserting the first tab, + // and calling tabText from slotCurrentChanged, + // see KTabWidget_UnitTest. + const_cast(this)->tabInserted(index); + } + return d->m_tabNames[ index ]; + } else { + return QString(); + } + } else { + return QTabWidget::tabText(index); + } +} + +void KTabWidget::setTabText(int index, const QString &text) +{ + if (text == tabText(index)) { + return; + } + + if (d->m_automaticResizeTabs) { + + tabBar()->setUpdatesEnabled(false); //no flicker + + QTabWidget::setTabText(index, text); + + if (index != -1) { + if (index >= d->m_tabNames.count()) { + kWarning(240) << "setTabText(" << index << ") called but d->m_tabNames has only" << d->m_tabNames.count() << "entries"; + while (index >= d->m_tabNames.count()) { + d->m_tabNames.append(QString()); + } + } + d->m_tabNames[ index ] = text; + d->resizeTabs(index); + } + + tabBar()->setUpdatesEnabled(true); + + } else { + QTabWidget::setTabText(index, text); + } +} + +void KTabWidget::dragEnterEvent(QDragEnterEvent *event) +{ + if (d->isEmptyTabbarSpace(event->pos())) { + bool accept = false; + // The receivers of the testCanDecode() signal has to adjust + // 'accept' accordingly. + emit testCanDecode(event, accept); + + event->setAccepted(accept); + return; + } + + QTabWidget::dragEnterEvent(event); +} + +void KTabWidget::dragMoveEvent(QDragMoveEvent *event) +{ + if (d->isEmptyTabbarSpace(event->pos())) { + bool accept = false; + // The receivers of the testCanDecode() signal has to adjust + // 'accept' accordingly. + emit testCanDecode(event, accept); + + event->setAccepted(accept); + return; + } + + QTabWidget::dragMoveEvent(event); +} + +void KTabWidget::dropEvent(QDropEvent *event) +{ + if (d->isEmptyTabbarSpace(event->pos())) { + emit(receivedDropEvent(event)); + return; + } + + QTabWidget::dropEvent(event); +} + +#ifndef QT_NO_WHEELEVENT +void KTabWidget::wheelEvent(QWheelEvent *event) +{ + if (d->isEmptyTabbarSpace(event->pos())) { + QCoreApplication::sendEvent(tabBar(), event); + } else { + QTabWidget::wheelEvent(event); + } +} + +void KTabWidget::wheelDelta(int delta) +{ + if (count() < 2) { + return; + } + + int page = currentIndex(); + if (delta < 0) { + page = (page + 1) % count(); + } else { + page--; + if (page < 0) { + page = count() - 1; + } + } + setCurrentIndex(page); +} +#endif + +void KTabWidget::mouseDoubleClickEvent(QMouseEvent *event) +{ + if (event->button() != Qt::LeftButton) { + return; + } + + if (d->isEmptyTabbarSpace(event->pos())) { + emit(mouseDoubleClick()); + return; + } + + QTabWidget::mouseDoubleClickEvent(event); +} + +void KTabWidget::mousePressEvent(QMouseEvent *event) +{ + if (event->button() == Qt::RightButton) { + if (d->isEmptyTabbarSpace(event->pos())) { + emit(contextMenu(mapToGlobal(event->pos()))); + return; + } + } + + QTabWidget::mousePressEvent(event); +} + +void KTabWidget::mouseReleaseEvent(QMouseEvent *event) +{ + if (event->button() == Qt::MidButton) { + if (d->isEmptyTabbarSpace(event->pos())) { + emit(mouseMiddleClick()); + return; + } + } + + QTabWidget::mouseReleaseEvent(event); +} + +void KTabWidget::receivedDropEvent(int index, QDropEvent *event) +{ + emit(receivedDropEvent(widget(index), event)); +} + +void KTabWidget::initiateDrag(int index) +{ + emit(initiateDrag(widget(index))); +} + +void KTabWidget::contextMenu(int index, const QPoint &point) +{ + emit(contextMenu(widget(index), point)); +} + +void KTabWidget::mouseDoubleClick(int index) +{ + emit(mouseDoubleClick(widget(index))); +} + +void KTabWidget::mouseMiddleClick(int index) +{ + emit(mouseMiddleClick(widget(index))); +} + +void KTabWidget::removeTab(int index) +{ + if (d->m_automaticResizeTabs) { + const bool wasUpdatesEnabled = updatesEnabled(); + setUpdatesEnabled(false); + d->removeTab(index); + setUpdatesEnabled(wasUpdatesEnabled); + } else { + d->removeTab(index); + } +} + +void KTabWidget::setAutomaticResizeTabs(bool enabled) +{ + if (d->m_automaticResizeTabs == enabled) { + return; + } + + setUpdatesEnabled(false); + + d->m_automaticResizeTabs = enabled; + if (enabled) { + d->m_tabNames.clear(); + for (int i = 0; i < count(); ++i) { + d->m_tabNames.append(tabBar()->tabText(i)); + } + } else + for (int i = 0; i < count(); ++i) { + tabBar()->setTabText(i, d->m_tabNames[ i ]); + } + + d->resizeTabs(); + + setUpdatesEnabled(true); +} + +bool KTabWidget::automaticResizeTabs() const +{ + return d->m_automaticResizeTabs; +} + +void KTabWidget::resizeEvent(QResizeEvent *event) +{ + QTabWidget::resizeEvent(event); + d->resizeTabs(); +} + +void KTabWidget::tabInserted(int idx) +{ + d->m_tabNames.insert(idx, tabBar()->tabText(idx)); +} + +#include "moc_ktabwidget.cpp"