diff options
Diffstat (limited to 'kexi/plugins/forms/widgets/kexidblabel.cpp')
-rw-r--r-- | kexi/plugins/forms/widgets/kexidblabel.cpp | 650 |
1 files changed, 650 insertions, 0 deletions
diff --git a/kexi/plugins/forms/widgets/kexidblabel.cpp b/kexi/plugins/forms/widgets/kexidblabel.cpp new file mode 100644 index 00000000..e30cc19e --- /dev/null +++ b/kexi/plugins/forms/widgets/kexidblabel.cpp @@ -0,0 +1,650 @@ +/* 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 <qbitmap.h> +#include <qpainter.h> +#include <qdrawutil.h> +#include <qapplication.h> +#include <qtimer.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 QLabel { + friend class KexiDBLabel; + public: + KexiDBInternalLabel( KexiDBLabel* ); + virtual ~KexiDBInternalLabel(); + + protected: + void updateFrame(); + + QImage makeShadow( const QImage& textImage, const QColor &bgColor, const QRect& boundingRect ); + QRect getBounding( const QImage &image, const QRect& startRect ); +// double defaultDecay( QImage& source, int i, int j ); + KPixmap getShadowPixmap(); + + QRect m_shadowRect; + KexiDBLabel *m_parentLabel; +}; + +KexiDBInternalLabel::KexiDBInternalLabel( KexiDBLabel* parent ) + : QLabel( parent ) + , m_parentLabel(parent) +{ + int a = alignment() | Qt::WordBreak; + a &= (0xffffff ^ Qt::AlignVertical_Mask); + a |= Qt::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 kdebase/kdesktop/kshadowengine.cpp +* Some modifactions were made. +* -- +* Christian Nitschkowski +*/ +QImage KexiDBInternalLabel::makeShadow( const QImage& textImage, + const QColor &bgColor, const QRect& boundingRect ) +{ + QImage result; + QString 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 + */ + QImage 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 + QMIN(50.0/double(256.0-qGray(bgColor.rgb())), 50.0); + //int _h, _s, _v; + //.getHsv( &_h, &_s, &_v ); + if (colorGroup().background()==Qt::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 kdebase/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 = ( qGray( img.pixel( i - 1, j - 1 ) ) * SHADOW_DIAGONAL_FACTOR + + qGray( img.pixel( i - 1, j ) ) * SHADOW_AXIS_FACTOR + + qGray( img.pixel( i - 1, j + 1 ) ) * SHADOW_DIAGONAL_FACTOR + + qGray( img.pixel( i , j - 1 ) ) * SHADOW_AXIS_FACTOR + + 0 + + qGray( img.pixel( i , j + 1 ) ) * SHADOW_AXIS_FACTOR + + qGray( img.pixel( i + 1, j - 1 ) ) * SHADOW_DIAGONAL_FACTOR + + qGray( img.pixel( i + 1, j ) ) * SHADOW_AXIS_FACTOR + + qGray( 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, qRgba( bgRed, bgGreen , bgBlue, + ( int ) (( alphaShadow > realOpacity ) ? realOpacity : alphaShadow) + ) ); + } +/*caused too much redraw problems if (period && i % period) { + qApp->processEvents(); + if (text() != origText) //text has been changed in the meantime: abort + return QImage(); + }*/ + } + return result; +} + +KPixmap KexiDBInternalLabel::getShadowPixmap() { + /*! + * Backup the default color used to draw text. + */ + const QColor textColor = colorGroup().foreground(); + + /*! + * Temporary storage for the generated shadow + */ + KPixmap finalPixmap, tempPixmap; + QImage shadowImage, tempImage; + QPainter painter; + + m_shadowRect = QRect(); + + tempPixmap.resize( size() ); + tempPixmap.fill( Qt::black ); + tempPixmap.setMask( tempPixmap.createHeuristicMask( true ) ); + + /*! + * The textcolor has to be white for creating shadows! + */ + setPaletteForegroundColor( Qt::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( QBitmap() ); + + /*! + 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( QMAX( m_shadowRect.x() - ( m_shadowRect.width() / 4 ), 0 ) ); + m_shadowRect.setY( QMAX( m_shadowRect.y() - ( m_shadowRect.height() / 4 ), 0 ) ); + m_shadowRect.setBottomRight( QPoint( + QMIN( m_shadowRect.x() + ( m_shadowRect.width() * 3 / 2 ), shadowImage.width() ), + QMIN( m_shadowRect.y() + ( m_shadowRect.height() * 3 / 2 ), shadowImage.height() ) ) ); + + shadowImage = makeShadow( shadowImage, + qGray( colorGroup().background().rgb() ) < 127 ? Qt::white : Qt::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() ? QPalette::Active : QPalette::Disabled, + QColorGroup::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 Qt 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; +} + +QRect KexiDBInternalLabel::getBounding( const QImage &image, const QRect& startRect ) { + QPoint topLeft; + QPoint bottomRight; + + const int startX = startRect.x(); + const int startY = startRect.y(); + /*! + * Ugly beast to get the correct width and height + */ + const int width = QMIN( ( startRect.bottomRight().x() > 0 + ? startRect.bottomRight().x() : QCOORD_MAX ), + image.width() ); + const int height = QMIN( ( startRect.bottomRight().y() > 0 + ? startRect.bottomRight().y() : QCOORD_MAX ), + image.height() ); + + /*! + Assume the first pixel has the color of the + background that has to be cut away. + Qt uses the four corner pixels to guess the + correct color, but in this case the topleft + pixel should be enough. + */ + QRgb 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 QRect( + 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; + QPoint shadowPosition; + KexiDBInternalLabel* internalLabel; + QTimer* timer; + QColor frameColor; + bool pixmapDirty : 1; + bool shadowEnabled : 1; + bool resizeEvent : 1; +}; + +//========================================================= + +KexiDBLabel::KexiDBLabel( QWidget *parent, const char *name, WFlags f ) + : QLabel( parent, name, f ) + , KexiDBTextWidgetInterface() + , KexiFormDataItemInterface() + , d( new Private() ) +{ + init(); +} + +KexiDBLabel::KexiDBLabel( const QString& text, QWidget *parent, const char *name, WFlags f ) + : QLabel( 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 QTimer(this, "KexiDBLabelTimer"); + connect(d->timer, SIGNAL(timeout()), this, 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( QPaintEvent* e ) +{ + QPainter 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()) { + QRect clipRect = QRect( + QMAX( e->rect().x() - d->shadowPosition.x(), 0 ), + QMAX( e->rect().y() - d->shadowPosition.y(), 0 ), + QMIN( e->rect().width() + d->shadowPosition.x(), d->shadowPixmap.width() ), + QMIN( 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 ); + QLabel::paintEvent( e ); +} + +void KexiDBLabel::setValueInternal( const QVariant& add, bool removeOld ) { + if (removeOld) + setText(add.toString()); + else + setText( m_origValue.toString() + add.toString() ); +} + +QVariant KexiDBLabel::value() { + return text(); +} + +void KexiDBLabel::setInvalidState( const QString& 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); +} + +QWidget* KexiDBLabel::widget() +{ + return this; +} + +bool KexiDBLabel::cursorAtStart() +{ + return false; +} + +bool KexiDBLabel::cursorAtEnd() +{ + return false; +} + +void KexiDBLabel::clear() +{ + setText(QString::null); +} + +bool KexiDBLabel::setProperty( const char * name, const QVariant & value ) +{ + const bool ret = QLabel::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( QResizeEvent* e ) { + if (isVisible()) + d->resizeEvent = true; + d->pixmapDirty = true; + QLabel::resizeEvent( e ); +} + +void KexiDBLabel::fontChange( const QFont& font ) { + d->pixmapDirty = true; + d->internalLabel->setFont( font ); + QLabel::fontChange( font ); +} + +void KexiDBLabel::styleChange( QStyle& style ) { + d->pixmapDirty = true; + QLabel::styleChange( style ); +} + +void KexiDBLabel::enabledChange( bool enabled ) { + d->pixmapDirty = true; + d->internalLabel->setEnabled( enabled ); + QLabel::enabledChange( enabled ); +} + +void KexiDBLabel::paletteChange( const QPalette& oldPal ) { + Q_UNUSED(oldPal); + d->pixmapDirty = true; + d->internalLabel->setPalette( palette() ); +} + +/*const QColor & KexiDBLabel::paletteForegroundColor () const +{ + return d->foregroundColor; +} + +void KexiDBLabel::setPaletteForegroundColor ( const QColor& color ) +{ + d->foregroundColor = color; +}*/ + +void KexiDBLabel::frameChanged() { + d->pixmapDirty = true; + d->internalLabel->updateFrame(); + QFrame::frameChanged(); +} + +void KexiDBLabel::showEvent( QShowEvent* e ) { + d->pixmapDirty = true; + QLabel::showEvent( e ); +} + +void KexiDBLabel::setText( const QString& text ) { + d->pixmapDirty = true; + QLabel::setText( text ); + //This is necessary for KexiFormDataItemInterface + valueChanged(); + repaint(); +} + +bool KexiDBLabel::shadowEnabled() const +{ + return d->shadowEnabled; +} + +#define ClassName KexiDBLabel +#define SuperClassName QLabel +#include "kexiframeutils_p.cpp" +#include "kexidblabel.moc" |