Logo Search packages:      
Sourcecode: albumshaper version File versions  Download package

photoDescEdit.cpp

//==============================================
//  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 <qlayout.h>
#include <qlabel.h>
#include <qimage.h>
#include <qpixmap.h>
#include <qtimer.h>
#include <qapplication.h>
#include <qpainter.h>
#include <qtextedit.h>
#include <qcursor.h>

//Projectwide includes
#include "photoDescEdit.h"
#include "photoPreviewWidget.h"
#include "photosIconView.h"
#include "clickableLabel.h"
#include "../config.h"
#include "../backend/photo.h"
#include "../backend/tools/imageTools.h"

#define EDIT_MARGIN 4

//PLATFORM_SPECIFIC_CODE
#if defined(Q_OS_MACX)
#include "/Developer/Headers/FlatCarbon/MacWindows.h"
#endif

//PLATFORM_SPECIFIC_CODE
#if defined(Q_OS_WIN)
#include <windows.h>
#include <winuser.h>
#define SPI_GETDROPSHADOW 0x1024
#define SPI_SETDROPSHADOW 0x1025
#endif

//==============================================
PhotoDescEdit::PhotoDescEdit( PhotoPreviewWidget* ppw, bool useAnimation,
                              QWidget* parent, const char* name ) :
                              QWidget(parent,name, 
                              Qt::WStyle_Customize | 
#if defined(Q_OS_MACX)
                              Qt::WStyle_Splash )
#else 
                              Qt::WStyle_NoBorder | Qt::WType_Popup )
