ALabel.cpp

Go to the documentation of this file.
00001 //==============================================
00002 //  copyright            : (C) 2003-2005 by Will Stokes
00003 //==============================================
00004 //  This program is free software; you can redistribute it
00005 //  and/or modify it under the terms of the GNU General
00006 //  Public License as published by the Free Software
00007 //  Foundation; either version 2 of the License, or
00008 //  (at your option) any later version.
00009 //==============================================
00010 
00011 //Systemwide includes
00012 #include <qtimer.h>
00013 #include <qpixmap.h>
00014 #include <qimage.h>
00015 #include <qapplication.h>
00016 #include <qpainter.h>
00017 #include <qstyle.h>
00018 #include <qmutex.h>
00019 #include <qcursor.h>
00020 
00021 //Projectwide includes
00022 #include "ALabel.h"
00023 
00024 //==============================================
00025 ALabel::ALabel( QWidget* parent, const char* name,
00026                         QPixmap* hoverOverImage,
00027                         int setMethod, int removalMethod,
00028                         int resetMethod, int removalBeforeResetMethod,
00029                         int initDelay,  int accel)  : QLabel(parent,name)
00030 {
00031   delayedActionHead = NULL;
00032   delayedActionTail = NULL;
00033 
00034   //set animation defaults
00035   this->setMethod = setMethod;
00036   this->removalMethod = removalMethod;
00037   this->removalBeforeResetMethod = removalBeforeResetMethod;
00038   this->resetMethod = resetMethod;
00039 
00040   //set hover over image
00041   this->hoverOverImage = hoverOverImage;
00042   drawHoverOverImage = false;
00043 
00044   setMouseTracking(true);
00045   handCursorShown = false;
00046 
00047   //by default image is not shown, we are not animating, nor resetting the image
00048   imageShown = false;
00049   animating = false;
00050   resettingImage = false;
00051   pixStore = NULL;
00052   resetPixStore = NULL;
00053 
00054   //don't clear pixmap area before painting, prevents flicker
00055   setWFlags(WRepaintNoErase);
00056 
00057   //set delay defaults
00058   this->initDelay = initDelay;
00059   this->accel = accel;
00060   this->minDelay = 1;
00061 
00062   //create timer object and setup signals
00063   timer = new QTimer();
00064   connect(timer, SIGNAL(timeout()), this, SLOT(animate()) );
00065 }
00066 //==============================================
00067 void ALabel::setAnimationMethods( int setMethod, int removalMethod,
00068                                                         int resetMethod, int removalBeforeResetMethod )
00069 {
00070   //set animation defaults
00071   this->setMethod = setMethod;
00072   this->removalMethod = removalMethod;
00073   this->removalBeforeResetMethod = removalBeforeResetMethod;
00074   this->resetMethod = resetMethod;
00075 }
00076 //==============================================
00077 void ALabel::setPixmap ( const QPixmap & p )
00078 {
00079   //get locks on queues
00080   queueMutex.lock();
00081   animatingBoolMutex.lock();
00082   //if currently animating then append job to queue
00083   if(animating || delayedActionHead != NULL)
00084   {
00085     appendJob(new QPixmap(p));
00086     animatingBoolMutex.unlock();
00087     queueMutex.unlock();
00088     return;
00089   }
00090   //else set animating to true, actually initiate job
00091   else
00092   {
00093     animating = true;
00094     animatingBoolMutex.unlock();
00095     queueMutex.unlock();
00096     internalSetPixmap( p );
00097   }
00098 }
00099 //==============================================
00100 void ALabel::internalSetPixmap ( const QPixmap & p )
00101 {
00102   //if previous image already present remove it first
00103   if(pixStore)
00104   {
00105     resettingImage = true;
00106 
00107     //backup new set image to be retrieved later
00108     if(resetPixStore)
00109     {
00110 //      cout << "LEAK DETECTED when trying to resetPixStore\n";
00111       delete resetPixStore;
00112     }
00113     resetPixStore = new QImage(p.convertToImage());
00114 
00115     //set animation method
00116     animationType = removalBeforeResetMethod;
00117 
00118     //remove old pixmap
00119     animatePixmap();
00120   }
00121   //else immediately set image
00122   else
00123   {
00124     resettingImage = false;
00125     //set pixmap store and animate setting it
00126     pixStore = new QImage(p.convertToImage());
00127     animationType = setMethod;
00128     animatePixmap();
00129   }
00130 }
00131 //==============================================
00132 void ALabel::removePixmap ( bool forceImmediate)
00133 {
00134   //get locks on queues
00135   queueMutex.lock();
00136   animatingBoolMutex.lock();
00137   //if currently animating then append job to queue
00138   if(animating || delayedActionHead != NULL)
00139   {
00140     appendJob( NULL );
00141     animatingBoolMutex.unlock();
00142     queueMutex.unlock();
00143     return;
00144   }
00145   //else set animating to true, actually initiate job (only if no image currently exists)
00146   else
00147   {
00148     //only remove image if image currently exists
00149     if(pixStore != NULL)
00150       animating = true;
00151 
00152     animatingBoolMutex.unlock();
00153     queueMutex.unlock();
00154 
00155     if(animating)
00156       internalRemovePixmap( forceImmediate );
00157   }
00158 }
00159 //==============================================
00160 void ALabel::internalRemovePixmap( bool forceImmediate )
00161 {
00162   //set animation method and animate
00163   if(forceImmediate)
00164     animationType = DISAPPEAR_IMMEDIATELY;
00165   else
00166     animationType = removalMethod;
00167 
00168   animatePixmap();
00169 }
00170 //==============================================
00171 void ALabel::animatePixmap()
00172 {
00173   //set the step paramater. for slide transitions this is the number
00174   //of columns to be displayed. for fade transitions this is what percentage
00175   //of the fade we have completed so start out at 1.
00176   if(animationType == SLIDE_OUT_LEFT ||
00177      animationType == SLIDE_OUT_RIGHT ||
00178      animationType == DISAPPEAR_IMMEDIATELY)
00179   { step = pixStore->width()-1; }
00180   else
00181   { step = 1; }
00182 
00183   //set initial delay/speed
00184   delay = initDelay;
00185 
00186   //find current time, used to decide how many new columns to reveal in first iteration
00187   lastTime.start();
00188 
00189   //begin animation
00190   animate();
00191 }
00192 //==============================================
00193 void ALabel::animate()
00194 {
00195   //---------------------------------
00196   //backup last # of columns shown
00197   int lastStep = step;
00198   //---------------------------------
00199   //determine new number of columns to be shown
00200   if(animationType == APPEAR_IMMEDIATELY)
00201     step = pixStore->width();
00202   else if(animationType == DISAPPEAR_IMMEDIATELY)
00203     step = 0;
00204   else
00205   {
00206     //determine # of ms that have passed since last redraw
00207     currentTime.start();
00208     double ms = lastTime.msecsTo(currentTime);
00209 
00210     //determine increment
00211     int inc = (int)(ms/(delay+1));
00212 
00213     if(animationType == SLIDE_OUT_LEFT ||
00214        animationType == SLIDE_OUT_RIGHT)
00215     { inc = -inc; }
00216 
00217     //if increment is not zero then update last time
00218     if(inc != 0)
00219     {
00220       lastTime = currentTime;
00221     }
00222 
00223     //update number of columns shown
00224     step = step + inc;
00225 
00226     //boundary conditions
00227     if( animationType == FADE_TRANSITION)
00228     {
00229       if(step > 100)
00230         step = 100;
00231     }
00232     else
00233     {
00234       if(step > pixStore->width())
00235         step = pixStore->width();
00236       else if(step < 0)
00237         step = 0;
00238      }
00239   }
00240   //---------------------------------
00241   //if percentage of frade has changed then redraw
00242   if(animationType == FADE_TRANSITION)
00243   {
00244     if(step != lastStep)
00245     {
00246       //compute intermediate image resolution
00247       double w1 = pixStore->width();
00248       double h1 = pixStore->height();
00249       double w2 = resetPixStore->width();
00250       double h2 = resetPixStore->height();
00251       double alpha = ((double)step) / 100.0;
00252       int w = (int) ( w1 + (w2-w1)*alpha );
00253       int h = (int) ( h1 + (h2-h1)*alpha );
00254 
00255       //resize old and new images
00256       QImage oldImg = pixStore->scale( w, h );
00257       QImage newImg = resetPixStore->scale( w, h );
00258 
00259       //scale each image by alpha (step/100) and 1-alpha and combine
00260       int maxDepth = pixStore->depth();
00261       if(resetPixStore->depth() > maxDepth)
00262         maxDepth = resetPixStore->depth();
00263 
00264       QImage tmpImage(w, h, maxDepth);
00265 
00266       int x,y;
00267       for(x = 0; x<w; x++)
00268       {
00269         for(y = 0; y<h; y++)
00270         {
00271           QRgb v1 = oldImg.pixel(x,y);
00272           QRgb v2 = newImg.pixel(x,y);
00273           int r = (int) (alpha* qRed(v2) + (1-alpha)*qRed(v1));
00274           int g = (int) (alpha* qGreen(v2) + (1-alpha)*qGreen(v1));
00275           int b = (int) (alpha* qBlue(v2) + (1-alpha)*qBlue(v1));
00276 
00277           tmpImage.setPixel(x, y, qRgb(r,g,b) );
00278         }
00279       }
00280 
00281       //set image shown to this interpolated image
00282       QPixmap tmpPixmap(step, pixStore->height() );
00283       tmpPixmap.convertFromImage( tmpImage );
00284       QLabel::setPixmap( tmpPixmap );
00285 
00286       //I've noticed when we replace images with ones which are shorter (height wise) some pixels
00287       //are left behind. this is annoying. a quick fix is to so a erase repaint in this situation.
00288       //another kludgly (but flickerless) improvement woudl be to create a pixmap that is the
00289       //same size as the current but paints the bottom and top edges with the background color.
00290       //I honestly think this is a bug in Qt that Trolltech needs to address. Maybe I should report it to them.
00291       if(h2 < h1)
00292         repaint(true);
00293       else
00294         repaint(false);
00295     }
00296   }
00297   //if the number of cols to be shown has changed then redraw
00298   else if(step != lastStep &&
00299      (
00300        animationType != DISAPPEAR_IMMEDIATELY ||
00301        (animationType == DISAPPEAR_IMMEDIATELY && !resettingImage)
00302      )
00303    )
00304   {
00305     //draw empty image
00306     if(step == 0)
00307     {
00308       QLabel::setPixmap( NULL );
00309       emit pixmapRemoved();
00310       repaint(true);
00311     }
00312     //draw full image
00313     else if(step == pixStore->width() )
00314     {
00315       QLabel::setPixmap( *pixStore );
00316       repaint(false);
00317     }
00318     //draw a portion the image
00319     else
00320     {
00321       //construct temp image
00322       QImage tmpImage(step, pixStore->height(), pixStore->depth() );
00323       int x,y;
00324       for(x = 0; x<step; x++)
00325       {
00326         for(y = 0; y<pixStore->height(); y++)
00327         {
00328           if(animationType == SLIDE_IN_LEFT ||
00329             animationType == SLIDE_OUT_LEFT)
00330           { tmpImage.setPixel( x, y, pixStore->pixel(pixStore->width()-step+x,y) ); }
00331           else
00332           { tmpImage.setPixel( x, y, pixStore->pixel(x,y) ); }
00333         } //end for y
00334       } //end for x
00335 
00336       //set label to use temp image (a portion of the full image)
00337       QPixmap tmpPixmap(step, pixStore->height() );
00338       tmpPixmap.convertFromImage( tmpImage );
00339       QLabel::setPixmap( tmpPixmap );
00340 
00341       if(animationType == SLIDE_OUT_LEFT ||
00342           animationType == SLIDE_OUT_RIGHT)
00343         repaint(true);
00344       else
00345         repaint(false);
00346     }
00347   }
00348   //---------------------------------
00349   //not done animating, reiterate
00350   if(
00351       ((animationType == SLIDE_IN_LEFT ||
00352        animationType == SLIDE_IN_RIGHT) && step < pixStore->width()) ||
00353       ((animationType == SLIDE_OUT_LEFT ||
00354        animationType == SLIDE_OUT_RIGHT) && step > 0) ||
00355       (animationType ==  FADE_TRANSITION && step < 100)
00356     )
00357   {
00358     //update speed
00359     delay = delay - accel;
00360     if(delay < minDelay) delay = minDelay;
00361 
00362     //restart timer
00363     timer->start( delay, TRUE );
00364   }
00365   //---------------------------------
00366   //done animating....
00367   else
00368   {
00369     //If we just removed the image then delete the old pixStore object
00370     if(step == 0)
00371     {
00372       imageShown = false;
00373       delete pixStore;
00374       pixStore = NULL;
00375     }
00376     else
00377     {
00378       imageShown = true;
00379     }
00380 
00381     //if we are actually in the middle of a reset action then we've
00382     //just finished removing the old image, now it's time to start setting the new image
00383     if(resettingImage && animationType !=  FADE_TRANSITION)
00384     {
00385       resettingImage = false;
00386 
00387       if(pixStore)
00388       {
00389 //        cout << "ERROR! Leak detected!\n";
00390         delete pixStore;
00391         pixStore = NULL;
00392       }
00393 
00394       pixStore = resetPixStore;
00395       resetPixStore = NULL;
00396       animationType = resetMethod;
00397       animatePixmap();
00398     }
00399     //else truely done
00400     else
00401     {
00402       //we were running a fade effect then delete old pixmap and repalce it with new one
00403       if( animationType == FADE_TRANSITION )
00404       {
00405         resettingImage = false;
00406         delete pixStore;
00407         pixStore = resetPixStore;
00408         resetPixStore = NULL;
00409       }
00410 
00411       //check to see pending animations exist, if so pop off next action from list
00412       queueMutex.lock();
00413 
00414       //simplify list of pending actions
00415       cleanStack();
00416 
00417       if(delayedActionHead == NULL)
00418       {
00419         //done!
00420         queueMutex.unlock();
00421         animatingBoolMutex.lock();
00422         animating = false;
00423         animatingBoolMutex.unlock();
00424         return;
00425       }
00426 
00427       //ok, we're not done, pop off first entry from queue, then start up actual job
00428       Action* currAction = delayedActionHead;
00429       delayedActionHead = delayedActionHead->getNext();
00430       if(delayedActionHead == NULL)
00431         delayedActionTail = NULL;
00432 
00433       queueMutex.unlock();
00434 
00435       //start job
00436       if(currAction->getImage() == NULL) internalRemovePixmap();
00437       else internalSetPixmap( *currAction->getImage() );
00438 
00439       //free old action object
00440       delete currAction;
00441     }
00442   }
00443   //---------------------------------------
00444 }
00445 //==============================================
00446 void ALabel::drawContents( QPainter* p )
00447 {
00448   //draw conents of label
00449   QLabel::drawContents(p);
00450 
00451   //if animation complete and image is being shown, draw hover over image
00452   if(!animating && imageShown && drawHoverOverImage)
00453   {
00454     QRect r = style().itemRect( p, contentsRect(), alignment(), isEnabled(), pixmap(), text());
00455 
00456     int minDim = r.width();
00457     if(r.height() < minDim)
00458       minDim = r.height();
00459     if(minDim > hoverOverImage->width())
00460     {
00461       r.setLeft( r.right() - hoverOverImage->width() );
00462       r.setBottom( r.top() + hoverOverImage->height() );
00463       hoverOverRect = r;
00464       p->drawPixmap( r, *hoverOverImage);
00465     }
00466     else
00467     {
00468       QImage resizedImage = hoverOverImage->convertToImage().scale(minDim, minDim);
00469       QPixmap resizedPixmap(resizedImage);
00470       r.setLeft( r.right() - resizedPixmap.width() );
00471       r.setBottom( r.top() + resizedPixmap.height() );
00472       hoverOverRect = r;
00473       p->drawPixmap( r, resizedPixmap);
00474     }
00475   }
00476 }
00477 //==============================================
00478 void ALabel::enterEvent( QEvent*)
00479 {
00480   if(hoverOverImage)
00481   {
00482     drawHoverOverImage = true;
00483     repaint( false );
00484   }
00485 }
00486 //==============================================
00487 void ALabel::leaveEvent( QEvent*)
00488 {
00489   if(hoverOverImage)
00490   {
00491     drawHoverOverImage = false;
00492     repaint(false);
00493   }
00494 }
00495 //==============================================
00496 void ALabel::mousePressEvent( QMouseEvent* )
00497 { emit mousePress(); }
00498 //==============================================
00499 void ALabel::mouseReleaseEvent( QMouseEvent* e)
00500 {
00501   //if there is no hover over image then ignore event
00502   if( hoverOverImage == NULL ) return;
00503   
00504   QPainter* p = new QPainter();
00505   QRect r = style().itemRect( p, contentsRect(), alignment(), isEnabled(), pixmap(), text());
00506   delete p;
00507   int minDim = r.width();
00508   if(r.height() < minDim)
00509     minDim = r.height();
00510   if(minDim > hoverOverImage->width())
00511   {
00512     r.setLeft( r.right() - hoverOverImage->width() );
00513     r.setBottom( r.top() + hoverOverImage->height() );
00514   }
00515   else
00516   {
00517     QImage resizedImage = hoverOverImage->convertToImage().scale(minDim, minDim);
00518     QPixmap resizedPixmap(resizedImage);
00519     r.setLeft( r.right() - resizedPixmap.width() );
00520     r.setBottom( r.top() + resizedPixmap.height() );
00521   }
00522   
00523   if(r.contains( e->pos() ) )
00524   {
00525     removePixmap();
00526     emit mouseRelease();
00527   }
00528 }
00529 //==============================================
00530 void ALabel::mouseDoubleClickEvent( QMouseEvent* )
00531 { emit mouseDoubleClick(); }
00532 //==============================================
00533 void ALabel::mouseMoveEvent( QMouseEvent* e)
00534 {
00535   //need rect so draw hover over image must exist before any checks can occur
00536   if( !drawHoverOverImage )
00537     return;
00538 
00539   //if hand not shown but over hover over image then turn hand cursor on
00540   if( !handCursorShown && hoverOverRect.contains( e->x(), e->y() ) )
00541   {
00542     setCursor( QCursor( Qt::PointingHandCursor ) );
00543     handCursorShown = true;
00544     return;
00545   }
00546 
00547   //if hand cursor shown but nolonger over hover over image set cursor back to normal
00548   if( handCursorShown && !hoverOverRect.contains( e->x(), e->y() ) )
00549   {
00550     setCursor( QCursor( Qt::ArrowCursor ) );
00551     handCursorShown = false;
00552     return;
00553   }
00554 }
00555 //==============================================
00556 void ALabel::appendJob(QPixmap* pix)
00557 {
00558   Action* newAct = new Action(pix);
00559   if(delayedActionHead == NULL)
00560     delayedActionHead = newAct;
00561   else
00562     delayedActionTail->setNext( newAct );
00563 
00564   delayedActionTail = newAct;
00565 }
00566 //==============================================
00567 void ALabel::cleanStack()
00568 {
00569   //if stack empty already clean
00570   if(delayedActionHead == NULL)
00571     return;
00572 
00573   //if no image currently displayed, pop off all remove actions from head of list
00574   if(pixStore == NULL)
00575   {
00576       Action* currAction = delayedActionHead;
00577       while(currAction != NULL && currAction->getImage() == NULL)
00578       {
00579         Action* next = currAction->getNext();
00580         delete currAction;
00581         currAction = next;
00582       }
00583 
00584       delayedActionHead = currAction;
00585       if(currAction == NULL)
00586         delayedActionTail = NULL;
00587   }
00588 
00589   //if one pending operations no simplifications possible
00590   if(delayedActionHead == delayedActionTail)
00591     return;
00592 
00593   //if last action is a set operation then remove all other entries
00594   if(delayedActionTail->getImage() != NULL)
00595   {
00596     Action* temp = delayedActionHead;
00597     delayedActionHead = delayedActionTail;
00598     while(temp != delayedActionHead)
00599     {
00600       Action* next = temp->getNext();
00601       delete temp;
00602       temp = next;
00603     }
00604     return;
00605   }
00606 
00607   //last action is a remove operation, if no current image then cull entire stack
00608   if(pixStore == NULL)
00609   {
00610     Action* temp = delayedActionHead;
00611     while(temp != NULL)
00612     {
00613       Action* next = temp->getNext();
00614       delete temp;
00615       temp = next;
00616     }
00617     delayedActionHead = NULL;
00618     delayedActionTail = NULL;
00619     return;
00620   }
00621   //else remove all but last pending operations since final remove action will remove current image
00622   else
00623   {
00624     Action* temp = delayedActionHead;
00625     while(temp != delayedActionTail)
00626     {
00627       Action* next = temp->getNext();
00628       delete temp;
00629       temp = next;
00630     }
00631     delayedActionHead = delayedActionTail;
00632     return;
00633   }
00634 }
00635 //==============================================
00636 Action::Action(QPixmap* image)
00637 {
00638   this->image = image;
00639   next = NULL;
00640 }
00641 //==============================================
00642 Action::~Action()
00643 {
00644   delete image;
00645   image = NULL;
00646 }
00647 //==============================================
00648 Action* Action::getNext()
00649 { return next; }
00650 //==============================================
00651 void Action::setNext( Action* next)
00652 { this->next = next; }
00653 //==============================================
00654 QPixmap* Action::getImage()
00655 { return image; }
00656 //==============================================

Generated on Thu Jan 3 10:52:45 2008 for AlbumShaper by  doxygen 1.5.4