You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
532 lines
14 KiB
532 lines
14 KiB
/*
|
|
A KIPI plugin to generate HTML image galleries
|
|
Copyright 2006 Aurelien Gateau <aurelien dot gateau at free.fr>
|
|
|
|
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.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
*/
|
|
// Self
|
|
#include "generator.moc"
|
|
|
|
// TQt
|
|
#include <tqdir.h>
|
|
#include <tqfile.h>
|
|
#include <tqpainter.h>
|
|
#include <tqregexp.h>
|
|
|
|
// KDE
|
|
#include <tdeaboutdata.h>
|
|
#include <tdeapplication.h>
|
|
#include <kdebug.h>
|
|
#include <tdelocale.h>
|
|
#include <tdeio/netaccess.h>
|
|
#include <kstandarddirs.h>
|
|
#include <kurl.h>
|
|
|
|
// KIPI
|
|
#include <libkipi/batchprogressdialog.h>
|
|
#include <libkipi/imageinfo.h>
|
|
#include <libkipi/interface.h>
|
|
|
|
// libxslt
|
|
#include <libxslt/transform.h>
|
|
#include <libxslt/xsltutils.h>
|
|
#include <libxslt/xslt.h>
|
|
#include <libexslt/exslt.h>
|
|
|
|
// Local
|
|
#include "abstractthemeparameter.h"
|
|
#include "galleryinfo.h"
|
|
#include "theme.h"
|
|
#include "xmlutils.h"
|
|
|
|
namespace KIPIHTMLExport {
|
|
|
|
|
|
typedef TQMap<TQCString,TQCString> XsltParameterMap;
|
|
|
|
|
|
/**
|
|
* Produce a web-friendly file name
|
|
*/
|
|
TQString webifyFileName(TQString fileName) {
|
|
fileName=fileName.lower();
|
|
|
|
// Remove potentially troublesome chars
|
|
fileName=fileName.replace(TQRegExp("[^-0-9a-z]+"), "_");
|
|
|
|
return fileName;
|
|
}
|
|
|
|
|
|
/**
|
|
* This helper class is used to make sure we use unique filenames
|
|
*/
|
|
class UniqueNameHelper {
|
|
public:
|
|
TQString makeNameUnique(TQString name) {
|
|
TQString nameBase = name;
|
|
int count=2;
|
|
while (mList.findIndex(name)!=-1) {
|
|
name = nameBase + TQString::number(count);
|
|
++count;
|
|
};
|
|
|
|
mList.append(name);
|
|
return name;
|
|
}
|
|
|
|
private:
|
|
TQStringList mList;
|
|
};
|
|
|
|
|
|
/**
|
|
* Prepare an XSLT param, managing quote mess.
|
|
* abc => 'abc'
|
|
* a"bc => 'a"bc'
|
|
* a'bc => "a'bc"
|
|
* a"'bc => concat('a"', "'", 'bc')
|
|
*/
|
|
TQCString makeXsltParam(const TQString& txt) {
|
|
TQString param;
|
|
static const char apos='\'';
|
|
static const char quote='"';
|
|
|
|
if (txt.find(apos)==-1) {
|
|
// First or second case: no apos
|
|
param= apos + txt + apos;
|
|
|
|
} else if (txt.find(quote)==-1) {
|
|
// Third case: only apos, no quote
|
|
param= quote + txt + quote;
|
|
|
|
} else {
|
|
// Forth case: both apos and quote :-(
|
|
TQStringList lst=TQStringList::split(apos, txt, true /*allowEmptyEntries*/);
|
|
|
|
TQStringList::Iterator it=lst.begin(), end=lst.end();
|
|
param= "concat(";
|
|
param+= apos + *it + apos;
|
|
++it;
|
|
for (;it!=end; ++it) {
|
|
param+= ", \"'\", ";
|
|
param+= apos + *it + apos;
|
|
}
|
|
param+= ")";
|
|
}
|
|
//kdDebug() << "param: " << txt << " => " << param << endl;
|
|
return param.utf8();
|
|
}
|
|
|
|
|
|
/**
|
|
* Genearate a square thumbnail from @fullImage of @size x @size pixels
|
|
*/
|
|
TQImage generateSquareThumbnail(const TQImage& fullImage, int size) {
|
|
TQImage image = fullImage.smoothScale(size, size, TQImage::ScaleMax);
|
|
|
|
if (image.width() == size && image.height() == size) {
|
|
return image;
|
|
}
|
|
TQPixmap croppedPix(size, size);
|
|
TQPainter painter(&croppedPix);
|
|
|
|
int sx=0, sy=0;
|
|
if (image.width()>size) {
|
|
sx=(image.width() - size)/2;
|
|
} else {
|
|
sy=(image.height() - size)/2;
|
|
}
|
|
painter.drawImage(0, 0, image, sx, sy, size, size);
|
|
painter.end();
|
|
|
|
return croppedPix.convertToImage();
|
|
}
|
|
|
|
|
|
struct Generator::Private {
|
|
KIPI::Interface* mInterface;
|
|
GalleryInfo* mInfo;
|
|
KIPI::BatchProgressDialog* mProgressDialog;
|
|
Theme::Ptr mTheme;
|
|
|
|
// State info
|
|
bool mWarnings;
|
|
TQString mXMLFileName;
|
|
UniqueNameHelper mUniqueNameHelper;
|
|
|
|
bool init() {
|
|
mTheme=Theme::findByInternalName(mInfo->theme());
|
|
if (!mTheme) {
|
|
logError( i18n("Could not find theme in '%1'").arg(mInfo->theme()) );
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool copyTheme() {
|
|
mProgressDialog->addedAction(i18n("Copying theme"), KIPI::ProgressMessage);
|
|
|
|
KURL srcURL=KURL(mTheme->directory());
|
|
|
|
KURL destURL=mInfo->destKURL();
|
|
destURL.addPath(srcURL.filename());
|
|
|
|
if (TQFile::exists(destURL.path())) {
|
|
TDEIO::NetAccess::del(destURL, mProgressDialog);
|
|
}
|
|
bool ok=TDEIO::NetAccess::dircopy(srcURL, destURL, mProgressDialog);
|
|
if (!ok) {
|
|
logError(i18n("Could not copy theme"));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool writeDataToFile(const TQByteArray& data, const TQString& destPath) {
|
|
TQFile destFile(destPath);
|
|
if (!destFile.open(IO_WriteOnly)) {
|
|
logWarning(i18n("Could not open file '%1' for writing").arg(destPath));
|
|
return false;
|
|
}
|
|
if (destFile.writeBlock(data) != (TQ_LONG)data.size()) {
|
|
logWarning(i18n("Could not save image to file '%1'").arg(destPath));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Helper class for generateImageAndXMLForURL
|
|
*/
|
|
void appendImageElementToXML(XMLWriter& xmlWriter, const TQString& elementName, const TQString& fileName, const TQImage& image) {
|
|
XMLAttributeList attrList;
|
|
attrList.append("fileName", fileName);
|
|
attrList.append("width", image.width());
|
|
attrList.append("height", image.height());
|
|
XMLElement elem(xmlWriter, elementName, &attrList);
|
|
}
|
|
|
|
|
|
/**
|
|
* Generate images (full and thumbnail) for imageURL
|
|
* Fills xmlWriter with info about this image
|
|
*/
|
|
void generateImageAndXMLForURL(XMLWriter& xmlWriter, const TQString& destDir, const KURL& imageURL) {
|
|
KIPI::ImageInfo info=mInterface->info(imageURL);
|
|
|
|
// Load image
|
|
TQString path=imageURL.path();
|
|
TQFile imageFile(path);
|
|
if (!imageFile.open(IO_ReadOnly)) {
|
|
logWarning(i18n("Could not read image '%1'").arg(path));
|
|
return;
|
|
}
|
|
|
|
TQString imageFormat = TQImageIO::imageFormat(&imageFile);
|
|
if (imageFormat.isEmpty()) {
|
|
logWarning(i18n("Format of image '%1' is unknown").arg(path));
|
|
return;
|
|
}
|
|
imageFile.close();
|
|
imageFile.open(IO_ReadOnly);
|
|
|
|
TQByteArray imageData = imageFile.readAll();
|
|
TQImage originalImage;
|
|
if (!originalImage.loadFromData(imageData) ) {
|
|
logWarning(i18n("Error loading image '%1'").arg(path));
|
|
return;
|
|
}
|
|
|
|
// Process images
|
|
TQImage fullImage = originalImage;
|
|
if (!mInfo->useOriginalImageAsFullImage()) {
|
|
if (mInfo->fullResize()) {
|
|
int size = mInfo->fullSize();
|
|
fullImage = fullImage.smoothScale(size, size, TQImage::ScaleMin);
|
|
}
|
|
if (info.angle() != 0) {
|
|
TQWMatrix matrix;
|
|
matrix.rotate(info.angle());
|
|
fullImage = fullImage.xForm(matrix);
|
|
}
|
|
}
|
|
|
|
TQImage thumbnail = generateSquareThumbnail(fullImage, mInfo->thumbnailSize());
|
|
|
|
// Save images
|
|
TQString baseFileName = webifyFileName(info.title());
|
|
baseFileName = mUniqueNameHelper.makeNameUnique(baseFileName);
|
|
|
|
// Save full
|
|
TQString fullFileName;
|
|
if (mInfo->useOriginalImageAsFullImage()) {
|
|
fullFileName = baseFileName + "." + imageFormat.lower();
|
|
if (!writeDataToFile(imageData, destDir + "/" + fullFileName)) {
|
|
return;
|
|
}
|
|
|
|
} else {
|
|
fullFileName = baseFileName + "." + mInfo->fullFormatString().lower();
|
|
TQString destPath = destDir + "/" + fullFileName;
|
|
if (!fullImage.save(destPath, mInfo->fullFormatString().ascii(), mInfo->fullQuality())) {
|
|
logWarning(i18n("Could not save image '%1' to '%2'").arg(path).arg(destPath));
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Save original
|
|
TQString originalFileName;
|
|
if (mInfo->copyOriginalImage()) {
|
|
originalFileName = "original_" + fullFileName;
|
|
if (!writeDataToFile(imageData, destDir + "/" + originalFileName)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Save thumbnail
|
|
TQString thumbnailFileName = "thumb_" + baseFileName + "." + mInfo->thumbnailFormatString().lower();
|
|
TQString destPath = destDir + "/" + thumbnailFileName;
|
|
if (!thumbnail.save(destPath, mInfo->thumbnailFormatString().ascii(), mInfo->thumbnailQuality())) {
|
|
logWarning(i18n("Could not save thumbnail for image '%1' to '%2'").arg(path).arg(destPath));
|
|
return;
|
|
}
|
|
|
|
// Write XML
|
|
XMLElement imageX(xmlWriter, "image");
|
|
xmlWriter.writeElement("title", info.title());
|
|
xmlWriter.writeElement("description", info.description());
|
|
|
|
appendImageElementToXML(xmlWriter, "full", fullFileName, fullImage);
|
|
appendImageElementToXML(xmlWriter, "thumbnail", thumbnailFileName, thumbnail);
|
|
if (mInfo->copyOriginalImage()) {
|
|
appendImageElementToXML(xmlWriter, "original", originalFileName, originalImage);
|
|
}
|
|
}
|
|
|
|
|
|
bool generateImagesAndXML() {
|
|
TQString baseDestDir=mInfo->destKURL().path();
|
|
if (!createDir(baseDestDir)) return false;
|
|
|
|
mXMLFileName=baseDestDir + "/gallery.xml";
|
|
XMLWriter xmlWriter;
|
|
if (!xmlWriter.open(mXMLFileName)) {
|
|
logError(i18n("Could not create gallery.xml"));
|
|
return false;
|
|
}
|
|
|
|
XMLElement collectionsX(xmlWriter, "collections");
|
|
|
|
// Loop on collections
|
|
TQValueList<KIPI::ImageCollection>::Iterator collectionIt=mInfo->mCollectionList.begin();
|
|
TQValueList<KIPI::ImageCollection>::Iterator collectionEnd=mInfo->mCollectionList.end();
|
|
for (; collectionIt!=collectionEnd; ++collectionIt) {
|
|
KIPI::ImageCollection collection=*collectionIt;
|
|
logInfo( i18n("Generating files for \"%1\"").arg(collection.name()) );
|
|
|
|
TQString collectionFileName = webifyFileName(collection.name());
|
|
TQString destDir = baseDestDir + "/" + collectionFileName;
|
|
if (!createDir(destDir)) return false;
|
|
|
|
XMLElement collectionX(xmlWriter, "collection");
|
|
xmlWriter.writeElement("name", collection.name());
|
|
xmlWriter.writeElement("fileName", collectionFileName);
|
|
|
|
// Loop on image in collection
|
|
KURL::List imageList = collection.images();
|
|
KURL::List::Iterator it = imageList.begin();
|
|
KURL::List::Iterator end = imageList.end();
|
|
|
|
int pos = 1;
|
|
int count = imageList.count();
|
|
for (; it!=end; ++it, ++pos) {
|
|
mProgressDialog->setProgress(pos, count);
|
|
tqApp->processEvents();
|
|
generateImageAndXMLForURL(xmlWriter, destDir, *it);
|
|
}
|
|
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
/**
|
|
* Add to map all the i18n parameters.
|
|
*/
|
|
void addI18nParameters(XsltParameterMap& map) {
|
|
map["i18nPrevious"] = makeXsltParam(i18n("Previous"));
|
|
map["i18nNext"] = makeXsltParam(i18n("Next"));
|
|
map["i18nCollectionList"] = makeXsltParam(i18n("Collection List"));
|
|
map["i18nOriginalImage"] = makeXsltParam(i18n("Original Image"));
|
|
map["i18nUp"] = makeXsltParam(i18n("Go Up"));
|
|
}
|
|
|
|
|
|
/**
|
|
* Add to map all the theme parameters, as specified by the user.
|
|
*/
|
|
void addThemeParameters(XsltParameterMap& map) {
|
|
Theme::ParameterList parameterList = mTheme->parameterList();
|
|
TQString themeInternalName = mTheme->internalName();
|
|
Theme::ParameterList::ConstIterator
|
|
it = parameterList.begin(),
|
|
end = parameterList.end();
|
|
for (; it!=end; ++it) {
|
|
AbstractThemeParameter* themeParameter = *it;
|
|
TQCString internalName = themeParameter->internalName();
|
|
TQString value = mInfo->getThemeParameterValue(
|
|
themeInternalName,
|
|
internalName,
|
|
themeParameter->defaultValue());
|
|
|
|
map[internalName] = makeXsltParam(value);
|
|
}
|
|
}
|
|
|
|
|
|
bool generateHTML() {
|
|
logInfo(i18n("Generating HTML files"));
|
|
|
|
TQString xsltFileName=mTheme->directory() + "/template.xsl";
|
|
CWrapper<xsltStylesheetPtr, xsltFreeStylesheet> xslt= xsltParseStylesheetFile( (const xmlChar*) xsltFileName.local8Bit().data() );
|
|
|
|
if (!xslt) {
|
|
logError(i18n("Could not load XSL file '%1'").arg(xsltFileName));
|
|
return false;
|
|
}
|
|
|
|
CWrapper<xmlDocPtr, xmlFreeDoc> xmlGallery=xmlParseFile( mXMLFileName.local8Bit().data() );
|
|
if (!xmlGallery) {
|
|
logError(i18n("Could not load XML file '%1'").arg(mXMLFileName));
|
|
return false;
|
|
}
|
|
|
|
// Prepare parameters
|
|
XsltParameterMap map;
|
|
addI18nParameters(map);
|
|
addThemeParameters(map);
|
|
|
|
const char** params=new const char*[map.size()*2+1];
|
|
|
|
XsltParameterMap::Iterator it=map.begin(), end=map.end();
|
|
const char** ptr=params;
|
|
for (;it!=end; ++it) {
|
|
*ptr=it.key().data();
|
|
++ptr;
|
|
*ptr=it.data().data();
|
|
++ptr;
|
|
}
|
|
*ptr=0;
|
|
|
|
// Move to the destination dir, so that external documents get correctly
|
|
// produced
|
|
TQString oldCD=TQDir::currentDirPath();
|
|
TQDir::setCurrent(mInfo->destKURL().path());
|
|
|
|
CWrapper<xmlDocPtr, xmlFreeDoc> xmlOutput= xsltApplyStylesheet(xslt, xmlGallery, params);
|
|
|
|
TQDir::setCurrent(oldCD);
|
|
//delete []params;
|
|
|
|
if (!xmlOutput) {
|
|
logError(i18n("Error processing XML file"));
|
|
return false;
|
|
}
|
|
|
|
TQString destFileName=mInfo->destKURL().path() + "/index.html";
|
|
FILE* file=fopen(destFileName.local8Bit().data(), "w");
|
|
if (!file) {
|
|
logError(i18n("Could not open '%1' for writing").arg(destFileName));
|
|
return false;
|
|
}
|
|
xsltSaveResultToFile(file, xmlOutput, xslt);
|
|
fclose(file);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool createDir(const TQString& dirName) {
|
|
TQStringList parts = TQStringList::split('/', dirName);
|
|
TQStringList::ConstIterator it = parts.begin(), end = parts.end();
|
|
TQDir dir = TQDir::root();
|
|
for( ;it!=end; ++it) {
|
|
TQString part = *it;
|
|
if (!dir.exists(part)) {
|
|
if (!dir.mkdir(part)) {
|
|
logError(i18n("Could not create folder '%1' in '%2'").arg(part).arg(dir.absPath()));
|
|
return false;
|
|
}
|
|
}
|
|
dir.cd(part);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
void logInfo(const TQString& msg) {
|
|
mProgressDialog->addedAction(msg, KIPI::ProgressMessage);
|
|
}
|
|
|
|
void logError(const TQString& msg) {
|
|
mProgressDialog->addedAction(msg, KIPI::ErrorMessage);
|
|
}
|
|
|
|
void logWarning(const TQString& msg) {
|
|
mProgressDialog->addedAction(msg, KIPI::WarningMessage);
|
|
mWarnings=true;
|
|
}
|
|
};
|
|
|
|
Generator::Generator(KIPI::Interface* interface, GalleryInfo* info, KIPI::BatchProgressDialog* progressDialog)
|
|
: TQObject() {
|
|
d=new Private;
|
|
d->mInterface=interface;
|
|
d->mInfo=info;
|
|
d->mProgressDialog=progressDialog;
|
|
d->mWarnings=false;
|
|
}
|
|
|
|
|
|
Generator::~Generator() {
|
|
delete d;
|
|
}
|
|
|
|
|
|
bool Generator::run() {
|
|
if (!d->init()) return false;
|
|
|
|
TQString destDir=d->mInfo->destKURL().path();
|
|
if (!d->createDir(destDir)) return false;
|
|
|
|
if (!d->copyTheme()) return false;
|
|
|
|
if (!d->generateImagesAndXML()) return false;
|
|
|
|
exsltRegisterAll();
|
|
bool result=d->generateHTML();
|
|
xsltCleanupGlobals();
|
|
xmlCleanupParser();
|
|
return result;
|
|
}
|
|
|
|
bool Generator::warnings() const {
|
|
return d->mWarnings;
|
|
}
|
|
|
|
} // namespace
|