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.
qt3/src/widgets/qbutton.cpp

1009 lines
24 KiB

/****************************************************************************
**
** Implementation of QButton widget class
**
** Created : 940206
**
** Copyright (C) 1992-2008 Trolltech ASA. All rights reserved.
**
** This file is part of the widgets module of the Qt GUI Toolkit.
**
** This file may be used under the terms of the GNU General
** Public License versions 2.0 or 3.0 as published by the Free
** Software Foundation and appearing in the files LICENSE.GPL2
** and LICENSE.GPL3 included in the packaging of this file.
** Alternatively you may (at your option) use any later version
** of the GNU General Public License if such license has been
** publicly approved by Trolltech ASA (or its successors, if any)
** and the KDE Free Qt Foundation.
**
** Please review the following information to ensure GNU General
** Public Licensing requirements will be met:
** http://trolltech.com/products/qt/licenses/licensing/opensource/.
** If you are unsure which license is appropriate for your use, please
** review the following information:
** http://trolltech.com/products/qt/licenses/licensing/licensingoverview
** or contact the sales department at sales@trolltech.com.
**
** This file may be used under the terms of the Q Public License as
** defined by Trolltech ASA and appearing in the file LICENSE.QPL
** included in the packaging of this file. Licensees holding valid Qt
** Commercial licenses may use this file in accordance with the Qt
** Commercial License Agreement provided with the Software.
**
** This file is provided "AS IS" with NO WARRANTY OF ANY KIND,
** INCLUDING THE WARRANTIES OF DESIGN, MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE. Trolltech reserves all rights not granted
** herein.
**
**********************************************************************/
#undef QT_NO_COMPAT
#include "qbutton.h"
#ifndef QT_NO_BUTTON
#include "qbuttongroup.h"
#include "qbitmap.h"
#include "qpainter.h"
#include "qtimer.h"
#include "qaccel.h"
#include "qpixmapcache.h"
#include "qapplication.h"
#include "qpushbutton.h"
#include "qradiobutton.h"
#include "qguardedptr.h"
#include "../kernel/qinternal_p.h"
#if defined(QT_ACCESSIBILITY_SUPPORT)
#include "qaccessible.h"
#endif
#define AUTO_REPEAT_DELAY 300
#define AUTO_REPEAT_PERIOD 100
class QButtonData
{
public:
QButtonData() {
#ifndef QT_NO_BUTTONGROUP
group = 0;
#endif
#ifndef QT_NO_ACCEL
a = 0;
#endif
}
#ifndef QT_NO_BUTTONGROUP
QButtonGroup *group;
#endif
QTimer timer;
#ifndef QT_NO_ACCEL
QAccel *a;
#endif
};
void QButton::ensureData()
{
if ( !d ) {
d = new QButtonData;
Q_CHECK_PTR( d );
connect(&d->timer, SIGNAL(timeout()), this, SLOT(autoRepeatTimeout()));
}
}
/*!
Returns the group that this button belongs to.
If the button is not a member of any QButtonGroup, this function
returns 0.
\sa QButtonGroup
*/
QButtonGroup *QButton::group() const
{
#ifndef QT_NO_BUTTONGROUP
return d ? d->group : 0;
#else
return 0;
#endif
}
void QButton::setGroup( QButtonGroup* g )
{
#ifndef QT_NO_BUTTONGROUP
ensureData();
d->group = g;
#endif
}
QTimer *QButton::timer()
{
ensureData();
return &d->timer;
}
/*!
\class QButton qbutton.h
\brief The QButton class is the abstract base class of button
widgets, providing functionality common to buttons.
\ingroup abstractwidgets
<b>If you want to create a button use QPushButton.</b>
The QButton class implements an \e abstract button, and lets
subclasses specify how to reply to user actions and how to draw
the button.
QButton provides both push and toggle buttons. The QRadioButton
and QCheckBox classes provide only toggle buttons; QPushButton and
QToolButton provide both toggle and push buttons.
Any button can have either a text or pixmap label. setText() sets
the button to be a text button and setPixmap() sets it to be a
pixmap button. The text/pixmap is manipulated as necessary to
create the "disabled" appearance when the button is disabled.
QButton provides most of the states used for buttons:
\list
\i isDown() indicates whether the button is \e pressed down.
\i isOn() indicates whether the button is \e on.
Only toggle buttons can be switched on and off (see below).
\i isEnabled() indicates whether the button can be pressed by the
user.
\i setAutoRepeat() sets whether the button will auto-repeat
if the user holds it down.
\i setToggleButton() sets whether the button is a toggle
button or not.
\endlist
The difference between isDown() and isOn() is as follows: When the
user clicks a toggle button to toggle it on, the button is first
\e pressed and then released into the \e on state. When the user
clicks it again (to toggle it off), the button moves first to the
\e pressed state, then to the \e off state (isOn() and isDown()
are both FALSE).
Default buttons (as used in many dialogs) are provided by
QPushButton::setDefault() and QPushButton::setAutoDefault().
QButton provides five signals:
\list 1
\i pressed() is emitted when the button is pressed. E.g. with the mouse
or when animateClick() is called.
\i released() is emitted when the button is released. E.g. when the mouse
is released or the cursor is moved outside the widget.
\i clicked() is emitted when the button is first pressed and then
released when the accelerator key is typed, or when
animateClick() is called.
\i toggled(bool) is emitted when the state of a toggle button changes.
\i stateChanged(int) is emitted when the state of a tristate
toggle button changes.
\endlist
If the button is a text button with an ampersand (\&) in its text,
QButton creates an automatic accelerator key. This code creates a
push button labelled "Ro<u>c</u>k \& Roll" (where the c is
underlined). The button gets an automatic accelerator key, Alt+C:
\code
QPushButton *p = new QPushButton( "Ro&ck && Roll", this );
\endcode
In this example, when the user presses Alt+C the button will call
animateClick().
You can also set a custom accelerator using the setAccel()
function. This is useful mostly for pixmap buttons because they
have no automatic accelerator.
\code
p->setPixmap( QPixmap("print.png") );
p->setAccel( ALT+Key_F7 );
\endcode
All of the buttons provided by Qt (\l QPushButton, \l QToolButton,
\l QCheckBox and \l QRadioButton) can display both text and
pixmaps.
To subclass QButton, you must reimplement at least drawButton()
(to draw the button's outline) and drawButtonLabel() (to draw its
text or pixmap). It is generally advisable to reimplement
sizeHint() as well, and sometimes hitButton() (to determine
whether a button press is within the button).
To reduce flickering, QButton::paintEvent() sets up a pixmap that
the drawButton() function draws in. You should not reimplement
paintEvent() for a subclass of QButton unless you want to take
over all drawing.
\sa QButtonGroup
*/
/*!
\enum QButton::ToggleType
This enum type defines what a button can do in response to a
mouse/keyboard press:
\value SingleShot pressing the button causes an action, then the
button returns to the unpressed state.
\value Toggle pressing the button toggles it between an \c On and
an \c Off state.
\value Tristate pressing the button cycles between the three
states \c On, \c Off and \c NoChange
*/
/*!
\enum QButton::ToggleState
This enum defines the state of a toggle button.
\value Off the button is in the "off" state
\value NoChange the button is in the default/unchanged state
\value On the button is in the "on" state
*/
/*!
\property QButton::accel
\brief the accelerator associated with the button
This property is 0 if there is no accelerator set. If you set this
property to 0 then any current accelerator is removed.
*/
/*!
\property QButton::autoRepeat
\brief whether autoRepeat is enabled
If autoRepeat is enabled then the clicked() signal is emitted at
regular intervals if the button is down. This property has no
effect on toggle buttons. autoRepeat is off by default.
*/
/*! \property QButton::autoResize
\brief whether autoResize is enabled
\obsolete
If autoResize is enabled then the button will resize itself
whenever the contents are changed.
*/
/*!
\property QButton::down
\brief whether the button is pressed
If this property is TRUE, the button is pressed down. The signals
pressed() and clicked() are not emitted if you set this property
to TRUE. The default is FALSE.
*/
/*!
\property QButton::exclusiveToggle
\brief whether the button is an exclusive toggle
If this property is TRUE and the button is in a QButtonGroup, the
button can only be toggled off by another one being toggled on.
The default is FALSE.
*/
/*!
\property QButton::on
\brief whether the button is toggled
This property should only be set for toggle buttons.
*/
/*!
\fn void QButton::setOn( bool on )
Sets the state of this button to On if \a on is TRUE; otherwise to
Off.
\sa toggleState
*/
/*!
\property QButton::pixmap
\brief the pixmap shown on the button
If the pixmap is monochrome (i.e. it is a QBitmap or its \link
QPixmap::depth() depth\endlink is 1) and it does not have a mask,
this property will set the pixmap to be its own mask. The purpose
of this is to draw transparent bitmaps which are important for
toggle buttons, for example.
pixmap() returns 0 if no pixmap was set.
*/
/*!
\property QButton::text
\brief the text shown on the button
This property will return a QString::null if the button has no
text. If the text has an ampersand (\&) in it, then an
accelerator is automatically created for it using the character
that follows the '\&' as the accelerator key. Any previous
accelerator will be overwritten, or cleared if no accelerator is
defined by the text.
There is no default text.
*/
/*!
\property QButton::toggleButton
\brief whether the button is a toggle button
The default value is FALSE.
*/
/*!
\fn QButton::setToggleButton( bool b )
If \a b is TRUE, this button becomes a toggle button; if \a b is
FALSE, this button becomes a command button.
\sa toggleButton
*/
/*!
\property QButton::toggleState
\brief the state of the toggle button
If this property is changed then it does not cause the button
to be repainted.
*/
/*!
\property QButton::toggleType
\brief the type of toggle on the button
The default toggle type is \c SingleShot.
\sa QButton::ToggleType
*/
/*!
Constructs a standard button called \a name with parent \a parent,
using the widget flags \a f.
If \a parent is a QButtonGroup, this constructor calls
QButtonGroup::insert().
*/
QButton::QButton( QWidget *parent, const char *name, WFlags f )
: QWidget( parent, name, f )
{
bpixmap = 0;
toggleTyp = SingleShot; // button is simple
buttonDown = FALSE; // button is up
stat = Off; // button is off
mlbDown = FALSE; // mouse left button up
autoresize = FALSE; // not auto resizing
animation = FALSE; // no pending animateClick
repeat = FALSE; // not in autorepeat mode
d = 0;
#ifndef QT_NO_BUTTONGROUP
if ( ::qt_cast<QButtonGroup*>(parent) ) {
setGroup((QButtonGroup*)parent);
group()->insert( this ); // insert into button group
}
#endif
setFocusPolicy( TabFocus );
}
/*!
Destroys the button.
*/
QButton::~QButton()
{
#ifndef QT_NO_BUTTONGROUP
if ( group() )
group()->remove( this );
#endif
delete bpixmap;
delete d;
}
/*!
\fn void QButton::pressed()
This signal is emitted when the button is pressed down.
\sa released(), clicked()
*/
/*!
\fn void QButton::released()
This signal is emitted when the button is released.
\sa pressed(), clicked(), toggled()
*/
/*!
\fn void QButton::clicked()
This signal is emitted when the button is activated (i.e. first
pressed down and then released when the mouse cursor is inside the
button), when the accelerator key is typed or when animateClick()
is called. This signal is \e not emitted if you call setDown().
The QButtonGroup::clicked() signal does the same job, if you want
to connect several buttons to the same slot.
\warning Don't launch a model dialog in response to this signal
for a button that has \c autoRepeat turned on.
\sa pressed(), released(), toggled() autoRepeat down
*/
/*!
\fn void QButton::toggled( bool on )
This signal is emitted whenever a toggle button changes status. \a
on is TRUE if the button is on, or FALSE if the button is off.
This may be the result of a user action, toggle() slot activation,
or because setOn() was called.
\sa clicked()
*/
/*!
\fn void QButton::stateChanged( int state )
This signal is emitted whenever a toggle button changes state. \a
state is \c On if the button is on, \c NoChange if it is in the
\link QCheckBox::setTristate() "no change" state\endlink or \c Off
if the button is off.
This may be the result of a user action, toggle() slot activation,
setState(), or because setOn() was called.
\sa clicked() QButton::ToggleState
*/
void QButton::setText( const QString &text )
{
if ( btext == text )
return;
btext = text;
#ifndef QT_NO_ACCEL
setAccel( QAccel::shortcutKey( text ) );
#endif
if ( bpixmap ) {
delete bpixmap;
bpixmap = 0;
}
if ( autoresize )
adjustSize();
update();
updateGeometry();
#if defined(QT_ACCESSIBILITY_SUPPORT)
QAccessible::updateAccessibility( this, 0, QAccessible::NameChanged );
#endif
}
void QButton::setPixmap( const QPixmap &pixmap )
{
if ( bpixmap && bpixmap->serialNumber() == pixmap.serialNumber() )
return;
bool newSize;
if ( bpixmap ) {
newSize = pixmap.width() != bpixmap->width() ||
pixmap.height() != bpixmap->height();
*bpixmap = pixmap;
} else {
newSize = TRUE;
bpixmap = new QPixmap( pixmap );
Q_CHECK_PTR( bpixmap );
}
if ( bpixmap->depth() == 1 && !bpixmap->mask() )
bpixmap->setMask( *((QBitmap *)bpixmap) );
if ( !btext.isNull() ) {
btext = QString::null;
#ifndef QT_NO_ACCEL
setAccel( QKeySequence() );
#endif
}
if ( autoresize && newSize )
adjustSize();
if ( autoMask() )
updateMask();
update();
if ( newSize )
updateGeometry();
}
#ifndef QT_NO_ACCEL
QKeySequence QButton::accel() const
{
if ( d && d->a )
return d->a->key( 0 );
return QKeySequence();
}
void QButton::setAccel( const QKeySequence& key )
{
if ( d && d->a )
d->a->clear();
if ( key.isEmpty() )
return;
ensureData();
if ( !d->a ) {
d->a = new QAccel( this, "buttonAccel" );
connect( d->a, SIGNAL( activated(int) ), this, SLOT( animateClick() ) );
connect( d->a, SIGNAL( activatedAmbiguously(int) ), this, SLOT( setFocus() ) );
}
d->a->insertItem( key, 0 );
}
#endif
#ifndef QT_NO_COMPAT
void QButton::setAutoResize( bool enable )
{
if ( (bool)autoresize != enable ) {
autoresize = enable;
if ( autoresize )
adjustSize(); // calls resize which repaints
}
}
#endif
void QButton::setAutoRepeat( bool enable )
{
repeat = (uint)enable;
if ( repeat && mlbDown )
timer()->start( AUTO_REPEAT_DELAY, TRUE );
}
/*!
Performs an animated click: the button is pressed and released a
short while later.
The pressed(), released(), clicked(), toggled(), and
stateChanged() signals are emitted as appropriate.
This function does nothing if the button is \link setEnabled()
disabled. \endlink
\sa setAccel()
*/
void QButton::animateClick()
{
if ( !isEnabled() || animation )
return;
animation = TRUE;
buttonDown = TRUE;
repaint( FALSE );
emit pressed();
QTimer::singleShot( 100, this, SLOT(animateTimeout()) );
}
void QButton::emulateClick()
{
if ( !isEnabled() || animation )
return;
animation = TRUE;
buttonDown = TRUE;
emit pressed();
animateTimeout();
}
void QButton::setDown( bool enable )
{
if ( d )
timer()->stop();
mlbDown = FALSE; // the safe setting
if ( (bool)buttonDown != enable ) {
buttonDown = enable;
repaint( FALSE );
#if defined(QT_ACCESSIBILITY_SUPPORT)
QAccessible::updateAccessibility( this, 0, QAccessible::StateChanged );
#endif
}
}
/*!
Sets the toggle state of the button to \a s. \a s can be \c Off, \c
NoChange or \c On.
*/
void QButton::setState( ToggleState s )
{
if ( !toggleTyp ) {
#if defined(QT_CHECK_STATE)
qWarning( "QButton::setState() / setOn: (%s) Only toggle buttons "
"may be switched", name( "unnamed" ) );
#endif
return;
}
if ( (ToggleState)stat != s ) { // changed state
bool was = stat != Off;
stat = s;
if ( autoMask() )
updateMask();
repaint( FALSE );
#if defined(QT_ACCESSIBILITY_SUPPORT)
QAccessible::updateAccessibility( this, 0, QAccessible::StateChanged );
#endif
// ### toggled for tristate makes no sense. Don't emit the signal in 4.0
if ( was != (stat != Off) )
emit toggled( stat != Off );
emit stateChanged( s );
}
}
/*!
Returns TRUE if \a pos is inside the clickable button rectangle;
otherwise returns FALSE.
By default, the clickable area is the entire widget. Subclasses
may reimplement it, though.
*/
bool QButton::hitButton( const QPoint &pos ) const
{
return rect().contains( pos );
}
/*!
Draws the button. The default implementation does nothing.
This virtual function is reimplemented by subclasses to draw real
buttons. At some point, these reimplementations should call
drawButtonLabel().
\sa drawButtonLabel(), paintEvent()
*/
#if (QT_VERSION-0 >= 0x040000)
#error "QButton. Make pure virtual"
#endif
void QButton::drawButton( QPainter * )
{
return;
}
/*!
Draws the button text or pixmap.
This virtual function is reimplemented by subclasses to draw real
buttons. It is invoked by drawButton().
\sa drawButton(), paintEvent()
*/
void QButton::drawButtonLabel( QPainter * )
{
return;
}
/*! \reimp */
void QButton::keyPressEvent( QKeyEvent *e )
{
switch ( e->key() ) {
case Key_Enter:
case Key_Return:
{
#ifndef QT_NO_PUSHBUTTON
QPushButton *pb = (QPushButton*)qt_cast( "QPushButton" );
if ( pb && ( pb->autoDefault() || pb->isDefault() ) )
emit clicked();
else
#endif
e->ignore();
}
break;
case Key_Space:
if ( !e->isAutoRepeat() ) {
setDown( TRUE );
#ifndef QT_NO_PUSHBUTTON
if ( ::qt_cast<QPushButton*>(this) )
emit pressed();
else
#endif
e->ignore();
}
break;
case Key_Up:
case Key_Left:
#ifndef QT_NO_BUTTONGROUP
if ( group() ) {
group()->moveFocus( e->key() );
} else
#endif
{
QFocusEvent::setReason(QFocusEvent::Backtab);
focusNextPrevChild( FALSE );
QFocusEvent::resetReason();
}
break;
case Key_Right:
case Key_Down:
#ifndef QT_NO_BUTTONGROUP
if ( group() ) {
group()->moveFocus( e->key() );
} else
#endif
{
QFocusEvent::setReason(QFocusEvent::Tab);
focusNextPrevChild( TRUE );
QFocusEvent::resetReason();
}
break;
case Key_Escape:
if ( buttonDown ) {
buttonDown = FALSE;
update();
break;
}
// fall through
default:
e->ignore();
}
}
/*! \reimp */
void QButton::keyReleaseEvent( QKeyEvent * e)
{
switch ( e->key() ) {
case Key_Space:
if ( buttonDown && !e->isAutoRepeat() ) {
buttonDown = FALSE;
nextState();
emit released();
emit clicked();
}
break;
default:
e->ignore();
}
}
/*! \reimp */
void QButton::mousePressEvent( QMouseEvent *e )
{
if ( e->button() != LeftButton ) {
e->ignore();
return;
}
bool hit = hitButton( e->pos() );
if ( hit ) { // mouse press on button
mlbDown = TRUE; // left mouse button down
buttonDown = TRUE;
if ( autoMask() )
updateMask();
repaint( FALSE );
#if defined(QT_ACCESSIBILITY_SUPPORT)
QAccessible::updateAccessibility( this, 0, QAccessible::StateChanged );
#endif
QGuardedPtr<QTimer> t = timer();
emit pressed();
if ( t && repeat )
t->start( AUTO_REPEAT_DELAY, TRUE );
}
}
/*! \reimp */
void QButton::mouseReleaseEvent( QMouseEvent *e)
{
if ( e->button() != LeftButton ) {
// clean up apperance if left button has been pressed
if (mlbDown || buttonDown) {
mlbDown = FALSE;
buttonDown = FALSE;
if ( autoMask() )
updateMask();
repaint( FALSE );
}
e->ignore();
return;
}
if ( !mlbDown )
return;
if ( d )
timer()->stop();
const bool oldButtonDown = buttonDown;
mlbDown = FALSE; // left mouse button up
buttonDown = FALSE;
if ( hitButton( e->pos() ) ) { // mouse release on button
nextState();
#if defined(QT_ACCESSIBILITY_SUPPORT)
QAccessible::updateAccessibility( this, 0, QAccessible::StateChanged );
#endif
emit released();
emit clicked();
} else {
repaint( FALSE );
#if defined(QT_ACCESSIBILITY_SUPPORT)
QAccessible::updateAccessibility( this, 0, QAccessible::StateChanged );
#endif
if (oldButtonDown)
emit released();
}
}
/*! \reimp */
void QButton::mouseMoveEvent( QMouseEvent *e )
{
if ( !((e->state() & LeftButton) && mlbDown) ) {
e->ignore();
return; // left mouse button is up
}
if ( hitButton(e->pos()) ) { // mouse move in button
if ( !buttonDown ) {
buttonDown = TRUE;
repaint( FALSE );
#if defined(QT_ACCESSIBILITY_SUPPORT)
QAccessible::updateAccessibility( this, 0, QAccessible::StateChanged );
#endif
emit pressed();
}
} else { // mouse move outside button
if ( buttonDown ) {
buttonDown = FALSE;
repaint( FALSE );
#if defined(QT_ACCESSIBILITY_SUPPORT)
QAccessible::updateAccessibility( this, 0, QAccessible::StateChanged );
#endif
emit released();
}
}
}
/*!
Handles paint events for buttons. Small and typically complex
buttons are painted double-buffered to reduce flicker. The
actually drawing is done in the virtual functions drawButton() and
drawButtonLabel().
\sa drawButton(), drawButtonLabel()
*/
void QButton::paintEvent( QPaintEvent *)
{
QSharedDoubleBuffer buffer( this );
drawButton( buffer.painter() );
}
/*! \reimp */
void QButton::focusInEvent( QFocusEvent * e)
{
QWidget::focusInEvent( e );
}
/*! \reimp */
void QButton::focusOutEvent( QFocusEvent * e )
{
buttonDown = FALSE;
QWidget::focusOutEvent( e );
}
/*!
Internal slot used for auto repeat.
*/
void QButton::autoRepeatTimeout()
{
if ( mlbDown && isEnabled() && autoRepeat() ) {
QGuardedPtr<QTimer> t = timer();
if ( buttonDown ) {
emit released();
emit clicked();
emit pressed();
}
if ( t )
t->start( AUTO_REPEAT_PERIOD, TRUE );
}
}
/*!
Internal slot used for the second stage of animateClick().
*/
void QButton::animateTimeout()
{
if ( !animation )
return;
animation = FALSE;
buttonDown = FALSE;
nextState();
emit released();
emit clicked();
}
void QButton::nextState()
{
bool t = isToggleButton() && !( isOn() && isExclusiveToggle() );
bool was = stat != Off;
if ( t ) {
if ( toggleTyp == Tristate )
stat = ( stat + 1 ) % 3;
else
stat = stat ? Off : On;
}
if ( autoMask() )
updateMask();
repaint( FALSE );
if ( t ) {
#if defined(QT_ACCESSIBILITY_SUPPORT)
QAccessible::updateAccessibility( this, 0, QAccessible::StateChanged );
#endif
if ( was != (stat != Off) )
emit toggled( stat != Off );
emit stateChanged( stat );
}
}
/*! \reimp */
void QButton::enabledChange( bool e )
{
if ( !isEnabled() )
setDown( FALSE );
QWidget::enabledChange( e );
}
/*!
Toggles the state of a toggle button.
\sa isOn(), setOn(), toggled(), isToggleButton()
*/
void QButton::toggle()
{
if ( isToggleButton() )
setOn( !isOn() );
}
/*!
Sets the toggle type of the button to \a type.
\a type can be set to \c SingleShot, \c Toggle and \c Tristate.
*/
void QButton::setToggleType( ToggleType type )
{
toggleTyp = type;
if ( type != Tristate && stat == NoChange )
setState( On );
#if defined(QT_ACCESSIBILITY_SUPPORT)
else
QAccessible::updateAccessibility( this, 0, QAccessible::StateChanged );
#endif
}
bool QButton::isExclusiveToggle() const
{
#ifndef QT_NO_BUTTONGROUP
return group() && ( group()->isExclusive() ||
( group()->isRadioButtonExclusive() &&
::qt_cast<QRadioButton*>(this) ) );
#else
return FALSE;
#endif
}
#endif