/* vi: ts=8 sts=4 sw=4
 * $Id$
 *
 * This file is part of the KDE project, module tdecore.
 * Copyright (C) 2000 Geert Jansen <jansen@kde.org>
 * with minor additions and based on ideas from
 * Torsten Rahn <torsten@kde.org>
 *
 * This is free software; it comes under the GNU Library General
 * Public License, version 2. See the file "COPYING.LIB" for the
 * exact licensing terms.
 */

#include <config.h>
#include <unistd.h>
#include <math.h>

#include <tqstring.h>
#include <tqstringlist.h>
#include <tqbitmap.h>
#include <tqpixmap.h>
#include <tqimage.h>
#include <tqcolor.h>
#include <tqwidget.h>
#include <tqpainter.h>
#include <tqpen.h>
#include <tqapplication.h>
#include <tqpoint.h>
#include <tqrect.h>

#include <kdebug.h>
#include <tdeglobal.h>
#include <tdeconfig.h>
#include <tdeglobalsettings.h>
#include <kicontheme.h>
#include "kiconeffect.h"

#if defined(Q_WS_WIN) || defined(Q_WS_MACX)
static bool tqt_use_xrender=true;
static bool tqt_has_xft=true;
#else
extern bool tqt_use_xrender;
extern bool tqt_has_xft;
#endif
class TDEIconEffectPrivate
{
public:
	TQString mKey[6][3];
	TQColor  mColor2[6][3];
};

TDEIconEffect::TDEIconEffect()
{
    d = new TDEIconEffectPrivate;
    init();
}

TDEIconEffect::~TDEIconEffect()
{
    delete d;
    d = 0L;
}

void TDEIconEffect::init()
{
    TDEConfig *config = TDEGlobal::config();

    int i, j, effect=-1;
    TQStringList groups;
    groups += "Desktop";
    groups += "Toolbar";
    groups += "MainToolbar";
    groups += "Small";
    groups += "Panel";

    TQStringList states;
    states += "Default";
    states += "Active";
    states += "Disabled";

    TQStringList::ConstIterator it, it2;
    TQString _togray("togray");
    TQString _colorize("colorize");
    TQString _desaturate("desaturate");
    TQString _togamma("togamma");
    TQString _none("none");
    TQString _tomonochrome("tomonochrome");

    TDEConfigGroupSaver cs(config, "default");

    for (it=groups.begin(), i=0; it!=groups.end(); it++, i++)
    {
	// Default effects
	mEffect[i][0] = NoEffect;
	mEffect[i][1] =  ((i==0)||(i==4)) ? ToGamma : NoEffect;
	mEffect[i][2] = ToGray; 
	
	mTrans[i][0] = false;
	mTrans[i][1] = false;
	mTrans[i][2] = true;
        mValue[i][0] = 1.0;
        mValue[i][1] = ((i==0)||(i==4)) ? 0.7 : 1.0;
        mValue[i][2] = 1.0;
        mColor[i][0] = TQColor(144,128,248);
        mColor[i][1] = TQColor(169,156,255);
        mColor[i][2] = TQColor(34,202,0);
        d->mColor2[i][0] = TQColor(0,0,0);
        d->mColor2[i][1] = TQColor(0,0,0);
        d->mColor2[i][2] = TQColor(0,0,0);

	config->setGroup(*it + "Icons");
	for (it2=states.begin(), j=0; it2!=states.end(); it2++, j++)
	{
	    TQString tmp = config->readEntry(*it2 + "Effect");
	    if (tmp == _togray)
		effect = ToGray;
	    else if (tmp == _colorize)
		effect = Colorize;
	    else if (tmp == _desaturate)
		effect = DeSaturate;
	    else if (tmp == _togamma)
		effect = ToGamma;
	    else if (tmp == _tomonochrome)
		effect = ToMonochrome;
            else if (tmp == _none)
		effect = NoEffect;
	    else
		continue;
	    if(effect != -1)
                mEffect[i][j] = effect;
	    mValue[i][j] = config->readDoubleNumEntry(*it2 + "Value");
	    mColor[i][j] = config->readColorEntry(*it2 + "Color");
	    d->mColor2[i][j] = config->readColorEntry(*it2 + "Color2");
	    mTrans[i][j] = config->readBoolEntry(*it2 + "SemiTransparent");

	}
    }    
}