#endif
{
  //PLATFORM_SPECIFIC_CODE
  //disable drop shadow on mac os x
  #if defined(Q_OS_MACX)
  ChangeWindowAttributes( (OpaqueWindowPtr*)winId(), kWindowNoShadowAttribute, kWindowNoAttributes );
  #endif

  //PLATFORM_SPECIFIC_CODE
  //disable drop shadow on win xp
  #if defined(Q_OS_WIN)
  SystemParametersInfo( SPI_GETDROPSHADOW, 0, &dropShadowsEnabled, 0 );
  SystemParametersInfo( SPI_SETDROPSHADOW, 0, NULL, 0 );
  #endif

  this->ppw = ppw;
  //-----------------------------------------------
  //don't erase before painting, avoids flicker
  setWFlags(WNoAutoErase);
  //-----------------------------------------------
  //determine small image size
  getImageSize( ppw->getPhoto()->getThumbnailFilename(), smallWidth, smallHeight );
  //-------------------------------------------
  QRect appRec = qApp->mainWidget()->frameGeometry();
  int finalWidth, finalHeight;
  int actualFinalWidth, actualFinalHeight;
  
  //image is wider than tall, place text and buttons below image
  if(smallWidth > smallHeight )
  {
    finalWidth = 400;
    finalHeight = (finalWidth * smallHeight) / smallWidth;

    //fix width
    if(finalWidth +2*EDIT_MARGIN> appRec.width())
    {
      finalWidth = appRec.width() - 2*EDIT_MARGIN;
      finalHeight = (finalWidth * smallHeight) / smallWidth;
    }

    //fix height
    QFontMetrics fm( qApp->font() );
    idealTextSize = 4*fm.height() + 5*fm.leading() + 4;
    
    if(finalHeight + idealTextSize + 2*EDIT_MARGIN > appRec.height() )
    {
      finalHeight = appRec.height() - idealTextSize - 2*EDIT_MARGIN;
      finalWidth = (finalHeight * smallWidth) / smallHeight;
    }

    //sanity check
    if(finalHeight < 0)
    {
      finalHeight = (appRec.height() - 2*EDIT_MARGIN) / 2;
      finalWidth = (finalHeight * smallWidth) / smallHeight;
      idealTextSize = finalHeight;
    }

    actualFinalWidth = finalWidth + 2*EDIT_MARGIN;
    actualFinalHeight = finalHeight + idealTextSize + 2*EDIT_MARGIN;
    
    //an additional fudge is necessary for MacOSX, not sure why
#if defined(Q_OS_MACX)
    actualFinalHeight+=2;
#endif
  }
  //image is taller than wide, text and buttons will be placed to the right
  else
  {
    finalHeight = 300;
    finalWidth = (finalHeight * smallWidth) / smallHeight;

    //fix height
    if(finalHeight + 2*EDIT_MARGIN > appRec.height())
    {
      finalHeight = appRec.height() - 2*EDIT_MARGIN;
      finalWidth = (finalHeight * smallWidth) / smallHeight;
    }

    //fix width
    QString calibrationString( qApp->translate("PhotoDescEdit", "This is the photo description calibration string.") );
    QFontMetrics fm( qApp->font() );
    idealTextSize = fm.width( calibrationString );
    if(finalWidth + idealTextSize + 2*EDIT_MARGIN > appRec.width() )
    {
      finalWidth = appRec.width() - idealTextSize - 2*EDIT_MARGIN;
      finalHeight = (finalWidth * smallHeight) / smallWidth;
    }

    //sanity check
    if(finalWidth < 0)
    {
      finalWidth = (appRec.width() - 2*EDIT_MARGIN) / 2;
      finalHeight = (finalWidth * smallHeight) / smallWidth;
    idealTextSize = finalWidth;
    }

    actualFinalWidth = finalWidth + idealTextSize + 2*EDIT_MARGIN;
    actualFinalHeight = finalHeight + 2*EDIT_MARGIN;
  }
  //-----------------------------------------------
  //setup scaled up image
  //find full size photo dimensions, if unable to then use scaled up thumbnail image
  int fullWidth, fullHeight;
  if(!getImageSize( ppw->getPhoto()->getImageFilename(), fullWidth, fullHeight ) )
  {
    imageLarge = new QImage( QImage( ppw->getPhoto()->getThumbnailFilename()).
                             scale(finalWidth,finalHeight, QImage::ScaleFree ));
  }
  //else find cropped region of slideshow image using these dimensions
  else
  {
    //load padded slideshow image
    QImage paddedSSImage( ppw->getPhoto()->getSlideshowFilename() );

    //unpadded dimensions
    int actualWidth, actualHeight;
    calcScaledImageDimensions( fullWidth, fullHeight, 
                               paddedSSImage.width(), paddedSSImage.height(), 
                               actualWidth, actualHeight );

    //construct new image with padding removed
    int leftOffset = (paddedSSImage.width() - actualWidth) / 2;
    int topOffset = (paddedSSImage.height() - actualHeight) / 2;
    QImage SSImage( actualWidth, actualHeight, paddedSSImage.depth() );
      
    int x,  y;
    for(x=0; x<actualWidth; x++)
    {
      for(y=0; y<actualHeight; y++)
      {
        SSImage.setPixel( x, y, red.rgb() );
        SSImage.setPixel( x, y, paddedSSImage.pixel(x+leftOffset, y+topOffset) );
      }
    }
    imageLarge = new QImage(SSImage.smoothScale(finalWidth,finalHeight, QImage::ScaleFree ));
  }
  //-----------------------------------------------
  //construct final text area pixmap used for morphing text region
  TextEdit tmpTextEdit;
  tmpTextEdit.setText( ppw->getPhoto()->getDescription() );

  if(smallWidth > smallHeight )
    tmpTextEdit.resize( finalWidth, idealTextSize );
  else
    tmpTextEdit.resize( idealTextSize, finalHeight );

  tmpTextEdit.setLineWidth( 0 );
  tmpTextEdit.setMargin( 0 );
  tmpTextEdit.setMidLineWidth( 0 );
  tmpTextEdit.setFrameStyle( QFrame::NoFrame | QFrame::Plain );
  
  tmpTextEdit.setWrapPolicy( QTextEdit::AtWordOrDocumentBoundary );
  tmpTextEdit.constPolish();
  tmpTextEdit.polish();

  tmpTextEdit.setWordWrap( QTextEdit::FixedPixelWidth );
  if(smallWidth > smallHeight )
    tmpTextEdit.setWrapColumnOrWidth( finalWidth );
  else
    tmpTextEdit.setWrapColumnOrWidth( idealTextSize );
  tmpTextEdit.updateScrollBars();
  tmpTextEdit.constPolish();
  tmpTextEdit.polish();

  if(smallWidth > smallHeight )
  {
    if(tmpTextEdit.lines() > 4)
    {
      tmpTextEdit.setWrapColumnOrWidth( finalWidth - tmpTextEdit.verticalScrollBar()->width() );
      tmpTextEdit.updateScrollBars();
      tmpTextEdit.constPolish();
      tmpTextEdit.polish();
    }
  }
  else
  {
    QFontMetrics fm( qApp->font() );
    if(tmpTextEdit.lines() > idealTextSize / (fm.leading() + fm.height()) )
    {
      tmpTextEdit.setWrapColumnOrWidth( idealTextSize - tmpTextEdit.verticalScrollBar()->width() );
      tmpTextEdit.updateScrollBars();
      tmpTextEdit.constPolish();
      tmpTextEdit.polish();
    }
  }

  //paint to pixmap
  tmpTextEdit.paintNow();
  textRectangle = new QImage( QPixmap::grabWidget(&tmpTextEdit).convertToImage() );
  //-----------------------------------------------
  //set beginning and end positions
  initPos = ppw->getPhotoPos();

  //offset by margin
  initPos += QPoint( -EDIT_MARGIN, -EDIT_MARGIN );

  int initCenterX = initPos.x() + smallWidth/2;
  int initCenterY = initPos.y() + smallHeight/2;

  finalPos = QPoint( initCenterX - actualFinalWidth/2, initCenterY - actualFinalHeight/2 );
  if(finalPos.x() < appRec.x() )
    finalPos.setX( appRec.x() );
  if(finalPos.x() + actualFinalWidth > appRec.x() + appRec.width() )
    finalPos.setX( appRec.x() + appRec.width()- actualFinalWidth );

  if(finalPos.y() < appRec.y() )
    finalPos.setY( appRec.y() );
  if(finalPos.y() + actualFinalHeight > appRec.y() + appRec.height() )
    finalPos.setY( appRec.y() + appRec.height()- actualFinalHeight );
  //-----------------------------------------------
  //find bounding rectangle
  left   = QMIN( finalPos.x(), initPos.x() );
  top    = QMIN( finalPos.y(), initPos.y() );
  right  = QMAX( finalPos.x() + actualFinalWidth, initPos.x() + smallWidth );
  bottom = QMAX( finalPos.y() + actualFinalHeight, initPos.y() + smallHeight );
  //-----------------------------------------------
  //grab window in region of interest, setup label and use this image
  backgroundImage = new QPixmap( QPixmap::grabWindow(QApplication::desktop()->winId(),
                                                     left, top, 
                                                     right-left, bottom-top) );
  setBackgroundMode( Qt::NoBackground );
  //-----------------------------------------------
  //Setup animation widgets and place in main grid
  animationLabel = new QLabel(this, "animationLabel", WNoAutoErase);
  animationLabel->setPixmap( *backgroundImage );
  animationLabel->setBackgroundMode( Qt::NoBackground );
  buffer = new QPixmap(  backgroundImage->width(), backgroundImage->height() );

  mainGrid = new QGridLayout( this, 1, 2, 0 );
  mainGrid->addWidget(animationLabel, 0, 0 );
  //-----------------------------------------------
  //Setup static widgets
  staticFrame = new QWidget(this);
  staticFrame->hide();
  staticFrame->setBackgroundMode( Qt::NoBackground );
  mainGrid->addWidget(staticFrame, 0, 1 );

  staticPhoto = new QLabel( staticFrame, "staticPhoto", WNoAutoErase);
  staticPhoto->setPixmap( QPixmap( *imageLarge) );
  staticPhoto->setBackgroundMode( Qt::NoBackground );

  photoDesc = new TextEdit( staticFrame );
  photoDesc->setText( ppw->getPhoto()->getDescription() );

  photoDesc->setWrapPolicy( QTextEdit::AtWordOrDocumentBoundary );
  photoDesc->setFrameStyle( QFrame::NoFrame );
  photoDesc->setLineWidth( 0 );
  photoDesc->setMargin( 0 );
  photoDesc->setMidLineWidth( 0 );
  photoDesc->setFrameStyle( QFrame::MenuBarPanel | QFrame::Plain );

  //start disappearing once the text edit reports the user is finished
  connect( photoDesc, SIGNAL( finished() ),
           this, SLOT( disappear() ) );
  
  QWidget* bw1 = new QWidget(staticFrame);
  QWidget* bw2 = new QWidget(staticFrame);
  QWidget* bw3 = new QWidget(staticFrame);
  QWidget* bw4 = new QWidget(staticFrame);
  QColor darkBlue(35, 75, 139);
  bw1->setPaletteBackgroundColor( darkBlue );
  bw2->setPaletteBackgroundColor( darkBlue );
  bw3->setPaletteBackgroundColor( darkBlue );
  bw4->setPaletteBackgroundColor( darkBlue );

  //image is wider than tall, place text and buttons below image
  if(smallWidth > smallHeight )
  {
    staticGrid = new QGridLayout( staticFrame, 4, 3);

    staticGrid->addWidget( staticPhoto, 1, 1 );
    staticGrid->addWidget( photoDesc, 2, 1 );

    staticGrid->setColSpacing( 2, staticPhoto->width() );
    staticGrid->setRowSpacing( 2, idealTextSize );

    staticGrid->addMultiCellWidget( bw1, 0, 0, 0, 2 );
    staticGrid->addMultiCellWidget( bw2, 1, 2, 0, 0 );
    staticGrid->addMultiCellWidget( bw3, 1, 2, 2, 2 );
    staticGrid->addMultiCellWidget( bw4, 3, 3, 0, 2 );
    staticGrid->setRowSpacing( 0, EDIT_MARGIN );
    staticGrid->setRowSpacing( 3, EDIT_MARGIN );
    staticGrid->setColSpacing( 0, EDIT_MARGIN );
    staticGrid->setColSpacing( 2, EDIT_MARGIN );
  }
  else
  {
    staticGrid = new QGridLayout( staticFrame, 3, 4);

    staticGrid->addWidget( staticPhoto, 1, 1 );
    staticGrid->addWidget( photoDesc, 1, 2 );

    staticGrid->setRowSpacing( 1, staticPhoto->height() );
    staticGrid->setColSpacing( 2, idealTextSize );

    staticGrid->addMultiCellWidget( bw1, 0, 0, 0, 3 );
    staticGrid->addWidget( bw2, 1, 0 );
    staticGrid->addWidget( bw3, 1, 3 );
    staticGrid->addMultiCellWidget( bw4, 2, 2, 0, 3 );
    staticGrid->setRowSpacing( 0, EDIT_MARGIN );
    staticGrid->setRowSpacing( 2, EDIT_MARGIN );
    staticGrid->setColSpacing( 0, EDIT_MARGIN );
    staticGrid->setColSpacing( 3, EDIT_MARGIN );
  }
  //-----------------------------------------------
  //set delay defaults
  initDelay = 130;
  accel = 50;
  minDelay = 1;

  this->useAnimation = useAnimation;
  if(useAnimation)
    step = 0;
  else
    step = 100;

  mode = STATIC;

  //create timer object and setup signals
  timer = new QTimer();
  connect(timer, SIGNAL(timeout()), this, SLOT(animate()) );
  //---------------------------
  //place widget in intial position
  move( left, top );
  show();

  //start appearing process
  mode = APPEARING;
  delay = initDelay;
  lastTime.start();
  animate();
}
//==============================================
PhotoDescEdit::~PhotoDescEdit()
{
  delete textRectangle;
  delete timer;
  delete buffer;
  delete backgroundImage;
  delete imageLarge;
}
//==============================================
void PhotoDescEdit::animate()
{
  //---------------------------------
  //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 increment is not zero then update last time
  if(inc != 0)
  {
    lastTime = currentTime;

    //update step
    step = step + inc;
    if(step > 100)
      step = 100;

    //update position and size
    double alpha = ((double)step) / 100.0;
    int newX, newY;
    int imageW, imageH;
    int textDim;
    QColor darkBlue(35, 75, 139);
    if(mode == APPEARING)
    {
      newX = (int)((1-alpha)*initPos.x() + alpha*finalPos.x());
      newY = (int)((1-alpha)*initPos.y() + alpha*finalPos.y());
      imageW = (int)((1-alpha)*smallWidth + alpha*imageLarge->width());
      imageH = (int)((1-alpha)*smallHeight + alpha*imageLarge->height());
      textDim = (int) (alpha * idealTextSize);
    }
    else
    {
      newX = (int)(alpha*initPos.x() + (1-alpha)*finalPos.x());
      newY = (int)(alpha*initPos.y() + (1-alpha)*finalPos.y());
      imageW = (int)(alpha*smallWidth + (1-alpha)*imageLarge->width());
      imageH = (int)(alpha*smallHeight + (1-alpha)*imageLarge->height());
      textDim = (int) ((1-alpha) * idealTextSize);
    }

    //draw background image to buffer
    QPainter bufferPainter( buffer );
    bufferPainter.drawPixmap(0,0, *backgroundImage );

    //draw selection and white text rectangles
    if(smallWidth > smallHeight )
    {
      bufferPainter.fillRect( newX - left, 
                              newY - top,
                              imageW + 2*EDIT_MARGIN,
                              imageH + 2*EDIT_MARGIN + textDim,
                              darkBlue );

      bufferPainter.drawPixmap( newX - left + EDIT_MARGIN,
                                newY - top + EDIT_MARGIN + imageH,
                                QPixmap( textRectangle->scale( imageW, textDim ) ) );
    }
    else
    {
      bufferPainter.fillRect( newX - left, newY - top,
                              imageW + 2*EDIT_MARGIN + textDim,
                              imageH + 2*EDIT_MARGIN,
                              darkBlue );

      bufferPainter.drawPixmap( newX - left + EDIT_MARGIN + imageW,
                                newY - top + EDIT_MARGIN,
                                QPixmap( textRectangle->scale( textDim, imageH ) ) );
    }

    //draw scaled moved image to buffer
    bufferPainter.drawPixmap( newX - left + EDIT_MARGIN,
                              newY - top + EDIT_MARGIN,
                              QPixmap( imageLarge->scale( imageW, imageH ) ) );

    //set label to use buffer pixmap
    animationLabel->setPixmap( *buffer );
  }

  //not done restart timer
  if(step < 100)
  {
    //update speed
    delay = delay - accel;
    if(delay < minDelay) delay = minDelay;

    //restart timer
    timer->start( delay, TRUE );
  }
  else
  {
    if(mode == APPEARING)
    {
      animationLabel->hide();
      staticFrame->show();

      //auto focus text area, put cursor at very end
      photoDesc->setFocus();

      mode = STATIC;
    }
    else
    {
      //reenable drop shadows on windows xp if they were previously enabled
      #if defined(Q_OS_WIN)
      if(dropShadowsEnabled)
        SystemParametersInfo( SPI_SETDROPSHADOW, 0, &dropShadowsEnabled, 0 );
      else
        SystemParametersInfo( SPI_SETDROPSHADOW, 0, NULL, 0 );
      #endif //Q_OS_WIN

      mode = DISAPPEARED;
      hide();
      qApp->mainWidget()->repaint(false);
    }
  }
}
//==============================================
void PhotoDescEdit::disappear()
{
  delete textRectangle;
  textRectangle = new QImage( QPixmap::grabWidget(photoDesc).convertToImage() );

  ppw->getPhoto()->setDescription( photoDesc->text() );
  ppw->setText( photoDesc->text() );

  //start disappearing process
  staticFrame->hide();
  animationLabel->show();

  initDelay = 130;
  accel = 50;
  minDelay = 1;

  if(useAnimation)
    step = 0;
  else
    step = 100;

  mode = DISAPPEARING;
  lastTime.start();
  animate();
}
//==============================================
void PhotoDescEdit::hide()
{
  if(mode == DISAPPEARED )
  {
    QWidget::hide();

    //check to see if mouse is over a new item, 
    //if so immediately set it as being moused over
    QIconView* iconView = ppw->iconView();
    QIconViewItem* item = iconView->findItem( iconView->viewport()->mapFromGlobal( QCursor::pos() )+=QPoint( iconView->contentsX(), iconView->contentsY() )  );
    if(item != NULL && item != ppw )
    {
      ((PhotosIconView*)item->iconView())->repaintGroup( item );
    }
  }
  else if(mode == STATIC)
  {
    disappear();
  }
}
//==============================================
TextEdit::TextEdit( QWidget* parent, const char* name ) : QTextEdit(parent,name) 
{ 
  setHScrollBarMode( QScrollView::AlwaysOff );
  setTextFormat( Qt::PlainText );
  contextMenu = NULL;
}
//==============================================
void TextEdit::paintNow()
{
  constPolish();
  repaint( rect(), false );
}
//==============================================
void TextEdit::keyPressEvent ( QKeyEvent * e )
{
  //finish when user hits escape
  if( e->key() == Qt::Key_Escape )
  {
    emit finished();
  }
  //if Ctrl+A then select all text, otherwise, apply base class key press rules
  else if( (e->state() & Qt::ControlButton) && e->key() == Qt::Key_A )
  {
    selectAll();
  }
  else
  {
    QTextEdit::keyPressEvent( e );
  }
}
//==============================================
void TextEdit::focusOutEvent ( QFocusEvent * )
{
  //if user right clicked on text field a context menu is popping up so ignore focusOut.
  //otherwise user has clicked off photo description so close
  if( contextMenu == NULL ) emit finished();
}
//==============================================
QPopupMenu* TextEdit::createPopupMenu ( const QPoint& pos )
{
  //when context menu's are created store their handle
  contextMenu = QTextEdit::createPopupMenu( pos );
  connect( ((QObject*)contextMenu), SIGNAL(aboutToHide()),
           this, SLOT(contextMenuHiding()) );
  return contextMenu;
}
//==============================================
void TextEdit::contextMenuHiding()
{
  //clear context menu handle since it's disappearing
  disconnect( ((QObject*)contextMenu), SIGNAL(aboutToHide()),
              this, SLOT(contextMenuHiding()) );
  contextMenu = NULL;
}
//==============================================

Generated by  Doxygen 1.6.0   Back to index