/* 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 #include class 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(KGlobal::config(), "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, Qt::escape( toolTipText ) ); 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(moveTab(int,int)), SLOT(moveTab(int,int))); connect(tabBar(), SIGNAL(tabMoved(int,int)), SLOT(slotTabMoved(int,int))); connect(tabBar(), SIGNAL(tabCloseRequested(int)), SLOT(closeRequest(int))); } KTabWidget::~KTabWidget() { delete d; } /*void KTabWidget::insertTab( QWidget *child, const QString &label, int index ) { QTabWidget::insertTab( child, label, index ); } void KTabWidget::insertTab( QWidget *child, const QIcon& iconset, const QString &label, int index ) { QTabWidget::insertTab( child, iconset, label, index ); } void KTabWidget::insertTab( QWidget *child, QTab *tab, int index ) { QTabWidget::insertTab( child, tab, index); if ( d->m_automaticResizeTabs ) { if ( index < 0 || index >= count() ) { d->m_tabNames.append( tab->text() ); d->resizeTabs( d->m_tabNames.count()-1 ); } else { d->m_tabNames.insert( d->m_tabNames.at( index ), tab->text() ); d->resizeTabs( index ); } } }*/ void KTabWidget::setTabBarHidden( bool hide ) { if (hide == isTabBarHidden()) return; QWidget *rightcorner = cornerWidget( Qt::TopRightCorner ); QWidget *leftcorner = cornerWidget( Qt::TopLeftCorner ); d->m_tabBarHidden = hide; if ( hide ) { if ( leftcorner ) leftcorner->hide(); if ( rightcorner ) rightcorner->hide(); tabBar()->hide(); } else { tabBar()->show(); if ( leftcorner ) leftcorner->show(); if ( rightcorner ) rightcorner->show(); } } bool KTabWidget::isTabBarHidden() const { return d->m_tabBarHidden; } void KTabWidget::setTabTextColor( int index, const QColor& color ) { tabBar()->setTabTextColor( index, color ); } QColor KTabWidget::tabTextColor( int index ) const { return tabBar()->tabTextColor( index ); } #ifndef KDE_NO_DEPRECATED void KTabWidget::setTabReorderingEnabled( bool on) { static_cast(tabBar())->setTabReorderingEnabled( on ); } #endif #ifndef KDE_NO_DEPRECATED bool KTabWidget::isTabReorderingEnabled() const { return static_cast(tabBar())->isTabReorderingEnabled(); } #endif #ifndef KDE_NO_DEPRECATED void KTabWidget::setTabCloseActivatePrevious( bool previous) { static_cast(tabBar())->setTabCloseActivatePrevious( previous ); } #endif #ifndef KDE_NO_DEPRECATED bool KTabWidget::tabCloseActivatePrevious() const { return static_cast(tabBar())->tabCloseActivatePrevious(); } #endif int KTabWidget::tabBarWidthForMaxChars( int maxLength ) { int hframe, overlap; hframe = tabBar()->style()->pixelMetric( QStyle::PM_TabBarTabHSpace, 0L, tabBar() ); overlap = tabBar()->style()->pixelMetric( QStyle::PM_TabBarTabOverlap, 0L, 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.width( newTitle ); int iw = 0; if ( !tabBar()->tabIcon( i ).isNull() ) { iw = tabBar()->tabIcon( i ).pixmap( style()->pixelMetric( QStyle::PM_SmallIconSize ), QIcon::Normal ).width() + 4; } #ifndef KDE_NO_DEPRECATED if ( isCloseButtonEnabled() ) { // FIXME: how to get the size of the close button directly from the tabBar()? iw += KIconLoader::SizeSmall * 3 / 2; } #endif x += ( tabBar()->style()->sizeFromContents( QStyle::CT_TabBarTab, 0L, 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::moveTab( int from, int to ) { setUpdatesEnabled(false); const QString tablabel = tabText( from ); QWidget *w = widget( from ); const QColor color = tabTextColor( from ); const QIcon tabiconset = tabIcon( from ); const QString tabtooltip = tabToolTip( from ); const bool current = ( from == currentIndex() ); const bool enabled = isTabEnabled( from ); const bool blocked = blockSignals( true ); QWidget *fw = QApplication::focusWidget(); removeTab( from ); insertTab( to, w, tablabel ); // Don't lose focus due to moving the tab (#159295) // (removeTab hides the widget, which gives focus to the "next in chain", could be anything) if (w->isAncestorOf(fw)) { fw->setFocus(); } setTabIcon( to, tabiconset ); setTabText( to, tablabel ); setTabToolTip( to, tabtooltip ); setTabTextColor( to, color ); if ( current ) setCurrentIndex( to ); setTabEnabled( to, enabled ); if ( d->m_automaticResizeTabs ) { d->resizeTabs( to ); } blockSignals( blocked ); setUpdatesEnabled(true); #ifndef KDE_NO_DEPRECATED emit ( movedTab( from, to ) ); #endif } void KTabWidget::removePage( QWidget *widget ) { // not just calling removeTab() because that one is also virtual. const int index = indexOf(widget); if ( d->m_automaticResizeTabs ) { setUpdatesEnabled(false); d->removeTab(index); setUpdatesEnabled(true); } else { d->removeTab(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 ); } } #ifndef KDE_NO_DEPRECATED void KTabWidget::setHoverCloseButton( bool button ) { // deprecated setTabsClosable( button ); } #endif #ifndef KDE_NO_DEPRECATED bool KTabWidget::hoverCloseButton() const { // deprecated return false; } #endif #ifndef KDE_NO_DEPRECATED void KTabWidget::setHoverCloseButtonDelayed( bool delayed ) { // deprecated Q_UNUSED( delayed ); } #endif #ifndef KDE_NO_DEPRECATED bool KTabWidget::hoverCloseButtonDelayed() const { // deprecated return tabsClosable(); } #endif #ifndef KDE_NO_DEPRECATED void KTabWidget::setCloseButtonEnabled( bool enable ) { static_cast( tabBar() )->setTabsClosable( enable ); } #endif #ifndef KDE_NO_DEPRECATED bool KTabWidget::isCloseButtonEnabled() const { return static_cast( tabBar() )->tabsClosable(); } #endif 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::closeRequest( int index ) { emit( closeRequest( widget( index ) ) ); } void KTabWidget::resizeEvent( QResizeEvent *event ) { QTabWidget::resizeEvent( event ); d->resizeTabs(); } void KTabWidget::tabInserted( int idx ) { d->m_tabNames.insert( idx, tabBar()->tabText( idx ) ); } void KTabWidget::tabRemoved( int idx ) { Q_UNUSED(idx) // d->m_tabNames is now updated in KTabWidget::Private::removeTab() } /* This function is kept only for BC reasons, it is not useful anymore */ #ifndef KDE_NO_DEPRECATED void KTabWidget::currentChanged( int ) { } #endif #include "ktabwidget.moc"