bool TDEIconEffect::hasEffect(int group, int state) const
{
    return mEffect[group][state] != NoEffect;
}

TQString TDEIconEffect::fingerprint(int group, int state) const
{
    if ( group >= TDEIcon::LastGroup ) return "";
    TQString cached = d->mKey[group][state];
    if (cached.isEmpty())
    {
        TQString tmp;
        cached = tmp.setNum(mEffect[group][state]);
        cached += ':';
        cached += tmp.setNum(mValue[group][state]);
        cached += ':';
        cached += mTrans[group][state] ? TQString::fromLatin1("trans")
            : TQString::fromLatin1("notrans");
        if (mEffect[group][state] == Colorize || mEffect[group][state] == ToMonochrome)
        {
            cached += ':';
            cached += mColor[group][state].name();
        }
        if (mEffect[group][state] == ToMonochrome)
        {
            cached += ':';
            cached += d->mColor2[group][state].name();
        }
    
        d->mKey[group][state] = cached;    
    }
    
    return cached;
}

TQImage TDEIconEffect::apply(TQImage image, int group, int state) const
{
    if (state >= TDEIcon::LastState)
    {
	kdDebug(265) << "Illegal icon state: " << state << "\n";
	return image;
    }
    if (group >= TDEIcon::LastGroup)
    {
	kdDebug(265) << "Illegal icon group: " << group << "\n";
	return image;
    }
    return apply(image, mEffect[group][state], mValue[group][state],
	    mColor[group][state], d->mColor2[group][state], mTrans[group][state]);
}

TQImage TDEIconEffect::apply(TQImage image, int effect, float value, const TQColor col, bool trans) const
{
    return apply (image, effect, value, col, TDEGlobalSettings::baseColor(), trans);
}

TQImage TDEIconEffect::apply(TQImage image, int effect, float value, const TQColor col, const TQColor col2, bool trans) const
{
    if (effect >= LastEffect )
    {
	kdDebug(265) << "Illegal icon effect: " << effect << "\n";
	return image;
    }
    if (value > 1.0)
	value = 1.0;
    else if (value < 0.0)
	value = 0.0;
    switch (effect)
    {
    case ToGray:
	toGray(image, value);
	break;
    case DeSaturate:
	deSaturate(image, value);
	break;
    case Colorize:
        colorize(image, col, value);
        break;
    case ToGamma:
        toGamma(image, value);
        break;
    case ToMonochrome:
        toMonochrome(image, col, col2, value);
        break;
    }
    if (trans == true)
    {
	semiTransparent(image);
    }
    return image;
}

TQPixmap TDEIconEffect::apply(TQPixmap pixmap, int group, int state) const
{
    if (state >= TDEIcon::LastState)
    {
	kdDebug(265) << "Illegal icon state: " << state << "\n";
	return pixmap;
    }
    if (group >= TDEIcon::LastGroup)
    {
	kdDebug(265) << "Illegal icon group: " << group << "\n";
	return pixmap;
    }
    return apply(pixmap, mEffect[group][state], mValue[group][state],
	    mColor[group][state], d->mColor2[group][state], mTrans[group][state]);
}

TQPixmap TDEIconEffect::apply(TQPixmap pixmap, int effect, float value,
	const TQColor col, bool trans) const
{
    return apply (pixmap, effect, value, col, TDEGlobalSettings::baseColor(), trans);
}

TQPixmap TDEIconEffect::apply(TQPixmap pixmap, int effect, float value,
	const TQColor col, const TQColor col2, bool trans) const
{
    TQPixmap result;

    if (effect >= LastEffect )
    {
	kdDebug(265) << "Illegal icon effect: " << effect << "\n";
	return result;
    }

    if ((trans == true) && (effect == NoEffect))
    {
        result = pixmap;
        semiTransparent(result);
    }
    else if ( effect != NoEffect )
    {
        TQImage tmpImg = pixmap.convertToImage();
        tmpImg = apply(tmpImg, effect, value, col, col2, trans);
        result.convertFromImage(tmpImg);
    }
    else
        result = pixmap;

    return result;
}

