// vim: set tabstop=4 shiftwidth=4 noexpandtab: /* Gwenview - A simple image viewer for KDE Copyright 2000-2006 Aurelien Gateau 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. */ #include // For S_ISDIR // TQt #include #include #include #include #include // KDE #include #include #include #include #include #include #include #include #include #include // Local #include "archive.h" #include "busylevelmanager.h" #include "cache.h" #include "documentloadingimpl.h" #include "documentimpl.h" #include "imagesavedialog.h" #include "imageutils/imageutils.h" #include "jpegformattype.h" #include "pngformattype.h" #include "mngformattype.h" #include "printdialog.h" #include "qxcfi.h" #include "xpm.h" #include "xcursor.h" #include "document.moc" namespace Gwenview { #undef ENABLE_LOG #undef LOG //#define ENABLE_LOG #ifdef ENABLE_LOG #define LOG(x) kdDebug() << k_funcinfo << x << endl #else #define LOG(x) ; #endif const char* CONFIG_SAVE_AUTOMATICALLY="save automatically"; /** * Returns a widget suitable to use as a dialog parent */ static TQWidget* dialogParentWidget() { return KApplication::kApplication()->mainWidget(); } //------------------------------------------------------------------- // // DocumentPrivate // //------------------------------------------------------------------- class DocumentPrivate { public: KURL mURL; bool mModified; TQImage mImage; TQString mMimeType; TQCString mImageFormat; DocumentImpl* mImpl; TQGuardedPtr mStatJob; int mFileSize; }; //------------------------------------------------------------------- // // Document // //------------------------------------------------------------------- Document::Document(TQObject* parent) : TQObject(parent) { d=new DocumentPrivate; d->mModified=false; d->mImpl=new DocumentEmptyImpl(this); d->mStatJob=0L; d->mFileSize=-1; // Register formats here to make sure they are always enabled KImageIO::registerFormats(); XCFImageFormat::registerFormat(); // First load TQt's plugins, so that Gwenview's decoders that // override some of them are installed later and thus come first. TQImageIO::inputFormats(); { static Gwenview::JPEGFormatType sJPEGFormatType; static Gwenview::PNGFormatType sPNGFormatType; static Gwenview::XPM sXPM; static Gwenview::MNG sMNG; static Gwenview::XCursorFormatType sXCursorFormatType; } connect( this, TQT_SIGNAL( loading()), this, TQT_SLOT( slotLoading())); connect( this, TQT_SIGNAL( loaded(const KURL&)), this, TQT_SLOT( slotLoaded())); } Document::~Document() { delete d->mImpl; delete d; } //--------------------------------------------------------------------- // // Properties // //--------------------------------------------------------------------- TQString Document::mimeType() const { return d->mMimeType; } void Document::setMimeType(const TQString& mimeType) { d->mMimeType = mimeType; } MimeTypeUtils::Kind Document::urlKind() const { return d->mImpl->urlKind(); } KURL Document::url() const { return d->mURL; } void Document::setURL(const KURL& paramURL) { if (paramURL==url()) return; // Make a copy, we might have to fix the protocol KURL localURL(paramURL); LOG("url: " << paramURL.prettyURL()); // Be sure we are not waiting for another stat result if (!d->mStatJob.isNull()) { d->mStatJob->kill(); } BusyLevelManager::instance()->setBusyLevel(this, BUSY_NONE); // Ask to save if necessary. saveBeforeClosing(); if (localURL.isEmpty()) { reset(); return; } // Set high busy level, so that operations like smoothing are suspended. // Otherwise the stat() below done using KIO can take quite long. BusyLevelManager::instance()->setBusyLevel( this, BUSY_CHECKING_NEW_IMAGE ); // Fix wrong protocol if (Archive::protocolIsArchive(localURL.protocol())) { TQFileInfo info(localURL.path()); if (info.exists()) { localURL.setProtocol("file"); } } d->mURL = localURL; // this may be fixed after stat() is complete, but set at least something d->mStatJob = KIO::stat( localURL, !localURL.isLocalFile() ); d->mStatJob->setWindow(KApplication::kApplication()->mainWidget()); connect( d->mStatJob, TQT_SIGNAL( result (KIO::Job *) ), this, TQT_SLOT( slotStatResult (KIO::Job *) ) ); } void Document::slotStatResult(KIO::Job* job) { LOG(""); Q_ASSERT(d->mStatJob==job); if (d->mStatJob!=job) { kdWarning() << k_funcinfo << "We did not get the right job!\n"; return; } BusyLevelManager::instance()->setBusyLevel( this, BUSY_NONE ); if (d->mStatJob->error()) return; bool isDir=false; KIO::UDSEntry entry = d->mStatJob->statResult(); d->mURL=d->mStatJob->url(); KIO::UDSEntry::ConstIterator it; for(it=entry.begin();it!=entry.end();++it) { if ((*it).m_uds==KIO::UDS_FILE_TYPE) { isDir=S_ISDIR( (*it).m_long ); break; } } if (isDir) { d->mURL.adjustPath( +1 ); // add trailing / reset(); return; } load(); } void Document::setDirURL(const KURL& paramURL) { saveBeforeClosing(); d->mURL=paramURL; d->mURL.adjustPath( +1 ); // add trailing / reset(); } const TQImage& Document::image() const { return d->mImage; } void Document::setImage(TQImage img) { bool sizechange = d->mImage.size() != img.size(); d->mImage = img; if( sizechange ) emit sizeUpdated(); } KURL Document::dirURL() const { if (filename().isEmpty()) { return d->mURL; } else { KURL url=d->mURL.upURL(); url.adjustPath(1); return url; } } TQString Document::filename() const { return d->mURL.filename(false); } const TQCString& Document::imageFormat() const { return d->mImageFormat; } void Document::setImageFormat(const TQCString& format) { d->mImageFormat=format; } void Document::setFileSize(int size) { d->mFileSize=size; } TQString Document::comment() const { return d->mImpl->comment(); } TQString Document::aperture() const { return d->mImpl->aperture(); } TQString Document::exposureTime() const { return d->mImpl->exposureTime(); } TQString Document::iso() const { return d->mImpl->iso(); } TQString Document::focalLength() const { return d->mImpl->focalLength(); } void Document::setComment(const TQString& comment) { d->mImpl->setComment(comment); d->mModified=true; emit modified(); } Document::CommentState Document::commentState() const { return d->mImpl->commentState(); } /** * Returns the duration of the document in seconds, or 0 if there is no * duration */ int Document::duration() const { return d->mImpl->duration(); } int Document::fileSize() const { return d->mFileSize; } bool Document::canBeSaved() const { return d->mImpl->canBeSaved(); } bool Document::isModified() const { return d->mModified; } void Document::slotLoading() { BusyLevelManager::instance()->setBusyLevel( this, BUSY_LOADING ); } void Document::slotLoaded() { BusyLevelManager::instance()->setBusyLevel( this, BUSY_NONE ); } //--------------------------------------------------------------------- // // Operations // //--------------------------------------------------------------------- void Document::reload() { Cache::instance()->tqinvalidate( url()); load(); emit reloaded(url()); } void Document::print(KPrinter *pPrinter) { TQPainter printPainter; printPainter.begin(pPrinter); doPaint(pPrinter, &printPainter); printPainter.end(); } void Document::doPaint(KPrinter *printer, TQPainter *painter) { // will contain the final image to print TQImage image = d->mImage; image.detach(); // We use a TQPaintDeviceMetrics to know the actual page size in pixel, // this gives the real painting area TQPaintDeviceMetrics pdMetrics(painter->device()); const int margin = pdMetrics.logicalDpiY() / 2; // half-inch margin painter->setFont( KGlobalSettings::generalFont() ); TQFontMetrics fMetrics = painter->fontMetrics(); int x = 0; int y = 0; int pdWidth = pdMetrics.width(); int pdHeight = pdMetrics.height(); TQString t = "true"; TQString f = "false"; int tqalignment = (printer->option("app-gwenview-position").isEmpty() ? TQt::AlignCenter : printer->option("app-gwenview-position").toInt()); // Compute filename offset int filenameOffset = 0; bool printFilename = printer->option( "app-gwenview-printFilename" ) != f; if ( printFilename ) { filenameOffset = fMetrics.lineSpacing() + 14; pdHeight -= filenameOffset; // filename goes into one line! } // Compute comment offset int commentOffset = 0; bool printComment = printer->option( "app-gwenview-printComment" ) != f; if ( commentOffset ) { commentOffset = fMetrics.lineSpacing() + 14;// #### TODO check if it's correct pdHeight -= commentOffset; // #### TODO check if it's correct } if (commentOffset || printFilename) { pdHeight -= margin; } // Apply scaling int scaling = printer->option( "app-gwenview-scale" ).toInt(); TQSize size = image.size(); if (scaling==GV_FITTOPAGE /* Fit to page */) { bool enlargeToFit = printer->option( "app-gwenview-enlargeToFit" ) != f; if ((image.width() > pdWidth || image.height() > pdHeight) || enlargeToFit) { size.tqscale( pdWidth, pdHeight, TQSize::ScaleMin ); } } else { if (scaling==GV_SCALE /* Scale To */) { int unit = (printer->option("app-gwenview-scaleUnit").isEmpty() ? GV_INCHES : printer->option("app-gwenview-scaleUnit").toInt()); double inches = 1; if (unit == GV_MILLIMETERS) { inches = 1/25.4; } else if (unit == GV_CENTIMETERS) { inches = 1/2.54; } double wImg = (printer->option("app-gwenview-scaleWidth").isEmpty() ? 1 : printer->option("app-gwenview-scaleWidth").toDouble()) * inches; double hImg = (printer->option("app-gwenview-scaleHeight").isEmpty() ? 1 : printer->option("app-gwenview-scaleHeight").toDouble()) * inches; size.setWidth( int(wImg * printer->resolution()) ); size.setHeight( int(hImg * printer->resolution()) ); } else { /* GV_NOSCALE: no scaling */ // try to get the density info so that we can print using original size // known if it is am image from scanner for instance const float INCHESPERMETER = (100. / 2.54); if (image.dotsPerMeterX()) { double wImg = double(size.width()) / double(image.dotsPerMeterX()) * INCHESPERMETER; size.setWidth( int(wImg *printer->resolution()) ); } if (image.dotsPerMeterY()) { double hImg = double(size.height()) / double(image.dotsPerMeterY()) * INCHESPERMETER; size.setHeight( int(hImg *printer->resolution()) ); } } if (size.width() > pdWidth || size.height() > pdHeight) { int resp = KMessageBox::warningYesNoCancel(dialogParentWidget(), i18n("The image will not fit on the page, what do you want to do?"), TQString(),KStdGuiItem::cont(), i18n("Shrink") ); if (resp==KMessageBox::Cancel) { printer->abort(); return; } else if (resp == KMessageBox::No) { // Shrink size.tqscale(pdWidth, pdHeight, TQSize::ScaleMin); } } } // Compute x and y if ( tqalignment & TQt::AlignHCenter ) x = (pdWidth - size.width())/2; else if ( tqalignment & TQt::AlignLeft ) x = 0; else if ( tqalignment & TQt::AlignRight ) x = pdWidth - size.width(); if ( tqalignment & TQt::AlignVCenter ) y = (pdHeight - size.height())/2; else if ( tqalignment & TQt::AlignTop ) y = 0; else if ( tqalignment & TQt::AlignBottom ) y = pdHeight - size.height(); // Draw, the image will be scaled to fit the given area if necessary painter->drawImage( TQRect( x, y, size.width(), size.height()), image ); if ( printFilename ) { TQString fname = KStringHandler::cPixelSqueeze( filename(), fMetrics, pdWidth ); if ( !fname.isEmpty() ) { int fw = fMetrics.width( fname ); int x = (pdWidth - fw)/2; int y = pdMetrics.height() - filenameOffset/2 -commentOffset/2 - margin; painter->drawText( x, y, fname ); } } if ( printComment ) { TQString comm = comment(); if ( !comm.isEmpty() ) { int fw = fMetrics.width( comm ); int x = (pdWidth - fw)/2; int y = pdMetrics.height() - commentOffset/2 - margin; painter->drawText( x, y, comm ); } } } void Document::transform(ImageUtils::Orientation orientation) { d->mImpl->transform(orientation); d->mModified=true; emit modified(); } void Document::save() { TQString msg=saveInternal(url(), d->mImageFormat); if (!msg.isNull()) { KMessageBox::error(dialogParentWidget(), msg); // If it can't be saved we leave it as modified, because user // could choose to save it to another path with saveAs } } void Document::saveAs() { KURL saveURL; ImageSaveDialog dialog(saveURL, d->mImageFormat, dialogParentWidget()); dialog.setSelection(url().fileName()); if (!dialog.exec()) return; TQString msg=saveInternal(saveURL, dialog.imageFormat() ); if (!msg.isNull()) { // If it can't be saved we leave it as modified, because user // could choose a wrong readonly path from dialog and retry to KMessageBox::error(dialogParentWidget(), msg); } } void Document::saveBeforeClosing() { if (!d->mModified) return; TQString msg=i18n("The image %1 has been modified, do you want to save the changes?") .tqarg(url().prettyURL()); int result=KMessageBox::questionYesNo(dialogParentWidget(), msg, TQString(), KStdGuiItem::save(), KStdGuiItem::discard(), CONFIG_SAVE_AUTOMATICALLY); if (result == KMessageBox::Yes) { saveInternal(url(), d->mImageFormat); // If it can't be saved it's useless to leave it as modified // since user is closing this image and changing to another one d->mModified=false; //FIXME it should be nice to tell the user it failed } else { d->mModified=false; } } //--------------------------------------------------------------------- // // Private stuff // //--------------------------------------------------------------------- void Document::switchToImpl(DocumentImpl* impl) { // There should always be an implementation defined Q_ASSERT(d->mImpl); Q_ASSERT(impl); delete d->mImpl; d->mImpl=impl; connect(d->mImpl, TQT_SIGNAL(finished(bool)), this, TQT_SLOT(slotFinished(bool)) ); connect(d->mImpl, TQT_SIGNAL(sizeUpdated()), this, TQT_SIGNAL(sizeUpdated()) ); connect(d->mImpl, TQT_SIGNAL(rectUpdated(const TQRect&)), this, TQT_SIGNAL(rectUpdated(const TQRect&)) ); d->mImpl->init(); } void Document::load() { KURL pixURL=url(); Q_ASSERT(!pixURL.isEmpty()); LOG("url: " << pixURL.prettyURL()); // DocumentLoadingImpl might emit "finished()" in its "init()" method, so // make sure we emit "loading()" before switching emit loading(); switchToImpl(new DocumentLoadingImpl(this)); } void Document::slotFinished(bool success) { LOG(""); if (success) { emit loaded(d->mURL); } else { // FIXME: Emit a failed signal instead emit loaded(d->mURL); } } TQString Document::saveInternal(const KURL& url, const TQCString& format) { TQString msg=d->mImpl->save(url, format); if (msg.isNull()) { emit saved(url); d->mModified=false; return TQString(); } LOG("Save failed: " << msg); return TQString("%1
") .tqarg(i18n("Could not save the image to %1.").tqarg(url.prettyURL())) + msg + "
"; } void Document::reset() { switchToImpl(new DocumentEmptyImpl(this)); emit loaded(d->mURL); } } // namespace