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/kexi/plugins/forms/widgets/kexidblabel.cpp

651 lines
16 KiB

/* This file is part of the KDE project
Copyright (C) 2005 Christian Nitschkowski <segfault_ii@web.de>
Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
This program 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 program 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 program; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kexidblabel.h"
#include <tqbitmap.h>
#include <tqpainter.h>
#include <tqdrawutil.h>
#include <tqapplication.h>
#include <tqtimer.h>
#include <kdebug.h>
#include <kimageeffect.h>
#include <kexidb/field.h>
#include <kexiutils/utils.h>
#define SHADOW_OFFSET_X 3
#define SHADOW_OFFSET_Y 3
#define SHADOW_FACTOR 16.0
#define SHADOW_OPACITY 50.0
#define SHADOW_AXIS_FACTOR 2.0
#define SHADOW_DIAGONAL_FACTOR 1.0
#define SHADOW_THICKNESS 1
//! @internal
class KexiDBInternalLabel : public TQLabel {
friend class KexiDBLabel;
public:
KexiDBInternalLabel( KexiDBLabel* );
virtual ~KexiDBInternalLabel();
protected:
void updateFrame();
TQImage makeShadow( const TQImage& textImage, const TQColor &bgColor, const TQRect& boundingRect );
TQRect getBounding( const TQImage &image, const TQRect& startRect );
// double defaultDecay( TQImage& source, int i, int j );
KPixmap getShadowPixmap();
TQRect m_shadowRect;
KexiDBLabel *m_parentLabel;
};
KexiDBInternalLabel::KexiDBInternalLabel( KexiDBLabel* parent )
: TQLabel( parent )
, m_parentLabel(parent)
{
int a = alignment() | TQt::WordBreak;
a &= (0xffffff ^ TQt::AlignVertical_Mask);
a |= TQt::AlignTop;
setAlignment( a );
updateFrame();
}
void KexiDBInternalLabel::updateFrame()
{
setIndent(m_parentLabel->indent());
setMargin(m_parentLabel->margin());
setFont(m_parentLabel->font());
setFrameShadow(m_parentLabel->frameShadow());
setFrameShape(m_parentLabel->frameShape());
setFrameStyle(m_parentLabel->frameStyle());
setMidLineWidth(m_parentLabel->midLineWidth());
setLineWidth(m_parentLabel->lineWidth());
}
KexiDBInternalLabel::~KexiDBInternalLabel()
{
}
/*!
* This method is copied from tdebase/kdesktop/kshadowengine.cpp
* Some modifactions were made.
* --
* Christian Nitschkowski
*/
TQImage KexiDBInternalLabel::makeShadow( const TQImage& textImage,
const TQColor &bgColor, const TQRect& boundingRect )
{
TQImage result;
TQString origText( text() );
// create a new image for for the shaddow
const int w = textImage.width();
const int h = textImage.height();
// avoid calling these methods for every pixel
const int bgRed = bgColor.red();
const int bgGreen = bgColor.green();
const int bgBlue = bgColor.blue();
const int startX = boundingRect.x() + SHADOW_THICKNESS;
const int startY = boundingRect.y() + SHADOW_THICKNESS;
const int effectWidth = boundingRect.bottomRight().x() - SHADOW_THICKNESS;
const int effectHeight = boundingRect.bottomRight().y() - SHADOW_THICKNESS;
// const int period = (effectWidth - startX) / 10;
double alphaShadow;
/*
* This is the source pixmap
*/
TQImage img = textImage.convertDepth( 32 );
/*
* Resize the image if necessary
*/
if ( ( result.width() != w ) || ( result.height() != h ) ) {
result.create( w, h, 32 );
}
// result.fill( 0 ); // all black
double realOpacity = SHADOW_OPACITY + TQMIN(50.0/double(256.0-tqGray(bgColor.rgb())), 50.0);
//int _h, _s, _v;
//.getHsv( &_h, &_s, &_v );
if (colorGroup().background()==TQt::red)//_s>=250 && _v>=250) //for colors like cyan or red, make the result more white
realOpacity += 50.0;
result.fill( (int)realOpacity );
result.setAlphaBuffer( true );
for ( int i = startX; i < effectWidth; i++ ) {
for ( int j = startY; j < effectHeight; j++ ) {
/*!
* This method is copied from tdebase/kdesktop/kshadowengine.cpp
* Some modifactions were made.
* --
* Christian Nitschkowski
*/
if ( ( i < 1 ) || ( j < 1 ) || ( i > img.width() - 2 ) || ( j > img.height() - 2 ) )
continue;
else
alphaShadow = ( tqGray( img.pixel( i - 1, j - 1 ) ) * SHADOW_DIAGONAL_FACTOR +
tqGray( img.pixel( i - 1, j ) ) * SHADOW_AXIS_FACTOR +
tqGray( img.pixel( i - 1, j + 1 ) ) * SHADOW_DIAGONAL_FACTOR +
tqGray( img.pixel( i , j - 1 ) ) * SHADOW_AXIS_FACTOR +
0 +
tqGray( img.pixel( i , j + 1 ) ) * SHADOW_AXIS_FACTOR +
tqGray( img.pixel( i + 1, j - 1 ) ) * SHADOW_DIAGONAL_FACTOR +
tqGray( img.pixel( i + 1, j ) ) * SHADOW_AXIS_FACTOR +
tqGray( img.pixel( i + 1, j + 1 ) ) * SHADOW_DIAGONAL_FACTOR ) / SHADOW_FACTOR;
// update the shadow's i,j pixel.
if (alphaShadow > 0)
result.setPixel( i, j, tqRgba( bgRed, bgGreen , bgBlue,
( int ) (( alphaShadow > realOpacity ) ? realOpacity : alphaShadow)
) );
}
/*caused too much redraw problems if (period && i % period) {
tqApp->processEvents();
if (text() != origText) //text has been changed in the meantime: abort
return TQImage();
}*/
}
return result;
}
KPixmap KexiDBInternalLabel::getShadowPixmap() {
/*!
* Backup the default color used to draw text.
*/
const TQColor textColor = colorGroup().foreground();
/*!
* Temporary storage for the generated shadow
*/
KPixmap finalPixmap, tempPixmap;
TQImage shadowImage, tempImage;
TQPainter painter;
m_shadowRect = TQRect();
tempPixmap.resize( size() );
tempPixmap.fill( TQt::black );
tempPixmap.setMask( tempPixmap.createHeuristicMask( true ) );
/*!
* The textcolor has to be white for creating shadows!
*/
setPaletteForegroundColor( TQt::white );
/*!
Draw the label "as usual" in a pixmap
*/
painter.begin( &tempPixmap );
painter.setFont( font() );
drawContents( &painter );
painter.end();
setPaletteForegroundColor( textColor );
/*!
* Calculate the first bounding rect.
* This will fit around the unmodified text.
*/
shadowImage = tempPixmap;
tempPixmap.setMask( TQBitmap() );
/*!
Get the first bounding rect.
This may speed up makeShadow later.
*/
m_shadowRect = getBounding( shadowImage, m_shadowRect );
/*!
* Enlarge the bounding rect to make sure the shadow
* will fit in.
* The new rect has to fit in the pixmap.
* I have to admit this isn't really nice code...
*/
m_shadowRect.setX( TQMAX( m_shadowRect.x() - ( m_shadowRect.width() / 4 ), 0 ) );
m_shadowRect.setY( TQMAX( m_shadowRect.y() - ( m_shadowRect.height() / 4 ), 0 ) );
m_shadowRect.setBottomRight( TQPoint(
TQMIN( m_shadowRect.x() + ( m_shadowRect.width() * 3 / 2 ), shadowImage.width() ),
TQMIN( m_shadowRect.y() + ( m_shadowRect.height() * 3 / 2 ), shadowImage.height() ) ) );
shadowImage = makeShadow( shadowImage,
tqGray( colorGroup().background().rgb() ) < 127 ? TQt::white : TQt::black,
m_shadowRect );
if (shadowImage.isNull())
return KPixmap();
/*!
Now get the final bounding rect.
*/
m_shadowRect = getBounding( shadowImage, m_shadowRect );
/*!
Paint the labels background in a new pixmap.
*/
finalPixmap.resize( size() );
painter.begin( &finalPixmap );
painter.fillRect( 0, 0, finalPixmap.width(), finalPixmap.height(),
palette().brush(
isEnabled() ? TQPalette::Active : TQPalette::Disabled,
TQColorGroup::Background ) );
painter.end();
/*!
Copy the part of the background the shadow will be on
to another pixmap.
*/
tempPixmap.resize( m_shadowRect.size() );
if (!finalPixmap.isNull()) {
bitBlt( &tempPixmap, 0, 0, &finalPixmap,
m_shadowRect.x() + SHADOW_OFFSET_X,
m_shadowRect.y() + SHADOW_OFFSET_Y,
m_shadowRect.width(),
m_shadowRect.height() );
}
/*!
Replace the big background pixmap with the
part we could out just before.
*/
finalPixmap = tempPixmap;
/*!
Copy the "interesting" part of the shadow image
to a new image.
I tried to copy this to a pixmap directly,
but it didn't work correctly.
Maybe a TQt bug?
*/
tempImage = shadowImage.copy( m_shadowRect );
tempPixmap.convertFromImage( tempImage );
/*!
Anyways, merge the shadow with the background.
*/
if (!tempPixmap.isNull()) {
bitBlt( &finalPixmap, 0, 0, &tempPixmap );
}
/**
Now move the rect.
Don't do this before the shadow is copied from shadowImage!
*/
m_shadowRect.moveBy( SHADOW_OFFSET_X, SHADOW_OFFSET_Y );
return finalPixmap;
}
TQRect KexiDBInternalLabel::getBounding( const TQImage &image, const TQRect& startRect ) {
TQPoint topLeft;
TQPoint bottomRight;
const int startX = startRect.x();
const int startY = startRect.y();
/*!
* Ugly beast to get the correct width and height
*/
const int width = TQMIN( ( startRect.bottomRight().x() > 0
? startRect.bottomRight().x() : TQCOORD_MAX ),
image.width() );
const int height = TQMIN( ( startRect.bottomRight().y() > 0
? startRect.bottomRight().y() : TQCOORD_MAX ),
image.height() );
/*!
Assume the first pixel has the color of the
background that has to be cut away.
TQt uses the four corner pixels to guess the
correct color, but in this case the topleft
pixel should be enough.
*/
TQRgb trans = image.pixel( 0, 0 );
for ( int y = startY; y < height; y++ ) {
for ( int x = startX; x < width; x++ ) {
if ( image.pixel( x, y ) != trans ) {
topLeft.setY( y );
y = height;
break;
}
}
}
for ( int x = startX; x < width; x++ ) {
for ( int y = startY; y < height; y++ ) {
if ( image.pixel( x, y ) != trans ) {
topLeft.setX( x );
x = width;
break;
}
}
}
for ( int y = height - 1; y > topLeft.y(); y-- ) {
for ( int x = width - 1; x > topLeft.x(); x-- ) {
if ( image.pixel( x, y ) != trans ) {
bottomRight.setY( y + 1 );
y = 0;
break;
}
}
}
for ( int x = width - 1; x > topLeft.x(); x-- ) {
for ( int y = height - 1; y > topLeft.y(); y-- ) {
if ( image.pixel( x, y ) != trans ) {
bottomRight.setX( x + 1 );
x = 0;
break;
}
}
}
return TQRect(
topLeft.x(),
topLeft.y(),
bottomRight.x() - topLeft.x(),
bottomRight.y() - topLeft.y() );
}
//=========================================================
//! @internal
class KexiDBLabel::Private
{
public:
Private()
: timer(0)
// , autonumberDisplayParameters(0)
, pixmapDirty( true )
, shadowEnabled( false )
, resizeEvent( false )
{
}
~Private() {}
KPixmap shadowPixmap;
TQPoint shadowPosition;
KexiDBInternalLabel* internalLabel;
TQTimer* timer;
TQColor frameColor;
bool pixmapDirty : 1;
bool shadowEnabled : 1;
bool resizeEvent : 1;
};
//=========================================================
KexiDBLabel::KexiDBLabel( TQWidget *parent, const char *name, WFlags f )
: TQLabel( parent, name, f )
, KexiDBTextWidgetInterface()
, KexiFormDataItemInterface()
, d( new Private() )
{
init();
}
KexiDBLabel::KexiDBLabel( const TQString& text, TQWidget *parent, const char *name, WFlags f )
: TQLabel( parent, name, f )
, KexiDBTextWidgetInterface()
, KexiFormDataItemInterface()
, d( new Private() )
{
init();
setText( text );
}
KexiDBLabel::~KexiDBLabel()
{
delete d;
}
void KexiDBLabel::init()
{
m_hasFocusableWidget = false;
d->internalLabel = new KexiDBInternalLabel( this );
d->internalLabel->hide();
d->frameColor = palette().active().foreground();
setAlignment( d->internalLabel->alignment() );
}
void KexiDBLabel::updatePixmapLater() {
if (d->resizeEvent) {
if (!d->timer) {
d->timer = new TQTimer(this, "KexiDBLabelTimer");
connect(d->timer, TQ_SIGNAL(timeout()), this, TQ_SLOT(updatePixmap()));
}
d->timer->start(100, true);
d->resizeEvent = false;
return;
}
if (d->timer && d->timer->isActive())
return;
updatePixmap();
}
void KexiDBLabel::updatePixmap() {
/*!
Whatever has changed in KexiDBLabel,
every parameter is set to our private-label.
Just in case...
*/
d->internalLabel->setText( text() );
d->internalLabel->setFixedSize( size() );
d->internalLabel->setPalette( palette() );
d->internalLabel->setAlignment( alignment() );
// d->shadowPixmap = KPixmap(); //parallel repaints won't hurt us cause incomplete pixmap
KPixmap shadowPixmap = d->internalLabel->getShadowPixmap();
if (shadowPixmap.isNull())
return;
d->shadowPixmap = shadowPixmap;
d->shadowPosition = d->internalLabel->m_shadowRect.topLeft();
d->pixmapDirty = false;
repaint();
}
void KexiDBLabel::paintEvent( TQPaintEvent* e )
{
TQPainter p( this );
if ( d->shadowEnabled ) {
/*!
If required, update the pixmap-cache.
*/
if ( d->pixmapDirty ) {
updatePixmapLater();
}
/*!
If the part that should be redrawn intersects with our shadow,
redraw the shadow where it intersects with e->rect().
Have to move the clipping rect around a bit because
the shadow has to be drawn using an offset relative to
the widgets border.
*/
if ( !d->pixmapDirty && e->rect().contains( d->shadowPosition ) && !d->shadowPixmap.isNull()) {
TQRect clipRect = TQRect(
TQMAX( e->rect().x() - d->shadowPosition.x(), 0 ),
TQMAX( e->rect().y() - d->shadowPosition.y(), 0 ),
TQMIN( e->rect().width() + d->shadowPosition.x(), d->shadowPixmap.width() ),
TQMIN( e->rect().height() + d->shadowPosition.y(), d->shadowPixmap.height() ) );
p.drawPixmap( d->internalLabel->m_shadowRect.topLeft(), d->shadowPixmap, clipRect );
}
}
KexiDBTextWidgetInterface::paint( this, &p, text().isEmpty(), alignment(), false );
TQLabel::paintEvent( e );
}
void KexiDBLabel::setValueInternal( const TQVariant& add, bool removeOld ) {
if (removeOld)
setText(add.toString());
else
setText( m_origValue.toString() + add.toString() );
}
TQVariant KexiDBLabel::value() {
return text();
}
void KexiDBLabel::setInvalidState( const TQString& displayText )
{
setText( displayText );
}
bool KexiDBLabel::valueIsNull()
{
return text().isNull();
}
bool KexiDBLabel::valueIsEmpty()
{
return text().isEmpty();
}
bool KexiDBLabel::isReadOnly() const
{
return true;
}
void KexiDBLabel::setReadOnly( bool readOnly )
{
Q_UNUSED(readOnly);
}
TQWidget* KexiDBLabel::widget()
{
return this;
}
bool KexiDBLabel::cursorAtStart()
{
return false;
}
bool KexiDBLabel::cursorAtEnd()
{
return false;
}
void KexiDBLabel::clear()
{
setText(TQString());
}
bool KexiDBLabel::setProperty( const char * name, const TQVariant & value )
{
const bool ret = TQLabel::setProperty(name, value);
if (d->shadowEnabled) {
if (0==qstrcmp("indent", name) || 0==qstrcmp("font", name) || 0==qstrcmp("margin", name)
|| 0==qstrcmp("frameShadow", name) || 0==qstrcmp("frameShape", name)
|| 0==qstrcmp("frameStyle", name) || 0==qstrcmp("midLineWidth", name)
|| 0==qstrcmp("lineWidth", name)) {
d->internalLabel->setProperty(name, value);
updatePixmap();
}
}
return ret;
}
void KexiDBLabel::setColumnInfo(KexiDB::QueryColumnInfo* cinfo)
{
KexiFormDataItemInterface::setColumnInfo(cinfo);
KexiDBTextWidgetInterface::setColumnInfo(cinfo, this);
}
void KexiDBLabel::setShadowEnabled( bool state ) {
d->shadowEnabled = state;
d->pixmapDirty = true;
if (state)
d->internalLabel->updateFrame();
repaint();
}
void KexiDBLabel::resizeEvent( TQResizeEvent* e ) {
if (isVisible())
d->resizeEvent = true;
d->pixmapDirty = true;
TQLabel::resizeEvent( e );
}
void KexiDBLabel::fontChange( const TQFont& font ) {
d->pixmapDirty = true;
d->internalLabel->setFont( font );
TQLabel::fontChange( font );
}
void KexiDBLabel::styleChange( TQStyle& style ) {
d->pixmapDirty = true;
TQLabel::styleChange( style );
}
void KexiDBLabel::enabledChange( bool enabled ) {
d->pixmapDirty = true;
d->internalLabel->setEnabled( enabled );
TQLabel::enabledChange( enabled );
}
void KexiDBLabel::paletteChange( const TQPalette& oldPal ) {
Q_UNUSED(oldPal);
d->pixmapDirty = true;
d->internalLabel->setPalette( palette() );
}
/*const TQColor & KexiDBLabel::paletteForegroundColor () const
{
return d->foregroundColor;
}
void KexiDBLabel::setPaletteForegroundColor ( const TQColor& color )
{
d->foregroundColor = color;
}*/
void KexiDBLabel::frameChanged() {
d->pixmapDirty = true;
d->internalLabel->updateFrame();
TQFrame::frameChanged();
}
void KexiDBLabel::showEvent( TQShowEvent* e ) {
d->pixmapDirty = true;
TQLabel::showEvent( e );
}
void KexiDBLabel::setText( const TQString& text ) {
d->pixmapDirty = true;
TQLabel::setText( text );
//This is necessary for KexiFormDataItemInterface
valueChanged();
repaint();
}
bool KexiDBLabel::shadowEnabled() const
{
return d->shadowEnabled;
}
#define ClassName KexiDBLabel
#define SuperClassName TQLabel
#include "kexiframeutils_p.cpp"
#include "kexidblabel.moc"