// Taken from KImageEffect. We don't want to link tdecore to tdeui! As long
// as this code is not too big, it doesn't seem much of a problem to me.

void TDEIconEffect::toGray(TQImage &img, float value)
{
    int pixels = (img.depth() > 8) ? img.width()*img.height()
	    : img.numColors();
    unsigned int *data = img.depth() > 8 ? (unsigned int *) img.bits()
	    : (unsigned int *) img.tqcolorTable();
    int rval, gval, bval, val, alpha, i;
    for (i=0; i<pixels; i++)
    {
	val = tqGray(data[i]);
	alpha = tqAlpha(data[i]);
	if (value < 1.0)
	{
	    rval = static_cast<int>(value*val+(1.0-value)*tqRed(data[i]));
	    gval = static_cast<int>(value*val+(1.0-value)*tqGreen(data[i]));
	    bval = static_cast<int>(value*val+(1.0-value)*tqBlue(data[i]));
	    data[i] = tqRgba(rval, gval, bval, alpha);
	} else
	    data[i] = tqRgba(val, val, val, alpha);
    }
}

void TDEIconEffect::colorize(TQImage &img, const TQColor &col, float value)
{
    int pixels = (img.depth() > 8) ? img.width()*img.height()
	    : img.numColors();
    unsigned int *data = img.depth() > 8 ? (unsigned int *) img.bits()
	    : (unsigned int *) img.tqcolorTable();
    int rval, gval, bval, val, alpha, i;
    float rcol = col.red(), gcol = col.green(), bcol = col.blue();
    for (i=0; i<pixels; i++)
    {
        val = tqGray(data[i]);
        if (val < 128)
        {
             rval = static_cast<int>(rcol/128*val);
             gval = static_cast<int>(gcol/128*val);
             bval = static_cast<int>(bcol/128*val);
        }
        else if (val > 128)
        {
             rval = static_cast<int>((val-128)*(2-rcol/128)+rcol-1);
             gval = static_cast<int>((val-128)*(2-gcol/128)+gcol-1);
             bval = static_cast<int>((val-128)*(2-bcol/128)+bcol-1);
        }
	else // val == 128
	{
             rval = static_cast<int>(rcol);
             gval = static_cast<int>(gcol);
             bval = static_cast<int>(bcol);
	}
	if (value < 1.0)
	{
	    rval = static_cast<int>(value*rval+(1.0 - value)*tqRed(data[i]));
	    gval = static_cast<int>(value*gval+(1.0 - value)*tqGreen(data[i]));
	    bval = static_cast<int>(value*bval+(1.0 - value)*tqBlue(data[i]));
	}

	alpha = tqAlpha(data[i]);
	data[i] = tqRgba(rval, gval, bval, alpha);
    }
}

void TDEIconEffect::toMonochrome(TQImage &img, const TQColor &black, const TQColor &white, float value) {
   int pixels = (img.depth() > 8) ? img.width()*img.height() : img.numColors();
   unsigned int *data = img.depth() > 8 ? (unsigned int *) img.bits()
         : (unsigned int *) img.tqcolorTable();
   int rval, gval, bval, alpha, i;
   int rw = white.red(), gw = white.green(), bw = white.blue();
   int rb = black.red(), gb = black.green(), bb = black.blue();
   
   double values = 0, sum = 0;
   bool grayscale = true;
   // Step 1: determine the average brightness
   for (i=0; i<pixels; i++) {
       sum += tqGray(data[i])*tqAlpha(data[i]) + 255*(255-tqAlpha(data[i]));
       values += 255;
       if ((tqRed(data[i]) != tqGreen(data[i]) ) || (tqGreen(data[i]) != tqBlue(data[i]) ))
           grayscale = false;
   }
   double medium = sum/values;

   // Step 2: Modify the image
   if (grayscale) {
       for (i=0; i<pixels; i++) {
           int v = tqRed(data[i]);
           rval = static_cast<int>( ((255-v)*rb + v*rw)*value/255 + (1.0-value)*tqRed(data[i]));
           gval = static_cast<int>( ((255-v)*gb + v*gw)*value/255 + (1.0-value)*tqGreen(data[i]));
           bval = static_cast<int>( ((255-v)*bb + v*bw)*value/255 + (1.0-value)*tqBlue(data[i]));

           alpha = tqAlpha(data[i]);
           data[i] = tqRgba(rval, gval, bval, alpha);
       }
   }
   else {
      for (i=0; i<pixels; i++) {
         if (tqGray(data[i]) <= medium) {
            rval = static_cast<int>(value*rb+(1.0-value)*tqRed(data[i]));
            gval = static_cast<int>(value*gb+(1.0-value)*tqGreen(data[i]));
            bval = static_cast<int>(value*bb+(1.0-value)*tqBlue(data[i]));
         }
         else {
            rval = static_cast<int>(value*rw+(1.0-value)*tqRed(data[i]));
            gval = static_cast<int>(value*gw+(1.0-value)*tqGreen(data[i]));
            bval = static_cast<int>(value*bw+(1.0-value)*tqBlue(data[i]));
         }

         alpha = tqAlpha(data[i]);
         data[i] = tqRgba(rval, gval, bval, alpha);
      }
   }
}

