You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
koffice/lib/kofficeui/KoTabBar.cpp

914 lines
22 KiB

/* This file is part of the KDE project
Copyright (C) 2003 Ariya Hidayat <ariya@kde.org>
Copyright (C) 2003 Norbert Andres <nandres@web.de>
Copyright (C) 2002 Laurent Montel <montel@kde.org>
Copyright (C) 1999 David Faure <faure@kde.org>
Copyright (C) 1999 Boris Wedl <boris.wedl@kfunigraz.ac.at>
Copyright (C) 1998-2000 Torben Weis <weis@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 "KoTabBar.h"
#include <tqdrawutil.h>
#include <tqpainter.h>
#include <tqstring.h>
#include <tqstringlist.h>
#include <tqstyle.h>
#include <tqtimer.h>
#include <tqtoolbutton.h>
#include <tqvaluevector.h>
#include <tqwidget.h>
// TODO
// improvement possibilities
// - use offscreen buffer to reduce flicker even more
// - keep track of tabs, only (re)layout when necessary
// - paint all tabs to buffer, show only by shifting
// - customizable button pixmaps
// - use TQStyle to paint the tabs & buttons (is it good/possible?)
class KoTabBarPrivate
{
public:
KoTabBar* tabbar;
// scroll buttons
TQToolButton* scrollFirstButton;
TQToolButton* scrollLastButton;
TQToolButton* scrollBackButton;
TQToolButton* scrollForwardButton;
// read-only: no mouse drag, double-click, right-click
bool readOnly;
// if true, layout is from right to left
bool reverseLayout;
// list of all tabs, in order of appearance
TQStringList tabs;
// array of TQRect for each visible tabs
TQValueVector<TQRect> tabRects;
// leftmost tab (or rightmost if reverseLayout)
int firstTab;
// rightmost tab (or leftmost if reverseLayout)
int lastTab;
// the active tab in the range form 1..n.
// if this value is 0, that means that no tab is active.
int activeTab;
// unusable space on the left, taken by the scroll buttons
int offset;
// when the user drag the tab (in order to move it)
// this is the target position, it's 0 if no tab is dragged
int targetTab;
// wheel movement since selected tab was last changed by the
// mouse wheel
int wheelDelta;
// true if autoscroll is active
bool autoScroll;
// calculate the bounding rectangle for each visible tab
void layoutTabs();
// reposition scroll buttons
void layoutButtons();
// find a tab whose bounding rectangle contains the pos
// return -1 if no such tab is found
int tabAt( const TQPoint& pos );
// draw a single tab
void drawTab( TQPainter& painter, TQRect& rect, const TQString& text, bool active );
// draw a marker to indicate tab moving
void drawMoveMarker( TQPainter& painter, int x, int y );
// update the enable/disable status of scroll buttons
void updateButtons();
};
// built-in pixmap for scroll-first button
static const char * arrow_leftmost_xpm[] = {
"10 10 2 1",
" c None",
". c #000000",
" ",
" . . ",
" . .. ",
" . ... ",
" . .... ",
" . ... ",
" . .. ",
" . . ",
" ",
" "};
// built-in pixmap for scroll-last button
static const char * arrow_rightmost_xpm[] = {
"10 10 2 1",
" c None",
". c #000000",
" ",
" . . ",
" .. . ",
" ... . ",
" .... . ",
" ... . ",
" .. . ",
" . . ",
" ",
" "};
// built-in pixmap for scroll-left button
static const char * arrow_left_xpm[] = {
"10 10 2 1",
" c None",
". c #000000",
" ",
" . ",
" .. ",
" ... ",
" .... ",
" ... ",
" .. ",
" . ",
" ",
" "};
// built-in pixmap for scroll-right button
static const char * arrow_right_xpm[] = {
"10 10 2 1",
" c None",
". c #000000",
" ",
" . ",
" .. ",
" ... ",
" .... ",
" ... ",
" .. ",
" . ",
" ",
" "};
void KoTabBarPrivate::layoutTabs()
{
tabRects.clear();
TQPainter painter( tabbar );
TQFont f = painter.font();
f.setBold( true );
painter.setFont( f );
TQFontMetrics fm = painter.fontMetrics();
if( !reverseLayout )
{
// left to right
int x = 0;
for( unsigned c = 0; c < tabs.count(); c++ )
{
TQRect rect;
if( (int)c >= firstTab-1 )
{
TQString text = tabs[ c ];
int tw = fm.width( text ) + 4;
rect = TQRect( x, 0, tw + 20, tabbar->height() );
x = x + tw + 20;
}
tabRects.append( rect );
}
lastTab = tabRects.count();
for( unsigned i = 0; i < tabRects.count(); i++ )
if( tabRects[i].right()-10+offset > tabbar->width() )
{
lastTab = i;
break;
}
}
else
{
// right to left
int x = tabbar->width() - offset;
for( unsigned c = 0; c < tabs.count(); c++ )
{
TQRect rect;
if( (int)c >= firstTab-1 )
{
TQString text = tabs[ c ];
int tw = fm.width( text ) + 4;
rect = TQRect( x - tw - 20, 0, tw + 20, tabbar->height() );
x = x - tw - 20;
}
tabRects.append( rect );
}
lastTab = tabRects.count();
for( unsigned i = tabRects.count()-1; i>0; i-- )
if( tabRects[i].left() > 0 )
{
lastTab = i+1;
break;
}
}
}
int KoTabBarPrivate::tabAt( const TQPoint& pos )
{
for( unsigned i = 0; i < tabRects.count(); i++ )
{
TQRect rect = tabRects[ i ];
if( rect.isNull() ) continue;
if( rect.contains( pos ) ) return i;
}
return -1; // not found
}
void KoTabBarPrivate::drawTab( TQPainter& painter, TQRect& rect, const TQString& text, bool active )
{
TQPointArray polygon;
if( !reverseLayout )
polygon.setPoints( 6, rect.x(), rect.y(),
rect.x(), rect.bottom()-3,
rect.x()+2, rect.bottom(),
rect.right()-4, rect.bottom(),
rect.right()-2, rect.bottom()-2,
rect.right()+5, rect.top() );
else
polygon.setPoints( 6, rect.right(), rect.top(),
rect.right(), rect.bottom()-3,
rect.right()-2, rect.bottom(),
rect.x()+4, rect.bottom(),
rect.x()+2, rect.bottom()-2,
rect.x()-5, rect.top() );
painter.save();
// fill it first
TQBrush bg = tabbar->colorGroup().background();
if( active ) bg = TQBrush(tabbar->colorGroup().base());
painter.setBrush( bg );
painter.setPen( TQPen( TQt::NoPen ) );
painter.drawPolygon( polygon );
// draw the lines
painter.setPen( tabbar->colorGroup().dark() );
if( !active )
painter.drawLine( rect.x()-25, rect.y(), rect.right()+25, rect.top() );
// TQt4: painter.setRenderHint( TQPainter::Antialiasing );
painter.drawPolyline( polygon );
painter.setPen( tabbar->colorGroup().buttonText() );
TQFont f = painter.font();
if( active ) f.setBold( true );
painter.setFont( f );
TQFontMetrics fm = painter.fontMetrics();
int tx = rect.x() + ( rect.width() - fm.width( text ) ) / 2;
int ty = rect.y() + ( rect.height() - fm.height() ) / 2 + fm.ascent();
painter.drawText( tx, ty, text );
painter.restore();
}
void KoTabBarPrivate::drawMoveMarker( TQPainter& painter, int x, int y )
{
TQPointArray movmark;
movmark.setPoints( 3, x, y, x + 7, y, x + 4, y + 6);
TQBrush oldBrush = painter.brush();
painter.setBrush( TQt::black );
painter.drawPolygon(movmark);
painter.setBrush( oldBrush );
}
void KoTabBarPrivate::layoutButtons()
{
int bw = tabbar->height();
int w = tabbar->width();
offset = bw * 4;
if( !reverseLayout )
{
scrollFirstButton->setGeometry( 0, 0, bw, bw );
scrollFirstButton->setPixmap( arrow_leftmost_xpm );
scrollBackButton->setGeometry( bw, 0, bw, bw );
scrollBackButton->setPixmap( arrow_left_xpm );
scrollForwardButton->setGeometry( bw*2, 0, bw, bw );
scrollForwardButton->setPixmap( arrow_right_xpm );
scrollLastButton->setGeometry( bw*3, 0, bw, bw );
scrollLastButton->setPixmap( arrow_rightmost_xpm );
}
else
{
scrollFirstButton->setGeometry( w-bw, 0, bw, bw );
scrollFirstButton->setPixmap( arrow_rightmost_xpm );
scrollBackButton->setGeometry( w-2*bw, 0, bw, bw );
scrollBackButton->setPixmap( arrow_right_xpm );
scrollForwardButton->setGeometry( w-3*bw, 0, bw, bw );
scrollForwardButton->setPixmap( arrow_left_xpm );
scrollLastButton->setGeometry( w-4*bw, 0, bw, bw );
scrollLastButton->setPixmap( arrow_leftmost_xpm );
}
}
void KoTabBarPrivate::updateButtons()
{
scrollFirstButton->setEnabled( tabbar->canScrollBack() );
scrollBackButton->setEnabled( tabbar->canScrollBack() );
scrollForwardButton->setEnabled( tabbar->canScrollForward() );
scrollLastButton->setEnabled( tabbar->canScrollForward() );
}
// creates a new tabbar
KoTabBar::KoTabBar( TQWidget* parent, const char* name )
: TQWidget( parent, name, TQt::WResizeNoErase | TQt::WRepaintNoErase )
{
d = new KoTabBarPrivate;
d->tabbar = this;
d->readOnly = false;
d->reverseLayout = false;
d->firstTab = 1;
d->lastTab = 0;
d->activeTab = 0;
d->targetTab = 0;
d->wheelDelta = 0;
d->autoScroll = false;
d->offset = 64;
// initialize the scroll buttons
d->scrollFirstButton = new TQToolButton( this );
connect( d->scrollFirstButton, TQT_SIGNAL( clicked() ),
this, TQT_SLOT( scrollFirst() ) );
d->scrollLastButton = new TQToolButton( this );
connect( d->scrollLastButton, TQT_SIGNAL( clicked() ),
this, TQT_SLOT( scrollLast() ) );
d->scrollBackButton = new TQToolButton( this );
connect( d->scrollBackButton, TQT_SIGNAL( clicked() ),
this, TQT_SLOT( scrollBack() ) );
d->scrollForwardButton = new TQToolButton( this );
connect( d->scrollForwardButton, TQT_SIGNAL( clicked() ),
this, TQT_SLOT( scrollForward() ) );
d->layoutButtons();
d->updateButtons();
}
// destroys the tabbar
KoTabBar::~KoTabBar()
{
delete d;
}
// adds a new visible tab
void KoTabBar::addTab( const TQString& text )
{
d->tabs.append( text );
update();
}
// removes a tab
void KoTabBar::removeTab( const TQString& text )
{
int i = d->tabs.findIndex( text );
if ( i == -1 ) return;
if ( d->activeTab == i + 1 )
d->activeTab = 0;
d->tabs.remove( text );
update();
}
// removes all tabs
void KoTabBar::clear()
{
d->tabs.clear();
d->activeTab = 0;
d->firstTab = 1;
update();
}
bool KoTabBar::readOnly() const
{
return d->readOnly;
}
void KoTabBar::setReadOnly( bool ro )
{
d->readOnly = ro;
}
bool KoTabBar::reverseLayout() const
{
return d->reverseLayout;
}
void KoTabBar::setReverseLayout( bool reverse )
{
if( reverse != d->reverseLayout )
{
d->reverseLayout = reverse;
d->layoutTabs();
d->layoutButtons();
d->updateButtons();
update();
}
}
void KoTabBar::setTabs( const TQStringList& list )
{
TQString left, active;
if( d->activeTab > 0 )
active = d->tabs[ d->activeTab-1 ];
if( d->firstTab > 0 )
left = d->tabs[ d->firstTab-1 ];
d->tabs = list;
if( !left.isNull() )
{
d->firstTab = d->tabs.findIndex( left ) + 1;
if( d->firstTab > (int)d->tabs.count() )
d->firstTab = 1;
if( d->firstTab <= 0 )
d->firstTab = 1;
}
d->activeTab = 0;
if( !active.isNull() )
setActiveTab( active );
update();
}
TQStringList KoTabBar::tabs() const
{
return d->tabs;
}
unsigned KoTabBar::count() const
{
return d->tabs.count();
}
bool KoTabBar::canScrollBack() const
{
if ( d->tabs.count() == 0 )
return false;
return d->firstTab > 1;
}
bool KoTabBar::canScrollForward() const
{
if ( d->tabs.count() == 0 )
return false;
return d->lastTab < (int)d->tabs.count();
}
void KoTabBar::scrollBack()
{
if ( !canScrollBack() )
return;
d->firstTab--;
if( d->firstTab < 1 ) d->firstTab = 1;
d->layoutTabs();
d->updateButtons();
update();
}
void KoTabBar::scrollForward()
{
if ( !canScrollForward() )
return;
d->firstTab ++;
if( d->firstTab > (int)d->tabs.count() )
d->firstTab = d->tabs.count();
d->layoutTabs();
d->updateButtons();
update();
}
void KoTabBar::scrollFirst()
{
if ( !canScrollBack() )
return;
d->firstTab = 1;
d->layoutTabs();
d->updateButtons();
update();
}
void KoTabBar::scrollLast()
{
if ( !canScrollForward() )
return;
d->layoutTabs();
if( !d->reverseLayout )
{
int fullWidth = d->tabRects[ d->tabRects.count()-1 ].right();
int delta = fullWidth - width() + d->offset;
for( unsigned i = 0; i < d->tabRects.count(); i++ )
if( d->tabRects[i].x() > delta )
{
d->firstTab = i+1;
break;
}
}
else
{
// FIXME optimize this, perhaps without loop
for( ; d->firstTab <= (int)d->tabRects.count();)
{
int x = d->tabRects[ d->tabRects.count()-1 ].x();
if( x > 0 ) break;
d->firstTab++;
d->layoutTabs();
}
}
d->layoutTabs();
d->updateButtons();
update();
}
void KoTabBar::ensureVisible( const TQString& tab )
{
int i = d->tabs.findIndex( tab );
if ( i == -1 )
return;
i++;
// already visible, then do nothing
if( ( i >= d->firstTab ) && ( i <= d->lastTab ) )
return;
if( i < d->firstTab )
while( i < d->firstTab )
scrollBack();
if( i > d->lastTab )
while( i > d->lastTab )
scrollForward();
}
void KoTabBar::moveTab( unsigned tab, unsigned target )
{
TQString tabName = d->tabs[ tab ];
TQStringList::Iterator it;
it = d->tabs.at( tab );
d->tabs.remove( it );
if( target > tab ) target--;
it = d->tabs.at( target );
if( target >= d->tabs.count() )
it = d->tabs.end();
d->tabs.insert( it, tabName );
if( d->activeTab == (int)tab+1 )
d->activeTab = target+1;
update();
}
void KoTabBar::setActiveTab( const TQString& text )
{
int i = d->tabs.findIndex( text );
if ( i == -1 )
return;
if ( i + 1 == d->activeTab )
return;
d->activeTab = i + 1;
d->updateButtons();
update();
emit tabChanged( text );
}
void KoTabBar::autoScrollBack()
{
if( !d->autoScroll ) return;
scrollBack();
if( !canScrollBack() )
d->autoScroll = false;
else
TQTimer::singleShot( 400, this, TQT_SLOT( autoScrollBack() ) );
}
void KoTabBar::autoScrollForward()
{
if( !d->autoScroll ) return;
scrollForward();
if( !canScrollForward() )
d->autoScroll = false;
else
TQTimer::singleShot( 400, this, TQT_SLOT( autoScrollForward() ) );
}
void KoTabBar::paintEvent( TQPaintEvent* )
{
if ( d->tabs.count() == 0 )
{
erase();
return;
}
TQPainter painter;
TQPixmap pm( size() );
pm.fill( colorGroup().background() );
painter.begin( TQT_TQPAINTDEVICE(&pm), this );
painter.setPen( colorGroup().dark() );
painter.drawLine( 0, 0, width(), 0 );
if( !d->reverseLayout )
painter.translate( 5, 0 );
d->layoutTabs();
d->updateButtons();
// draw first all non-active, visible tabs
for( int c = d->tabRects.count()-1; c>=0; c-- )
{
TQRect rect = d->tabRects[ c ];
if( rect.isNull() ) continue;
TQString text = d->tabs[ c ];
d->drawTab( painter, rect, text, false );
}
// draw the active tab
if( d->activeTab > 0 )
{
TQRect rect = d->tabRects[ d->activeTab-1 ];
if( !rect.isNull() )
{
TQString text = d->tabs[ d->activeTab-1 ];
d->drawTab( painter, rect, text, true );
}
}
// draw the move marker
if( d->targetTab > 0 )
{
int p = TQMIN( d->targetTab, (int)d->tabRects.count() );
TQRect rect = d->tabRects[ p-1 ];
if( !rect.isNull() )
{
int x = !d->reverseLayout ? rect.x() : rect.right()-7;
if( d->targetTab > (int)d->tabRects.count() )
x = !d->reverseLayout ? rect.right()-7 : rect.x()-3;
d->drawMoveMarker( painter, x, rect.y() );
}
}
painter.end();
if( !d->reverseLayout )
bitBlt( this, d->offset, 0, &pm );
else
bitBlt( this, 0, 0, &pm );
}
void KoTabBar::resizeEvent( TQResizeEvent* )
{
d->layoutButtons();
d->updateButtons();
update();
}
TQSize KoTabBar::sizeHint() const
{
return TQSize( 40, style().pixelMetric( TQStyle::PM_ScrollBarExtent, this ) );
}
void KoTabBar::renameTab( const TQString& old_name, const TQString& new_name )
{
TQStringList::Iterator it = d->tabs.find( old_name );
(*it) = new_name;
update();
}
TQString KoTabBar::activeTab() const
{
if( d->activeTab == 0 )
return TQString();
else
return d->tabs[ d->activeTab ];
}
void KoTabBar::mousePressEvent( TQMouseEvent* ev )
{
if ( d->tabs.count() == 0 )
{
erase();
return;
}
d->layoutTabs();
TQPoint pos = ev->pos();
if( !d->reverseLayout ) pos = pos - TQPoint( d->offset,0 );
int tab = d->tabAt( pos ) + 1;
if( ( tab > 0 ) && ( tab != d->activeTab ) )
{
d->activeTab = tab;
update();
emit tabChanged( d->tabs[ d->activeTab-1] );
// scroll if partially visible
if( d->tabRects[ tab-1 ].right() > width() - d->offset )
scrollForward();
}
if( ev->button() == Qt::RightButton )
if( !d->readOnly )
emit contextMenu( ev->globalPos() );
}
void KoTabBar::mouseReleaseEvent( TQMouseEvent* ev )
{
if ( d->readOnly ) return;
d->autoScroll = false;
if ( ev->button() == Qt::LeftButton && d->targetTab != 0 )
{
emit tabMoved( d->activeTab-1, d->targetTab-1 );
d->targetTab = 0;
}
}
void KoTabBar::mouseMoveEvent( TQMouseEvent* ev )
{
if ( d->readOnly ) return;
TQPoint pos = ev->pos();
if( !d->reverseLayout) pos = pos - TQPoint( d->offset,0 );
// check if user drags a tab to move it
int i = d->tabAt( pos ) + 1;
if( ( i > 0 ) && ( i != d->targetTab ) )
{
if( i == d->activeTab ) i = 0;
if( i == d->activeTab+1 ) i = 0;
if( i != d->targetTab )
{
d->targetTab = i;
d->autoScroll = false;
update();
}
}
// drag past the very latest visible tab
// e.g move a tab to the last ordering position
TQRect r = d->tabRects[ d->tabRects.count()-1 ];
bool moveToLast = false;
if( r.isValid() )
{
if( !d->reverseLayout )
if( pos.x() > r.right() )
if( pos.x() < width() )
moveToLast = true;
if( d->reverseLayout )
if( pos.x() < r.x() )
if( pos.x() > 0 )
moveToLast = true;
}
if( moveToLast )
if( d->targetTab != (int)d->tabRects.count()+1 )
{
d->targetTab = d->tabRects.count()+1;
d->autoScroll = false;
update();
}
// outside far too left ? activate autoscroll...
if ( pos.x() < 0 && !d->autoScroll )
{
d->autoScroll = true;
autoScrollBack();
}
// outside far too right ? activate autoscroll...
int w = width() - d->offset;
if ( pos.x() > w && !d->autoScroll )
{
d->autoScroll = true;
autoScrollForward();
}
}
void KoTabBar::mouseDoubleClickEvent( TQMouseEvent* ev )
{
int offset = d->reverseLayout ? 0 : d->offset;
if( ev->pos().x() > offset )
if( !d->readOnly )
emit doubleClicked();
}
void KoTabBar::wheelEvent( TQWheelEvent * e )
{
if ( d->tabs.count() == 0 )
{
erase();
return;
}
// Currently one wheel movement is a delta of 120.
// The 'unused' delta is stored for devices that allow
// a higher scrolling resolution.
// The delta required to move one tab is one wheel movement:
const int deltaRequired = 120;
d->wheelDelta += e->delta();
int tabDelta = - (d->wheelDelta / deltaRequired);
d->wheelDelta = d->wheelDelta % deltaRequired;
int numTabs = d->tabs.size();
if(d->activeTab + tabDelta > numTabs)
{
// Would take us past the last tab
d->activeTab = numTabs;
}
else if (d->activeTab + tabDelta < 1)
{
// Would take us before the first tab
d->activeTab = 1;
}
else
{
d->activeTab = d->activeTab + tabDelta;
}
// Find the left and right edge of the new tab. If we're
// going forward, and the right of the new tab isn't visible
// then scroll forward. Likewise, if going back, and the
// left of the new tab isn't visible, then scroll back.
int activeTabRight = d->tabRects[ d->activeTab-1 ].right();
int activeTabLeft = d->tabRects[ d->activeTab-1 ].left();
if(tabDelta > 0 && activeTabRight > width() - d->offset )
{
scrollForward();
}
else if(tabDelta < 0 && activeTabLeft < width() - d->offset )
{
scrollBack();
}
update();
emit tabChanged( d->tabs[ d->activeTab-1] );
}
#include "KoTabBar.moc"