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.
619 lines
16 KiB
619 lines
16 KiB
// vim: set tabstop=4 shiftwidth=4 noexpandtab:
|
|
/*
|
|
Gwenview - A simple image viewer for TDE
|
|
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 <sys/stat.h> // For S_ISDIR
|
|
|
|
// TQt
|
|
#include <tqfileinfo.h>
|
|
#include <tqguardedptr.h>
|
|
#include <tqpaintdevicemetrics.h>
|
|
#include <tqpainter.h>
|
|
#include <tqwmatrix.h>
|
|
|
|
// KDE
|
|
#include <tdeapplication.h>
|
|
#include <kdebug.h>
|
|
#include <tdefilemetainfo.h>
|
|
#include <tdeglobalsettings.h>
|
|
#include <kimageio.h>
|
|
#include <tdeio/job.h>
|
|
#include <tdelocale.h>
|
|
#include <tdemessagebox.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 TQWidget* dialogParentWidget() {
|
|
return TDEApplication::kApplication()->mainWidget();
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
//
|
|
// DocumentPrivate
|
|
//
|
|
//-------------------------------------------------------------------
|
|
class DocumentPrivate {
|
|
public:
|
|
KURL mURL;
|
|
bool mModified;
|
|
TQImage mImage;
|
|
TQString mMimeType;
|
|
TQCString mImageFormat;
|
|
DocumentImpl* mImpl;
|
|
TQGuardedPtr<TDEIO::StatJob> 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 TDEIO 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 = TDEIO::stat( localURL, !localURL.isLocalFile() );
|
|
d->mStatJob->setWindow(TDEApplication::kApplication()->mainWidget());
|
|
connect( d->mStatJob, TQT_SIGNAL( result (TDEIO::Job *) ),
|
|
this, TQT_SLOT( slotStatResult (TDEIO::Job *) ) );
|
|
}
|
|
|
|
|
|
void Document::slotStatResult(TDEIO::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;
|
|
TDEIO::UDSEntry entry = d->mStatJob->statResult();
|
|
d->mURL=d->mStatJob->url();
|
|
|
|
TDEIO::UDSEntry::ConstIterator it;
|
|
for(it=entry.begin();it!=entry.end();++it) {
|
|
if ((*it).m_uds==TDEIO::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()->invalidate( 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( TDEGlobalSettings::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 alignment = (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.scale( 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.scale(pdWidth, pdHeight, TQSize::ScaleMin);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Compute x and y
|
|
if ( alignment & TQt::AlignHCenter )
|
|
x = (pdWidth - size.width())/2;
|
|
else if ( alignment & TQt::AlignLeft )
|
|
x = 0;
|
|
else if ( alignment & TQt::AlignRight )
|
|
x = pdWidth - size.width();
|
|
|
|
if ( alignment & TQt::AlignVCenter )
|
|
y = (pdHeight - size.height())/2;
|
|
else if ( alignment & TQt::AlignTop )
|
|
y = 0;
|
|
else if ( alignment & 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("<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, 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("<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
|