void TDEIconEffect::deSaturate(TQImage &img, float value)
{
    int pixels = (img.depth() > 8) ? img.width()*img.height()
	    : img.numColors();
    unsigned int *data = (img.depth() > 8) ? (unsigned int *) img.bits()
	    : (unsigned int *) img.tqcolorTable();
    TQColor color;
    int h, s, v, i;
    for (i=0; i<pixels; i++)
    {
        color.setRgb(data[i]);
        color.hsv(&h, &s, &v);
        color.setHsv(h, (int) (s * (1.0 - value) + 0.5), v);
	data[i] = tqRgba(color.red(), color.green(), color.blue(),
		tqAlpha(data[i]));
    }
}

void TDEIconEffect::toGamma(TQImage &img, float value)
{
    int pixels = (img.depth() > 8) ? img.width()*img.height()
	    : img.numColors();
    unsigned int *data = (img.depth() > 8) ? (unsigned int *) img.bits()
	    : (unsigned int *) img.tqcolorTable();
    TQColor color;
    int i, rval, gval, bval;
    float gamma;
    gamma = 1/(2*value+0.5);

    for (i=0; i<pixels; i++)
    {
        color.setRgb(data[i]);
        color.rgb(&rval, &gval, &bval);
        rval = static_cast<int>(pow(static_cast<float>(rval)/255 , gamma)*255);
        gval = static_cast<int>(pow(static_cast<float>(gval)/255 , gamma)*255);
        bval = static_cast<int>(pow(static_cast<float>(bval)/255 , gamma)*255);
	data[i] = tqRgba(rval, gval, bval, tqAlpha(data[i]));
    }
}

void TDEIconEffect::semiTransparent(TQImage &img)
{
    img.setAlphaBuffer(true);

    int x, y;
    if (img.depth() == 32)
    {
	int width  = img.width();
	int height = img.height();
	
	if (tqt_use_xrender && tqt_has_xft )
	  for (y=0; y<height; y++)
	  {
#ifdef WORDS_BIGENDIAN
	    uchar *line = (uchar*) img.scanLine(y);
#else
	    uchar *line = (uchar*) img.scanLine(y) + 3;
#endif
	    for (x=0; x<width; x++)
	    {
		*line >>= 1;
		line += 4;
	    }
	  }
	else
	  for (y=0; y<height; y++)
	  {
	    QRgb *line = (QRgb *) img.scanLine(y);
	    for (x=(y%2); x<width; x+=2)
		line[x] &= 0x00ffffff;
	  }

    } else
    {
	// Insert transparent pixel into the clut.
	int transColor = -1;

        // search for a color that is already transparent
        for (x=0; x<img.numColors(); x++)
        {
            // try to find already transparent pixel
            if (tqAlpha(img.color(x)) < 127)
            {
                transColor = x;
                break;
            }
        }


        // FIXME: image must have transparency
        if(transColor < 0 || transColor >= img.numColors())
            return;

	img.setColor(transColor, 0);
        if(img.depth() == 8)
        {
            for (y=0; y<img.height(); y++)
            {
                unsigned char *line = img.scanLine(y);
                for (x=(y%2); x<img.width(); x+=2)
                    line[x] = transColor;
            }
	}
        else
        {
            // SLOOW, but simple, as we would have to
            // deal with endianess etc on our own here
            for (y=0; y<img.height(); y++)
                for (x=(y%2); x<img.width(); x+=2)
                    img.setPixel(x, y, transColor);
        }
    }
}

