//============================================== // copyright : (C) 2003-2005 by Will Stokes //============================================== // This program is free software; you can redistribute it // and/or modify it under the terms of the GNU General // Public License as published by the Free Software // Foundation; either version 2 of the License, or // (at your option) any later version. //============================================== //Systemwide includes #include #include #include #include #include #include #include #include //Projectwide includes #include "ALabel.h" //============================================== ALabel::ALabel( QWidget* parent, const char* name, QPixmap* hoverOverImage, int setMethod, int removalMethod, int resetMethod, int removalBeforeResetMethod, int initDelay, int accel) : QLabel(parent,name) { delayedActionHead = NULL; delayedActionTail = NULL; //set animation defaults this->setMethod = setMethod; this->removalMethod = removalMethod; this->removalBeforeResetMethod = removalBeforeResetMethod; this->resetMethod = resetMethod; //set hover over image this->hoverOverImage = hoverOverImage; drawHoverOverImage = false; setMouseTracking(true); handCursorShown = false; //by default image is not shown, we are not animating, nor resetting the image imageShown = false; animating = false; resettingImage = false; pixStore = NULL; resetPixStore = NULL; //don't clear pixmap area before painting, prevents flicker setWFlags(WRepaintNoErase); //set delay defaults this->initDelay = initDelay; this->accel = accel; this->minDelay = 1; //create timer object and setup signals timer = new QTimer(); connect(timer, SIGNAL(timeout()), this, SLOT(animate()) ); } //============================================== void ALabel::setAnimationMethods( int setMethod, int removalMethod, int resetMethod, int removalBeforeResetMethod ) { //set animation defaults this->setMethod = setMethod; this->removalMethod = removalMethod; this->removalBeforeResetMethod = removalBeforeResetMethod; this->resetMethod = resetMethod; } //============================================== void ALabel::setPixmap ( const QPixmap & p ) { //get locks on queues queueMutex.lock(); animatingBoolMutex.lock(); //if currently animating then append job to queue if(animating || delayedActionHead != NULL) { appendJob(new QPixmap(p)); animatingBoolMutex.unlock(); queueMutex.unlock(); return; } //else set animating to true, actually initiate job else { animating = true; animatingBoolMutex.unlock(); queueMutex.unlock(); internalSetPixmap( p ); } } //============================================== void ALabel::internalSetPixmap ( const QPixmap & p ) { //if previous image already present remove it first if(pixStore) { resettingImage = true; //backup new set image to be retrieved later if(resetPixStore) { // cout << "LEAK DETECTED when trying to resetPixStore\n"; delete resetPixStore; } resetPixStore = new QImage(p.convertToImage()); //set animation method animationType = removalBeforeResetMethod; //remove old pixmap animatePixmap(); } //else immediately set image else { resettingImage = false; //set pixmap store and animate setting it pixStore = new QImage(p.convertToImage()); animationType = setMethod; animatePixmap(); } } //============================================== void ALabel::removePixmap ( bool forceImmediate) { //get locks on queues queueMutex.lock(); animatingBoolMutex.lock(); //if currently animating then append job to queue if(animating || delayedActionHead != NULL) { appendJob( NULL ); animatingBoolMutex.unlock(); queueMutex.unlock(); return; } //else set animating to true, actually initiate job (only if no image currently exists) else { //only remove image if image currently exists if(pixStore != NULL) animating = true; animatingBoolMutex.unlock(); queueMutex.unlock(); if(animating) internalRemovePixmap( forceImmediate ); } } //============================================== void ALabel::internalRemovePixmap( bool forceImmediate ) { //set animation method and animate if(forceImmediate) animationType = DISAPPEAR_IMMEDIATELY; else animationType = removalMethod; animatePixmap(); } //============================================== void ALabel::animatePixmap() { //set the step paramater. for slide transitions this is the number //of columns to be displayed. for fade transitions this is what percentage //of the fade we have completed so start out at 1. if(animationType == SLIDE_OUT_LEFT || animationType == SLIDE_OUT_RIGHT || animationType == DISAPPEAR_IMMEDIATELY) { step = pixStore->width()-1; } else { step = 1; } //set initial delay/speed delay = initDelay; //find current time, used to decide how many new columns to reveal in first iteration lastTime.start(); //begin animation animate(); } //============================================== void ALabel::animate() { //--------------------------------- //backup last # of columns shown int lastStep = step; //--------------------------------- //determine new number of columns to be shown if(animationType == APPEAR_IMMEDIATELY) step = pixStore->width(); else if(animationType == DISAPPEAR_IMMEDIATELY) step = 0; else { //determine # of ms that have passed since last redraw currentTime.start(); double ms = lastTime.msecsTo(currentTime); //determine increment int inc = (int)(ms/(delay+1)); if(animationType == SLIDE_OUT_LEFT || animationType == SLIDE_OUT_RIGHT) { inc = -inc; } //if increment is not zero then update last time if(inc != 0) { lastTime = currentTime; } //update number of columns shown step = step + inc; //boundary conditions if( animationType == FADE_TRANSITION) { if(step > 100) step = 100; } else { if(step > pixStore->width()) step = pixStore->width(); else if(step < 0) step = 0; } } //--------------------------------- //if percentage of frade has changed then redraw if(animationType == FADE_TRANSITION) { if(step != lastStep) { //compute intermediate image resolution double w1 = pixStore->width(); double h1 = pixStore->height(); double w2 = resetPixStore->width(); double h2 = resetPixStore->height(); double alpha = ((double)step) / 100.0; int w = (int) ( w1 + (w2-w1)*alpha ); int h = (int) ( h1 + (h2-h1)*alpha ); //resize old and new images QImage oldImg = pixStore->scale( w, h ); QImage newImg = resetPixStore->scale( w, h ); //scale each image by alpha (step/100) and 1-alpha and combine int maxDepth = pixStore->depth(); if(resetPixStore->depth() > maxDepth) maxDepth = resetPixStore->depth(); QImage tmpImage(w, h, maxDepth); int x,y; for(x = 0; xheight() ); tmpPixmap.convertFromImage( tmpImage ); QLabel::setPixmap( tmpPixmap ); //I've noticed when we replace images with ones which are shorter (height wise) some pixels //are left behind. this is annoying. a quick fix is to so a erase repaint in this situation. //another kludgly (but flickerless) improvement woudl be to create a pixmap that is the //same size as the current but paints the bottom and top edges with the background color. //I honestly think this is a bug in Qt that Trolltech needs to address. Maybe I should report it to them. if(h2 < h1) repaint(true); else repaint(false); } } //if the number of cols to be shown has changed then redraw else if(step != lastStep && ( animationType != DISAPPEAR_IMMEDIATELY || (animationType == DISAPPEAR_IMMEDIATELY && !resettingImage) ) ) { //draw empty image if(step == 0) { QLabel::setPixmap( NULL ); emit pixmapRemoved(); repaint(true); } //draw full image else if(step == pixStore->width() ) { QLabel::setPixmap( *pixStore ); repaint(false); } //draw a portion the image else { //construct temp image QImage tmpImage(step, pixStore->height(), pixStore->depth() ); int x,y; for(x = 0; xheight(); y++) { if(animationType == SLIDE_IN_LEFT || animationType == SLIDE_OUT_LEFT) { tmpImage.setPixel( x, y, pixStore->pixel(pixStore->width()-step+x,y) ); } else { tmpImage.setPixel( x, y, pixStore->pixel(x,y) ); } } //end for y } //end for x //set label to use temp image (a portion of the full image) QPixmap tmpPixmap(step, pixStore->height() ); tmpPixmap.convertFromImage( tmpImage ); QLabel::setPixmap( tmpPixmap ); if(animationType == SLIDE_OUT_LEFT || animationType == SLIDE_OUT_RIGHT) repaint(true); else repaint(false); } } //--------------------------------- //not done animating, reiterate if( ((animationType == SLIDE_IN_LEFT || animationType == SLIDE_IN_RIGHT) && step < pixStore->width()) || ((animationType == SLIDE_OUT_LEFT || animationType == SLIDE_OUT_RIGHT) && step > 0) || (animationType == FADE_TRANSITION && step < 100) ) { //update speed delay = delay - accel; if(delay < minDelay) delay = minDelay; //restart timer timer->start( delay, TRUE ); } //--------------------------------- //done animating.... else { //If we just removed the image then delete the old pixStore object if(step == 0) { imageShown = false; delete pixStore; pixStore = NULL; } else { imageShown = true; } //if we are actually in the middle of a reset action then we've //just finished removing the old image, now it's time to start setting the new image if(resettingImage && animationType != FADE_TRANSITION) { resettingImage = false; if(pixStore) { // cout << "ERROR! Leak detected!\n"; delete pixStore; pixStore = NULL; } pixStore = resetPixStore; resetPixStore = NULL; animationType = resetMethod; animatePixmap(); } //else truely done else { //we were running a fade effect then delete old pixmap and repalce it with new one if( animationType == FADE_TRANSITION ) { resettingImage = false; delete pixStore; pixStore = resetPixStore; resetPixStore = NULL; } //check to see pending animations exist, if so pop off next action from list queueMutex.lock(); //simplify list of pending actions cleanStack(); if(delayedActionHead == NULL) { //done! queueMutex.unlock(); animatingBoolMutex.lock(); animating = false; animatingBoolMutex.unlock(); return; } //ok, we're not done, pop off first entry from queue, then start up actual job Action* currAction = delayedActionHead; delayedActionHead = delayedActionHead->getNext(); if(delayedActionHead == NULL) delayedActionTail = NULL; queueMutex.unlock(); //start job if(currAction->getImage() == NULL) internalRemovePixmap(); else internalSetPixmap( *currAction->getImage() ); //free old action object delete currAction; } } //--------------------------------------- } //============================================== void ALabel::drawContents( QPainter* p ) { //draw conents of label QLabel::drawContents(p); //if animation complete and image is being shown, draw hover over image if(!animating && imageShown && drawHoverOverImage) { QRect r = style().itemRect( p, contentsRect(), alignment(), isEnabled(), pixmap(), text()); int minDim = r.width(); if(r.height() < minDim) minDim = r.height(); if(minDim > hoverOverImage->width()) { r.setLeft( r.right() - hoverOverImage->width() ); r.setBottom( r.top() + hoverOverImage->height() ); hoverOverRect = r; p->drawPixmap( r, *hoverOverImage); } else { QImage resizedImage = hoverOverImage->convertToImage().scale(minDim, minDim); QPixmap resizedPixmap(resizedImage); r.setLeft( r.right() - resizedPixmap.width() ); r.setBottom( r.top() + resizedPixmap.height() ); hoverOverRect = r; p->drawPixmap( r, resizedPixmap); } } } //============================================== void ALabel::enterEvent( QEvent*) { if(hoverOverImage) { drawHoverOverImage = true; repaint( false ); } } //============================================== void ALabel::leaveEvent( QEvent*) { if(hoverOverImage) { drawHoverOverImage = false; repaint(false); } } //============================================== void ALabel::mousePressEvent( QMouseEvent* ) { emit mousePress(); } //============================================== void ALabel::mouseReleaseEvent( QMouseEvent* e) { //if there is no hover over image then ignore event if( hoverOverImage == NULL ) return; QPainter* p = new QPainter(); QRect r = style().itemRect( p, contentsRect(), alignment(), isEnabled(), pixmap(), text()); delete p; int minDim = r.width(); if(r.height() < minDim) minDim = r.height(); if(minDim > hoverOverImage->width()) { r.setLeft( r.right() - hoverOverImage->width() ); r.setBottom( r.top() + hoverOverImage->height() ); } else { QImage resizedImage = hoverOverImage->convertToImage().scale(minDim, minDim); QPixmap resizedPixmap(resizedImage); r.setLeft( r.right() - resizedPixmap.width() ); r.setBottom( r.top() + resizedPixmap.height() ); } if(r.contains( e->pos() ) ) { removePixmap(); emit mouseRelease(); } } //============================================== void ALabel::mouseDoubleClickEvent( QMouseEvent* ) { emit mouseDoubleClick(); } //============================================== void ALabel::mouseMoveEvent( QMouseEvent* e) { //need rect so draw hover over image must exist before any checks can occur if( !drawHoverOverImage ) return; //if hand not shown but over hover over image then turn hand cursor on if( !handCursorShown && hoverOverRect.contains( e->x(), e->y() ) ) { setCursor( QCursor( Qt::PointingHandCursor ) ); handCursorShown = true; return; } //if hand cursor shown but nolonger over hover over image set cursor back to normal if( handCursorShown && !hoverOverRect.contains( e->x(), e->y() ) ) { setCursor( QCursor( Qt::ArrowCursor ) ); handCursorShown = false; return; } } //============================================== void ALabel::appendJob(QPixmap* pix) { Action* newAct = new Action(pix); if(delayedActionHead == NULL) delayedActionHead = newAct; else delayedActionTail->setNext( newAct ); delayedActionTail = newAct; } //============================================== void ALabel::cleanStack() { //if stack empty already clean if(delayedActionHead == NULL) return; //if no image currently displayed, pop off all remove actions from head of list if(pixStore == NULL) { Action* currAction = delayedActionHead; while(currAction != NULL && currAction->getImage() == NULL) { Action* next = currAction->getNext(); delete currAction; currAction = next; } delayedActionHead = currAction; if(currAction == NULL) delayedActionTail = NULL; } //if one pending operations no simplifications possible if(delayedActionHead == delayedActionTail) return; //if last action is a set operation then remove all other entries if(delayedActionTail->getImage() != NULL) { Action* temp = delayedActionHead; delayedActionHead = delayedActionTail; while(temp != delayedActionHead) { Action* next = temp->getNext(); delete temp; temp = next; } return; } //last action is a remove operation, if no current image then cull entire stack if(pixStore == NULL) { Action* temp = delayedActionHead; while(temp != NULL) { Action* next = temp->getNext(); delete temp; temp = next; } delayedActionHead = NULL; delayedActionTail = NULL; return; } //else remove all but last pending operations since final remove action will remove current image else { Action* temp = delayedActionHead; while(temp != delayedActionTail) { Action* next = temp->getNext(); delete temp; temp = next; } delayedActionHead = delayedActionTail; return; } } //============================================== Action::Action(QPixmap* image) { this->image = image; next = NULL; } //============================================== Action::~Action() { delete image; image = NULL; } //============================================== Action* Action::getNext() { return next; } //============================================== void Action::setNext( Action* next) { this->next = next; } //============================================== QPixmap* Action::getImage() { return image; } //==============================================