kdelibs/kdeui/widgets/ktabwidget.cpp
2014-11-13 01:04:59 +02:00

726 lines
20 KiB
C++

/* This file is part of the KDE libraries
Copyright (C) 2003 Stephan Binner <binner@kde.org>
Copyright (C) 2003 Zack Rusin <zack@kde.org>
Copyright (C) 2009 Urs Wolfer <uwolfer @ kde.org>
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 <QtGui/QApplication>
#include <QtGui/QDragMoveEvent>
#include <QtGui/QDropEvent>
#include <QtGui/QMouseEvent>
#include <QtGui/QStyle>
#include <QtGui/QStyleOption>
#include <QtGui/QTextDocument>
#include <QtGui/QWheelEvent>
#include <QtCore/QList>
#include <ksharedconfig.h>
#include <kiconloader.h>
#include <kstringhandler.h>
#include <kdebug.h>
#include <ktabbar.h>
#include <kconfiggroup.h>
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<KTabBar*>(tabBar())->setTabReorderingEnabled( on );
}
#endif
#ifndef KDE_NO_DEPRECATED
bool KTabWidget::isTabReorderingEnabled() const
{
return static_cast<KTabBar*>(tabBar())->isTabReorderingEnabled();
}
#endif
#ifndef KDE_NO_DEPRECATED
void KTabWidget::setTabCloseActivatePrevious( bool previous)
{
static_cast<KTabBar*>(tabBar())->setTabCloseActivatePrevious( previous );
}
#endif
#ifndef KDE_NO_DEPRECATED
bool KTabWidget::tabCloseActivatePrevious() const
{
return static_cast<KTabBar*>(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<KTabWidget*>(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<KTabBar*>( tabBar() )->setTabsClosable( enable );
}
#endif
#ifndef KDE_NO_DEPRECATED
bool KTabWidget::isCloseButtonEnabled() const
{
return static_cast<KTabBar*>( 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"