void TDEIconEffect::semiTransparent(TQPixmap &pix)
{
    if ( tqt_use_xrender && tqt_has_xft )
    {
	TQImage img=pix.convertToImage();
	semiTransparent(img);
	pix.convertFromImage(img);
	return;
    }

    TQImage img;
    if (pix.mask() != 0L)
	img = pix.mask()->convertToImage();
    else
    {
	img.create(pix.size(), 1, 2, TQImage::BigEndian);
	img.fill(1);
    }

    for (int y=0; y<img.height(); y++)
    {
	QRgb *line = (QRgb *) img.scanLine(y);
	QRgb pattern = (y % 2) ? 0x55555555 : 0xaaaaaaaa;
	for (int x=0; x<(img.width()+31)/32; x++)
	    line[x] &= pattern;
    }
    TQBitmap mask;
    mask.convertFromImage(img);
    pix.setMask(mask);
}

TQImage TDEIconEffect::doublePixels(TQImage src) const
{
    TQImage dst;
    if (src.depth() == 1)
    {
	kdDebug(265) << "image depth 1 not supported\n";
	return dst;
    }

    int w = src.width();
    int h = src.height();
    dst.create(w*2, h*2, src.depth());
    dst.setAlphaBuffer(src.hasAlphaBuffer());

    int x, y;
    if (src.depth() == 32)
    {
	QRgb *l1, *l2;
	for (y=0; y<h; y++)
	{
	    l1 = (QRgb *) src.scanLine(y);
	    l2 = (QRgb *) dst.scanLine(y*2);
	    for (x=0; x<w; x++)
	    {
		l2[x*2] = l2[x*2+1] = l1[x];
	    }
	    memcpy(dst.scanLine(y*2+1), l2, dst.bytesPerLine());
	}
    } else
    {
	for (x=0; x<src.numColors(); x++)
	    dst.setColor(x, src.color(x));

	unsigned char *l1, *l2;
	for (y=0; y<h; y++)
	{
	    l1 = src.scanLine(y);
	    l2 = dst.scanLine(y*2);
	    for (x=0; x<w; x++)
	    {
		l2[x*2] = l1[x];
		l2[x*2+1] = l1[x];
	    }
	    memcpy(dst.scanLine(y*2+1), l2, dst.bytesPerLine());
	}
    }
    return dst;
}

void TDEIconEffect::overlay(TQImage &src, TQImage &overlay)
{
    if (src.depth() != overlay.depth())
    {
	kdDebug(265) << "Image depth src != overlay!\n";
	return;
    }
    if (src.size() != overlay.size())
    {
	kdDebug(265) << "Image size src != overlay\n";
	return;
    }
    if (!overlay.hasAlphaBuffer())
    {
	kdDebug(265) << "Overlay doesn't have alpha buffer!\n";
	return;
    }

    int i, j;

    // We don't do 1 bpp

    if (src.depth() == 1)
    {
	kdDebug(265) << "1bpp not supported!\n";
	return;
    }

    // Overlay at 8 bpp doesn't use alpha blending

    if (src.depth() == 8)
    {
	if (src.numColors() + overlay.numColors() > 255)
	{
	    kdDebug(265) << "Too many colors in src + overlay!\n";
	    return;
	}

	// Find transparent pixel in overlay
	int trans;
	for (trans=0; trans<overlay.numColors(); trans++)
	{
	    if (tqAlpha(overlay.color(trans)) == 0)
	    {
		kdDebug(265) << "transparent pixel found at " << trans << "\n";
		break;
	    }
	}
	if (trans == overlay.numColors())
	{
	    kdDebug(265) << "transparent pixel not found!\n";
	    return;
	}

	// Merge color tables
	int nc = src.numColors();
	src.setNumColors(nc + overlay.numColors());
	for (i=0; i<overlay.numColors(); i++)
	{
	    src.setColor(nc+i, overlay.color(i));
	}

	// Overwrite nontransparent pixels.
	unsigned char *oline, *sline;
	for (i=0; i<src.height(); i++)
	{
	    oline = overlay.scanLine(i);
	    sline = src.scanLine(i);
	    for (j=0; j<src.width(); j++)
	    {
		if (oline[j] != trans)
		    sline[j] = oline[j]+nc;
	    }
	}
    }

    // Overlay at 32 bpp does use alpha blending

    if (src.depth() == 32)
    {
	QRgb *oline, *sline;
	int r1, g1, b1, a1;
	int r2, g2, b2, a2;

	for (i=0; i<src.height(); i++)
	{
	    oline = (QRgb *) overlay.scanLine(i);
	    sline = (QRgb *) src.scanLine(i);

	    for (j=0; j<src.width(); j++)
	    {
		r1 = tqRed(oline[j]);
		g1 = tqGreen(oline[j]);
		b1 = tqBlue(oline[j]);
		a1 = tqAlpha(oline[j]);

		r2 = tqRed(sline[j]);
		g2 = tqGreen(sline[j]);
		b2 = tqBlue(sline[j]);
		a2 = tqAlpha(sline[j]);

		r2 = (a1 * r1 + (0xff - a1) * r2) >> 8;
		g2 = (a1 * g1 + (0xff - a1) * g2) >> 8;
		b2 = (a1 * b1 + (0xff - a1) * b2) >> 8;
		a2 = TQMAX(a1, a2);

		sline[j] = tqRgba(r2, g2, b2, a2);
	    }
	}
    }

    return;
}

    void
