//============================================== // 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 "photo.h" #include "subalbum.h" #include "album.h" #include "tools/fileTools.h" #include "tools/imageTools.h" #include "tools/xmlTools.h" #include "tools/md5.h" #include "../config.h" //============================================== Photo::Photo(Subalbum* subalbum, Photo* prev, int photoNumber) { //set subalbum pointer this->subalbum = subalbum; //set prev pointer this->prev = prev; //set next pointer to NULL, new photos are always //inserted at the end of collections next = NULL; //set initial photo and subalbum numbers initialPhotoNumber = photoNumber; initialSubalbumNumber = subalbum->getSubalbumNumber(); //set default to the empty string description = QString::null; //set thumbnail image thumbnailImage = NULL; //set filenames and checksums to null until the actual photo data has been set imageLocation = QString::null; imageChecksum = QString::null; slideshowLocation = QString::null; slideshowChecksum = QString::null; thumbnailLocation = QString::null; thumbnailChecksum = QString::null; //a default photo is not interesting. once the //actual photo data or description files have //been reset the photo will need to be saved. needsSaving = false; //by default a photos are assumed to be saved. //new photos are setup with a uniqueID and we'll set this bool false there everSaved = true; //photo not recently reverted recentlyReverted = false; } //============================================== Photo::~Photo() { //free the thumbnail image delete thumbnailImage; } //============================================== QImage* Photo::getThumbnailImage() { return thumbnailImage; } //============================================== bool Photo::constructSmallerImages() { //construct and save slideshow and thumbnail images QImage TslideshowImage, TthumbnailImage; constructImages( imageLocation, TslideshowImage, TthumbnailImage ); TslideshowImage.save( slideshowLocation, "JPEG", 95 ); TthumbnailImage.save( thumbnailLocation, "JPEG", 95 ); //load up thumbnail image delete thumbnailImage; thumbnailImage = new QImage(thumbnailLocation); //image is being stored in temp location, needs saving! needsSaving = true; //set the subalbum as being modified and return subalbum->setModified(); return true; } //============================================== bool Photo::setImage(QString imageName, QString slideshowName, QString thumbnailName) { //set filenames, we'll lazily compute MD5 checksums for files when saving imageLocation = imageName; slideshowLocation = slideshowName; thumbnailLocation = thumbnailName; //load thumbnail image delete thumbnailImage; thumbnailImage = new QImage(thumbnailName); if(thumbnailImage->isNull()) return false; //image just loaded, no changes yet needsSaving = false; return true; } //============================================== bool Photo::setImage(QString imageName, int uniqueID) { //this is a new photo, use a unique ID to construct temporary filenames setEverSaved(false); initialSubalbumNumber = 0; initialPhotoNumber = uniqueID; QString tmpDir = subalbum->getAlbum()->getTmpDir(); imageLocation = QString("%1/%2_%3.jpg") .arg(tmpDir).arg(initialSubalbumNumber).arg(initialPhotoNumber); slideshowLocation = QString("%1/%2_%3_slideshow.jpg").arg(tmpDir).arg(initialSubalbumNumber).arg(initialPhotoNumber); thumbnailLocation = QString("%1/%2_%3_thumb.jpg") .arg(tmpDir).arg(initialSubalbumNumber).arg(initialPhotoNumber); //if image in jpeg format simply copy file over if( isJpeg(imageName) ) { copyFile( imageName, imageLocation ); } //otherwise we must load it up and save it out as jpeg else { //if unable to open image at all using Qt then giveup QImage tempImage(imageName); if( tempImage.isNull() ) { return false; } //save out as jpeg tempImage.save( imageLocation, "JPEG", 95 ); } //construct smaller iamges return constructSmallerImages(); } //============================================== bool Photo::setImage(QString editedImageFilename) { //if the image has been saved then simply change the image,slideshow/thubnail //filename handles to point to the temporary directory. We don't need to worry about backing up the //image because the new version will be written to the temporary directory and the save location if( getEverSaved() ) { imageLocation = subalbum->getAlbum()->getTmpDir() + QString("/%1_%2.jpg").arg(initialSubalbumNumber).arg(initialPhotoNumber); slideshowLocation = subalbum->getAlbum()->getTmpDir() + QString("/%1_%2_slideshow.jpg").arg(initialSubalbumNumber).arg(initialPhotoNumber); thumbnailLocation = subalbum->getAlbum()->getTmpDir() + QString("/%1_%2_thumb.jpg").arg(initialSubalbumNumber).arg(initialPhotoNumber); } //otherwise image has never been saved, make sure original form has been backed up! else { QString tempOrigLocation = imageLocation; tempOrigLocation.truncate( imageLocation.length() - 4 ); tempOrigLocation = tempOrigLocation + "_orig.jpg"; QDir tmpDir; if(!tmpDir.exists( tempOrigLocation ) ) { copyFile( imageLocation, tempOrigLocation ); } } //copy over full size image copyFile( editedImageFilename, imageLocation ); //reset revert flag since image has not been modified recentlyReverted = false; //construct smaller iamges return constructSmallerImages(); } //============================================== QString Photo::getImageFilename() { return imageLocation; } QString Photo::getSlideshowFilename() { return slideshowLocation; } QString Photo::getThumbnailFilename() { return thumbnailLocation; } void Photo::setImageFilename(QString val) { imageLocation = val; } void Photo::setSlideshowFilename(QString val) { slideshowLocation = val; } void Photo::setThumbnailFilename(QString val) { thumbnailLocation = val; } //============================================== QString Photo::getImageChecksum() { return imageChecksum; } QString Photo::getThumbnailChecksum() { return thumbnailChecksum; } QString Photo::getSlideshowChecksum() { return slideshowChecksum; } void Photo::setImageChecksum(QString val) { imageChecksum = val; } void Photo::setThumbnailChecksum(QString val) { thumbnailChecksum = val; } void Photo::setSlideshowChecksum(QString val) { slideshowChecksum = val; } //============================================== QString Photo::getDescription() { return QString(description); } //============================================== void Photo::setDescription(QString val) { //set empty strings as null, takes up less space and necessary //to check for string modification if( val.isEmpty() ) val = QString::null; if(description.compare(val) != 0) { description = val; subalbum->setModified(); } } //============================================== Photo* Photo::getPrev() { return prev; } Photo* Photo::getNext() { return next; } //============================================== void Photo::setPrev(Photo* val) { prev = val; subalbum->setModified(); } //============================================== void Photo::setNext(Photo* val) { next = val; subalbum->setModified(); } //============================================== QDateTime* Photo::importFromDisk(QDomNode* root) { //create modified date/time object for returning QDateTime* modified = new QDateTime[3]; QDomNode node = root->firstChild(); QDomText val; while( !node.isNull() ) { //------------------------------------------------------------ //photo description if( node.isElement() && node.nodeName() == "description" ) { val = node.firstChild().toText(); if(!val.isNull()) description = val.nodeValue(); description.replace("\\"","\""); } //------------------------------------------------------------ //image information else if( node.isElement() && node.nodeName() == "image" ) { QDomNode childNode = node.firstChild(); while( !childNode.isNull() ) { //------------------------------------------------------------ if( childNode.isElement() && childNode.nodeName() == "md5" ) { val = childNode.firstChild().toText(); if(!val.isNull()) imageChecksum = val.nodeValue(); } //------------------------------------------------------------ else if( childNode.isElement() && childNode.nodeName() == "modified" ) { val = childNode.firstChild().toText(); //split value based on spaces, should be 7 fields QStringList vals = QStringList::split( QRegExp(" "), val.nodeValue() ); int i=0; int intVals[7]; QStringList::Iterator it; for ( it = vals.begin(); it != vals.end(); ++it ) { //sanity check incase more fields are provided than there should be if(i >6) break; intVals[i] = QString(*it).toInt(); i++; } modified[0].setDate( QDate(intVals[0], intVals[1], intVals[2]) ); modified[0].setTime( QTime(intVals[3], intVals[4], intVals[5], intVals[6]) ); } //------------------------------------------------------------ childNode = childNode.nextSibling(); } } //------------------------------------------------------------ //slideshow information else if( node.isElement() && node.nodeName() == "slideshow" ) { QDomNode childNode = node.firstChild(); while( !childNode.isNull() ) { //------------------------------------------------------------ if( childNode.isElement() && childNode.nodeName() == "md5" ) { val = childNode.firstChild().toText(); if(!val.isNull()) slideshowChecksum = val.nodeValue(); } //------------------------------------------------------------ else if( childNode.isElement() && childNode.nodeName() == "modified" ) { val = childNode.firstChild().toText(); //split value based on spaces, should be 6 fields QStringList vals = QStringList::split( QRegExp(" "), val.nodeValue() ); int i=0; int intVals[7]; QStringList::Iterator it; for ( it = vals.begin(); it != vals.end(); ++it ) { //sanity check incase more fields are provided than there should be if(i >6) break; intVals[i] = QString(*it).toInt(); i++; } modified[1].setDate( QDate(intVals[0], intVals[1], intVals[2]) ); modified[1].setTime( QTime(intVals[3], intVals[4], intVals[5], intVals[6]) ); } //------------------------------------------------------------ childNode = childNode.nextSibling(); } } //------------------------------------------------------------ //slideshow information else if( node.isElement() && node.nodeName() == "thumb" ) { QDomNode childNode = node.firstChild(); while( !childNode.isNull() ) { //------------------------------------------------------------ if( childNode.isElement() && childNode.nodeName() == "md5" ) { val = childNode.firstChild().toText(); if(!val.isNull()) thumbnailChecksum = val.nodeValue(); } //------------------------------------------------------------ else if( childNode.isElement() && childNode.nodeName() == "modified" ) { val = childNode.firstChild().toText(); //split value based on spaces, should be 6 fields QStringList vals = QStringList::split( QRegExp(" "), val.nodeValue() ); int i=0; int intVals[7]; QStringList::Iterator it; for ( it = vals.begin(); it != vals.end(); ++it ) { //sanity check incase more fields are provided than there should be if(i >6) break; intVals[i] = QString(*it).toInt(); i++; } modified[2].setDate( QDate(intVals[0], intVals[1], intVals[2]) ); modified[2].setTime( QTime(intVals[3], intVals[4], intVals[5], intVals[6]) ); } //------------------------------------------------------------ childNode = childNode.nextSibling(); } } //------------------------------------------------------------ //------------------------------------------------------------ //Handle md5 info as specified in 1.0a and 1.0a2 xml format //image md5 else if( node.isElement() && node.nodeName() == "imageMD5" ) { val = node.firstChild().toText(); if(!val.isNull()) imageChecksum = val.nodeValue(); } //------------------------------------------------------------ //slideshow md5 else if( node.isElement() && node.nodeName() == "slideMD5" ) { val = node.firstChild().toText(); if(!val.isNull()) slideshowChecksum = val.nodeValue(); } //------------------------------------------------------------ //thumbnail md5 else if( node.isElement() && node.nodeName() == "thumbMD5" ) { val = node.firstChild().toText(); if(!val.isNull()) thumbnailChecksum = val.nodeValue(); } //------------------------------------------------------------ //------------------------------------------------------------ //advance to next node node = node.nextSibling(); //------------------------------------------------------------ } //return modification dates read in return modified; } //============================================== void Photo::exportToXML(QTextStream& stream) { QFileInfo info; //write photo information stream << " \n"; //----- stream << " " << fixXMLString(description) << "\n"; //----- //full image info.setFile( getImageFilename() ); QDateTime modified = info.lastModified(); stream << " \n"; stream << " " << fixXMLString(imageChecksum) << "\n"; stream << " "; stream << modified.date().year() << " "; stream << modified.date().month() << " "; stream << modified.date().day() << " "; stream << modified.time().hour() << " "; stream << modified.time().minute() << " "; stream << modified.time().second() << " "; stream << modified.time().msec() << "\n"; stream << " \n"; //----- //slidehow image info.setFile( getSlideshowFilename() ); modified = info.lastModified(); stream << " \n"; stream << " " << fixXMLString(slideshowChecksum) << "\n"; stream << " "; stream << modified.date().year() << " "; stream << modified.date().month() << " "; stream << modified.date().day() << " "; stream << modified.time().hour() << " "; stream << modified.time().minute() << " "; stream << modified.time().second() << " "; stream << modified.time().msec() << "\n"; stream << " \n"; //----- //thumbnail image info.setFile( getThumbnailFilename() ); modified = info.lastModified(); stream << " \n"; stream << " " << fixXMLString(thumbnailChecksum) << "\n"; stream << " "; stream << modified.date().year() << " "; stream << modified.date().month() << " "; stream << modified.date().day() << " "; stream << modified.time().hour() << " "; stream << modified.time().minute() << " "; stream << modified.time().second() << " "; stream << modified.time().msec() << "\n"; stream << " \n"; //----- stream << " \n"; } //============================================== void Photo::rotate90() { applyTransformation( ROTATE_90 ); } void Photo::rotate270() { applyTransformation( ROTATE_270 ); } void Photo::flipHorizontally() { applyTransformation( FLIP_H ); } void Photo::flipVertically() { applyTransformation( FLIP_V ); } //============================================== void Photo::applyTransformation(TRANSFORM_CODE transformation) { //backup old filename QString oldName = imageLocation; //if the image did not previously need saving, //reset filenames to point to temporary location and //immediately perform transformation if(!needsSaving) { imageLocation = subalbum->getAlbum()->getTmpDir() + QString("/%1_%2.jpg") .arg(initialSubalbumNumber) .arg(initialPhotoNumber); slideshowLocation = subalbum->getAlbum()->getTmpDir() + QString("/%1_%2_slideshow.jpg") .arg(initialSubalbumNumber) .arg(initialPhotoNumber); thumbnailLocation = subalbum->getAlbum()->getTmpDir() + QString("/%1_%2_thumb.jpg") .arg(initialSubalbumNumber) .arg(initialPhotoNumber); transformImage( oldName, imageLocation, transformation ); } else { //images that need saving already exist in the temporary directory //this poses two problems: //1.) fast jpeg transformations cannot be done in place, so we'll employ an // intermediate image QString intermediateName = QString("%1_intermdiate.jpg").arg(oldName); transformImage( oldName, intermediateName, transformation ); //2.) If the photo has never been saved and an orig file in the temporary // directory does not exist then the current file is the original version. we // must make sure that this original photo is maintained using an orig file so // in the future users can revert the photo to it's original form. QString origName = subalbum->getAlbum()->getTmpDir() + QString("/0_%1_orig.jpg") .arg(initialPhotoNumber); QDir tmpDir; if( !getEverSaved() && !tmpDir.exists(origName) ) { moveFile( oldName, origName ); moveFile( intermediateName, imageLocation ); } else { moveFile( intermediateName, imageLocation ); } } //image now modified from original form so orig file needs to be kept recentlyReverted = false; //construct smaller iamges constructSmallerImages(); } //============================================== bool Photo::getNeedsSavingVal() { return needsSaving; } void Photo::setNeedsSavingVal(bool val) { needsSaving = val; } //============================================== bool Photo::getEverSaved() { return everSaved; } void Photo::setEverSaved(bool val) { everSaved = val; } //============================================== bool Photo::revertPossible() { //if photo not recently reverted and orig and current filenames differ QString newName = getImageFilename(); QString origName = originalImageFilename(); return ( !recentlyReverted && origName.compare( newName ) !=0 ); } //============================================== bool Photo::getRecentlyReverted() { return recentlyReverted; } //============================================== void Photo::setRecentlyReverted(bool val) { recentlyReverted = val; } //============================================== void Photo::revertPhoto() { //ignore if revert is not possible if(!revertPossible()) return; //set image to reverted form QString origName = originalImageFilename(); setImage( origName ); //recently reverted, orig file should be //removed during next save since it's redundant recentlyReverted = true; } //============================================== QString Photo::originalImageFilename() { //determining the an images original filename is tricky //if the photo has never been saved check for presence of an _orig file, //otherwise use the current filename since the photo has not yet been modified if( !getEverSaved() ) { QString tempOrigLocation = imageLocation; tempOrigLocation.truncate( imageLocation.length() - 4 ); tempOrigLocation = tempOrigLocation + "_orig.jpg"; QDir tmpDir; if(tmpDir.exists( tempOrigLocation ) ) return tempOrigLocation; else return imageLocation; } //if the photo was previously saved, it's original form could either be: //1.) the permanant storage location + _orig //2.) the permanant storage location else { QString storedOrigLocation = subalbum->getAlbum()->getSaveLocation() + QString("/img/%1/%2_orig.jpg").arg(initialSubalbumNumber).arg(initialPhotoNumber); QString lastSavedLocation = subalbum->getAlbum()->getSaveLocation() + QString("/img/%1/%2.jpg").arg(initialSubalbumNumber).arg(initialPhotoNumber); QDir tmpDir; if(tmpDir.exists( storedOrigLocation ) ) return storedOrigLocation; else return lastSavedLocation; } } //============================================== int Photo::getInitialPhotoNumber() { return initialPhotoNumber; } void Photo::setInitialPhotoNumber(int val) { initialPhotoNumber = val; } int Photo::getInitialSubalbumNumber() { return initialSubalbumNumber; } void Photo::setInitialSubalbumNumber(int val) { initialSubalbumNumber = val; } //==============================================