summaryrefslogtreecommitdiffstats
path: root/src/gvcore/document.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/gvcore/document.cpp')
-rw-r--r--src/gvcore/document.cpp618
1 files changed, 618 insertions, 0 deletions
diff --git a/src/gvcore/document.cpp b/src/gvcore/document.cpp
new file mode 100644
index 0000000..058efe2
--- /dev/null
+++ b/src/gvcore/document.cpp
@@ -0,0 +1,618 @@
+// 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+*/
+
+#include <sys/stat.h> // For S_ISDIR
+
+// Qt
+#include <qfileinfo.h>
+#include <qguardedptr.h>
+#include <qpaintdevicemetrics.h>
+#include <qpainter.h>
+#include <qwmatrix.h>
+
+// KDE
+#include <kapplication.h>
+#include <kdebug.h>
+#include <kfilemetainfo.h>
+#include <kglobalsettings.h>
+#include <kimageio.h>
+#include <kio/job.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kprinter.h>
+#include <kstringhandler.h>
+
+// 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 QWidget* dialogParentWidget() {
+ return KApplication::kApplication()->mainWidget();
+}
+
+//-------------------------------------------------------------------
+//
+// DocumentPrivate
+//
+//-------------------------------------------------------------------
+class DocumentPrivate {
+public:
+ KURL mURL;
+ bool mModified;
+ QImage mImage;
+ QString mMimeType;
+ QCString mImageFormat;
+ DocumentImpl* mImpl;
+ QGuardedPtr<KIO::StatJob> mStatJob;
+ int mFileSize;
+};
+
+
+//-------------------------------------------------------------------
+//
+// Document
+//
+//-------------------------------------------------------------------
+Document::Document(QObject* parent)
+: QObject(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 Qt's plugins, so that Gwenview's decoders that
+ // override some of them are installed later and thus come first.
+ QImageIO::inputFormats();
+ {
+ static Gwenview::JPEGFormatType sJPEGFormatType;
+ static Gwenview::PNGFormatType sPNGFormatType;
+ static Gwenview::XPM sXPM;
+ static Gwenview::MNG sMNG;
+ static Gwenview::XCursorFormatType sXCursorFormatType;
+ }
+
+ connect( this, SIGNAL( loading()),
+ this, SLOT( slotLoading()));
+ connect( this, SIGNAL( loaded(const KURL&)),
+ this, SLOT( slotLoaded()));
+}
+
+
+Document::~Document() {
+ delete d->mImpl;
+ delete d;
+}
+
+
+//---------------------------------------------------------------------
+//
+// Properties
+//
+//---------------------------------------------------------------------
+QString Document::mimeType() const {
+ return d->mMimeType;
+}
+
+void Document::setMimeType(const QString& 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())) {
+ QFileInfo 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, SIGNAL( result (KIO::Job *) ),
+ this, 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 QImage& Document::image() const {
+ return d->mImage;
+}
+
+void Document::setImage(QImage 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;
+ }
+}
+
+QString Document::filename() const {
+ return d->mURL.filename(false);
+}
+
+const QCString& Document::imageFormat() const {
+ return d->mImageFormat;
+}
+
+void Document::setImageFormat(const QCString& format) {
+ d->mImageFormat=format;
+}
+
+void Document::setFileSize(int size) {
+ d->mFileSize=size;
+}
+
+QString Document::comment() const {
+ return d->mImpl->comment();
+}
+
+QString Document::aperture() const {
+ return d->mImpl->aperture();
+}
+
+QString Document::exposureTime() const {
+ return d->mImpl->exposureTime();
+}
+
+QString Document::iso() const {
+ return d->mImpl->iso();
+}
+
+QString Document::focalLength() const {
+ return d->mImpl->focalLength();
+}
+
+void Document::setComment(const QString& 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()->invalidate( url());
+ load();
+ emit reloaded(url());
+}
+
+
+void Document::print(KPrinter *pPrinter) {
+ QPainter printPainter;
+ printPainter.begin(pPrinter);
+ doPaint(pPrinter, &printPainter);
+ printPainter.end();
+}
+
+
+void Document::doPaint(KPrinter *printer, QPainter *painter) {
+ // will contain the final image to print
+ QImage image = d->mImage;
+ image.detach();
+
+ // We use a QPaintDeviceMetrics to know the actual page size in pixel,
+ // this gives the real painting area
+ QPaintDeviceMetrics pdMetrics(painter->device());
+ const int margin = pdMetrics.logicalDpiY() / 2; // half-inch margin
+
+ painter->setFont( KGlobalSettings::generalFont() );
+ QFontMetrics fMetrics = painter->fontMetrics();
+
+ int x = 0;
+ int y = 0;
+ int pdWidth = pdMetrics.width();
+ int pdHeight = pdMetrics.height();
+
+ QString t = "true";
+ QString f = "false";
+
+ int alignment = (printer->option("app-gwenview-position").isEmpty() ?
+ Qt::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();
+
+ QSize 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.scale( pdWidth, pdHeight, QSize::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?"),
+ QString::null,KStdGuiItem::cont(),
+ i18n("Shrink") );
+
+ if (resp==KMessageBox::Cancel) {
+ printer->abort();
+ return;
+ } else if (resp == KMessageBox::No) { // Shrink
+ size.scale(pdWidth, pdHeight, QSize::ScaleMin);
+ }
+ }
+ }
+
+ // Compute x and y
+ if ( alignment & Qt::AlignHCenter )
+ x = (pdWidth - size.width())/2;
+ else if ( alignment & Qt::AlignLeft )
+ x = 0;
+ else if ( alignment & Qt::AlignRight )
+ x = pdWidth - size.width();
+
+ if ( alignment & Qt::AlignVCenter )
+ y = (pdHeight - size.height())/2;
+ else if ( alignment & Qt::AlignTop )
+ y = 0;
+ else if ( alignment & Qt::AlignBottom )
+ y = pdHeight - size.height();
+
+ // Draw, the image will be scaled to fit the given area if necessary
+ painter->drawImage( QRect( x, y, size.width(), size.height()), image );
+
+ if ( printFilename ) {
+ QString 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 ) {
+ QString 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() {
+ QString 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;
+
+ QString 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;
+
+ QString msg=i18n("<qt>The image <b>%1</b> has been modified, do you want to save the changes?</qt>")
+ .arg(url().prettyURL());
+
+ int result=KMessageBox::questionYesNo(dialogParentWidget(), msg, QString::null,
+ 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, SIGNAL(finished(bool)),
+ this, SLOT(slotFinished(bool)) );
+ connect(d->mImpl, SIGNAL(sizeUpdated()),
+ this, SIGNAL(sizeUpdated()) );
+ connect(d->mImpl, SIGNAL(rectUpdated(const QRect&)),
+ this, SIGNAL(rectUpdated(const QRect&)) );
+ 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);
+ }
+}
+
+
+QString Document::saveInternal(const KURL& url, const QCString& format) {
+ QString msg=d->mImpl->save(url, format);
+
+ if (msg.isNull()) {
+ emit saved(url);
+ d->mModified=false;
+ return QString::null;
+ }
+
+ LOG("Save failed: " << msg);
+ return QString("<qt><b>%1</b><br/>")
+ .arg(i18n("Could not save the image to %1.").arg(url.prettyURL()))
+ + msg + "</qt>";
+}
+
+
+void Document::reset() {
+ switchToImpl(new DocumentEmptyImpl(this));
+ emit loaded(d->mURL);
+}
+
+} // namespace