TDEIconEffect::visualActivate(TQWidget * widget, TQRect rect)
{
    if (!TDEGlobalSettings::visualActivate())
        return;

    uint actSpeed = TDEGlobalSettings::visualActivateSpeed();

    uint actCount = TQMIN(rect.width(), rect.height()) / 2;

    // Clip actCount to range 1..10.

    if (actCount < 1)
        actCount = 1;

    else if (actCount > 10)
        actCount = 10;

    // Clip actSpeed to range 1..100.

    if (actSpeed < 1)
        actSpeed = 1;

    else if (actSpeed > 100)
        actSpeed = 100;

    // actSpeed needs to be converted to actDelay.
    // actDelay is inversely proportional to actSpeed and needs to be
    // divided up into actCount portions.
    // We also convert the us value to ms.

    unsigned int actDelay = (1000 * (100 - actSpeed)) / actCount;

    //kdDebug() << "actCount=" << actCount << " actDelay=" << actDelay << endl;

    TQPoint c = rect.center();

    TQPainter p(widget);

    // Use NotROP to avoid having to repaint the pixmap each time.
    p.setPen(TQPen(Qt::black, 2, Qt::DotLine));
    p.setRasterOp(TQt::NotROP);

    // The spacing between the rects we draw.
    // Use the minimum of width and height to avoid painting outside the
    // pixmap area.
    //unsigned int delta(TQMIN(rect.width() / actCount, rect.height() / actCount));

    // Support for rectangles by David
    unsigned int deltaX = rect.width() / actCount;
    unsigned int deltaY = rect.height() / actCount;

    for (unsigned int i = 1; i < actCount; i++) {

        int w = i * deltaX;
        int h = i * deltaY;

        rect.setRect(c.x() - w / 2, c.y() - h / 2, w, h);

        p.drawRect(rect);
        p.flush();

        usleep(actDelay);

        p.drawRect(rect);
    }
}

void
TDEIconEffect::visualActivate(TQWidget * widget, TQRect rect, TQPixmap *pixmap)
{
    if (!TDEGlobalSettings::visualActivate())
        return;

    // Image too big to display smoothly
    if ((rect.width() > 160) || (rect.height() > 160)) {
	visualActivate(widget, rect); // call old effect
	return;
    }

    uint actSpeed = TDEGlobalSettings::visualActivateSpeed();
    uint actCount = TQMIN(rect.width(), rect.height()) / 4;


    // Clip actCount to range 1..10.
    if (actCount < 1)
        actCount = 1;

    else if (actCount > 10)
        actCount = 10;

    // Clip actSpeed to range 1..100.
    if (actSpeed < 1)
        actSpeed = 1;

    else if (actSpeed > 100)
        actSpeed = 100;

    // actSpeed needs to be converted to actDelay.
    // actDelay is inversely proportional to actSpeed and needs to be
    // divided up into actCount portions.
    // We also convert the us value to ms.

    unsigned int actDelay = (1000 * (100 - actSpeed)) / actCount;

    unsigned int deltaX = rect.width() / actCount * 1.5;
    unsigned int deltaY = rect.height() / actCount * 1.5;

    TQPoint c = rect.center();
    TQRect maxRect(c.x() - (actCount * 2) * deltaX /2,
	          c.y() - (actCount * 2) * deltaY /2,
		  actCount * 2 * deltaX,
		  actCount * 2 * deltaY);

    // convert rect to global coordinates if needed
    if ((widget->rect().width() <= maxRect.width())
       || (widget->rect().height() <= maxRect.height()))
    {
	TQPoint topLeft(rect.x(), rect.y());
	rect.moveLeft(widget->mapToGlobal(topLeft).x());
	rect.moveTop(widget->mapToGlobal(topLeft).y());
	c = rect.center();
	maxRect.setRect(c.x() - (actCount * 2) * deltaX /2,
	        	c.y() - (actCount * 2) * deltaY /2,
			actCount * 2 * deltaX,
			actCount * 2 * deltaY);
    }

    TQPainter *p;
    TQImage img = pixmap->convertToImage();
    TQPixmap pix;
    TQPixmap composite(maxRect.width(), maxRect.height(), -1, TQPixmap::BestOptim);
    TQPainter cPainter(&composite);
    TQPoint cComposite = composite.rect().center();

    // enable alpha blending
    img.setAlphaBuffer(true);
    
    // Ugly hack... Get "Screenshot" to blt into and even do that on the
    // root window if the display area of <widget> is too small
    if ((widget->rect().width() <= maxRect.width())
       || (widget->rect().height() <= maxRect.height()))
    {
//	p = new TQPainter(TQApplication::desktop()->screen( -1 ), TRUE);	// WARNING: This was done in Qt3.  It only worked in this placement due to a glitch in Qt3; it has therefore been moved below grabWidget, where it should have been in the first place.
	pix = TQPixmap::grabWindow((TQApplication::desktop()->screen( -1 ))->winId(),
		    		      maxRect.x(),
				      maxRect.y(),
				      maxRect.width(),
				      maxRect.height());
        p = new TQPainter(TQApplication::desktop()->screen( -1 ), TRUE);
    } else
    {
	// not as ugly as drawing directly to the screen
//	p = new TQPainter(widget);	// WARNING: This was done in Qt3.  See above.
	pix = TQPixmap::grabWidget(widget,
			              maxRect.x(),
				      maxRect.y(),
				      maxRect.width(),
				      maxRect.height());
	p = new TQPainter(widget);
    }
    uchar deltaAlpha = 255 / (actCount * 1.2);
    
    // Activate effect like MacOS X
    for (unsigned int i = actCount; i < actCount * 2; i++) {

        int w = i * deltaX;
        int h = i * deltaY;

        rect.setRect(cComposite.x() - w / 2, cComposite.y() - h / 2, w, h);

	// draw offscreen
	cPainter.drawPixmap(0, 0, pix, 0, 0, pix.width(), pix.height());
	cPainter.drawImage(rect, img);
	cPainter.flush();

	// put onscreen
	p->drawPixmap(maxRect, composite);
        p->flush();

	// Fade out Icon a bit more
        int x, y;
        if ((img.depth() == 32) && tqt_use_xrender && tqt_has_xft)
        {
    	    int width  = img.width();
	    int height = img.height();
	
	    for (y=0; y<height; y++)
	    {
#ifdef WORDS_BIGENDIAN
		uchar *line = (uchar*) img.scanLine(y);
#else
		uchar *line = (uchar*) img.scanLine(y) + 3;
#endif
		for (x=0; x<width; x++)
		{
		    *line = (*line < deltaAlpha) ? 0 : *line - deltaAlpha;
		    line += 4;
		}
	    }
	}
        usleep(actDelay*3);
    }

    // remove traces of the effect
    if ((widget->rect().width() <= maxRect.width())
       || (widget->rect().height() <= maxRect.height()))
	p->drawPixmap(maxRect, pix);
    else {
	 p->drawPixmap(maxRect, pix);
        widget->update(rect);
    }

    delete p;
}