summaryrefslogtreecommitdiffstats
path: root/src/gvcore
diff options
context:
space:
mode:
Diffstat (limited to 'src/gvcore')
-rw-r--r--src/gvcore/.vimrc4
-rw-r--r--src/gvcore/Makefile.am85
-rw-r--r--src/gvcore/archive.cpp87
-rw-r--r--src/gvcore/archive.h46
-rw-r--r--src/gvcore/bcgdialog.cpp84
-rw-r--r--src/gvcore/bcgdialog.h53
-rw-r--r--src/gvcore/bcgdialogbase.ui179
-rw-r--r--src/gvcore/busylevelmanager.cpp108
-rw-r--r--src/gvcore/busylevelmanager.h113
-rw-r--r--src/gvcore/cache.cpp402
-rw-r--r--src/gvcore/cache.h67
-rw-r--r--src/gvcore/captionformatter.cpp57
-rw-r--r--src/gvcore/captionformatter.h57
-rw-r--r--src/gvcore/clicklineedit.cpp106
-rw-r--r--src/gvcore/clicklineedit.h63
-rw-r--r--src/gvcore/cursortracker.cpp86
-rw-r--r--src/gvcore/cursortracker.h56
-rw-r--r--src/gvcore/deletedialog.cpp133
-rw-r--r--src/gvcore/deletedialog.h55
-rw-r--r--src/gvcore/deletedialogbase.ui111
-rw-r--r--src/gvcore/document.cpp618
-rw-r--r--src/gvcore/document.h194
-rw-r--r--src/gvcore/documentanimatedloadedimpl.cpp97
-rw-r--r--src/gvcore/documentanimatedloadedimpl.h62
-rw-r--r--src/gvcore/documentimpl.cpp103
-rw-r--r--src/gvcore/documentimpl.h103
-rw-r--r--src/gvcore/documentjpegloadedimpl.cpp143
-rw-r--r--src/gvcore/documentjpegloadedimpl.h62
-rw-r--r--src/gvcore/documentloadedimpl.cpp198
-rw-r--r--src/gvcore/documentloadedimpl.h54
-rw-r--r--src/gvcore/documentloadingimpl.cpp165
-rw-r--r--src/gvcore/documentloadingimpl.h55
-rw-r--r--src/gvcore/documentotherloadedimpl.cpp58
-rw-r--r--src/gvcore/documentotherloadedimpl.h52
-rw-r--r--src/gvcore/dragpixmapgenerator.h179
-rw-r--r--src/gvcore/externaltoolaction.cpp56
-rw-r--r--src/gvcore/externaltoolaction.h50
-rw-r--r--src/gvcore/externaltoolcontext.cpp80
-rw-r--r--src/gvcore/externaltoolcontext.h59
-rw-r--r--src/gvcore/externaltooldialog.cpp355
-rw-r--r--src/gvcore/externaltooldialog.h59
-rw-r--r--src/gvcore/externaltooldialogbase.ui373
-rw-r--r--src/gvcore/externaltoolmanager.cpp294
-rw-r--r--src/gvcore/externaltoolmanager.h67
-rw-r--r--src/gvcore/filedetailview.cpp552
-rw-r--r--src/gvcore/filedetailview.h133
-rw-r--r--src/gvcore/filedetailviewitem.cpp70
-rw-r--r--src/gvcore/filedetailviewitem.h89
-rw-r--r--src/gvcore/fileoperation.cpp119
-rw-r--r--src/gvcore/fileoperation.h95
-rw-r--r--src/gvcore/fileoperationconfig.kcfg22
-rw-r--r--src/gvcore/fileoperationconfig.kcfgc7
-rw-r--r--src/gvcore/fileopobject.cpp347
-rw-r--r--src/gvcore/fileopobject.h143
-rw-r--r--src/gvcore/filethumbnailview.cpp866
-rw-r--r--src/gvcore/filethumbnailview.h131
-rw-r--r--src/gvcore/filethumbnailviewitem.cpp394
-rw-r--r--src/gvcore/filethumbnailviewitem.h69
-rw-r--r--src/gvcore/fileviewbase.h47
-rw-r--r--src/gvcore/fileviewconfig.kcfg62
-rw-r--r--src/gvcore/fileviewconfig.kcfgc7
-rw-r--r--src/gvcore/fileviewcontroller.cpp1321
-rw-r--r--src/gvcore/fileviewcontroller.h256
-rw-r--r--src/gvcore/filterbar.ui255
-rw-r--r--src/gvcore/fullscreenbar.cpp171
-rw-r--r--src/gvcore/fullscreenbar.h56
-rw-r--r--src/gvcore/fullscreenconfig.kcfg16
-rw-r--r--src/gvcore/fullscreenconfig.kcfgc7
-rw-r--r--src/gvcore/gimp.h138
-rw-r--r--src/gvcore/imageframe.h42
-rw-r--r--src/gvcore/imageloader.cpp917
-rw-r--r--src/gvcore/imageloader.h123
-rw-r--r--src/gvcore/imagesavedialog.cpp131
-rw-r--r--src/gvcore/imagesavedialog.h53
-rw-r--r--src/gvcore/imageview.cpp1469
-rw-r--r--src/gvcore/imageview.h190
-rw-r--r--src/gvcore/imageviewconfig.kcfg54
-rw-r--r--src/gvcore/imageviewconfig.kcfgc7
-rw-r--r--src/gvcore/imageviewcontroller.cpp527
-rw-r--r--src/gvcore/imageviewcontroller.h84
-rw-r--r--src/gvcore/imageviewtools.cpp213
-rw-r--r--src/gvcore/imageviewtools.h96
-rw-r--r--src/gvcore/inputdialog.cpp78
-rw-r--r--src/gvcore/inputdialog.h55
-rw-r--r--src/gvcore/jpegformattype.cpp527
-rw-r--r--src/gvcore/jpegformattype.h51
-rw-r--r--src/gvcore/libgwenview_export.h36
-rw-r--r--src/gvcore/mimetypeutils.cpp87
-rw-r--r--src/gvcore/mimetypeutils.h47
-rw-r--r--src/gvcore/miscconfig.kcfg31
-rw-r--r--src/gvcore/miscconfig.kcfgc7
-rw-r--r--src/gvcore/mngformattype.cpp520
-rw-r--r--src/gvcore/mngformattype.h55
-rw-r--r--src/gvcore/pngformattype.cpp559
-rw-r--r--src/gvcore/pngformattype.h63
-rw-r--r--src/gvcore/printdialog.cpp297
-rw-r--r--src/gvcore/printdialog.h80
-rw-r--r--src/gvcore/printdialogpagebase.ui408
-rw-r--r--src/gvcore/qxcfi.cpp2405
-rw-r--r--src/gvcore/qxcfi.h332
-rw-r--r--src/gvcore/slideshow.cpp217
-rw-r--r--src/gvcore/slideshow.h86
-rw-r--r--src/gvcore/slideshowconfig.kcfg27
-rw-r--r--src/gvcore/slideshowconfig.kcfgc7
-rw-r--r--src/gvcore/threadgate.cpp60
-rw-r--r--src/gvcore/threadgate.h45
-rw-r--r--src/gvcore/thumbnaildetailsdialog.cpp78
-rw-r--r--src/gvcore/thumbnaildetailsdialog.h47
-rw-r--r--src/gvcore/thumbnaildetailsdialogbase.ui117
-rw-r--r--src/gvcore/thumbnailloadjob.cpp763
-rw-r--r--src/gvcore/thumbnailloadjob.h211
-rw-r--r--src/gvcore/thumbnailsize.h42
-rw-r--r--src/gvcore/timeutils.cpp53
-rw-r--r--src/gvcore/timeutils.h44
-rw-r--r--src/gvcore/xcursor.cpp190
-rw-r--r--src/gvcore/xcursor.h37
-rw-r--r--src/gvcore/xpm.cpp439
-rw-r--r--src/gvcore/xpm.h54
118 files changed, 22922 insertions, 0 deletions
diff --git a/src/gvcore/.vimrc b/src/gvcore/.vimrc
new file mode 100644
index 0000000..a37475c
--- /dev/null
+++ b/src/gvcore/.vimrc
@@ -0,0 +1,4 @@
+set tabstop=4
+set shiftwidth=4
+set noexpandtab
+set makeprg=unsermake
diff --git a/src/gvcore/Makefile.am b/src/gvcore/Makefile.am
new file mode 100644
index 0000000..1bec46b
--- /dev/null
+++ b/src/gvcore/Makefile.am
@@ -0,0 +1,85 @@
+AM_CPPFLAGS = -I$(srcdir)/.. $(all_includes) -D_LARGEFILE64_SOURCE
+
+lib_LTLIBRARIES = libgwenviewcore.la
+
+libgwenviewcore_la_LDFLAGS = $(all_libraries) -version-info 1:0:0 -no-undefined
+
+libgwenviewcore_la_LIBADD = \
+ $(LIB_KFILE) $(LIB_KDEUI) $(LIB_KDECORE) $(LIB_KDEPRINT) $(LIB_QT) \
+ $(LIBJPEG) $(LIBPNG) $(LIBMNG) $(GV_LIB_XCURSOR) $(LIB_EXIV2)\
+ -lkmediaplayer ../imageutils/libgvimageutils.la \
+ ../tsthread/libtsthread.la
+
+libgwenviewcore_la_METASOURCES = AUTO
+
+noinst_HEADERS = libgwenview_export.h
+
+# Be sure to keep pngformattype.cpp first, to avoid troubles with --enable-final
+# See bug #134919
+libgwenviewcore_la_SOURCES = \
+ pngformattype.cpp \
+ printdialog.cpp \
+ printdialogpagebase.ui \
+ thumbnailloadjob.cpp \
+ imageview.cpp \
+ imageviewcontroller.cpp \
+ document.cpp \
+ externaltoolmanager.cpp \
+ externaltoolcontext.cpp \
+ externaltoolaction.cpp \
+ externaltooldialogbase.ui \
+ externaltooldialog.cpp \
+ fileviewcontroller.cpp \
+ filethumbnailview.cpp \
+ fileoperation.cpp \
+ fileopobject.cpp \
+ filethumbnailviewitem.cpp \
+ filterbar.ui \
+ qxcfi.cpp \
+ archive.cpp \
+ slideshow.cpp \
+ filedetailview.cpp \
+ filedetailviewitem.cpp \
+ imagesavedialog.cpp \
+ jpegformattype.cpp \
+ mngformattype.cpp \
+ xpm.cpp \
+ documentimpl.cpp \
+ documentloadingimpl.cpp \
+ documentloadedimpl.cpp \
+ documentjpegloadedimpl.cpp \
+ documentanimatedloadedimpl.cpp \
+ documentotherloadedimpl.cpp \
+ busylevelmanager.cpp \
+ cache.cpp \
+ threadgate.cpp \
+ imageviewtools.cpp \
+ fullscreenbar.cpp \
+ imageloader.cpp \
+ cursortracker.cpp \
+ captionformatter.cpp \
+ thumbnaildetailsdialogbase.ui \
+ thumbnaildetailsdialog.cpp \
+ xcursor.cpp \
+ mimetypeutils.cpp \
+ bcgdialog.cpp \
+ bcgdialogbase.ui \
+ timeutils.cpp \
+ clicklineedit.cpp \
+ inputdialog.cpp \
+ deletedialog.cpp \
+ deletedialogbase.ui \
+ miscconfig.kcfgc \
+ slideshowconfig.kcfgc \
+ fileoperationconfig.kcfgc \
+ fullscreenconfig.kcfgc \
+ imageviewconfig.kcfgc \
+ fileviewconfig.kcfgc
+
+kde_kcfg_DATA = \
+ miscconfig.kcfg \
+ slideshowconfig.kcfg \
+ fileoperationconfig.kcfg \
+ fullscreenconfig.kcfg \
+ imageviewconfig.kcfg \
+ fileviewconfig.kcfg
diff --git a/src/gvcore/archive.cpp b/src/gvcore/archive.cpp
new file mode 100644
index 0000000..d31d1a7
--- /dev/null
+++ b/src/gvcore/archive.cpp
@@ -0,0 +1,87 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2000-2004 Aurélien Gâteau
+
+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.
+
+*/
+
+// KDE includes
+#include <kfileitem.h>
+
+// Our includes
+#include "archive.h"
+namespace Gwenview {
+
+
+namespace Archive {
+
+typedef QMap<QString,QString> MimeTypeProtocols;
+
+static const char* KDE_PROTOCOL = "X-KDE-LocalProtocol";
+
+static const MimeTypeProtocols& mimeTypeProtocols() {
+ static MimeTypeProtocols map;
+ if (map.isEmpty()) {
+ KMimeType::List list = KMimeType::allMimeTypes();
+ KMimeType::List::Iterator it=list.begin(), end=list.end();
+ for (; it!=end; ++it) {
+ if ( (*it)->propertyNames().findIndex(KDE_PROTOCOL)!= -1 ) {
+ QString protocol = (*it)->property(KDE_PROTOCOL).toString();
+ map[(*it)->name()] = protocol;
+ }
+ }
+ }
+ return map;
+}
+
+
+bool fileItemIsArchive(const KFileItem* item) {
+ return mimeTypeProtocols().contains(item->mimetype());
+}
+
+bool fileItemIsDirOrArchive(const KFileItem* item) {
+ return item->isDir() || Archive::fileItemIsArchive(item);
+}
+
+bool protocolIsArchive(const QString& protocol) {
+ const MimeTypeProtocols& map=mimeTypeProtocols();
+ MimeTypeProtocols::ConstIterator it;
+ for (it=map.begin();it!=map.end();++it) {
+ if (it.data()==protocol) return true;
+ }
+ return false;
+}
+
+QStringList mimeTypes() {
+ const MimeTypeProtocols& map=mimeTypeProtocols();
+ MimeTypeProtocols::ConstIterator it;
+ QStringList strlist;
+ for (it=map.begin();it!=map.end();++it) {
+ strlist+=it.key();
+ }
+ return strlist;
+ //return mimeTypeProtocols().keys(); // keys() does not exist in Qt 3.0
+}
+
+
+QString protocolForMimeType(const QString& mimeType) {
+ return mimeTypeProtocols()[mimeType];
+}
+
+}
+
+} // namespace
diff --git a/src/gvcore/archive.h b/src/gvcore/archive.h
new file mode 100644
index 0000000..cc98ced
--- /dev/null
+++ b/src/gvcore/archive.h
@@ -0,0 +1,46 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2000-2004 Aurélien Gâteau
+
+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.
+
+*/
+
+#ifndef ARCHIVE_H
+#define ARCHIVE_H
+
+// Qt includes
+#include <qstringlist.h>
+#include "libgwenview_export.h"
+class KFileItem;
+namespace Gwenview {
+
+/**
+ * Helper functions to deal with archives
+ */
+namespace Archive {
+
+LIBGWENVIEW_EXPORT bool fileItemIsArchive(const KFileItem*);
+LIBGWENVIEW_EXPORT bool fileItemIsDirOrArchive(const KFileItem*);
+bool protocolIsArchive(const QString&);
+QStringList mimeTypes();
+QString protocolForMimeType(const QString&);
+
+}
+
+} // namespace
+#endif
+
diff --git a/src/gvcore/bcgdialog.cpp b/src/gvcore/bcgdialog.cpp
new file mode 100644
index 0000000..18b7822
--- /dev/null
+++ b/src/gvcore/bcgdialog.cpp
@@ -0,0 +1,84 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2006 Aurélien Gâteau
+
+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.
+
+*/
+// Self
+#include "bcgdialog.moc"
+
+// Qt
+#include <qslider.h>
+#include <qvbox.h>
+
+// KDE
+#include <klocale.h>
+
+// Local
+#include "imageview.h"
+#include "bcgdialogbase.h"
+
+namespace Gwenview {
+
+
+struct BCGDialog::Private {
+ ImageView* mView;
+ BCGDialogBase* mContent;
+};
+
+
+BCGDialog::BCGDialog(ImageView* view)
+: KDialogBase(view, "bcg_dialog", false /* modal */,
+ i18n("Adjust Brightness/Contrast/Gamma"), KDialogBase::Close | KDialogBase::Default)
+{
+ d=new Private;
+ d->mView=view;
+ d->mContent=new BCGDialogBase(this);
+ setMainWidget(d->mContent);
+ connect(d->mContent->mBSlider, SIGNAL(valueChanged(int)),
+ view, SLOT(setBrightness(int)) );
+ connect(d->mContent->mCSlider, SIGNAL(valueChanged(int)),
+ view, SLOT(setContrast(int)) );
+ connect(d->mContent->mGSlider, SIGNAL(valueChanged(int)),
+ view, SLOT(setGamma(int)) );
+
+ connect(view, SIGNAL(bcgChanged()),
+ this, SLOT(updateFromImageView()) );
+}
+
+
+BCGDialog::~BCGDialog() {
+ delete d;
+}
+
+
+void BCGDialog::slotDefault() {
+ d->mView->setBrightness(0);
+ d->mView->setContrast(0);
+ d->mView->setGamma(0);
+ updateFromImageView();
+}
+
+
+void BCGDialog::updateFromImageView() {
+ d->mContent->mBSlider->setValue(d->mView->brightness());
+ d->mContent->mCSlider->setValue(d->mView->contrast());
+ d->mContent->mGSlider->setValue(d->mView->gamma());
+}
+
+
+} // namespace
diff --git a/src/gvcore/bcgdialog.h b/src/gvcore/bcgdialog.h
new file mode 100644
index 0000000..4093320
--- /dev/null
+++ b/src/gvcore/bcgdialog.h
@@ -0,0 +1,53 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2006 Aurélien Gâteau
+
+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.
+
+*/
+#ifndef BCGDIALOG_H
+#define BCGDIALOG_H
+
+// KDE
+#include <kdialogbase.h>
+
+class QShowEvent;
+
+namespace Gwenview {
+
+class ImageView;
+
+class BCGDialog : public KDialogBase {
+Q_OBJECT
+public:
+ BCGDialog(ImageView*);
+ ~BCGDialog();
+
+protected:
+ virtual void slotDefault();
+
+private slots:
+ void updateFromImageView();
+
+private:
+ struct Private;
+ Private* d;
+};
+
+
+} // namespace
+
+#endif /* BCGDIALOG_H */
diff --git a/src/gvcore/bcgdialogbase.ui b/src/gvcore/bcgdialogbase.ui
new file mode 100644
index 0000000..81a79a3
--- /dev/null
+++ b/src/gvcore/bcgdialogbase.ui
@@ -0,0 +1,179 @@
+<!DOCTYPE UI><UI version="3.2" stdsetdef="1">
+<class>BCGDialogBase</class>
+<widget class="QWidget">
+ <property name="name">
+ <cstring>BCGDialogBase</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>377</width>
+ <height>140</height>
+ </rect>
+ </property>
+ <grid>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <widget class="QLabel" row="1" column="0">
+ <property name="name">
+ <cstring>textLabel2</cstring>
+ </property>
+ <property name="text">
+ <string>&amp;Contrast:</string>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring>mCSlider</cstring>
+ </property>
+ </widget>
+ <widget class="QLabel" row="2" column="0">
+ <property name="name">
+ <cstring>textLabel2_2</cstring>
+ </property>
+ <property name="text">
+ <string>&amp;Gamma:</string>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring>mGSlider</cstring>
+ </property>
+ </widget>
+ <widget class="QSlider" row="0" column="1">
+ <property name="name">
+ <cstring>mBSlider</cstring>
+ </property>
+ <property name="minValue">
+ <number>-100</number>
+ </property>
+ <property name="maxValue">
+ <number>100</number>
+ </property>
+ <property name="orientation">
+ <enum>Horizontal</enum>
+ </property>
+ </widget>
+ <widget class="QSlider" row="1" column="1">
+ <property name="name">
+ <cstring>mCSlider</cstring>
+ </property>
+ <property name="minValue">
+ <number>-100</number>
+ </property>
+ <property name="maxValue">
+ <number>100</number>
+ </property>
+ <property name="orientation">
+ <enum>Horizontal</enum>
+ </property>
+ </widget>
+ <widget class="QSlider" row="2" column="1">
+ <property name="name">
+ <cstring>mGSlider</cstring>
+ </property>
+ <property name="minValue">
+ <number>-100</number>
+ </property>
+ <property name="maxValue">
+ <number>100</number>
+ </property>
+ <property name="orientation">
+ <enum>Horizontal</enum>
+ </property>
+ </widget>
+ <widget class="QSpinBox" row="0" column="2">
+ <property name="name">
+ <cstring>mBSpinBox</cstring>
+ </property>
+ <property name="maxValue">
+ <number>100</number>
+ </property>
+ <property name="minValue">
+ <number>-100</number>
+ </property>
+ </widget>
+ <widget class="QSpinBox" row="1" column="2">
+ <property name="name">
+ <cstring>mCSpinBox</cstring>
+ </property>
+ <property name="maxValue">
+ <number>100</number>
+ </property>
+ <property name="minValue">
+ <number>-100</number>
+ </property>
+ </widget>
+ <widget class="QSpinBox" row="2" column="2">
+ <property name="name">
+ <cstring>mGSpinBox</cstring>
+ </property>
+ <property name="maxValue">
+ <number>100</number>
+ </property>
+ <property name="minValue">
+ <number>-100</number>
+ </property>
+ </widget>
+ <widget class="QLabel" row="0" column="0">
+ <property name="name">
+ <cstring>textLabel1</cstring>
+ </property>
+ <property name="text">
+ <string>&amp;Brightness:</string>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring>mBSlider</cstring>
+ </property>
+ </widget>
+ </grid>
+</widget>
+<connections>
+ <connection>
+ <sender>mBSlider</sender>
+ <signal>valueChanged(int)</signal>
+ <receiver>mBSpinBox</receiver>
+ <slot>setValue(int)</slot>
+ </connection>
+ <connection>
+ <sender>mCSlider</sender>
+ <signal>valueChanged(int)</signal>
+ <receiver>mCSpinBox</receiver>
+ <slot>setValue(int)</slot>
+ </connection>
+ <connection>
+ <sender>mGSlider</sender>
+ <signal>valueChanged(int)</signal>
+ <receiver>mGSpinBox</receiver>
+ <slot>setValue(int)</slot>
+ </connection>
+ <connection>
+ <sender>mBSpinBox</sender>
+ <signal>valueChanged(int)</signal>
+ <receiver>mBSlider</receiver>
+ <slot>setValue(int)</slot>
+ </connection>
+ <connection>
+ <sender>mCSpinBox</sender>
+ <signal>valueChanged(int)</signal>
+ <receiver>mCSlider</receiver>
+ <slot>setValue(int)</slot>
+ </connection>
+ <connection>
+ <sender>mGSpinBox</sender>
+ <signal>valueChanged(int)</signal>
+ <receiver>mGSlider</receiver>
+ <slot>setValue(int)</slot>
+ </connection>
+</connections>
+<tabstops>
+ <tabstop>mBSlider</tabstop>
+ <tabstop>mBSpinBox</tabstop>
+ <tabstop>mCSlider</tabstop>
+ <tabstop>mCSpinBox</tabstop>
+ <tabstop>mGSlider</tabstop>
+ <tabstop>mGSpinBox</tabstop>
+</tabstops>
+<layoutdefaults spacing="6" margin="11"/>
+</UI>
diff --git a/src/gvcore/busylevelmanager.cpp b/src/gvcore/busylevelmanager.cpp
new file mode 100644
index 0000000..adfdf55
--- /dev/null
+++ b/src/gvcore/busylevelmanager.cpp
@@ -0,0 +1,108 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2000-2004 Aurélien Gâteau
+
+ 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.
+
+*/
+
+// Qt
+#include <qtimer.h>
+
+// KDE
+#include <kdebug.h>
+
+// Local
+#include "busylevelmanager.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
+
+BusyLevelManager::BusyLevelManager()
+: mCurrentBusyLevel( BUSY_NONE )
+{
+ connect( &mDelayedBusyLevelTimer, SIGNAL( timeout()),
+ this, SLOT( delayedBusyLevelChanged()));
+}
+
+BusyLevelManager* BusyLevelManager::instance() {
+ static BusyLevelManager manager;
+ return &manager;
+}
+
+// How the busy level stuff works:
+// This system allows suspending less important tasks while more important
+// task are active, i.e. no thumbnails are generated when the viewed
+// image is being loaded and painted.
+// All objects responsible for operations set their busy level
+// to the matching value when the operation starts and reset their busy
+// level when the operation is done. They all connect to busyLevelChanged()
+// signal and suspend their operation if the current busy level is higher
+// than the busy level of their operation. If a new operation is started,
+// it needs to be immediatelly suspended if the current busy level is higher!
+// Note that there can be only one level per object,
+// so if one object is responsible for more operations,
+// it needs to use helper objects for setBusyLevel().
+
+void BusyLevelManager::setBusyLevel( QObject* obj, BusyLevel level ) {
+ LOG("BUSY:" << level << ":" << obj << ":" << obj->className() );
+ if( level > BUSY_NONE ) {
+ if( mBusyLevels.contains( obj ) && mBusyLevels[ obj ] == level ) return;
+ if( !mBusyLevels.contains( obj )) {
+ connect( obj, SIGNAL( destroyed( QObject* )), this, SLOT( objectDestroyed( QObject* )));
+ }
+ mBusyLevels[ obj ] = level;
+ } else {
+ mBusyLevels.remove( obj );
+ disconnect( obj, SIGNAL( destroyed( QObject* )), this, SLOT( objectDestroyed( QObject* )));
+ }
+ mDelayedBusyLevelTimer.start( 0, true );
+}
+
+void BusyLevelManager::objectDestroyed( QObject* obj ) {
+ LOG("DESTROYED:" << obj );
+ mBusyLevels.remove( obj );
+ mDelayedBusyLevelTimer.start( 0, true );
+}
+
+void BusyLevelManager::delayedBusyLevelChanged() {
+ BusyLevel newLevel = BUSY_NONE;
+ for( QMap< QObject*, BusyLevel >::ConstIterator it = mBusyLevels.begin();
+ it != mBusyLevels.end();
+ ++it ) {
+ newLevel = QMAX( newLevel, *it );
+ }
+
+ if( newLevel != mCurrentBusyLevel ) {
+ LOG("CHANGED BUSY:" << newLevel);
+ mCurrentBusyLevel = newLevel;
+ emit busyLevelChanged( newLevel );
+ }
+}
+
+BusyLevel BusyLevelManager::busyLevel() const {
+ return mCurrentBusyLevel;
+}
+
+
+} // namespace
diff --git a/src/gvcore/busylevelmanager.h b/src/gvcore/busylevelmanager.h
new file mode 100644
index 0000000..21bee6d
--- /dev/null
+++ b/src/gvcore/busylevelmanager.h
@@ -0,0 +1,113 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2000-2004 Aurélien Gâteau
+
+ 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.
+
+*/
+#ifndef BUSYLEVELMANAGER_H
+#define BUSYLEVELMANAGER_H
+
+// Qt
+#include <qtimer.h>
+namespace Gwenview {
+
+// KDE
+#include "libgwenview_export.h"
+
+/*
+ Busy level of the application.
+ Sorted by increasing priority.
+*/
+enum BusyLevel {
+ BUSY_NONE,
+ BUSY_THUMBNAILS,
+ BUSY_PRELOADING,
+ BUSY_LOADING,
+ BUSY_SMOOTHING,
+ BUSY_PAINTING,
+ BUSY_CHECKING_NEW_IMAGE
+};
+
+class LIBGWENVIEW_EXPORT BusyLevelManager : public QObject {
+Q_OBJECT
+public:
+ static BusyLevelManager* instance();
+
+ /**
+ * Announces that the given object is busy.
+ */
+ void setBusyLevel( QObject* obj, BusyLevel level );
+
+ /**
+ * Returns the busy level of the whole application (i.e. maximum).
+ */
+ BusyLevel busyLevel() const;
+
+signals:
+ /**
+ * When emitted, operations that are less important than current level
+ * should be suspended until the level decreases to their level.
+ * E.g. when loading a picture thumbnail generation should get suspended.
+ */
+ void busyLevelChanged( BusyLevel level );
+
+private slots:
+ void delayedBusyLevelChanged();
+ void objectDestroyed( QObject* obj );
+
+private:
+ BusyLevelManager();
+ QMap< QObject*, BusyLevel > mBusyLevels;
+ BusyLevel mCurrentBusyLevel;
+ QTimer mDelayedBusyLevelTimer;
+};
+
+
+/**
+ Helper class. Constructor sets its busy level to the given level,
+ destructor resets the busy level to none.
+ */
+class BusyLevelHelper : public QObject {
+Q_OBJECT
+public:
+ BusyLevelHelper( BusyLevel level );
+ ~BusyLevelHelper();
+ void reset();
+};
+
+inline
+BusyLevelHelper::BusyLevelHelper( BusyLevel level )
+{
+ BusyLevelManager::instance()->setBusyLevel( this, level );
+}
+
+inline
+void BusyLevelHelper::reset()
+{
+ BusyLevelManager::instance()->setBusyLevel( this, BUSY_NONE );
+}
+
+inline
+BusyLevelHelper::~BusyLevelHelper()
+{
+ reset();
+}
+
+
+} // namespace
+#endif
+
diff --git a/src/gvcore/cache.cpp b/src/gvcore/cache.cpp
new file mode 100644
index 0000000..77b0211
--- /dev/null
+++ b/src/gvcore/cache.cpp
@@ -0,0 +1,402 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2000-2004 Aurélien Gâteau
+
+ 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 "cache.h"
+
+// Qt
+
+// KDE
+#include <kconfig.h>
+#include <kdebug.h>
+#include <kdeversion.h>
+#include <ksharedptr.h>
+#include <kstaticdeleter.h>
+#include <kio/global.h>
+
+#include "cache.moc"
+
+namespace Gwenview {
+
+// Local
+
+#undef ENABLE_LOG
+#undef LOG
+//#define ENABLE_LOG
+#ifdef ENABLE_LOG
+#define LOG(x) kdDebug() << k_funcinfo << x << endl
+#else
+#define LOG(x) ;
+#endif
+
+//#define DEBUG_CACHE
+
+const char CONFIG_CACHE_MAXSIZE[]="maxSize";
+
+
+struct ImageData : public KShared {
+ ImageData( const KURL& url, const QDateTime& _timestamp )
+ : timestamp(_timestamp)
+ , age(0)
+ , fast_url( url.isLocalFile() && !KIO::probably_slow_mounted( url.path()))
+ , priority( false ) {
+ }
+
+ void addFile( const QByteArray& file );
+ void addImage( const ImageFrames& frames, const QCString& format );
+ void addThumbnail( const QPixmap& thumbnail, QSize imagesize );
+ long long cost() const;
+ int size() const;
+ QByteArray file;
+ ImageFrames frames;
+ QPixmap thumbnail;
+ QSize imagesize;
+ QCString format;
+ QDateTime timestamp;
+ mutable int age;
+ bool fast_url;
+ bool priority;
+ int fileSize() const;
+ int imageSize() const;
+ int thumbnailSize() const;
+ bool reduceSize();
+ bool isEmpty() const;
+
+ typedef KSharedPtr<ImageData> Ptr;
+};
+
+typedef QMap<KURL, ImageData::Ptr> ImageMap;
+
+struct Cache::Private {
+ ImageMap mImages;
+ int mMaxSize;
+ int mThumbnailSize;
+ QValueList< KURL > mPriorityURLs;
+
+
+ /**
+ * This function tries to returns a valid ImageData for url and timestamp.
+ * If it can't find one, it will create a new one and return it.
+ */
+ ImageData::Ptr getOrCreateImageData(const KURL& url, const QDateTime& timestamp) {
+ if (mImages.contains(url)) {
+ ImageData::Ptr data = mImages[url];
+ if (data->timestamp == timestamp) return data;
+ }
+
+ ImageData::Ptr data = new ImageData(url, timestamp);
+ mImages[url] = data;
+ if (mPriorityURLs.contains(url)) data->priority = true;
+ return data;
+ }
+};
+
+
+Cache::Cache()
+{
+ d = new Private;
+ d->mMaxSize = DEFAULT_MAXSIZE;
+ // don't remember size for every thumbnail, but have one global and dump all if needed
+ d->mThumbnailSize = 0;
+}
+
+
+Cache::~Cache() {
+ d->mImages.clear();
+ delete d;
+}
+
+
+static Cache* sCache;
+static KStaticDeleter<Cache> sCacheDeleter;
+
+
+Cache* Cache::instance() {
+ if (!sCache) {
+ sCacheDeleter.setObject(sCache, new Cache());
+ }
+ return sCache;
+}
+
+// Priority URLs are used e.g. when prefetching for the slideshow - after an image is prefetched,
+// the loader tries to put the image in the cache. When the slideshow advances, the next loader
+// just gets the image from the cache. However, the prefetching may be useless if the image
+// actually doesn't stay long enough in the cache, e.g. because of being too big for the cache.
+// Marking an URL as a priority one will make sure it stays in the cache and that the cache
+// will be even enlarged as necessary if needed.
+void Cache::setPriorityURL( const KURL& url, bool set ) {
+ if( set ) {
+ d->mPriorityURLs.append( url );
+ if( d->mImages.contains( url )) {
+ d->mImages[ url ]->priority = true;
+ }
+ } else {
+ d->mPriorityURLs.remove( url );
+ if( d->mImages.contains( url )) {
+ d->mImages[ url ]->priority = false;
+ }
+ checkMaxSize();
+ }
+}
+
+
+void Cache::addFile( const KURL& url, const QByteArray& file, const QDateTime& timestamp ) {
+ LOG(url.prettyURL());
+ updateAge();
+ d->getOrCreateImageData(url, timestamp)->addFile(file);
+ checkMaxSize();
+}
+
+void Cache::addImage( const KURL& url, const ImageFrames& frames, const QCString& format, const QDateTime& timestamp ) {
+ LOG(url.prettyURL());
+ updateAge();
+ d->getOrCreateImageData(url, timestamp)->addImage(frames, format);
+ checkMaxSize();
+}
+
+void Cache::addThumbnail( const KURL& url, const QPixmap& thumbnail, QSize imagesize, const QDateTime& timestamp ) {
+// Thumbnails are many and often - things would age too quickly. Therefore
+// when adding thumbnails updateAge() is called from the outside only once for all of them.
+// updateAge();
+ d->getOrCreateImageData(url, timestamp)->addThumbnail(thumbnail, imagesize);
+ checkMaxSize();
+}
+
+void Cache::invalidate( const KURL& url ) {
+ d->mImages.remove( url );
+}
+
+QDateTime Cache::timestamp( const KURL& url ) const {
+ LOG(url.prettyURL());
+ if( d->mImages.contains( url )) return d->mImages[ url ]->timestamp;
+ return QDateTime();
+}
+
+QByteArray Cache::file( const KURL& url ) const {
+ LOG(url.prettyURL());
+ if( d->mImages.contains( url )) {
+ const ImageData::Ptr data = d->mImages[ url ];
+ if( data->file.isNull()) return QByteArray();
+ data->age = 0;
+ return data->file;
+ }
+ return QByteArray();
+}
+
+void Cache::getFrames( const KURL& url, ImageFrames* frames, QCString* format ) const {
+ LOG(url.prettyURL());
+ Q_ASSERT(frames);
+ Q_ASSERT(format);
+ frames->clear();
+ *format = QCString();
+ if( d->mImages.contains( url )) {
+ const ImageData::Ptr data = d->mImages[ url ];
+ if( data->frames.isEmpty()) return;
+ *frames = data->frames;
+ *format = data->format;
+ data->age = 0;
+ }
+}
+
+QPixmap Cache::thumbnail( const KURL& url, QSize& imagesize ) const {
+ if( d->mImages.contains( url )) {
+ const ImageData::Ptr data = d->mImages[ url ];
+ if( data->thumbnail.isNull()) return QPixmap();
+ imagesize = data->imagesize;
+// data.age = 0;
+ return data->thumbnail;
+ }
+ return QPixmap();
+}
+
+void Cache::updateAge() {
+ for( ImageMap::Iterator it = d->mImages.begin();
+ it != d->mImages.end();
+ ++it ) {
+ (*it)->age++;
+ }
+}
+
+void Cache::checkThumbnailSize( int size ) {
+ if( size != d->mThumbnailSize ) {
+ // simply remove all thumbnails, should happen rarely
+ for( ImageMap::Iterator it = d->mImages.begin();
+ it != d->mImages.end();
+ ) {
+ if( !(*it)->thumbnail.isNull()) {
+ ImageMap::Iterator it2 = it;
+ ++it;
+ d->mImages.remove( it2 );
+ } else {
+ ++it;
+ }
+ }
+ d->mThumbnailSize = size;
+ }
+}
+
+#ifdef DEBUG_CACHE
+static KURL _cache_url; // hack only for debugging for item to show also its key
+#endif
+
+void Cache::checkMaxSize() {
+ for(;;) {
+ int size = 0;
+ ImageMap::Iterator max;
+ long long max_cost = -1;
+#ifdef DEBUG_CACHE
+ int with_file = 0;
+ int with_thumb = 0;
+ int with_image = 0;
+#endif
+ for( ImageMap::Iterator it = d->mImages.begin();
+ it != d->mImages.end();
+ ++it ) {
+ size += (*it)->size();
+#ifdef DEBUG_CACHE
+ if( !(*it).file.isNull()) ++with_file;
+ if( !(*it).thumbnail.isNull()) ++with_thumb;
+ if( !(*it).frames.isEmpty()) ++with_image;
+#endif
+ long long cost = (*it)->cost();
+ if( cost > max_cost && ! (*it)->priority ) {
+ max_cost = cost;
+ max = it;
+ }
+ }
+ if( size <= d->mMaxSize || max_cost == -1 ) {
+#if 0
+#ifdef DEBUG_CACHE
+ kdDebug() << "Cache: Statistics (" << d->mImages.size() << "/" << with_file << "/"
+ << with_thumb << "/" << with_image << ")" << endl;
+#endif
+#endif
+ break;
+ }
+#ifdef DEBUG_CACHE
+ _cache_url = max.key();
+#endif
+
+ if( !(*max)->reduceSize() || (*max)->isEmpty()) d->mImages.remove( max );
+ }
+}
+
+void Cache::readConfig(KConfig* config,const QString& group) {
+ KConfigGroupSaver saver( config, group );
+ d->mMaxSize = config->readNumEntry( CONFIG_CACHE_MAXSIZE, d->mMaxSize );
+ checkMaxSize();
+}
+
+
+void ImageData::addFile( const QByteArray& f ) {
+ file = f;
+ file.detach(); // explicit sharing
+ age = 0;
+}
+
+void ImageData::addImage( const ImageFrames& fs, const QCString& f ) {
+ frames = fs;
+ format = f;
+ age = 0;
+}
+
+void ImageData::addThumbnail( const QPixmap& thumb, QSize imgsize ) {
+ thumbnail = thumb;
+ imagesize = imgsize;
+// age = 0;
+}
+
+int ImageData::size() const {
+ return QMAX( fileSize() + imageSize() + thumbnailSize(), 100 ); // some minimal size per item
+}
+
+int ImageData::fileSize() const {
+ return !file.isNull() ? file.size() : 0;
+}
+
+int ImageData::thumbnailSize() const {
+ return !thumbnail.isNull() ? thumbnail.height() * thumbnail.width() * thumbnail.depth() / 8 : 0;
+}
+
+int ImageData::imageSize() const {
+ int ret = 0;
+ for( ImageFrames::ConstIterator it = frames.begin(); it != frames.end(); ++it ) {
+ ret += (*it).image.height() * (*it).image.width() * (*it).image.depth() / 8;
+ }
+ return ret;
+}
+
+bool ImageData::reduceSize() {
+ if( !file.isNull() && fast_url && !frames.isEmpty()) {
+ file = QByteArray();
+#ifdef DEBUG_CACHE
+ kdDebug() << "Cache: Dumping fast file: " << _cache_url.prettyURL() << ":" << cost() << endl;
+#endif
+ return true;
+ }
+ if( !thumbnail.isNull()) {
+#ifdef DEBUG_CACHE
+ kdDebug() << "Cache: Dumping thumbnail: " << _cache_url.prettyURL() << ":" << cost() << endl;
+#endif
+ thumbnail = QPixmap();
+ return true;
+ }
+ if( !file.isNull() && !frames.isEmpty()) {
+ // possibly slow file to fetch - dump the image data unless the image
+ // is JPEG (which needs raw data anyway) or the raw data much larger than the image
+ if( format == "JPEG" || fileSize() < imageSize() / 10 ) {
+ frames.clear();
+#ifdef DEBUG_CACHE
+ kdDebug() << "Cache: Dumping images: " << _cache_url.prettyURL() << ":" << cost() << endl;
+#endif
+ } else {
+ file = QByteArray();
+#ifdef DEBUG_CACHE
+ kdDebug() << "Cache: Dumping file: " << _cache_url.prettyURL() << ":" << cost() << endl;
+#endif
+ }
+ return true;
+ }
+#ifdef DEBUG_CACHE
+ kdDebug() << "Cache: Dumping completely: " << _cache_url.prettyURL() << ":" << cost() << endl;
+#endif
+ return false; // reducing here would mean clearing everything
+}
+
+bool ImageData::isEmpty() const {
+ return file.isNull() && frames.isEmpty() && thumbnail.isNull();
+}
+
+long long ImageData::cost() const {
+ long long s = size();
+ if( fast_url && !file.isNull()) {
+ s *= ( format == "JPEG" ? 10 : 100 ); // heavy penalty for storing local files
+ } else if( !thumbnail.isNull()) {
+ s *= 10 * 10; // thumbnails are small, and try to get rid of them soon
+ }
+ static const int mod[] = { 50, 30, 20, 16, 12, 10 };
+ if( age <= 5 ) {
+ return s * 10 / mod[ age ];
+ } else {
+ return s * ( age - 5 );
+ }
+}
+
+} // namespace
diff --git a/src/gvcore/cache.h b/src/gvcore/cache.h
new file mode 100644
index 0000000..bf81b91
--- /dev/null
+++ b/src/gvcore/cache.h
@@ -0,0 +1,67 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2000-2004 Aurélien Gâteau
+
+ 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.
+
+*/
+#ifndef CACHE_H
+#define CACHE_H
+
+// Qt
+#include <qcstring.h>
+#include <qdatetime.h>
+#include <qimage.h>
+#include <qobject.h>
+#include <qtimer.h>
+#include <qvaluelist.h>
+
+// KDE
+#include <kurl.h>
+
+// Local
+#include "imageframe.h"
+#include "libgwenview_export.h"
+class KConfig;
+
+namespace Gwenview {
+class LIBGWENVIEW_EXPORT Cache : public QObject {
+Q_OBJECT
+public:
+ static Cache* instance();
+ ~Cache();
+ void addImage( const KURL& url, const ImageFrames& frames, const QCString& format, const QDateTime& timestamp );
+ void addFile( const KURL& url, const QByteArray& file, const QDateTime& timestamp );
+ void addThumbnail( const KURL& url, const QPixmap& thumbnail, QSize imagesize, const QDateTime& timestamp );
+ QDateTime timestamp( const KURL& url ) const;
+ QByteArray file( const KURL& url ) const;
+ void getFrames( const KURL& url, ImageFrames* frames, QCString* format ) const;
+ QPixmap thumbnail( const KURL& url, QSize& imagesize ) const;
+ void setPriorityURL( const KURL& url, bool set );
+ void invalidate( const KURL& url );
+ void checkThumbnailSize( int size );
+ void readConfig(KConfig*,const QString& group);
+ void updateAge();
+ enum { DEFAULT_MAXSIZE = 16 * 1024 * 1024 }; // 16MiB
+private:
+ Cache();
+ void checkMaxSize();
+ class Private;
+ Private* d;
+};
+
+} // namespace
+#endif
diff --git a/src/gvcore/captionformatter.cpp b/src/gvcore/captionformatter.cpp
new file mode 100644
index 0000000..4bfac6b
--- /dev/null
+++ b/src/gvcore/captionformatter.cpp
@@ -0,0 +1,57 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab:
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2005 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 "captionformatter.h"
+
+// KDE
+#include <klocale.h>
+
+
+namespace Gwenview {
+
+
+QString CaptionFormatter::format(const QString& format) {
+ QString comment=mComment;
+ if (comment.isNull()) {
+ comment=i18n("(No comment)");
+ }
+
+ QString resolution;
+ if (mImageSize.isValid()) {
+ resolution = QString( "%1x%2" ).arg( mImageSize.width()).arg( mImageSize.height());
+ }
+
+ QString str=format;
+ str.replace("%f", mFileName);
+ str.replace("%p", mPath);
+ str.replace("%c", comment);
+ str.replace("%r", resolution);
+ str.replace("%n", QString::number(mPosition));
+ str.replace("%N", QString::number(mCount));
+ str.replace("%a", mAperture);
+ str.replace("%t", mExposureTime);
+ str.replace("%i", mIso);
+ str.replace("%l", mFocalLength);
+
+ return str;
+}
+
+
+} // namespace
diff --git a/src/gvcore/captionformatter.h b/src/gvcore/captionformatter.h
new file mode 100644
index 0000000..edce26a
--- /dev/null
+++ b/src/gvcore/captionformatter.h
@@ -0,0 +1,57 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab:
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2005 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.
+
+*/
+#ifndef CAPTIONFORMATTER_H
+#define CAPTIONFORMATTER_H
+
+// Qt
+#include <qsize.h>
+#include <qstring.h>
+
+// Local
+#include "libgwenview_export.h"
+
+namespace Gwenview {
+
+
+/**
+ * A class to format image captions. Used for example in fullscreen mode.
+ * All attributes of the class are public because it's just a "record" with a
+ * format() function.
+ */
+class LIBGWENVIEW_EXPORT CaptionFormatter {
+public:
+ QString mPath;
+ QString mFileName;
+ QString mComment;
+ QString mAperture;
+ QString mFocalLength;
+ QString mExposureTime;
+ QString mIso;
+
+ QSize mImageSize;
+ int mPosition;
+ int mCount;
+ QString format(const QString& format);
+};
+
+} // namespace
+
+#endif /* CAPTIONFORMATTER_H */
diff --git a/src/gvcore/clicklineedit.cpp b/src/gvcore/clicklineedit.cpp
new file mode 100644
index 0000000..a1728a0
--- /dev/null
+++ b/src/gvcore/clicklineedit.cpp
@@ -0,0 +1,106 @@
+/*
+ This file is part of libkdepim.
+ Copyright (c) 2004 Daniel Molkentin <molkentin@kde.org>
+ based on code by Cornelius Schumacher <schumacher@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+
+#include "clicklineedit.h"
+
+#include "qpainter.h"
+
+namespace Gwenview {
+
+ClickLineEdit::ClickLineEdit(QWidget *parent, const char* name ) :
+ KLineEdit( parent, name )
+{
+ mDrawClickMsg = true;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC
+/////////////////////////////////////////////////////////////////////////////////////
+
+void ClickLineEdit::setClickMessage( const QString &msg )
+{
+ mClickMessage = msg;
+ repaint();
+}
+
+
+void ClickLineEdit::setText( const QString &txt )
+{
+ mDrawClickMsg = txt.isEmpty();
+ repaint();
+ KLineEdit::setText( txt );
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////
+// PROTECTED
+/////////////////////////////////////////////////////////////////////////////////////
+
+//#include <kiconloader.h>
+void ClickLineEdit::drawContents( QPainter *p )
+{
+ KLineEdit::drawContents( p );
+
+ if ( mDrawClickMsg == true && !hasFocus() ) {
+ QPen tmp = p->pen();
+ p->setPen( palette().color( QPalette::Disabled, QColorGroup::Text ) );
+ QRect cr = contentsRect();
+
+ //p->drawPixmap( 3, 3, SmallIcon("filter") );
+
+ // Add two pixel margin on the left side
+ cr.rLeft() += 3;
+ p->drawText( cr, AlignAuto | AlignVCenter, mClickMessage );
+ p->setPen( tmp );
+ }
+}
+
+void ClickLineEdit::dropEvent( QDropEvent *ev )
+{
+ mDrawClickMsg = false;
+ KLineEdit::dropEvent( ev );
+}
+
+
+void ClickLineEdit::focusInEvent( QFocusEvent *ev )
+{
+ if ( mDrawClickMsg == true ) {
+ mDrawClickMsg = false;
+ repaint();
+ }
+ QLineEdit::focusInEvent( ev );
+}
+
+
+void ClickLineEdit::focusOutEvent( QFocusEvent *ev )
+{
+ if ( text().isEmpty() ) {
+ mDrawClickMsg = true;
+ repaint();
+ }
+ QLineEdit::focusOutEvent( ev );
+}
+} // namespace
+
+#include "clicklineedit.moc"
+
diff --git a/src/gvcore/clicklineedit.h b/src/gvcore/clicklineedit.h
new file mode 100644
index 0000000..05624eb
--- /dev/null
+++ b/src/gvcore/clicklineedit.h
@@ -0,0 +1,63 @@
+/*
+ This file is part of libkdepim.
+ Copyright (c) 2004 Daniel Molkentin <molkentin@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#ifndef CLICKLINEEDIT_H
+#define CLICKLINEEDIT_H
+
+#include <klineedit.h>
+
+namespace Gwenview {
+
+/**
+ This class provides a KLineEdit which contains a greyed-out hinting
+ text as long as the user didn't enter any text
+
+ @short LineEdit with customizable "Click here" text
+ @author Daniel Molkentin
+*/
+class ClickLineEdit : public KLineEdit
+{
+ Q_OBJECT
+ Q_PROPERTY( QString clickMessage READ clickMessage WRITE setClickMessage )
+ public:
+ ClickLineEdit(QWidget *parent, const char* name = 0 );
+
+ void setClickMessage( const QString &msg );
+ QString clickMessage() const { return mClickMessage; }
+
+ virtual void setText( const QString& txt );
+
+ protected:
+ virtual void drawContents( QPainter *p );
+ virtual void dropEvent( QDropEvent *ev );
+ virtual void focusInEvent( QFocusEvent *ev );
+ virtual void focusOutEvent( QFocusEvent *ev );
+
+ private:
+ QString mClickMessage;
+ bool mDrawClickMsg;
+
+};
+
+}
+
+#endif // CLICKLINEEDIT_H
+
+
diff --git a/src/gvcore/cursortracker.cpp b/src/gvcore/cursortracker.cpp
new file mode 100644
index 0000000..cc7ff89
--- /dev/null
+++ b/src/gvcore/cursortracker.cpp
@@ -0,0 +1,86 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab:
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2000-2004 Aur�ien G�eau
+
+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 "cursortracker.h"
+
+// Qt
+#include <qevent.h>
+#include <qtooltip.h>
+
+namespace Gwenview {
+
+CursorTracker::CursorTracker(const QString& txt, QWidget* reference)
+: QLabel(txt, 0, "", WX11BypassWM) {
+ reference->setMouseTracking(true);
+ reference->installEventFilter(this);
+}
+
+
+/**
+ * Overload to make sure the widget size is correct
+ */
+void CursorTracker::setText(const QString& txt) {
+ QLabel::setText(txt);
+ adjustSize();
+}
+
+
+bool CursorTracker::eventFilter(QObject* object, QEvent* _event) {
+ QWidget* widget=static_cast<QWidget*>(object);
+
+ switch (_event->type()) {
+ case QEvent::MouseMove: {
+ QMouseEvent* event=static_cast<QMouseEvent*>(_event);
+ if (widget->rect().contains(event->pos()) || (event->stateAfter() & LeftButton)) {
+ show();
+ move(event->globalPos().x() + 15, event->globalPos().y() + 15);
+ } else {
+ hide();
+ }
+ break;
+ }
+
+ case QEvent::MouseButtonRelease: {
+ QMouseEvent* event=static_cast<QMouseEvent*>(_event);
+ if ( !widget->rect().contains(event->pos()) ) {
+ hide();
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ return false;
+}
+
+
+TipTracker::TipTracker(const QString& txt, QWidget* reference)
+: CursorTracker(txt, reference) {
+ setPalette(QToolTip::palette());
+ setFrameStyle(QFrame::Plain | QFrame::Box);
+ setLineWidth(1);
+ setAlignment(AlignAuto | AlignTop);
+}
+
+
+} // namespace
diff --git a/src/gvcore/cursortracker.h b/src/gvcore/cursortracker.h
new file mode 100644
index 0000000..1dc4003
--- /dev/null
+++ b/src/gvcore/cursortracker.h
@@ -0,0 +1,56 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab:
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2000-2004 Aur�ien G�eau
+
+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.
+
+*/
+
+#ifndef CURSORTRACKER_H
+#define CURSORTRACKER_H
+
+// Qt
+#include <qlabel.h>
+
+namespace Gwenview {
+
+/**
+ * This class implements a decoration-less window which will follow the cursor
+ * when it's over a specified widget.
+ */
+class CursorTracker : public QLabel {
+public:
+ CursorTracker(const QString& txt, QWidget* reference);
+
+ void setText(const QString& txt);
+
+protected:
+ bool eventFilter(QObject*, QEvent*);
+};
+
+
+/**
+ * A specialized CursorTracker class, which looks like a tool tip.
+ */
+class TipTracker : public CursorTracker {
+public:
+ TipTracker(const QString& txt, QWidget* reference);
+};
+
+
+} // namespace
+
+#endif /* CURSORTRACKER_H */
diff --git a/src/gvcore/deletedialog.cpp b/src/gvcore/deletedialog.cpp
new file mode 100644
index 0000000..19c34ba
--- /dev/null
+++ b/src/gvcore/deletedialog.cpp
@@ -0,0 +1,133 @@
+/***************************************************************************
+ begin : Tue Aug 31 21:59:58 EST 2004
+ copyright : (C) 2004 by Michael Pyne <michael.pyne@kdemail.net>
+ (C) 2006 by Ian Monroe <ian@monroe.nu>
+ (C) 2006 by Aurelien Gateau <aurelien.gateau@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. *
+ * *
+ ***************************************************************************/
+
+#include <kconfig.h>
+#include <kdeversion.h>
+#include <kdialogbase.h>
+#include <kglobal.h>
+#include <kiconloader.h>
+#include <kio/job.h>
+#include <klocale.h>
+#include <kstdguiitem.h>
+#include <kurl.h>
+
+#include <qstringlist.h>
+#include <qcheckbox.h>
+#include <qlayout.h>
+#include <qlabel.h>
+#include <qtimer.h>
+#include <qvbox.h>
+#include <qhbox.h>
+#include <qpushbutton.h>
+
+#include "fileoperationconfig.h"
+#include "deletedialog.h"
+#include "deletedialogbase.h"
+
+namespace Gwenview {
+
+DeleteDialog::DeleteDialog(QWidget *parent, const char *name) :
+ KDialogBase(Swallow, WStyle_DialogBorder, parent, name,
+ true /* modal */, i18n("About to delete selected files"),
+ Ok | Cancel, Cancel /* Default */, true /* separator */),
+ m_trashGuiItem(i18n("&Send to Trash"), "trashcan_full")
+{
+ m_widget = new DeleteDialogBase(this, "delete_dialog_widget");
+ setMainWidget(m_widget);
+
+ m_widget->setMinimumSize(400, 300);
+
+ actionButton(Ok)->setFocus();
+
+ bool deleteInstead = ! FileOperationConfig::deleteToTrash();
+ m_widget->ddShouldDelete->setChecked(deleteInstead);
+
+ connect(m_widget->ddShouldDelete, SIGNAL(toggled(bool)), SLOT(updateUI()));
+}
+
+void DeleteDialog::setURLList(const KURL::List &files)
+{
+ m_widget->ddFileList->clear();
+ for( KURL::List::ConstIterator it = files.begin(); it != files.end(); it++) {
+ m_widget->ddFileList->insertItem( (*it).pathOrURL() );
+ }
+ m_widget->ddNumFiles->setText(i18n("<b>1</b> item selected.", "<b>%n</b> items selected.", files.count()));
+ updateUI();
+}
+
+void DeleteDialog::accept()
+{
+ FileOperationConfig::setDeleteToTrash( ! shouldDelete() );
+ FileOperationConfig::writeConfig();
+
+ KDialogBase::accept();
+}
+
+
+void DeleteDialog::updateUI()
+{
+ QString msg, iconName;
+
+ int fileCount = m_widget->ddFileList->count();
+ bool reallyDelete = m_widget->ddShouldDelete->isChecked();
+
+ if(reallyDelete) {
+ msg = i18n(
+ "<qt>This item will be <b>permanently deleted</b> from your hard disk.</qt>",
+ "<qt>These items will be <b>permanently deleted</b> from your hard disk.</qt>",
+ fileCount);
+ iconName = "messagebox_warning";
+ }
+ else {
+ msg = i18n(
+ "<qt>This item will be moved to the trash bin.</qt>",
+ "<qt>These items will be moved to the trash bin.</qt>",
+ fileCount);
+ iconName = "trashcan_full";
+ }
+ QPixmap icon = KGlobal::iconLoader()->loadIcon(iconName, KIcon::NoGroup, KIcon::SizeMedium);
+
+ m_widget->ddDeleteText->setText(msg);
+ m_widget->ddWarningIcon->setPixmap(icon);
+
+ setButtonGuiItem(Ok, reallyDelete ? KStdGuiItem::del() : m_trashGuiItem);
+ adjustSize();
+}
+
+
+bool DeleteDialog::shouldDelete() const {
+ return m_widget->ddShouldDelete->isChecked();
+}
+
+
+QSize DeleteDialog::sizeHint() const {
+ m_widget->adjustSize();
+ QSize hint = m_widget->minimumSize();
+ hint = calculateSize(hint.width(), hint.height());
+
+ // For some reason calculateSize does not return a correct height. As I'm
+ // fed up fighting with it, let's just add a few more pixels.
+ hint.rheight() += 50;
+ return hint;
+}
+
+
+
+} // namespace
+
+#include "deletedialog.moc"
+
+// vim: set et ts=4 sw=4:
diff --git a/src/gvcore/deletedialog.h b/src/gvcore/deletedialog.h
new file mode 100644
index 0000000..0340e09
--- /dev/null
+++ b/src/gvcore/deletedialog.h
@@ -0,0 +1,55 @@
+/***************************************************************************
+ begin : Tue Aug 31 21:54:20 EST 2004
+ copyright : (C) 2004 by Michael Pyne <michael.pyne@kdemail.net>
+ (C) 2006 by Ian Monroe <ian@monroe.nu>
+ (C) 2006 by Aurelien Gateau <aurelien.gateau@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. *
+ * *
+ ***************************************************************************/
+
+#ifndef _DELETEDIALOG_H
+#define _DELETEDIALOG_H
+
+
+#include <kdialogbase.h>
+
+class DeleteDialogBase;
+class KGuiItem;
+
+namespace Gwenview {
+
+class DeleteDialog : public KDialogBase
+{
+ Q_OBJECT
+
+public:
+ DeleteDialog(QWidget *parent, const char *name = "delete_dialog");
+
+ void setURLList(const KURL::List &files);
+ bool shouldDelete() const;
+
+ QSize sizeHint() const;
+
+protected slots:
+ virtual void accept();
+
+private slots:
+ void updateUI();
+
+private:
+ DeleteDialogBase *m_widget;
+ KGuiItem m_trashGuiItem;
+};
+
+} // namespace
+
+#endif
+
+// vim: set et ts=4 sw=4:
diff --git a/src/gvcore/deletedialogbase.ui b/src/gvcore/deletedialogbase.ui
new file mode 100644
index 0000000..dd338ea
--- /dev/null
+++ b/src/gvcore/deletedialogbase.ui
@@ -0,0 +1,111 @@
+<!DOCTYPE UI><UI version="3.2" stdsetdef="1">
+<class>DeleteDialogBase</class>
+<widget class="QWidget">
+ <property name="name">
+ <cstring>DeleteDialogBase</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>542</width>
+ <height>374</height>
+ </rect>
+ </property>
+ <vbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <widget class="QLayoutWidget">
+ <property name="name">
+ <cstring>layout3</cstring>
+ </property>
+ <hbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QLabel">
+ <property name="name">
+ <cstring>ddWarningIcon</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>4</hsizetype>
+ <vsizetype>4</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Icon Placeholder, not in GUI</string>
+ </property>
+ </widget>
+ <widget class="QLabel">
+ <property name="name">
+ <cstring>ddDeleteText</cstring>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>60</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>Deletion method placeholder, not in GUI</string>
+ </property>
+ <property name="alignment">
+ <set>WordBreak|AlignCenter</set>
+ </property>
+ </widget>
+ </hbox>
+ </widget>
+ <widget class="KListBox">
+ <property name="name">
+ <cstring>ddFileList</cstring>
+ </property>
+ <property name="focusPolicy">
+ <enum>NoFocus</enum>
+ </property>
+ <property name="selectionMode">
+ <enum>NoSelection</enum>
+ </property>
+ </widget>
+ <widget class="QLabel">
+ <property name="name">
+ <cstring>ddNumFiles</cstring>
+ </property>
+ <property name="text">
+ <string>Placeholder for number of files, not in GUI</string>
+ </property>
+ <property name="alignment">
+ <set>AlignVCenter|AlignRight</set>
+ </property>
+ </widget>
+ <widget class="QCheckBox">
+ <property name="name">
+ <cstring>ddShouldDelete</cstring>
+ </property>
+ <property name="text">
+ <string>&amp;Delete items instead of moving them to the trash</string>
+ </property>
+ <property name="toolTip" stdset="0">
+ <string>If checked, items will be permanently removed instead of being placed in the trash bin</string>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>&lt;qt&gt;&lt;p&gt;If this box is checked, items will be &lt;b&gt;permanently removed&lt;/b&gt; instead of being placed in the trash bin.&lt;/p&gt;
+
+&lt;p&gt;&lt;em&gt;Use this option with caution&lt;/em&gt;: Most filesystems are unable to reliably undelete deleted files.&lt;/p&gt;&lt;/qt&gt;</string>
+ </property>
+ </widget>
+ </vbox>
+</widget>
+<customwidgets>
+</customwidgets>
+<layoutdefaults spacing="6" margin="11"/>
+<includehints>
+ <includehint>klistbox.h</includehint>
+</includehints>
+</UI>
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
diff --git a/src/gvcore/document.h b/src/gvcore/document.h
new file mode 100644
index 0000000..98b14ca
--- /dev/null
+++ b/src/gvcore/document.h
@@ -0,0 +1,194 @@
+// 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.
+
+*/
+#ifndef DOCUMENT_H
+#define DOCUMENT_H
+
+// Qt
+#include <qcstring.h>
+#include <qobject.h>
+#include <qimage.h>
+
+// KDE
+#include <kurl.h>
+#include <kprinter.h>
+
+// Local
+#include "imageutils/orientation.h"
+#include "mimetypeutils.h"
+#include "libgwenview_export.h"
+namespace KIO { class Job; }
+
+namespace Gwenview {
+class DocumentPrivate;
+class DocumentImpl;
+
+/**
+ * The application document.
+ * It knows what the current url is and will emit signals when
+ * loading/loaded/modified...
+ *
+ * The ordering of loading() and loaded() signals is:
+ * - setURL() is called
+ * - URL is stated
+ * - loading() is emitted (may be skipped if no loading is needed, e.g. wrong URL)
+ * - image is being loaded
+ * - loaded() is emitted
+ */
+class LIBGWENVIEW_EXPORT Document : public QObject {
+Q_OBJECT
+public:
+ enum CommentState { NONE=0, READ_ONLY=1, WRITABLE=2 };
+
+ Document(QObject*);
+ ~Document();
+
+ // Properties
+ const QImage& image() const;
+ KURL url() const;
+ KURL dirURL() const;
+ QString filename() const;
+ const QCString& imageFormat() const;
+ int fileSize() const;
+ QString mimeType() const;
+ MimeTypeUtils::Kind urlKind() const;
+ bool isModified() const;
+
+ /**
+ * Returns true if Gwenview knows how to save such an image
+ */
+ bool canBeSaved() const;
+
+ // Convenience methods
+ bool isNull() const { return image().isNull(); }
+ int width() const { return image().width(); }
+ int height() const { return image().height(); }
+
+ Document::CommentState commentState() const;
+ QString comment() const;
+ void setComment(const QString&);
+ QString aperture() const;
+ QString exposureTime() const;
+ QString iso() const;
+ QString focalLength() const;
+
+ int duration() const;
+
+public slots:
+ void setURL(const KURL&);
+ void setDirURL(const KURL&);
+ void reload();
+
+ /**
+ * Save to the current file.
+ */
+ void save();
+ void saveAs();
+
+ /** print the selected file */
+ void print(KPrinter *pPrinter);
+
+ /**
+ * If the image has been modified, prompt the user to save the changes.
+ */
+ void saveBeforeClosing();
+
+ // "Image manipulation"
+ void transform(ImageUtils::Orientation);
+
+signals:
+ /**
+ * Emitted when the class starts to load the image.
+ */
+ void loading();
+
+ /**
+ * Emitted when the class has finished loading the image.
+ * Also emitted if the image could not be loaded.
+ */
+ void loaded(const KURL& url);
+
+ /**
+ * Emitted when the image has been modified.
+ */
+ void modified();
+
+ /**
+ * Emitted when the image has been saved on disk.
+ */
+ void saved(const KURL& url);
+
+ /**
+ * Emitted when the image has been reloaded.
+ */
+ void reloaded(const KURL& url);
+
+ /**
+ * Emitted to show a part of the image must be refreshed
+ */
+ void rectUpdated(const QRect& rect);
+
+ /**
+ * Emitted when the size is known
+ */
+ void sizeUpdated();
+
+ /**
+ * Emitted when something goes wrong, like when save fails
+ */
+ void errorHappened(const QString& message);
+
+private slots:
+ void slotStatResult(KIO::Job*);
+ void slotFinished(bool success);
+ void slotLoading();
+ void slotLoaded();
+
+private:
+ friend class DocumentImpl;
+ friend class DocumentPrivate;
+
+ DocumentPrivate* d;
+
+ // These methods are used by DocumentImpl and derived
+ void switchToImpl(DocumentImpl*);
+ void setImage(QImage);
+ void setImageFormat(const QCString&);
+ void setMimeType(const QString&);
+ void setFileSize(int);
+
+ void reset();
+ void load();
+ void doPaint(KPrinter *pPrinter, QPainter *p);
+
+ /**
+ * The returned string is null if the image was successfully saved,
+ * otherwise it's the translated error message.
+ */
+ QString saveInternal(const KURL& url, const QCString& format);
+
+ Document(const Document&);
+ Document &operator=(const Document&);
+};
+
+
+} // namespace
+#endif
+
diff --git a/src/gvcore/documentanimatedloadedimpl.cpp b/src/gvcore/documentanimatedloadedimpl.cpp
new file mode 100644
index 0000000..a6cabb1
--- /dev/null
+++ b/src/gvcore/documentanimatedloadedimpl.cpp
@@ -0,0 +1,97 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2000-2004 Aurélien Gâteau
+
+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 "documentanimatedloadedimpl.moc"
+
+// Qt
+#include <qstring.h>
+#include <qtimer.h>
+
+// KDE
+#include <kdebug.h>
+#include <klocale.h>
+namespace Gwenview {
+
+// Local
+
+#undef ENABLE_LOG
+#undef LOG
+//#define ENABLE_LOG
+#ifdef ENABLE_LOG
+#define LOG(x) kdDebug() << k_funcinfo << x << endl
+#else
+#define LOG(x) ;
+#endif
+
+class DocumentAnimatedLoadedImplPrivate {
+public:
+ ImageFrames mFrames;
+ int mCurrentFrame;
+ QTimer mFrameTimer;
+};
+
+
+DocumentAnimatedLoadedImpl::DocumentAnimatedLoadedImpl(Document* document, const ImageFrames& frames)
+: DocumentLoadedImpl(document) {
+ LOG("" << mDocument->url().prettyURL() << ", frames: " << frames.count() );
+ d=new DocumentAnimatedLoadedImplPrivate;
+ d->mFrames = frames;
+ d->mCurrentFrame = -1;
+ connect( &d->mFrameTimer, SIGNAL( timeout()), SLOT( nextFrame()));
+}
+
+void DocumentAnimatedLoadedImpl::init() {
+ DocumentLoadedImpl::init();
+ nextFrame();
+}
+
+void DocumentAnimatedLoadedImpl::nextFrame() {
+ ++d->mCurrentFrame;
+ if( d->mCurrentFrame == int( d->mFrames.count())) d->mCurrentFrame = 0;
+ d->mFrameTimer.start( QMAX( 10, d->mFrames[ d->mCurrentFrame ].delay ));
+// NOTE! If this ever gets changed to already animate the picture while it's still
+// loading, with MNG the frame delay gets announced only after the frame is ready.
+// See ImageLoader::frameDone() .
+ LOG("" << d->mCurrentFrame );
+
+ setImage(d->mFrames[ d->mCurrentFrame ].image);
+ emitImageRectUpdated();
+}
+
+DocumentAnimatedLoadedImpl::~DocumentAnimatedLoadedImpl() {
+ delete d;
+}
+
+
+void DocumentAnimatedLoadedImpl::transform(ImageUtils::Orientation orientation) {
+ for( ImageFrames::Iterator it = d->mFrames.begin(); it != d->mFrames.end(); ++it ) {
+ (*it).image = ImageUtils::transform( (*it).image, orientation );
+ }
+ setImage( d->mFrames[ d->mCurrentFrame ].image);
+ emitImageRectUpdated();
+}
+
+
+QString DocumentAnimatedLoadedImpl::localSave(QFile* /*file*/, const QCString& /*format*/) const {
+ return i18n("Sorry, cannot save animated images.");
+}
+
+} // namespace
diff --git a/src/gvcore/documentanimatedloadedimpl.h b/src/gvcore/documentanimatedloadedimpl.h
new file mode 100644
index 0000000..b9266b3
--- /dev/null
+++ b/src/gvcore/documentanimatedloadedimpl.h
@@ -0,0 +1,62 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2000-2004 Aurélien Gâteau
+
+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.
+
+*/
+#ifndef DOCUMENTANIMATEDIMPL_H
+#define DOCUMENTANIMATEDIMPL_H
+
+// Qt
+#include <qimage.h>
+#include <qvaluevector.h>
+
+// Local
+#include "documentloadedimpl.h"
+#include "imageutils/imageutils.h"
+#include "imageframe.h"
+
+class QFile;
+class QCString;
+
+namespace Gwenview {
+class Document;
+
+class DocumentAnimatedLoadedImplPrivate;
+
+class DocumentAnimatedLoadedImpl : public DocumentLoadedImpl {
+Q_OBJECT
+public:
+ DocumentAnimatedLoadedImpl(Document* document, const ImageFrames& frames);
+ ~DocumentAnimatedLoadedImpl();
+ void init();
+
+ void transform(ImageUtils::Orientation);
+ virtual bool canBeSaved() const { return false; }
+
+protected:
+ QString localSave(QFile*, const QCString& format) const;
+
+private slots:
+ void nextFrame();
+private:
+ DocumentAnimatedLoadedImplPrivate* d;
+};
+
+} // namespace
+#endif /* DOCUMENTANIMATEDIMPL_H */
+
diff --git a/src/gvcore/documentimpl.cpp b/src/gvcore/documentimpl.cpp
new file mode 100644
index 0000000..3221229
--- /dev/null
+++ b/src/gvcore/documentimpl.cpp
@@ -0,0 +1,103 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2000-2004 Aurélien Gâteau
+
+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.
+
+*/
+// KDE
+#include <klocale.h>
+
+// Local
+#include "document.h"
+#include "documentimpl.moc"
+namespace Gwenview {
+
+DocumentImpl::DocumentImpl(Document* document)
+: mDocument(document) {}
+
+void DocumentImpl::init() {}
+
+DocumentImpl::~DocumentImpl() {}
+
+void DocumentImpl::switchToImpl(DocumentImpl* impl) {
+ mDocument->switchToImpl(impl);
+}
+
+void DocumentImpl::setImage(QImage img) {
+ if (img.depth() == 1) {
+ // 1 bit depth images are difficult to scale. Let's convert to 8 bit
+ // depth. See bug #155518.
+ img = img.convertDepth(8);
+ }
+ mDocument->setImage(img);
+}
+
+void DocumentImpl::emitImageRectUpdated() {
+ emit rectUpdated(mDocument->image().rect());
+}
+
+void DocumentImpl::setImageFormat(const QCString& format) {
+ mDocument->setImageFormat(format);
+}
+
+void DocumentImpl::setMimeType(const QString& mimeType) {
+ mDocument->setMimeType(mimeType);
+}
+
+void DocumentImpl::setFileSize(int size) const {
+ mDocument->setFileSize(size);
+}
+
+QString DocumentImpl::aperture() const {
+ return QString::null;
+}
+
+QString DocumentImpl::exposureTime() const {
+ return QString::null;
+}
+
+QString DocumentImpl::iso() const {
+ return QString::null;
+}
+
+QString DocumentImpl::focalLength() const {
+ return QString::null;
+}
+
+QString DocumentImpl::comment() const {
+ return QString::null;
+}
+
+Document::CommentState DocumentImpl::commentState() const {
+ return Document::NONE;
+}
+
+void DocumentImpl::setComment(const QString&) {
+}
+
+int DocumentImpl::duration() const {
+ return 0;
+}
+
+void DocumentImpl::transform(ImageUtils::Orientation) {
+}
+
+QString DocumentImpl::save(const KURL&, const QCString&) const {
+ return i18n("No document to save");
+}
+
+} // namespace
diff --git a/src/gvcore/documentimpl.h b/src/gvcore/documentimpl.h
new file mode 100644
index 0000000..d5ca875
--- /dev/null
+++ b/src/gvcore/documentimpl.h
@@ -0,0 +1,103 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2000-2004 Aurélien Gâteau
+
+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.
+
+*/
+#ifndef DOCUMENTIMPL_H
+#define DOCUMENTIMPL_H
+
+// Qt
+#include <qobject.h>
+#include <qrect.h>
+
+// Local
+#include "document.h"
+#include "imageutils/orientation.h"
+namespace Gwenview {
+
+
+class DocumentImpl : public QObject {
+Q_OBJECT
+public:
+ DocumentImpl(Document* document);
+ virtual ~DocumentImpl();
+ /**
+ * This method is called by Document::switchToImpl after it has connect
+ * signals to the object
+ */
+ virtual void init();
+
+ void switchToImpl(DocumentImpl*);
+ void setImage(QImage);
+ void setMimeType(const QString&);
+ void setImageFormat(const QCString&);
+ void setFileSize(int) const;
+
+ /**
+ * Convenience method to emit rectUpdated with the whole image rect
+ */
+ void emitImageRectUpdated();
+
+ virtual QString aperture() const;
+ virtual QString exposureTime() const;
+ virtual QString iso() const;
+ virtual QString focalLength() const;
+
+ virtual QString comment() const;
+ virtual Document::CommentState commentState() const;
+ virtual void setComment(const QString&);
+ virtual int duration() const;
+
+ virtual void transform(ImageUtils::Orientation);
+ virtual QString save(const KURL&, const QCString& format) const;
+
+ virtual MimeTypeUtils::Kind urlKind() const=0;
+
+ virtual bool canBeSaved() const=0;
+
+
+signals:
+ void finished(bool success);
+ void sizeUpdated();
+ void rectUpdated(const QRect&);
+
+protected:
+ Document* mDocument;
+};
+
+class DocumentEmptyImpl : public DocumentImpl {
+public:
+ DocumentEmptyImpl(Document* document)
+ : DocumentImpl(document) {
+ setImage(QImage());
+ setImageFormat(0);
+ setMimeType("application/x-zerosize");
+ }
+
+ MimeTypeUtils::Kind urlKind() const {
+ return MimeTypeUtils::KIND_UNKNOWN;
+ }
+
+ bool canBeSaved() const {
+ return false;
+ }
+};
+
+} // namespace
+#endif /* DOCUMENTIMPL_H */
+
diff --git a/src/gvcore/documentjpegloadedimpl.cpp b/src/gvcore/documentjpegloadedimpl.cpp
new file mode 100644
index 0000000..2f3250b
--- /dev/null
+++ b/src/gvcore/documentjpegloadedimpl.cpp
@@ -0,0 +1,143 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2000-2004 Aurélien Gâteau
+
+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.
+
+*/
+// Qt
+#include <qcstring.h>
+#include <qfile.h>
+#include <qfileinfo.h>
+#include <qtimer.h>
+
+// KDE
+#include <kdebug.h>
+#include <kio/netaccess.h>
+#include <klocale.h>
+
+// Local
+#include "miscconfig.h"
+#include "imageutils/jpegcontent.h"
+#include "imageutils/imageutils.h"
+#include "documentjpegloadedimpl.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
+
+
+class DocumentJPEGLoadedImplPrivate {
+public:
+ ImageUtils::JPEGContent mJPEGContent;
+
+};
+
+
+DocumentJPEGLoadedImpl::DocumentJPEGLoadedImpl(Document* document, const QByteArray& rawData)
+: DocumentLoadedImpl(document) {
+ LOG("" << mDocument->url().prettyURL() << ", data size: " << rawData.size() );
+ d=new DocumentJPEGLoadedImplPrivate;
+ d->mJPEGContent.loadFromData(rawData);
+}
+
+
+void DocumentJPEGLoadedImpl::init() {
+ LOG("");
+ ImageUtils::Orientation orientation=d->mJPEGContent.orientation();
+
+ if (MiscConfig::autoRotateImages()
+ && orientation!=ImageUtils::NOT_AVAILABLE
+ && orientation!=ImageUtils::NORMAL)
+ {
+ d->mJPEGContent.transform(orientation);
+ }
+
+ DocumentLoadedImpl::init();
+}
+
+
+DocumentJPEGLoadedImpl::~DocumentJPEGLoadedImpl() {
+ delete d;
+}
+
+
+void DocumentJPEGLoadedImpl::transform(ImageUtils::Orientation orientation) {
+ d->mJPEGContent.transform(orientation);
+ setImage(ImageUtils::transform(mDocument->image(), orientation));
+ emitImageRectUpdated();
+}
+
+
+QString DocumentJPEGLoadedImpl::localSave(QFile* file, const QCString& format) const {
+ if (qstrcmp(format, "JPEG")==0) {
+ LOG("JPEG Reset orientation");
+ d->mJPEGContent.resetOrientation();
+ if (!d->mJPEGContent.thumbnail().isNull()) {
+ d->mJPEGContent.setThumbnail( ImageUtils::scale(
+ mDocument->image(), 128, 128, ImageUtils::SMOOTH_FAST, QImage::ScaleMin));
+ }
+
+ LOG("JPEG Lossless save");
+ if (!d->mJPEGContent.save(file)) {
+ return i18n("Could not save this JPEG file.");
+ }
+ } else {
+ QString msg=DocumentLoadedImpl::localSave(file, format);
+ if (!msg.isNull()) return msg;
+ }
+
+ return QString::null;
+}
+
+
+QString DocumentJPEGLoadedImpl::comment() const {
+ return d->mJPEGContent.comment();
+}
+
+void DocumentJPEGLoadedImpl::setComment(const QString& comment) {
+ d->mJPEGContent.setComment(comment);
+}
+
+QString DocumentJPEGLoadedImpl::aperture() const {
+ return d->mJPEGContent.aperture();
+}
+
+QString DocumentJPEGLoadedImpl::exposureTime() const {
+ return d->mJPEGContent.exposureTime();
+}
+
+QString DocumentJPEGLoadedImpl::iso() const {
+ return d->mJPEGContent.iso();
+}
+
+QString DocumentJPEGLoadedImpl::focalLength() const {
+ return d->mJPEGContent.focalLength();
+}
+
+Document::CommentState DocumentJPEGLoadedImpl::commentState() const {
+ return Document::WRITABLE;
+}
+
+
+} // namespace
diff --git a/src/gvcore/documentjpegloadedimpl.h b/src/gvcore/documentjpegloadedimpl.h
new file mode 100644
index 0000000..abb1e81
--- /dev/null
+++ b/src/gvcore/documentjpegloadedimpl.h
@@ -0,0 +1,62 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2000-2004 Aurélien Gâteau
+
+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.
+
+*/
+#ifndef DOCUMENTJPEGLOADEDIMPL_H
+#define DOCUMENTJPEGLOADEDIMPL_H
+
+// Qt
+#include <qimage.h>
+
+// Local
+#include "documentloadedimpl.h"
+namespace Gwenview {
+
+class Document;
+
+class DocumentJPEGLoadedImplPrivate;
+
+class DocumentJPEGLoadedImpl : public DocumentLoadedImpl {
+Q_OBJECT
+public:
+ DocumentJPEGLoadedImpl(Document* document, const QByteArray& rawData);
+ ~DocumentJPEGLoadedImpl();
+ void init();
+
+ QString comment() const;
+ void setComment(const QString&);
+ Document::CommentState commentState() const;
+
+ QString aperture() const;
+ QString exposureTime() const;
+ QString iso() const;
+ QString focalLength() const;
+
+ void transform(ImageUtils::Orientation);
+
+protected:
+ QString localSave(QFile*, const QCString& format) const;
+
+private:
+ DocumentJPEGLoadedImplPrivate* d;
+};
+
+} // namespace
+#endif /* DOCUMENTJPEGLOADEDIMPL_H */
+
diff --git a/src/gvcore/documentloadedimpl.cpp b/src/gvcore/documentloadedimpl.cpp
new file mode 100644
index 0000000..1d9456a
--- /dev/null
+++ b/src/gvcore/documentloadedimpl.cpp
@@ -0,0 +1,198 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab:
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2000-2004 Aurélien Gâteau
+
+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 "documentloadedimpl.moc"
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+// Qt
+#include <qdir.h>
+#include <qfileinfo.h>
+#include <qtimer.h>
+
+// KDE
+#include <kapplication.h>
+#include <kdebug.h>
+#include <kio/netaccess.h>
+#include <klargefile.h>
+#include <klocale.h>
+#include <ktempfile.h>
+
+// Local
+#include "imageutils/imageutils.h"
+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
+
+
+class DocumentLoadedImplPrivate {
+ int mSize;
+ QDateTime mModified;
+};
+
+DocumentLoadedImpl::DocumentLoadedImpl(Document* document)
+: DocumentImpl(document) {
+ LOG("");
+}
+
+
+void DocumentLoadedImpl::init() {
+ emit finished(true);
+}
+
+
+DocumentLoadedImpl::~DocumentLoadedImpl() {
+}
+
+
+void DocumentLoadedImpl::transform(ImageUtils::Orientation orientation) {
+ setImage(ImageUtils::transform(mDocument->image(), orientation));
+ emitImageRectUpdated();
+}
+
+
+QString DocumentLoadedImpl::save(const KURL& _url, const QCString& format) const {
+ if (!QImageIO::outputFormats().contains(format)) {
+ return i18n("Gwenview cannot write files in this format.");
+ }
+
+ QString msg;
+ KURL url(_url);
+
+ // Use the umask to determine default mode (will be used if the dest file
+ // does not exist)
+ int _umask=umask(0);
+ umask(_umask);
+ mode_t mode=0666 & ~_umask;
+
+ if (url.isLocalFile()) {
+ // If the file is a link, dereference it but take care of circular
+ // links
+ QFileInfo info(url.path());
+ if (info.isSymLink()) {
+ QStringList links;
+ while (info.isSymLink()) {
+ links.append(info.filePath());
+ QString path=info.readLink();
+ if (path[0]!='/') {
+ path=info.dirPath(true) + '/' + path;
+ }
+ path=QDir::cleanDirPath(path);
+ if (links.contains(path)) {
+ return i18n("This is a circular link.");
+ }
+ info.setFile(path);
+ }
+ url.setPath(info.filePath());
+ }
+
+
+ // Make some quick tests on the file if it is local
+ if (info.exists() && ! info.isWritable()) {
+ return i18n("This file is read-only.");
+ }
+
+ if (info.exists()) {
+ // Get current file mode
+ KDE_struct_stat st;
+ if (KDE_stat(QFile::encodeName(info.filePath()), &st)==0) {
+ mode=st.st_mode & 07777;
+ } else {
+ // This should not happen
+ kdWarning() << "Could not stat " << info.filePath() << endl;
+ }
+
+ } else {
+ QFileInfo parent=QFileInfo(info.dirPath());
+ if (!parent.isWritable()) {
+ return
+ i18n("The %1 folder is read-only.")
+ .arg(parent.filePath());
+ }
+ }
+ }
+
+ // Save the file to a tmp file
+ QString prefix;
+ if (url.isLocalFile()) {
+ // We set the prefix to url.path() so that the temp file is on the
+ // same partition as the destination file. If we don't do this, rename
+ // will fail
+ prefix=url.path();
+ }
+ KTempFile tmp(prefix, "gwenview", mode);
+ tmp.setAutoDelete(true);
+ if (tmp.status()!=0) {
+ QString reason( strerror(tmp.status()) );
+ return i18n("Could not create a temporary file.\nReason: %1.")
+ .arg(reason);
+ }
+ QFile* file=tmp.file();
+ msg=localSave(file, format);
+ if (!msg.isNull()) return msg;
+ file->close();
+
+ if (tmp.status()!=0) {
+ QString reason( strerror(tmp.status()) );
+ return i18n("Saving image to a temporary file failed.\nReason: %1.")
+ .arg(reason);
+ }
+
+ QString tmpName=tmp.name();
+ int tmpSize=QFileInfo(tmpName).size();
+ setFileSize(tmpSize);
+
+ // Move the tmp file to the final dest
+ if (url.isLocalFile()) {
+ if( ::rename( QFile::encodeName(tmpName), QFile::encodeName( url.path())) < 0 ) {
+ return i18n("Could not write to %1.").arg(url.path());
+ }
+ } else {
+ if (!KIO::NetAccess::upload(tmp.name(), url, KApplication::kApplication()->mainWidget() )) {
+ return i18n("Could not upload the file to %1.").arg(url.prettyURL());
+ }
+ }
+
+ return QString::null;
+}
+
+
+QString DocumentLoadedImpl::localSave(QFile* file, const QCString& format) const {
+ QImageIO iio(file, format);
+ iio.setImage(mDocument->image());
+ if (!iio.write()) {
+ return
+ i18n("An error happened while saving.");
+ }
+ return QString::null;
+}
+
+
+} // namespace
diff --git a/src/gvcore/documentloadedimpl.h b/src/gvcore/documentloadedimpl.h
new file mode 100644
index 0000000..27a8ea7
--- /dev/null
+++ b/src/gvcore/documentloadedimpl.h
@@ -0,0 +1,54 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2000-2004 Aurélien Gâteau
+
+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.
+
+*/
+#ifndef DOCUMENTLOADEDIMPL_H
+#define DOCUMENTLOADEDIMPL_H
+
+// Qt
+#include <qimage.h>
+
+// Local
+#include "documentimpl.h"
+
+class QFile;
+
+namespace Gwenview {
+class Document;
+
+class DocumentLoadedImpl : public DocumentImpl {
+Q_OBJECT
+public:
+ DocumentLoadedImpl(Document* document);
+ void init();
+ ~DocumentLoadedImpl();
+
+ void transform(ImageUtils::Orientation);
+ QString save(const KURL&, const QCString& format) const;
+
+ virtual MimeTypeUtils::Kind urlKind() const { return MimeTypeUtils::KIND_RASTER_IMAGE; }
+ virtual bool canBeSaved() const { return true; }
+
+protected:
+ virtual QString localSave(QFile* file, const QCString& format) const;
+};
+
+} // namespace
+#endif /* DOCUMENTLOADEDIMPL_H */
+
diff --git a/src/gvcore/documentloadingimpl.cpp b/src/gvcore/documentloadingimpl.cpp
new file mode 100644
index 0000000..e148543
--- /dev/null
+++ b/src/gvcore/documentloadingimpl.cpp
@@ -0,0 +1,165 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2000-2004 Aurélien Gâteau
+
+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 "documentloadingimpl.moc"
+
+// Qt
+
+// KDE
+#include <kmimetype.h>
+
+// Local
+#include "imageloader.h"
+#include "documentotherloadedimpl.h"
+#include "documentanimatedloadedimpl.h"
+#include "documentloadedimpl.h"
+#include "documentjpegloadedimpl.h"
+#include "mimetypeutils.h"
+
+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
+
+//---------------------------------------------------------------------
+//
+// DocumentLoadingImplPrivate
+//
+//---------------------------------------------------------------------
+
+class DocumentLoadingImplPrivate {
+public:
+ DocumentLoadingImplPrivate()
+ : mLoader( NULL )
+ {}
+
+ ImageLoader* mLoader;
+};
+
+//---------------------------------------------------------------------
+//
+// DocumentLoadingImpl
+//
+//---------------------------------------------------------------------
+DocumentLoadingImpl::DocumentLoadingImpl(Document* document)
+: DocumentImpl(document) {
+ LOG("");
+ d=new DocumentLoadingImplPrivate;
+}
+
+
+DocumentLoadingImpl::~DocumentLoadingImpl() {
+ LOG("");
+ delete d;
+}
+
+
+void DocumentLoadingImpl::init() {
+ LOG("");
+ d->mLoader = ImageLoader::loader( mDocument->url(), this, BUSY_LOADING );
+ if (d->mLoader->urlKind()==MimeTypeUtils::KIND_FILE) {
+ LOG("urlKind already determined");
+ switchToImpl(new DocumentOtherLoadedImpl(mDocument));
+ return;
+ }
+ connect( d->mLoader, SIGNAL( urlKindDetermined()), SLOT( slotURLKindDetermined() ));
+ connect( d->mLoader, SIGNAL( sizeLoaded( int, int )), SLOT( sizeLoaded( int, int )));
+ connect( d->mLoader, SIGNAL( imageChanged( const QRect& )), SLOT( imageChanged( const QRect& )));
+ connect( d->mLoader, SIGNAL( imageLoaded( bool )), SLOT( imageLoaded( bool )));
+
+ // it's possible the loader already has the whole or at least part of the image loaded
+ QImage image = d->mLoader->processedImage();
+ if (!image.isNull()) {
+ if( d->mLoader->frames().count() > 0 ) {
+ setImage( d->mLoader->frames().first().image);
+ emitImageRectUpdated();
+ } else {
+ setImage(image);
+ QMemArray< QRect > rects = d->mLoader->loadedRegion().rects();
+ for( unsigned int i = 0; i < rects.count(); ++i ) {
+ emit rectUpdated(rects[i]);
+ }
+ }
+ }
+ if( d->mLoader->completed()) imageLoaded( d->mLoader->frames().count() != 0 );
+ // 'this' may be deleted here
+}
+
+
+void DocumentLoadingImpl::slotURLKindDetermined() {
+ LOG("");
+ if (d->mLoader->urlKind()==MimeTypeUtils::KIND_FILE) {
+ switchToImpl(new DocumentOtherLoadedImpl(mDocument));
+ }
+}
+
+
+void DocumentLoadingImpl::imageLoaded( bool ok ) {
+ LOG("");
+
+ QCString format = d->mLoader->imageFormat();
+ if ( !ok || format.isEmpty()) {
+ // Unknown format, no need to go further
+ emit finished(false);
+ switchToImpl(new DocumentEmptyImpl(mDocument));
+ return;
+ }
+ setImageFormat( format );
+ setMimeType(d->mLoader->mimeType());
+
+ // Update file info
+ setFileSize(d->mLoader->rawData().size());
+
+ // Now we switch to a loaded implementation
+ if ( d->mLoader->frames().count() > 1 ) {
+ switchToImpl( new DocumentAnimatedLoadedImpl(mDocument, d->mLoader->frames()));
+ } else if ( format == "JPEG" ) {
+ switchToImpl( new DocumentJPEGLoadedImpl(mDocument, d->mLoader->rawData()) );
+ } else {
+ switchToImpl(new DocumentLoadedImpl(mDocument));
+ }
+}
+
+
+void DocumentLoadingImpl::imageChanged(const QRect& rect) {
+ LOG(rect);
+ setImage(d->mLoader->processedImage());
+ emit rectUpdated(rect);
+}
+
+
+void DocumentLoadingImpl::sizeLoaded(int width, int height) {
+ LOG(width << "x" << height);
+ // Silence compiler
+ width=width;
+ height=height;
+
+ setImage(d->mLoader->processedImage());
+ emit sizeUpdated();
+}
+
+} // namespace
diff --git a/src/gvcore/documentloadingimpl.h b/src/gvcore/documentloadingimpl.h
new file mode 100644
index 0000000..a17b25d
--- /dev/null
+++ b/src/gvcore/documentloadingimpl.h
@@ -0,0 +1,55 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2000-2004 Aurélien Gâteau
+
+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.
+
+*/
+#ifndef DOCUMENTLOADINGIMPL_H
+#define DOCUMENTLOADINGIMPL_H
+
+// Local
+#include "documentimpl.h"
+#include "mimetypeutils.h"
+
+namespace Gwenview {
+
+class Document;
+
+class DocumentLoadingImplPrivate;
+
+class DocumentLoadingImpl : public DocumentImpl {
+Q_OBJECT
+public:
+ DocumentLoadingImpl(Document* document);
+ ~DocumentLoadingImpl();
+ virtual void init();
+ virtual MimeTypeUtils::Kind urlKind() const { return MimeTypeUtils::KIND_RASTER_IMAGE; }
+ virtual bool canBeSaved() const { return false; }
+
+private:
+ DocumentLoadingImplPrivate* d;
+
+private slots:
+ void slotURLKindDetermined();
+ void sizeLoaded(int, int);
+ void imageChanged(const QRect&);
+ void imageLoaded( bool ok );
+};
+
+} // namespace
+#endif /* DOCUMENTLOADINGIMPL_H */
+
diff --git a/src/gvcore/documentotherloadedimpl.cpp b/src/gvcore/documentotherloadedimpl.cpp
new file mode 100644
index 0000000..1e3674b
--- /dev/null
+++ b/src/gvcore/documentotherloadedimpl.cpp
@@ -0,0 +1,58 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 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.
+
+*/
+// Self
+#include "documentotherloadedimpl.h"
+
+// KDE
+#include <kdebug.h>
+#include <kfilemetainfo.h>
+
+#undef ENABLE_LOG
+#undef LOG
+//#define ENABLE_LOG
+#ifdef ENABLE_LOG
+#define LOG(x) kdDebug() << k_funcinfo << x << endl
+#else
+#define LOG(x) ;
+#endif
+
+namespace Gwenview {
+
+int DocumentOtherLoadedImpl::duration() const {
+ KFileMetaInfo fmi(mDocument->url());
+ if (!fmi.isValid()) {
+ LOG("No meta info available for " << mDocument->url());
+ return 0;
+ }
+
+ KFileMetaInfoItem item=fmi.item("Length");
+ if (!item.isValid()) {
+ kdWarning() << "Can't adjust slideshow time: meta info for " << mDocument->url() << " does not contain 'Length' information.";
+ return 0;
+ }
+
+ int length = item.value().toInt();
+ LOG("Length for " << mDocument->url() << " is " << length);
+
+ return length;
+}
+
+} // namespace
diff --git a/src/gvcore/documentotherloadedimpl.h b/src/gvcore/documentotherloadedimpl.h
new file mode 100644
index 0000000..d9ffbdb
--- /dev/null
+++ b/src/gvcore/documentotherloadedimpl.h
@@ -0,0 +1,52 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 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.
+
+*/
+#ifndef DOCUMENTOTHERLOADEDIMPL_H
+#define DOCUMENTOTHERLOADEDIMPL_H
+
+// Local
+#include "document.h"
+#include "documentimpl.h"
+
+namespace Gwenview {
+class Document;
+
+class DocumentOtherLoadedImpl : public DocumentImpl {
+public:
+ DocumentOtherLoadedImpl(Document* document)
+ : DocumentImpl(document) {
+ setImage(QImage());
+ setImageFormat(0);
+ }
+
+ void init() {
+ emit finished(true);
+ }
+
+ virtual MimeTypeUtils::Kind urlKind() const { return MimeTypeUtils::KIND_FILE; }
+
+ virtual bool canBeSaved() const { return false; }
+
+ virtual int duration() const;
+};
+
+} // namespace
+#endif /* DOCUMENTOTHERLOADEDIMPL_H */
+
diff --git a/src/gvcore/dragpixmapgenerator.h b/src/gvcore/dragpixmapgenerator.h
new file mode 100644
index 0000000..7699eed
--- /dev/null
+++ b/src/gvcore/dragpixmapgenerator.h
@@ -0,0 +1,179 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab:
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2000-2004 Aurélien Gâteau
+
+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.
+
+*/
+#ifndef DRAGPIXMAPGENERATOR_H
+#define DRAGPIXMAPGENERATOR_H
+
+// Qt
+#include <qapplication.h>
+#include <qpixmap.h>
+#include <qtooltip.h>
+#include <qvaluelist.h>
+#include <qvaluevector.h>
+
+// KDE
+#include <klocale.h>
+
+namespace Gwenview {
+
+
+template <class T>
+class DragPixmapGenerator;
+
+
+template <class T>
+class DragPixmapItemDrawer {
+public:
+ DragPixmapItemDrawer()
+ : mGenerator(0) {}
+ virtual ~DragPixmapItemDrawer() {}
+ virtual void setGenerator(DragPixmapGenerator<T>* generator) {
+ mGenerator = generator;
+ }
+ virtual QSize itemSize(T)=0;
+ virtual void drawItem(QPainter*, int left, int top, T)=0;
+ virtual int spacing() const {
+ return 0;
+ }
+
+protected:
+ DragPixmapGenerator<T>* mGenerator;
+};
+
+
+QPixmap dragPixmapGeneratorHelper(QValueVector<QPixmap> pixmapVector);
+
+
+template <class T>
+class DragPixmapGenerator {
+public:
+ /** Offset between cursor and dragged images */
+ static const uint DRAG_OFFSET=16;
+
+
+ /** Maximum width of an item painted by DragPixmapItemDrawer */
+ static const int ITEM_MAX_WIDTH=128;
+
+ static const int MAX_HEIGHT=200;
+
+ static const int DRAG_MARGIN=4;
+
+
+ DragPixmapGenerator()
+ : mPixmapWidth(0) {}
+
+
+ void addItem(const T& item) {
+ mItemList << item;
+ }
+
+
+ int maxWidth() const {
+ return ITEM_MAX_WIDTH;
+ }
+
+
+ /**
+ * Returns the width of the generated pixmap, not including the margin.
+ * To be used by DragPixmapItemDrawer<T>::drawItem. Should not be used
+ * anywhere else since this value is initialized in generate().
+ */
+ int pixmapWidth() const {
+ return mPixmapWidth;
+ }
+
+ void setItemDrawer(DragPixmapItemDrawer<T>* drawer) {
+ mItemDrawer = drawer;
+ drawer->setGenerator(this);
+ }
+
+ QPixmap generate() {
+ int width = 0, height = 0;
+ int dragCount = 0;
+ int spacing = mItemDrawer->spacing();
+ bool listCropped;
+ QString bottomText;
+ QFontMetrics fm = QApplication::fontMetrics();
+
+ // Compute pixmap size and update dragCount
+ QValueListIterator<T> it = mItemList.begin();
+ QValueListIterator<T> end = mItemList.end();
+ height = -spacing;
+ for (; it!= end && height < MAX_HEIGHT; ++dragCount, ++it) {
+ QSize itemSize = mItemDrawer->itemSize(*it);
+ Q_ASSERT(itemSize.width() <= ITEM_MAX_WIDTH);
+
+ width = QMAX(width, itemSize.width());
+ height += itemSize.height() + spacing;
+ }
+
+ listCropped = it != end;
+ if (listCropped) {
+ // If list has been cropped, leave space for item count text
+ height += fm.height();
+ bottomText = i18n("%1 items").arg(mItemList.count());
+ width = QMAX(width, fm.width("... " + bottomText));
+ }
+
+ mPixmapWidth = width;
+
+ // Init pixmap
+ QPixmap pixmap(width + 2*DRAG_MARGIN, height + 2*DRAG_MARGIN);
+ QColorGroup cg = QToolTip::palette().active();
+
+ pixmap.fill(cg.base());
+ QPainter painter(&pixmap);
+
+ // Draw border
+ painter.setPen(cg.dark());
+ painter.drawRect(pixmap.rect());
+
+ // Draw items
+ it = mItemList.begin();
+ height = DRAG_MARGIN;
+ for (int pos=0; pos < dragCount; ++pos, ++it) {
+ mItemDrawer->drawItem(&painter, DRAG_MARGIN, height, *it);
+ height += mItemDrawer->itemSize(*it).height() + spacing;
+ }
+
+ // Draw text if necessary
+ if (listCropped) {
+ int posY= height + fm.ascent();
+ painter.drawText(DRAG_MARGIN, posY, "...");
+ int offset = width - fm.width(bottomText);
+ painter.drawText(DRAG_MARGIN + offset, posY, bottomText);
+ }
+ painter.end();
+
+ return pixmap;
+ }
+
+
+private:
+ QValueList<T> mItemList;
+ DragPixmapItemDrawer<T>* mItemDrawer;
+ int mPixmapWidth;
+};
+
+
+} // namespace
+
+
+#endif /* DRAGPIXMAPGENERATOR_H */
diff --git a/src/gvcore/externaltoolaction.cpp b/src/gvcore/externaltoolaction.cpp
new file mode 100644
index 0000000..db47ff8
--- /dev/null
+++ b/src/gvcore/externaltoolaction.cpp
@@ -0,0 +1,56 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2000-2004 Aurélien Gâteau
+
+ 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.
+
+*/
+// Qt
+#include <qdir.h>
+
+// KDE
+#include <kdebug.h>
+#include <krun.h>
+#include <kservice.h>
+
+// Local
+#include "externaltoolaction.moc"
+namespace Gwenview {
+
+ExternalToolAction::ExternalToolAction(
+ QObject* parent, const KService* service,
+ const KURL::List& urls)
+: KAction(parent)
+, mService(service)
+, mURLs(urls)
+{
+ setText(service->name());
+ setIcon(service->icon());
+ connect(this, SIGNAL(activated()),
+ this, SLOT(openExternalTool()) );
+
+}
+
+
+void ExternalToolAction::openExternalTool() {
+ QString dir=mURLs.first().directory();
+ QDir::setCurrent(dir);
+
+ QStringList args=KRun::processDesktopExec(*mService, mURLs, true);
+ KRun::runCommand(args.join(" "), mService->name(), mService->icon());
+}
+
+} // namespace
diff --git a/src/gvcore/externaltoolaction.h b/src/gvcore/externaltoolaction.h
new file mode 100644
index 0000000..7f62160
--- /dev/null
+++ b/src/gvcore/externaltoolaction.h
@@ -0,0 +1,50 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2000-2004 Aurélien Gâteau
+
+ 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.
+
+*/
+#ifndef EXTERNALTOOLACTION_H
+#define EXTERNALTOOLACTION_H
+
+// KDE
+#include <kaction.h>
+#include <kurl.h>
+
+class KService;
+
+namespace Gwenview {
+/**
+ * A specialized version of KAction, which is aware of the tool to run as well
+ * as the urls to call it with.
+ */
+class ExternalToolAction : public KAction {
+Q_OBJECT
+public:
+ ExternalToolAction(QObject* parent, const KService*, const KURL::List&);
+
+private slots:
+ void openExternalTool();
+
+private:
+ const KService* mService;
+ const KURL::List& mURLs;
+};
+
+} // namespace
+#endif
+
diff --git a/src/gvcore/externaltoolcontext.cpp b/src/gvcore/externaltoolcontext.cpp
new file mode 100644
index 0000000..fef6ac0
--- /dev/null
+++ b/src/gvcore/externaltoolcontext.cpp
@@ -0,0 +1,80 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2000-2004 Aurélien Gâteau
+
+ 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 "externaltoolcontext.moc"
+
+// KDE
+#include <kaction.h>
+#include <kapplication.h>
+#include <kiconloader.h>
+#include <klocale.h>
+#include <kpopupmenu.h>
+#include <krun.h>
+#include <kservice.h>
+
+// Local
+#include "externaltoolaction.h"
+#include "externaltooldialog.h"
+
+namespace Gwenview {
+
+ExternalToolContext::ExternalToolContext(
+ QObject* parent,
+ std::list<KService*> services,
+ KURL::List urls)
+: QObject(parent)
+, mServices(services)
+, mURLs(urls)
+{}
+
+
+void ExternalToolContext::showExternalToolDialog() {
+ ExternalToolDialog* dialog=new ExternalToolDialog(kapp->mainWidget());
+ dialog->show();
+}
+
+
+void ExternalToolContext::showOpenWithDialog() {
+ KRun::displayOpenWithDialog(mURLs, false /*tempFiles*/);
+}
+
+
+QPopupMenu* ExternalToolContext::popupMenu() {
+ QPopupMenu* menu=new QPopupMenu();
+ std::list<KService*>::const_iterator it=mServices.begin();
+ std::list<KService*>::const_iterator itEnd=mServices.end();
+ for (;it!=itEnd; ++it) {
+ ExternalToolAction* action=
+ new ExternalToolAction(this, *it, mURLs);
+ action->plug(menu);
+ }
+
+ menu->insertSeparator();
+ menu->insertItem(i18n("Other..."),
+ this, SLOT(showOpenWithDialog()) );
+ menu->insertItem(
+ SmallIcon("configure"),
+ i18n("Configure External Tools..."),
+ this, SLOT(showExternalToolDialog()) );
+
+ return menu;
+}
+
+} // namespace
diff --git a/src/gvcore/externaltoolcontext.h b/src/gvcore/externaltoolcontext.h
new file mode 100644
index 0000000..a253ede
--- /dev/null
+++ b/src/gvcore/externaltoolcontext.h
@@ -0,0 +1,59 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2000-2004 Aurélien Gâteau
+
+ 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.
+
+*/
+#ifndef EXTERNALTOOLCONTEXT_H
+#define EXTERNALTOOLCONTEXT_H
+
+// STL
+#include <list>
+
+// Qt
+#include <qobject.h>
+
+// KDE
+#include <kurl.h>
+
+// Local
+#include "libgwenview_export.h"
+
+class QPopupMenu;
+class KService;
+
+namespace Gwenview {
+class LIBGWENVIEW_EXPORT ExternalToolContext : public QObject {
+Q_OBJECT
+public:
+ ExternalToolContext(QObject* parent,
+ std::list<KService*> services,
+ KURL::List urls);
+ QPopupMenu* popupMenu();
+
+private slots:
+ void showExternalToolDialog();
+ void showOpenWithDialog();
+
+private:
+ std::list<KService*> mServices;
+ KURL::List mURLs;
+};
+
+} // namespace
+#endif
+
diff --git a/src/gvcore/externaltooldialog.cpp b/src/gvcore/externaltooldialog.cpp
new file mode 100644
index 0000000..89ffb79
--- /dev/null
+++ b/src/gvcore/externaltooldialog.cpp
@@ -0,0 +1,355 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2000-2004 Aurélien Gâteau
+
+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.
+
+*/
+// Qt
+#include <qbuttongroup.h>
+#include <qheader.h>
+#include <qwhatsthis.h>
+
+// KDE
+#include <kdebug.h>
+#include <kdesktopfile.h>
+#include <kicondialog.h>
+#include <kiconloader.h>
+#include <kimageio.h>
+#include <klineedit.h>
+#include <klistview.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <krun.h>
+#include <kurllabel.h>
+#include <kurlrequester.h>
+
+// Local
+#include "archive.h"
+#include "mimetypeutils.h"
+#include "externaltoolmanager.h"
+#include "externaltooldialogbase.h"
+#include "externaltooldialog.moc"
+namespace Gwenview {
+
+
+enum { ID_ALL_IMAGES=0, ID_ALL_FILES, ID_CUSTOM };
+
+
+class ToolListViewItem : public KListViewItem {
+public:
+ ToolListViewItem(KListView* parent, const QString& label)
+ : KListViewItem(parent, label), mDesktopFile(0L) {}
+
+ void setDesktopFile(KDesktopFile* df) {
+ mDesktopFile=df;
+ }
+
+ KDesktopFile* desktopFile() const {
+ return mDesktopFile;
+ }
+
+private:
+ KDesktopFile* mDesktopFile;
+};
+
+
+struct ExternalToolDialogPrivate {
+ ExternalToolDialogBase* mContent;
+ QPtrList<KDesktopFile> mDeletedTools;
+ ToolListViewItem* mSelectedItem;
+
+
+ ExternalToolDialogPrivate()
+ : mSelectedItem(0L) {}
+
+ void fillMimeTypeListView() {
+ QStringList mimeTypes=MimeTypeUtils::rasterImageMimeTypes();
+ mimeTypes.append("inode/directory");
+ mimeTypes+=Archive::mimeTypes();
+
+ QStringList::const_iterator it=mimeTypes.begin();
+ for(; it!=mimeTypes.end(); ++it) {
+ (void)new QCheckListItem(mContent->mMimeTypeListView, *it, QCheckListItem::CheckBox);
+ }
+ }
+
+
+ void fillToolListView() {
+ QDict<KDesktopFile> desktopFiles=ExternalToolManager::instance()->desktopFiles();
+
+ QDictIterator<KDesktopFile> it(desktopFiles);
+ for (; it.current(); ++it) {
+ ToolListViewItem* item=new ToolListViewItem(mContent->mToolListView, it.current()->readName());
+ item->setPixmap(0, SmallIcon(it.current()->readIcon()) );
+ item->setDesktopFile(it.current());
+ }
+ mContent->mToolListView->setSortColumn(0);
+ mContent->mToolListView->sort();
+ }
+
+
+ void writeServiceTypes(KDesktopFile* desktopFile) {
+ QButton* button=mContent->mFileAssociationGroup->selected();
+ if (!button) {
+ desktopFile->writeEntry("ServiceTypes", "*");
+ return;
+ }
+
+ int id=mContent->mFileAssociationGroup->id(button);
+ if (id==ID_ALL_IMAGES) {
+ desktopFile->writeEntry("ServiceTypes", "image/*");
+ return;
+ }
+ if (id==ID_ALL_FILES) {
+ desktopFile->writeEntry("ServiceTypes", "*");
+ return;
+ }
+
+ QStringList mimeTypes;
+ QListViewItem* item=mContent->mMimeTypeListView->firstChild();
+ for (; item; item=item->nextSibling()) {
+ if (static_cast<QCheckListItem*>(item)->isOn()) {
+ mimeTypes.append(item->text(0));
+ }
+ }
+ desktopFile->writeEntry("ServiceTypes", mimeTypes);
+ }
+
+
+ bool saveChanges() {
+ if (!mSelectedItem) return true;
+
+ // Check name
+ QString name=mContent->mName->text().stripWhiteSpace();
+ if (name.isEmpty()) {
+ KMessageBox::sorry(mContent, i18n("The tool name cannot be empty"));
+ return false;
+ }
+
+ QListViewItem* item=mContent->mToolListView->firstChild();
+ for (; item; item=item->nextSibling()) {
+ if (item==mSelectedItem) continue;
+ if (name==item->text(0)) {
+ KMessageBox::sorry(mContent, i18n("There is already a tool named \"%1\"").arg(name));
+ return false;
+ }
+ }
+
+ // Save data
+ KDesktopFile* desktopFile=mSelectedItem->desktopFile();
+ if (desktopFile) {
+ if (desktopFile->isReadOnly()) {
+ desktopFile=ExternalToolManager::instance()->editSystemDesktopFile(desktopFile);
+ mSelectedItem->setDesktopFile(desktopFile);
+ }
+ } else {
+ desktopFile=ExternalToolManager::instance()->createUserDesktopFile(name);
+ mSelectedItem->setDesktopFile(desktopFile);
+ }
+ desktopFile->writeEntry("Name", name);
+ desktopFile->writeEntry("Icon", mContent->mIconButton->icon());
+ desktopFile->writeEntry("Exec", mContent->mCommand->url());
+ writeServiceTypes(desktopFile);
+
+ mSelectedItem->setPixmap(0, SmallIcon(mContent->mIconButton->icon()) );
+ mSelectedItem->setText(0, name);
+
+ return true;
+ }
+
+
+ void updateFileAssociationGroup(const QStringList& serviceTypes) {
+ QListViewItem* item=mContent->mMimeTypeListView->firstChild();
+ for (; item; item=item->nextSibling()) {
+ static_cast<QCheckListItem*>(item)->setOn(false);
+ }
+
+ if (serviceTypes.size()==0) {
+ mContent->mFileAssociationGroup->setButton(ID_ALL_FILES);
+ return;
+ }
+ if (serviceTypes.size()==1) {
+ QString serviceType=serviceTypes[0];
+ if (serviceType=="image/*") {
+ mContent->mFileAssociationGroup->setButton(ID_ALL_IMAGES);
+ return;
+ }
+ if (serviceType=="*") {
+ mContent->mFileAssociationGroup->setButton(ID_ALL_FILES);
+ return;
+ }
+ }
+
+ mContent->mFileAssociationGroup->setButton(ID_CUSTOM);
+ QStringList::ConstIterator it=serviceTypes.begin();
+ for (;it!=serviceTypes.end(); ++it) {
+ QListViewItem* item=
+ mContent->mMimeTypeListView->findItem(*it, 0, Qt::ExactMatch);
+ if (item) static_cast<QCheckListItem*>(item)->setOn(true);
+ }
+ }
+
+
+ void updateDetails() {
+ mContent->mDetails->setEnabled(mSelectedItem!=0);
+
+ if (mSelectedItem) {
+ KDesktopFile* desktopFile=mSelectedItem->desktopFile();
+ if (desktopFile) {
+ mContent->mName->setText(desktopFile->readName());
+ mContent->mCommand->setURL(desktopFile->readEntry("Exec"));
+ mContent->mIconButton->setIcon(desktopFile->readIcon());
+ QStringList serviceTypes=desktopFile->readListEntry("ServiceTypes");
+ updateFileAssociationGroup(serviceTypes);
+ return;
+ }
+ }
+
+ mContent->mName->setText(QString::null);
+ mContent->mCommand->setURL(QString::null);
+ mContent->mIconButton->setIcon(QString::null);
+ mContent->mFileAssociationGroup->setButton(ID_ALL_IMAGES);
+ }
+
+ bool apply() {
+ if (!saveChanges()) return false;
+ QPtrListIterator<KDesktopFile> it(mDeletedTools);
+ for(; it.current(); ++it) {
+ ExternalToolManager::instance()->hideDesktopFile(it.current());
+ }
+ ExternalToolManager::instance()->updateServices();
+ return true;
+ }
+};
+
+
+/**
+ * This event filter object is here to prevent the user from selecting a
+ * different tool in the tool list view if the current tool could not be saved.
+ */
+class ToolListViewFilterObject : public QObject {
+ ExternalToolDialogPrivate* d;
+public:
+ ToolListViewFilterObject(QObject* parent, ExternalToolDialogPrivate* _d)
+ : QObject(parent), d(_d) {}
+
+ bool eventFilter(QObject*, QEvent* event) {
+ if (event->type()!=QEvent::MouseButtonPress) return false;
+ return !d->saveChanges();
+ }
+};
+
+
+ExternalToolDialog::ExternalToolDialog(QWidget* parent)
+: KDialogBase(
+ parent,0, false, QString::null, KDialogBase::Ok|KDialogBase::Apply|KDialogBase::Cancel,
+ KDialogBase::Ok, true)
+{
+ setWFlags(getWFlags() | Qt::WDestructiveClose);
+ d=new ExternalToolDialogPrivate;
+
+ d->mContent=new ExternalToolDialogBase(this);
+ setMainWidget(d->mContent);
+ setCaption(d->mContent->caption());
+
+ d->mContent->mToolListView->header()->hide();
+ d->mContent->mMimeTypeListView->header()->hide();
+
+ d->fillMimeTypeListView();
+ d->fillToolListView();
+ d->mContent->mToolListView->viewport()->installEventFilter(
+ new ToolListViewFilterObject(this, d));
+
+ connect( d->mContent->mToolListView, SIGNAL(selectionChanged(QListViewItem*)),
+ this, SLOT(slotSelectionChanged(QListViewItem*)) );
+ connect( d->mContent->mAddButton, SIGNAL(clicked()),
+ this, SLOT(addTool()) );
+ connect( d->mContent->mDeleteButton, SIGNAL(clicked()),
+ this, SLOT(deleteTool()) );
+ connect( d->mContent->mHelp, SIGNAL(leftClickedURL()),
+ this, SLOT(showCommandHelp()) );
+ connect( d->mContent->mMoreTools, SIGNAL(leftClickedURL(const QString&)),
+ this, SLOT(openURL(const QString&)) );
+
+ KListView* view=d->mContent->mToolListView;
+ if (view->firstChild()) {
+ view->setSelected(view->firstChild(), true);
+ }
+ d->updateDetails();
+}
+
+
+ExternalToolDialog::~ExternalToolDialog() {
+ delete d;
+}
+
+
+void ExternalToolDialog::slotOk() {
+ if (!d->apply()) return;
+ accept();
+}
+
+
+void ExternalToolDialog::slotApply() {
+ d->apply();
+}
+
+
+void ExternalToolDialog::slotCancel() {
+ KDialogBase::slotCancel();
+}
+
+
+void ExternalToolDialog::slotSelectionChanged(QListViewItem* item) {
+ d->mSelectedItem=static_cast<ToolListViewItem*>(item);
+ d->updateDetails();
+}
+
+
+void ExternalToolDialog::addTool() {
+ KListView* view=d->mContent->mToolListView;
+ QString name=i18n("<Unnamed tool>");
+ ToolListViewItem* item=new ToolListViewItem(view, name);
+ view->setSelected(item, true);
+}
+
+
+void ExternalToolDialog::deleteTool() {
+ KListView* view=d->mContent->mToolListView;
+ ToolListViewItem* item=static_cast<ToolListViewItem*>(view->selectedItem());
+ if (!item) return;
+
+ KDesktopFile* desktopFile=item->desktopFile();
+ delete item;
+ d->mDeletedTools.append(desktopFile);
+ d->mSelectedItem=0L;
+ d->updateDetails();
+}
+
+
+void ExternalToolDialog::showCommandHelp() {
+ KURLRequester* lbl=d->mContent->mCommand;
+ QWhatsThis::display(QWhatsThis::textFor(lbl),
+ lbl->mapToGlobal( lbl->rect().bottomRight() ) );
+}
+
+
+void ExternalToolDialog::openURL(const QString& url) {
+ new KRun(KURL(url));
+}
+
+} // namespace
diff --git a/src/gvcore/externaltooldialog.h b/src/gvcore/externaltooldialog.h
new file mode 100644
index 0000000..306cb59
--- /dev/null
+++ b/src/gvcore/externaltooldialog.h
@@ -0,0 +1,59 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2000-2004 Aurélien Gâteau
+
+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.
+
+*/
+#ifndef EXTERNALTOOLDIALOG_H
+#define EXTERNALTOOLDIALOG_H
+
+// KDE
+#include <kdialogbase.h>
+
+// Our includes
+#include "libgwenview_export.h"
+
+namespace Gwenview {
+
+
+class ExternalToolDialogPrivate;
+class LIBGWENVIEW_EXPORT ExternalToolDialog : public KDialogBase {
+Q_OBJECT
+public:
+ ExternalToolDialog(QWidget* parent);
+ ~ExternalToolDialog();
+
+protected slots:
+ void slotOk();
+ void slotApply();
+ void slotCancel();
+
+private slots:
+ void slotSelectionChanged(QListViewItem*);
+ void addTool();
+ void deleteTool();
+ void showCommandHelp();
+ void openURL(const QString& url);
+
+private:
+ ExternalToolDialogPrivate* d;
+};
+
+
+} // namespace
+#endif
+
diff --git a/src/gvcore/externaltooldialogbase.ui b/src/gvcore/externaltooldialogbase.ui
new file mode 100644
index 0000000..7b17ed4
--- /dev/null
+++ b/src/gvcore/externaltooldialogbase.ui
@@ -0,0 +1,373 @@
+<!DOCTYPE UI><UI version="3.2" stdsetdef="1">
+<class>ExternalToolDialogBase</class>
+<widget class="QWidget">
+ <property name="name">
+ <cstring>ExternalToolDialogBase</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>723</width>
+ <height>361</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>5</hsizetype>
+ <vsizetype>5</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>720</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="caption">
+ <string>Configure External Tools</string>
+ </property>
+ <grid>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <spacer row="1" column="2">
+ <property name="name">
+ <cstring>spacer3_2</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Expanding</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>487</width>
+ <height>16</height>
+ </size>
+ </property>
+ </spacer>
+ <widget class="QPushButton" row="1" column="1">
+ <property name="name">
+ <cstring>mDeleteButton</cstring>
+ </property>
+ <property name="text">
+ <string>&amp;Delete</string>
+ </property>
+ </widget>
+ <widget class="QPushButton" row="1" column="0">
+ <property name="name">
+ <cstring>mAddButton</cstring>
+ </property>
+ <property name="text">
+ <string>&amp;Add</string>
+ </property>
+ </widget>
+ <widget class="KListView" row="0" column="0" rowspan="1" colspan="2">
+ <column>
+ <property name="text">
+ <string>Name</string>
+ </property>
+ <property name="clickable">
+ <bool>false</bool>
+ </property>
+ <property name="resizable">
+ <bool>true</bool>
+ </property>
+ </column>
+ <property name="name">
+ <cstring>mToolListView</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>5</hsizetype>
+ <vsizetype>7</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="fullWidth">
+ <bool>true</bool>
+ </property>
+ </widget>
+ <widget class="KURLLabel" row="1" column="3">
+ <property name="name">
+ <cstring>mMoreTools</cstring>
+ </property>
+ <property name="text">
+ <string>Get more tools</string>
+ </property>
+ <property name="url" stdset="0">
+ <string>http://gwenview.sourceforge.net/tools</string>
+ </property>
+ <property name="useTips">
+ <bool>true</bool>
+ </property>
+ </widget>
+ <widget class="QFrame" row="0" column="2" rowspan="1" colspan="2">
+ <property name="name">
+ <cstring>mDetails</cstring>
+ </property>
+ <property name="frameShape">
+ <enum>NoFrame</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>Raised</enum>
+ </property>
+ <grid>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <widget class="QLabel" row="1" column="0">
+ <property name="name">
+ <cstring>textLabel1_2</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>5</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <bold>1</bold>
+ </font>
+ </property>
+ <property name="text">
+ <string>File Associations</string>
+ </property>
+ </widget>
+ <widget class="QLayoutWidget" row="0" column="0">
+ <property name="name">
+ <cstring>layout3</cstring>
+ </property>
+ <grid>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="KIconButton" row="0" column="3" rowspan="2" colspan="1">
+ <property name="name">
+ <cstring>mIconButton</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>1</hsizetype>
+ <vsizetype>1</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string></string>
+ </property>
+ </widget>
+ <widget class="QLabel" row="0" column="0">
+ <property name="name">
+ <cstring>textLabel1</cstring>
+ </property>
+ <property name="text">
+ <string>Name:</string>
+ </property>
+ </widget>
+ <widget class="KURLLabel" row="1" column="2">
+ <property name="name">
+ <cstring>mHelp</cstring>
+ </property>
+ <property name="text">
+ <string>Help</string>
+ </property>
+ <property name="url" stdset="0">
+ <string></string>
+ </property>
+ </widget>
+ <widget class="QLabel" row="1" column="0">
+ <property name="name">
+ <cstring>textLabel2</cstring>
+ </property>
+ <property name="text">
+ <string>Command:</string>
+ </property>
+ </widget>
+ <widget class="KLineEdit" row="0" column="1" rowspan="1" colspan="2">
+ <property name="name">
+ <cstring>mName</cstring>
+ </property>
+ </widget>
+ <widget class="KURLRequester" row="1" column="1">
+ <property name="name">
+ <cstring>mCommand</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>7</hsizetype>
+ <vsizetype>5</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>&lt;qt&gt;
+You can use keywords in the Command field:
+&lt;ul&gt;
+&lt;li&gt;&lt;tt&gt;%u&lt;/tt&gt;: Current URL.&lt;/li&gt;
+&lt;li&gt;&lt;tt&gt;%U&lt;/tt&gt;: Current URLs. Use this if the tool can handle multiple files.&lt;/li&gt;
+&lt;li&gt;&lt;tt&gt;%f&lt;/tt&gt;: Current file. Use this if the tool can't handle URLs.&lt;/li&gt;
+&lt;li&gt;&lt;tt&gt;%F&lt;/tt&gt;: Same as %f, but for multiple files.&lt;/li&gt;
+&lt;/ul&gt;
+&lt;/qt&gt;</string>
+ </property>
+ </widget>
+ </grid>
+ </widget>
+ <widget class="QButtonGroup" row="2" column="0">
+ <property name="name">
+ <cstring>mFileAssociationGroup</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>5</hsizetype>
+ <vsizetype>7</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="frameShape">
+ <enum>NoFrame</enum>
+ </property>
+ <property name="title">
+ <string></string>
+ </property>
+ <grid>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <widget class="QRadioButton" row="0" column="0" rowspan="1" colspan="2">
+ <property name="name">
+ <cstring>radioButton1</cstring>
+ </property>
+ <property name="text">
+ <string>All images</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ <property name="buttonGroupId">
+ <number>0</number>
+ </property>
+ </widget>
+ <widget class="QRadioButton" row="1" column="0" rowspan="1" colspan="2">
+ <property name="name">
+ <cstring>radioButton2</cstring>
+ </property>
+ <property name="text">
+ <string>All files</string>
+ </property>
+ <property name="buttonGroupId">
+ <number>1</number>
+ </property>
+ </widget>
+ <widget class="QRadioButton" row="2" column="0">
+ <property name="name">
+ <cstring>radioButton3</cstring>
+ </property>
+ <property name="text">
+ <string>Custom:</string>
+ </property>
+ </widget>
+ <spacer row="3" column="0">
+ <property name="name">
+ <cstring>spacer3</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Expanding</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>21</width>
+ <height>140</height>
+ </size>
+ </property>
+ </spacer>
+ <widget class="KListView" row="2" column="1" rowspan="2" colspan="1">
+ <column>
+ <property name="text">
+ <string>Mime Type</string>
+ </property>
+ <property name="clickable">
+ <bool>false</bool>
+ </property>
+ <property name="resizable">
+ <bool>true</bool>
+ </property>
+ </column>
+ <property name="name">
+ <cstring>mMimeTypeListView</cstring>
+ </property>
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="fullWidth">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </grid>
+ </widget>
+ </grid>
+ </widget>
+ </grid>
+</widget>
+<customwidgets>
+</customwidgets>
+<connections>
+ <connection>
+ <sender>radioButton3</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>mMimeTypeListView</receiver>
+ <slot>setEnabled(bool)</slot>
+ </connection>
+</connections>
+<tabstops>
+ <tabstop>mToolListView</tabstop>
+ <tabstop>mAddButton</tabstop>
+ <tabstop>mDeleteButton</tabstop>
+ <tabstop>mName</tabstop>
+ <tabstop>mCommand</tabstop>
+ <tabstop>radioButton1</tabstop>
+ <tabstop>mMimeTypeListView</tabstop>
+</tabstops>
+<layoutdefaults spacing="6" margin="11"/>
+<includehints>
+ <includehint>klistview.h</includehint>
+ <includehint>kurllabel.h</includehint>
+ <includehint>kicondialog.h</includehint>
+ <includehint>kurllabel.h</includehint>
+ <includehint>klineedit.h</includehint>
+ <includehint>kurlrequester.h</includehint>
+ <includehint>klineedit.h</includehint>
+ <includehint>kpushbutton.h</includehint>
+ <includehint>klistview.h</includehint>
+</includehints>
+</UI>
diff --git a/src/gvcore/externaltoolmanager.cpp b/src/gvcore/externaltoolmanager.cpp
new file mode 100644
index 0000000..1df124f
--- /dev/null
+++ b/src/gvcore/externaltoolmanager.cpp
@@ -0,0 +1,294 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2000-2004 Aurélien Gâteau
+
+ 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.
+
+*/
+// STL
+#include <list>
+
+// Qt
+#include <qdir.h>
+
+// KDE
+#include <kdebug.h>
+#include <kdesktopfile.h>
+#include <kglobal.h>
+#include <kservice.h>
+#include <kstandarddirs.h>
+#include <kurl.h>
+
+// Local
+#include "externaltoolcontext.h"
+#include "externaltoolmanager.h"
+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
+
+// Helper functions for createContextInternal
+inline bool mimeTypeMatches(const QString& candidate, const QString& reference) {
+ if (reference=="*") return true;
+
+ if (reference.right(1)=="*") {
+ return candidate.startsWith(reference.left(reference.length()-1));
+ } else {
+ return candidate==reference;
+ }
+}
+
+inline bool isSubSetOf(const QStringList& subSet, const QStringList& set) {
+ // Simple implementation, might need some optimization
+ QStringList::ConstIterator itSubSet=subSet.begin();
+ QStringList::ConstIterator itSetBegin=set.begin();
+ QStringList::ConstIterator itSetEnd=set.end();
+
+ for (; itSubSet!=subSet.end(); ++itSubSet) {
+ bool matchFound=false;
+ QStringList::ConstIterator itSet=itSetBegin;
+ for (; itSet!=itSetEnd; ++itSet) {
+ if (mimeTypeMatches(*itSubSet, *itSet)) {
+ matchFound=true;
+ break;
+ }
+ }
+ if (!matchFound) {
+ return false;
+ }
+ }
+ return true;
+}
+
+
+struct ExternalToolManagerPrivate {
+ QDict<KDesktopFile> mDesktopFiles;
+ QPtrList<KService> mServices;
+ QString mUserToolDir;
+
+ /**
+ * Helper function for createContextInternal
+ */
+ static bool compareKServicePtrByName(const KService* s1, const KService* s2) {
+ Q_ASSERT(s1);
+ Q_ASSERT(s2);
+ return s1->name() < s2->name();
+ }
+
+ ExternalToolContext* createContextInternal(
+ QObject* parent, const KURL::List& urls, const QStringList& mimeTypes)
+ {
+ bool onlyOneURL=urls.size()==1;
+
+ // Only add to selectionServices the services which can handle all the
+ // different mime types present in the selection
+ //
+ // We use std::list instead of QValueList because it's not possible to
+ // pass a sort functor to qHeapSort
+ std::list<KService*> selectionServices;
+ QPtrListIterator<KService> it(mServices);
+ for (; it.current(); ++it) {
+ KService* service=it.current();
+ if (!onlyOneURL && !service->allowMultipleFiles()) {
+ continue;
+ }
+
+ QStringList serviceTypes=service->serviceTypes();
+ if (isSubSetOf(mimeTypes, serviceTypes)) {
+ selectionServices.push_back(service);
+ }
+ }
+ selectionServices.sort(compareKServicePtrByName);
+
+ return new ExternalToolContext(parent, selectionServices, urls);
+ }
+
+};
+
+
+// Helper function for ctor
+void loadDesktopFiles(QDict<KDesktopFile>& dict, const QString& dirString) {
+ QDir dir(dirString);
+ QStringList list=dir.entryList("*.desktop");
+ QStringList::ConstIterator it=list.begin();
+ for (; it!=list.end(); ++it) {
+ KDesktopFile* df=new KDesktopFile( dir.filePath(*it) );
+ dict.insert(*it, df);
+ }
+}
+
+inline QString addSlash(const QString& _str) {
+ QString str(_str);
+ if (str.right(1)!="/") str.append('/');
+ return str;
+}
+
+ExternalToolManager::ExternalToolManager() {
+ d=new ExternalToolManagerPrivate;
+
+ // Getting dirs
+ d->mUserToolDir=KGlobal::dirs()->saveLocation("appdata", "tools");
+ d->mUserToolDir=addSlash(d->mUserToolDir);
+ Q_ASSERT(!d->mUserToolDir.isEmpty());
+ LOG("d->mUserToolDir:" << d->mUserToolDir);
+
+ QStringList dirs=KGlobal::dirs()->findDirs("appdata", "tools");
+ LOG("dirs:" << dirs.join(","));
+
+ // Loading desktop files
+ QDict<KDesktopFile> systemDesktopFiles;
+ QStringList::ConstIterator it;
+ for (it=dirs.begin(); it!=dirs.end(); ++it) {
+ if (addSlash(*it)==d->mUserToolDir) {
+ LOG("skipping " << *it);
+ continue;
+ }
+ LOG("loading system desktop files from " << *it);
+ loadDesktopFiles(systemDesktopFiles, *it);
+ }
+ QDict<KDesktopFile> userDesktopFiles;
+ loadDesktopFiles(userDesktopFiles, d->mUserToolDir);
+
+ // Merge system and user desktop files into our KDesktopFile dictionary
+ d->mDesktopFiles=systemDesktopFiles;
+ d->mDesktopFiles.setAutoDelete(true);
+ QDictIterator<KDesktopFile> itDict(userDesktopFiles);
+
+ for (; itDict.current(); ++itDict) {
+ QString name=itDict.currentKey();
+ KDesktopFile* df=itDict.current();
+ if (d->mDesktopFiles.find(name)) {
+ d->mDesktopFiles.remove(name);
+ }
+ if (df->readBoolEntry("Hidden")) {
+ delete df;
+ } else {
+ d->mDesktopFiles.insert(name, df);
+ }
+ }
+
+ d->mServices.setAutoDelete(true);
+ updateServices();
+}
+
+
+ExternalToolManager::~ExternalToolManager() {
+ delete d;
+}
+
+
+ExternalToolManager* ExternalToolManager::instance() {
+ static ExternalToolManager manager;
+ return &manager;
+}
+
+
+void ExternalToolManager::updateServices() {
+ d->mServices.clear();
+ QDictIterator<KDesktopFile> it(d->mDesktopFiles);
+ for (; it.current(); ++it) {
+ KDesktopFile* desktopFile=it.current();
+ // If sync() is not called, KService does not read up to date content
+ desktopFile->sync();
+ KService* service=new KService(desktopFile);
+ d->mServices.append(service);
+ }
+}
+
+
+QDict<KDesktopFile>& ExternalToolManager::desktopFiles() const {
+ return d->mDesktopFiles;
+}
+
+
+void ExternalToolManager::hideDesktopFile(KDesktopFile* desktopFile) {
+ QFileInfo fi(desktopFile->fileName());
+ QString name=QString("%1.desktop").arg( fi.baseName(true) );
+ d->mDesktopFiles.take(name);
+
+ if (desktopFile->isReadOnly()) {
+ delete desktopFile;
+ desktopFile=new KDesktopFile(d->mUserToolDir + "/" + name, false);
+ }
+ desktopFile->writeEntry("Hidden", true);
+ desktopFile->sync();
+ delete desktopFile;
+}
+
+
+KDesktopFile* ExternalToolManager::editSystemDesktopFile(const KDesktopFile* desktopFile) {
+ Q_ASSERT(desktopFile);
+ QFileInfo fi(desktopFile->fileName());
+
+ QString name=fi.baseName(true);
+ d->mDesktopFiles.remove(QString("%1.desktop").arg(name));
+
+ return createUserDesktopFile(name);
+}
+
+
+KDesktopFile* ExternalToolManager::createUserDesktopFile(const QString& name) {
+ Q_ASSERT(!name.isEmpty());
+ KDesktopFile* desktopFile=new KDesktopFile(
+ d->mUserToolDir + "/" + name + ".desktop", false);
+ d->mDesktopFiles.insert(QString("%1.desktop").arg(name), desktopFile);
+
+ return desktopFile;
+}
+
+
+ExternalToolContext* ExternalToolManager::createContext(
+ QObject* parent, const KFileItemList* items)
+{
+ KURL::List urls;
+ QStringList mimeTypes;
+
+ // Create our URL list and a list of the different mime types present in
+ // the selection
+ QPtrListIterator<KFileItem> it(*items);
+ for (; it.current(); ++it) {
+ urls.append(it.current()->url());
+ QString mimeType=it.current()->mimetype();
+ if (!mimeTypes.contains(mimeType)) {
+ mimeTypes.append(mimeType);
+ }
+ }
+
+ return d->createContextInternal(parent, urls, mimeTypes);
+}
+
+
+ExternalToolContext* ExternalToolManager::createContext(
+ QObject* parent, const KURL& url)
+{
+ KURL::List urls;
+ QStringList mimeTypes;
+
+ urls.append(url);
+ QString mimeType=KMimeType::findByURL(url, 0, url.isLocalFile(), true)->name();
+ mimeTypes.append(mimeType);
+
+ return d->createContextInternal(parent, urls, mimeTypes);
+}
+
+
+} // namespace
diff --git a/src/gvcore/externaltoolmanager.h b/src/gvcore/externaltoolmanager.h
new file mode 100644
index 0000000..8cf01ab
--- /dev/null
+++ b/src/gvcore/externaltoolmanager.h
@@ -0,0 +1,67 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2000-2004 Aurélien Gâteau
+
+ 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.
+
+*/
+#ifndef EXTERNALTOOLMANAGER_H
+#define EXTERNALTOOLMANAGER_H
+
+// Qt
+#include <qdict.h>
+#include <qptrlist.h>
+
+// KDE
+#include <kfileitem.h>
+
+// Local
+#include "libgwenview_export.h"
+
+class KActionMenu;
+class KURL;
+
+namespace Gwenview {
+class ExternalToolContext;
+class ExternalToolManagerPrivate;
+
+class LIBGWENVIEW_EXPORT ExternalToolManager {
+public:
+ ~ExternalToolManager();
+
+ ExternalToolContext* createContext(QObject* parent, const KFileItemList*);
+ ExternalToolContext* createContext(QObject* parent, const KURL&);
+
+ static ExternalToolManager* instance();
+ QDict<KDesktopFile>& desktopFiles() const;
+
+ void hideDesktopFile(KDesktopFile*);
+
+ // Create a new desktop file
+ KDesktopFile* createUserDesktopFile(const QString& name);
+
+ // Create a desktop file based on a existing (system) desktop file
+ KDesktopFile* editSystemDesktopFile(const KDesktopFile* desktopFile);
+ void updateServices();
+
+private:
+ ExternalToolManager();
+ ExternalToolManagerPrivate* d;
+};
+
+} // namespace
+#endif
+
diff --git a/src/gvcore/filedetailview.cpp b/src/gvcore/filedetailview.cpp
new file mode 100644
index 0000000..b36a1dd
--- /dev/null
+++ b/src/gvcore/filedetailview.cpp
@@ -0,0 +1,552 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/* This file is based on kfiledetailview.cpp v1.43 from the KDE libs. Original
+ copyright follows.
+*/
+/* This file is part of the KDE libraries
+ Copyright (C) 1997 Stephan Kulow <coolo@kde.org>
+ 2000, 2001 Carsten Pfeiffer <pfeiffer@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+
+// Qt
+#include <qbitmap.h>
+#include <qevent.h>
+#include <qheader.h>
+#include <qkeycode.h>
+#include <qpainter.h>
+#include <qpixmap.h>
+
+// KDE
+#include <kapplication.h>
+#include <kdebug.h>
+#include <kfileitem.h>
+#include <kglobalsettings.h>
+#include <kiconloader.h>
+#include <klocale.h>
+#include <kurldrag.h>
+#include <kwordwrap.h>
+
+// Local
+#include "archive.h"
+#include "dragpixmapgenerator.h"
+#include "filedetailviewitem.h"
+#include "filedetailview.moc"
+#include "timeutils.h"
+namespace Gwenview {
+
+
+static QPixmap createShownItemPixmap(int size, const QColor& color) {
+ QPixmap pix(size, size);
+ pix.fill(Qt::red);
+ QPainter painter(&pix);
+ int margin = 2;
+
+ QPointArray pa(3);
+ int arrowSize = size/2 - margin;
+ int center = size/2 - 1;
+ pa[0] = QPoint((size - arrowSize) / 2, center - arrowSize);
+ pa[1] = QPoint((size + arrowSize) / 2, center);
+ pa[2] = QPoint(pa[0].x(), center + arrowSize);
+
+ painter.setBrush(color);
+ painter.setPen(color);
+ painter.drawPolygon(pa);
+ painter.end();
+
+ pix.setMask(pix.createHeuristicMask());
+
+ return pix;
+}
+
+
+FileDetailView::FileDetailView(QWidget *parent, const char *name)
+ : KListView(parent, name), FileViewBase()
+{
+ mSortingCol = COL_NAME;
+ mBlockSortingSignal = false;
+
+ addColumn( i18n( "Name" ) );
+ addColumn( i18n( "Size" ) );
+ addColumn( i18n( "Date" ) );
+ addColumn( i18n( "Permissions" ) );
+ addColumn( i18n( "Owner" ) );
+ addColumn( i18n( "Group" ) );
+ setShowSortIndicator( TRUE );
+ setAllColumnsShowFocus( TRUE );
+
+ connect( header(), SIGNAL( sectionClicked(int)),
+ SLOT(slotSortingChanged(int) ));
+
+
+ connect( this, SIGNAL( returnPressed(QListViewItem *) ),
+ SLOT( slotActivate( QListViewItem *) ) );
+
+ connect( this, SIGNAL( clicked(QListViewItem *, const QPoint&, int)),
+ SLOT( selected( QListViewItem *) ) );
+ connect( this, SIGNAL( doubleClicked(QListViewItem *, const QPoint&, int)),
+ SLOT( slotActivate( QListViewItem *) ) );
+
+ connect( this, SIGNAL(contextMenuRequested( QListViewItem *,
+ const QPoint &, int )),
+ this, SLOT( slotActivateMenu( QListViewItem *, const QPoint& )));
+
+ QListView::setSelectionMode( QListView::Extended );
+ connect( this, SIGNAL( selectionChanged() ),
+ SLOT( slotSelectionChanged() ));
+
+ // FileViewStack need to be aware of sort changes, to update the sort menu
+ connect( sig, SIGNAL(sortingChanged(QDir::SortSpec)),
+ this, SIGNAL(sortingChanged(QDir::SortSpec)) );
+
+ setSorting( sorting() );
+
+
+ mResolver =
+ new KMimeTypeResolver<FileDetailViewItem,FileDetailView>( this );
+
+ setDragEnabled(true);
+
+ setAcceptDrops(true);
+ setDropVisualizer(false);
+ setDropHighlighter(false);
+
+ int size = IconSize(KIcon::Small);
+ mShownItemUnselectedPixmap = createShownItemPixmap(size, colorGroup().highlight());
+ mShownItemSelectedPixmap = createShownItemPixmap(size, colorGroup().highlightedText());
+}
+
+
+FileDetailView::~FileDetailView()
+{
+ delete mResolver;
+}
+
+
+void FileDetailView::setSelected( const KFileItem *info, bool enable )
+{
+ if (!info) return;
+ FileDetailViewItem *item = viewItem(info);
+ if (item) KListView::setSelected(item, enable);
+}
+
+void FileDetailView::setCurrentItem( const KFileItem *item )
+{
+ if (!item) return;
+ FileDetailViewItem *listItem = viewItem(item);
+ if (listItem) KListView::setCurrentItem(listItem);
+}
+
+KFileItem * FileDetailView::currentFileItem() const
+{
+ FileDetailViewItem *current = static_cast<FileDetailViewItem*>( currentItem() );
+ if ( current ) return current->fileInfo();
+
+ return 0L;
+}
+
+void FileDetailView::clearSelection()
+{
+ KListView::clearSelection();
+}
+
+void FileDetailView::selectAll()
+{
+ KListView::selectAll( true );
+}
+
+void FileDetailView::invertSelection()
+{
+ KListView::invertSelection();
+}
+
+void FileDetailView::slotActivateMenu (QListViewItem *item,const QPoint& pos )
+{
+ if ( !item ) {
+ sig->activateMenu( 0, pos );
+ return;
+ }
+ FileDetailViewItem *i = (FileDetailViewItem*) item;
+ sig->activateMenu( i->fileInfo(), pos );
+}
+
+void FileDetailView::clearView()
+{
+ mResolver->m_lstPendingMimeIconItems.clear();
+ mShownFileItem=0L;
+ KListView::clear();
+}
+
+void FileDetailView::insertItem( KFileItem *i )
+{
+ KFileView::insertItem( i );
+
+ FileDetailViewItem *item = new FileDetailViewItem( (QListView*) this, i );
+
+ setSortingKey( item, i );
+
+ i->setExtraData( this, item );
+
+ if ( !i->isMimeTypeKnown() )
+ mResolver->m_lstPendingMimeIconItems.append( item );
+}
+
+void FileDetailView::slotActivate( QListViewItem *item )
+{
+ if ( !item ) return;
+
+ const KFileItem *fi = ( (FileDetailViewItem*)item )->fileInfo();
+ if ( fi ) sig->activate( fi );
+}
+
+void FileDetailView::selected( QListViewItem *item )
+{
+ if ( !item ) return;
+
+ if ( KGlobalSettings::singleClick() ) {
+ const KFileItem *fi = ( (FileDetailViewItem*)item )->fileInfo();
+ if ( fi && (fi->isDir() || !onlyDoubleClickSelectsFiles()) )
+ sig->activate( fi );
+ }
+}
+
+void FileDetailView::highlighted( QListViewItem *item )
+{
+ if ( !item ) return;
+
+ const KFileItem *fi = ( (FileDetailViewItem*)item )->fileInfo();
+ if ( fi ) sig->highlightFile( fi );
+}
+
+
+bool FileDetailView::isSelected(const KFileItem* fileItem) const
+{
+ if (!fileItem) return false;
+
+ FileDetailViewItem *item = viewItem(fileItem);
+ return item && item->isSelected();
+}
+
+
+void FileDetailView::updateView( bool b )
+{
+ if ( !b ) return;
+
+ QListViewItemIterator it( (QListView*)this );
+ for ( ; it.current(); ++it ) {
+ FileDetailViewItem *item=static_cast<FileDetailViewItem *>(it.current());
+ item->setPixmap( 0, item->fileInfo()->pixmap(KIcon::SizeSmall) );
+ }
+}
+
+void FileDetailView::updateView( const KFileItem *i )
+{
+ if ( !i ) return;
+
+ FileDetailViewItem *item = viewItem(i);
+ if ( !item ) return;
+
+ item->init();
+ setSortingKey( item, i );
+}
+
+
+void FileDetailView::setSortingKey( FileDetailViewItem *dvItem, const KFileItem *item)
+{
+ QDir::SortSpec spec = KFileView::sorting();
+ bool isDirOrArchive=item->isDir() || Archive::fileItemIsArchive(item);
+
+ QString key;
+ if ( spec & QDir::Time ) {
+ time_t time = TimeUtils::getTime(item);
+ key=sortingKey(time, isDirOrArchive, spec);
+
+ } else if ( spec & QDir::Size ) {
+ key=sortingKey( item->size(), isDirOrArchive, spec );
+
+ } else {
+ // Name or Unsorted
+ key=sortingKey( item->text(), isDirOrArchive, spec );
+ }
+
+ dvItem->setKey(key);
+}
+
+
+void FileDetailView::removeItem( const KFileItem *i )
+{
+ if ( !i ) return;
+
+ FileDetailViewItem *item = viewItem(i);
+ mResolver->m_lstPendingMimeIconItems.remove( item );
+ if(mShownFileItem==i) mShownFileItem=0L;
+ delete item;
+
+ KFileView::removeItem( i );
+}
+
+void FileDetailView::slotSortingChanged( int col )
+{
+ QDir::SortSpec sort = sorting();
+ int sortSpec = -1;
+ bool reversed = col == mSortingCol && (sort & QDir::Reversed) == 0;
+ mSortingCol = col;
+
+ switch( col ) {
+ case COL_NAME:
+ sortSpec = (sort & ~QDir::SortByMask | QDir::Name);
+ break;
+ case COL_SIZE:
+ sortSpec = (sort & ~QDir::SortByMask | QDir::Size);
+ break;
+ case COL_DATE:
+ sortSpec = (sort & ~QDir::SortByMask | QDir::Time);
+ break;
+
+ // the following columns have no equivalent in QDir, so we set it
+ // to QDir::Unsorted and remember the column (mSortingCol)
+ case COL_OWNER:
+ case COL_GROUP:
+ case COL_PERM:
+ // grmbl, QDir::Unsorted == SortByMask.
+ sortSpec = (sort & ~QDir::SortByMask);// | QDir::Unsorted;
+ break;
+ default:
+ break;
+ }
+
+ if ( reversed )
+ sortSpec |= QDir::Reversed;
+ else
+ sortSpec &= ~QDir::Reversed;
+
+ if ( sort & QDir::IgnoreCase )
+ sortSpec |= QDir::IgnoreCase;
+ else
+ sortSpec &= ~QDir::IgnoreCase;
+
+
+ KFileView::setSorting( static_cast<QDir::SortSpec>( sortSpec ) );
+
+ KFileItem *item;
+ KFileItemListIterator it( *items() );
+
+ for ( ; (item = it.current() ); ++it ) {
+ FileDetailViewItem* thumbItem=viewItem( item );
+ if (thumbItem) setSortingKey(thumbItem,item);
+ }
+
+ KListView::setSorting( mSortingCol, !reversed );
+ KListView::sort();
+
+ if (!mBlockSortingSignal) sig->changeSorting( static_cast<QDir::SortSpec>( sortSpec ) );
+}
+
+
+void FileDetailView::setSorting( QDir::SortSpec spec )
+{
+ int col = 0;
+ if ( spec & QDir::Time )
+ col = COL_DATE;
+ else if ( spec & QDir::Size )
+ col = COL_SIZE;
+ else if ( spec & QDir::Unsorted )
+ col = mSortingCol;
+ else
+ col = COL_NAME;
+
+ // inversed, because slotSortingChanged will reverse it
+ if ( spec & QDir::Reversed )
+ spec = (QDir::SortSpec) (spec & ~QDir::Reversed);
+ else
+ spec = (QDir::SortSpec) (spec | QDir::Reversed);
+
+ mSortingCol = col;
+ KFileView::setSorting( (QDir::SortSpec) spec );
+
+
+ // don't emit sortingChanged() when called via setSorting()
+ mBlockSortingSignal = true; // can't use blockSignals()
+ slotSortingChanged( col );
+ mBlockSortingSignal = false;
+}
+
+void FileDetailView::ensureItemVisible( const KFileItem *i )
+{
+ if ( !i ) return;
+
+ FileDetailViewItem *item = viewItem(i);
+
+ if ( item ) KListView::ensureItemVisible( item );
+}
+
+// we're in multiselection mode
+void FileDetailView::slotSelectionChanged()
+{
+ sig->highlightFile( 0L );
+}
+
+KFileItem * FileDetailView::firstFileItem() const
+{
+ FileDetailViewItem *item = static_cast<FileDetailViewItem*>( firstChild() );
+ if ( item ) return item->fileInfo();
+ return 0L;
+}
+
+KFileItem * FileDetailView::nextItem( const KFileItem *fileItem ) const
+{
+ if ( fileItem ) {
+ FileDetailViewItem *item = viewItem( fileItem );
+ if ( item && item->itemBelow() )
+ return ((FileDetailViewItem*) item->itemBelow())->fileInfo();
+ else
+ return 0L;
+ }
+ else
+ return firstFileItem();
+}
+
+KFileItem * FileDetailView::prevItem( const KFileItem *fileItem ) const
+{
+ if ( fileItem ) {
+ FileDetailViewItem *item = viewItem( fileItem );
+ if ( item && item->itemAbove() )
+ return ((FileDetailViewItem*) item->itemAbove())->fileInfo();
+ else
+ return 0L;
+ }
+ else
+ return firstFileItem();
+}
+
+void FileDetailView::keyPressEvent( QKeyEvent *e )
+{
+ KListView::keyPressEvent( e );
+
+ if ( e->key() == Key_Return || e->key() == Key_Enter ) {
+ if ( e->state() & ControlButton )
+ e->ignore();
+ else
+ e->accept();
+ }
+}
+
+//
+// mimetype determination on demand
+//
+void FileDetailView::mimeTypeDeterminationFinished()
+{
+ // anything to do?
+}
+
+void FileDetailView::determineIcon( FileDetailViewItem *item )
+{
+ (void) item->fileInfo()->determineMimeType();
+ updateView( item->fileInfo() );
+}
+
+void FileDetailView::listingCompleted()
+{
+ mResolver->start();
+}
+
+void FileDetailView::startDrag()
+{
+ /**
+ * The item drawer for DragPixmapGenerator
+ */
+ struct ItemDrawer : public DragPixmapItemDrawer<KFileItem*> {
+ ItemDrawer(const QFontMetrics& fontMetrics)
+ : mFontMetrics(fontMetrics) {}
+
+ QSize itemSize(KFileItem* fileItem) {
+ if (!fileItem) return QSize();
+ QString name = fileItem->name();
+ int width = QMIN(mGenerator->maxWidth(), mFontMetrics.width(name));
+ int height = mFontMetrics.height();
+ return QSize(width, height);
+ }
+
+ void drawItem(QPainter* painter, int left, int top, KFileItem* fileItem) {
+ QString name = fileItem->name();
+ painter->save();
+ KWordWrap::drawFadeoutText(painter,
+ left, top + mFontMetrics.ascent(),
+ mGenerator->maxWidth(), name);
+ painter->restore();
+ }
+
+ QFontMetrics mFontMetrics;
+ };
+ ItemDrawer drawer(fontMetrics());
+
+
+ KURL::List urls;
+ KFileItemListIterator it(*KFileView::selectedItems());
+
+ DragPixmapGenerator<KFileItem*> generator;
+ generator.setItemDrawer(&drawer);
+
+ for ( ; it.current(); ++it ) {
+ urls.append(it.current()->url());
+ generator.addItem(it.current());
+ }
+
+ if (urls.isEmpty()) {
+ kdWarning() << "No item to drag\n";
+ return;
+ }
+
+ QDragObject* drag=new KURLDrag(urls, this, 0);
+ QPixmap dragPixmap = generator.generate();
+
+ drag->setPixmap( dragPixmap, QPoint(-generator.DRAG_OFFSET, -generator.DRAG_OFFSET));
+ drag->dragCopy();
+}
+
+
+void FileDetailView::setShownFileItem(KFileItem* fileItem)
+{
+ if( fileItem == mShownFileItem ) return;
+ FileDetailViewItem* oldShownItem=viewItem(mShownFileItem);
+ FileDetailViewItem* newShownItem=viewItem(fileItem);
+
+ FileViewBase::setShownFileItem(fileItem);
+ if (oldShownItem) oldShownItem->repaint();
+ if (newShownItem) newShownItem->repaint();
+}
+
+
+//----------------------------------------------------------------------
+//
+// Drop support
+//
+//----------------------------------------------------------------------
+bool FileDetailView::acceptDrag(QDropEvent* event) const {
+ return KURLDrag::canDecode(event);
+}
+
+void FileDetailView::contentsDropEvent(QDropEvent *event) {
+ KFileItem* fileItem=0L;
+ QListViewItem *item=itemAt(contentsToViewport(event->pos() ) );
+
+ if (item) {
+ fileItem=static_cast<FileDetailViewItem*>(item)->fileInfo();
+ }
+ emit dropped(event,fileItem);
+}
+
+} // namespace
diff --git a/src/gvcore/filedetailview.h b/src/gvcore/filedetailview.h
new file mode 100644
index 0000000..12313f6
--- /dev/null
+++ b/src/gvcore/filedetailview.h
@@ -0,0 +1,133 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/* This file is based on kfiledetailview.h v1.30 from the KDE libs. Original
+ copyright follows.
+*/
+/* This file is part of the KDE libraries
+ Copyright (C) 1997 Stephan Kulow <coolo@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+
+#ifndef FILEDETAILVIEW_H
+#define FILEDETAILVIEW_H
+
+class KFileItem;
+class QWidget;
+class QKeyEvent;
+
+// Qt includes
+#include <qdir.h>
+
+// KDE includes
+#include <klistview.h>
+#include <kmimetyperesolver.h>
+
+// Our includes
+#include "fileviewbase.h"
+namespace Gwenview {
+
+class FileDetailViewItem;
+
+class FileDetailView : public KListView, public FileViewBase
+{
+ Q_OBJECT
+
+ friend class FileDetailViewItem;
+
+public:
+ FileDetailView(QWidget* parent, const char* name);
+ virtual ~FileDetailView();
+
+ virtual QWidget* widget() { return this; }
+ virtual void clearView();
+
+ virtual void updateView( bool );
+ virtual void updateView(const KFileItem*);
+ virtual void removeItem( const KFileItem* );
+ virtual void listingCompleted();
+
+ virtual void setSelected(const KFileItem* , bool);
+ virtual bool isSelected(const KFileItem* i) const;
+ virtual void clearSelection();
+ virtual void selectAll();
+ virtual void invertSelection();
+
+ virtual void setCurrentItem( const KFileItem* );
+ virtual KFileItem* currentFileItem() const;
+ virtual KFileItem* firstFileItem() const;
+ virtual KFileItem* nextItem( const KFileItem* ) const;
+ virtual KFileItem* prevItem( const KFileItem* ) const;
+
+ virtual void insertItem( KFileItem* i );
+
+ // implemented to get noticed about sorting changes (for sortingIndicator)
+ virtual void setSorting( QDir::SortSpec );
+
+ void ensureItemVisible( const KFileItem* );
+
+ // for KMimeTypeResolver
+ void mimeTypeDeterminationFinished();
+ void determineIcon( FileDetailViewItem* item );
+ QScrollView* scrollWidget() { return this; }
+
+ void setShownFileItem(KFileItem* fileItem);
+
+signals:
+ void dropped(QDropEvent* event, KFileItem* item);
+ void sortingChanged(QDir::SortSpec);
+
+protected:
+ virtual bool acceptDrag(QDropEvent*) const;
+ virtual void contentsDropEvent(QDropEvent*);
+ virtual void keyPressEvent(QKeyEvent*);
+
+ int mSortingCol;
+
+protected slots:
+ void slotSelectionChanged();
+
+private slots:
+ void slotSortingChanged( int );
+ void selected( QListViewItem* item );
+ void slotActivate( QListViewItem* item );
+ void highlighted( QListViewItem* item );
+ void slotActivateMenu ( QListViewItem* item, const QPoint& pos );
+
+private:
+ bool mBlockSortingSignal;
+ KMimeTypeResolver<FileDetailViewItem,FileDetailView>* mResolver;
+
+ virtual void insertItem(QListViewItem* i) { KListView::insertItem(i); }
+ virtual void setSorting(int i, bool b) { KListView::setSorting(i, b); }
+ virtual void setSelected(QListViewItem* i, bool b) { KListView::setSelected(i, b); }
+
+ FileDetailViewItem* viewItem( const KFileItem* item ) const {
+ if (item) return (FileDetailViewItem*)item->extraData(this);
+ return 0L;
+ }
+
+ void setSortingKey(FileDetailViewItem* item, const KFileItem* i);
+
+ void startDrag();
+
+ QPixmap mShownItemSelectedPixmap;
+ QPixmap mShownItemUnselectedPixmap;
+};
+
+
+} // namespace
+#endif
+
diff --git a/src/gvcore/filedetailviewitem.cpp b/src/gvcore/filedetailviewitem.cpp
new file mode 100644
index 0000000..d02427d
--- /dev/null
+++ b/src/gvcore/filedetailviewitem.cpp
@@ -0,0 +1,70 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/* This file is based on kfiledetailview.cpp from the KDE libs. Original
+ copyright follows.
+*/
+/* This file is part of the KDE libraries
+ Copyright (C) 1997 Stephan Kulow <coolo@kde.org>
+ 2000, 2001 Carsten Pfeiffer <pfeiffer@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+
+// KDE includes
+#include <kglobal.h>
+#include <klocale.h>
+
+// Our includes
+#include "filedetailviewitem.h"
+#include "filedetailview.h"
+#include "timeutils.h"
+namespace Gwenview {
+
+
+void FileDetailViewItem::init()
+{
+ time_t time = TimeUtils::getTime(inf);
+ setPixmap( COL_NAME, inf->pixmap(KIcon::SizeSmall));
+
+ setText( COL_NAME, inf->text() );
+ setText( COL_SIZE, KGlobal::locale()->formatNumber( inf->size(), 0));
+ setText( COL_DATE, TimeUtils::formatTime(time) );
+ setText( COL_PERM, inf->permissionsString() );
+ setText( COL_OWNER, inf->user() );
+ setText( COL_GROUP, inf->group() );
+}
+
+
+const QPixmap* FileDetailViewItem::pixmap(int column) const {
+ const QPixmap* normalPix = KListViewItem::pixmap(column);
+ if (column!=0) {
+ return normalPix;
+ }
+
+ FileDetailView* view=static_cast<FileDetailView*>(listView());
+ FileDetailViewItem* viewedItem=view->viewItem(view->shownFileItem());
+ if (viewedItem!=this) {
+ return normalPix;
+ }
+
+ if (isSelected()) {
+ return &view->mShownItemSelectedPixmap;
+ } else {
+ return &view->mShownItemUnselectedPixmap;
+ }
+}
+
+
+} // namespace
diff --git a/src/gvcore/filedetailviewitem.h b/src/gvcore/filedetailviewitem.h
new file mode 100644
index 0000000..2e4c8ff
--- /dev/null
+++ b/src/gvcore/filedetailviewitem.h
@@ -0,0 +1,89 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/* This file is based on kfiledetailview.h from the KDE libs. Original
+ copyright follows.
+*/
+/* This file is part of the KDE libraries
+ Copyright (C) 1997 Stephan Kulow <coolo@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+
+#ifndef FILEDETAILVIEWITEM_H
+#define FILEDETAILVIEWITEM_H
+
+// KDE includes
+#include <klistview.h>
+#include <kfileitem.h>
+namespace Gwenview {
+
+#define COL_NAME 0
+#define COL_SIZE 1
+#define COL_DATE 2
+#define COL_PERM 3
+#define COL_OWNER 4
+#define COL_GROUP 5
+
+class FileDetailViewItem : public KListViewItem
+{
+public:
+ FileDetailViewItem( QListView* parent, const QString &text,
+ const QPixmap &icon, KFileItem* fi )
+ : KListViewItem( parent, text ), inf( fi ) {
+ setPixmap( 0, icon );
+ setText( 0, text );
+ }
+
+ FileDetailViewItem( QListView* parent, KFileItem* fi )
+ : KListViewItem( parent ), inf( fi ) {
+ init();
+ }
+
+ FileDetailViewItem( QListView* parent, const QString &text,
+ const QPixmap &icon, KFileItem* fi,
+ QListViewItem* after)
+ : KListViewItem( parent, after ), inf( fi ) {
+ setPixmap( 0, icon );
+ setText( 0, text );
+ }
+
+ ~FileDetailViewItem() {
+ inf->removeExtraData( listView() );
+ }
+
+ KFileItem* fileInfo() const { return inf; }
+
+ virtual QString key( int /*column*/, bool /*ascending*/ ) const { return m_key; }
+
+ void setKey( const QString& key ) { m_key = key; }
+
+ QRect rect() const
+ {
+ QRect r = listView()->itemRect(this);
+ return QRect( listView()->viewportToContents( r.topLeft() ),
+ QSize( r.width(), r.height() ) );
+ }
+
+ void init();
+ virtual const QPixmap* pixmap(int column) const;
+
+private:
+ KFileItem* inf;
+ QString m_key;
+};
+
+} // namespace
+#endif
+
diff --git a/src/gvcore/fileoperation.cpp b/src/gvcore/fileoperation.cpp
new file mode 100644
index 0000000..99b015b
--- /dev/null
+++ b/src/gvcore/fileoperation.cpp
@@ -0,0 +1,119 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2000-2004 Aurélien Gâteau
+
+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.
+
+*/
+// Self
+#include "fileoperation.moc"
+
+// Qt
+#include <qcursor.h>
+#include <qpopupmenu.h>
+#include <qobject.h>
+
+// KDE
+#include <kconfig.h>
+#include <kiconloader.h>
+#include <klocale.h>
+
+// Local
+#include "fileopobject.h"
+#include "fileoperationconfig.h"
+
+namespace Gwenview {
+
+
+namespace FileOperation {
+
+void copyTo(const KURL::List& srcURL,QWidget* parent) {
+ FileOpObject* op=new FileOpCopyToObject(srcURL,parent);
+ (*op)();
+}
+
+void linkTo(const KURL::List& srcURL,QWidget* parent) {
+ FileOpObject* op=new FileOpLinkToObject(srcURL,parent);
+ (*op)();
+}
+
+void moveTo(const KURL::List& srcURL,QWidget* parent,QObject* receiver,const char* slot) {
+ FileOpObject* op=new FileOpMoveToObject(srcURL,parent);
+ if (receiver && slot) QObject::connect(op,SIGNAL(success()),receiver,slot);
+ (*op)();
+}
+
+void makeDir(const KURL& parentURL, QWidget* parent, QObject* receiver, const char* slot) {
+ FileOpObject* op=new FileOpMakeDirObject(parentURL, parent);
+ if (receiver && slot) QObject::connect(op,SIGNAL(success()),receiver,slot);
+ (*op)();
+}
+
+void del(const KURL::List& url,QWidget* parent,QObject* receiver,const char* slot) {
+ FileOpObject* op = new FileOpDelObject(url,parent);
+ if (receiver && slot) QObject::connect(op,SIGNAL(success()),receiver,slot);
+ (*op)();
+}
+
+
+void trash(const KURL::List& url, QWidget* parent, QObject* receiver, const char* slot) {
+ FileOpObject* op = new FileOpTrashObject(url,parent);
+ if (receiver && slot) QObject::connect(op,SIGNAL(success()),receiver,slot);
+ (*op)();
+}
+
+
+void realDelete(const KURL::List& url, QWidget* parent, QObject* receiver, const char* slot) {
+ FileOpObject* op = new FileOpRealDeleteObject(url,parent);
+ if (receiver && slot) QObject::connect(op,SIGNAL(success()),receiver,slot);
+ (*op)();
+}
+
+
+void rename(const KURL& url,QWidget* parent,QObject* receiver,const char* slot) {
+ FileOpObject* op=new FileOpRenameObject(url,parent);
+ if (receiver && slot) QObject::connect(op,SIGNAL(renamed(const QString&)),receiver,slot);
+ (*op)();
+}
+
+
+void fillDropURLMenu(QPopupMenu* menu, const KURL::List& urls, const KURL& target, bool* wasMoved) {
+ DropMenuContext* context=new DropMenuContext(menu, urls, target, wasMoved);
+ menu->insertItem( SmallIcon("goto"), i18n("&Move Here"),
+ context, SLOT(move()) );
+ menu->insertItem( SmallIcon("editcopy"), i18n("&Copy Here"),
+ context, SLOT(copy()) );
+ menu->insertItem( SmallIcon("www"), i18n("&Link Here"),
+ context, SLOT(link()) );
+}
+
+
+void openDropURLMenu(QWidget* parent, const KURL::List& urls, const KURL& target, bool* wasMoved) {
+ QPopupMenu menu(parent);
+ if (wasMoved) *wasMoved=false;
+
+ fillDropURLMenu(&menu, urls, target, wasMoved);
+ menu.insertSeparator();
+ menu.insertItem( SmallIcon("cancel"), i18n("Cancel") );
+
+ menu.setMouseTracking(true);
+ menu.exec(QCursor::pos());
+}
+
+
+} // namespace FileOperation
+
+} // namespace
diff --git a/src/gvcore/fileoperation.h b/src/gvcore/fileoperation.h
new file mode 100644
index 0000000..bb71ed6
--- /dev/null
+++ b/src/gvcore/fileoperation.h
@@ -0,0 +1,95 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2000-2004 Aurélien Gâteau
+
+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.
+
+*/
+#ifndef FILEOPERATION_H
+#define FILEOPERATION_H
+
+// KDE
+#include <kurl.h>
+#include <kio/job.h>
+
+#include "libgwenview_export.h"
+
+class QPopupMenu;
+class QWidget;
+
+namespace Gwenview {
+/**
+ * This namespace handles all steps of a file operation :
+ * - asking the user what to do with a file
+ * - performing the operation
+ * - showing result dialogs
+ */
+namespace FileOperation {
+
+LIBGWENVIEW_EXPORT void copyTo(const KURL::List&,QWidget* parent=0L);
+LIBGWENVIEW_EXPORT void moveTo(const KURL::List&,QWidget* parent,QObject* receiver=0L,const char* slot=0L);
+LIBGWENVIEW_EXPORT void linkTo(const KURL::List& srcURL,QWidget* parent);
+LIBGWENVIEW_EXPORT void makeDir(const KURL& parentURL, QWidget* parent, QObject* receiver=0L, const char* slot=0L);
+LIBGWENVIEW_EXPORT void del(const KURL::List&,QWidget* parent,QObject* receiver=0L,const char* slot=0L);
+LIBGWENVIEW_EXPORT void trash(const KURL::List&,QWidget* parent,QObject* receiver=0L,const char* slot=0L);
+LIBGWENVIEW_EXPORT void realDelete(const KURL::List&,QWidget* parent,QObject* receiver=0L,const char* slot=0L);
+LIBGWENVIEW_EXPORT void rename(const KURL&,QWidget* parent,QObject* receiver=0L,const char* slot=0L);
+
+
+/**
+ * @internal
+ */
+class DropMenuContext : public QObject {
+Q_OBJECT
+public:
+ DropMenuContext(QObject* parent, const KURL::List& src, const KURL& dst, bool* wasMoved)
+ : QObject(parent)
+ , mSrc(src)
+ , mDst(dst)
+ , mWasMoved(wasMoved)
+ {
+ if (mWasMoved) *mWasMoved=false;
+ }
+
+public slots:
+ void copy() {
+ KIO::copy(mSrc, mDst, true);
+ }
+
+ void move() {
+ KIO::move(mSrc, mDst, true);
+ if (mWasMoved) *mWasMoved=true;
+ }
+
+ void link() {
+ KIO::link(mSrc, mDst, true);
+ }
+
+private:
+ KURL::List mSrc;
+ KURL mDst;
+ bool* mWasMoved;
+};
+
+
+LIBGWENVIEW_EXPORT void fillDropURLMenu(QPopupMenu*, const KURL::List&, const KURL& target, bool* wasMoved=0L);
+LIBGWENVIEW_EXPORT void openDropURLMenu(QWidget* parent, const KURL::List&, const KURL& target, bool* wasMoved=0L);
+
+} // namespace
+
+} // namespace
+#endif
+
diff --git a/src/gvcore/fileoperationconfig.kcfg b/src/gvcore/fileoperationconfig.kcfg
new file mode 100644
index 0000000..c876396
--- /dev/null
+++ b/src/gvcore/fileoperationconfig.kcfg
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE kcfg SYSTEM "http://www.kde.org/standards/kcfg/1.0/kcfg.dtd">
+<kcfg>
+ <kcfgfile name="gwenviewrc"/>
+ <group name="file operations">
+ <entry name="deleteToTrash" key="delete to trash" type="Bool">
+ <default>true</default>
+ </entry>
+ <entry name="confirmDelete" key="confirm file delete" type="Bool">
+ <default>true</default>
+ </entry>
+ <entry name="confirmMove" key="confirm file move" type="Bool">
+ <default>true</default>
+ </entry>
+ <entry name="confirmCopy" key="confirm file copy" type="Bool">
+ <default>true</default>
+ </entry>
+
+ <entry name="destDir" key="destination dir" type="Path">
+ </entry>
+ </group>
+</kcfg>
diff --git a/src/gvcore/fileoperationconfig.kcfgc b/src/gvcore/fileoperationconfig.kcfgc
new file mode 100644
index 0000000..1b236c8
--- /dev/null
+++ b/src/gvcore/fileoperationconfig.kcfgc
@@ -0,0 +1,7 @@
+File=fileoperationconfig.kcfg
+ClassName=FileOperationConfig
+NameSpace=Gwenview
+Singleton=true
+Mutators=true
+IncludeFiles=gvcore/libgwenview_export.h
+Visibility=LIBGWENVIEW_EXPORT
diff --git a/src/gvcore/fileopobject.cpp b/src/gvcore/fileopobject.cpp
new file mode 100644
index 0000000..c480156
--- /dev/null
+++ b/src/gvcore/fileopobject.cpp
@@ -0,0 +1,347 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2000-2004 Aur�ien G�eau
+
+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.
+
+*/
+
+// Qt
+#include <qfile.h>
+#include <qstylesheet.h>
+#include <qwidget.h>
+
+// KDE
+#include <kdeversion.h>
+#include <kfiledialog.h>
+#include <kfilefiltercombo.h>
+#include <kglobalsettings.h>
+#include <klineedit.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kstandarddirs.h>
+#include <kurlcombobox.h>
+
+// Local
+#include "deletedialog.h"
+#include "fileoperation.h"
+#include "fileopobject.moc"
+#include "fileoperationconfig.h"
+#include "inputdialog.h"
+namespace Gwenview {
+
+
+/**
+ * A tweaked KFileDialog used to select an existing directory. More efficient
+ * than KDirSelectDialog, since it provides access to bookmarks and let you
+ * create a dir.
+ */
+class DirSelectDialog : public KFileDialog {
+public:
+ DirSelectDialog(const QString& startDir, QWidget* parent)
+ : KFileDialog(startDir, QString::null, parent, "dirselectdialog", true) {
+ locationEdit->setEnabled(false);
+ filterWidget->setEnabled(false);
+ setMode(KFile::Directory | KFile::ExistingOnly);
+
+ // Cast to avoid gcc being confused
+ setPreviewWidget(static_cast<KPreviewWidgetBase*>(0));
+ }
+};
+
+
+//-FileOpObject--------------------------------------------------------------------
+FileOpObject::FileOpObject(const KURL& url,QWidget* parent)
+: mParent(parent)
+{
+ mURLList.append(url);
+}
+
+
+FileOpObject::FileOpObject(const KURL::List& list,QWidget* parent)
+: mParent(parent), mURLList(list)
+{}
+
+
+void FileOpObject::slotResult(KIO::Job* job) {
+ if (job->error()) {
+ job->showErrorDialog(mParent);
+ }
+
+ emit success();
+
+// Let's shoot ourself in the foot...
+ delete this;
+}
+
+
+void FileOpObject::polishJob(KIO::Job* job) {
+ job->setWindow(mParent->topLevelWidget());
+ connect( job, SIGNAL( result(KIO::Job*) ),
+ this, SLOT( slotResult(KIO::Job*) ) );
+}
+
+
+//-FileOpCopyToObject--------------------------------------------------------------
+
+
+void FileOpCopyToObject::operator()() {
+ KURL destURL;
+
+ if (FileOperationConfig::confirmCopy()) {
+ QString destDir = FileOperationConfig::destDir();
+ if( !destDir.isEmpty()) {
+ destDir += "/";
+ }
+ if (mURLList.size()==1) {
+ destURL=KFileDialog::getSaveURL(destDir + mURLList.first().fileName(),
+ QString::null, mParent, i18n("Copy File"));
+ } else {
+ DirSelectDialog dialog(destDir, mParent);
+ dialog.setCaption(i18n("Select Folder Where Files Will be Copied"));
+ dialog.exec();
+ destURL=dialog.selectedURL();
+ }
+ } else {
+ destURL.setPath(FileOperationConfig::destDir());
+ }
+ if (destURL.isEmpty()) return;
+
+// Copy the file
+ KIO::Job* job=KIO::copy(mURLList,destURL,true);
+ polishJob(job);
+
+}
+
+
+//-FileOpCopyToObject--------------------------------------------------------------
+
+
+void FileOpLinkToObject::operator()() {
+ KURL destURL;
+
+ if (FileOperationConfig::confirmCopy()) {
+ QString destDir = FileOperationConfig::destDir();
+ if( !destDir.isEmpty()) {
+ destDir += "/";
+ }
+ if (mURLList.size()==1) {
+ destURL=KFileDialog::getSaveURL(destDir + mURLList.first().fileName(),
+ QString::null, mParent, i18n("Link File"));
+ } else {
+ DirSelectDialog dialog(destDir, mParent);
+ dialog.setCaption(i18n("Select Folder Where the Files Will be Linked"));
+ dialog.exec();
+ destURL=dialog.selectedURL();
+ }
+ } else {
+ destURL.setPath(FileOperationConfig::destDir());
+ }
+ if (destURL.isEmpty()) return;
+
+// Copy the file
+ KIO::Job* job=KIO::link(mURLList,destURL,true);
+ polishJob(job);
+}
+
+
+//-FileOpMoveToObject--------------------------------------------------------------
+void FileOpMoveToObject::operator()() {
+ KURL destURL;
+
+ if (FileOperationConfig::confirmMove()) {
+ QString destDir = FileOperationConfig::destDir();
+ if( !destDir.isEmpty()) {
+ destDir += "/";
+ }
+ if (mURLList.size()==1) {
+ destURL=KFileDialog::getSaveURL(destDir + mURLList.first().fileName(),
+ QString::null, mParent, i18n("Move File"));
+ } else {
+ DirSelectDialog dialog(destDir, mParent);
+ dialog.setCaption(i18n("Select Folder Where Files Will be Moved"));
+ dialog.exec();
+ destURL=dialog.selectedURL();
+ }
+ } else {
+ destURL.setPath(FileOperationConfig::destDir());
+ }
+ if (destURL.isEmpty()) return;
+
+// Move the file
+ KIO::Job* job=KIO::move(mURLList,destURL,true);
+ polishJob(job);
+}
+
+
+//-FileOpMakeDirObject-------------------------------------------------------------
+void FileOpMakeDirObject::operator()() {
+ InputDialog dlg(mParent);
+ dlg.setCaption( i18n("Creating Folder") );
+ dlg.setLabel( i18n("Enter the name of the new folder:") );
+ dlg.setButtonOK( KGuiItem(i18n("Create Folder"), "folder_new") );
+ if (!dlg.exec()) return;
+
+ QString newDir = dlg.lineEdit()->text();
+
+ KURL newURL(mURLList.first());
+ newURL.addPath(newDir);
+ KIO::Job* job=KIO::mkdir(newURL);
+ polishJob(job);
+}
+
+
+static KIO::Job* createTrashJob(KURL::List lst) {
+ KURL trashURL("trash:/");
+ // Go do it
+ if (lst.count()==1) {
+ // If there's only one file, KIO::move will think we want to overwrite
+ // the trash dir with the file to trash, so we add the file name
+ trashURL.addPath(lst.first().fileName());
+ }
+ return KIO::move(lst, trashURL);
+}
+
+static KIO::Job* createDeleteJob(KURL::List lst) {
+ return KIO::del(lst, false, true);
+}
+
+
+//-FileOpDelObject-----------------------------------------------------------------
+void FileOpDelObject::operator()() {
+ bool shouldDelete;
+ if (FileOperationConfig::confirmDelete()) {
+ DeleteDialog dlg(mParent);
+ dlg.setURLList(mURLList);
+ if (!dlg.exec()) return;
+ shouldDelete = dlg.shouldDelete();
+ } else {
+ shouldDelete = not FileOperationConfig::deleteToTrash();
+ }
+
+
+ KIO::Job* job;
+ if (shouldDelete) {
+ job = createDeleteJob(mURLList);
+ } else {
+ job = createTrashJob(mURLList);
+ }
+ polishJob(job);
+}
+
+
+//-FileOpTrashObject---------------------------------------------------------------
+void FileOpTrashObject::operator()() {
+ // Confirm operation
+ if (FileOperationConfig::confirmDelete()) {
+ int response;
+ if (mURLList.count()>1) {
+ QStringList fileList;
+ KURL::List::ConstIterator it=mURLList.begin();
+ for (; it!=mURLList.end(); ++it) {
+ fileList.append((*it).filename());
+ }
+ response=KMessageBox::warningContinueCancelList(mParent,
+ i18n("Do you really want to trash these files?"),fileList,i18n("Trash used as a verb", "Trash Files"),KGuiItem(i18n("Trash used as a verb", "&Trash"),"edittrash"));
+ } else {
+ QString filename=QStyleSheet::escape(mURLList.first().filename());
+ response=KMessageBox::warningContinueCancel(mParent,
+ i18n("<p>Do you really want to move <b>%1</b> to the trash?</p>").arg(filename),i18n("Trash used as a verb", "Trash File"),KGuiItem(i18n("Trash used as a verb", "&Trash"),"edittrash"));
+ }
+ if (response!=KMessageBox::Continue) return;
+ }
+
+ KIO::Job* job = createTrashJob(mURLList);
+ polishJob(job);
+}
+
+//-FileOpRealDeleteObject----------------------------------------------------------
+void FileOpRealDeleteObject::operator()() {
+ // Confirm operation
+ if (FileOperationConfig::confirmDelete()) {
+ int response;
+ if (mURLList.count()>1) {
+ QStringList fileList;
+ KURL::List::ConstIterator it=mURLList.begin();
+ for (; it!=mURLList.end(); ++it) {
+ fileList.append((*it).filename());
+ }
+ response=KMessageBox::warningContinueCancelList(mParent,
+ i18n("Do you really want to delete these files?"),fileList,
+ i18n("Delete Files"),
+ KStdGuiItem::del()
+ );
+ } else {
+ QString filename=QStyleSheet::escape(mURLList.first().filename());
+ response=KMessageBox::warningContinueCancel(mParent,
+ i18n("<p>Do you really want to delete <b>%1</b>?</p>").arg(filename),
+ i18n("Delete File"),
+ KStdGuiItem::del()
+ );
+ }
+ if (response!=KMessageBox::Continue) return;
+ }
+
+ // Delete the file
+ KIO::Job* job = createDeleteJob(mURLList);
+ polishJob(job);
+}
+
+
+//-FileOpRenameObject--------------------------------------------------------------
+void FileOpRenameObject::operator()() {
+ KURL srcURL=mURLList.first();
+
+ // Prompt for the new filename
+ QString filename = srcURL.filename();
+ InputDialog dlg(mParent);
+ dlg.setCaption(i18n("Renaming File"));
+ dlg.setLabel(i18n("<p>Rename file <b>%1</b> to:</p>").arg(QStyleSheet::escape(filename)));
+ dlg.setButtonOK( KGuiItem(i18n("&Rename"), "edit") );
+
+ dlg.lineEdit()->setText(filename);
+ int extPos = filename.findRev('.');
+ if (extPos != -1) {
+ if (filename.mid(extPos - 4, 4) == ".tar") {
+ // Special case: *.tar.*
+ extPos -= 4;
+ }
+ dlg.lineEdit()->setSelection(0, extPos);
+ }
+ if (!dlg.exec()) return;
+ mNewFilename = dlg.lineEdit()->text();
+
+ // Rename the file
+ KURL destURL=srcURL;
+ destURL.setFileName(mNewFilename);
+ KIO::Job* job=KIO::move(srcURL,destURL);
+ polishJob(job);
+}
+
+
+void FileOpRenameObject::slotResult(KIO::Job* job) {
+ if (job->error()) {
+ job->showErrorDialog(mParent);
+ }
+
+ emit success();
+ emit renamed(mNewFilename);
+
+// Let's shoot ourself in the foot...
+ delete this;
+}
+
+} // namespace
diff --git a/src/gvcore/fileopobject.h b/src/gvcore/fileopobject.h
new file mode 100644
index 0000000..d26f763
--- /dev/null
+++ b/src/gvcore/fileopobject.h
@@ -0,0 +1,143 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2000-2004 Aurélien Gâteau
+
+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.
+
+*/
+#ifndef FILEOPOBJECT_H
+#define FILEOPOBJECT_H
+
+// Qt includes
+#include <qobject.h>
+#include <qstring.h>
+
+// KDE includes
+#include <kio/job.h>
+#include <kurl.h>
+
+class QWidget;
+
+
+namespace Gwenview {
+/**
+ * This class is a base class for wrappers to KIO slaves asynchronous
+ * file operations. These classes handle all steps of a file operation :
+ * - asking the user what to do with a file
+ * - performing the operation
+ * - showing result dialogs
+ *
+ * All these classes are used by the @FileOperation namespace-like class
+ */
+class FileOpObject : public QObject {
+Q_OBJECT
+public:
+ FileOpObject(const KURL&,QWidget* parent=0L);
+ FileOpObject(const KURL::List&,QWidget* parent=0L);
+ virtual void operator()()=0;
+
+signals:
+ void success();
+
+protected slots:
+ virtual void slotResult(KIO::Job*);
+
+protected:
+ QWidget* mParent;
+ KURL::List mURLList;
+
+ void polishJob(KIO::Job*);
+};
+
+
+class FileOpCopyToObject : public FileOpObject {
+Q_OBJECT
+public:
+ FileOpCopyToObject(const KURL& url,QWidget* parent=0L) : FileOpObject(url,parent) {}
+ FileOpCopyToObject(const KURL::List& urlList,QWidget* parent=0L) : FileOpObject(urlList,parent) {}
+ void operator()();
+};
+
+class FileOpLinkToObject : public FileOpObject {
+Q_OBJECT
+public:
+ FileOpLinkToObject(const KURL& url,QWidget* parent=0L) : FileOpObject(url,parent) {}
+ FileOpLinkToObject(const KURL::List& urlList,QWidget* parent=0L) : FileOpObject(urlList,parent) {}
+ void operator()();
+};
+
+class FileOpMoveToObject : public FileOpObject {
+Q_OBJECT
+public:
+ FileOpMoveToObject(const KURL& url,QWidget* parent=0L) : FileOpObject(url,parent) {}
+ FileOpMoveToObject(const KURL::List& urlList,QWidget* parent=0L) : FileOpObject(urlList,parent) {}
+ void operator()();
+};
+
+class FileOpMakeDirObject : public FileOpObject {
+Q_OBJECT
+public:
+ FileOpMakeDirObject(const KURL& url, QWidget* parent=0L) : FileOpObject(url, parent) {}
+ void operator()();
+};
+
+class FileOpDelObject : public FileOpObject {
+Q_OBJECT
+public:
+ FileOpDelObject(const KURL& url,QWidget* parent=0L) : FileOpObject(url,parent) {}
+ FileOpDelObject(const KURL::List& urlList,QWidget* parent=0L) : FileOpObject(urlList,parent) {}
+ void operator()();
+};
+
+
+class FileOpTrashObject : public FileOpObject {
+Q_OBJECT
+public:
+ FileOpTrashObject(const KURL& url,QWidget* parent=0L) : FileOpObject(url,parent) {}
+ FileOpTrashObject(const KURL::List& urlList,QWidget* parent=0L) : FileOpObject(urlList,parent) {}
+ void operator()();
+};
+
+
+class FileOpRealDeleteObject : public FileOpObject {
+Q_OBJECT
+public:
+ FileOpRealDeleteObject(const KURL& url,QWidget* parent=0L) : FileOpObject(url,parent) {}
+ FileOpRealDeleteObject(const KURL::List& urlList,QWidget* parent=0L) : FileOpObject(urlList,parent) {}
+ void operator()();
+};
+
+
+class FileOpRenameObject : public FileOpObject {
+Q_OBJECT
+public:
+ FileOpRenameObject(const KURL& url,QWidget* parent=0L) : FileOpObject(url,parent) {}
+ void operator()();
+
+signals:
+ void renamed(const QString& newName);
+
+protected slots:
+ virtual void slotResult(KIO::Job*);
+
+private:
+ QString mNewFilename;
+};
+
+
+} // namespace
+#endif
+
diff --git a/src/gvcore/filethumbnailview.cpp b/src/gvcore/filethumbnailview.cpp
new file mode 100644
index 0000000..d9d3ca2
--- /dev/null
+++ b/src/gvcore/filethumbnailview.cpp
@@ -0,0 +1,866 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab:
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2000-2004 Aurélien Gâteau
+
+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.
+
+*/
+
+// Qt
+#include <qframe.h>
+#include <qlayout.h>
+#include <qpainter.h>
+#include <qpen.h>
+#include <qpixmap.h>
+#include <qpushbutton.h>
+#include <qtimer.h>
+#include <qvaluevector.h>
+
+// KDE
+#include <kapplication.h>
+#include <kconfig.h>
+#include <kdebug.h>
+#include <kglobalsettings.h>
+#include <kiconloader.h>
+#include <klocale.h>
+#include <kprogress.h>
+#include <kstandarddirs.h>
+#include <kurldrag.h>
+#include <kwordwrap.h>
+
+// Local
+#include "fileviewconfig.h"
+#include "filethumbnailviewitem.h"
+#include "archive.h"
+#include "dragpixmapgenerator.h"
+#include "thumbnailloadjob.h"
+#include "busylevelmanager.h"
+#include "imageloader.h"
+#include "timeutils.h"
+#include "thumbnailsize.h"
+#include "thumbnaildetailsdialog.h"
+
+#undef ENABLE_LOG
+#undef LOG
+#define ENABLE_LOG
+#ifdef ENABLE_LOG
+#define LOG(x) kdDebug() << k_funcinfo << x << endl
+#else
+#define LOG(x) ;
+#endif
+
+#include "filethumbnailview.moc"
+namespace Gwenview {
+
+static const int THUMBNAIL_UPDATE_DELAY=500;
+
+static const int RIGHT_TEXT_WIDTH=128;
+static const int BOTTOM_MIN_TEXT_WIDTH=96;
+
+class ProgressWidget : public QFrame {
+ KProgress* mProgressBar;
+ QPushButton* mStop;
+public:
+ ProgressWidget(FileThumbnailView* view, int count)
+ : QFrame(view)
+ {
+ QHBoxLayout* layout=new QHBoxLayout(this, 3, 3);
+ layout->setAutoAdd(true);
+ setFrameStyle( QFrame::StyledPanel | QFrame::Raised );
+
+ mStop=new QPushButton(this);
+ mStop->setPixmap(SmallIcon("stop"));
+ mStop->setFlat(true);
+
+ mProgressBar=new KProgress(count, this);
+ mProgressBar->setFormat("%v/%m");
+
+ view->clipper()->installEventFilter(this);
+ }
+
+ void polish() {
+ QFrame::polish();
+ setMinimumWidth(layout()->minimumSize().width());
+ //setFixedHeight( mProgressBar->height() );
+ setFixedHeight( mStop->height() );
+ }
+
+ void showEvent(QShowEvent*) {
+ updatePosition();
+ }
+
+ bool eventFilter(QObject*, QEvent* event) {
+ if (event->type()==QEvent::Resize) {
+ updatePosition();
+ }
+ return false;
+ }
+
+ void updatePosition() {
+ FileThumbnailView* view=static_cast<FileThumbnailView*>(parent());
+ QSize tmp=view->clipper()->size() - size();
+ move(tmp.width() - 2, tmp.height() - 2);
+ }
+
+ KProgress* progressBar() const { return mProgressBar; }
+ QPushButton* stopButton() const { return mStop; }
+};
+
+
+struct FileThumbnailView::Private {
+ int mThumbnailSize;
+ int mMarginSize;
+ bool mUpdateThumbnailsOnNextShow;
+ QPixmap mWaitPixmap; // The wait pixmap (32 x 32)
+ QPixmap mWaitThumbnail; // The wait thumbnail (mThumbnailSize x mThumbnailSize)
+ ProgressWidget* mProgressWidget;
+
+ QGuardedPtr<ThumbnailLoadJob> mThumbnailLoadJob;
+
+ QTimer* mThumbnailUpdateTimer;
+
+ int mItemDetails;
+
+ ImageLoader* mPrefetch;
+ ThumbnailDetailsDialog* mThumbnailsDetailDialog;
+
+ void updateWaitThumbnail(const FileThumbnailView* view) {
+ mWaitThumbnail=QPixmap(mThumbnailSize, mThumbnailSize);
+ mWaitThumbnail.fill(view->paletteBackgroundColor());
+ QPainter painter(&mWaitThumbnail);
+
+ painter.setPen(view->colorGroup().button());
+ painter.drawRect(0,0,mThumbnailSize,mThumbnailSize);
+ painter.drawPixmap(
+ (mThumbnailSize-mWaitPixmap.width())/2,
+ (mThumbnailSize-mWaitPixmap.height())/2,
+ mWaitPixmap);
+ painter.end();
+ }
+};
+
+
+static FileThumbnailViewItem* viewItem(const FileThumbnailView* view, const KFileItem* fileItem) {
+ if (!fileItem) return 0L;
+ return static_cast<FileThumbnailViewItem*>( const_cast<void*>(fileItem->extraData(view) ) );
+}
+
+
+FileThumbnailView::FileThumbnailView(QWidget* parent)
+: KIconView(parent), FileViewBase()
+{
+ d=new Private;
+ d->mUpdateThumbnailsOnNextShow=false;
+ d->mThumbnailLoadJob=0L;
+ d->mWaitPixmap=QPixmap(::locate("appdata", "thumbnail/wait.png"));
+ d->mProgressWidget=0L;
+ d->mThumbnailUpdateTimer=new QTimer(this);
+ d->mMarginSize=FileViewConfig::thumbnailMarginSize();
+ d->mItemDetails=FileViewConfig::thumbnailDetails();
+ d->mPrefetch = NULL;
+ d->mThumbnailSize = 0;
+ d->mThumbnailsDetailDialog = 0;
+
+ setItemTextPos( QIconView::ItemTextPos(FileViewConfig::thumbnailTextPos()) );
+ setAutoArrange(true);
+ QIconView::setSorting(true);
+ setItemsMovable(false);
+ setResizeMode(Adjust);
+ setShowToolTips(true);
+ setSpacing(0);
+ setAcceptDrops(true);
+
+ // We can't use KIconView::Execute mode because in this mode the current
+ // item is unselected after being clicked, so we use KIconView::Select mode
+ // and emit the execute() signal with slotClicked() ourself.
+ setMode(KIconView::Select);
+ connect(this, SIGNAL(clicked(QIconViewItem*)),
+ this, SLOT(slotClicked(QIconViewItem*)) );
+ connect(this, SIGNAL(doubleClicked(QIconViewItem*)),
+ this, SLOT(slotDoubleClicked(QIconViewItem*)) );
+
+ connect(this, SIGNAL(dropped(QDropEvent*,const QValueList<QIconDragItem>&)),
+ this, SLOT(slotDropped(QDropEvent*)) );
+ connect(this, SIGNAL( contentsMoving( int, int )),
+ this, SLOT( slotContentsMoving( int, int )));
+ connect(this, SIGNAL(currentChanged(QIconViewItem*)),
+ this, SLOT(slotCurrentChanged(QIconViewItem*)) );
+
+ QIconView::setSelectionMode(Extended);
+
+ connect(BusyLevelManager::instance(), SIGNAL(busyLevelChanged(BusyLevel)),
+ this, SLOT( slotBusyLevelChanged(BusyLevel)));
+
+ connect(d->mThumbnailUpdateTimer, SIGNAL(timeout()),
+ this, SLOT( startThumbnailUpdate()) );
+}
+
+
+FileThumbnailView::~FileThumbnailView() {
+ stopThumbnailUpdate();
+ FileViewConfig::setThumbnailDetails(d->mItemDetails);
+ FileViewConfig::setThumbnailTextPos( int(itemTextPos()) );
+ FileViewConfig::writeConfig();
+ delete d;
+}
+
+
+void FileThumbnailView::setThumbnailSize(int value) {
+ if (value==d->mThumbnailSize) return;
+ d->mThumbnailSize=value;
+ updateGrid();
+
+ KFileItemListIterator it( *items() );
+ for ( ; it.current(); ++it ) {
+ KFileItem *item=it.current();
+ QPixmap pixmap=createItemPixmap(item);
+ QIconViewItem* iconItem=viewItem(this, item);
+ if (iconItem) iconItem->setPixmap(pixmap);
+ }
+ arrangeItemsInGrid();
+ d->mThumbnailUpdateTimer->start(THUMBNAIL_UPDATE_DELAY, true);
+}
+
+
+int FileThumbnailView::thumbnailSize() const {
+ return d->mThumbnailSize;
+}
+
+
+/**
+ * Overriden to call updateGrid
+ */
+void FileThumbnailView::setItemTextPos(ItemTextPos pos) {
+ QIconView::setItemTextPos(pos);
+ updateGrid();
+}
+
+
+void FileThumbnailView::setMarginSize(int value) {
+ if (value==d->mMarginSize) return;
+ d->mMarginSize=value;
+ updateGrid();
+}
+
+
+int FileThumbnailView::marginSize() const {
+ return d->mMarginSize;
+}
+
+
+void FileThumbnailView::setItemDetails(int details) {
+ d->mItemDetails=details;
+ for (QIconViewItem* item=firstItem(); item; item=item->nextItem()) {
+ static_cast<FileThumbnailViewItem*>(item)->updateLines();
+ }
+ arrangeItemsInGrid();
+}
+
+
+int FileThumbnailView::itemDetails() const {
+ return d->mItemDetails;
+}
+
+
+void FileThumbnailView::setThumbnailPixmap(const KFileItem* fileItem, const QPixmap& thumbnail, const QSize& size) {
+ FileThumbnailViewItem* iconItem=viewItem(this, fileItem);
+ if (!iconItem) return;
+
+ iconItem->setPixmap(thumbnail);
+
+ // Update item info
+ if (size.isValid()) {
+ iconItem->setImageSize(size);
+ }
+ iconItem->repaint();
+
+ // Notify progress
+ if (d->mProgressWidget) {
+ // mProgressWidget might be null if we get called after the thumbnail
+ // job finished. This can happen when the thumbnail job use KPreviewJob
+ // to generate a thumbnail.
+ d->mProgressWidget->progressBar()->advance(1);
+ }
+}
+
+
+
+
+void FileThumbnailView::setShownFileItem(KFileItem* fileItem) {
+ if( fileItem == mShownFileItem ) return;
+ FileThumbnailViewItem* oldShownItem=viewItem(this, mShownFileItem);
+ FileThumbnailViewItem* newShownItem=viewItem(this, fileItem);
+
+ FileViewBase::setShownFileItem(fileItem);
+ if (oldShownItem) repaintItem(oldShownItem);
+ if (newShownItem) repaintItem(newShownItem);
+}
+
+
+//-----------------------------------------------------------------------------
+//
+// Thumbnail code
+//
+//-----------------------------------------------------------------------------
+QPixmap FileThumbnailView::createItemPixmap(const KFileItem* item) const {
+ bool isDirOrArchive=item->isDir() || Archive::fileItemIsArchive(item);
+ if (!isDirOrArchive) {
+ if (d->mWaitThumbnail.width()!=d->mThumbnailSize) {
+ d->updateWaitThumbnail(this);
+ }
+ return d->mWaitThumbnail;
+ }
+
+ QPixmap thumbnail(d->mThumbnailSize, d->mThumbnailSize);
+ thumbnail.fill(paletteBackgroundColor());
+ QPainter painter(&thumbnail);
+
+ // Load the icon
+ QPixmap itemPix=item->pixmap(QMIN(d->mThumbnailSize, ThumbnailSize::NORMAL));
+ painter.drawPixmap(
+ (d->mThumbnailSize-itemPix.width())/2,
+ (d->mThumbnailSize-itemPix.height())/2,
+ itemPix);
+
+ return thumbnail;
+}
+
+
+void FileThumbnailView::startThumbnailUpdate() {
+ // Delay thumbnail update if the widget is not visible
+ if (!isVisible()) {
+ d->mUpdateThumbnailsOnNextShow=true;
+ return;
+ }
+ d->mUpdateThumbnailsOnNextShow=false;
+ stopThumbnailUpdate(); // just in case
+ doStartThumbnailUpdate(items());
+}
+
+
+void FileThumbnailView::doStartThumbnailUpdate(const KFileItemList* list) {
+ QValueVector<const KFileItem*> imageList;
+ imageList.reserve( list->count());
+ QPtrListIterator<KFileItem> it(*list);
+ for (;it.current(); ++it) {
+ KFileItem* item=it.current();
+ if (!item->isDir() && !Archive::fileItemIsArchive(item)) {
+ imageList.append( item );
+ }
+ }
+ if (imageList.empty()) return;
+
+ BusyLevelManager::instance()->setBusyLevel( this, BUSY_THUMBNAILS );
+
+ Q_ASSERT(!d->mProgressWidget);
+ d->mProgressWidget=new ProgressWidget(this, imageList.count() );
+
+ connect(d->mProgressWidget->stopButton(), SIGNAL(clicked()),
+ this, SLOT(stopThumbnailUpdate()) );
+ d->mProgressWidget->show();
+
+ d->mThumbnailLoadJob = new ThumbnailLoadJob(&imageList, d->mThumbnailSize);
+
+ connect(d->mThumbnailLoadJob, SIGNAL(thumbnailLoaded(const KFileItem*, const QPixmap&, const QSize&)),
+ this, SLOT(setThumbnailPixmap(const KFileItem*,const QPixmap&, const QSize&)) );
+ connect(d->mThumbnailLoadJob, SIGNAL(result(KIO::Job*)),
+ this, SLOT(slotUpdateEnded()) );
+
+ slotBusyLevelChanged( BusyLevelManager::instance()->busyLevel());
+ // start updating at visible position
+ slotContentsMoving( contentsX(), contentsY());
+ d->mThumbnailLoadJob->start();
+}
+
+
+void FileThumbnailView::stopThumbnailUpdate() {
+ if (!d->mThumbnailLoadJob.isNull()) {
+ d->mThumbnailLoadJob->kill(false);
+ }
+}
+
+
+void FileThumbnailView::slotUpdateEnded() {
+ Q_ASSERT(d->mProgressWidget);
+ delete d->mProgressWidget;
+ d->mProgressWidget=0L;
+
+ BusyLevelManager::instance()->setBusyLevel( this, BUSY_NONE );
+}
+
+
+void FileThumbnailView::updateThumbnail(const KFileItem* fileItem) {
+ if (fileItem->isDir() || Archive::fileItemIsArchive(fileItem)) {
+ return;
+ }
+
+ ThumbnailLoadJob::deleteImageThumbnail(fileItem->url());
+ if (d->mThumbnailLoadJob.isNull()) {
+ KFileItemList list;
+ list.append(fileItem);
+ doStartThumbnailUpdate(&list);
+ } else {
+ d->mThumbnailLoadJob->appendItem(fileItem);
+ }
+}
+
+// temporarily stop loading thumbnails when busy loading the selected image,
+// otherwise thumbnail loading slows it down
+void FileThumbnailView::slotBusyLevelChanged(BusyLevel level) {
+ if( !d->mThumbnailLoadJob.isNull()) {
+ if( level > BUSY_THUMBNAILS ) {
+ d->mThumbnailLoadJob->suspend();
+ } else {
+ d->mThumbnailLoadJob->resume();
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+//
+// KFileView methods
+//
+//-----------------------------------------------------------------------------
+void FileThumbnailView::clearView() {
+ stopThumbnailUpdate();
+ mShownFileItem=0L;
+ QIconView::clear();
+}
+
+
+void FileThumbnailView::insertItem(KFileItem* item) {
+ if (!item) return;
+ bool isDirOrArchive=item->isDir() || Archive::fileItemIsArchive(item);
+
+ QPixmap thumbnail=createItemPixmap(item);
+ FileThumbnailViewItem* iconItem=new FileThumbnailViewItem(this,item->text(),thumbnail,item);
+ iconItem->setDropEnabled(isDirOrArchive);
+
+ setSortingKey(iconItem, item);
+ item->setExtraData(this,iconItem);
+}
+
+
+void FileThumbnailView::updateView(const KFileItem* fileItem) {
+ if (!fileItem) return;
+
+ FileThumbnailViewItem* iconItem=viewItem(this, fileItem);
+ if (iconItem) {
+ iconItem->setText(fileItem->text());
+ updateThumbnail(fileItem);
+ }
+ sort();
+}
+
+
+void FileThumbnailView::ensureItemVisible(const KFileItem* fileItem) {
+ if (!fileItem) return;
+
+ FileThumbnailViewItem* iconItem=viewItem(this, fileItem);
+ if (iconItem) QIconView::ensureItemVisible(iconItem);
+}
+
+
+void FileThumbnailView::setCurrentItem(const KFileItem* fileItem) {
+ if (!fileItem) return;
+
+ FileThumbnailViewItem* iconItem=viewItem(this, fileItem);
+ if (iconItem) QIconView::setCurrentItem(iconItem);
+}
+
+
+void FileThumbnailView::setSelected(const KFileItem* fileItem,bool enable) {
+ if (!fileItem) return;
+
+ FileThumbnailViewItem* iconItem=viewItem(this, fileItem);
+ if (iconItem) QIconView::setSelected(iconItem, enable, true /* do not unselect others */);
+}
+
+
+bool FileThumbnailView::isSelected(const KFileItem* fileItem) const {
+ if (!fileItem) return false;
+
+ FileThumbnailViewItem* iconItem=viewItem(this, fileItem);
+ if (!iconItem) return false;
+
+ return iconItem->isSelected();
+}
+
+
+void FileThumbnailView::removeItem(const KFileItem* fileItem) {
+ if (!fileItem) return;
+
+ // Remove it from the image preview job
+ if (!d->mThumbnailLoadJob.isNull())
+ d->mThumbnailLoadJob->itemRemoved(fileItem);
+
+ if (fileItem==mShownFileItem) mShownFileItem=0L;
+
+ // Remove it from our view
+ FileThumbnailViewItem* iconItem=viewItem(this, fileItem);
+ if (iconItem) delete iconItem;
+ KFileView::removeItem(fileItem);
+ arrangeItemsInGrid();
+}
+
+
+KFileItem* FileThumbnailView::firstFileItem() const {
+ FileThumbnailViewItem* iconItem=static_cast<FileThumbnailViewItem*>(firstItem());
+ if (!iconItem) return 0L;
+ return iconItem->fileItem();
+}
+
+
+KFileItem* FileThumbnailView::prevItem(const KFileItem* fileItem) const {
+ const FileThumbnailViewItem* iconItem=viewItem(this, fileItem);
+ if (!iconItem) return 0L;
+
+ iconItem=static_cast<const FileThumbnailViewItem*>(iconItem->prevItem());
+ if (!iconItem) return 0L;
+
+ return iconItem->fileItem();
+}
+
+
+KFileItem* FileThumbnailView::currentFileItem() const {
+ const QIconViewItem* iconItem=currentItem();
+ if (!iconItem) return 0L;
+
+ return static_cast<const FileThumbnailViewItem*>(iconItem)->fileItem();
+}
+
+
+KFileItem* FileThumbnailView::nextItem(const KFileItem* fileItem) const {
+ const FileThumbnailViewItem* iconItem=viewItem(this, fileItem);
+ if (!iconItem) return 0L;
+
+ iconItem=static_cast<const FileThumbnailViewItem*>(iconItem->nextItem());
+ if (!iconItem) return 0L;
+
+ return iconItem->fileItem();
+}
+
+
+void FileThumbnailView::setSorting(QDir::SortSpec spec) {
+ KFileView::setSorting(spec);
+
+ KFileItem *item;
+ KFileItemListIterator it( *items() );
+
+ for ( ; (item = it.current() ); ++it ) {
+ QIconViewItem* iconItem=viewItem(this, item);
+ if (iconItem) setSortingKey(iconItem, item);
+ }
+
+ KIconView::sort(! (spec & QDir::Reversed) );
+}
+
+//--------------------------------------------------------------------------
+//
+// Drop support
+//
+//--------------------------------------------------------------------------
+void FileThumbnailView::contentsDragEnterEvent(QDragEnterEvent* event) {
+ return event->accept( KURLDrag::canDecode(event) );
+}
+
+
+void FileThumbnailView::slotDropped(QDropEvent* event) {
+ emit dropped(event,0L);
+}
+
+
+void FileThumbnailView::showEvent(QShowEvent* event) {
+ KIconView::showEvent(event);
+ if (!d->mUpdateThumbnailsOnNextShow) return;
+
+ d->mUpdateThumbnailsOnNextShow=false;
+ QTimer::singleShot(0, this, SLOT(startThumbnailUpdate()));
+}
+
+
+//--------------------------------------------------------------------------
+//
+// Private
+//
+//--------------------------------------------------------------------------
+void FileThumbnailView::updateGrid() {
+ if (itemTextPos()==Right) {
+ setGridX(
+ d->mThumbnailSize
+ + FileThumbnailViewItem::PADDING*3
+ + RIGHT_TEXT_WIDTH);
+ } else {
+ setGridX(
+ QMAX(d->mThumbnailSize, BOTTOM_MIN_TEXT_WIDTH)
+ + FileThumbnailViewItem::PADDING*2);
+ }
+ setSpacing(d->mMarginSize);
+}
+
+
+void FileThumbnailView::setSortingKey(QIconViewItem *iconItem, const KFileItem *item)
+{
+ // see also setSorting()
+ QDir::SortSpec spec = KFileView::sorting();
+ bool isDirOrArchive=item->isDir() || Archive::fileItemIsArchive(item);
+
+ QString key;
+ if ( spec & QDir::Time ) {
+ time_t time = TimeUtils::getTime(item);
+ key=sortingKey(time, isDirOrArchive, spec);
+
+ } else if ( spec & QDir::Size ) {
+ key=sortingKey( item->size(), isDirOrArchive, spec );
+
+ } else {
+ // Name or Unsorted
+ key=sortingKey( item->text(), isDirOrArchive, spec );
+ }
+
+ iconItem->setKey(key);
+}
+
+
+//--------------------------------------------------------------------------
+//
+// Private slots
+//
+//--------------------------------------------------------------------------
+void FileThumbnailView::slotDoubleClicked(QIconViewItem* iconItem) {
+ if (!iconItem) return;
+ if (KGlobalSettings::singleClick()) return;
+ FileThumbnailViewItem* thumbItem=static_cast<FileThumbnailViewItem*>(iconItem);
+
+ KFileItem* fileItem=thumbItem->fileItem();
+
+ if (fileItem->isDir() || Archive::fileItemIsArchive(fileItem)) {
+ emit executed(iconItem);
+ }
+}
+
+
+void FileThumbnailView::slotClicked(QIconViewItem* iconItem) {
+ if (!iconItem) return;
+ if (!KGlobalSettings::singleClick()) return;
+ FileThumbnailViewItem* thumbItem=static_cast<FileThumbnailViewItem*>(iconItem);
+
+ KFileItem* fileItem=thumbItem->fileItem();
+
+ if (fileItem->isDir() || Archive::fileItemIsArchive(fileItem)) {
+ emit executed(iconItem);
+ }
+}
+
+void FileThumbnailView::slotContentsMoving( int x, int y ) {
+ updateVisibilityInfo( x, y ); // use x,y, the signal is emitted before moving
+}
+
+void FileThumbnailView::slotCurrentChanged(QIconViewItem* item ) {
+ // trigger generating thumbnails from the current one
+ updateVisibilityInfo( contentsX(), contentsY());
+ prefetchDone();
+ // if the first image is selected, no matter how, preload the next one
+ for( QIconViewItem* pos = item;
+ pos != NULL;
+ pos = pos->nextItem()) {
+ FileThumbnailViewItem* cur = static_cast< FileThumbnailViewItem* >( pos );
+ if( cur->fileItem()->isDir() || Archive::fileItemIsArchive(cur->fileItem())) continue;
+ if( pos == item && pos->nextItem() != NULL ) {
+ d->mPrefetch = ImageLoader::loader(
+ static_cast<const FileThumbnailViewItem*>( cur->nextItem() )->fileItem()->url(),
+ this, BUSY_PRELOADING );
+ connect( d->mPrefetch, SIGNAL( imageLoaded( bool )), SLOT( prefetchDone()));
+ }
+ }
+}
+
+/**
+ * when generating thumbnails, make the current thumbnail
+ * to be the next one processed by the thumbnail job, if visible,
+ * otherwise use the first visible thumbnail
+ */
+void FileThumbnailView::updateVisibilityInfo( int x, int y ) {
+ if (d->mThumbnailLoadJob.isNull()) return;
+
+ QRect rect( x, y, visibleWidth(), visibleHeight());
+ FileThumbnailViewItem* first = static_cast< FileThumbnailViewItem* >( findFirstVisibleItem( rect ));
+ if (!first) {
+ d->mThumbnailLoadJob->setPriorityItems(NULL,NULL,NULL);
+ return;
+ }
+
+ FileThumbnailViewItem* last = static_cast< FileThumbnailViewItem* >( findLastVisibleItem( rect ));
+ Q_ASSERT(last); // If we get a first item, then there must be a last
+
+ if (currentItem() && currentItem()->intersects(rect)) {
+ KFileItem* fileItem = currentFileItem();
+ d->mThumbnailLoadJob->setPriorityItems(fileItem,
+ first->fileItem(), last->fileItem());
+ return;
+ }
+
+ d->mThumbnailLoadJob->setPriorityItems(
+ first->fileItem(),
+ first->fileItem(),
+ last->fileItem());
+}
+
+void FileThumbnailView::keyPressEvent( QKeyEvent* e ) {
+// When the user presses e.g. the Down key, try to preload the next image in that direction.
+ if( e->key() != Key_Left
+ && e->key() != Key_Right
+ && e->key() != Key_Up
+ && e->key() != Key_Down ) return KIconView::keyPressEvent( e );
+
+ QIconViewItem* current = currentItem();
+ KIconView::keyPressEvent( e );
+ QIconViewItem* next = NULL;
+ if( current != currentItem() && currentItem() != NULL ) { // it actually moved
+ switch( e->key()) {
+ case Key_Left:
+ next = currentItem()->prevItem();
+ break;
+ case Key_Right:
+ next = currentItem()->nextItem();
+ break;
+ case Key_Up:
+ // This relies on the thumbnails being in a grid ( x() == x() )
+ for( next = currentItem()->prevItem();
+ next != NULL && next->x() != currentItem()->x();
+ next = next->prevItem())
+ ;
+ break;
+ case Key_Down:
+ for( next = currentItem()->nextItem();
+ next != NULL && next->x() != currentItem()->x();
+ next = next->nextItem())
+ ;
+ break;
+ }
+
+ }
+ prefetchDone();
+ if( next != NULL ) {
+ d->mPrefetch = ImageLoader::loader(
+ static_cast<const FileThumbnailViewItem*>( next )->fileItem()->url(),
+ this, BUSY_PRELOADING );
+ connect( d->mPrefetch, SIGNAL( imageLoaded( bool )), SLOT( prefetchDone()));
+ }
+}
+
+void FileThumbnailView::prefetchDone() {
+ if( d->mPrefetch != NULL ) {
+ d->mPrefetch->release( this );
+ d->mPrefetch = NULL;
+ }
+}
+
+//--------------------------------------------------------------------------
+//
+// Protected
+//
+//--------------------------------------------------------------------------
+void FileThumbnailView::startDrag() {
+ /**
+ * The item drawer for DragPixmapGenerator
+ */
+ struct ItemDrawer : public DragPixmapItemDrawer<KFileItem*> {
+ ItemDrawer(FileThumbnailView* view)
+ : mView(view) {}
+
+ QSize itemSize(KFileItem* fileItem) {
+ QPixmap* pix = pixmapFromFileItem(fileItem);
+ if (!pix) return QSize();
+
+ QSize size = pix->size();
+ int maxWidth = mGenerator->maxWidth();
+ if (size.width() > maxWidth) {
+ size.rheight() = size.height() * maxWidth / size.width();
+ size.rwidth() = maxWidth;
+ }
+ return size;
+ }
+
+ int spacing() const {
+ return 2;
+ }
+
+ void drawItem(QPainter* painter, int left, int top, KFileItem* fileItem) {
+ QPixmap* pix = pixmapFromFileItem(fileItem);
+ if (!pix) return;
+
+ QSize size = itemSize(fileItem);
+ left += (mGenerator->pixmapWidth() - size.width()) / 2;
+ if (size == pix->size()) {
+ painter->drawPixmap(left, top, *pix);
+ return;
+ }
+
+ QImage img = pix->convertToImage();
+ img = img.smoothScale(size, QImage::ScaleMin);
+ painter->drawImage(left, top, img);
+ }
+
+ QPixmap* pixmapFromFileItem(KFileItem* fileItem) {
+ FileThumbnailViewItem* iconItem = viewItem(mView, fileItem);
+ Q_ASSERT(iconItem);
+ if (!iconItem) return 0;
+
+ QPixmap* pix = iconItem->pixmap();
+ Q_ASSERT(pix);
+ if (!pix) return 0;
+ return pix;
+ }
+
+ FileThumbnailView* mView;
+ };
+ ItemDrawer drawer(this);
+
+
+ KURL::List urls;
+ KFileItemListIterator it(*KFileView::selectedItems());
+
+ DragPixmapGenerator<KFileItem*> generator;
+ generator.setItemDrawer(&drawer);
+
+ for ( ; it.current(); ++it ) {
+ urls.append(it.current()->url());
+ generator.addItem(it.current());
+ }
+
+ if (urls.isEmpty()) {
+ kdWarning() << "No item to drag\n";
+ return;
+ }
+
+ QDragObject* drag=new KURLDrag(urls, this, 0);
+ QPixmap dragPixmap = generator.generate();
+
+ drag->setPixmap( dragPixmap, QPoint(generator.DRAG_OFFSET, -generator.DRAG_OFFSET));
+ drag->dragCopy();
+}
+
+
+void FileThumbnailView::showThumbnailDetailsDialog() {
+ if (!d->mThumbnailsDetailDialog) {
+ d->mThumbnailsDetailDialog = new ThumbnailDetailsDialog(this);
+ }
+ d->mThumbnailsDetailDialog->show();
+}
+
+
+} // namespace
diff --git a/src/gvcore/filethumbnailview.h b/src/gvcore/filethumbnailview.h
new file mode 100644
index 0000000..25258b7
--- /dev/null
+++ b/src/gvcore/filethumbnailview.h
@@ -0,0 +1,131 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2000-2004 Aurélien Gâteau
+
+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.
+
+*/
+#ifndef FILETHUMBNAILVIEW_H
+#define FILETHUMBNAILVIEW_H
+
+// Qt includes
+#include <qguardedptr.h>
+#include <qptrlist.h>
+
+// KDE includes
+#include <kiconview.h>
+
+// Our includes
+#include "fileviewbase.h"
+#include "busylevelmanager.h"
+#include "libgwenview_export.h"
+
+class QDragEnterEvent;
+class QIconViewItem;
+class QPopupMenu;
+class QShowEvent;
+
+class KConfig;
+class KFileItem;
+typedef QPtrList<KFileItem> KFileItemList;
+
+namespace Gwenview {
+class FileThumbnailViewItem;
+
+class LIBGWENVIEW_EXPORT FileThumbnailView : public KIconView, public FileViewBase {
+Q_OBJECT
+ friend class FileThumbnailViewItem;
+
+public:
+ enum ItemDetail { FILENAME=1, FILESIZE=2, FILEDATE=4, IMAGESIZE=8 };
+ FileThumbnailView(QWidget* parent);
+ ~FileThumbnailView();
+
+ QWidget* widget() { return this; }
+
+ // KFileView methods
+ void clearView();
+ void clearSelection() { QIconView::clearSelection(); }
+ void insertItem(KFileItem* item);
+ void ensureItemVisible(const KFileItem* item);
+ void setCurrentItem(const KFileItem* item);
+ void setSelected(const KFileItem* item,bool enable);
+ bool isSelected(const KFileItem* item) const;
+ void removeItem(const KFileItem* item);
+ void updateView(const KFileItem* item);
+ void setSorting(QDir::SortSpec);
+
+ KFileItem* firstFileItem() const;
+ KFileItem* prevItem( const KFileItem*) const;
+ KFileItem* currentFileItem() const;
+ KFileItem* nextItem( const KFileItem*) const;
+
+ void setThumbnailSize(int value);
+ int thumbnailSize() const;
+
+ void setMarginSize(int value);
+ int marginSize() const;
+
+ void setItemDetails(int);
+ int itemDetails() const;
+
+ void setItemTextPos(ItemTextPos);
+
+ void setShownFileItem(KFileItem*);
+
+ void updateThumbnail(const KFileItem*);
+
+public slots:
+ void setThumbnailPixmap(const KFileItem*,const QPixmap&, const QSize&);
+ void startThumbnailUpdate();
+ void stopThumbnailUpdate();
+
+ void showThumbnailDetailsDialog();
+
+signals:
+ void dropped(QDropEvent*, KFileItem* target);
+
+protected:
+ void showEvent(QShowEvent*);
+ void contentsDragEnterEvent(QDragEnterEvent*);
+ void startDrag();
+ virtual void keyPressEvent( QKeyEvent* );
+
+private:
+ class Private;
+ Private* d;
+
+ void updateGrid();
+ QPixmap createItemPixmap(const KFileItem*) const;
+ void doStartThumbnailUpdate(const KFileItemList*);
+ void setSortingKey(QIconViewItem*, const KFileItem*);
+ void updateVisibilityInfo( int x, int y );
+
+private slots:
+ void slotClicked(QIconViewItem*);
+ void slotDoubleClicked(QIconViewItem*);
+ void slotDropped(QDropEvent*);
+ void slotContentsMoving( int, int );
+ void slotCurrentChanged(QIconViewItem*);
+ void slotBusyLevelChanged( BusyLevel );
+ void slotUpdateEnded();
+ void prefetchDone();
+};
+
+
+} // namespace
+#endif
+
diff --git a/src/gvcore/filethumbnailviewitem.cpp b/src/gvcore/filethumbnailviewitem.cpp
new file mode 100644
index 0000000..df4e548
--- /dev/null
+++ b/src/gvcore/filethumbnailviewitem.cpp
@@ -0,0 +1,394 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab:
+/* Gwenview - A simple image viewer for KDE
+ Copyright 2000-2004 Aurélien Gâteau
+ This class is based on the KIconViewItem class from KDE libs.
+ Original copyright follows.
+*/
+/* This file is part of the KDE libraries
+ Copyright (C) 1999 Torben Weis <weis@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License version 2 as published by the Free Software Foundation.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+// Qt includes
+#include <qapplication.h>
+#include <qcolor.h>
+#include <qpainter.h>
+#include <qpen.h>
+#include <qpixmap.h>
+
+// KDE includes
+#include <kdebug.h>
+#include <kwordwrap.h>
+#include <kurldrag.h>
+
+// Our includes
+#include "archive.h"
+#include "filethumbnailview.h"
+#include "filethumbnailviewitem.h"
+#include "fileviewconfig.h"
+#include "timeutils.h"
+
+namespace Gwenview {
+
+const int SHOWN_ITEM_INDICATOR_SIZE = 8;
+
+#if 0
+static void printRect(const QString& txt,const QRect& rect) {
+ kdWarning() << txt << " : " << rect.x() << "x" << rect.y() << " " << rect.width() << "x" << rect.height() << endl;
+}
+#endif
+
+
+/**
+ * An helper class to handle a caption line and help drawing it
+ */
+class FileThumbnailViewItem::Line {
+protected:
+ const QIconViewItem* mItem;
+ QString mTxt;
+ int mWidth;
+public:
+ Line(const QIconViewItem* item, const QString& txt)
+ : mItem(item)
+ , mTxt(txt)
+ , mWidth(-1) {
+ }
+ virtual ~Line() {}
+
+ virtual void setWidth(int width) {
+ mWidth=width;
+ }
+
+ virtual int height() const=0;
+
+ void paint(QPainter* p, int textX, int textY, int align) const {
+ Q_ASSERT(mWidth!=-1);
+ int length=fontMetrics().width(mTxt);
+ if (length<=mWidth ) {
+ p->drawText(
+ textX,
+ textY,
+ mWidth,
+ fontMetrics().height(),
+ align,
+ mTxt);
+ } else {
+ p->save();
+ complexPaint(p, textX, textY, align);
+ p->restore();
+ }
+ };
+
+protected:
+ const FileThumbnailView* view() const {
+ return static_cast<const FileThumbnailView*>(mItem->iconView());
+ }
+
+ QFontMetrics fontMetrics() const {
+ return view()->fontMetrics();
+ }
+
+ /**
+ * Called when the text won't fit the available space
+ */
+ virtual void complexPaint(QPainter* p, int textX, int textY, int align) const=0;
+};
+
+
+/**
+ * A line which will get cropped if necessary
+ */
+class FileThumbnailViewItem::CroppedLine : public FileThumbnailViewItem::Line {
+public:
+ CroppedLine(const QIconViewItem* item, const QString& txt)
+ : Line(item, txt) {}
+
+ int height() const {
+ return fontMetrics().height();
+ }
+
+ void complexPaint(QPainter* p, int textX, int textY, int /*align*/) const {
+ KWordWrap::drawFadeoutText(p,
+ textX,
+ textY + fontMetrics().ascent(),
+ mWidth,
+ mTxt);
+ }
+};
+
+/**
+ * A line which will get wrapped if necessary
+ */
+
+class FileThumbnailViewItem::WrappedLine : public FileThumbnailViewItem::Line {
+ KWordWrap* mWordWrap;
+public:
+ WrappedLine(const QIconViewItem* item, const QString& txt)
+ : Line(item, txt)
+ , mWordWrap(0) {}
+
+ ~WrappedLine() {
+ delete mWordWrap;
+ }
+
+ int height() const {
+ Q_ASSERT(mWordWrap);
+ if (!mWordWrap) return 0;
+ return mWordWrap->boundingRect().height();
+ }
+
+ /**
+ * Regenerates mWordWrap if the width has changed
+ */
+ void setWidth(int width) {
+ if (width==mWidth) return;
+ mWidth=width;
+ delete mWordWrap;
+ QFontMetrics fm=fontMetrics();
+ mWordWrap=KWordWrap::formatText(fm,
+ QRect(0, 0, mWidth, fm.height()*3),
+ 0 /*flags*/,
+ mTxt);
+ }
+
+ void complexPaint(QPainter* p, int textX, int textY, int align) const {
+ Q_ASSERT(mWordWrap);
+ if (!mWordWrap) return;
+
+ int xpos=0;
+ if (align & AlignHCenter) {
+ xpos=( mWidth - mWordWrap->boundingRect().width() ) / 2;
+ }
+
+ mWordWrap->drawText(p,
+ textX + xpos,
+ textY,
+ align);
+ }
+};
+
+
+FileThumbnailViewItem::FileThumbnailViewItem(QIconView* view,const QString& text,const QPixmap& icon, KFileItem* fileItem)
+: QIconViewItem(view,text,icon), mFileItem(fileItem) {
+ updateLines();
+ calcRect();
+}
+
+
+FileThumbnailViewItem::~FileThumbnailViewItem() {
+ QValueVector<Line*>::ConstIterator it=mLines.begin();
+ QValueVector<Line*>::ConstIterator itEnd=mLines.end();
+ for (;it!=itEnd; ++it) {
+ delete *it;
+ }
+}
+
+
+void FileThumbnailViewItem::updateLines() {
+ QValueVector<Line*>::ConstIterator it=mLines.begin();
+ QValueVector<Line*>::ConstIterator itEnd=mLines.end();
+ for (;it!=itEnd; ++it) {
+ delete *it;
+ }
+ mLines.clear();
+ if (!mFileItem) return;
+
+ bool isDir=mFileItem->isDir();
+ if (iconView()->itemTextPos()==QIconView::Right) {
+ // Text is on the right, show everything
+
+ time_t time = TimeUtils::getTime(mFileItem);
+ mLines.append( new WrappedLine(this, mFileItem->name()) );
+ mLines.append( new CroppedLine(this, TimeUtils::formatTime(time)) );
+ if (mImageSize.isValid()) {
+ QString txt=QString::number(mImageSize.width())+"x"+QString::number(mImageSize.height());
+ mLines.append( new CroppedLine(this, txt) );
+ }
+ if (!isDir) {
+ mLines.append( new CroppedLine(this, KIO::convertSize(mFileItem->size())) );
+ }
+
+ } else {
+ // Text is below the icon, only show details selected in
+ // view->itemDetails()
+ FileThumbnailView *view=static_cast<FileThumbnailView*>(iconView());
+ int details=view->itemDetails();
+ bool isImage=!Archive::fileItemIsDirOrArchive(mFileItem);
+
+ if (!isImage || (details & FileThumbnailView::FILENAME)) {
+ mLines.append( new WrappedLine(this, mFileItem->name()) );
+ }
+ if (details & FileThumbnailView::FILEDATE) {
+ time_t time = TimeUtils::getTime(mFileItem);
+ mLines.append( new CroppedLine(this, TimeUtils::formatTime(time)) );
+ }
+ if (details & FileThumbnailView::IMAGESIZE) {
+ QString txt;
+ if (mImageSize.isValid()) {
+ txt=QString::number(mImageSize.width())+"x"+QString::number(mImageSize.height());
+ }
+ mLines.append( new CroppedLine(this, txt) );
+ }
+ if (!isDir && (details & FileThumbnailView::FILESIZE)) {
+ mLines.append( new CroppedLine(this, KIO::convertSize(mFileItem->size())) );
+ }
+
+ }
+
+ calcRect();
+}
+
+
+void FileThumbnailViewItem::calcRect(const QString&) {
+ FileThumbnailView *view=static_cast<FileThumbnailView*>(iconView());
+ bool isRight=view->itemTextPos()==QIconView::Right;
+
+ int textW=view->gridX();
+ int thumbnailSize=FileViewConfig::thumbnailSize();
+ if (isRight) {
+ textW-=PADDING * 3 + thumbnailSize;
+ } else {
+ textW-=PADDING * 2;
+ }
+
+ int textH=0;
+ QValueVector<Line*>::ConstIterator it=mLines.begin();
+ QValueVector<Line*>::ConstIterator itEnd=mLines.end();
+ for (;it!=itEnd; ++it) {
+ (*it)->setWidth(textW);
+ textH+=(*it)->height();
+ }
+
+ QRect itemRect(x(), y(), view->gridX(), 0);
+ QRect itemPixmapRect(PADDING, PADDING, thumbnailSize, thumbnailSize);
+ QRect itemTextRect(0, 0, textW, textH);
+ if (isRight) {
+ itemRect.setHeight( QMAX(thumbnailSize + PADDING*2, textH) );
+ itemTextRect.moveLeft(thumbnailSize + PADDING * 2 );
+ itemTextRect.moveTop((itemRect.height() - textH)/2);
+ } else {
+ itemPixmapRect.moveLeft( (itemRect.width() - itemPixmapRect.width()) / 2 );
+ itemRect.setHeight(thumbnailSize + PADDING*3 + textH);
+ itemTextRect.moveLeft(PADDING);
+ itemTextRect.moveTop(thumbnailSize + PADDING * 2);
+ }
+
+ // Update rects
+ if ( itemPixmapRect != pixmapRect() ) {
+ setPixmapRect( itemPixmapRect );
+ }
+ if ( itemTextRect != textRect() ) {
+ setTextRect( itemTextRect );
+ }
+ if ( itemRect != rect() ) {
+ setItemRect( itemRect );
+ }
+}
+
+
+void FileThumbnailViewItem::paintItem(QPainter *p, const QColorGroup &cg) {
+ FileThumbnailView *view=static_cast<FileThumbnailView*>(iconView());
+ Q_ASSERT(view);
+ if (!view) return;
+
+ bool isRight=view->itemTextPos()==QIconView::Right;
+ bool isShownItem=view->shownFileItem() && view->shownFileItem()->extraData(view)==this;
+ bool isImage=!Archive::fileItemIsDirOrArchive(mFileItem);
+ int textX, textY, textW, textH;
+ int thumbnailSize=FileViewConfig::thumbnailSize();
+
+ textX=textRect(false).x();
+ textY=textRect(false).y();
+ textW=textRect(false).width();
+ textH=textRect(false).height();
+
+ // Draw pixmap
+ QRect pRect = pixmapRect(false);
+ int pixX = pRect.left() + ( thumbnailSize - pixmap()->width() ) / 2;
+ int pixY = pRect.top() + ( thumbnailSize - pixmap()->height() ) / 2;
+ p->drawPixmap( pixX, pixY, *pixmap() );
+
+ QColor bg;
+ if ( isSelected() ) {
+ bg=cg.highlight();
+ } else {
+ bg=cg.mid();
+ }
+
+ // Draw shown item indicator
+ if (isShownItem) {
+ QPointArray pa(3);
+ pa[0] = pixmapRect(false).bottomLeft();
+ pa[0].rx() += pixmapRect(false).width() / 2;
+ pa[0].ry() += PADDING - 1;
+ pa[0].ry() -= SHOWN_ITEM_INDICATOR_SIZE;
+
+ pa[1] = pa[0];
+ pa[1].rx() -= SHOWN_ITEM_INDICATOR_SIZE;
+ pa[1].ry() += SHOWN_ITEM_INDICATOR_SIZE;
+
+ pa[2] = pa[1];
+ pa[2].rx() += SHOWN_ITEM_INDICATOR_SIZE * 2;
+
+ p->setBrush(cg.highlight());
+ p->setPen(cg.base());
+ p->drawPolygon(pa);
+ }
+
+ if (isImage || isSelected()) {
+ // Draw frame
+ QRect frmRect=pixmapRect(false);
+ frmRect.addCoords(-PADDING, -PADDING, PADDING, PADDING);
+
+ p->setBrush(QBrush());
+ p->setPen(bg);
+ p->drawRect(frmRect);
+ if (isSelected()) {
+ frmRect.addCoords(1, 1, -1, -1);
+ p->drawRect(frmRect);
+ }
+ }
+
+ // Draw text
+ p->setPen(cg.text());
+ p->setBackgroundColor(cg.base());
+ int align = (isRight ? AlignAuto : AlignHCenter) | AlignTop;
+
+ QValueVector<Line*>::ConstIterator it=mLines.begin();
+ QValueVector<Line*>::ConstIterator itEnd=mLines.end();
+ for (;it!=itEnd; ++it) {
+ const Line* line=*it;
+ line->paint(p, textX, textY, align);
+ textY+=line->height();
+ }
+}
+
+
+bool FileThumbnailViewItem::acceptDrop(const QMimeSource* source) const {
+ return KURLDrag::canDecode(source);
+}
+
+
+void FileThumbnailViewItem::dropped(QDropEvent* event, const QValueList<QIconDragItem>&) {
+ FileThumbnailView *view=static_cast<FileThumbnailView*>(iconView());
+ emit view->dropped(event,mFileItem);
+}
+
+void FileThumbnailViewItem::setImageSize(const QSize& size) {
+ mImageSize=size;
+ updateLines();
+}
+
+} // namespace
diff --git a/src/gvcore/filethumbnailviewitem.h b/src/gvcore/filethumbnailviewitem.h
new file mode 100644
index 0000000..154c4b7
--- /dev/null
+++ b/src/gvcore/filethumbnailviewitem.h
@@ -0,0 +1,69 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2000-2004 Aurélien Gâteau
+
+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.
+
+*/
+
+#ifndef FILETHUMBNAILVIEWITEM_H
+#define FILETHUMBNAILVIEWITEM_H
+
+// Qt
+#include <qiconview.h>
+#include <qpixmap.h>
+#include <qstring.h>
+#include <qvaluevector.h>
+
+class KFileItem;
+
+namespace Gwenview {
+/**
+ * We override the QIconViewItem to control the look of selected items
+ * and get a pointer to our KFileItem
+ */
+class FileThumbnailViewItem : public QIconViewItem {
+public:
+ class Line;
+ class CroppedLine;
+ class WrappedLine;
+ enum { PADDING=4 };
+
+ FileThumbnailViewItem(QIconView* parent,const QString& text,const QPixmap& icon, KFileItem* fileItem);
+ ~FileThumbnailViewItem();
+
+ KFileItem* fileItem() const { return mFileItem; }
+
+ void setImageSize(const QSize&);
+
+ void updateLines();
+
+protected:
+ void paintItem(QPainter* painter, const QColorGroup& colorGroup);
+ void calcRect( const QString& text_=QString::null );
+ void paintFocus(QPainter*, const QColorGroup&) {}
+ bool acceptDrop(const QMimeSource*) const;
+ void dropped(QDropEvent*, const QValueList<QIconDragItem>&);
+
+ KFileItem* mFileItem;
+ QValueVector<Line*> mLines;
+
+ QSize mImageSize;
+};
+
+} // namespace
+#endif
+
diff --git a/src/gvcore/fileviewbase.h b/src/gvcore/fileviewbase.h
new file mode 100644
index 0000000..9d36598
--- /dev/null
+++ b/src/gvcore/fileviewbase.h
@@ -0,0 +1,47 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2000-2004 Aurélien Gâteau
+
+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.
+
+*/
+
+#ifndef FILEVIEWBASE_H
+#define FILEVIEWBASE_H
+
+// Qt includes
+#include <qcolor.h>
+
+// KDE includes
+#include <kfileview.h>
+namespace Gwenview {
+
+class FileViewBase : public KFileView {
+public:
+ FileViewBase() : mShownFileItem(0L) {}
+
+ KFileItem* shownFileItem() const { return mShownFileItem; }
+ virtual void setShownFileItem(KFileItem* fileItem) { mShownFileItem=fileItem; }
+
+ virtual void updateFromSettings() {}
+
+protected:
+ KFileItem* mShownFileItem;
+};
+
+} // namespace
+#endif
+
diff --git a/src/gvcore/fileviewconfig.kcfg b/src/gvcore/fileviewconfig.kcfg
new file mode 100644
index 0000000..0ef5f1a
--- /dev/null
+++ b/src/gvcore/fileviewconfig.kcfg
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE kcfg SYSTEM "http://www.kde.org/standards/kcfg/1.0/kcfg.dtd">
+<kcfg>
+ <include>qiconview.h</include>
+ <kcfgfile name="gwenviewrc"/>
+ <group name="file widget">
+ <entry name="showDirs" key="show dirs" type="Bool">
+ <default>true</default>
+ </entry>
+ <entry name="showDotFiles" key="show dot files" type="Bool">
+ <default>false</default>
+ </entry>
+ <entry name="startWithThumbnails" key="start with thumbnails" type="Bool">
+ <default>true</default>
+ </entry>
+ <entry name="thumbnailTextPos" key="item text pos" type="Int">
+ <default code="true">QIconView::Right</default>
+ </entry>
+ <entry name="thumbnailSize" key="thumbnail size" type="Int">
+ <default>48</default>
+ </entry>
+ <entry name="thumbnailMarginSize" key="margin size" type="Int">
+ <default>5</default>
+ </entry>
+ <entry name="thumbnailDetails" key="item details" type="Int">
+ <default>9</default>
+ <description>
+ This is a bit set of FileThumbnailView::ItemDetail:
+ enum ItemDetail { FILENAME=1, FILESIZE=2, FILEDATE=4, IMAGESIZE=8 };
+ </description>
+ </entry>
+ <entry name="filterMode" type="Enum">
+ <default>All</default>
+ <choices>
+ <choice name="All"/>
+ <choice name="ImagesOnly"/>
+ <choice name="VideosOnly"/>
+ </choices>
+ </entry>
+ <entry name="showFilterBar" type="Bool">
+ <default>false</default>
+ </entry>
+ <entry name="nameFilter" type="String"></entry>
+ <entry name="fromDateFilter" type="DateTime"></entry>
+ <entry name="toDateFilter" type="DateTime"></entry>
+ </group>
+
+
+ <!-- thumbnail cache keys are really a mess :-( -->
+ <group name="thumbnail loading">
+ <entry name="storeThumbnailsInCache" key="path" type="Bool">
+ <default>true</default>
+ </entry>
+ </group>
+ <group name="main window">
+ <entry name="deleteCacheOnExit" key="Delete Thumbnail Cache whe exit" type="Bool">
+ <default>false</default>
+ </entry>
+ </group>
+
+
+</kcfg>
diff --git a/src/gvcore/fileviewconfig.kcfgc b/src/gvcore/fileviewconfig.kcfgc
new file mode 100644
index 0000000..ae7a375
--- /dev/null
+++ b/src/gvcore/fileviewconfig.kcfgc
@@ -0,0 +1,7 @@
+File=fileviewconfig.kcfg
+ClassName=FileViewConfig
+NameSpace=Gwenview
+Singleton=true
+Mutators=true
+IncludeFiles=gvcore/libgwenview_export.h
+Visibility=LIBGWENVIEW_EXPORT
diff --git a/src/gvcore/fileviewcontroller.cpp b/src/gvcore/fileviewcontroller.cpp
new file mode 100644
index 0000000..5daf10b
--- /dev/null
+++ b/src/gvcore/fileviewcontroller.cpp
@@ -0,0 +1,1321 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab:
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2000-2004 Aur�ien G�eau
+
+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.
+
+*/
+
+// Qt
+#include <qcheckbox.h>
+#include <qdatetimeedit.h>
+#include <qhbox.h>
+#include <qlayout.h>
+#include <qpopupmenu.h>
+#include <qpushbutton.h>
+#include <qtooltip.h>
+#include <qwidgetstack.h>
+
+// KDE
+#include <kaction.h>
+#include <kapplication.h>
+#include <kcombobox.h>
+#include <kdebug.h>
+#include <kdirlister.h>
+#include <kicontheme.h>
+#include <kiconeffect.h>
+#include <kiconloader.h>
+#include <kimageio.h>
+#include <klistview.h>
+#include <klocale.h>
+#include <kpropertiesdialog.h>
+#include <kprotocolinfo.h>
+#include <kstdaction.h>
+#include <ktoolbar.h>
+#include <ktoolbarlabelaction.h>
+#include <kurldrag.h>
+#include <kio/job.h>
+#include <kio/file.h>
+
+// Local
+#include "archive.h"
+#include "cache.h"
+#include "clicklineedit.h"
+#include "cursortracker.h"
+#include "filedetailview.h"
+#include "fileoperation.h"
+#include "filethumbnailview.h"
+#include "filterbar.h"
+#include "imageloader.h"
+#include "mimetypeutils.h"
+#include "timeutils.h"
+#include "thumbnailsize.h"
+#include "fileviewconfig.h"
+#include "miscconfig.h"
+
+#include "fileviewcontroller.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
+
+static const int SLIDER_RESOLUTION=4;
+
+
+//-----------------------------------------------------------------------
+//
+// internal class which allows dynamically turning off visual error reporting
+//
+//-----------------------------------------------------------------------
+class DirLister : public KDirLister {
+public:
+ DirLister()
+ : KDirLister()
+ , mError(false)
+ , mCheck(false) {}
+
+ virtual bool validURL(const KURL& url) const {
+ if( !url.isValid()) mError = true;
+ if( mCheck ) return KDirLister::validURL( url );
+ return url.isValid();
+ }
+
+ virtual void handleError(KIO::Job* job) {
+ mError = true;
+ if(mCheck) KDirLister::handleError( job );
+ };
+
+ bool error() const {
+ return mError;
+ }
+
+ void clearError() {
+ mError = false;
+ }
+
+ void setCheck(bool c) {
+ mCheck = c;
+ }
+
+ void setDateFilter(const QDate& from, const QDate& to) {
+ mFromDate = from;
+ mToDate =to;
+ }
+
+ virtual bool itemMatchFilters(const KFileItem* item) const {
+ if (!matchesFilter(item)) return false;
+ return matchesMimeFilter(item);
+ }
+
+public:
+ virtual bool matchesMimeFilter(const KFileItem* item) const {
+ // Do mime filtering ourself because we use startsWith instead of ==
+ QStringList lst = mimeFilters();
+ QStringList::Iterator it = lst.begin(), end = lst.end();
+ bool result = false;
+ QString type = item->mimetype();
+ for (; it!=end; ++it) {
+ if (type.startsWith(*it)) {
+ result = true;
+ break;
+ }
+ }
+ if (!result) return false;
+
+ if (item->isDir() || Archive::fileItemIsArchive(item)) {
+ // Do not filter out dirs or archives
+ return true;
+ }
+
+ if (!mFromDate.isValid() && !mToDate.isValid()) return result;
+
+ // Convert item time to a QDate
+ time_t time=TimeUtils::getTime(item);
+ QDateTime dateTime;
+ dateTime.setTime_t(time);
+ QDate date=dateTime.date();
+
+ if (mFromDate.isValid() && date < mFromDate) return false;
+ if (mToDate.isValid() && date > mToDate) return false;
+ return true;
+ }
+
+private:
+ mutable bool mError;
+ bool mCheck;
+ QDate mFromDate;
+ QDate mToDate;
+};
+
+
+//-----------------------------------------------------------------------
+//
+// FileViewController::Private
+//
+//-----------------------------------------------------------------------
+class FileViewController::Private {
+public:
+ ~Private() {
+ delete mSliderTracker;
+ }
+ FileViewController* that;
+ FilterBar* mFilterBar;
+ KToolBar* mToolBar;
+ QWidgetStack* mStack;
+ KSelectAction* mSortAction;
+ KToggleAction* mRevertSortAction;
+ TipTracker* mSliderTracker;
+
+ QHBox* mFilterHBox;
+ QComboBox* mFilterComboBox;
+ QCheckBox* mShowFilterBarCheckBox;
+
+ void initFilterBar() {
+ mFilterBar=new FilterBar(that);
+ mFilterBar->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
+ mFilterBar->hide();
+
+ QIconSet resetIS=BarIcon("locationbar_erase");
+ mFilterBar->mResetNameCombo->setIconSet(resetIS);
+ mFilterBar->mResetFrom->setIconSet(resetIS);
+ mFilterBar->mResetTo->setIconSet(resetIS);
+
+ QObject::connect(
+ mFilterBar->mResetNameCombo, SIGNAL(clicked()),
+ that, SLOT(resetNameFilter()) );
+ QObject::connect(
+ mFilterBar->mResetFrom, SIGNAL(clicked()),
+ that, SLOT(resetFromFilter()) );
+ QObject::connect(
+ mFilterBar->mResetTo, SIGNAL(clicked()),
+ that, SLOT(resetToFilter()) );
+
+ QObject::connect(
+ mFilterBar->mFilterButton, SIGNAL(clicked()),
+ that, SLOT(applyFilter()) );
+ }
+
+ void initFilterCombo() {
+ mFilterHBox=new QHBox(mToolBar, "kde toolbar widget");
+ mFilterHBox->setSpacing(KDialog::spacingHint());
+
+ mFilterComboBox=new QComboBox(mFilterHBox);
+ mFilterComboBox->insertItem(i18n("All files"), ALL);
+ mFilterComboBox->insertItem(i18n("Images only"), IMAGES_ONLY);
+ mFilterComboBox->insertItem(i18n("Videos only"), VIDEOS_ONLY);
+
+ QObject::connect(
+ mFilterComboBox, SIGNAL(activated(int)),
+ that, SLOT(applyFilter()) );
+
+ mShowFilterBarCheckBox = new QCheckBox(i18n("More"), mFilterHBox);
+ QObject::connect(
+ mShowFilterBarCheckBox, SIGNAL(toggled(bool)),
+ mFilterBar, SLOT(setShown(bool)) );
+ QObject::connect(
+ mShowFilterBarCheckBox, SIGNAL(toggled(bool)),
+ that, SLOT(applyFilter()) );
+ }
+
+
+ void loadFilterSettings() {
+ mFilterComboBox->setCurrentItem(FileViewConfig::filterMode());
+ mShowFilterBarCheckBox->setChecked(FileViewConfig::showFilterBar());
+ mFilterBar->mNameEdit->setText(FileViewConfig::nameFilter());
+ mFilterBar->mFromDateEdit->setDate(FileViewConfig::fromDateFilter().date());
+ mFilterBar->mToDateEdit->setDate(FileViewConfig::toDateFilter().date());
+ }
+};
+
+
+//-----------------------------------------------------------------------
+//
+// FileViewController
+//
+//-----------------------------------------------------------------------
+FileViewController::FileViewController(QWidget* parent,KActionCollection* actionCollection)
+: QWidget(parent)
+, mMode(FILE_LIST)
+, mPrefetch( NULL )
+, mChangeDirStatus(CHANGE_DIR_STATUS_NONE)
+, mBrowsing(false)
+, mSelecting(false)
+{
+ d=new Private;
+ d->that=this;
+ setMinimumWidth(1);
+ d->mToolBar=new KToolBar(this, "", true);
+ d->initFilterBar();
+ d->initFilterCombo();
+ d->mStack=new QWidgetStack(this);
+
+ QVBoxLayout *layout=new QVBoxLayout(this);
+ layout->addWidget(d->mToolBar);
+ layout->addWidget(d->mFilterBar);
+ layout->addWidget(d->mStack);
+
+ // Actions
+ mSelectFirst=new KAction(i18n("&First"),
+ QApplication::reverseLayout() ? "2rightarrow":"2leftarrow", Key_Home,
+ this,SLOT(slotSelectFirst()), actionCollection, "first");
+
+ mSelectLast=new KAction(i18n("&Last"),
+ QApplication::reverseLayout() ? "2leftarrow":"2rightarrow", Key_End,
+ this,SLOT(slotSelectLast()), actionCollection, "last");
+
+ mSelectPrevious=new KAction(i18n("&Previous"),
+ QApplication::reverseLayout() ? "1rightarrow":"1leftarrow", Key_BackSpace,
+ this,SLOT(slotSelectPrevious()), actionCollection, "previous");
+
+ mSelectNext=new KAction(i18n("&Next"),
+ QApplication::reverseLayout() ? "1leftarrow":"1rightarrow", Key_Space,
+ this,SLOT(slotSelectNext()), actionCollection, "next");
+
+ mSelectPreviousDir=new KAction(i18n("&Previous Folder"),
+ QApplication::reverseLayout() ? "player_fwd":"player_rew", ALT + Key_BackSpace,
+ this,SLOT(slotSelectPreviousDir()), actionCollection, "previous_folder");
+
+ mSelectNextDir=new KAction(i18n("&Next Folder"),
+ QApplication::reverseLayout() ? "player_rew":"player_fwd", ALT + Key_Space,
+ this,SLOT(slotSelectNextDir()), actionCollection, "next_folder");
+
+ mSelectFirstSubDir=new KAction(i18n("&First Sub Folder"), "down", ALT + Key_Down,
+ this,SLOT(slotSelectFirstSubDir()), actionCollection, "first_sub_folder");
+
+ mListMode=new KRadioAction(i18n("Details"),"view_detailed",0,this,SLOT(updateViewMode()),actionCollection,"list_mode");
+ mListMode->setExclusiveGroup("thumbnails");
+ mSideThumbnailMode=new KRadioAction(i18n("Thumbnails with Info on Side"),"view_multicolumn",0,this,SLOT(updateViewMode()),actionCollection,"side_thumbnail_mode");
+ mSideThumbnailMode->setExclusiveGroup("thumbnails");
+ mBottomThumbnailMode=new KRadioAction(i18n("Thumbnails with Info on Bottom"),"view_icon",0,this,SLOT(updateViewMode()),actionCollection,"bottom_thumbnail_mode");
+ mBottomThumbnailMode->setExclusiveGroup("thumbnails");
+
+ // Size slider
+ mSizeSlider=new QSlider(Horizontal, d->mToolBar);
+ mSizeSlider->setFixedWidth(120);
+ mSizeSlider->setRange(
+ ThumbnailSize::MIN/SLIDER_RESOLUTION,
+ ThumbnailSize::LARGE/SLIDER_RESOLUTION);
+ mSizeSlider->setValue(FileViewConfig::thumbnailSize() / SLIDER_RESOLUTION);
+
+ connect(mSizeSlider, SIGNAL(valueChanged(int)), this, SLOT(updateThumbnailSize(int)) );
+ connect(mListMode, SIGNAL(toggled(bool)), mSizeSlider, SLOT(setDisabled(bool)) );
+ KAction* sliderAction=new KWidgetAction(mSizeSlider, i18n("Thumbnail Size"), 0, 0, 0, actionCollection, "size_slider");
+ d->mSliderTracker=new TipTracker("", mSizeSlider);
+ // /Size slider
+
+ mShowDotFiles=new KToggleAction(i18n("Show &Hidden Files"),CTRL + Key_H,this,SLOT(toggleShowDotFiles()),actionCollection,"show_dot_files");
+
+ d->mSortAction=new KSelectAction(i18n("Sort"), 0, this, SLOT(setSorting()), actionCollection, "view_sort");
+ QStringList sortItems;
+ sortItems << i18n("By Name") << i18n("By Date") << i18n("By Size");
+ d->mSortAction->setItems(sortItems);
+ d->mSortAction->setCurrentItem(0);
+
+ d->mRevertSortAction=new KToggleAction(i18n("Descending"),0, this, SLOT(setSorting()), actionCollection, "descending");
+ QPopupMenu* sortMenu=d->mSortAction->popupMenu();
+ Q_ASSERT(sortMenu);
+ sortMenu->insertSeparator();
+ d->mRevertSortAction->plug(sortMenu);
+
+ // Dir lister
+ mDirLister=new DirLister;
+ mDirLister->setMainWindow(topLevelWidget());
+ connect(mDirLister,SIGNAL(clear()),
+ this,SLOT(dirListerClear()) );
+
+ connect(mDirLister,SIGNAL(newItems(const KFileItemList&)),
+ this,SLOT(dirListerNewItems(const KFileItemList&)) );
+
+ connect(mDirLister,SIGNAL(deleteItem(KFileItem*)),
+ this,SLOT(dirListerDeleteItem(KFileItem*)) );
+
+ connect(mDirLister,SIGNAL(refreshItems(const KFileItemList&)),
+ this,SLOT(dirListerRefreshItems(const KFileItemList&)) );
+
+ connect(mDirLister,SIGNAL(started(const KURL&)),
+ this,SLOT(dirListerStarted()) );
+
+ connect(mDirLister,SIGNAL(completed()),
+ this,SLOT(dirListerCompleted()) );
+
+ connect(mDirLister,SIGNAL(canceled()),
+ this,SLOT(dirListerCanceled()) );
+
+ // Propagate canceled signals
+ connect(mDirLister,SIGNAL(canceled()),
+ this,SIGNAL(canceled()) );
+
+ // File detail widget
+ mFileDetailView=new FileDetailView(d->mStack, "filedetailview");
+ d->mStack->addWidget(mFileDetailView,0);
+ mFileDetailView->viewport()->installEventFilter(this);
+
+ connect(mFileDetailView,SIGNAL(executed(QListViewItem*)),
+ this,SLOT(slotViewExecuted()) );
+ connect(mFileDetailView,SIGNAL(returnPressed(QListViewItem*)),
+ this,SLOT(slotViewExecuted()) );
+ connect(mFileDetailView,SIGNAL(currentChanged(QListViewItem*)),
+ this,SLOT(slotViewClicked()) );
+ connect(mFileDetailView,SIGNAL(selectionChanged()),
+ this,SLOT(slotViewClicked()) );
+ connect(mFileDetailView,SIGNAL(clicked(QListViewItem*)),
+ this,SLOT(slotViewClicked()) );
+ connect(mFileDetailView,SIGNAL(contextMenu(KListView*, QListViewItem*, const QPoint&)),
+ this,SLOT(openContextMenu(KListView*, QListViewItem*, const QPoint&)) );
+ connect(mFileDetailView,SIGNAL(dropped(QDropEvent*,KFileItem*)),
+ this,SLOT(openDropURLMenu(QDropEvent*, KFileItem*)) );
+ connect(mFileDetailView, SIGNAL(sortingChanged(QDir::SortSpec)),
+ this, SLOT(updateSortMenu(QDir::SortSpec)) );
+ connect(mFileDetailView, SIGNAL(doubleClicked(QListViewItem*)),
+ this, SLOT(slotViewDoubleClicked()) );
+ connect(mFileDetailView, SIGNAL(selectionChanged()),
+ this, SIGNAL(selectionChanged()) );
+
+ // Thumbnail widget
+ mFileThumbnailView=new FileThumbnailView(d->mStack);
+ d->mStack->addWidget(mFileThumbnailView,1);
+ mFileThumbnailView->viewport()->installEventFilter(this);
+
+ connect(mFileThumbnailView,SIGNAL(executed(QIconViewItem*)),
+ this,SLOT(slotViewExecuted()) );
+ connect(mFileThumbnailView,SIGNAL(returnPressed(QIconViewItem*)),
+ this,SLOT(slotViewExecuted()) );
+ connect(mFileThumbnailView,SIGNAL(currentChanged(QIconViewItem*)),
+ this,SLOT(slotViewClicked()) );
+ connect(mFileThumbnailView,SIGNAL(selectionChanged()),
+ this,SLOT(slotViewClicked()) );
+ connect(mFileThumbnailView,SIGNAL(clicked(QIconViewItem*)),
+ this,SLOT(slotViewClicked()) );
+ connect(mFileThumbnailView,SIGNAL(contextMenuRequested(QIconViewItem*,const QPoint&)),
+ this,SLOT(openContextMenu(QIconViewItem*,const QPoint&)) );
+ connect(mFileThumbnailView,SIGNAL(dropped(QDropEvent*,KFileItem*)),
+ this,SLOT(openDropURLMenu(QDropEvent*, KFileItem*)) );
+ connect(mFileThumbnailView, SIGNAL(doubleClicked(QIconViewItem*)),
+ this, SLOT(slotViewDoubleClicked()) );
+ connect(mFileThumbnailView, SIGNAL(selectionChanged()),
+ this, SIGNAL(selectionChanged()) );
+
+ // Thumbnail details dialog
+ KAction* thumbnailDetailsDialogAction=new KAction(i18n("Edit Thumbnail Details..."), "configure", 0, mFileThumbnailView, SLOT(showThumbnailDetailsDialog()), actionCollection, "thumbnail_details_dialog");
+ connect(mBottomThumbnailMode, SIGNAL(toggled(bool)),
+ thumbnailDetailsDialogAction, SLOT(setEnabled(bool)) );
+
+ // Fill toolbar
+ mListMode->plug(d->mToolBar);
+ mSideThumbnailMode->plug(d->mToolBar);
+ mBottomThumbnailMode->plug(d->mToolBar);
+ d->mToolBar->insertSeparator();
+ sliderAction->plug(d->mToolBar);
+ d->mToolBar->insertSeparator();
+ thumbnailDetailsDialogAction->plug(d->mToolBar);
+
+ int id=d->mToolBar->insertWidget(-1, 0, d->mFilterHBox);
+ d->mToolBar->alignItemRight(id, true);
+
+ mShowDotFiles->setChecked(FileViewConfig::showDotFiles());
+
+ bool startWithThumbnails=FileViewConfig::startWithThumbnails();
+ setMode(startWithThumbnails?THUMBNAIL:FILE_LIST);
+ mSizeSlider->setEnabled(startWithThumbnails);
+
+ if (startWithThumbnails) {
+ if (mFileThumbnailView->itemTextPos()==QIconView::Right) {
+ mSideThumbnailMode->setChecked(true);
+ } else {
+ mBottomThumbnailMode->setChecked(true);
+ }
+ // Make sure the thumbnail view and the slider tooltip are updated
+ updateThumbnailSize(mSizeSlider->value());
+ mFileThumbnailView->startThumbnailUpdate();
+ } else {
+ mListMode->setChecked(true);
+ }
+ thumbnailDetailsDialogAction->setEnabled(mBottomThumbnailMode->isChecked());
+
+ if (MiscConfig::rememberFilter()) {
+ d->loadFilterSettings();
+ }
+ updateFromSettings();
+}
+
+
+FileViewController::~FileViewController() {
+ // Save various settings
+ FileViewConfig::setStartWithThumbnails(mMode==THUMBNAIL);
+
+ int filterMode = d->mFilterComboBox->currentItem();
+ FileViewConfig::setFilterMode(filterMode);
+
+ FileViewConfig::setShowFilterBar(d->mShowFilterBarCheckBox->isChecked());
+ FileViewConfig::setNameFilter(d->mFilterBar->mNameEdit->text());
+ FileViewConfig::setFromDateFilter(d->mFilterBar->mFromDateEdit->date());
+ FileViewConfig::setToDateFilter(d->mFilterBar->mToDateEdit->date());
+
+ FileViewConfig::writeConfig();
+ delete mDirLister;
+ delete d;
+}
+
+
+void FileViewController::setFocus() {
+ currentFileView()->widget()->setFocus();
+}
+
+
+/**
+ * Do not let double click events propagate if Ctrl or Shift is down, to avoid
+ * toggling fullscreen
+ */
+bool FileViewController::eventFilter(QObject*, QEvent* event) {
+ if (event->type()!=QEvent::MouseButtonDblClick) return false;
+
+ QMouseEvent* mouseEvent=static_cast<QMouseEvent*>(event);
+ if (mouseEvent->state() & Qt::ControlButton || mouseEvent->state() & Qt::ShiftButton) {
+ return true;
+ }
+ return false;
+}
+
+
+bool FileViewController::lastURLError() const {
+ return mDirLister->error();
+}
+
+
+//-----------------------------------------------------------------------
+//
+// Public slots
+//
+//-----------------------------------------------------------------------
+void FileViewController::setDirURL(const KURL& url) {
+ LOG(url.prettyURL());
+ if ( mDirURL.equals(url,true) ) {
+ LOG("Same URL");
+ return;
+ }
+ prefetchDone();
+ mDirURL=url;
+ if (!KProtocolInfo::supportsListing(mDirURL)) {
+ LOG("Protocol does not support listing");
+ return;
+ }
+
+ mDirLister->clearError();
+ currentFileView()->setShownFileItem(0L);
+ mFileNameToSelect=QString::null;
+ mDirLister->openURL(mDirURL);
+ emit urlChanged(mDirURL);
+ emit directoryChanged(mDirURL);
+ updateActions();
+}
+
+
+/**
+ * Sets the file to select once the dir lister is done. If it's not running,
+ * immediatly selects the file.
+ */
+void FileViewController::setFileNameToSelect(const QString& fileName) {
+ mFileNameToSelect=fileName;
+ if (mDirLister->isFinished()) {
+ browseToFileNameToSelect();
+ }
+}
+
+void FileViewController::prefetch( KFileItem* item ) {
+ prefetchDone();
+ if( item == NULL ) return;
+ mPrefetch = ImageLoader::loader( item->url(), this, BUSY_PRELOADING );
+ connect( mPrefetch, SIGNAL( imageLoaded( bool )), SLOT( prefetchDone()));
+}
+
+void FileViewController::prefetchDone() {
+ if( mPrefetch != NULL ) {
+ mPrefetch->release( this );
+ mPrefetch = NULL;
+ }
+}
+
+void FileViewController::slotSelectFirst() {
+ browseTo( findFirstImage());
+ prefetch( findNextImage());
+}
+
+void FileViewController::slotSelectLast() {
+ browseTo(findLastImage());
+ prefetch( findPreviousImage());
+}
+
+void FileViewController::slotSelectPrevious() {
+ browseTo(findPreviousImage());
+ prefetch( findPreviousImage());
+}
+
+void FileViewController::slotSelectNext() {
+ browseTo(findNextImage());
+ prefetch( findNextImage());
+}
+
+void FileViewController::slotSelectPreviousDir() {
+ mChangeDirStatus = CHANGE_DIR_STATUS_PREV;
+ mDirLister->clearError();
+ mDirLister->openURL(mDirURL.upURL());
+}
+
+void FileViewController::slotSelectNextDir() {
+ mChangeDirStatus = CHANGE_DIR_STATUS_NEXT;
+ mDirLister->clearError();
+ mDirLister->openURL(mDirURL.upURL());
+}
+
+void FileViewController::slotSelectFirstSubDir() {
+ KFileItem* item=currentFileView()->firstFileItem();
+ while (item && !Archive::fileItemIsDirOrArchive(item)) {
+ item=currentFileView()->nextItem(item);
+ }
+ if (!item) {
+ LOG("No item found");
+ return;
+ }
+ LOG("item->url(): " << item->url().prettyURL());
+ KURL tmp=item->url();
+ if (Archive::fileItemIsArchive(item)) {
+ tmp.setProtocol(Archive::protocolForMimeType(item->mimetype()));
+ }
+ tmp.adjustPath(1);
+ setDirURL(tmp);
+}
+
+
+void FileViewController::resetNameFilter() {
+ d->mFilterBar->mNameEdit->clear();
+}
+
+
+void FileViewController::resetFromFilter() {
+ d->mFilterBar->mFromDateEdit->setDate(QDate());
+}
+
+
+void FileViewController::resetToFilter() {
+ d->mFilterBar->mToDateEdit->setDate(QDate());
+}
+
+
+void FileViewController::browseTo(KFileItem* item) {
+ prefetchDone();
+ if (mBrowsing) return;
+ mBrowsing = true;
+ if (item) {
+ currentFileView()->setCurrentItem(item);
+ currentFileView()->clearSelection();
+ currentFileView()->setSelected(item,true);
+ currentFileView()->ensureItemVisible(item);
+ if (!item->isDir() && !Archive::fileItemIsArchive(item)) {
+ emitURLChanged();
+ }
+ }
+ updateActions();
+ mBrowsing = false;
+}
+
+
+void FileViewController::browseToFileNameToSelect() {
+ // There's something to select
+ if (!mFileNameToSelect.isEmpty()) {
+ browseTo(findItemByFileName(mFileNameToSelect));
+ mFileNameToSelect=QString::null;
+ return;
+ }
+
+ // Nothing to select, but an item is already shown
+ if (currentFileView()->shownFileItem()) return;
+
+ // Now we have to make some default choice
+ slotSelectFirst();
+
+ // If no item is selected, make sure the first one is
+ if (currentFileView()->selectedItems()->count()==0) {
+ KFileItem* item=currentFileView()->firstFileItem();
+ if (item) {
+ currentFileView()->setCurrentItem(item);
+ currentFileView()->setSelected(item, true);
+ currentFileView()->ensureItemVisible(item);
+ }
+ }
+}
+
+
+void FileViewController::updateThumbnail(const KURL& url) {
+ if (mMode==FILE_LIST) return;
+
+ KFileItem* item=mDirLister->findByURL(url);
+ if (!item) return;
+ mFileThumbnailView->updateThumbnail(item);
+}
+
+
+//-----------------------------------------------------------------------
+//
+// Private slots
+//
+//-----------------------------------------------------------------------
+void FileViewController::slotViewExecuted() {
+ KFileItem* item=currentFileView()->currentFileItem();
+ if (!item) return;
+
+ bool isDir=item->isDir();
+ bool isArchive=Archive::fileItemIsArchive(item);
+ if (isDir || isArchive) {
+ KURL tmp=url();
+
+ if (isArchive) {
+ tmp.setProtocol(Archive::protocolForMimeType(item->mimetype()));
+ }
+ tmp.adjustPath(1);
+ setDirURL(tmp);
+ } else {
+ emitURLChanged();
+ }
+}
+
+
+void FileViewController::slotViewClicked() {
+ updateActions();
+ KFileItem* item=currentFileView()->currentFileItem();
+ if (!item || Archive::fileItemIsDirOrArchive(item)) return;
+
+ mSelecting = true;
+ emitURLChanged();
+ mSelecting = false;
+}
+
+
+void FileViewController::slotViewDoubleClicked() {
+ updateActions();
+ KFileItem* item=currentFileView()->currentFileItem();
+ if (item && !Archive::fileItemIsDirOrArchive(item)) emit imageDoubleClicked();
+}
+
+
+void FileViewController::updateViewMode() {
+ if (mListMode->isChecked()) {
+ setMode(FILE_LIST);
+ return;
+ }
+ if (mSideThumbnailMode->isChecked()) {
+ mFileThumbnailView->setItemTextPos(QIconView::Right);
+ } else {
+ mFileThumbnailView->setItemTextPos(QIconView::Bottom);
+ }
+
+ // Only switch the view if we are going from no thumbs to either side or
+ // bottom thumbs, not when switching between side and bottom thumbs
+ if (mMode==FILE_LIST) {
+ setMode(THUMBNAIL);
+ } else {
+ KFileItemList items=*mFileThumbnailView->items();
+ KFileItem* shownFileItem=mFileThumbnailView->shownFileItem();
+
+ mFileThumbnailView->FileViewBase::clear();
+ mFileThumbnailView->addItemList(items);
+ mFileThumbnailView->setShownFileItem(shownFileItem);
+ }
+
+ updateThumbnailSize(mSizeSlider->value());
+ mFileThumbnailView->startThumbnailUpdate();
+}
+
+
+void FileViewController::updateThumbnailSize(int size) {
+ size*=SLIDER_RESOLUTION;
+ d->mSliderTracker->setText(i18n("Thumbnail size: %1x%2").arg(size).arg(size));
+ FileViewConfig::setThumbnailSize(size);
+ mFileThumbnailView->setThumbnailSize(size);
+ Cache::instance()->checkThumbnailSize(size);
+}
+
+
+void FileViewController::toggleShowDotFiles() {
+ mDirLister->setShowingDotFiles(mShowDotFiles->isChecked());
+ mDirLister->openURL(mDirURL);
+}
+
+
+void FileViewController::updateSortMenu(QDir::SortSpec _spec) {
+ int spec=_spec & (QDir::Name | QDir::Time | QDir::Size);
+ int item;
+ switch (spec) {
+ case QDir::Name:
+ item=0;
+ break;
+ case QDir::Time:
+ item=1;
+ break;
+ case QDir::Size:
+ item=2;
+ break;
+ default:
+ item=-1;
+ break;
+ }
+ d->mSortAction->setCurrentItem(item);
+}
+
+
+void FileViewController::setSorting() {
+ QDir::SortSpec spec;
+
+ switch (d->mSortAction->currentItem()) {
+ case 0: // Name
+ spec=QDir::Name;
+ break;
+ case 1: // Date
+ spec=QDir::Time;
+ break;
+ case 2: // Size
+ spec=QDir::Size;
+ break;
+ default:
+ return;
+ }
+ if (d->mRevertSortAction->isChecked()) {
+ spec=QDir::SortSpec(spec | QDir::Reversed);
+ }
+ currentFileView()->setSorting(QDir::SortSpec(spec | QDir::DirsFirst));
+ emit sortingChanged();
+}
+
+
+//-----------------------------------------------------------------------
+//
+// Context menu
+//
+//-----------------------------------------------------------------------
+void FileViewController::openContextMenu(KListView*,QListViewItem* item,const QPoint& pos) {
+ emit requestContextMenu(pos, item!=0);
+}
+
+
+void FileViewController::openContextMenu(QIconViewItem* item,const QPoint& pos) {
+ emit requestContextMenu(pos, item!=0);
+}
+
+
+//-----------------------------------------------------------------------
+//
+// Drop URL menu
+//
+//-----------------------------------------------------------------------
+void FileViewController::openDropURLMenu(QDropEvent* event, KFileItem* item) {
+ KURL dest;
+
+ if (item) {
+ dest=item->url();
+ } else {
+ dest=mDirURL;
+ }
+
+ KURL::List urls;
+ if (!KURLDrag::decode(event,urls)) return;
+
+ FileOperation::openDropURLMenu(d->mStack, urls, dest);
+}
+
+
+//-----------------------------------------------------------------------
+//
+// File operations
+//
+//-----------------------------------------------------------------------
+KURL::List FileViewController::selectedURLs() const {
+ KURL::List list;
+
+ KFileItemListIterator it( *currentFileView()->selectedItems() );
+ for ( ; it.current(); ++it ) {
+ list.append(it.current()->url());
+ }
+ if (list.isEmpty()) {
+ const KFileItem* item=currentFileView()->shownFileItem();
+ if (item) list.append(item->url());
+ }
+ return list;
+}
+
+
+KURL::List FileViewController::selectedImageURLs() const {
+ KURL::List list;
+
+ KFileItemListIterator it( *currentFileView()->selectedItems() );
+ for ( ; it.current(); ++it ) {
+ KFileItem* item=it.current();
+ if (!Archive::fileItemIsDirOrArchive(item)) {
+ list.append(item->url());
+ }
+ }
+ if (list.isEmpty()) {
+ const KFileItem* item=currentFileView()->shownFileItem();
+ if (item && !Archive::fileItemIsDirOrArchive(item)) list.append(item->url());
+ }
+ return list;
+}
+
+
+//-----------------------------------------------------------------------
+//
+// Properties
+//
+//-----------------------------------------------------------------------
+QString FileViewController::fileName() const {
+ KFileItem* item=currentFileView()->currentFileItem();
+ if (!item) return "";
+ return item->text();
+}
+
+
+FileViewBase* FileViewController::currentFileView() const {
+ if (mMode==FILE_LIST) {
+ return mFileDetailView;
+ } else {
+ return mFileThumbnailView;
+ }
+}
+
+
+uint FileViewController::fileCount() const {
+ uint count=currentFileView()->count();
+
+ KFileItem* item=currentFileView()->firstFileItem();
+ while (item && Archive::fileItemIsDirOrArchive(item)) {
+ item=currentFileView()->nextItem(item);
+ count--;
+ }
+ return count;
+}
+
+
+int FileViewController::shownFilePosition() const {
+ KFileItem* shownItem=currentFileView()->shownFileItem();
+ if (!shownItem) return -1;
+ KFileItem* item=currentFileView()->firstFileItem();
+ int position=0;
+ for (;
+ item && item!=shownItem;
+ item=currentFileView()->nextItem(item) )
+ {
+ if (!Archive::fileItemIsDirOrArchive(item)) ++position;
+ }
+ return position;
+}
+
+
+KURL FileViewController::url() const {
+ KFileItem* item=currentFileView()->currentFileItem();
+ if (!item) return mDirURL;
+ return item->url();
+}
+
+KURL FileViewController::dirURL() const {
+ return mDirURL;
+}
+
+
+uint FileViewController::selectionSize() const {
+ const KFileItemList* selectedItems=currentFileView()->selectedItems();
+ return selectedItems->count();
+}
+
+
+void FileViewController::setMode(FileViewController::Mode mode) {
+ const KFileItemList* items;
+ FileViewBase* oldView;
+ FileViewBase* newView;
+
+ mMode=mode;
+
+ if (mMode==FILE_LIST) {
+ mFileThumbnailView->stopThumbnailUpdate();
+ oldView=mFileThumbnailView;
+ newView=mFileDetailView;
+ } else {
+ oldView=mFileDetailView;
+ newView=mFileThumbnailView;
+ }
+
+ bool wasFocused=oldView->widget()->hasFocus();
+ // Show the new active view
+ d->mStack->raiseWidget(newView->widget());
+ if (wasFocused) newView->widget()->setFocus();
+
+ // Fill the new view
+ newView->clear();
+ newView->addItemList(*oldView->items());
+
+ // Set the new view to the same state as the old
+ items=oldView->selectedItems();
+ for(KFileItemListIterator it(*items);it.current()!=0L;++it) {
+ newView->setSelected(it.current(), true);
+ }
+ newView->setShownFileItem(oldView->shownFileItem());
+ newView->setCurrentItem(oldView->currentFileItem());
+
+ // Remove references to the old view from KFileItems
+ items=oldView->items();
+ for(KFileItemListIterator it(*items);it.current()!=0L;++it) {
+ it.current()->removeExtraData(oldView);
+ }
+
+ // Update sorting
+ newView->setSorting(oldView->sorting());
+
+ // Clear the old view
+ oldView->FileViewBase::clear();
+}
+
+
+void FileViewController::updateFromSettings() {
+ applyFilter();
+ mFileThumbnailView->setMarginSize(FileViewConfig::thumbnailMarginSize());
+ mFileThumbnailView->setItemDetails(FileViewConfig::thumbnailDetails());
+ currentFileView()->widget()->update();
+}
+
+
+void FileViewController::setSilentMode( bool silent ) {
+ mDirLister->setCheck( !silent );
+}
+
+
+void FileViewController::retryURL() {
+ mDirLister->clearError();
+ mDirLister->openURL( url());
+}
+
+
+//-----------------------------------------------------------------------
+//
+// Dir lister slots
+//
+//-----------------------------------------------------------------------
+void FileViewController::dirListerDeleteItem(KFileItem* item) {
+ KFileItem* newShownItem=0L;
+ const KFileItem* shownItem=currentFileView()->shownFileItem();
+ if (shownItem==item) {
+ newShownItem=findNextImage();
+ if (!newShownItem) newShownItem=findPreviousImage();
+ }
+
+ currentFileView()->removeItem(item);
+
+ if (shownItem==item) {
+ currentFileView()->setCurrentItem(newShownItem);
+ currentFileView()->setSelected(newShownItem, true);
+ if (newShownItem) {
+ emit urlChanged(newShownItem->url());
+ } else {
+ emit urlChanged(KURL());
+ }
+ }
+}
+
+
+void FileViewController::dirListerNewItems(const KFileItemList& items) {
+ LOG("");
+ mThumbnailsNeedUpdate=true;
+ currentFileView()->addItemList(items);
+}
+
+
+void FileViewController::dirListerRefreshItems(const KFileItemList& list) {
+ LOG("");
+ const KFileItem* item=currentFileView()->shownFileItem();
+ KFileItemListIterator it(list);
+ for (; *it!=0L; ++it) {
+ currentFileView()->updateView(*it);
+ if (*it==item) {
+ emit shownFileItemRefreshed(item);
+ }
+ }
+}
+
+
+void FileViewController::refreshItems(const KURL::List& urls) {
+ LOG("");
+ KFileItemList list;
+ for( KURL::List::ConstIterator it = urls.begin();
+ it != urls.end();
+ ++it ) {
+ KURL dir = *it;
+ dir.setFileName( QString::null );
+ if( dir != mDirURL ) continue;
+ // TODO this could be quite slow for many images?
+ KFileItem* item = findItemByFileName( (*it).filename());
+ if( item ) list.append( item );
+ }
+ dirListerRefreshItems( list );
+}
+
+
+void FileViewController::dirListerClear() {
+ currentFileView()->clear();
+}
+
+
+void FileViewController::dirListerStarted() {
+ LOG("");
+ mThumbnailsNeedUpdate=false;
+}
+
+
+void FileViewController::dirListerCompleted() {
+ LOG("");
+ // Delay the code to be executed when the dir lister has completed its job
+ // to avoid crash in KDirLister (see bug #57991)
+ QTimer::singleShot(0,this,SLOT(delayedDirListerCompleted()));
+}
+
+
+void FileViewController::delayedDirListerCompleted() {
+ // The call to sort() is a work around to a bug which causes
+ // FileThumbnailView::firstFileItem() to return a wrong item. This work
+ // around is not in firstFileItem() because it's const and sort() is a non
+ // const method
+ if (mMode!=FILE_LIST) {
+ mFileThumbnailView->sort(mFileThumbnailView->sortDirection());
+ }
+
+ if (mChangeDirStatus != CHANGE_DIR_STATUS_NONE) {
+ KFileItem *item;
+ QString fileName = mDirURL.filename();
+ for (item=currentFileView()->firstFileItem(); item; item=currentFileView()->nextItem(item) ) {
+ if (item->name() == fileName) {
+ if (mChangeDirStatus == CHANGE_DIR_STATUS_NEXT) {
+ do {
+ item=currentFileView()->nextItem(item);
+ } while (item && !Archive::fileItemIsDirOrArchive(item));
+ } else {
+ do {
+ item=currentFileView()->prevItem(item);
+ } while (item && !Archive::fileItemIsDirOrArchive(item));
+ }
+ break;
+ };
+ }
+ mChangeDirStatus = CHANGE_DIR_STATUS_NONE;
+ if (!item) {
+ mDirLister->openURL(mDirURL);
+ } else {
+ KURL tmp=item->url();
+ LOG("item->url(): " << item->url().prettyURL());
+ if (Archive::fileItemIsArchive(item)) {
+ tmp.setProtocol(Archive::protocolForMimeType(item->mimetype()));
+ }
+ tmp.adjustPath(1);
+ setDirURL(tmp);
+ }
+ } else {
+ browseToFileNameToSelect();
+ emit completed();
+
+ if (mMode!=FILE_LIST && mThumbnailsNeedUpdate) {
+ mFileThumbnailView->startThumbnailUpdate();
+ }
+ }
+}
+
+
+void FileViewController::dirListerCanceled() {
+ if (mMode!=FILE_LIST) {
+ mFileThumbnailView->stopThumbnailUpdate();
+ }
+
+ browseToFileNameToSelect();
+}
+
+
+void FileViewController::setShowFilterBar(bool value) {
+ d->mShowFilterBarCheckBox->setChecked(value);
+}
+
+
+void FileViewController::setFilterMode(int mode) {
+ d->mFilterComboBox->setCurrentItem(mode);
+}
+
+
+void FileViewController::setFilterName(const QString& name) {
+ d->mFilterBar->mNameEdit->setText(name);
+}
+
+
+void FileViewController::setFilterFromDate(const QDate& date) {
+ d->mFilterBar->mFromDateEdit->setDate(date);
+}
+
+
+void FileViewController::setFilterToDate(const QDate& date) {
+ d->mFilterBar->mToDateEdit->setDate(date);
+}
+
+
+void FileViewController::applyFilter() {
+ QStringList mimeTypes;
+ FilterMode filterMode = static_cast<FilterMode>( d->mFilterComboBox->currentItem() );
+
+ if (FileViewConfig::showDirs()) {
+ mimeTypes << "inode/directory";
+ mimeTypes += Archive::mimeTypes();
+ }
+
+ if (filterMode != VIDEOS_ONLY) {
+ mimeTypes += MimeTypeUtils::rasterImageMimeTypes();
+ mimeTypes << "image/svg";
+ }
+
+ if (filterMode != IMAGES_ONLY) {
+ mimeTypes << "video/";
+ }
+
+ if (d->mShowFilterBarCheckBox->isChecked()) {
+ QString txt=d->mFilterBar->mNameEdit->text();
+ QDate from=d->mFilterBar->mFromDateEdit->date();
+ QDate to=d->mFilterBar->mToDateEdit->date();
+
+ mDirLister->setNameFilter(txt);
+ mDirLister->setDateFilter(from, to);
+ } else {
+ mDirLister->setNameFilter(QString::null);
+ mDirLister->setDateFilter(QDate(), QDate());
+ }
+
+ mDirLister->setShowingDotFiles(mShowDotFiles->isChecked());
+ mDirLister->setMimeFilter(mimeTypes);
+
+ // Find next item matching the filter if any, so that we can keep it
+ // current
+ KFileItem* item=currentFileView()->currentFileItem();
+ for (; item; item=currentFileView()->nextItem(item)) {
+ if (mDirLister->itemMatchFilters(item)) {
+ mFileNameToSelect=item->name();
+ break;
+ }
+ }
+
+ mDirLister->openURL(mDirURL);
+}
+
+
+void FileViewController::updateActions() {
+ KFileItem* firstImage=findFirstImage();
+
+ // There isn't any image, no need to continue
+ if (!firstImage) {
+ mSelectFirst->setEnabled(false);
+ mSelectPrevious->setEnabled(false);
+ mSelectNext->setEnabled(false);
+ mSelectLast->setEnabled(false);
+ return;
+ }
+
+ // We did not select any image, let's activate everything
+ KFileItem* currentItem=currentFileView()->currentFileItem();
+ if (!currentItem || Archive::fileItemIsDirOrArchive(currentItem)) {
+ mSelectFirst->setEnabled(true);
+ mSelectPrevious->setEnabled(true);
+ mSelectNext->setEnabled(true);
+ mSelectLast->setEnabled(true);
+ return;
+ }
+
+ // There is at least one image, and an image is selected, let's be precise
+ bool isFirst=currentItem==firstImage;
+ bool isLast=currentItem==findLastImage();
+
+ mSelectFirst->setEnabled(!isFirst);
+ mSelectPrevious->setEnabled(!isFirst);
+ mSelectNext->setEnabled(!isLast);
+ mSelectLast->setEnabled(!isLast);
+}
+
+
+void FileViewController::emitURLChanged() {
+ KFileItem* item=currentFileView()->currentFileItem();
+ currentFileView()->setShownFileItem(item);
+
+ // We use a tmp value because the signal parameter is a reference
+ KURL tmp=url();
+ LOG("urlChanged: " << tmp.prettyURL());
+ emit urlChanged(tmp);
+}
+
+KFileItem* FileViewController::findFirstImage() const {
+ KFileItem* item=currentFileView()->firstFileItem();
+ while (item && Archive::fileItemIsDirOrArchive(item)) {
+ item=currentFileView()->nextItem(item);
+ }
+ if (item) {
+ LOG("item->url(): " << item->url().prettyURL());
+ } else {
+ LOG("No item found");
+ }
+ return item;
+}
+
+KFileItem* FileViewController::findLastImage() const {
+ KFileItem* item=currentFileView()->items()->getLast();
+ while (item && Archive::fileItemIsDirOrArchive(item)) {
+ item=currentFileView()->prevItem(item);
+ }
+ return item;
+}
+
+KFileItem* FileViewController::findPreviousImage() const {
+ KFileItem* item=currentFileView()->shownFileItem();
+ if (!item) return 0L;
+ do {
+ item=currentFileView()->prevItem(item);
+ } while (item && Archive::fileItemIsDirOrArchive(item));
+ return item;
+}
+
+KFileItem* FileViewController::findNextImage() const {
+ KFileItem* item=currentFileView()->shownFileItem();
+ if (!item) return 0L;
+ do {
+ item=currentFileView()->nextItem(item);
+ } while (item && Archive::fileItemIsDirOrArchive(item));
+ return item;
+}
+
+KFileItem* FileViewController::findItemByFileName(const QString& fileName) const {
+ KFileItem *item;
+ if (fileName.isEmpty()) return 0L;
+ for (item=currentFileView()->firstFileItem();
+ item;
+ item=currentFileView()->nextItem(item) ) {
+ if (item->name()==fileName) return item;
+ }
+
+ return 0L;
+}
+
+
+} // namespace
diff --git a/src/gvcore/fileviewcontroller.h b/src/gvcore/fileviewcontroller.h
new file mode 100644
index 0000000..773de57
--- /dev/null
+++ b/src/gvcore/fileviewcontroller.h
@@ -0,0 +1,256 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab:
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2000-2004 Aurélien Gâteau
+
+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.
+
+*/
+#ifndef FILEVIEWCONTROLLER_H
+#define FILEVIEWCONTROLLER_H
+
+// Qt
+#include <qdir.h>
+#include <qslider.h>
+#include <qwidget.h>
+
+// KDE
+#include <kdirlister.h>
+#include <kfileitem.h>
+#include <kio/job.h>
+#include <kurl.h>
+
+#include "libgwenview_export.h"
+class QIconViewItem;
+class QListViewItem;
+class QPopupMenu;
+
+class KAccel;
+class KAction;
+class KActionCollection;
+class KConfig;
+class KListView;
+class KRadioAction;
+class KToggleAction;
+
+namespace Gwenview {
+class FileViewBase;
+class FileDetailView;
+class FileThumbnailView;
+class ImageLoader;
+
+
+class DirLister;
+
+class LIBGWENVIEW_EXPORT FileViewController : public QWidget {
+Q_OBJECT
+
+public:
+ enum Mode { FILE_LIST, THUMBNAIL};
+ enum FilterMode { ALL, IMAGES_ONLY, VIDEOS_ONLY };
+
+ FileViewController(QWidget* parent,KActionCollection*);
+ ~FileViewController();
+
+ // Properties
+ void setMode(Mode);
+
+ QString fileName() const;
+ KURL url() const;
+ KURL dirURL() const;
+ uint fileCount() const;
+ int shownFilePosition() const;
+
+ uint selectionSize() const;
+
+ FileViewBase* currentFileView() const;
+ FileThumbnailView* fileThumbnailView() const { return mFileThumbnailView; }
+
+ KAction* selectFirst() const { return mSelectFirst; }
+ KAction* selectLast() const { return mSelectLast; }
+ KAction* selectPrevious() const { return mSelectPrevious; }
+ KAction* selectNext() const { return mSelectNext; }
+ KAction* selectPreviousDir() const { return mSelectPreviousDir; }
+ KAction* selectNextDir() const { return mSelectNextDir; }
+ KAction* selectFirstSubDir() const { return mSelectFirstSubDir; }
+ KRadioAction* listMode() const { return mListMode; }
+ KRadioAction* sideThumbnailMode() const { return mSideThumbnailMode; }
+ KRadioAction* bottomThumbnailMode() const { return mBottomThumbnailMode; }
+ KToggleAction* showDotFiles() const { return mShowDotFiles; }
+
+ KURL::List selectedURLs() const;
+ KURL::List selectedImageURLs() const;
+ /**
+ * If set to true, no error messages will be displayed.
+ */
+ void setSilentMode( bool silent );
+ /**
+ * Returns true if there was an error since last URL had been opened.
+ */
+ bool lastURLError() const;
+ /**
+ * Tries to open again the active URL. Useful for showing error messages
+ * initially supressed by silent mode.
+ */
+ void retryURL();
+
+ void refreshItems( const KURL::List& urls ); // used by a workaround in KIPIInterface
+
+ virtual void setFocus();
+
+public slots:
+ void setDirURL(const KURL&);
+ void setFileNameToSelect(const QString&);
+
+ void slotSelectFirst();
+ void slotSelectLast();
+ void slotSelectPrevious();
+ void slotSelectNext();
+ void slotSelectPreviousDir();
+ void slotSelectNextDir();
+ void slotSelectFirstSubDir();
+
+ void updateThumbnail(const KURL&);
+
+ void updateFromSettings();
+
+ void setShowFilterBar(bool);
+ // 'int' suck, but I don't want to #include fileviewconfig.h
+ void setFilterMode(int);
+ void setFilterName(const QString&);
+ void setFilterFromDate(const QDate&);
+ void setFilterToDate(const QDate&);
+ void applyFilter();
+
+signals:
+ void urlChanged(const KURL&);
+ /**
+ * Used by DirPart to tell Konqueror to change directory
+ */
+ void directoryChanged(const KURL&);
+
+ void selectionChanged();
+ void completed();
+ void canceled();
+ void imageDoubleClicked();
+ void shownFileItemRefreshed(const KFileItem*);
+ void sortingChanged();
+ void requestContextMenu(const QPoint& pos, bool onItem);
+
+private slots:
+ void delayedDirListerCompleted();
+
+ // Used to enter directories
+ void slotViewExecuted();
+
+ // Used to change the current image
+ void slotViewClicked();
+
+ void slotViewDoubleClicked();
+
+ // These two methods forward the context menu requests from either view to
+ // openContextMenu(const QPoint&);
+ void openContextMenu(KListView*, QListViewItem*, const QPoint&);
+ void openContextMenu(QIconViewItem*,const QPoint&);
+
+ // Get called by the thumbnail mode actions
+ void updateViewMode();
+
+ // Get called by the thumbnail slider
+ void updateThumbnailSize(int);
+
+ void toggleShowDotFiles();
+ void setSorting();
+ void updateSortMenu(QDir::SortSpec);
+
+ // Dir lister slots
+ void dirListerDeleteItem(KFileItem* item);
+ void dirListerNewItems(const KFileItemList& items);
+ void dirListerRefreshItems(const KFileItemList&);
+ void dirListerClear();
+ void dirListerStarted();
+ void dirListerCanceled();
+ void dirListerCompleted();
+
+ void openDropURLMenu(QDropEvent*, KFileItem*);
+
+ void prefetchDone();
+
+ void resetNameFilter();
+ void resetFromFilter();
+ void resetToFilter();
+
+private:
+ struct Private;
+ Private* d;
+ Mode mMode;
+ FileDetailView* mFileDetailView;
+ FileThumbnailView* mFileThumbnailView;
+ DirLister* mDirLister;
+ KURL mDirURL;
+ ImageLoader* mPrefetch;
+
+ // Our actions
+ KAction* mSelectFirst;
+ KAction* mSelectLast;
+ KAction* mSelectPrevious;
+ KAction* mSelectNext;
+ KAction* mSelectPreviousDir;
+ KAction* mSelectNextDir;
+ KAction* mSelectFirstSubDir;
+
+ KRadioAction* mListMode;
+ KRadioAction* mSideThumbnailMode;
+ KRadioAction* mBottomThumbnailMode;
+
+ QSlider* mSizeSlider;
+
+ KToggleAction* mShowDotFiles;
+
+ // Temp data used by the dir lister
+ bool mThumbnailsNeedUpdate;
+ QString mFileNameToSelect;
+ enum ChangeDirStatusVals {
+ CHANGE_DIR_STATUS_NONE,
+ CHANGE_DIR_STATUS_PREV,
+ CHANGE_DIR_STATUS_NEXT
+ } mChangeDirStatus;
+
+ bool mBrowsing;
+ bool mSelecting;
+
+ /**
+ * Browse to the given item. Prevents multiple calls using mBrowsing.
+ */
+ void browseTo(KFileItem* item);
+
+ void browseToFileNameToSelect();
+ void emitURLChanged();
+ void updateActions();
+ void prefetch( KFileItem* item );
+
+ KFileItem* findFirstImage() const;
+ KFileItem* findLastImage() const;
+ KFileItem* findPreviousImage() const;
+ KFileItem* findNextImage() const;
+ KFileItem* findItemByFileName(const QString& fileName) const;
+
+ bool eventFilter(QObject*, QEvent*);
+};
+
+
+} // namespace
+#endif
+
diff --git a/src/gvcore/filterbar.ui b/src/gvcore/filterbar.ui
new file mode 100644
index 0000000..461e9f7
--- /dev/null
+++ b/src/gvcore/filterbar.ui
@@ -0,0 +1,255 @@
+<!DOCTYPE UI><UI version="3.2" stdsetdef="1">
+<class>FilterBar</class>
+<widget class="QWidget">
+ <property name="name">
+ <cstring>FilterBar</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>809</width>
+ <height>30</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>5</hsizetype>
+ <vsizetype>5</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <hbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <property name="margin">
+ <number>3</number>
+ </property>
+ <widget class="QPushButton">
+ <property name="name">
+ <cstring>mResetNameCombo</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string></string>
+ </property>
+ </widget>
+ <widget class="Gwenview::ClickLineEdit">
+ <property name="name">
+ <cstring>mNameEdit</cstring>
+ </property>
+ <property name="clickMessage" stdset="0">
+ <string>Name</string>
+ </property>
+ <property name="toolTip" stdset="0">
+ <string>Filter files with wildcards, like *.png</string>
+ </property>
+ </widget>
+ <spacer>
+ <property name="name">
+ <cstring>spacer1_2_2_2_3</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Maximum</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>16</width>
+ <height>16</height>
+ </size>
+ </property>
+ </spacer>
+ <widget class="QPushButton">
+ <property name="name">
+ <cstring>mResetFrom</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string></string>
+ </property>
+ </widget>
+ <widget class="QLabel">
+ <property name="name">
+ <cstring>textLabel1_2</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>5</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>From:</string>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring>mFromDateEdit</cstring>
+ </property>
+ </widget>
+ <widget class="QDateEdit">
+ <property name="name">
+ <cstring>mFromDateEdit</cstring>
+ </property>
+ <property name="toolTip" stdset="0">
+ <string>Only show files newer than or
+equal to this date</string>
+ </property>
+ </widget>
+ <spacer>
+ <property name="name">
+ <cstring>spacer1_2_2_2_2</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Maximum</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>16</width>
+ <height>16</height>
+ </size>
+ </property>
+ </spacer>
+ <widget class="QPushButton">
+ <property name="name">
+ <cstring>mResetTo</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string></string>
+ </property>
+ </widget>
+ <widget class="QLabel">
+ <property name="name">
+ <cstring>textLabel2</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>5</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>To:</string>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring>mToDateEdit</cstring>
+ </property>
+ </widget>
+ <widget class="QDateEdit">
+ <property name="name">
+ <cstring>mToDateEdit</cstring>
+ </property>
+ <property name="toolTip" stdset="0">
+ <string>Only show files older than or equal to this date</string>
+ </property>
+ </widget>
+ <spacer>
+ <property name="name">
+ <cstring>spacer1_2_2_2</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Maximum</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>16</width>
+ <height>16</height>
+ </size>
+ </property>
+ </spacer>
+ <widget class="QPushButton">
+ <property name="name">
+ <cstring>mFilterButton</cstring>
+ </property>
+ <property name="text">
+ <string>&amp;Filter</string>
+ </property>
+ </widget>
+ <spacer>
+ <property name="name">
+ <cstring>spacer1_2_2</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Expanding</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>16</width>
+ <height>16</height>
+ </size>
+ </property>
+ </spacer>
+ </hbox>
+</widget>
+<customwidgets>
+ <customwidget>
+ <class>Gwenview::ClickLineEdit</class>
+ <header location="local">clicklineedit.h</header>
+ <sizehint>
+ <width>100</width>
+ <height>20</height>
+ </sizehint>
+ <container>0</container>
+ <sizepolicy>
+ <hordata>1</hordata>
+ <verdata>0</verdata>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ <pixmap>image0</pixmap>
+ <property type="String">clickMessage</property>
+ </customwidget>
+</customwidgets>
+<images>
+ <image name="image0">
+ <data format="PNG" length="818">89504e470d0a1a0a0000000d4948445200000016000000160806000000c4b46c3b000002f949444154388db5943168dc6614c77f57343cc10d12dc20c1054e5b0e6ac875ea1dedd2ed0e3ad4e0a1052f860c69c7ae490a1d1bdc4c4d8752770a5d42132834834137f4a20b047a1e0a2ab8200f067d83411f44a03708d2413e3bcef9da42ddb7083ebdeff7fddfff7ddf6b2549c23246a3d12bae20922469b596e067f367afa228425f2ab9c9b1a5fd57102d95344d8967315aead9bab354fae8a747848140d712f615384f1267997909b8ae181c0322fcf2e40d3080be54e85af232c6960ba82b10a0762972302715e0127484f09a22ed0aad4f37b7c10b2f1e7806ce4d4ed4d7536801e293a510ff5c319f5960698dc7e086c764cb673004289aaada6bc0b6b4287aaad4e7b75fe1c1bdbc417584fedb3dc481ec48591c581607969d5b21938f5da0827a0df8dc50c85278702f0360733b623206afd3fc53f549f661ef7ec6deb71961d0673816702e36fbad1570ed123f2ecea09f6c0bd2a9400aec89103f553ef850f8ec4e04c0c31f72d40a82ac572c0e14392c9e2b41c7633206757204416dc8f75f17cc67066b02766e85c44f3d16cf2dd9a1aed47e51b1d3745f51a2eb82d70171042d7dbef9aa81f6ba1ec3f77c5c4f190c7c00ccf13f8101700145a4f1d49e9c43015c11fc50c05196d557b5ac5056c05e07c0233b52548564aacc678620f0e8f502d23f0dbb7733b23f02cc61850051af91b3d66380e81af46f78a40796641f265b1ed6060cdff5f14361f7ae92fe6eb8fd29a8b5785d21da0073fc778a6b9076c5e6478d777bf733e65365e766c83b4388ae5b3effb24faf17608d416bd8dc0a89ba50bd360256153ba0350c86b0733364efbb94dd2f32e27d8fc186db3437cb3046c1697c9d4f2b46630fd75973ddb454b4ae4e570b26db2e6130e0e18f398b996131836630094157d8dc8a984e0b8a524169be9781d3346da6541b96ad188e61f07e487a18628f9650883620ecc1681c35673996f4c51a703c8b41042f04695ba89b2b25c8799603c640661af5ae231425a42f2cf91bcdbb60c5ebf3f4bfc6250fe40ac14992b4fe17f055c3932469fd051dce4f49a767eff00000000049454e44ae426082</data>
+ </image>
+</images>
+<tabstops>
+ <tabstop>mResetNameCombo</tabstop>
+ <tabstop>mResetFrom</tabstop>
+ <tabstop>mFromDateEdit</tabstop>
+ <tabstop>mResetTo</tabstop>
+ <tabstop>mToDateEdit</tabstop>
+ <tabstop>mFilterButton</tabstop>
+</tabstops>
+<layoutdefaults spacing="6" margin="11"/>
+</UI>
diff --git a/src/gvcore/fullscreenbar.cpp b/src/gvcore/fullscreenbar.cpp
new file mode 100644
index 0000000..fb837ec
--- /dev/null
+++ b/src/gvcore/fullscreenbar.cpp
@@ -0,0 +1,171 @@
+// vim:set tabstop=4 shiftwidth=4 noexpandtab:
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2000-2004 Aur�ien G�eau
+
+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.
+
+*/
+// Qt
+#include <qbitmap.h>
+#include <qevent.h>
+#include <qlayout.h>
+#include <qpainter.h>
+#include <qtimer.h>
+
+// KDE
+#include <kdebug.h>
+
+// Local
+#include "fullscreenbar.moc"
+namespace Gwenview {
+
+
+const int FULLSCREEN_ICON_SIZE = 32;
+const int FULLSCREEN_LABEL_RADIUS = 6;
+// Intervals are in milliseconds
+const int SLIDE_IN_INTERVAL = 4;
+const int SLIDE_OUT_INTERVAL = 12;
+// Step is in pixels
+const int SLIDE_STEP = 4;
+
+
+static void fillMask(QPainter& painter, const QRect& rect) {
+ painter.fillRect(
+ rect.left(),
+ rect.top(),
+ rect.width() - FULLSCREEN_LABEL_RADIUS,
+ rect.height(),
+ painter.brush());
+
+ painter.fillRect(
+ rect.right() - FULLSCREEN_LABEL_RADIUS + 1,
+ rect.top(),
+ FULLSCREEN_LABEL_RADIUS,
+ rect.height() - FULLSCREEN_LABEL_RADIUS,
+ painter.brush());
+
+ painter.drawPie(
+ rect.right() - 2*FULLSCREEN_LABEL_RADIUS + 1,
+ rect.bottom() - 2*FULLSCREEN_LABEL_RADIUS + 1,
+ FULLSCREEN_LABEL_RADIUS*2, FULLSCREEN_LABEL_RADIUS*2,
+ 0, -16*90);
+}
+
+
+enum BarState { OUT, SLIDING_OUT, SLIDING_IN, IN };
+
+
+struct FullScreenBar::Private {
+ QTimer mTimer;
+ BarState mState;
+ bool mFirstShow;
+};
+
+
+FullScreenBar::FullScreenBar(QWidget* parent)
+: KToolBar(parent, "FullScreenBar") {
+ d=new Private;
+ d->mState=OUT;
+ d->mFirstShow=true;
+ setIconSize(FULLSCREEN_ICON_SIZE);
+ setMovingEnabled(false);
+
+ QColor bg=colorGroup().highlight();
+ QColor fg=colorGroup().highlightedText();
+ QPalette pal(palette());
+ pal.setColor(QColorGroup::Background, bg);
+ pal.setColor(QColorGroup::Foreground, fg);
+ pal.setColor(QColorGroup::Button, bg);
+ pal.setColor(QColorGroup::ButtonText, fg);
+ setPalette(pal);
+
+ // Timer
+ connect(&d->mTimer, SIGNAL(timeout()), this, SLOT(slotUpdateSlide()) );
+}
+
+
+FullScreenBar::~FullScreenBar() {
+ delete d;
+}
+
+
+void FullScreenBar::resizeEvent(QResizeEvent* event) {
+ KToolBar::resizeEvent(event);
+
+ // Create a mask
+ QPainter painter;
+ QBitmap mask(size(), true);
+ painter.begin(&mask);
+ painter.setBrush(Qt::white);
+ fillMask(painter, rect());
+ painter.end();
+
+ setMask(mask);
+}
+
+
+void FullScreenBar::showEvent(QShowEvent* event) {
+ KToolBar::showEvent(event);
+ // Make sure the bar position corresponds to the OUT state
+ if (!d->mFirstShow) return;
+ d->mFirstShow=false;
+ move(0, -height());
+ layout()->setResizeMode(QLayout::Fixed);
+}
+
+
+void FullScreenBar::slideIn() {
+ if (d->mState!=IN) {
+ d->mState=SLIDING_IN;
+ d->mTimer.start(SLIDE_IN_INTERVAL);
+ }
+}
+
+
+void FullScreenBar::slideOut() {
+ if (d->mState!=OUT) {
+ d->mState=SLIDING_OUT;
+ d->mTimer.start(SLIDE_OUT_INTERVAL);
+ }
+}
+
+
+void FullScreenBar::slotUpdateSlide() {
+ int pos=y();
+
+ switch (d->mState) {
+ case SLIDING_OUT:
+ pos-=SLIDE_STEP;
+ if (pos<=-height()) {
+ d->mState=OUT;
+ d->mTimer.stop();
+ }
+ break;
+ case SLIDING_IN:
+ pos+=SLIDE_STEP;
+ if (pos>=0) {
+ pos=0;
+ d->mState=IN;
+ d->mTimer.stop();
+ }
+ break;
+ default:
+ kdWarning() << k_funcinfo << "We should not get there\n";
+ }
+ move(0, pos);
+}
+
+} // namespace
diff --git a/src/gvcore/fullscreenbar.h b/src/gvcore/fullscreenbar.h
new file mode 100644
index 0000000..3eb1919
--- /dev/null
+++ b/src/gvcore/fullscreenbar.h
@@ -0,0 +1,56 @@
+// vim:set tabstop=4 shiftwidth=4 noexpandtab:
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2000-2004 Aur�ien G�eau
+
+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.
+
+*/
+
+#ifndef FULLSCREENBAR_H
+#define FULLSCREENBAR_H
+
+// KDE
+#include <ktoolbar.h>
+
+class QResizeEvent;
+class QShowEvent;
+class QString;
+
+namespace Gwenview {
+class FullScreenBar : public KToolBar {
+Q_OBJECT
+public:
+ FullScreenBar(QWidget* parent);
+ ~FullScreenBar();
+
+ void slideIn();
+ void slideOut();
+
+protected:
+ virtual void resizeEvent(QResizeEvent*);
+ virtual void showEvent(QShowEvent*);
+
+private slots:
+ void slotUpdateSlide();
+
+private:
+ class Private;
+ Private* d;
+};
+
+} // namespace
+#endif /* FULLSCREENBAR_H */
+
diff --git a/src/gvcore/fullscreenconfig.kcfg b/src/gvcore/fullscreenconfig.kcfg
new file mode 100644
index 0000000..38dbc4e
--- /dev/null
+++ b/src/gvcore/fullscreenconfig.kcfg
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE kcfg SYSTEM "http://www.kde.org/standards/kcfg/1.0/kcfg.dtd">
+<kcfg>
+ <kcfgfile name="gwenviewrc"/>
+ <group name="main window">
+ <entry name="showBusyPtr" key="busy ptr in full screen" type="Bool">
+ <default>true</default>
+ </entry>
+ </group>
+ <group name="pixmap widget">
+ <entry name="osdFormat" type="String">
+ <default>%f - %n/%N
+%c</default>
+ </entry>
+ </group>
+</kcfg>
diff --git a/src/gvcore/fullscreenconfig.kcfgc b/src/gvcore/fullscreenconfig.kcfgc
new file mode 100644
index 0000000..74be809
--- /dev/null
+++ b/src/gvcore/fullscreenconfig.kcfgc
@@ -0,0 +1,7 @@
+File=fullscreenconfig.kcfg
+ClassName=FullScreenConfig
+NameSpace=Gwenview
+Singleton=true
+Mutators=true
+IncludeFiles=gvcore/libgwenview_export.h
+Visibility=LIBGWENVIEW_EXPORT
diff --git a/src/gvcore/gimp.h b/src/gvcore/gimp.h
new file mode 100644
index 0000000..0c92b51
--- /dev/null
+++ b/src/gvcore/gimp.h
@@ -0,0 +1,138 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/* -*- c++ -*-
+ * gimp.h: Header for a Qt 3 plug-in for reading GIMP XCF image files
+ * Copyright (C) 2001 lignum Computing, Inc. <allen@lignumcomputing.com>
+ *
+ * This plug-in is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+/*
+ * These are the constants and functions I extracted from The GIMP source
+ * code. If the reader fails to work, this is probably the place to start
+ * looking for discontinuities.
+ */
+
+// From GIMP "tile.h" v1.2
+
+const uint TILE_WIDTH = 64; //!< Width of a tile in the XCF file.
+const uint TILE_HEIGHT = 64; //!< Height of a tile in the XCF file.
+
+// From GIMP "paint_funcs.c" v1.2
+
+const int RANDOM_TABLE_SIZE = 4096; //!< Size of dissolve random number table.
+const int RANDOM_SEED = 314159265; //!< Seed for dissolve random number table.
+const double EPSILON = 0.0001; //!< Roundup in alpha blending.
+
+// From GIMP "paint_funcs.h" v1.2
+
+const uchar OPAQUE_OPACITY = 255; //!< Opaque value for 8-bit alpha component.
+
+// From GIMP "apptypes.h" v1.2
+
+//! Basic GIMP image type. QImage converter may produce a deeper image
+//! than is specified here. For example, a grayscale image with an
+//! alpha channel must (currently) use a 32-bit Qt image.
+
+typedef enum
+{
+ RGB,
+ GRAY,
+ INDEXED
+} GimpImageBaseType;
+
+//! Type of individual layers in an XCF file.
+
+typedef enum
+{
+ RGB_GIMAGE,
+ RGBA_GIMAGE,
+ GRAY_GIMAGE,
+ GRAYA_GIMAGE,
+ INDEXED_GIMAGE,
+ INDEXEDA_GIMAGE
+} GimpImageType;
+
+//! Effect to apply when layers are merged together.
+
+typedef enum
+{
+ NORMAL_MODE,
+ DISSOLVE_MODE,
+ BEHIND_MODE,
+ MULTIPLY_MODE,
+ SCREEN_MODE,
+ OVERLAY_MODE,
+ DIFFERENCE_MODE,
+ ADDITION_MODE,
+ SUBTRACT_MODE,
+ DARKEN_ONLY_MODE,
+ LIGHTEN_ONLY_MODE,
+ HUE_MODE,
+ SATURATION_MODE,
+ COLOR_MODE,
+ VALUE_MODE,
+ DIVIDE_MODE,
+ ERASE_MODE,
+ REPLACE_MODE,
+ ANTI_ERASE_MODE
+} LayerModeEffects;
+
+// From GIMP "xcf.c" v1.2
+
+//! Properties which can be stored in an XCF file.
+
+typedef enum
+{
+ PROP_END = 0,
+ PROP_COLORMAP = 1,
+ PROP_ACTIVE_LAYER = 2,
+ PROP_ACTIVE_CHANNEL = 3,
+ PROP_SELECTION = 4,
+ PROP_FLOATING_SELECTION = 5,
+ PROP_OPACITY = 6,
+ PROP_MODE = 7,
+ PROP_VISIBLE = 8,
+ PROP_LINKED = 9,
+ PROP_PRESERVE_TRANSPARENCY = 10,
+ PROP_APPLY_MASK = 11,
+ PROP_EDIT_MASK = 12,
+ PROP_SHOW_MASK = 13,
+ PROP_SHOW_MASKED = 14,
+ PROP_OFFSETS = 15,
+ PROP_COLOR = 16,
+ PROP_COMPRESSION = 17,
+ PROP_GUIDES = 18,
+ PROP_RESOLUTION = 19,
+ PROP_TATTOO = 20,
+ PROP_PARASITES = 21,
+ PROP_UNIT = 22,
+ PROP_PATHS = 23,
+ PROP_USER_UNIT = 24
+} PropType;
+
+// From GIMP "xcf.c" v1.2
+
+//! Compression type used in layer tiles.
+
+typedef enum
+{
+ COMPRESS_NONE = 0,
+ COMPRESS_RLE = 1,
+ COMPRESS_ZLIB = 2,
+ COMPRESS_FRACTAL = 3 /* Unused. */
+} CompressionType;
+
+
diff --git a/src/gvcore/imageframe.h b/src/gvcore/imageframe.h
new file mode 100644
index 0000000..3825f06
--- /dev/null
+++ b/src/gvcore/imageframe.h
@@ -0,0 +1,42 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2000-2004 Aurélien Gâteau
+
+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.
+
+*/
+#ifndef IMAGEFRAME_H
+#define IMAGEFRAME_H
+
+// Qt
+#include <qimage.h>
+#include <qvaluevector.h>
+namespace Gwenview {
+
+// Local
+
+struct ImageFrame {
+ ImageFrame( const QImage& i, int d ) : image( i ), delay( d ) {};
+ ImageFrame() : delay( 0 ) {} // stupid Qt containers
+ QImage image;
+ int delay; // how long this frame should be shown in the animation
+};
+
+typedef QValueVector< ImageFrame > ImageFrames;
+
+} // namespace
+#endif /* IMAGEFRAME_H */
+
diff --git a/src/gvcore/imageloader.cpp b/src/gvcore/imageloader.cpp
new file mode 100644
index 0000000..e1be4e8
--- /dev/null
+++ b/src/gvcore/imageloader.cpp
@@ -0,0 +1,917 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2000-2004 Aurélien Gâteau
+
+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 "imageloader.h"
+
+#include <assert.h>
+
+// Qt
+#include <qtimer.h>
+#include <qwmatrix.h>
+
+// KDE
+#include <kapplication.h>
+#include <kimageio.h>
+#include <kmimetype.h>
+
+// Local
+#include "cache.h"
+#include "miscconfig.h"
+#include "imageutils/imageutils.h"
+#include "imageutils/jpegcontent.h"
+
+#include "imageloader.moc"
+namespace Gwenview {
+
+const unsigned int DECODE_CHUNK_SIZE=4096;
+
+/** Interval between image updates, in milli seconds */
+const int IMAGE_UPDATE_INTERVAL=100;
+
+#undef ENABLE_LOG
+#undef LOG
+#undef LOG2
+
+#define ENABLE_LOG 0
+
+#if ENABLE_LOG >= 1
+#define LOG(x) kdDebug() << k_funcinfo << x << endl
+#else
+#define LOG(x) ;
+#endif
+
+#if ENABLE_LOG >= 2
+#define LOG2(x) kdDebug() << k_funcinfo << x << endl
+#else
+#define LOG2(x) ;
+#endif
+
+static QMap< KURL, ImageLoader* > sLoaders;
+
+//---------------------------------------------------------------------
+//
+// CancellableBuffer
+// This class acts like QBuffer, but will simulates a truncated file if the
+// TSThread which was passed to its constructor has been asked for cancellation
+//
+//---------------------------------------------------------------------
+class CancellableBuffer : public QBuffer {
+public:
+ CancellableBuffer(QByteArray buffer, TSThread* thread)
+ : QBuffer(buffer), mThread(thread) {}
+
+ bool atEnd() const {
+ if (mThread->testCancel()) {
+ LOG("cancel detected");
+ return true;
+ }
+ return QBuffer::atEnd();
+ }
+
+ Q_LONG readBlock(char * data, Q_ULONG maxlen) {
+ if (mThread->testCancel()) {
+ LOG("cancel detected");
+ return 0;
+ }
+ return QBuffer::readBlock(data, maxlen);
+ }
+
+ Q_LONG readLine(char * data, Q_ULONG maxlen) {
+ if (mThread->testCancel()) {
+ LOG("cancel detected");
+ return 0;
+ }
+ return QBuffer::readLine(data, maxlen);
+ }
+
+ QByteArray readAll() {
+ if (mThread->testCancel()) {
+ LOG("cancel detected");
+ return QByteArray();
+ }
+ return QBuffer::readAll();
+ }
+
+ int getch() {
+ if (mThread->testCancel()) {
+ LOG("cancel detected");
+ setStatus(IO_ReadError);
+ return -1;
+ }
+ return QBuffer::getch();
+ }
+
+private:
+ TSThread* mThread;
+};
+
+
+//---------------------------------------------------------------------
+//
+// DecoderThread
+//
+//---------------------------------------------------------------------
+void DecoderThread::run() {
+ QMutexLocker locker(&mMutex);
+ LOG("");
+
+ // This block makes sure imageIO won't access the image after the signal
+ // has been posted
+ {
+ QImageIO imageIO;
+
+ CancellableBuffer buffer(mRawData, this);
+ buffer.open(IO_ReadOnly);
+ imageIO.setIODevice(&buffer);
+ bool ok=imageIO.read();
+ if (testCancel()) {
+ LOG("cancelled");
+ return;
+ }
+
+ if (!ok) {
+ LOG("failed");
+ postSignal( this, SIGNAL(failed()) );
+ return;
+ }
+
+ LOG("succeeded");
+ mImage=imageIO.image();
+ }
+
+ LOG("succeeded, emitting signal");
+ postSignal( this, SIGNAL(succeeded()) );
+}
+
+
+void DecoderThread::setRawData(const QByteArray& data) {
+ QMutexLocker locker(&mMutex);
+ mRawData=data.copy();
+}
+
+
+QImage DecoderThread::popLoadedImage() {
+ QMutexLocker locker(&mMutex);
+ QImage img=mImage;
+ mImage=QImage();
+ return img;
+}
+
+
+
+//---------------------------------------------------------------------
+//
+// ImageLoaderPrivate
+//
+//---------------------------------------------------------------------
+struct OwnerData {
+ const QObject* owner;
+ BusyLevel priority;
+};
+
+enum GetState {
+ GET_PENDING_STAT, // Stat has not been started
+ GET_STATING, // Stat has been started
+ GET_PENDING_GET, // Stat is done, get has not been started
+ GET_GETTING, // Get has been started
+ GET_DONE, // All data has been received
+};
+
+
+enum DecodeState {
+ DECODE_WAITING, // No data to decode yet
+ DECODE_PENDING_THREADED_DECODING, // Waiting for all data to start threaded decoding
+ DECODE_THREADED_DECODING, // Threaded decoder is running
+ DECODE_INCREMENTAL_DECODING, // Incremental decoder is running
+ DECODE_INCREMENTAL_DECODING_DONE, // Incremental decoder is done
+ DECODE_CACHED, // Image has been obtained from cache, but raw data was missing. Wait for get to finish.
+ DECODE_DONE, // All done
+};
+
+class ImageLoaderPrivate {
+public:
+ ImageLoaderPrivate(ImageLoader* impl)
+ : mDecodedSize(0)
+ , mGetState(GET_PENDING_STAT)
+ , mDecodeState(DECODE_WAITING)
+ , mDecoder(impl)
+ , mSuspended(false)
+ , mNextFrameDelay(0)
+ , mWasFrameData(false)
+ , mOrientation(ImageUtils::NOT_AVAILABLE)
+ , mURLKind(MimeTypeUtils::KIND_UNKNOWN)
+ {}
+
+ // How many of the raw data we have already decoded
+ unsigned int mDecodedSize;
+
+ GetState mGetState;
+ DecodeState mDecodeState;
+
+ KURL mURL;
+
+ // The file timestamp
+ QDateTime mTimestamp;
+
+ // The raw data we get
+ QByteArray mRawData;
+
+ // The async decoder and it's waking timer
+ QImageDecoder mDecoder;
+ QTimer mDecoderTimer;
+
+ // The decoder thread
+ DecoderThread mDecoderThread;
+
+ // A rect of recently loaded pixels that the rest of the application has
+ // not been notified about with the imageChanged() signal
+ QRect mLoadChangedRect;
+
+ // The time since we last emitted the imageChanged() signal
+ QTime mTimeSinceLastUpdate;
+
+ // Whether the loading should be suspended
+ bool mSuspended;
+
+ // Delay used for next frame after it's finished decoding.
+ int mNextFrameDelay;
+
+ bool mWasFrameData;
+
+ QImage mProcessedImage; // image frame currently being decoded
+
+ QRegion mLoadedRegion; // loaded parts of mProcessedImage
+
+ ImageFrames mFrames;
+
+ QCString mImageFormat;
+
+ ImageUtils::Orientation mOrientation;
+
+ QString mMimeType;
+ MimeTypeUtils::Kind mURLKind;
+
+ QValueVector< OwnerData > mOwners; // loaders may be shared
+
+
+ void determineImageFormat() {
+ Q_ASSERT(mRawData.size()>0);
+ QBuffer buffer(mRawData);
+ buffer.open(IO_ReadOnly);
+ mImageFormat = QImageIO::imageFormat(&buffer);
+ }
+};
+
+
+//---------------------------------------------------------------------
+//
+// ImageLoader
+//
+//---------------------------------------------------------------------
+ImageLoader::ImageLoader() {
+ LOG("");
+ d = new ImageLoaderPrivate(this);
+ connect( BusyLevelManager::instance(), SIGNAL( busyLevelChanged(BusyLevel)),
+ this, SLOT( slotBusyLevelChanged(BusyLevel)));
+}
+
+
+ImageLoader::~ImageLoader() {
+ LOG("");
+ if (d->mDecoderThread.running()) {
+ d->mDecoderThread.cancel();
+ d->mDecoderThread.wait();
+ }
+ delete d;
+}
+
+
+void ImageLoader::setURL( const KURL& url ) {
+ assert( d->mURL.isEmpty());
+ d->mURL = url;
+}
+
+void ImageLoader::startLoading() {
+ d->mTimestamp = Cache::instance()->timestamp( d->mURL );
+ slotBusyLevelChanged( BusyLevelManager::instance()->busyLevel());
+
+ connect(&d->mDecoderTimer, SIGNAL(timeout()), this, SLOT(decodeChunk()) );
+
+ connect(&d->mDecoderThread, SIGNAL(succeeded()),
+ this, SLOT(slotDecoderThreadSucceeded()) );
+ connect(&d->mDecoderThread, SIGNAL(failed()),
+ this, SLOT(slotDecoderThreadFailed()) );
+
+ checkPendingStat();
+}
+
+void ImageLoader::checkPendingStat() {
+ if( d->mSuspended || d->mGetState != GET_PENDING_STAT ) return;
+
+ KIO::Job* job=KIO::stat( d->mURL, false );
+ job->setWindow(KApplication::kApplication()->mainWidget());
+ connect(job, SIGNAL(result(KIO::Job*)),
+ this, SLOT(slotStatResult(KIO::Job*)) );
+ d->mGetState = GET_STATING;
+}
+
+void ImageLoader::slotStatResult(KIO::Job* job) {
+ LOG("error code: " << job->error());
+
+ // Get modification time of the original file
+ KIO::UDSEntry entry = static_cast<KIO::StatJob*>(job)->statResult();
+ KIO::UDSEntry::ConstIterator it= entry.begin();
+ QDateTime urlTimestamp;
+ for (; it!=entry.end(); it++) {
+ if ((*it).m_uds == KIO::UDS_MODIFICATION_TIME) {
+ urlTimestamp.setTime_t( (*it).m_long );
+ break;
+ }
+ }
+
+ if( d->mTimestamp.isValid() && urlTimestamp == d->mTimestamp ) {
+ // We have the image in cache
+ LOG(d->mURL << ", We have the image in cache");
+ d->mRawData = Cache::instance()->file( d->mURL );
+ Cache::instance()->getFrames(d->mURL, &d->mFrames, &d->mImageFormat);
+
+ if( !d->mFrames.isEmpty()) {
+ LOG("The image in cache can be used");
+ d->mProcessedImage = d->mFrames[0].image;
+ emit sizeLoaded(d->mProcessedImage.width(), d->mProcessedImage.height());
+ emit imageChanged(d->mProcessedImage.rect());
+
+ if (d->mRawData.isNull() && d->mImageFormat=="JPEG") {
+ // Raw data is needed for JPEG, wait for it to be downloaded
+ LOG("Wait for raw data to be downloaded");
+ d->mDecodeState = DECODE_CACHED;
+ } else {
+ // We don't care about raw data
+ finish(true);
+ return;
+ }
+ } else {
+ // Image in cache is broken
+ LOG("The image in cache cannot be used");
+ if( !d->mRawData.isNull()) {
+ LOG("Using cached raw data");
+ // Raw data is ok, skip get step and decode it
+ d->mGetState = GET_DONE;
+ d->mTimeSinceLastUpdate.start();
+ d->mDecoderTimer.start(0, false);
+ return;
+ }
+ }
+ }
+
+ d->mTimestamp = urlTimestamp;
+ d->mRawData.resize(0);
+ d->mGetState = GET_PENDING_GET;
+ checkPendingGet();
+}
+
+void ImageLoader::checkPendingGet() {
+ if( d->mSuspended || d->mGetState != GET_PENDING_GET ) return;
+
+ // Start loading the image
+ KIO::Job* getJob=KIO::get( d->mURL, false, false);
+ getJob->setWindow(KApplication::kApplication()->mainWidget());
+
+ connect(getJob, SIGNAL(data(KIO::Job*, const QByteArray&)),
+ this, SLOT(slotDataReceived(KIO::Job*, const QByteArray&)) );
+
+ connect(getJob, SIGNAL(result(KIO::Job*)),
+ this, SLOT(slotGetResult(KIO::Job*)) );
+
+ d->mTimeSinceLastUpdate.start();
+ d->mGetState = GET_GETTING;
+}
+
+
+void ImageLoader::slotGetResult(KIO::Job* job) {
+ LOG("error code: " << job->error());
+ if( job->error() != 0 ) {
+ // failed
+ finish( false );
+ return;
+ }
+
+ d->mGetState = GET_DONE;
+
+ // Store raw data in cache
+ // Note: Cache will give high cost to non-JPEG raw data.
+ Cache::instance()->addFile( d->mURL, d->mRawData, d->mTimestamp );
+
+
+ switch (d->mDecodeState) {
+ case DECODE_CACHED:
+ // image was in cache, but not raw data
+ finish( true );
+ break;
+
+ case DECODE_PENDING_THREADED_DECODING:
+ // Start the decoder thread if needed
+ startThread();
+ break;
+
+ default:
+ // Finish decoding if needed
+ if (!d->mDecoderTimer.isActive()) d->mDecoderTimer.start(0);
+ }
+}
+
+// There is no way in KImageIO to get the mimeType from the image format.
+// This function assumes KImageIO::types and KImageIO::mimeTypes return items
+// in the same order (which they do, according to the source code).
+static QString mimeTypeFromFormat(const char* format) {
+ QStringList formats = KImageIO::types(KImageIO::Reading);
+ QStringList mimeTypes = KImageIO::mimeTypes(KImageIO::Reading);
+ int pos = formats.findIndex(QString::fromAscii(format));
+ Q_ASSERT(pos != -1);
+ return mimeTypes[pos];
+}
+
+void ImageLoader::slotDataReceived(KIO::Job* job, const QByteArray& chunk) {
+ LOG2("size: " << chunk.size());
+ if (chunk.size()<=0) return;
+
+ int oldSize=d->mRawData.size();
+ d->mRawData.resize(oldSize + chunk.size());
+ memcpy(d->mRawData.data()+oldSize, chunk.data(), chunk.size() );
+
+ if (oldSize==0) {
+ // Try to determine the data type
+ QBuffer buffer(d->mRawData);
+ buffer.open(IO_ReadOnly);
+ const char* format = QImageIO::imageFormat(&buffer);
+ if (format) {
+ // This is a raster image, get the mime type now
+ d->mURLKind = MimeTypeUtils::KIND_RASTER_IMAGE;
+ d->mMimeType = mimeTypeFromFormat(format);
+ } else {
+ KMimeType::Ptr ptr = KMimeType::findByContent(d->mRawData);
+ d->mMimeType = ptr->name();
+ d->mURLKind = MimeTypeUtils::mimeTypeKind(d->mMimeType);
+ }
+ if (d->mURLKind!=MimeTypeUtils::KIND_RASTER_IMAGE) {
+ Q_ASSERT(!d->mDecoderTimer.isActive());
+ job->kill(true /* quietly */);
+ LOG("emit urlKindDetermined(!raster)");
+ emit urlKindDetermined();
+ return;
+ }
+ LOG("emit urlKindDetermined(raster)");
+ emit urlKindDetermined();
+ }
+
+ // Decode the received data
+ if( !d->mDecoderTimer.isActive() &&
+ (d->mDecodeState==DECODE_WAITING || d->mDecodeState==DECODE_INCREMENTAL_DECODING)
+ ) {
+ d->mDecoderTimer.start(0);
+ }
+}
+
+
+void ImageLoader::decodeChunk() {
+ if( d->mSuspended ) {
+ LOG("suspended");
+ d->mDecoderTimer.stop();
+ return;
+ }
+
+ int chunkSize = QMIN(DECODE_CHUNK_SIZE, int(d->mRawData.size())-d->mDecodedSize);
+ int decodedSize = 0;
+ if (chunkSize>0) {
+ decodedSize = d->mDecoder.decode(
+ (const uchar*)(d->mRawData.data()+d->mDecodedSize),
+ chunkSize);
+
+ if (decodedSize<0) {
+ // We can't use incremental decoding, switch to threaded decoding
+ d->mDecoderTimer.stop();
+ if (d->mGetState == GET_DONE) {
+ startThread();
+ } else {
+ d->mDecodeState = DECODE_PENDING_THREADED_DECODING;
+ }
+ return;
+ }
+
+ // We just decoded some data
+ if (d->mDecodeState == DECODE_WAITING) {
+ d->mDecodeState = DECODE_INCREMENTAL_DECODING;
+ }
+ d->mDecodedSize+=decodedSize;
+ }
+
+ if (decodedSize == 0) {
+ // We decoded as much as possible from the buffer, wait to receive
+ // more data before coming again in decodeChunk
+ d->mDecoderTimer.stop();
+
+ if (d->mGetState == GET_DONE) {
+ // All available data has been received.
+ if (d->mDecodeState == DECODE_INCREMENTAL_DECODING) {
+ // Decoder is not finished, the image must be truncated,
+ // let's simulate its end
+ kdWarning() << "ImageLoader::decodeChunk(): image '" << d->mURL.prettyURL() << "' is truncated.\n";
+
+ if (d->mProcessedImage.isNull()) {
+ d->mProcessedImage = d->mDecoder.image();
+ }
+ emit imageChanged(d->mProcessedImage.rect());
+ end();
+ }
+ }
+ }
+}
+
+
+void ImageLoader::startThread() {
+ LOG("starting decoder thread");
+ d->mDecodeState = DECODE_THREADED_DECODING;
+ d->mDecoderThread.setRawData(d->mRawData);
+ d->mDecoderThread.start();
+}
+
+
+void ImageLoader::slotDecoderThreadFailed() {
+ LOG("");
+ // Image can't be loaded
+ finish( false );
+}
+
+
+void ImageLoader::slotDecoderThreadSucceeded() {
+ LOG("");
+ d->mProcessedImage = d->mDecoderThread.popLoadedImage();
+ d->mFrames.append( ImageFrame( d->mProcessedImage, 0 ));
+ emit sizeLoaded(d->mProcessedImage.width(), d->mProcessedImage.height());
+ emit imageChanged(d->mProcessedImage.rect());
+ finish(true);
+}
+
+
+/**
+ * Cache image and emit imageLoaded
+ */
+void ImageLoader::finish( bool ok ) {
+ LOG("");
+
+ d->mDecodeState = DECODE_DONE;
+
+ if (!ok) {
+ d->mFrames.clear();
+ d->mRawData = QByteArray();
+ d->mImageFormat = QCString();
+ d->mProcessedImage = QImage();
+ emit imageLoaded( false );
+ return;
+ }
+
+ if (d->mImageFormat.isEmpty()) {
+ d->determineImageFormat();
+ }
+ Q_ASSERT(d->mFrames.count() > 0);
+ Cache::instance()->addImage( d->mURL, d->mFrames, d->mImageFormat, d->mTimestamp );
+ emit imageLoaded( true );
+}
+
+
+BusyLevel ImageLoader::priority() const {
+ BusyLevel mylevel = BUSY_NONE;
+ for( QValueVector< OwnerData >::ConstIterator it = d->mOwners.begin();
+ it != d->mOwners.end();
+ ++it ) {
+ mylevel = QMAX( mylevel, (*it).priority );
+ }
+ return mylevel;
+}
+
+void ImageLoader::slotBusyLevelChanged( BusyLevel level ) {
+ // this loader may be needed for normal loading (BUSY_LOADING), or
+ // only for prefetching
+ BusyLevel mylevel = priority();
+ if( level > mylevel ) {
+ suspendLoading();
+ } else {
+ resumeLoading();
+ }
+}
+
+void ImageLoader::suspendLoading() {
+ d->mDecoderTimer.stop();
+ d->mSuspended = true;
+}
+
+void ImageLoader::resumeLoading() {
+ d->mSuspended = false;
+ d->mDecoderTimer.start(0, false);
+ checkPendingGet();
+ checkPendingStat();
+}
+
+
+//---------------------------------------------------------------------
+//
+// QImageConsumer
+//
+//---------------------------------------------------------------------
+void ImageLoader::end() {
+ LOG("");
+
+ // Notify about the last loaded rectangle
+ LOG("mLoadChangedRect " << d->mLoadChangedRect);
+ if (!d->mLoadChangedRect.isEmpty()) {
+ emit imageChanged( d->mLoadChangedRect );
+ }
+
+ d->mDecoderTimer.stop();
+ d->mDecodeState = DECODE_INCREMENTAL_DECODING_DONE;
+
+ // We are done
+ if( d->mFrames.count() == 0 ) {
+ d->mFrames.append( ImageFrame( d->mProcessedImage, 0 ));
+ }
+ // The image has been totally decoded, we delay the call to finish because
+ // when we return from this function we will be in decodeChunk(), after the
+ // call to decode(), so we don't want to switch to a new impl yet, since
+ // this means deleting "this".
+ QTimer::singleShot(0, this, SLOT(callFinish()) );
+}
+
+
+void ImageLoader::callFinish() {
+ finish(true);
+}
+
+
+void ImageLoader::changed(const QRect& constRect) {
+ LOG2("");
+ QRect rect = constRect;
+
+ if (d->mLoadedRegion.isEmpty()) {
+ // This is the first time we get called. Init mProcessedImage and emit
+ // sizeLoaded.
+ LOG("mLoadedRegion is empty");
+
+ // By default, mProcessedImage should use the image from mDecoder
+ d->mProcessedImage = d->mDecoder.image();
+
+ if (d->mImageFormat.isEmpty()) {
+ d->determineImageFormat();
+ }
+ Q_ASSERT(!d->mImageFormat.isEmpty());
+ if (d->mImageFormat == "JPEG") {
+ // This is a JPEG, extract orientation and adjust mProcessedImage
+ // if necessary according to misc options
+ ImageUtils::JPEGContent content;
+
+ if (content.loadFromData(d->mRawData)) {
+ d->mOrientation = content.orientation();
+ if (MiscConfig::autoRotateImages() &&
+ d->mOrientation != ImageUtils::NOT_AVAILABLE && d->mOrientation != ImageUtils::NORMAL) {
+ QSize size = content.size();
+ d->mProcessedImage = QImage(size, d->mDecoder.image().depth());
+ }
+ d->mProcessedImage.setDotsPerMeterX(content.dotsPerMeterX());
+ d->mProcessedImage.setDotsPerMeterY(content.dotsPerMeterY());
+ } else {
+ kdWarning() << "ImageLoader::changed(): JPEGContent could not load '" << d->mURL.prettyURL() << "'\n";
+ }
+ }
+
+ LOG("emit sizeLoaded " << d->mProcessedImage.size());
+ emit sizeLoaded(d->mProcessedImage.width(), d->mProcessedImage.height());
+ }
+
+ // Apply orientation if necessary and if wanted by user settings (misc options)
+ if (MiscConfig::autoRotateImages() &&
+ d->mOrientation != ImageUtils::NOT_AVAILABLE && d->mOrientation != ImageUtils::NORMAL) {
+ // We can only rotate whole images, so copy the loaded rect in a temp
+ // image, rotate the temp image and copy it to mProcessedImage
+
+ // Copy loaded rect
+ QImage temp(rect.size(), d->mProcessedImage.depth());
+ bitBlt(&temp, 0, 0,
+ &d->mDecoder.image(), rect.left(), rect.top(), rect.width(), rect.height());
+
+ // Rotate
+ temp = ImageUtils::transform(temp, d->mOrientation);
+
+ // Compute destination rect
+ QWMatrix matrix = ImageUtils::transformMatrix(d->mOrientation);
+
+ QRect imageRect = d->mDecoder.image().rect();
+ imageRect = matrix.mapRect(imageRect);
+
+ rect = matrix.mapRect(rect);
+ rect.moveBy(-imageRect.left(), -imageRect.top());
+
+ // copy temp to mProcessedImage
+ bitBlt(&d->mProcessedImage, rect.left(), rect.top(),
+ &temp, 0, 0, temp.width(), temp.height());
+ }
+
+ // Update state tracking vars
+ d->mWasFrameData = true;
+ d->mLoadChangedRect |= rect;
+ d->mLoadedRegion |= rect;
+ if( d->mTimeSinceLastUpdate.elapsed() > IMAGE_UPDATE_INTERVAL ) {
+ LOG("emitting imageChanged " << d->mLoadChangedRect);
+ d->mTimeSinceLastUpdate.start();
+ emit imageChanged(d->mLoadChangedRect);
+ d->mLoadChangedRect = QRect();
+ }
+}
+
+void ImageLoader::frameDone() {
+ frameDone( QPoint( 0, 0 ), d->mDecoder.image().rect());
+}
+
+void ImageLoader::frameDone(const QPoint& offset, const QRect& rect) {
+ LOG("");
+ // Another case where the image loading in Qt's is a bit borken.
+ // It's possible to get several notes about a frame being done for one frame (with MNG).
+ if( !d->mWasFrameData ) {
+ // To make it even more fun, with MNG the sequence is actually
+ // setFramePeriod( 0 )
+ // frameDone()
+ // setFramePeriod( delay )
+ // frameDone()
+ // Therefore ignore the second frameDone(), but fix the delay that should be
+ // after the frame.
+ if( d->mFrames.count() > 0 ) {
+ d->mFrames.last().delay = d->mNextFrameDelay;
+ d->mNextFrameDelay = 0;
+ }
+ return;
+ }
+ d->mWasFrameData = false;
+ if( !d->mLoadChangedRect.isEmpty()) {
+ emit imageChanged(d->mLoadChangedRect);
+ d->mLoadChangedRect = QRect();
+ d->mTimeSinceLastUpdate.start();
+ }
+ d->mLoadedRegion = QRegion();
+
+ QImage image;
+ if (d->mProcessedImage.isNull()) {
+ image = d->mDecoder.image().copy();
+ } else {
+ image = d->mProcessedImage.copy();
+ }
+
+ if( offset != QPoint( 0, 0 ) || rect != image.rect()) {
+ // Blit last frame below 'image'
+ if( !d->mFrames.isEmpty()) {
+ QImage im = d->mFrames.last().image.copy();
+ bitBlt( &im, offset.x(), offset.y(), &image, rect.x(), rect.y(), rect.width(), rect.height());
+ image = im;
+ }
+ }
+ d->mFrames.append( ImageFrame( image, d->mNextFrameDelay ));
+ d->mNextFrameDelay = 0;
+}
+
+void ImageLoader::setLooping(int) {
+}
+
+void ImageLoader::setFramePeriod(int milliseconds) {
+ if( milliseconds < 0 ) milliseconds = 0; // -1 means showing immediately
+ if( d->mNextFrameDelay == 0 || milliseconds != 0 ) {
+ d->mNextFrameDelay = milliseconds;
+ }
+}
+
+void ImageLoader::setSize(int, int) {
+ // Do nothing, size is handled when ::changed() is called for the first
+ // time
+}
+
+
+QImage ImageLoader::processedImage() const {
+ return d->mProcessedImage;
+}
+
+
+ImageFrames ImageLoader::frames() const {
+ return d->mFrames;
+}
+
+
+QCString ImageLoader::imageFormat() const {
+ return d->mImageFormat;
+}
+
+
+QByteArray ImageLoader::rawData() const {
+ return d->mRawData;
+}
+
+
+QString ImageLoader::mimeType() const {
+ return d->mMimeType;
+}
+
+
+MimeTypeUtils::Kind ImageLoader::urlKind() const {
+ return d->mURLKind;
+}
+
+
+KURL ImageLoader::url() const {
+ return d->mURL;
+}
+
+
+QRegion ImageLoader::loadedRegion() const {
+ return d->mLoadedRegion;
+}
+
+
+bool ImageLoader::completed() const {
+ return d->mDecodeState == DECODE_DONE;
+}
+
+
+void ImageLoader::ref( const QObject* owner, BusyLevel priority ) {
+ OwnerData data;
+ data.owner = owner;
+ data.priority = priority;
+ d->mOwners.append( data );
+ connect( owner, SIGNAL( destroyed()), SLOT( ownerDestroyed()));
+}
+
+void ImageLoader::deref( const QObject* owner ) {
+ for( QValueVector< OwnerData >::Iterator it = d->mOwners.begin();
+ it != d->mOwners.end();
+ ++it ) {
+ if( (*it).owner == owner ) {
+ d->mOwners.erase( it );
+ if( d->mOwners.count() == 0 ) {
+ sLoaders.remove( d->mURL );
+ delete this;
+ }
+ return;
+ }
+ }
+ assert( false );
+}
+
+void ImageLoader::release( const QObject* owner ) {
+ disconnect( owner );
+ deref( owner );
+}
+
+void ImageLoader::ownerDestroyed() {
+ deref( sender());
+}
+
+//---------------------------------------------------------------------
+//
+// Managing loaders
+//
+//---------------------------------------------------------------------
+
+ImageLoader* ImageLoader::loader( const KURL& url, const QObject* owner, BusyLevel priority ) {
+ if( sLoaders.contains( url )) {
+ ImageLoader* loader = sLoaders[ url ];
+ loader->ref( owner, priority );
+ // resume if this owner has high priority
+ loader->slotBusyLevelChanged( BusyLevelManager::instance()->busyLevel());
+ return loader;
+ }
+ ImageLoader* loader = new ImageLoader;
+ loader->ref( owner, priority );
+ sLoaders[ url ] = loader;
+ loader->setURL( url );
+ // Code using a loader first calls loader() to get ImageLoader* and only after that it can
+ // connect to its signals etc., so don't start loading immediately.
+ // This also helps with preloading jobs, since BUSY_LOADING busy level is not entered immediately
+ // when a new picture is selected, so preloading jobs without this delay could start working
+ // immediately.
+ QTimer::singleShot( priority >= BUSY_LOADING ? 0 : 10, loader, SLOT( startLoading()));
+ return loader;
+}
+
+} // namespace
diff --git a/src/gvcore/imageloader.h b/src/gvcore/imageloader.h
new file mode 100644
index 0000000..c1cc1ec
--- /dev/null
+++ b/src/gvcore/imageloader.h
@@ -0,0 +1,123 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2000-2004 Aurélien Gâteau
+
+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.
+
+*/
+#ifndef IMAGELOADER_H
+#define IMAGELOADER_H
+
+// Qt
+#include <qasyncimageio.h>
+#include <qbuffer.h>
+#include <qcstring.h>
+
+// KDE
+#include <kio/job.h>
+
+// Local
+#include "tsthread/tsthread.h"
+#include "imageframe.h"
+#include "busylevelmanager.h"
+#include "mimetypeutils.h"
+
+#include "libgwenview_export.h"
+namespace Gwenview {
+class DecoderThread : public TSThread {
+Q_OBJECT
+public:
+ void setRawData(const QByteArray&);
+ QImage popLoadedImage();
+
+signals:
+ void failed();
+ void succeeded();
+
+protected:
+ void run();
+
+private:
+ QMutex mMutex;
+ QByteArray mRawData;
+ QImage mImage;
+};
+
+class ImageLoaderPrivate;
+
+class LIBGWENVIEW_EXPORT ImageLoader : public QObject, public QImageConsumer {
+Q_OBJECT
+public:
+ static ImageLoader* loader( const KURL& url, const QObject* owner, BusyLevel priority ); // use this instead of ctor
+ void release( const QObject* owner ); // use this instead of dtor
+
+ QImage processedImage() const;
+ ImageFrames frames() const;
+ QCString imageFormat() const;
+ QByteArray rawData() const;
+ QString mimeType() const;
+ MimeTypeUtils::Kind urlKind() const;
+ KURL url() const;
+ QRegion loadedRegion() const; // valid parts of processedImage()
+ bool completed() const;
+
+signals:
+ void urlKindDetermined();
+ void sizeLoaded(int, int);
+ void imageChanged(const QRect&);
+ void imageLoaded( bool ok );
+
+private slots:
+ void slotStatResult(KIO::Job*);
+ void slotDataReceived(KIO::Job*, const QByteArray& chunk);
+ void slotGetResult(KIO::Job*);
+ void decodeChunk();
+ void slotDecoderThreadFailed();
+ void slotDecoderThreadSucceeded();
+ void slotBusyLevelChanged( BusyLevel );
+ void ownerDestroyed();
+ void startLoading();
+ void callFinish();
+
+private:
+ ImageLoader();
+ ~ImageLoader();
+ void ref( const QObject* owner, BusyLevel priority );
+ void deref( const QObject* owner );
+ void suspendLoading();
+ void resumeLoading();
+ void finish( bool ok );
+ void startThread();
+ void setURL( const KURL& url );
+ void checkPendingStat();
+ void checkPendingGet();
+ BusyLevel priority() const;
+
+ // QImageConsumer methods
+ void end();
+ void changed(const QRect&);
+ void frameDone();
+ void frameDone(const QPoint& offset, const QRect& rect);
+ void setLooping(int);
+ void setFramePeriod(int milliseconds);
+ void setSize(int, int);
+
+ ImageLoaderPrivate* d;
+};
+
+} // namespace
+#endif /* IMAGELOADER_H */
+
diff --git a/src/gvcore/imagesavedialog.cpp b/src/gvcore/imagesavedialog.cpp
new file mode 100644
index 0000000..f7405af
--- /dev/null
+++ b/src/gvcore/imagesavedialog.cpp
@@ -0,0 +1,131 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2000-2004 Aurélien Gâteau
+
+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.
+
+*/
+// Qt includes
+#include <qtimer.h>
+
+// KDE includes
+#include <kdebug.h>
+#include <kdeversion.h>
+#include <kfilefiltercombo.h>
+#include <kimageio.h>
+#include <klocale.h>
+#include <kurlcombobox.h>
+
+// Our includes
+#include "imagesavedialog.moc"
+namespace Gwenview {
+
+
+static int findFormatInFilterList(const QStringList& filters, const QString& format) {
+ int pos=0;
+ for(QStringList::const_iterator it=filters.begin(); it!=filters.end(); ++it,++pos) {
+ QStringList list=QStringList::split("|",*it);
+ if ( list[1].startsWith(format) ) return pos;
+ }
+ return -1;
+}
+
+
+ImageSaveDialog::ImageSaveDialog(KURL& url, const QCString& imageFormat, QWidget* parent)
+: KFileDialog(":ImageSaveDialog",QString::null,parent,"imagesavedialog",true)
+, mURL(url)
+, mImageFormat(imageFormat)
+{
+ setOperationMode(KFileDialog::Saving);
+
+ // FIXME: Ugly code to define the filter combo label.
+ KMimeType::List types;
+ setFilterMimeType(i18n("Format:"),types,KMimeType::mimeType(""));
+
+ QStringList filters;
+
+ // Create our filter list
+ QStringList mimeTypes=KImageIO::mimeTypes();
+ for(QStringList::const_iterator it=mimeTypes.begin(); it!=mimeTypes.end(); ++it) {
+ QString format=KImageIO::typeForMime(*it);
+
+ // Create the pattern part of the filter string
+ KMimeType::Ptr mt=KMimeType::mimeType(*it);
+ QStringList patterns;
+ for (QStringList::const_iterator patIt=mt->patterns().begin();patIt!=mt->patterns().end();++patIt) {
+ QString pattern=(*patIt).lower();
+ if (!patterns.contains(pattern)) patterns.append(pattern);
+ }
+ if (patterns.isEmpty()) {
+ patterns.append( QString("*.%1").arg(format.lower()) );
+ }
+ QString patternString=patterns.join(" ");
+
+ // Create the filter string
+ QString filter=patternString + "|"
+ + format + " - " + mt->comment()
+ + " (" + patternString + ")";
+
+ // Add it to our list
+ filters.append(filter);
+ }
+
+ qHeapSort(filters);
+ setFilter(filters.join("\n"));
+
+ // Select the default image format
+ int pos=findFormatInFilterList(filters,mImageFormat);
+ if (pos==-1) {
+ pos=findFormatInFilterList(filters,"PNG");
+ mImageFormat="PNG";
+ }
+
+ filterWidget->setCurrentItem(pos);
+
+ // Tweak the filter widget
+ filterWidget->setEditable(false);
+
+ connect(filterWidget,SIGNAL(activated(const QString&)),
+ this,SLOT(updateImageFormat(const QString&)) );
+
+ // Call slotFilterChanged() to get the list filtered by the filter we
+ // selected. If we don't use a single shot, it leads to a crash :-/
+ QTimer::singleShot(0,this,SLOT(slotFilterChanged()));
+}
+
+
+void ImageSaveDialog::accept() {
+ KFileDialog::accept();
+ mURL=selectedURL();
+}
+
+
+void ImageSaveDialog::updateImageFormat(const QString& text) {
+ QStringList list=QStringList::split(" ",text);
+ mImageFormat=list[0].local8Bit();
+
+ QString name=locationEdit->currentText();
+ QString suffix=KImageIO::suffix(mImageFormat);
+ int dotPos=name.findRev('.');
+ if (dotPos>-1) {
+ name=name.left(dotPos);
+ }
+ name.append('.').append(suffix);
+ locationEdit->setCurrentText(name);
+}
+
+
+} // namespace
diff --git a/src/gvcore/imagesavedialog.h b/src/gvcore/imagesavedialog.h
new file mode 100644
index 0000000..6aba763
--- /dev/null
+++ b/src/gvcore/imagesavedialog.h
@@ -0,0 +1,53 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2000-2004 Aurélien Gâteau
+
+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.
+
+*/
+#ifndef IMAGESAVEDIALOG_H
+#define IMAGESAVEDIALOG_H
+
+// KDE includes
+#include <kfiledialog.h>
+
+class QString;
+class QWidget;
+
+class KURL;
+
+namespace Gwenview {
+class ImageSaveDialog : public KFileDialog {
+Q_OBJECT
+public:
+ ImageSaveDialog(KURL& url,const QCString& imageFormat,QWidget* parent);
+ QCString imageFormat() const { return mImageFormat; }
+
+protected slots:
+ void accept();
+
+private slots:
+ void updateImageFormat(const QString&);
+
+private:
+ KURL& mURL;
+ QCString mImageFormat;
+ QMap<QString,QString> mImageFormats;
+};
+
+} // namespace
+#endif
+
diff --git a/src/gvcore/imageview.cpp b/src/gvcore/imageview.cpp
new file mode 100644
index 0000000..f22b171
--- /dev/null
+++ b/src/gvcore/imageview.cpp
@@ -0,0 +1,1469 @@
+// vim:set tabstop=4 shiftwidth=4 noexpandtab:
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2000-2004 Aur�ien G�eau
+
+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 "config.h"
+
+#include "imageview.moc"
+
+#include <assert.h>
+#include <math.h>
+
+// Qt
+#include <qcolor.h>
+#include <qcombobox.h>
+#include <qcursor.h>
+#include <qdatetime.h>
+#include <qevent.h>
+#include <qpainter.h>
+#include <qpixmap.h>
+#include <qlabel.h>
+#include <qtimer.h>
+#include <qvaluevector.h>
+
+// KDE
+#include <kaction.h>
+#include <kconfig.h>
+#include <kdebug.h>
+#include <kdeversion.h>
+#include <klocale.h>
+#include <kstandarddirs.h>
+#include <kstdaction.h>
+#include <kurldrag.h>
+#include <kapplication.h>
+
+// Local
+#include "document.h"
+#include "imageutils/imageutils.h"
+#include "bcgdialog.h"
+#include "busylevelmanager.h"
+#include "imageviewtools.h"
+#include "imageutils/croppedqimage.h"
+#include "imageviewconfig.h"
+
+namespace Gwenview {
+
+/*
+
+Coordinates:
+
+The image can be zoomed, can have a position offset, and additionally there is
+QScrollView's viewport. This means there are several coordinate systems.
+
+
+
+Let's start from simple things. Viewport ignored, zoom ignored:
+
+ A-----------------------------------
+ | |
+ | |
+ | B--------------------- |
+ | | | |
+ | | | |
+ | | | |
+ | | | |
+ | | | |
+ | | | |
+ | ---------------------C |
+ | |
+ | |
+ ------------------------------------
+
+
+The inner rectangle is the image, outer rectangle is the widget.
+A = [ 0, 0 ]
+B = [ mXOffset, mYOffset ]
+C = B + [ mDocument->width(), mDocument->height() ]
+
+
+
+The same, additionally the image is zoomed.
+
+A = [ 0, 0 ]
+B = [ mXOffset, mYOffset ]
+C = [ mZoom * mDocument->width(), mZoom * mDocument->height()) ]
+
+The groups of functions imageToWidget() and widgetToImage() do conversions
+between the image and widget coordinates, i.e. imageToWidget() accepts coordinates
+in the image (original,not zoomed,image's topleft corner is [0,0]) and returns
+coordinates in the picture above, widgetToImage() works the other way around.
+
+There's no bounds checking, so widgetToImage( A ) in the example above would
+return image coordinate with negative x,y.
+
+The widgetToImage() functions round the values (in order to have the conversion
+as approximate as possible). However when converting from widget to image and back
+this can result in the final rectangle being smaller than the original.
+The widgetToImageBounding() function converts from widget to image coordinates
+in a way which makes sure the reverse conversion will be at least as large
+as the original geometry.
+
+There are no conversion functions for only width/height, as their conversion
+depends on the position (because of the rounding etc.). For similar reasons
+conversions should not be done with the bottomright corner of a rectangle,
+but with the point next to it.
+
+
+
+For conversions from/to QScrollView's viewport, usually QScrollView methods should
+be used: contentsX(), contentsY(), contentsWidth(), contentsHeight(), visibleWidth(),
+visibleHeight(), contentsToViewport() and viewportToContents().
+
+*/
+
+const double MAX_ZOOM=16.0; // Same value as GIMP
+
+const int DEFAULT_MAX_REPAINT_SIZE = 10000;
+const int LIMIT_MAX_REPAINT_SIZE = 10000000;
+
+#ifndef HAVE_LROUND
+inline
+long int lround( double x ) {
+ return static_cast< long int >( x >= 0 ? x + 0.5 : x - 0.5 );
+}
+#endif
+
+
+struct ImageView::Private {
+ Document* mDocument;
+
+ Tools mTools;
+
+ ToolID mToolID;
+
+ // Offset to center images
+ int mXOffset, mYOffset;
+
+ // Zoom info
+ ZoomMode mZoomMode;
+ double mZoom;
+
+ // Gamma, brightness, contrast - multiplied by 100
+ int mGamma, mBrightness, mContrast;
+
+ // Our actions
+ QComboBox* mZoomCombo;
+ // We do not use KSelectAction because it's not possible to set the combo text
+ KWidgetAction* mZoomComboAction;
+ KToggleAction* mZoomToFit;
+ KToggleAction* mZoomToWidth;
+ KToggleAction* mZoomToHeight;
+ QValueVector<KToggleAction*> mZoomComboActions;
+ KAction* mZoomIn;
+ KAction* mZoomOut;
+ KAction* mResetZoom;
+ KToggleAction* mLockZoom;
+ KAction* mAdjustBCG;
+ KAction* mIncreaseGamma;
+ KAction* mDecreaseGamma;
+ KAction* mIncreaseBrightness;
+ KAction* mDecreaseBrightness;
+ KAction* mIncreaseContrast;
+ KAction* mDecreaseContrast;
+ KActionCollection* mActionCollection;
+ BCGDialog* mBCGDialog;
+
+ // Fullscreen stuff
+ bool mFullScreen;
+
+ // Object state info
+ bool mOperaLikePrevious; // Flag to avoid showing the popup menu on Opera like previous
+ double mZoomBeforeAuto;
+ int mXCenterBeforeAuto, mYCenterBeforeAuto;
+
+ QMap< long long, PendingPaint > mPendingPaints;
+ QRegion mPendingNormalRegion;
+ QRegion mPendingSmoothRegion;
+ int mPendingOperations;
+ QTimer mPendingPaintTimer;
+ bool mSmoothingSuspended;
+ QRegion mValidImageArea;
+
+ int imageToWidgetX( int x ) const {
+ if( mZoom == 1.0 ) return x + mXOffset;
+ return lround( x * mZoom ) + mXOffset;
+ }
+
+ int imageToWidgetY( int y ) const {
+ if( mZoom == 1.0 ) return y + mYOffset;
+ return lround( y * mZoom ) + mYOffset;
+ }
+
+ QPoint imageToWidget( const QPoint& p ) const {
+ return QPoint( imageToWidgetX( p.x()), imageToWidgetY( p.y()));
+ }
+
+ QRect imageToWidget( const QRect& r ) const {
+ return QRect( imageToWidget( r.topLeft()),
+ // don't use bottomright corner for conversion, but the one next to it
+ imageToWidget( r.bottomRight() + QPoint( 1, 1 )) - QPoint( 1, 1 ));
+ }
+
+ int widgetToImageX( int x ) const {
+ if( mZoom == 1.0 ) return x - mXOffset;
+ return lround( ( x - mXOffset ) / mZoom );
+ }
+
+ int widgetToImageY( int y ) const {
+ if( mZoom == 1.0 ) return y - mYOffset;
+ return lround( ( y - mYOffset ) / mZoom );
+ }
+
+ QPoint widgetToImage( const QPoint& p ) const {
+ return QPoint( widgetToImageX( p.x()), widgetToImageY( p.y()));
+ }
+
+ QRect widgetToImage( const QRect& r ) const {
+ return QRect( widgetToImage( r.topLeft()),
+ // don't use bottomright corner for conversion, but the one next to it
+ widgetToImage( r.bottomRight() + QPoint( 1, 1 )) - QPoint( 1, 1 ));
+ }
+
+ QRect widgetToImageBounding( const QRect& r, int extra ) const {
+ QRect ret = widgetToImage( r );
+ // make sure converting to image and back always returns QRect at least as large as 'r'
+ extra += mZoom == 1.0 ? 0 : int( ceil( 1 / mZoom ));
+ ret.addCoords( -extra, -extra, extra, extra );
+ return ret;
+ }
+
+ void initZoomCombo() {
+ mZoomCombo->clear();
+ for (QValueVector<KToggleAction*>::iterator it=mZoomComboActions.begin();
+ it!=mZoomComboActions.end();
+ ++it)
+ {
+ QString txt=(*it)->plainText();
+ mZoomCombo->insertItem(txt);
+ }
+
+ const double zoomValues[] = { 0.5, 1, 2 };
+ int nbValues=sizeof(zoomValues) / sizeof(double);
+ for (int pos=0; pos<nbValues; ++pos) {
+ QString txt=QString("%1%").arg( int(zoomValues[pos]*100) );
+ mZoomCombo->insertItem(txt);
+ }
+ }
+};
+
+
+inline bool doDelayedSmoothing() {
+ return ImageViewConfig::delayedSmoothing()
+ && ImageViewConfig::smoothAlgorithm()!=ImageUtils::SMOOTH_NONE;
+}
+
+
+class ImageView::EventFilter : public QObject {
+public:
+ EventFilter(ImageView* parent)
+ : QObject(parent) {}
+
+ bool eventFilter(QObject*, QEvent* event) {
+ switch (event->type()) {
+ case QEvent::KeyPress:
+ case QEvent::KeyRelease:
+ case QEvent::AccelOverride:
+ return static_cast< ImageView* >( parent())
+ ->viewportKeyEvent(static_cast<QKeyEvent*>(event));
+ default:
+ break;
+ }
+ return false;
+ }
+};
+
+
+
+ImageView::ImageView(QWidget* parent,Document* document, KActionCollection* actionCollection)
+: QScrollView(parent,0L,WResizeNoErase|WRepaintNoErase|WPaintClever)
+{
+ d=new Private;
+ d->mDocument=document;
+ d->mToolID=SCROLL;
+ d->mXOffset=0;
+ d->mYOffset=0;
+ d->mZoomMode=static_cast<ZoomMode>( ImageViewConfig::zoomMode() );
+ d->mZoom=1;
+ d->mActionCollection=actionCollection;
+ d->mFullScreen=false;
+ d->mOperaLikePrevious=false;
+ d->mZoomBeforeAuto=1;
+ d->mPendingOperations= 0 ;
+ d->mSmoothingSuspended= false ;
+ d->mGamma = 100;
+ d->mBrightness = 0;
+ d->mContrast = 100;
+ d->mBCGDialog = 0;
+
+ viewport()->setFocusPolicy(WheelFocus);
+ setFrameStyle(NoFrame);
+ setAcceptDrops( true );
+ viewport()->setAcceptDrops( true );
+
+ updateScrollBarMode();
+ viewport()->setBackgroundColor(ImageViewConfig::backgroundColor() );
+
+ d->mTools[SCROLL]=new ScrollTool(this);
+ d->mTools[ZOOM]=new ZoomTool(this);
+ d->mTools[d->mToolID]->updateCursor();
+
+ // Create actions
+ d->mZoomToFit=new KToggleAction(i18n("Fit to &Window"),"viewmagfit",0,d->mActionCollection,"view_zoom_to_fit");
+ connect(d->mZoomToFit,SIGNAL(toggled(bool)),
+ this,SLOT(setZoomToFit(bool)) );
+ d->mZoomToWidth=new KToggleAction(i18n("Fit to &Width"),0,0,d->mActionCollection,"view_zoom_to_width");
+ connect(d->mZoomToWidth,SIGNAL(toggled(bool)),
+ this,SLOT(setZoomToWidth(bool)) );
+ d->mZoomToHeight=new KToggleAction(i18n("Fit to &Height"),0,0,d->mActionCollection,"view_zoom_to_height");
+ connect(d->mZoomToHeight,SIGNAL(toggled(bool)),
+ this,SLOT(setZoomToHeight(bool)) );
+
+ d->mZoomIn=KStdAction::zoomIn(this,SLOT(slotZoomIn()),d->mActionCollection);
+
+ d->mZoomOut=KStdAction::zoomOut(this,SLOT(slotZoomOut()),d->mActionCollection);
+
+ d->mResetZoom=KStdAction::actualSize(this,SLOT(slotResetZoom()),d->mActionCollection);
+ d->mResetZoom->setIcon("viewmag1");
+
+ d->mLockZoom=new KToggleAction(i18n("&Lock Zoom"),"lock",0,d->mActionCollection,"view_zoom_lock");
+ d->mLockZoom->setChecked(ImageViewConfig::lockZoom());
+ connect(d->mLockZoom,SIGNAL(toggled(bool)),
+ this,SLOT(setLockZoom(bool)) );
+
+ d->mZoomCombo=new QComboBox(true);
+ // Avoid stealing focus
+ d->mZoomCombo->setFocusPolicy(ClickFocus);
+ connect(d->mZoomCombo, SIGNAL(activated(int)),
+ this, SLOT(slotSelectZoom()) );
+
+ d->mZoomComboAction=new KWidgetAction(d->mZoomCombo, i18n("Zoom"), 0, 0, 0, d->mActionCollection, "view_zoom_to");
+
+ d->mZoomComboActions.append(d->mZoomToFit);
+ d->mZoomComboActions.append(d->mZoomToWidth);
+ d->mZoomComboActions.append(d->mZoomToHeight);
+ if (d->mZoomMode!=ZOOM_FREE) {
+ d->mZoomComboActions[d->mZoomMode]->setChecked(true);
+ }
+ d->initZoomCombo();
+
+ d->mAdjustBCG=new KAction(i18n("Adjust Brightness/Contrast/Gamma"), "colorize", 0,
+ this, SLOT(showBCGDialog()), d->mActionCollection, "adjust_bcg");
+ d->mIncreaseGamma=new KAction(i18n("Increase Gamma"),0,CTRL+Key_G,
+ this,SLOT(increaseGamma()),d->mActionCollection,"increase_gamma");
+ d->mDecreaseGamma=new KAction(i18n("Decrease Gamma"),0,SHIFT+CTRL+Key_G,
+ this,SLOT(decreaseGamma()),d->mActionCollection,"decrease_gamma");
+ d->mIncreaseBrightness=new KAction(i18n("Increase Brightness" ),0,CTRL+Key_B,
+ this,SLOT(increaseBrightness()),d->mActionCollection,"increase_brightness");
+ d->mDecreaseBrightness=new KAction(i18n("Decrease Brightness" ),0,SHIFT+CTRL+Key_B,
+ this,SLOT(decreaseBrightness()),d->mActionCollection,"decrease_brightness");
+ d->mIncreaseContrast=new KAction(i18n("Increase Contrast" ),0,CTRL+Key_C,
+ this,SLOT(increaseContrast()),d->mActionCollection,"increase_contrast");
+ d->mDecreaseContrast=new KAction(i18n("Decrease Contrast" ),0,SHIFT+CTRL+Key_C,
+ this,SLOT(decreaseContrast()),d->mActionCollection,"decrease_contrast");
+
+ // Connect to some interesting signals
+ connect(d->mDocument,SIGNAL(loaded(const KURL&)),
+ this,SLOT(slotLoaded()) );
+
+ connect(d->mDocument,SIGNAL(loading()),
+ this,SLOT( loadingStarted()) );
+
+ connect(d->mDocument,SIGNAL(modified()),
+ this,SLOT(slotModified()) );
+
+ connect(d->mDocument, SIGNAL(sizeUpdated()),
+ this, SLOT(slotImageSizeUpdated()) );
+
+ connect(d->mDocument, SIGNAL(rectUpdated(const QRect&)),
+ this, SLOT(slotImageRectUpdated(const QRect&)) );
+
+ connect(&d->mPendingPaintTimer,SIGNAL(timeout()),
+ this,SLOT(checkPendingOperations()) );
+
+ connect(BusyLevelManager::instance(),SIGNAL(busyLevelChanged(BusyLevel)),
+ this,SLOT(slotBusyLevelChanged(BusyLevel) ));
+
+ // This event filter is here to make sure the pixmap view is aware of the changes
+ // in the keyboard modifiers, even if it isn't focused. However, making this widget
+ // itself the filter would lead to doubled paint events, because QScrollView
+ // installs an event filter on its viewport, and doesn't filter out the paint
+ // events -> it'd get it twice, first from app filter, second from viewport filter.
+ EventFilter* filter=new EventFilter(this);
+ kapp->installEventFilter(filter);
+}
+
+
+ImageView::~ImageView() {
+ ImageViewConfig::setZoomMode(d->mZoomMode);
+ ImageViewConfig::setLockZoom(d->mLockZoom->isChecked());
+ ImageViewConfig::self()->writeConfig();
+ delete d->mTools[SCROLL];
+ delete d->mTools[ZOOM];
+ delete d;
+}
+
+
+void ImageView::slotLoaded() {
+ if (d->mDocument->isNull()) {
+ resizeContents(0,0);
+ viewport()->repaint(false);
+ return;
+ }
+
+ if (doDelayedSmoothing()) scheduleOperation( SMOOTH_PASS );
+}
+
+
+void ImageView::slotModified() {
+ if (d->mZoomMode!=ZOOM_FREE) {
+ updateZoom(d->mZoomMode);
+ } else {
+ updateContentSize();
+ updateImageOffset();
+ updateZoomActions();
+ fullRepaint();
+ }
+}
+
+
+void ImageView::loadingStarted() {
+ cancelPending();
+ d->mSmoothingSuspended = true;
+ d->mValidImageArea = QRegion();
+ d->mGamma = 100;
+ d->mBrightness = 0;
+ d->mContrast = 100;
+
+ if (!d->mLockZoom->isChecked()) {
+ d->mZoomBeforeAuto = 1.;
+ }
+}
+
+//------------------------------------------------------------------------
+//
+// Properties
+//
+//------------------------------------------------------------------------
+double ImageView::zoom() const {
+ return d->mZoom;
+}
+
+
+bool ImageView::fullScreen() const {
+ return d->mFullScreen;
+}
+
+
+QPoint ImageView::offset() const {
+ return QPoint(d->mXOffset, d->mYOffset);
+}
+
+
+bool ImageView::canZoom(bool in) const {
+ KAction* zoomAction=in ? d->mZoomIn : d->mZoomOut;
+ return zoomAction->isEnabled();
+}
+
+
+KToggleAction* ImageView::zoomToFit() const {
+ return d->mZoomToFit;
+}
+
+
+void ImageView::updateFromSettings() {
+ // Reset, so that next repaint doesn't possibly take longer because of
+ // smoothing
+ ImageViewConfig::setMaxRepaintSize(DEFAULT_MAX_REPAINT_SIZE);
+ ImageViewConfig::setMaxScaleRepaintSize(DEFAULT_MAX_REPAINT_SIZE);
+ ImageViewConfig::setMaxSmoothRepaintSize(DEFAULT_MAX_REPAINT_SIZE);
+
+ if( doDelayedSmoothing() ) {
+ scheduleOperation( SMOOTH_PASS );
+ } else {
+ fullRepaint();
+ }
+
+ // If enlargeSmallImage changed
+ if (d->mZoomMode!=ZOOM_FREE) {
+ updateZoom(d->mZoomMode);
+ }
+
+ updateScrollBarMode();
+
+ if (!d->mFullScreen) {
+ viewport()->setBackgroundColor(ImageViewConfig::backgroundColor() );
+ }
+}
+
+
+void ImageView::setZoom(double zoom, int centerX, int centerY) {
+ updateZoom(ZOOM_FREE, zoom, centerX, centerY);
+}
+
+
+void ImageView::updateZoom(ZoomMode zoomMode, double value, int centerX, int centerY) {
+ ZoomMode oldZoomMode = d->mZoomMode;
+ double oldZoom=d->mZoom;
+ d->mZoomMode=zoomMode;
+ KAction* checkedZoomAction=0;
+
+ viewport()->setUpdatesEnabled(false);
+
+ if (zoomMode==ZOOM_FREE) {
+ Q_ASSERT(value!=0);
+ d->mZoom=value;
+ } else {
+ if (oldZoomMode == ZOOM_FREE) {
+ // Only store zoom before auto if we were in ZOOM_FREE mode, otherwise
+ // we will store the computed auto zoom value (Bug 134590)
+ d->mZoomBeforeAuto = d->mZoom;
+ }
+ d->mXCenterBeforeAuto=width()/2 + contentsX() + d->mXOffset;
+ d->mYCenterBeforeAuto=height()/2 + contentsY() + d->mYOffset;
+
+ if (zoomMode==ZOOM_FIT) {
+ d->mZoom=computeZoomToFit();
+ checkedZoomAction=d->mZoomToFit;
+
+ } else if (zoomMode==ZOOM_FIT_WIDTH) {
+ d->mZoom=computeZoomToWidth();
+ checkedZoomAction=d->mZoomToWidth;
+
+ } else {
+ d->mZoom=computeZoomToHeight();
+ checkedZoomAction=d->mZoomToHeight;
+ }
+ }
+
+ // Make sure only one zoom action is toggled on
+ d->mZoomToFit->setChecked( checkedZoomAction==d->mZoomToFit);
+ d->mZoomToWidth->setChecked( checkedZoomAction==d->mZoomToWidth);
+ d->mZoomToHeight->setChecked(checkedZoomAction==d->mZoomToHeight);
+
+ updateContentSize();
+
+ // Find the coordinate of the center of the image
+ // and center the view on it
+ if (centerX==-1) {
+ centerX=int( ((visibleWidth()/2+contentsX()-d->mXOffset)/oldZoom)*d->mZoom );
+ }
+ if (centerY==-1) {
+ centerY=int( ((visibleHeight()/2+contentsY()-d->mYOffset)/oldZoom)*d->mZoom );
+ }
+ center(centerX,centerY);
+
+ updateScrollBarMode();
+ updateImageOffset();
+ updateZoomActions();
+
+ viewport()->setUpdatesEnabled(true);
+ fullRepaint();
+}
+
+
+void ImageView::setFullScreen(bool fullScreen) {
+ d->mFullScreen=fullScreen;
+
+ if (d->mFullScreen) {
+ viewport()->setBackgroundColor(black);
+ } else {
+ viewport()->setBackgroundColor(ImageViewConfig::backgroundColor() );
+ }
+}
+
+
+//------------------------------------------------------------------------
+//
+// Overloaded methods
+//
+//------------------------------------------------------------------------
+void ImageView::resizeEvent(QResizeEvent* event) {
+ QScrollView::resizeEvent(event);
+ if (d->mZoomMode!=ZOOM_FREE) {
+ updateZoom(d->mZoomMode);
+ } else {
+ updateContentSize();
+ updateImageOffset();
+ }
+}
+
+
+inline void composite(uint* rgba,uint value) {
+ uint alpha=(*rgba) >> 24;
+ if (alpha<255) {
+ uint alphaValue=(255-alpha)*value;
+
+ uint c1=( ( (*rgba & 0xFF0000) >> 16 ) * alpha + alphaValue ) >> 8;
+ uint c2=( ( (*rgba & 0x00FF00) >> 8 ) * alpha + alphaValue ) >> 8;
+ uint c3=( ( (*rgba & 0x0000FF) >> 0 ) * alpha + alphaValue ) >> 8;
+ *rgba=0xFF000000 + (c1<<16) + (c2<<8) + c3;
+ }
+}
+
+void ImageView::drawContents(QPainter* painter,int clipx,int clipy,int clipw,int cliph) {
+ // Erase borders
+ QRect imageRect(0, 0, d->mDocument->width(), d->mDocument->height());
+ imageRect = d->imageToWidget(imageRect);
+
+ QRect widgetRect = QRect(0, 0, visibleWidth(), visibleHeight());
+
+ QRegion region = QRegion(widgetRect) - imageRect;
+ QMemArray<QRect> rects = region.rects();
+ for(unsigned int pos = 0; pos < rects.count(); ++pos ) {
+ painter->eraseRect(rects[pos]);
+ }
+
+ // Repaint
+ if( !d->mValidImageArea.isEmpty()) {
+ addPendingPaint( false, QRect( clipx, clipy, clipw, cliph ));
+ }
+}
+
+// How this pending stuff works:
+// There's a queue of areas to paint (each with bool saying whether it's smooth pass).
+// Also, there's a bitfield of pending operations, operations are handled only after
+// there's nothing more to paint (so that smooth pass is started).
+void ImageView::addPendingPaint( bool smooth, QRect rect ) {
+ if( d->mSmoothingSuspended && smooth ) return;
+
+ // try to avoid scheduling already scheduled areas
+ QRegion& region = smooth ? d->mPendingSmoothRegion : d->mPendingNormalRegion;
+ if( region.intersect( rect ) == QRegion( rect ))
+ return; // whole rect has already pending paints
+ // at least try to remove the part that's already scheduled
+ rect = ( QRegion( rect ) - region ).boundingRect();
+ region += rect;
+ if( rect.isEmpty())
+ return;
+ addPendingPaintInternal( smooth, rect );
+}
+
+void ImageView::addPendingPaintInternal( bool smooth, QRect rect ) {
+ const long long MAX_DIM = 1000000; // if monitors get larger than this, we're in trouble :)
+ // QMap will ensure ordering (non-smooth first, top-to-bottom, left-to-right)
+ long long key = ( smooth ? MAX_DIM * MAX_DIM : 0 ) + rect.y() * MAX_DIM + rect.x();
+ // handle the case of two different paints at the same position (just in case)
+ key *= 100;
+ bool insert = true;
+ while( d->mPendingPaints.contains( key )) {
+ if( d->mPendingPaints[ key ].rect.contains( rect )) {
+ insert = false;
+ break;
+ }
+ if( rect.contains( d->mPendingPaints[ key ].rect )) {
+ break;
+ }
+ ++key;
+ }
+ if( insert ) {
+ d->mPendingPaints[ key ] = PendingPaint( smooth, rect );
+ }
+ scheduleOperation( CHECK_OPERATIONS );
+}
+
+void ImageView::checkPendingOperations() {
+ checkPendingOperationsInternal();
+ if( d->mPendingPaints.isEmpty() && d->mPendingOperations == 0 ) {
+ d->mPendingPaintTimer.stop();
+ }
+ updateBusyLevels();
+}
+
+void ImageView::limitPaintSize( PendingPaint& paint ) {
+ // The only thing that makes time spent in performPaint() vary
+ // is whether there will be scaling and whether there will be smoothing.
+ // So there are three max sizes for each mode.
+ int maxSize = ImageViewConfig::maxRepaintSize();
+ if( d->mZoom != 1.0 ) {
+ if( paint.smooth || !doDelayedSmoothing() ) {
+ maxSize = ImageViewConfig::maxSmoothRepaintSize();
+ } else {
+ maxSize = ImageViewConfig::maxScaleRepaintSize();
+ }
+ }
+ // don't paint more than max_size pixels at a time
+ int maxHeight = ( maxSize + paint.rect.width() - 1 ) / paint.rect.width(); // round up
+ maxHeight = QMAX( maxHeight, 5 ); // at least 5 lines together
+ // can't repaint whole paint at once, adjust height and schedule the rest
+ if( maxHeight < paint.rect.height()) {
+ QRect remaining = paint.rect;
+ remaining.setTop( remaining.top() + maxHeight );
+ addPendingPaintInternal( paint.smooth, remaining );
+ paint.rect.setHeight( maxHeight );
+ }
+}
+
+
+void ImageView::checkPendingOperationsInternal() {
+ if( !d->mPendingPaintTimer.isActive()) // suspended
+ return;
+ while( !d->mPendingPaints.isEmpty()) {
+ PendingPaint paint = *d->mPendingPaints.begin();
+ d->mPendingPaints.remove( d->mPendingPaints.begin());
+ limitPaintSize( paint ); // modifies paint.rect if necessary
+ QRegion& region = paint.smooth ? d->mPendingSmoothRegion : d->mPendingNormalRegion;
+ region -= paint.rect;
+ QRect visibleRect( contentsX(), contentsY(), visibleWidth(), visibleHeight());
+ QRect paintRect = paint.rect.intersect( visibleRect );
+ if( !paintRect.isEmpty()) {
+ QPainter painter( viewport());
+ painter.translate( -contentsX(), -contentsY());
+ performPaint( &painter, paintRect.x(), paintRect.y(),
+ paintRect.width(), paintRect.height(), paint.smooth );
+ return;
+ }
+ }
+ if( d->mPendingOperations & SMOOTH_PASS ) {
+ d->mSmoothingSuspended = false;
+ if( doDelayedSmoothing() ) {
+ QRect visibleRect( contentsX(), contentsY(), visibleWidth(), visibleHeight());
+ addPendingPaint( true, visibleRect );
+ }
+ d->mPendingOperations &= ~SMOOTH_PASS;
+ return;
+ }
+}
+
+void ImageView::scheduleOperation( Operation operation )
+{
+ d->mPendingOperations |= operation;
+ slotBusyLevelChanged( BusyLevelManager::instance()->busyLevel());
+ updateBusyLevels();
+}
+
+void ImageView::updateBusyLevels() {
+ if( !d->mPendingPaintTimer.isActive()) {
+ BusyLevelManager::instance()->setBusyLevel( this, BUSY_NONE );
+ } else if( !d->mPendingPaints.isEmpty() && !(*d->mPendingPaints.begin()).smooth ) {
+ BusyLevelManager::instance()->setBusyLevel( this, BUSY_PAINTING );
+ } else if(( d->mPendingOperations & SMOOTH_PASS )
+ || ( !d->mPendingPaints.isEmpty() && (*d->mPendingPaints.begin()).smooth )) {
+ BusyLevelManager::instance()->setBusyLevel( this, BUSY_SMOOTHING );
+ } else {
+ assert( false );
+ }
+}
+
+void ImageView::slotBusyLevelChanged( BusyLevel level ) {
+ bool resume = false;
+ if( level <= BUSY_PAINTING
+ && !d->mPendingPaints.isEmpty() && !(*d->mPendingPaints.begin()).smooth ) {
+ resume = true;
+ } else if( level <= BUSY_SMOOTHING
+ && (( d->mPendingOperations & SMOOTH_PASS )
+ || ( !d->mPendingPaints.isEmpty() && (*d->mPendingPaints.begin()).smooth ))) {
+ resume = true;
+ }
+ if( resume ) {
+ d->mPendingPaintTimer.start( 0 );
+ } else {
+ d->mPendingPaintTimer.stop();
+ }
+}
+
+// How to do painting:
+// When something needs to be erased: QPainter on viewport and eraseRect()
+// When whole picture needs to be repainted: fullRepaint()
+// When a part of the picture needs to be updated: viewport()->repaint(area,false)
+// All other paints will be changed to progressive painting.
+void ImageView::fullRepaint() {
+ if( !viewport()->isUpdatesEnabled()) return;
+ cancelPending();
+ viewport()->repaint(false);
+}
+
+void ImageView::cancelPending() {
+ d->mPendingPaints.clear();
+ d->mPendingNormalRegion = QRegion();
+ d->mPendingSmoothRegion = QRegion();
+ d->mPendingPaintTimer.stop();
+ d->mPendingOperations = 0;
+ updateBusyLevels();
+}
+
+//#define DEBUG_RECTS
+
+// do the actual painting
+void ImageView::performPaint( QPainter* painter, int clipx, int clipy, int clipw, int cliph, bool secondPass ) {
+ #ifdef DEBUG_RECTS
+ static QColor colors[4]={QColor(255,0,0),QColor(0,255,0),QColor(0,0,255),QColor(255,255,0) };
+ static int numColor=0;
+ #endif
+
+ QTime t;
+ t.start();
+
+ if (d->mDocument->isNull()) {
+ painter->eraseRect(clipx,clipy,clipw,cliph);
+ return;
+ }
+
+ // True if another pass will follow
+ bool fastpass = doDelayedSmoothing() && zoom() != 1.0 && !secondPass;
+
+ ImageUtils::SmoothAlgorithm smoothAlgo = ImageUtils::SMOOTH_NONE;
+ if( zoom() != 1.0 ) {
+ if (doDelayedSmoothing() && !secondPass) {
+ // Add a second, smoothing pass
+ addPendingPaint( true, QRect( clipx, clipy, clipw, cliph ));
+ } else {
+ // We need to smooth now
+ smoothAlgo = static_cast<ImageUtils::SmoothAlgorithm>( ImageViewConfig::smoothAlgorithm() );
+ }
+ }
+
+ int extraPixels = ImageUtils::extraScalePixels( smoothAlgo, zoom());
+ QRect imageRect = d->widgetToImageBounding( QRect(clipx,clipy,clipw,cliph), extraPixels );
+ imageRect = imageRect.intersect( QRect( 0, 0, d->mDocument->width(), d->mDocument->height()));
+ QMemArray< QRect > rects = d->mValidImageArea.intersect( imageRect ).rects();
+ for( unsigned int i = 1; i < rects.count(); ++i ) {
+ addPendingPaint( secondPass, d->imageToWidget( rects[ i ] ));
+ }
+ imageRect = rects.count() > 0 ? rects[ 0 ] : QRect();
+ if (imageRect.isEmpty()) {
+ painter->eraseRect(clipx,clipy,clipw,cliph);
+ return;
+ }
+ QRect widgetRect = d->imageToWidget( imageRect );
+ if (widgetRect.isEmpty() || imageRect.isEmpty()) {
+ painter->eraseRect(clipx,clipy,clipw,cliph);
+ return;
+ }
+
+// With very large images, just getting a subimage using QImage::copy( QRect ) takes a significant
+// portion of time here (even though it's just copying of data - probably because it's a lot of data).
+// So don't do any subimage copying but instead use CroppedQImage which just manipulates scanline
+// pointers. Note however that it's a bit hackish and there may be trouble if any code accesses
+// the image data directly as a whole. See CroppedQImage for details.
+
+// QImage image = d->mDocument->image().copy( imageRect );
+ ImageUtils::CroppedQImage image( d->mDocument->image(), imageRect );
+
+ if( zoom() != 1.0 ) {
+ image=ImageUtils::scale(image,widgetRect.width(),widgetRect.height(), smoothAlgo );
+ }
+
+ if( d->mBrightness != 0 ) {
+ image.normalize(); // needed, it will be modified
+ image = ImageUtils::changeBrightness( image, d->mBrightness );
+ }
+
+ if( d->mContrast != 100 ) { // != 1.0
+ image.normalize(); // needed, it will be modified
+ image = ImageUtils::changeContrast( image, d->mContrast );
+ }
+
+ if( d->mGamma != 100 ) { // != 1.0
+ image.normalize(); // needed, it will be modified
+ image = ImageUtils::changeGamma( image, d->mGamma );
+ }
+
+// Calling normalize() here would make image to be a proper QImage without modified scanlines,
+// so that even calling QImage::copy() would work. However, it seems it's not necessary to call
+// it here. The code above checks that QImage::copy() or similar doesn't occur (that zoom() != 1.0
+// is there primarily to avoid that). If any kind of redraw trouble occurs, try uncommenting this
+// line below first.
+// image.normalize(); // make it use its own data, if needed
+
+ if (image.hasAlphaBuffer()) {
+ image.normalize(); // needed, it will be modified
+ if (image.depth()!=32) {
+ image=image.convertDepth(32);
+ }
+
+ bool light;
+
+ int imageXOffset=widgetRect.x()-d->mXOffset;
+ int imageYOffset=widgetRect.y()-d->mYOffset;
+ int imageWidth=image.width();
+ int imageHeight=image.height();
+ for (int y=0;y<imageHeight;++y) {
+ uint* rgba=(uint*)(image.scanLine(y));
+ for(int x=0;x<imageWidth;x++) {
+ light= ((x+imageXOffset) & 16) ^ ((y+imageYOffset) & 16);
+ composite(rgba,light?192:128);
+ rgba++;
+ }
+ }
+ image.setAlphaBuffer(false);
+ }
+
+ QRect paintRect( clipx, clipy, clipw, cliph );
+ QPixmap buffer( paintRect.size());
+ {
+ QPainter bufferPainter(&buffer);
+ bufferPainter.setBackgroundColor(painter->backgroundColor());
+ bufferPainter.eraseRect(0,0,paintRect.width(),paintRect.height());
+ bufferPainter.drawImage(widgetRect.topLeft()-paintRect.topLeft(),image,
+ fastpass?ThresholdDither:0);
+ }
+ painter->drawPixmap(paintRect.topLeft(),buffer);
+
+ if( paintRect.width() * paintRect.height() >= 10000 ) { // ignore small repaints
+ // try to do one step in 0.1sec
+ int size = paintRect.width() * paintRect.height() * 100 / QMAX( t.elapsed(), 1 );
+
+ int maxRepaintSize;
+ if (zoom() == 1.0) {
+ maxRepaintSize=ImageViewConfig::maxRepaintSize();
+ } else {
+ if (smoothAlgo!=ImageUtils::SMOOTH_NONE) {
+ maxRepaintSize=ImageViewConfig::maxSmoothRepaintSize();
+ } else {
+ maxRepaintSize=ImageViewConfig::maxScaleRepaintSize();
+ }
+ }
+
+ maxRepaintSize = KCLAMP(
+ ( size + maxRepaintSize ) / 2,
+ 10000, LIMIT_MAX_REPAINT_SIZE);
+
+ if (zoom() == 1.0) {
+ ImageViewConfig::setMaxRepaintSize(maxRepaintSize);
+ } else {
+ if (smoothAlgo!=ImageUtils::SMOOTH_NONE) {
+ ImageViewConfig::setMaxSmoothRepaintSize(maxRepaintSize);
+ } else {
+ ImageViewConfig::setMaxScaleRepaintSize(maxRepaintSize);
+ }
+ }
+ }
+
+ #ifdef DEBUG_RECTS
+ painter->setPen(colors[numColor]);
+ numColor=(numColor+1)%4;
+ painter->drawRect(paintRect);
+ #endif
+
+ QApplication::flushX();
+}
+
+
+void ImageView::viewportMousePressEvent(QMouseEvent* event) {
+ viewport()->setFocus();
+ switch (event->button()) {
+ case Qt::LeftButton:
+ d->mTools[d->mToolID]->leftButtonPressEvent(event);
+ break;
+ case Qt::RightButton:
+ d->mTools[d->mToolID]->rightButtonPressEvent(event);
+ break;
+ default: // Avoid compiler complain
+ break;
+ }
+}
+
+
+void ImageView::viewportMouseMoveEvent(QMouseEvent* event) {
+ selectTool(event->state(), true);
+ d->mTools[d->mToolID]->mouseMoveEvent(event);
+}
+
+
+void ImageView::viewportMouseReleaseEvent(QMouseEvent* event) {
+ switch (event->button()) {
+ case Qt::LeftButton:
+ if (event->stateAfter() & Qt::RightButton) {
+ d->mOperaLikePrevious=true;
+ emit selectPrevious();
+ return;
+ }
+ d->mTools[d->mToolID]->leftButtonReleaseEvent(event);
+ break;
+
+ case Qt::MidButton:
+ d->mTools[d->mToolID]->midButtonReleaseEvent(event);
+ break;
+
+ case Qt::RightButton:
+ if (event->stateAfter() & Qt::LeftButton) {
+ emit selectNext();
+ return;
+ }
+
+ if (d->mOperaLikePrevious) { // Avoid showing the popup menu after Opera like previous
+ d->mOperaLikePrevious=false;
+ } else {
+ d->mTools[d->mToolID]->rightButtonReleaseEvent(event);
+ }
+ break;
+
+ default: // Avoid compiler complain
+ break;
+ }
+}
+
+
+bool ImageView::eventFilter(QObject* obj, QEvent* event) {
+ switch (event->type()) {
+ case QEvent::KeyPress:
+ case QEvent::KeyRelease:
+ case QEvent::AccelOverride:
+ return viewportKeyEvent(static_cast<QKeyEvent*>(event));
+
+ case QEvent::MouseButtonDblClick:
+ if (d->mToolID==ZOOM) return false;
+ emit doubleClicked();
+ return true;
+
+ // Getting/loosing focus causes repaints, but repainting here is expensive,
+ // and there's no need to repaint on focus changes, as the focus is not
+ // indicated.
+ case QEvent::FocusIn:
+ case QEvent::FocusOut:
+ return true;
+
+ case QEvent::Enter:
+ selectTool( kapp->keyboardMouseState(), true );
+ emitRequestHintDisplay();
+ break;
+
+ default:
+ break;
+ }
+ return QScrollView::eventFilter(obj,event);
+}
+
+
+bool ImageView::viewportKeyEvent(QKeyEvent* event) {
+ selectTool(event->stateAfter(), false);
+ return false;
+}
+
+
+void ImageView::contentsDragEnterEvent(QDragEnterEvent* event) {
+ event->accept( QUriDrag::canDecode( event ));
+}
+
+void ImageView::contentsDropEvent(QDropEvent* event) {
+ KURL::List list;
+ if( KURLDrag::decode( event, list )) {
+ d->mDocument->setURL( list.first());
+ }
+}
+
+void ImageView::keyPressEvent( QKeyEvent *event ) {
+ QScrollView::keyPressEvent( event );
+ int deltaX, deltaY;
+
+ if (event->state() != Qt::NoButton) {
+ return;
+ }
+ switch (event->key()) {
+ case Key_Up:
+ deltaX = 0;
+ deltaY = -1;
+ break;
+ case Key_Down:
+ deltaX = 0;
+ deltaY = 1;
+ break;
+ case Key_Left:
+ deltaX = -1;
+ deltaY = 0;
+ break;
+ case Key_Right:
+ deltaX = 1;
+ deltaY = 0;
+ break;
+ default:
+ return;
+ }
+ deltaX *= width() / 2;
+ deltaY *= height() / 2;
+ scrollBy (deltaX, deltaY);
+}
+
+/**
+ * If force is set, the cursor will be updated even if the tool is not
+ * different from the current one.
+ */
+void ImageView::selectTool(ButtonState state, bool force) {
+ ToolID oldToolID=d->mToolID;
+ if (state & ControlButton) {
+ d->mToolID=ZOOM;
+ if (d->mToolID!=oldToolID) {
+ emitRequestHintDisplay();
+ }
+ } else {
+ d->mToolID=SCROLL;
+ }
+
+ if (d->mToolID!=oldToolID || force) {
+ d->mTools[d->mToolID]->updateCursor();
+ }
+}
+
+
+void ImageView::wheelEvent(QWheelEvent* event) {
+ d->mTools[d->mToolID]->wheelEvent(event);
+}
+
+
+//------------------------------------------------------------------------
+//
+// Slots
+//
+//------------------------------------------------------------------------
+void ImageView::slotZoomIn() {
+ updateZoom(ZOOM_FREE, computeZoom(true));
+}
+
+
+void ImageView::slotZoomOut() {
+ updateZoom(ZOOM_FREE, computeZoom(false));
+}
+
+
+void ImageView::slotResetZoom() {
+ updateZoom(ZOOM_FREE, 1.0);
+}
+
+
+void ImageView::slotSelectZoom() {
+ int currentItem=d->mZoomCombo->currentItem();
+
+ if (currentItem>=int(d->mZoomComboActions.count()) ) {
+ QString txt=d->mZoomCombo->currentText();
+ txt=txt.left(txt.find('%'));
+ double value=KGlobal::locale()->readNumber(txt) / 100.0;
+ updateZoom(ZOOM_FREE, value);
+ } else {
+ d->mZoomComboActions[currentItem]->activate();
+ }
+}
+
+
+void ImageView::setZoomToFit(bool on) {
+ if (on) {
+ updateZoom(ZOOM_FIT);
+ } else {
+ updateZoom(ZOOM_FREE, d->mZoomBeforeAuto, d->mXCenterBeforeAuto, d->mYCenterBeforeAuto);
+ }
+}
+
+
+void ImageView::setZoomToWidth(bool on) {
+ if (on) {
+ updateZoom(ZOOM_FIT_WIDTH);
+ } else {
+ updateZoom(ZOOM_FREE, d->mZoomBeforeAuto, d->mXCenterBeforeAuto, d->mYCenterBeforeAuto);
+ }
+}
+
+
+void ImageView::setZoomToHeight(bool on) {
+ if (on) {
+ updateZoom(ZOOM_FIT_HEIGHT);
+ } else {
+ updateZoom(ZOOM_FREE, d->mZoomBeforeAuto, d->mXCenterBeforeAuto, d->mYCenterBeforeAuto);
+ }
+}
+
+
+void ImageView::setLockZoom(bool value) {
+ if( value ) {
+ d->mZoomToFit->setChecked( false );
+ d->mZoomToWidth->setChecked( false );
+ d->mZoomToHeight->setChecked( false );
+ }
+ // don't change zoom here, keep it even if it was from some auto zoom mode
+}
+
+
+void ImageView::showBCGDialog() {
+ if (!d->mBCGDialog) {
+ d->mBCGDialog=new BCGDialog(this);
+ }
+ d->mBCGDialog->show();
+}
+
+
+int ImageView::brightness() const {
+ return d->mBrightness;
+}
+
+
+void ImageView::setBrightness(int value) {
+ d->mBrightness=value;
+ fullRepaint();
+}
+
+void ImageView::increaseBrightness() {
+ d->mBrightness = KCLAMP( d->mBrightness + 5, -100, 100 );
+ emit bcgChanged();
+ fullRepaint();
+}
+
+void ImageView::decreaseBrightness() {
+ d->mBrightness = KCLAMP( d->mBrightness - 5, -100, 100 );
+ emit bcgChanged();
+ fullRepaint();
+}
+
+
+
+int ImageView::contrast() const {
+ return d->mContrast - 100;
+}
+
+void ImageView::setContrast(int value) {
+ d->mContrast=value + 100;
+ fullRepaint();
+}
+
+void ImageView::increaseContrast() {
+ d->mContrast = KCLAMP( d->mContrast + 10, 0, 500 );
+ emit bcgChanged();
+ fullRepaint();
+}
+
+void ImageView::decreaseContrast() {
+ d->mContrast = KCLAMP( d->mContrast - 10, 0, 500 );
+ emit bcgChanged();
+ fullRepaint();
+}
+
+
+
+int ImageView::gamma() const {
+ return d->mGamma - 100;
+}
+
+
+void ImageView::setGamma(int value) {
+ d->mGamma=value + 100;
+ fullRepaint();
+}
+
+void ImageView::increaseGamma() {
+ d->mGamma = KCLAMP( d->mGamma + 10, 10, 500 );
+ emit bcgChanged();
+ fullRepaint();
+}
+
+void ImageView::decreaseGamma() {
+ d->mGamma = KCLAMP( d->mGamma - 10, 10, 500 );
+ emit bcgChanged();
+ fullRepaint();
+}
+//------------------------------------------------------------------------
+//
+// Private
+//
+//------------------------------------------------------------------------
+void ImageView::emitRequestHintDisplay() {
+ if (d->mDocument->isNull()) return;
+
+ emit requestHintDisplay( d->mTools[d->mToolID]->hint() );
+}
+
+
+void ImageView::slotImageSizeUpdated() {
+ d->mXOffset=0;
+ d->mYOffset=0;
+
+ d->mValidImageArea = QRegion();
+ if (d->mZoomMode!=ZOOM_FREE) {
+ d->mXCenterBeforeAuto=0;
+ d->mYCenterBeforeAuto=0;
+ } else {
+ horizontalScrollBar()->setValue(0);
+ verticalScrollBar()->setValue(0);
+ }
+ if (d->mZoomMode!=ZOOM_FREE) {
+ updateZoom(d->mZoomMode);
+ } else {
+ if( !d->mLockZoom->isChecked()) {
+ setZoom( 1.0 );
+ }
+ }
+
+ updateZoomActions();
+ d->mAdjustBCG->setEnabled(!d->mDocument->isNull());
+ d->mIncreaseGamma->setEnabled(!d->mDocument->isNull());
+ d->mDecreaseGamma->setEnabled(!d->mDocument->isNull());
+ d->mIncreaseBrightness->setEnabled(!d->mDocument->isNull());
+ d->mDecreaseBrightness->setEnabled(!d->mDocument->isNull());
+ d->mIncreaseContrast->setEnabled(!d->mDocument->isNull());
+ d->mDecreaseContrast->setEnabled(!d->mDocument->isNull());
+
+ updateContentSize();
+ updateImageOffset();
+ updateScrollBarMode();
+ fullRepaint();
+}
+
+void ImageView::slotImageRectUpdated(const QRect& imageRect) {
+ d->mValidImageArea += imageRect;
+ viewport()->repaint( d->imageToWidget( imageRect ), false );
+}
+
+
+void ImageView::updateScrollBarMode() {
+ if (d->mZoomMode==ZOOM_FIT || !ImageViewConfig::showScrollBars()) {
+ setVScrollBarMode(AlwaysOff);
+ setHScrollBarMode(AlwaysOff);
+ } else {
+ setVScrollBarMode(Auto);
+ setHScrollBarMode(Auto);
+ }
+}
+
+
+void ImageView::updateContentSize() {
+ resizeContents(
+ int(d->mDocument->width()*d->mZoom),
+ int(d->mDocument->height()*d->mZoom) );
+}
+
+double ImageView::computeZoomToFit() const {
+ if (d->mDocument->isNull()) {
+ return 1.0;
+ }
+ QSize size=d->mDocument->image().size();
+ size.scale(width(),height(),QSize::ScaleMin);
+
+ double zoom=double(size.width())/d->mDocument->width();
+ if (zoom>1.0 && !ImageViewConfig::enlargeSmallImages()) return 1.0;
+ return zoom;
+}
+
+double ImageView::computeZoomToWidth() const {
+ if (d->mDocument->isNull()) {
+ return 1.0;
+ }
+ int sw = verticalScrollBar()->sizeHint().width(); // geometry is not valid before first show()
+ int w = width();
+ int dw = d->mDocument->width();
+ switch( vScrollBarMode()) {
+ case AlwaysOff:
+ return double(w)/dw;
+ case AlwaysOn:
+ return double(w-sw)/dw;
+ case Auto:
+ default:
+ // there will be a vertical scrollbar if the image's height will be too large
+ if( d->mDocument->height() * (double(w)/dw) > height()) return double(w-sw)/dw;
+ return double(w)/dw;
+ }
+}
+
+double ImageView::computeZoomToHeight() const {
+ if (d->mDocument->isNull()) {
+ return 1.0;
+ }
+ int sh = horizontalScrollBar()->sizeHint().height();
+ int h = height();
+ int dh = d->mDocument->height();
+ switch( vScrollBarMode()) {
+ case AlwaysOff:
+ return double(h)/dh;
+ case AlwaysOn:
+ return double(h-sh)/dh;
+ case Auto:
+ default:
+ if( d->mDocument->width() * (double(h)/dh) > width()) return double(h-sh)/dh;
+ return double(h)/dh;
+ }
+}
+
+double ImageView::computeZoom(bool in) const {
+ const double F = 0.5; // change in 0.5 steps
+ double zoomtofit = computeZoomToFit();
+ double zoomtowidth = computeZoomToWidth();
+ double zoomtoheight = computeZoomToHeight();
+ if (in) {
+ double newzoom;
+ if (d->mZoom>=1.0) {
+ newzoom = (floor(d->mZoom/F)+1.0)*F;
+ } else {
+ newzoom = 1/(( ceil(1/d->mZoom/F)-1.0 )*F);
+ }
+ if( d->mZoom < zoomtofit && zoomtofit < newzoom ) newzoom = zoomtofit;
+ if( d->mZoom < zoomtowidth && zoomtowidth < newzoom ) newzoom = zoomtowidth;
+ if( d->mZoom < zoomtoheight && zoomtoheight < newzoom ) newzoom = zoomtoheight;
+ return newzoom;
+ } else {
+ double newzoom;
+ if (d->mZoom>1.0) {
+ newzoom = (ceil(d->mZoom/F)-1.0)*F;
+ } else {
+ newzoom = 1/(( floor(1/d->mZoom/F)+1.0 )*F);
+ }
+ if( d->mZoom > zoomtofit && zoomtofit > newzoom ) newzoom = zoomtofit;
+ if( d->mZoom > zoomtowidth && zoomtowidth > newzoom ) newzoom = zoomtowidth;
+ if( d->mZoom > zoomtoheight && zoomtoheight > newzoom ) newzoom = zoomtoheight;
+ return newzoom;
+ }
+}
+
+void ImageView::updateImageOffset() {
+ int viewWidth=width();
+ int viewHeight=height();
+
+ // Compute d->mXOffset and d->mYOffset in case the image does not fit
+ // the view width or height
+ int zpixWidth=int(d->mDocument->width() * d->mZoom);
+ int zpixHeight=int(d->mDocument->height() * d->mZoom);
+
+ if (zpixWidth>viewWidth && hScrollBarMode()!=AlwaysOff) {
+ // use sizeHint() - geometry is not valid before first show()
+ viewHeight-=horizontalScrollBar()->sizeHint().height();
+ }
+ if (zpixHeight>viewHeight && vScrollBarMode()!=AlwaysOff) {
+ viewWidth-=verticalScrollBar()->sizeHint().width();
+ }
+
+ d->mXOffset=QMAX(0,(viewWidth-zpixWidth)/2);
+ d->mYOffset=QMAX(0,(viewHeight-zpixHeight)/2);
+}
+
+
+void ImageView::updateZoomActions() {
+ // Disable most actions if there's no image
+ if (d->mDocument->isNull()) {
+ d->mZoomComboAction->setEnabled(false);
+ d->mZoomIn->setEnabled(false);
+ d->mZoomOut->setEnabled(false);
+ d->mResetZoom->setEnabled(false);
+ return;
+ }
+
+ d->mZoomComboAction->setEnabled(true);
+ d->mZoomToFit->setEnabled(true);
+ d->mZoomToWidth->setEnabled(true);
+ d->mZoomToHeight->setEnabled(true);
+ d->mResetZoom->setEnabled(true);
+
+
+ if (d->mZoomMode==ZOOM_FREE) {
+ d->mZoomIn->setEnabled(d->mZoom<MAX_ZOOM);
+ d->mZoomOut->setEnabled(d->mZoom>1/MAX_ZOOM);
+ QString zoomText=QString("%1%").arg(int(d->mZoom*100));
+ d->mZoomCombo->setCurrentText(zoomText);
+ } else {
+ d->mZoomIn->setEnabled(true);
+ d->mZoomOut->setEnabled(true);
+ d->mZoomCombo->setCurrentItem(d->mZoomMode);
+ }
+}
+
+} // namespace
diff --git a/src/gvcore/imageview.h b/src/gvcore/imageview.h
new file mode 100644
index 0000000..74e3c66
--- /dev/null
+++ b/src/gvcore/imageview.h
@@ -0,0 +1,190 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2000-2004 Aurélien Gâteau
+
+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.
+
+*/
+
+
+#ifndef IMAGEVIEW_H
+#define IMAGEVIEW_H
+
+// Qt
+#include <qmap.h>
+#include <qscrollview.h>
+#include <qtimer.h>
+#include <qvaluelist.h>
+
+// Local
+#include "busylevelmanager.h"
+#include "imageutils/imageutils.h"
+#include "libgwenview_export.h"
+class QEvent;
+class QLabel;
+class QMouseEvent;
+class QPainter;
+class QTimer;
+class QWheelEvent;
+class KAction;
+class KActionCollection;
+class KToggleAction;
+typedef QValueList<KAction *> KActionPtrList;
+
+namespace Gwenview {
+class Document;
+
+class LIBGWENVIEW_EXPORT ImageView : public QScrollView {
+Q_OBJECT
+
+public:
+ class ToolBase;
+ class ZoomTool;
+ class ScrollTool;
+ class EventFilter;
+#if __GNUC__ < 3
+ friend class ToolBase;
+ friend class ZoomTool;
+ friend class ScrollTool;
+#endif
+ friend class EventFilter;
+
+ enum ToolID { SCROLL, ZOOM };
+ enum ZoomMode { ZOOM_FIT, ZOOM_FIT_WIDTH, ZOOM_FIT_HEIGHT, ZOOM_FREE };
+ typedef QMap<ToolID,ToolBase*> Tools;
+
+ ImageView(QWidget* parent,Document*,KActionCollection*);
+ ~ImageView();
+
+ // Properties
+ double zoom() const;
+ void setZoom(double zoom, int centerX=-1, int centerY=-1);
+ bool fullScreen() const;
+ void setFullScreen(bool);
+
+ int brightness() const;
+ int contrast() const;
+ int gamma() const;
+
+public slots:
+ void setBrightness(int);
+ void setContrast(int);
+ void setGamma(int);
+ void updateFromSettings();
+
+signals:
+ void selectPrevious();
+ void selectNext();
+ void doubleClicked();
+ void requestContextMenu(const QPoint&);
+
+ // Emitted whenever an hint should be displayed
+ void requestHintDisplay(const QString& hint);
+
+ // Emitted whenever brightness, contrast or gamma changes
+ void bcgChanged();
+
+protected:
+ virtual void contentsDragEnterEvent(QDragEnterEvent*);
+ virtual void contentsDropEvent(QDropEvent*);
+ virtual void keyPressEvent(QKeyEvent*);
+
+private:
+ struct Private;
+ Private* d;
+
+ struct PendingPaint {
+ PendingPaint( bool s, const QRect& r ) : rect( r ), smooth( s ) {};
+ PendingPaint() {}; // stupid Qt containers
+ QRect rect;
+ bool smooth;
+ };
+ enum Operation { CHECK_OPERATIONS = 0, SMOOTH_PASS = 1 << 0 };
+
+ void addPendingPaint( bool smooth, QRect rect = QRect());
+ void addPendingPaintInternal( bool smooth, QRect rect = QRect());
+ void performPaint( QPainter* painter, int clipx, int clipy, int clipw, int cliph, bool smooth );
+ void limitPaintSize( PendingPaint& paint );
+ void fullRepaint();
+ void cancelPending();
+ void scheduleOperation( Operation operation );
+ void checkPendingOperationsInternal();
+ void updateBusyLevels();
+
+ void updateZoom(ZoomMode, double value=0, int centerX=-1, int centerY=-1);
+ double computeZoom(bool in) const;
+ double computeZoomToFit() const;
+ double computeZoomToWidth() const;
+ double computeZoomToHeight() const;
+
+ void updateImageOffset();
+ void updateScrollBarMode();
+ void updateContentSize();
+ void updateFullScreenLabel();
+ void updateZoomActions();
+ void selectTool(ButtonState, bool force);
+ void restartAutoHideTimer();
+
+ void emitRequestHintDisplay();
+
+ // Used by the scroll tool
+ void emitSelectPrevious() { emit selectPrevious(); }
+ void emitSelectNext() { emit selectNext(); }
+
+ // Used by the zoom tool
+ QPoint offset() const;
+ bool canZoom(bool in) const;
+ KToggleAction* zoomToFit() const;
+
+private slots:
+ void slotLoaded();
+ void slotModified();
+ void slotZoomIn();
+ void slotZoomOut();
+ void slotResetZoom();
+ void slotSelectZoom();
+ void setZoomToFit(bool);
+ void setZoomToWidth(bool);
+ void setZoomToHeight(bool);
+ void setLockZoom(bool);
+ void increaseGamma();
+ void decreaseGamma();
+ void increaseBrightness();
+ void decreaseBrightness();
+ void increaseContrast();
+ void decreaseContrast();
+ void slotImageSizeUpdated();
+ void slotImageRectUpdated(const QRect&);
+ void checkPendingOperations();
+ void loadingStarted();
+ void slotBusyLevelChanged(BusyLevel);
+ void showBCGDialog();
+
+protected:
+ // Overloaded methods
+ bool eventFilter(QObject*, QEvent*);
+ void viewportMousePressEvent(QMouseEvent*);
+ void viewportMouseMoveEvent(QMouseEvent*);
+ void viewportMouseReleaseEvent(QMouseEvent*);
+ bool viewportKeyEvent(QKeyEvent*); // This one is not inherited, it's called from the eventFilter
+ void wheelEvent(QWheelEvent* event);
+ void resizeEvent(QResizeEvent* event);
+ void drawContents(QPainter* p,int clipx,int clipy,int clipw,int cliph);
+};
+
+} // namespace
+#endif
+
diff --git a/src/gvcore/imageviewconfig.kcfg b/src/gvcore/imageviewconfig.kcfg
new file mode 100644
index 0000000..f13dfad
--- /dev/null
+++ b/src/gvcore/imageviewconfig.kcfg
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE kcfg SYSTEM "http://www.kde.org/standards/kcfg/1.0/kcfg.dtd">
+<kcfg>
+ <include>qapplication.h</include>
+ <include>qpalette.h</include>
+ <kcfgfile name="gwenviewrc"/>
+ <group name="pixmap widget">
+ <entry name="smoothAlgorithm" key="smooth scale" type="Enum">
+ <choices>
+ <choice name="None"/>
+ <choice name="Fast"/>
+ <choice name="Normal"/>
+ <choice name="Best"/>
+ </choices>
+ <default>None</default>
+ </entry>
+ <entry name="delayedSmoothing" key="delayed smoothing" type="Bool">
+ <default>false</default>
+ </entry>
+ <entry name="backgroundColor" key="background color" type="Color">
+ <default code="true">QApplication::palette().active().dark()</default>
+ </entry>
+ <entry name="enlargeSmallImages" key="enlarge small images" type="Bool">
+ <default>false</default>
+ </entry>
+ <entry name="showScrollBars" key="show scroll bars" type="Bool">
+ <default>true</default>
+ </entry>
+ <entry name="mouseWheelScroll" key="mouse wheel scrolls image" type="Bool">
+ <default>false</default>
+ </entry>
+ <entry name="zoomMode" type="Enum">
+ <choices>
+ <choice name="FitWindow"/>
+ <choice name="FitWidth"/>
+ <choice name="FitHeight"/>
+ <choice name="Free"/>
+ </choices>
+ <default>FitWindow</default>
+ </entry>
+ <entry name="lockZoom" key="lockZoom" type="Bool">
+ <default>false</default>
+ </entry>
+ <entry name="maxRepaintSize" key="max repaint size" type="Int">
+ <default>10000</default>
+ </entry>
+ <entry name="maxScaleRepaintSize" key="max scale repaint size" type="Int">
+ <default>10000</default>
+ </entry>
+ <entry name="maxSmoothRepaintSize" key="max smooth repaint size" type="Int">
+ <default>10000</default>
+ </entry>
+ </group>
+</kcfg>
diff --git a/src/gvcore/imageviewconfig.kcfgc b/src/gvcore/imageviewconfig.kcfgc
new file mode 100644
index 0000000..6336197
--- /dev/null
+++ b/src/gvcore/imageviewconfig.kcfgc
@@ -0,0 +1,7 @@
+File=imageviewconfig.kcfg
+ClassName=ImageViewConfig
+NameSpace=Gwenview
+Singleton=true
+Mutators=true
+IncludeFiles=gvcore/libgwenview_export.h
+Visibility=LIBGWENVIEW_EXPORT
diff --git a/src/gvcore/imageviewcontroller.cpp b/src/gvcore/imageviewcontroller.cpp
new file mode 100644
index 0000000..85a587a
--- /dev/null
+++ b/src/gvcore/imageviewcontroller.cpp
@@ -0,0 +1,527 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2006 Aurélien Gâteau
+
+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 <imageviewcontroller.moc>
+
+// Qt
+#include <qcursor.h>
+#include <qlayout.h>
+#include <qpopupmenu.h>
+#include <qvbox.h>
+#include <qwidgetstack.h>
+
+// KDE
+#include <kaction.h>
+#include <kapplication.h>
+#include <kdebug.h>
+#include <klocale.h>
+#include <kmediaplayer/player.h>
+#include <kmimetype.h>
+#include <ktoolbar.h>
+#include <kuserprofile.h>
+#include <kparts/componentfactory.h>
+#include <kxmlguibuilder.h>
+#include <kxmlguifactory.h>
+
+// Local
+#include <document.h>
+#include <externaltoolcontext.h>
+#include <externaltoolmanager.h>
+#include <fullscreenbar.h>
+#include <imageview.h>
+
+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
+
+
+/**
+ * A KXMLGUIBuilder which only creates containers for toolbars.
+ */
+class XMLGUIBuilder : public KXMLGUIBuilder {
+public:
+ XMLGUIBuilder(QWidget* parent) : KXMLGUIBuilder(parent) {}
+
+ virtual QWidget* createContainer(QWidget *parent, int index, const QDomElement &element, int &id) {
+ if (element.tagName().lower() == "toolbar") {
+ return KXMLGUIBuilder::createContainer(parent, index, element, id);
+ } else {
+ return 0;
+ }
+ }
+};
+
+
+const int AUTO_HIDE_TIMEOUT=4000;
+
+
+//------------------------------------------------------------------------
+//
+// ImageViewController::Private
+//
+//------------------------------------------------------------------------
+struct ImageViewController::Private {
+ ImageViewController* mImageViewController;
+
+ Document* mDocument;
+ KActionCollection* mActionCollection;
+ QWidget* mContainer;
+ KToolBar* mToolBar;
+ KXMLGUIFactory* mFactory;
+ XMLGUIBuilder* mBuilder;
+ QWidgetStack* mStack;
+
+ ImageView* mImageView;
+ KActionPtrList mImageViewActions;
+
+ // Hide cursor stuff
+ QTimer* mAutoHideTimer;
+ bool mCursorHidden;
+
+ KParts::ReadOnlyPart* mPlayerPart;
+
+ // Fullscreen stuff
+ bool mFullScreen;
+ FullScreenBar* mFullScreenBar;
+ KActionPtrList mFullScreenCommonActions;
+
+
+ void setXMLGUIClient(KXMLGUIClient* client) {
+ QPtrList<KXMLGUIClient> list=mFactory->clients();
+ KXMLGUIClient* oldClient=list.getFirst();
+ if (oldClient) {
+ mFactory->removeClient(oldClient);
+ // There should be at most one client, so the list should be empty
+ // now
+ Q_ASSERT(!mFactory->clients().getFirst());
+ }
+
+ // Unplug image view actions, if plugged
+ KActionPtrList::Iterator
+ it=mImageViewActions.begin(),
+ end=mImageViewActions.end();
+ for (; it!=end; ++it) {
+ KAction* action=*it;
+ if (action->isPlugged(mToolBar)) {
+ action->unplug(mToolBar);
+ }
+ }
+
+ if (client) {
+ mFactory->addClient(client);
+ }
+ }
+
+
+ void createPlayerPart(void) {
+ if (mPlayerPart) {
+ setXMLGUIClient(0);
+ delete mPlayerPart;
+ }
+ mPlayerPart=0;
+
+ QString mimeType=KMimeType::findByURL(mDocument->url())->name();
+ KService::Ptr service = KServiceTypeProfile::preferredService(mimeType, "KParts/ReadOnlyPart");
+ if (!service) {
+ kdWarning() << "Couldn't find a KPart for " << mimeType << endl;
+ return;
+ }
+
+ QString library=service->library();
+ Q_ASSERT(!library.isNull());
+ LOG("Library:" << library);
+ mPlayerPart = KParts::ComponentFactory::createPartInstanceFromService<KParts::ReadOnlyPart>(service, mStack, 0, mStack, 0);
+ if (!mPlayerPart) {
+ kdWarning() << "Failed to instantiate KPart from library " << library << endl;
+ return;
+ }
+ mStack->addWidget(mPlayerPart->widget());
+ setXMLGUIClient(mPlayerPart);
+ }
+
+
+ void showPlayerPart(void) {
+ LOG("");
+ createPlayerPart();
+ if (!mPlayerPart) return;
+ mStack->raiseWidget(mPlayerPart->widget());
+ mPlayerPart->openURL(mDocument->url());
+
+ // If the part implements the KMediaPlayer::Player interface, start
+ // playing (needed for Kaboodle)
+ KMediaPlayer::Player* player=dynamic_cast<KMediaPlayer::Player *>(mPlayerPart);
+ if (player) {
+ player->play();
+ }
+ }
+
+
+ void showImageView(void) {
+ LOG("");
+ if (mStack->visibleWidget()==mImageView) {
+ KAction* action=mImageViewActions.first();
+ if (action && !action->isPlugged(mToolBar)) {
+ // In the ctor, we set the imageview as the visible widget but
+ // we did not fill the toolbar because mImageViewActions was
+ // empty. In this case, fill the toolbar now.
+ plugImageViewActions();
+ }
+ return;
+ }
+
+ if (mPlayerPart) {
+ setXMLGUIClient(0);
+ delete mPlayerPart;
+ mPlayerPart=0;
+ }
+ plugImageViewActions();
+ mStack->raiseWidget(mImageView);
+ }
+
+ void plugImageViewActions() {
+ KActionPtrList::Iterator
+ it=mImageViewActions.begin(),
+ end=mImageViewActions.end();
+ for (; it!=end; ++it) {
+ KAction* action=*it;
+ action->plug(mToolBar);
+ }
+ }
+
+
+ void restartAutoHideTimer() {
+ mAutoHideTimer->start(AUTO_HIDE_TIMEOUT,true);
+ }
+
+
+ void updateFullScreenBarPosition() {
+ int mouseY=mStack->mapFromGlobal(QCursor::pos()).y();
+ bool visible = mFullScreenBar->y()==0;
+
+ if (visible && mouseY>mFullScreenBar->height()) {
+ mFullScreenBar->slideOut();
+ }
+
+ if (!visible && mouseY<2) {
+ mFullScreenBar->slideIn();
+ }
+ }
+
+
+ /**
+ * This function creates the fullscreen toolbar.
+ * NOTE: It should not be called from/merged with setFullScreenActions,
+ * otherwise the toolbar will have a one pixel border which will prevent
+ * reaching easily buttons by pushing the mouse to the top edge of the
+ * screen.
+ * My guess is that instanciating the toolbar *before* the main
+ * window is shown causes the main window to tweak its bars. This happens
+ * with KDE 3.5.1.
+ */
+ void initFullScreenBar() {
+ Q_ASSERT(!mFullScreenBar);
+ mFullScreenBar=new FullScreenBar(mContainer);
+
+ KActionPtrList::ConstIterator
+ it=mFullScreenCommonActions.begin(),
+ end=mFullScreenCommonActions.end();
+
+ for (; it!=end; ++it) {
+ (*it)->plug(mFullScreenBar);
+ }
+ }
+};
+
+
+//------------------------------------------------------------------------
+//
+// ImageViewController
+//
+//------------------------------------------------------------------------
+
+
+ImageViewController::ImageViewController(QWidget* parent, Document* document, KActionCollection* actionCollection)
+: QObject(parent) {
+ d=new Private;
+ d->mImageViewController=this;
+ d->mDocument=document;
+ d->mActionCollection=actionCollection;
+ d->mAutoHideTimer=new QTimer(this);
+ d->mCursorHidden=false;
+
+ d->mContainer=new QWidget(parent);
+ d->mContainer->setMinimumWidth(1); // Make sure we can resize the toolbar smaller than its minimum size
+ QVBoxLayout* layout=new QVBoxLayout(d->mContainer);
+ d->mToolBar=new KToolBar(d->mContainer, "", true);
+
+ layout->add(d->mToolBar);
+ d->mStack=new QWidgetStack(d->mContainer);
+ layout->add(d->mStack);
+
+ d->mImageView=new ImageView(d->mStack, document, actionCollection);
+ d->mStack->addWidget(d->mImageView);
+
+ KApplication::kApplication()->installEventFilter(this);
+
+ d->mPlayerPart=0;
+ d->mBuilder=new XMLGUIBuilder(d->mToolBar);
+ d->mFactory=new KXMLGUIFactory(d->mBuilder, this);
+
+ d->mFullScreen=false;
+ d->mFullScreenBar=0;
+
+ connect(d->mDocument,SIGNAL(loaded(const KURL&)),
+ this,SLOT(slotLoaded()) );
+
+ connect(d->mImageView, SIGNAL(requestContextMenu(const QPoint&)),
+ this, SLOT(openImageViewContextMenu(const QPoint&)) );
+
+ connect(d->mImageView, SIGNAL(requestHintDisplay(const QString&)),
+ this, SIGNAL(requestHintDisplay(const QString&)) );
+
+ connect(d->mAutoHideTimer,SIGNAL(timeout()),
+ this,SLOT(slotAutoHide()) );
+
+ // Forward Image view signals
+ connect(d->mImageView, SIGNAL(selectPrevious()), SIGNAL(selectPrevious()) );
+ connect(d->mImageView, SIGNAL(selectNext()), SIGNAL(selectNext()) );
+ connect(d->mImageView, SIGNAL(doubleClicked()), SIGNAL(doubleClicked()) );
+}
+
+
+ImageViewController::~ImageViewController() {
+ delete d->mBuilder;
+ delete d;
+}
+
+
+void ImageViewController::setFocus() {
+ QWidget* view;
+ if (d->mPlayerPart) {
+ view = d->mPlayerPart->widget();
+ } else {
+ view = d->mImageView;
+ }
+ view->setFocus();
+}
+
+
+void ImageViewController::slotLoaded() {
+ LOG("");
+ if (d->mDocument->urlKind()==MimeTypeUtils::KIND_FILE) {
+ d->showPlayerPart();
+ } else {
+ d->showImageView();
+ }
+}
+
+
+void ImageViewController::setFullScreen(bool fullScreen) {
+ d->mFullScreen=fullScreen;
+ d->mImageView->setFullScreen(fullScreen);
+
+ if (d->mFullScreen) {
+ d->restartAutoHideTimer();
+ if (!d->mFullScreenBar) {
+ d->initFullScreenBar();
+ }
+ } else {
+ d->mAutoHideTimer->stop();
+ QApplication::restoreOverrideCursor();
+ d->mCursorHidden=false;
+ }
+
+ d->mToolBar->setHidden(d->mFullScreen);
+ if (d->mFullScreenBar) {
+ d->mFullScreenBar->setHidden(!d->mFullScreen);
+ }
+}
+
+
+void ImageViewController::setNormalCommonActions(const KActionPtrList& actions) {
+ KActionPtrList::ConstIterator
+ it=actions.begin(),
+ end=actions.end();
+
+ for (; it!=end; ++it) {
+ (*it)->plug(d->mToolBar);
+ }
+ d->mToolBar->insertLineSeparator();
+}
+
+
+void ImageViewController::setFullScreenCommonActions(const KActionPtrList& actions) {
+ d->mFullScreenCommonActions=actions;
+}
+
+
+void ImageViewController::setImageViewActions(const KActionPtrList& actions) {
+ d->mImageViewActions=actions;
+}
+
+
+void ImageViewController::slotAutoHide() {
+ if (d->mFullScreenBar) {
+ // Do not auto hide if the cursor is over the bar
+ QPoint pos=d->mFullScreenBar->mapFromGlobal(QCursor::pos());
+ if (d->mFullScreenBar->rect().contains(pos)) {
+ d->restartAutoHideTimer();
+ return;
+ }
+ }
+
+ // Only hide cursor if we are not over a dialog
+ QWidget* widget = KApplication::kApplication()->activeWindow();
+ if (!widget || !widget->inherits("QDialog")) {
+ QApplication::setOverrideCursor(blankCursor);
+ d->mCursorHidden=true;
+ }
+}
+
+
+QWidget* ImageViewController::widget() const {
+ return d->mContainer;
+}
+
+
+void ImageViewController::updateFromSettings() {
+ d->mImageView->updateFromSettings();
+}
+
+
+/**
+ * This event filter monitors mouse moves and make sure the position of the
+ * fullscreen bar is updated.
+ */
+bool ImageViewController::eventFilter(QObject* object, QEvent* event) {
+ if (!d->mFullScreen) return false;
+ if (!event->type()==QEvent::MouseMove) return false;
+
+ // Check we must filter this object. This is an application filter, so we
+ // have to check we are not dealing with another object.
+ bool isAChildOfStack=false;
+ QObject* parentObject;
+ for (parentObject=object->parent(); parentObject; parentObject=parentObject->parent()) {
+ if (parentObject==d->mStack) {
+ isAChildOfStack=true;
+ break;
+ }
+ }
+ if (!isAChildOfStack) return false;
+
+ d->updateFullScreenBarPosition();
+
+ if (event->type()==QEvent::MouseMove) {
+ d->mCursorHidden=false;
+ d->restartAutoHideTimer();
+ }
+
+ if (d->mCursorHidden) {
+ QApplication::setOverrideCursor(blankCursor,true);
+ } else {
+ QApplication::restoreOverrideCursor();
+ }
+
+ return false;
+}
+
+
+/**
+ * Little helper to plug an action if it exists
+ */
+inline void plugAction(QPopupMenu* menu, KActionCollection* actionCollection, const char* actionName) {
+ KAction* action=actionCollection->action(actionName);
+ if (action) action->plug(menu);
+}
+
+
+void ImageViewController::openImageViewContextMenu(const QPoint& pos) {
+ QPopupMenu menu(d->mImageView);
+ bool noImage=d->mDocument->filename().isEmpty();
+ bool validImage=!d->mDocument->isNull();
+
+ // The fullscreen item is always there, to be able to leave fullscreen mode
+ // if necessary. But KParts may not have the action itself.
+ plugAction(&menu, d->mActionCollection, "fullscreen");
+
+ plugAction(&menu, d->mActionCollection, "slideshow");
+
+ if (validImage) {
+ menu.insertSeparator();
+
+ plugAction(&menu, d->mActionCollection, "view_zoom_to_fit");
+ plugAction(&menu, d->mActionCollection, "view_zoom_in");
+ plugAction(&menu, d->mActionCollection, "view_zoom_out");
+ plugAction(&menu, d->mActionCollection, "view_actual_size");
+ plugAction(&menu, d->mActionCollection, "view_zoom_lock");
+ }
+
+ menu.insertSeparator();
+
+ plugAction(&menu, d->mActionCollection, "first");
+ plugAction(&menu, d->mActionCollection, "previous");
+ plugAction(&menu, d->mActionCollection, "next");
+ plugAction(&menu, d->mActionCollection, "last");
+
+ if (validImage) {
+ menu.insertSeparator();
+
+ QPopupMenu* editMenu=new QPopupMenu(&menu);
+ plugAction(editMenu, d->mActionCollection, "rotate_left");
+ plugAction(editMenu, d->mActionCollection, "rotate_right");
+ plugAction(editMenu, d->mActionCollection, "mirror");
+ plugAction(editMenu, d->mActionCollection, "flip");
+ plugAction(editMenu, d->mActionCollection, "adjust_bcg");
+ menu.insertItem( i18n("Edit"), editMenu );
+
+ ExternalToolContext* externalToolContext=
+ ExternalToolManager::instance()->createContext(
+ this, d->mDocument->url());
+
+ menu.insertItem(
+ i18n("External Tools"), externalToolContext->popupMenu());
+ }
+
+ if (!noImage) {
+ menu.insertSeparator();
+
+ plugAction(&menu, d->mActionCollection, "file_rename");
+ plugAction(&menu, d->mActionCollection, "file_copy");
+ plugAction(&menu, d->mActionCollection, "file_move");
+ plugAction(&menu, d->mActionCollection, "file_link");
+ plugAction(&menu, d->mActionCollection, "file_delete");
+
+ menu.insertSeparator();
+
+ plugAction(&menu, d->mActionCollection, "file_properties");
+ }
+
+ menu.exec(pos);
+}
+
+
+} // namespace
diff --git a/src/gvcore/imageviewcontroller.h b/src/gvcore/imageviewcontroller.h
new file mode 100644
index 0000000..7ccb3be
--- /dev/null
+++ b/src/gvcore/imageviewcontroller.h
@@ -0,0 +1,84 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2006 Aurélien Gâteau
+
+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.
+
+*/
+#ifndef IMAGEVIEWCONTROLLER_H
+#define IMAGEVIEWCONTROLLER_H
+
+// Qt
+#include <qobject.h>
+
+// KDE
+#include <kaction.h>
+
+// Local
+#include "libgwenview_export.h"
+
+class QPoint;
+class QWidget;
+
+class KToolBar;
+
+namespace Gwenview {
+
+
+class Document;
+class ImageView;
+
+
+class LIBGWENVIEW_EXPORT ImageViewController : public QObject {
+Q_OBJECT
+public:
+ ImageViewController(QWidget* parent, Document*, KActionCollection*);
+ ~ImageViewController();
+
+ QWidget* widget() const;
+
+ void setImageViewActions(const KActionPtrList&);
+
+ void setFullScreen(bool);
+ void setNormalCommonActions(const KActionPtrList&);
+ void setFullScreenCommonActions(const KActionPtrList&);
+ void setFocus();
+
+protected:
+ virtual bool eventFilter(QObject*, QEvent*);
+
+public slots:
+ void updateFromSettings();
+
+signals:
+ void requestHintDisplay(const QString&);
+ void selectPrevious();
+ void selectNext();
+ void doubleClicked();
+
+private slots:
+ void slotLoaded();
+ void openImageViewContextMenu(const QPoint&);
+ void slotAutoHide();
+
+private:
+ struct Private;
+ Private* d;
+};
+
+} // namespace
+
+#endif /* IMAGEVIEWCONTROLLER_H */
diff --git a/src/gvcore/imageviewtools.cpp b/src/gvcore/imageviewtools.cpp
new file mode 100644
index 0000000..06a483b
--- /dev/null
+++ b/src/gvcore/imageviewtools.cpp
@@ -0,0 +1,213 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2000-2004 Aurélien Gâteau
+
+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.
+
+*/
+// Our header
+#include "imageviewtools.h"
+
+// KDE
+#include <kaction.h>
+#include <kdebug.h>
+#include <klocale.h>
+#include <kstandarddirs.h>
+
+// Local
+#include "imageviewconfig.h"
+
+namespace Gwenview {
+
+
+// Helper function
+static QCursor loadCursor(const QString& name) {
+ QString path;
+ path=locate("data", QString("gwenview/cursors/%1.png").arg(name));
+ return QCursor(QPixmap(path));
+}
+
+
+//----------------------------------------------------------------------------
+//
+// ImageView::ToolBase
+//
+//----------------------------------------------------------------------------
+ImageView::ToolBase::ToolBase(ImageView* view)
+: mView(view) {}
+
+
+ImageView::ToolBase::~ToolBase() {}
+
+void ImageView::ToolBase::mouseMoveEvent(QMouseEvent*) {}
+void ImageView::ToolBase::leftButtonPressEvent(QMouseEvent*) {}
+void ImageView::ToolBase::leftButtonReleaseEvent(QMouseEvent*) {}
+
+void ImageView::ToolBase::midButtonReleaseEvent(QMouseEvent*) {
+ mView->zoomToFit()->activate();
+}
+
+void ImageView::ToolBase::rightButtonPressEvent(QMouseEvent* event) {
+ emit mView->requestContextMenu(event->globalPos());
+}
+
+void ImageView::ToolBase::rightButtonReleaseEvent(QMouseEvent*) {
+}
+
+void ImageView::ToolBase::wheelEvent(QWheelEvent* event) {
+ event->accept();
+}
+
+void ImageView::ToolBase::updateCursor() {
+ mView->viewport()->setCursor(ArrowCursor);
+}
+
+
+//----------------------------------------------------------------------------
+//
+// ImageView::ZoomTool
+//
+//----------------------------------------------------------------------------
+ImageView::ZoomTool::ZoomTool(ImageView* view)
+: ImageView::ToolBase(view) {
+ mZoomCursor=loadCursor("zoom");
+}
+
+
+void ImageView::ZoomTool::zoomTo(const QPoint& pos, bool in) {
+ if (!mView->canZoom(in)) return;
+
+ QPoint centerPos=QPoint(mView->visibleWidth(), mView->visibleHeight())/2;
+ // Compute image position
+ QPoint imgPos=mView->viewportToContents(pos) - mView->offset();
+ double newZoom=mView->computeZoom(in);
+
+ imgPos*=newZoom/mView->zoom();
+ imgPos=imgPos-pos+centerPos;
+ mView->setZoom(newZoom, imgPos.x(), imgPos.y());
+}
+
+
+void ImageView::ZoomTool::leftButtonReleaseEvent(QMouseEvent* event) {
+ zoomTo(event->pos(), true);
+}
+
+
+void ImageView::ZoomTool::wheelEvent(QWheelEvent* event) {
+ zoomTo(event->pos(), event->delta()>0);
+ event->accept();
+}
+
+
+void ImageView::ZoomTool::rightButtonPressEvent(QMouseEvent*) {
+}
+
+
+void ImageView::ZoomTool::rightButtonReleaseEvent(QMouseEvent* event) {
+ zoomTo(event->pos(), false);
+}
+
+
+void ImageView::ZoomTool::updateCursor() {
+ mView->viewport()->setCursor(mZoomCursor);
+}
+
+
+QString ImageView::ZoomTool::hint() const {
+ return i18n("Left click to zoom in, right click to zoom out. You can also use the mouse wheel.");
+}
+
+
+//----------------------------------------------------------------------------
+//
+// ImageView::ScrollTool
+//
+//----------------------------------------------------------------------------
+ImageView::ScrollTool::ScrollTool(ImageView* view)
+: ImageView::ToolBase(view)
+, mScrollStartX(0), mScrollStartY(0)
+, mDragStarted(false) {
+}
+
+
+void ImageView::ScrollTool::leftButtonPressEvent(QMouseEvent* event) {
+ mScrollStartX=event->x();
+ mScrollStartY=event->y();
+ mView->viewport()->setCursor(SizeAllCursor);
+ mDragStarted=true;
+}
+
+
+void ImageView::ScrollTool::mouseMoveEvent(QMouseEvent* event) {
+ if (!mDragStarted) return;
+
+ int deltaX,deltaY;
+
+ deltaX=mScrollStartX - event->x();
+ deltaY=mScrollStartY - event->y();
+
+ mScrollStartX=event->x();
+ mScrollStartY=event->y();
+ mView->scrollBy(deltaX,deltaY);
+}
+
+
+void ImageView::ScrollTool::leftButtonReleaseEvent(QMouseEvent*) {
+ if (!mDragStarted) return;
+
+ mDragStarted=false;
+ mView->viewport()->setCursor(ArrowCursor);
+}
+
+
+void ImageView::ScrollTool::wheelEvent(QWheelEvent* event) {
+ if (ImageViewConfig::mouseWheelScroll()) {
+ int deltaX, deltaY;
+
+ if (event->state() & AltButton || event->orientation()==Horizontal) {
+ deltaX = event->delta();
+ deltaY = 0;
+ } else {
+ deltaX = 0;
+ deltaY = event->delta();
+ }
+ mView->scrollBy(-deltaX, -deltaY);
+ } else {
+ if (event->delta()<0) {
+ mView->emitSelectNext();
+ } else {
+ mView->emitSelectPrevious();
+ }
+ }
+ event->accept();
+}
+
+
+void ImageView::ScrollTool::updateCursor() {
+ if (mDragStarted) {
+ mView->viewport()->setCursor(SizeAllCursor);
+ } else {
+ mView->viewport()->setCursor(ArrowCursor);
+ }
+}
+
+
+QString ImageView::ScrollTool::hint() const {
+ return i18n("Drag to move the image, middle-click to toggle auto-zoom. Hold the Control key to switch to the zoom tool.");
+}
+
+
+} // namespace
diff --git a/src/gvcore/imageviewtools.h b/src/gvcore/imageviewtools.h
new file mode 100644
index 0000000..59ce3e2
--- /dev/null
+++ b/src/gvcore/imageviewtools.h
@@ -0,0 +1,96 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2000-2004 Aur�ien G�eau
+
+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.
+
+*/
+#ifndef IMAGEVIEWTOOLS_H
+#define IMAGEVIEWTOOLS_H
+
+// Qt
+#include <qcursor.h>
+
+// Local
+#include "imageview.h"
+namespace Gwenview {
+
+
+class ImageView::ToolBase {
+protected:
+ ImageView* mView;
+
+public:
+ ToolBase(ImageView* view);
+ virtual ~ToolBase();
+ virtual void mouseMoveEvent(QMouseEvent*);
+
+ virtual void leftButtonPressEvent(QMouseEvent*);
+ virtual void leftButtonReleaseEvent(QMouseEvent*);
+
+ virtual void midButtonReleaseEvent(QMouseEvent*);
+
+ virtual void rightButtonPressEvent(QMouseEvent* event);
+ virtual void rightButtonReleaseEvent(QMouseEvent*);
+
+ virtual void wheelEvent(QWheelEvent* event);
+
+ virtual void updateCursor();
+
+ /**
+ * Return a hint about the use of the tool
+ */
+ virtual QString hint() const=0;
+};
+
+
+class ImageView::ZoomTool : public ImageView::ToolBase {
+private:
+ QCursor mZoomCursor;
+ void zoomTo(const QPoint& pos, bool in);
+
+public:
+ ZoomTool(ImageView* view);
+ void leftButtonReleaseEvent(QMouseEvent* event);
+
+ void wheelEvent(QWheelEvent* event);
+ void rightButtonPressEvent(QMouseEvent*);
+ void rightButtonReleaseEvent(QMouseEvent* event);
+
+ void updateCursor();
+ virtual QString hint() const;
+};
+
+
+class ImageView::ScrollTool : public ImageView::ToolBase {
+ int mScrollStartX,mScrollStartY;
+ bool mDragStarted;
+
+public:
+ ScrollTool(ImageView* view);
+ void leftButtonPressEvent(QMouseEvent* event);
+ void mouseMoveEvent(QMouseEvent* event);
+ void leftButtonReleaseEvent(QMouseEvent*);
+ void wheelEvent(QWheelEvent* event);
+
+ void updateCursor();
+ virtual QString hint() const;
+};
+
+
+} // namespace
+#endif /* IMAGEVIEWTOOLS_H */
+
diff --git a/src/gvcore/inputdialog.cpp b/src/gvcore/inputdialog.cpp
new file mode 100644
index 0000000..3da70e9
--- /dev/null
+++ b/src/gvcore/inputdialog.cpp
@@ -0,0 +1,78 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2006 Aurélien Gâteau
+
+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.
+
+*/
+// Self
+#include "inputdialog.moc"
+
+// Qt
+#include <qlabel.h>
+#include <qvbox.h>
+
+// KDE
+#include <klineedit.h>
+
+namespace Gwenview {
+
+struct InputDialog::Private {
+ KLineEdit* mLineEdit;
+ QLabel* mLabel;
+};
+
+
+InputDialog::InputDialog(QWidget* parent)
+: KDialogBase(parent, "InputDialog", true, QString::null,
+ KDialogBase::Ok|KDialogBase::Cancel)
+{
+ d = new Private;
+ QVBox* page = makeVBoxMainWidget();
+ d->mLabel = new QLabel(page);
+
+ d->mLineEdit = new KLineEdit(page);
+ d->mLineEdit->setFocus();
+
+ setMinimumWidth(350);
+
+ connect(d->mLineEdit, SIGNAL(textChanged(const QString&)),
+ this, SLOT(updateButtons()) );
+}
+
+
+InputDialog::~InputDialog() {
+ delete d;
+}
+
+
+void InputDialog::setLabel(const QString& label) {
+ d->mLabel->setText(label);
+}
+
+
+KLineEdit* InputDialog::lineEdit() const {
+ return d->mLineEdit;
+}
+
+
+void InputDialog::updateButtons() {
+ enableButtonOK( ! d->mLineEdit->text().isEmpty() );
+}
+
+
+} // namespace
+
diff --git a/src/gvcore/inputdialog.h b/src/gvcore/inputdialog.h
new file mode 100644
index 0000000..a849eb5
--- /dev/null
+++ b/src/gvcore/inputdialog.h
@@ -0,0 +1,55 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2006 Aurélien Gâteau
+
+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.
+
+*/
+#ifndef INPUTDIALOG_H
+#define INPUTDIALOG_H
+
+// KDE
+#include <kdialogbase.h>
+
+
+class KLineEdit;
+
+
+namespace Gwenview {
+
+
+/**
+ * An input dialog which give access to its line edit
+ */
+class InputDialog : public KDialogBase {
+Q_OBJECT
+public:
+ InputDialog(QWidget* parent);
+ ~InputDialog();
+ void setLabel(const QString& label);
+ KLineEdit* lineEdit() const;
+
+private slots:
+ void updateButtons();
+
+private:
+ struct Private;
+ Private* d;
+};
+
+} // namespace
+
+#endif /* INPUTDIALOG_H */
diff --git a/src/gvcore/jpegformattype.cpp b/src/gvcore/jpegformattype.cpp
new file mode 100644
index 0000000..ccb020e
--- /dev/null
+++ b/src/gvcore/jpegformattype.cpp
@@ -0,0 +1,527 @@
+/* This file is based on kdelibs-3.2.0/khtml/misc/loader_jpeg.cpp. Original
+ * copyright follows.
+ */
+/*
+ This file is part of the KDE libraries
+
+ Copyright (C) 2000 Dirk Mueller (mueller@kde.org)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+*/
+
+
+// System
+#include <stdio.h>
+#include <setjmp.h>
+extern "C" {
+#define XMD_H
+#include <jpeglib.h>
+#undef const
+}
+
+// Qt
+#include <qdatetime.h>
+
+// KDE
+#include <kdebug.h>
+#include <kglobal.h>
+
+// Local
+#include "jpegformattype.h"
+#include "imageutils/jpegerrormanager.h"
+
+namespace Gwenview {
+
+#undef BUFFER_DEBUG
+//#define BUFFER_DEBUG
+
+#undef JPEG_DEBUG
+//#define JPEG_DEBUG
+
+
+static const int MAX_BUFFER = 32768;
+// how long it will consume data before starting outputing progressive scan
+static const int MAX_CONSUMING_TIME = 100;
+
+//-----------------------------------------------------------------------------
+//
+// JPEGSourceManager
+// (Does not follow HACKING file recommandation to be consistent with
+// jpeg_source_mgr naming)
+//
+//-----------------------------------------------------------------------------
+struct JPEGSourceManager : public jpeg_source_mgr {
+ JOCTET jpeg_buffer[MAX_BUFFER];
+
+ int valid_buffer_length;
+ size_t skip_input_bytes;
+ bool at_eof;
+ QRect change_rect;
+ QRect old_change_rect;
+ QTime decoder_timestamp;
+ bool final_pass;
+ bool decoding_done;
+ bool do_progressive;
+
+ JPEGSourceManager() {
+ // jpeg_source_mgr fields
+ init_source = gvJPEGDummyDecompress;
+ fill_input_buffer = gvFillInputBuffer;
+ skip_input_data = gvSkipInputData;
+ resync_to_restart = jpeg_resync_to_restart;
+ term_source = gvJPEGDummyDecompress;
+
+ bytes_in_buffer = 0;
+ next_input_byte = jpeg_buffer;
+
+ // JPEGSourceManager fields
+ valid_buffer_length = 0;
+ skip_input_bytes = 0;
+ at_eof = 0;
+ final_pass = false;
+ decoding_done = false;
+ }
+
+ static void gvJPEGDummyDecompress(j_decompress_ptr) {}
+
+ /* Do not replace boolean with bool, it's the libjpeg boolean type */
+ static boolean gvFillInputBuffer(j_decompress_ptr cinfo) {
+#ifdef BUFFER_DEBUG
+ qDebug("FillInputBuffer called!");
+#endif
+
+ JPEGSourceManager* src = (JPEGSourceManager*)cinfo->src;
+
+ if ( src->at_eof ) {
+ /* Insert a fake EOI marker - as per jpeglib recommendation */
+ src->jpeg_buffer[0] = (JOCTET) 0xFF;
+ src->jpeg_buffer[1] = (JOCTET) JPEG_EOI;
+ src->bytes_in_buffer = 2;
+ src->next_input_byte = (JOCTET *) src->jpeg_buffer;
+#ifdef BUFFER_DEBUG
+ qDebug("...returning true!");
+#endif
+ return true;
+ } else {
+ return false; /* I/O suspension mode */
+ }
+ }
+
+ static void gvSkipInputData(j_decompress_ptr cinfo, long num_bytes) {
+ if(num_bytes <= 0) return; /* required noop */
+
+#ifdef BUFFER_DEBUG
+ qDebug("SkipInputData (%d) called!", num_bytes);
+#endif
+
+ JPEGSourceManager* src = (JPEGSourceManager*)cinfo->src;
+ src->skip_input_bytes += num_bytes;
+
+ unsigned int skipbytes = kMin(src->bytes_in_buffer, src->skip_input_bytes);
+
+#ifdef BUFFER_DEBUG
+ qDebug("skip_input_bytes is now %d", src->skip_input_bytes);
+ qDebug("skipbytes is now %d", skipbytes);
+ qDebug("valid_buffer_length is before %d", src->valid_buffer_length);
+ qDebug("bytes_in_buffer is %d", src->bytes_in_buffer);
+#endif
+
+ if(skipbytes < src->bytes_in_buffer) {
+ memmove(src->jpeg_buffer,
+ src->next_input_byte+skipbytes,
+ src->bytes_in_buffer - skipbytes);
+ }
+
+ src->bytes_in_buffer -= skipbytes;
+ src->valid_buffer_length = src->bytes_in_buffer;
+ src->skip_input_bytes -= skipbytes;
+
+ /* adjust data for jpeglib */
+ cinfo->src->next_input_byte = (JOCTET *) src->jpeg_buffer;
+ cinfo->src->bytes_in_buffer = (size_t) src->valid_buffer_length;
+#ifdef BUFFER_DEBUG
+ qDebug("valid_buffer_length is afterwards %d", src->valid_buffer_length);
+ qDebug("skip_input_bytes is now %d", src->skip_input_bytes);
+#endif
+
+ }
+};
+
+
+//-----------------------------------------------------------------------------
+//
+// JPEGFormat
+//
+//-----------------------------------------------------------------------------
+class JPEGFormat : public QImageFormat {
+public:
+ JPEGFormat();
+
+ virtual ~JPEGFormat();
+
+ virtual int decode(QImage& img, QImageConsumer* consumer,
+ const uchar* buffer, int length);
+private:
+
+ enum {
+ INIT,
+ START_DECOMPRESS,
+ DECOMPRESS_STARTED,
+ CONSUME_INPUT,
+ PREPARE_OUTPUT_SCAN,
+ DO_OUTPUT_SCAN,
+ READ_DONE,
+ INVALID
+ } mState;
+
+ // structs for the jpeglib
+ jpeg_decompress_struct mDecompress;
+ ImageUtils::JPEGErrorManager mErrorManager;
+ JPEGSourceManager mSourceManager;
+};
+
+
+JPEGFormat::JPEGFormat() {
+ memset(&mDecompress, 0, sizeof(mDecompress));
+ mDecompress.err = &mErrorManager;
+ jpeg_create_decompress(&mDecompress);
+ mDecompress.src = &mSourceManager;
+ mState = INIT;
+}
+
+
+JPEGFormat::~JPEGFormat() {
+ (void) jpeg_destroy_decompress(&mDecompress);
+}
+
+/*
+ * return > 0 means "consumed x bytes, need more"
+ * return == 0 means "end of frame reached"
+ * return < 0 means "fatal error in image decoding, don't call me ever again"
+ */
+
+int JPEGFormat::decode(QImage& image, QImageConsumer* consumer, const uchar* buffer, int length) {
+#ifdef JPEG_DEBUG
+ qDebug("JPEGFormat::decode(%p, %p, %p, %d)",
+ &image, consumer, buffer, length);
+#endif
+
+ if(mSourceManager.at_eof) {
+#ifdef JPEG_DEBUG
+ qDebug("at_eof, eating");
+#endif
+ return length;
+ }
+
+ if(setjmp(mErrorManager.jmp_buffer)) {
+#ifdef JPEG_DEBUG
+ qDebug("jump into state INVALID");
+#endif
+ if(consumer) consumer->end();
+
+ // this is fatal
+ return -1;
+ }
+
+ int consumed = kMin(length, MAX_BUFFER - mSourceManager.valid_buffer_length);
+
+#ifdef BUFFER_DEBUG
+ qDebug("consuming %d bytes", consumed);
+#endif
+
+ // filling buffer with the new data
+ memcpy(mSourceManager.jpeg_buffer + mSourceManager.valid_buffer_length, buffer, consumed);
+ mSourceManager.valid_buffer_length += consumed;
+
+ if(mSourceManager.skip_input_bytes) {
+#ifdef BUFFER_DEBUG
+ qDebug("doing skipping");
+ qDebug("valid_buffer_length %d", mSourceManager.valid_buffer_length);
+ qDebug("skip_input_bytes %d", mSourceManager.skip_input_bytes);
+#endif
+ int skipbytes = kMin((size_t) mSourceManager.valid_buffer_length, mSourceManager.skip_input_bytes);
+
+ if(skipbytes < mSourceManager.valid_buffer_length) {
+ memmove(mSourceManager.jpeg_buffer,
+ mSourceManager.jpeg_buffer+skipbytes,
+ mSourceManager.valid_buffer_length - skipbytes);
+ }
+
+ mSourceManager.valid_buffer_length -= skipbytes;
+ mSourceManager.skip_input_bytes -= skipbytes;
+
+ // still more bytes to skip
+ if(mSourceManager.skip_input_bytes) {
+ if(consumed <= 0) qDebug("ERROR!!!");
+ return consumed;
+ }
+
+ }
+
+ mDecompress.src->next_input_byte = (JOCTET *) mSourceManager.jpeg_buffer;
+ mDecompress.src->bytes_in_buffer = (size_t) mSourceManager.valid_buffer_length;
+
+#ifdef BUFFER_DEBUG
+ qDebug("buffer contains %d bytes", mSourceManager.valid_buffer_length);
+#endif
+
+ if(mState == INIT) {
+ if(jpeg_read_header(&mDecompress, true) != JPEG_SUSPENDED) {
+ if (consumer) {
+ consumer->setSize(
+ mDecompress.image_width/mDecompress.scale_denom,
+ mDecompress.image_height/mDecompress.scale_denom);
+ }
+
+ mState = START_DECOMPRESS;
+ }
+ }
+
+ if(mState == START_DECOMPRESS) {
+ mSourceManager.do_progressive = jpeg_has_multiple_scans( &mDecompress );
+
+#ifdef JPEG_DEBUG
+ qDebug( "**** DOPROGRESSIVE: %d", mSourceManager.do_progressive );
+#endif
+ mDecompress.buffered_image = mSourceManager.do_progressive;
+
+ // setup image sizes
+ jpeg_calc_output_dimensions( &mDecompress );
+
+ if (mDecompress.jpeg_color_space == JCS_YCbCr) {
+ mDecompress.out_color_space = JCS_RGB;
+ }
+
+ mDecompress.do_fancy_upsampling = true;
+ mDecompress.do_block_smoothing = false;
+ mDecompress.quantize_colors = false;
+
+ // false: IO suspension
+ if(jpeg_start_decompress(&mDecompress)) {
+ if ( mDecompress.output_components == 3 || mDecompress.output_components == 4) {
+ image.create( mDecompress.output_width, mDecompress.output_height, 32 );
+ } else if ( mDecompress.output_components == 1 ) {
+ image.create( mDecompress.output_width, mDecompress.output_height, 8, 256 );
+ for (int i=0; i<256; i++) {
+ image.setColor(i, qRgb(i,i,i));
+ }
+ }
+
+#ifdef JPEG_DEBUG
+ qDebug("will create a picture %d/%d in size", mDecompress.output_width, mDecompress.output_height);
+#endif
+
+#ifdef JPEG_DEBUG
+ qDebug("ok, going to DECOMPRESS_STARTED");
+#endif
+
+ mSourceManager.decoder_timestamp.start();
+ mState = mSourceManager.do_progressive ? DECOMPRESS_STARTED : DO_OUTPUT_SCAN;
+ }
+ }
+
+again:
+
+ if(mState == DECOMPRESS_STARTED) {
+ mState = (!mSourceManager.final_pass && mSourceManager.decoder_timestamp.elapsed() < MAX_CONSUMING_TIME)
+ ? CONSUME_INPUT : PREPARE_OUTPUT_SCAN;
+ }
+
+ if(mState == CONSUME_INPUT) {
+ int retval;
+
+ do {
+ retval = jpeg_consume_input(&mDecompress);
+ } while (retval != JPEG_SUSPENDED && retval != JPEG_REACHED_EOI
+ && (retval != JPEG_REACHED_SOS || mSourceManager.decoder_timestamp.elapsed() < MAX_CONSUMING_TIME));
+
+ if( mSourceManager.final_pass
+ || mSourceManager.decoder_timestamp.elapsed() >= MAX_CONSUMING_TIME
+ || retval == JPEG_REACHED_EOI
+ || retval == JPEG_REACHED_SOS) {
+ mState = PREPARE_OUTPUT_SCAN;
+ }
+ }
+
+ if(mState == PREPARE_OUTPUT_SCAN) {
+ if ( jpeg_start_output(&mDecompress, mDecompress.input_scan_number) ) {
+ mState = DO_OUTPUT_SCAN;
+ }
+ }
+
+ if(mState == DO_OUTPUT_SCAN) {
+ if(image.isNull() || mSourceManager.decoding_done) {
+#ifdef JPEG_DEBUG
+ qDebug("complete in doOutputscan, eating..");
+#endif
+ return consumed;
+ }
+ uchar** lines = image.jumpTable();
+ int oldoutput_scanline = mDecompress.output_scanline;
+
+ while(mDecompress.output_scanline < mDecompress.output_height &&
+ jpeg_read_scanlines(&mDecompress, lines+mDecompress.output_scanline, mDecompress.output_height))
+ ; // here happens all the magic of decoding
+
+ int completed_scanlines = mDecompress.output_scanline - oldoutput_scanline;
+#ifdef JPEG_DEBUG
+ qDebug("completed now %d scanlines", completed_scanlines);
+#endif
+
+ if ( mDecompress.output_components == 3 ) {
+ // Expand 24->32 bpp.
+ for (int j=oldoutput_scanline; j<oldoutput_scanline+completed_scanlines; j++) {
+ uchar *in = image.scanLine(j) + mDecompress.output_width * 3;
+ QRgb *out = (QRgb*)image.scanLine(j);
+
+ for (uint i=mDecompress.output_width; i--; ) {
+ in-=3;
+ out[i] = qRgb(in[0], in[1], in[2]);
+ }
+ }
+ }
+
+ if(consumer && completed_scanlines) {
+ QRect r(0, oldoutput_scanline, mDecompress.output_width, completed_scanlines);
+#ifdef JPEG_DEBUG
+ qDebug("changing %d/%d %d/%d", r.x(), r.y(), r.width(), r.height());
+#endif
+ mSourceManager.change_rect |= r;
+
+ if ( mSourceManager.decoder_timestamp.elapsed() >= MAX_CONSUMING_TIME ) {
+ if( !mSourceManager.old_change_rect.isEmpty()) {
+ consumer->changed(mSourceManager.old_change_rect);
+ mSourceManager.old_change_rect = QRect();
+ }
+ consumer->changed(mSourceManager.change_rect);
+ mSourceManager.change_rect = QRect();
+ mSourceManager.decoder_timestamp.restart();
+ }
+ }
+
+ if(mDecompress.output_scanline >= mDecompress.output_height) {
+ if ( mSourceManager.do_progressive ) {
+ jpeg_finish_output(&mDecompress);
+ mSourceManager.final_pass = jpeg_input_complete(&mDecompress);
+ mSourceManager.decoding_done = mSourceManager.final_pass && mDecompress.input_scan_number == mDecompress.output_scan_number;
+ if ( !mSourceManager.decoding_done ) {
+ mSourceManager.old_change_rect |= mSourceManager.change_rect;
+ mSourceManager.change_rect = QRect();
+ }
+ } else {
+ mSourceManager.decoding_done = true;
+ }
+#ifdef JPEG_DEBUG
+ qDebug("one pass is completed, final_pass = %d, dec_done: %d, complete: %d",
+ mSourceManager.final_pass, mSourceManager.decoding_done, jpeg_input_complete(&mDecompress));
+#endif
+ if(!mSourceManager.decoding_done) {
+#ifdef JPEG_DEBUG
+ qDebug("starting another one, input_scan_number is %d/%d", mDecompress.input_scan_number,
+ mDecompress.output_scan_number);
+#endif
+ mSourceManager.decoder_timestamp.restart();
+ mState = DECOMPRESS_STARTED;
+ // don't return until necessary!
+ goto again;
+ }
+ }
+
+ if(mState == DO_OUTPUT_SCAN && mSourceManager.decoding_done) {
+#ifdef JPEG_DEBUG
+ qDebug("input is complete, cleaning up, returning..");
+#endif
+ if ( consumer && !mSourceManager.change_rect.isEmpty() ) {
+ consumer->changed( mSourceManager.change_rect );
+ }
+
+ if(consumer) consumer->end();
+
+ // get the density X and Y info and the related units to have
+ // the aspect ratio of the image
+ // field: units -- one byte: Units for the X and Y densities
+ // 0 => no units, X and Y specify the pixel aspect ratio
+ // 1 => X and Y are dots per inch
+ // 2 => X and Y are dots per cm
+ // Xdensity -- two bytes
+ // Ydensity -- two bytes
+ const float INCHESPERMETER = (100. / 2.54);
+ switch (mDecompress.density_unit)
+ {
+ case 0: // no units
+ break;
+ case 1: // dots per inch
+ image.setDotsPerMeterX(int(mDecompress.X_density * INCHESPERMETER));
+ image.setDotsPerMeterY(int(mDecompress.Y_density * INCHESPERMETER));
+ break;
+ case 2: // dots per cm
+ image.setDotsPerMeterX(mDecompress.X_density * 100);
+ image.setDotsPerMeterY(mDecompress.Y_density * 100);
+ break;
+ }
+
+ mSourceManager.at_eof = true;
+
+ (void) jpeg_finish_decompress(&mDecompress);
+ (void) jpeg_destroy_decompress(&mDecompress);
+
+ mState = READ_DONE;
+
+ return 0;
+ }
+ }
+
+#ifdef BUFFER_DEBUG
+ qDebug("valid_buffer_length is now %d", mSourceManager.valid_buffer_length);
+ qDebug("bytes_in_buffer is now %d", mSourceManager.bytes_in_buffer);
+ qDebug("consumed %d bytes", consumed);
+#endif
+ if(mSourceManager.bytes_in_buffer
+ && mSourceManager.jpeg_buffer != mSourceManager.next_input_byte) {
+ memmove(mSourceManager.jpeg_buffer,
+ mSourceManager.next_input_byte,
+ mSourceManager.bytes_in_buffer);
+ }
+ mSourceManager.valid_buffer_length = mSourceManager.bytes_in_buffer;
+ return consumed;
+}
+
+
+//-----------------------------------------------------------------------------
+//
+// JPEGFormatType
+//
+//-----------------------------------------------------------------------------
+QImageFormat* JPEGFormatType::decoderFor(const uchar* buffer, int length) {
+ if(length < 3) return 0;
+
+ if(buffer[0] == 0377 &&
+ buffer[1] == 0330 &&
+ buffer[2] == 0377) {
+ return new JPEGFormat;
+ }
+
+ return 0;
+}
+
+const char* JPEGFormatType::formatName() const {
+ return "JPEG";
+}
+
+} // namespace
diff --git a/src/gvcore/jpegformattype.h b/src/gvcore/jpegformattype.h
new file mode 100644
index 0000000..b92b69d
--- /dev/null
+++ b/src/gvcore/jpegformattype.h
@@ -0,0 +1,51 @@
+/* This file is based on kdelibs-3.2.0/khtml/misc/loader_jpeg.h. Original
+ * copyright follows.
+ */
+/*
+ This file is part of the KDE libraries
+
+ Copyright (C) 2000 Dirk Mueller (mueller@kde.org)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+ This is a helper for progressive loading of JPEG's.
+*/
+
+#ifndef gvjpegformattype_h
+#define gvjpegformattype_h
+
+#include <qasyncimageio.h>
+
+namespace Gwenview {
+
+/**
+ * @internal
+ *
+ * An incremental loader factory for JPEG's.
+ */
+class JPEGFormatType : public QImageFormatType {
+public:
+ QImageFormat* decoderFor(const uchar* buffer, int length);
+ const char* formatName() const;
+};
+
+
+// -----------------------------------------------------------------------------
+
+} // namespace
+#endif // gvjpegformattype_h
diff --git a/src/gvcore/libgwenview_export.h b/src/gvcore/libgwenview_export.h
new file mode 100644
index 0000000..769141f
--- /dev/null
+++ b/src/gvcore/libgwenview_export.h
@@ -0,0 +1,36 @@
+/*
+ This file is part of gwenview project
+ Copyright (c) 2005 Laurent Montel <montel@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+
+#ifndef _LIBGWENVIEW_EXPORT_H
+#define _LIBGWENVIEW_EXPORT_H
+
+#include <kdemacros.h>
+
+#ifdef __KDE_HAVE_GCC_VISIBILITY
+
+#define LIBGWENVIEW_EXPORT KDE_EXPORT
+
+#else
+#define LIBGWENVIEW_EXPORT
+#endif
+
+#endif /* _LIBGWENVIEW_EXPORT_H */
+
+
diff --git a/src/gvcore/mimetypeutils.cpp b/src/gvcore/mimetypeutils.cpp
new file mode 100644
index 0000000..954a24b
--- /dev/null
+++ b/src/gvcore/mimetypeutils.cpp
@@ -0,0 +1,87 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 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 "mimetypeutils.h"
+
+// Qt
+#include <qstringlist.h>
+
+// KDE
+#include <kapplication.h>
+#include <kfileitem.h>
+#include <kimageio.h>
+#include <kio/netaccess.h>
+#include <kmimetype.h>
+#include <kurl.h>
+
+// Local
+#include "archive.h"
+
+
+namespace Gwenview {
+
+namespace MimeTypeUtils {
+
+const QStringList& rasterImageMimeTypes() {
+ static QStringList list;
+ if (list.isEmpty()) {
+ list=KImageIO::mimeTypes(KImageIO::Reading);
+ list.append("image/x-xcf-gimp");
+ list.append("image/x-xcursor");
+ // KImageIO does not return this one :'(
+ list.append("image/pjpeg");
+ }
+ return list;
+}
+
+
+Kind mimeTypeKind(const QString& mimeType) {
+ if (mimeType.startsWith("inode/directory")) {
+ return KIND_DIR;
+ }
+ if (Archive::mimeTypes().contains(mimeType)) {
+ return KIND_ARCHIVE;
+ }
+ if (rasterImageMimeTypes().contains(mimeType)) {
+ return KIND_RASTER_IMAGE;
+ }
+
+ return KIND_FILE;
+}
+
+
+Kind fileItemKind(const KFileItem* item) {
+ return mimeTypeKind(item->mimetype());
+}
+
+
+Kind urlKind(const KURL& url) {
+ QString mimeType;
+ if (url.isLocalFile()) {
+ mimeType=KMimeType::findByURL(url)->name();
+ } else {
+ mimeType=KIO::NetAccess::mimetype(url, KApplication::kApplication()->mainWidget());
+ }
+ return mimeTypeKind(mimeType);
+}
+
+
+} // namespace MimeTypeUtils
+} // namespace Gwenview
diff --git a/src/gvcore/mimetypeutils.h b/src/gvcore/mimetypeutils.h
new file mode 100644
index 0000000..02aa6dc
--- /dev/null
+++ b/src/gvcore/mimetypeutils.h
@@ -0,0 +1,47 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 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.
+
+*/
+#ifndef MIMETYPEUTILS_H
+#define MIMETYPEUTILS_H
+
+// Local
+#include "libgwenview_export.h"
+
+class KFileItem;
+class KURL;
+
+class QString;
+class QStringList;
+
+namespace Gwenview {
+
+namespace MimeTypeUtils {
+enum Kind { KIND_UNKNOWN, KIND_DIR, KIND_ARCHIVE, KIND_FILE, KIND_RASTER_IMAGE };
+
+LIBGWENVIEW_EXPORT const QStringList& rasterImageMimeTypes();
+Kind fileItemKind(const KFileItem*);
+Kind urlKind(const KURL&);
+Kind mimeTypeKind(const QString& mimeType);
+
+} // namespace FileUtils
+
+} // namespace Gwenview
+
+#endif /* MIMETYPEUTILS_H */
diff --git a/src/gvcore/miscconfig.kcfg b/src/gvcore/miscconfig.kcfg
new file mode 100644
index 0000000..c3cc726
--- /dev/null
+++ b/src/gvcore/miscconfig.kcfg
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE kcfg SYSTEM "http://www.kde.org/standards/kcfg/1.0/kcfg.dtd">
+<kcfg>
+ <kcfgfile name="gwenviewrc"/>
+ <group name="misc">
+ <entry name="autoRotateImages" type="Bool">
+ <label>Whether Gwenview should rotate images if orientation information is available.</label>
+ <default>true</default>
+ </entry>
+ <entry name="history" type="PathList">
+ </entry>
+ <entry name="rememberFilter" type="Bool">
+ <label>Whether Gwenview should remember the file filter.</label>
+ <default>false</default>
+ </entry>
+ <entry name="rememberURL" type="Bool">
+ <label>Whether Gwenview should remember the last URL.</label>
+ <default>false</default>
+ </entry>
+ </group>
+ <!-- This is the group for KDialog don't-ask-again checkboxes -->
+ <group name="Notification Messages">
+ <entry name="modifiedBehavior" key="save automatically" type="Enum">
+ <choices>
+ <choice name="ask"/> <!-- == 'ask what to do' -->
+ <choice name="yes"/> <!-- == 'save' -->
+ <choice name="no"/> <!-- == 'discard' -->
+ </choices>
+ </entry>
+ </group>
+</kcfg>
diff --git a/src/gvcore/miscconfig.kcfgc b/src/gvcore/miscconfig.kcfgc
new file mode 100644
index 0000000..3123823
--- /dev/null
+++ b/src/gvcore/miscconfig.kcfgc
@@ -0,0 +1,7 @@
+File=miscconfig.kcfg
+ClassName=MiscConfig
+NameSpace=Gwenview
+Singleton=true
+Mutators=true
+IncludeFiles=gvcore/libgwenview_export.h
+Visibility=LIBGWENVIEW_EXPORT
diff --git a/src/gvcore/mngformattype.cpp b/src/gvcore/mngformattype.cpp
new file mode 100644
index 0000000..60f64ef
--- /dev/null
+++ b/src/gvcore/mngformattype.cpp
@@ -0,0 +1,520 @@
+// this code is copied from Qt, with fixes for not finishing decoding
+// prematurely
+
+/* The actual patch is:
+=====
+--- /opt/_q/src/kernel/qmngio.cpp 2004-05-04 18:28:15.000000000 +0200
++++ gvmngformattype.cpp 2005-04-13 16:11:50.000000000 +0200
+@@ -411,8 +417,11 @@ int QMNGFormat::decode( QImage& img, QIm
+ }
+
+ losttime += losingtimer.elapsed();
+- if ( ndata || !length )
+- mng_display_resume(handle);
++ bool needmore = false;
++ if ( ndata ) {
++ mng_retcode r = mng_display_resume(handle);
++ needmore = ( r == MNG_NEEDMOREDATA );
++ }
+ losingtimer.start();
+
+ image = 0;
+@@ -422,6 +431,13 @@ int QMNGFormat::decode( QImage& img, QIm
+ // Move back unused tail
+ memcpy(buffer,buffer+ubuffer,nbuffer);
+ }
++ // "The function should return without processing all the data if it reaches the end of a frame in the input."
++ if( ndata && !needmore ) {
++ length -= ndata;
++ ndata = 0;
++ if( length == 0 ) // 0 means done, process at least one byte
++ length = ndata = 1;
++ }
+ if ( ndata ) {
+ // Not all used.
+ enlargeBuffer(nbuffer+ndata);
+=====
+*/
+
+/****************************************************************************
+**
+**
+** Implementation of MNG QImage IOHandler
+**
+** Created : 970521
+**
+** Copyright (C) 1997-2004 Trolltech AS. All rights reserved.
+**
+** This file is part of the kernel module of the Qt GUI Toolkit.
+**
+** This file may be distributed under the terms of the Q Public License
+** as defined by Trolltech AS of Norway and appearing in the file
+** LICENSE.QPL included in the packaging of this file.
+**
+** This file may be distributed and/or modified under the terms of the
+** GNU General Public License version 2 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file.
+**
+** Licensees holding valid Qt Enterprise Edition or Qt Professional Edition
+** licenses may use this file in accordance with the Qt Commercial License
+** Agreement provided with the Software.
+**
+** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
+** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+**
+** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for
+** information about Qt Commercial License Agreements.
+** See http://www.trolltech.com/qpl/ for QPL licensing information.
+** See http://www.trolltech.com/gpl/ for GPL licensing information.
+**
+** Contact info@trolltech.com if any conditions of this licensing are
+** not clear to you.
+**
+**********************************************************************/
+
+#include <config.h>
+
+#ifndef QT_CLEAN_NAMESPACE
+#define QT_CLEAN_NAMESPACE
+#endif
+
+#include "qdatetime.h"
+#include "mngformattype.h"
+
+//#ifndef QT_NO_IMAGEIO_MNG
+#ifdef HAVE_LIBMNG
+
+#include "qimage.h"
+#include "qasyncimageio.h"
+#include "qiodevice.h"
+
+// Define XMD_H prohibits the included headers of libmng.h to typedef INT32.
+// This is needed for Borland with STL support, since in that case, INT32 is
+// already defined by some Borland header.
+#define XMD_H
+#if defined(Q_OS_UNIXWARE)
+# define HAVE_BOOLEAN // libjpeg under Unixware seems to need this
+#endif
+#include <libmng.h>
+#include <stdlib.h>
+
+
+#ifndef QT_NO_ASYNC_IMAGE_IO
+namespace Gwenview {
+
+class MNGFormat : public QImageFormat {
+public:
+ MNGFormat();
+ virtual ~MNGFormat();
+
+ int decode(QImage& img, QImageConsumer* consumer,
+ const uchar* buffer, int length);
+
+ bool openstream()
+ {
+ // ### We should figure out how many loops an MNG has, but for now always assume infinite.
+ if (consumer)
+ consumer->setLooping(0);
+ return TRUE;
+ }
+ bool closestream( )
+ {
+ if (consumer)
+ consumer->end();
+ return TRUE;
+ }
+ bool readdata( mng_ptr pBuf, mng_uint32 iBuflen, mng_uint32p pRead )
+ {
+ uint m = ndata + nbuffer - ubuffer;
+ if ( iBuflen > m ) {
+ iBuflen = m;
+ }
+ *pRead = iBuflen;
+ uint n = nbuffer-ubuffer;
+ if ( iBuflen < n ) {
+ // enough in buffer
+ memcpy(pBuf, buffer+ubuffer, iBuflen);
+ ubuffer += iBuflen;
+ return TRUE;
+ }
+ if ( n ) {
+ // consume buffer
+ memcpy(pBuf, buffer+ubuffer, n );
+ pBuf = (mng_ptr)((char*)pBuf + n);
+ iBuflen -= n;
+ ubuffer = nbuffer;
+ }
+ if ( iBuflen ) {
+ // fill from incoming data
+ memcpy(pBuf, data, iBuflen);
+ data += iBuflen;
+ ndata -= iBuflen;
+ }
+ return TRUE;
+ }
+ bool errorproc( mng_int32 iErrorcode,
+ mng_int8 /*iSeverity*/,
+ mng_chunkid iChunkname,
+ mng_uint32 /*iChunkseq*/,
+ mng_int32 iExtra1,
+ mng_int32 iExtra2,
+ mng_pchar zErrortext )
+ {
+ qWarning("MNG error %d: %s; chunk %c%c%c%c; subcode %d:%d",
+ iErrorcode,zErrortext,
+ (iChunkname>>24)&0xff,
+ (iChunkname>>16)&0xff,
+ (iChunkname>>8)&0xff,
+ (iChunkname>>0)&0xff,
+ iExtra1,iExtra2);
+ return TRUE;
+ }
+ bool processheader( mng_uint32 iWidth, mng_uint32 iHeight )
+ {
+ image->create(iWidth,iHeight,32);
+ image->setAlphaBuffer(TRUE);
+ memset(image->bits(),0,iWidth*iHeight*4);
+ consumer->setSize(iWidth,iHeight);
+ mng_set_canvasstyle(handle,
+ QImage::systemByteOrder() == QImage::LittleEndian
+ ? MNG_CANVAS_BGRA8 : MNG_CANVAS_ARGB8 );
+ return TRUE;
+ }
+ mng_ptr getcanvasline( mng_uint32 iLinenr )
+ {
+ return image->scanLine(iLinenr);
+ }
+ mng_bool refresh( mng_uint32 x, mng_uint32 y, mng_uint32 w, mng_uint32 h )
+ {
+ QRect r(x,y,w,h);
+ consumer->changed(r);
+ consumer->setFramePeriod(0);
+ consumer->frameDone();
+ return TRUE;
+ }
+ mng_uint32 gettickcount( )
+ {
+ return timer.elapsed() - losttime;
+ }
+ bool settimer( mng_uint32 iMsecs )
+ {
+ consumer->setFramePeriod(iMsecs);
+ consumer->frameDone();
+ state = Time;
+ losingtimer.start();
+ losttime -= iMsecs;
+ return TRUE;
+ }
+
+private:
+ // Animation-level information
+ enum { MovieStart, Time, Data, Data2 } state;
+
+ // Image-level information
+ mng_handle handle;
+
+ // For storing unused data
+ uchar *buffer;
+ uint maxbuffer;
+ uint nbuffer;
+
+ // Timing
+ QTime timer;
+ QTime losingtimer;
+ int losttime;
+
+ void enlargeBuffer(uint n)
+ {
+ if ( n > maxbuffer ) {
+ maxbuffer = n;
+ buffer = (uchar*)realloc(buffer,n);
+ }
+ }
+
+ // Temporary locals during single data-chunk processing
+ const uchar* data;
+ uint ndata;
+ uint ubuffer;
+ QImageConsumer* consumer;
+ QImage* image;
+};
+
+class MNGFormatType : public QImageFormatType
+{
+ QImageFormat* decoderFor(const uchar* buffer, int length);
+ const char* formatName() const;
+};
+
+
+/*
+ \class QMNGFormat qmngio.h
+ \brief Incremental image decoder for MNG image format.
+
+ \ingroup images
+ \ingroup graphics
+
+ This subclass of QImageFormat decodes MNG format images,
+ including animated MNGs.
+
+ Animated MNG images are standard MNG images. The MNG standard
+ defines two extension chunks that are useful for animations:
+
+ <dl>
+ <dt>gIFg - GIF-like Graphic Control Extension
+ <dd>Includes frame disposal, user input flag (we ignore this),
+ and inter-frame delay.
+ <dt>gIFx - GIF-like Application Extension
+ <dd>Multi-purpose, but we just use the Netscape extension
+ which specifies looping.
+ </dl>
+
+ The subimages usually contain a offset chunk (oFFs) but need not.
+
+ The first image defines the "screen" size. Any subsequent image that
+ doesn't fit is clipped.
+
+TODO: decide on this point. gIFg gives disposal types, so it can be done.
+ All images paste (\e not composite, just place all-channel copying)
+ over the previous image to produce a subsequent frame.
+*/
+
+/*
+ \class QMNGFormatType qasyncimageio.h
+ \brief Incremental image decoder for MNG image format.
+
+ \ingroup images
+ \ingroup graphics
+ \ingroup io
+
+ This subclass of QImageFormatType recognizes MNG
+ format images, creating a QMNGFormat when required. An instance
+ of this class is created automatically before any other factories,
+ so you should have no need for such objects.
+*/
+
+QImageFormat* MNGFormatType::decoderFor( const uchar* buffer, int length )
+{
+ if (length < 8) return 0;
+
+ if (buffer[0]==138 // MNG signature
+ && buffer[1]=='M'
+ && buffer[2]=='N'
+ && buffer[3]=='G'
+ && buffer[4]==13
+ && buffer[5]==10
+ && buffer[6]==26
+ && buffer[7]==10
+ || buffer[0]==139 // JNG signature
+ && buffer[1]=='J'
+ && buffer[2]=='N'
+ && buffer[3]=='G'
+ && buffer[4]==13
+ && buffer[5]==10
+ && buffer[6]==26
+ && buffer[7]==10
+#ifdef QT_NO_IMAGEIO_PNG // if we don't have native PNG support use libmng
+ || buffer[0]==137 // PNG signature
+ && buffer[1]=='P'
+ && buffer[2]=='N'
+ && buffer[3]=='G'
+ && buffer[4]==13
+ && buffer[5]==10
+ && buffer[6]==26
+ && buffer[7]==10
+#endif
+ )
+ return new MNGFormat;
+ return 0;
+}
+
+const char* MNGFormatType::formatName() const
+{
+ return "MNG";
+}
+
+
+/*!
+ Constructs a QMNGFormat.
+*/
+MNGFormat::MNGFormat()
+{
+ state = MovieStart;
+ handle = 0;
+ nbuffer = 0;
+ maxbuffer = 0;
+ buffer = 0;
+ losttime = 0;
+}
+
+/*
+ Destroys a QMNGFormat.
+*/
+MNGFormat::~MNGFormat()
+{
+ // We're setting the consumer to 0 since it may have been
+ // deleted by read_async_image in qimage.cpp
+ consumer = 0;
+ if (handle) mng_cleanup(&handle);
+}
+
+
+// C-callback to C++-member-function conversion
+//
+static mng_bool openstream( mng_handle handle )
+{
+ return ((MNGFormat*)mng_get_userdata(handle))->openstream();
+}
+static mng_bool closestream( mng_handle handle )
+{
+ return ((MNGFormat*)mng_get_userdata(handle))->closestream();
+}
+static mng_bool readdata( mng_handle handle, mng_ptr pBuf, mng_uint32 iBuflen, mng_uint32p pRead )
+{
+ return ((MNGFormat*)mng_get_userdata(handle))->readdata(pBuf,iBuflen,pRead);
+}
+static mng_bool errorproc( mng_handle handle,
+ mng_int32 iErrorcode,
+ mng_int8 iSeverity,
+ mng_chunkid iChunkname,
+ mng_uint32 iChunkseq,
+ mng_int32 iExtra1,
+ mng_int32 iExtra2,
+ mng_pchar zErrortext )
+{
+ return ((MNGFormat*)mng_get_userdata(handle))->errorproc(iErrorcode,
+ iSeverity,iChunkname,iChunkseq,iExtra1,iExtra2,zErrortext);
+}
+static mng_bool processheader( mng_handle handle,
+ mng_uint32 iWidth, mng_uint32 iHeight )
+{
+ return ((MNGFormat*)mng_get_userdata(handle))->processheader(iWidth,iHeight);
+}
+static mng_ptr getcanvasline( mng_handle handle, mng_uint32 iLinenr )
+{
+ return ((MNGFormat*)mng_get_userdata(handle))->getcanvasline(iLinenr);
+}
+static mng_bool refresh( mng_handle handle,
+ mng_uint32 iTop,
+ mng_uint32 iLeft,
+ mng_uint32 iBottom,
+ mng_uint32 iRight )
+{
+ return ((MNGFormat*)mng_get_userdata(handle))->refresh(iTop,iLeft,iBottom,iRight);
+}
+static mng_uint32 gettickcount( mng_handle handle )
+{
+ return ((MNGFormat*)mng_get_userdata(handle))->gettickcount();
+}
+static mng_bool settimer( mng_handle handle, mng_uint32 iMsecs )
+{
+ return ((MNGFormat*)mng_get_userdata(handle))->settimer(iMsecs);
+}
+
+static mng_ptr memalloc( mng_size_t iLen )
+{
+ return calloc(1,iLen);
+}
+static void memfree( mng_ptr iPtr, mng_size_t /*iLen*/ )
+{
+ free(iPtr);
+}
+
+/*!
+ This function decodes some data into image changes.
+
+ Returns the number of bytes consumed.
+*/
+int MNGFormat::decode( QImage& img, QImageConsumer* cons,
+ const uchar* buf, int length )
+{
+ consumer = cons;
+ image = &img;
+
+ data = buf;
+ ndata = length;
+ ubuffer = 0;
+
+ if ( state == MovieStart ) {
+ handle = mng_initialize( (mng_ptr)this, Gwenview::memalloc, Gwenview::memfree, 0 );
+ mng_set_suspensionmode( handle, MNG_TRUE );
+ mng_setcb_openstream( handle, Gwenview::openstream );
+ mng_setcb_closestream( handle, Gwenview::closestream );
+ mng_setcb_readdata( handle, Gwenview::readdata );
+ mng_setcb_errorproc( handle, Gwenview::errorproc );
+ mng_setcb_processheader( handle, Gwenview::processheader );
+ mng_setcb_getcanvasline( handle, Gwenview::getcanvasline );
+ mng_setcb_refresh( handle, Gwenview::refresh );
+ mng_setcb_gettickcount( handle, Gwenview::gettickcount );
+ mng_setcb_settimer( handle, Gwenview::settimer );
+ state = Data;
+ mng_readdisplay(handle);
+ losingtimer.start();
+ }
+
+ losttime += losingtimer.elapsed();
+ bool needmore = false;
+ if ( ndata ) {
+ mng_retcode r = mng_display_resume(handle);
+ needmore = ( r == MNG_NEEDMOREDATA );
+ }
+ losingtimer.start();
+
+ image = 0;
+
+ nbuffer -= ubuffer;
+ if ( nbuffer ) {
+ // Move back unused tail
+ memcpy(buffer,buffer+ubuffer,nbuffer);
+ }
+ // "The function should return without processing all the data if it reaches the end of a frame in the input."
+ if( ndata && !needmore ) {
+ length -= ndata;
+ ndata = 0;
+ if( length == 0 ) // 0 means done, process at least one byte
+ length = ndata = 1;
+ }
+ if ( ndata ) {
+ // Not all used.
+ enlargeBuffer(nbuffer+ndata);
+ memcpy(buffer+nbuffer,data,ndata);
+ nbuffer += ndata;
+ }
+
+ return length;
+}
+
+static MNGFormatType* globalMngFormatTypeObject = 0;
+
+#endif // QT_NO_ASYNC_IMAGE_IO
+
+#ifndef QT_NO_ASYNC_IMAGE_IO
+void gvCleanupMngIO()
+{
+ if ( globalMngFormatTypeObject ) {
+ delete globalMngFormatTypeObject;
+ globalMngFormatTypeObject = 0;
+ }
+}
+#endif
+
+void gvInitMngIO()
+{
+ static bool done = FALSE;
+ if ( !done ) {
+ done = TRUE;
+#ifndef QT_NO_ASYNC_IMAGE_IO
+ globalMngFormatTypeObject = new MNGFormatType;
+ qAddPostRoutine( gvCleanupMngIO );
+#endif
+ }
+}
+
+#else
+void gvInitMngIO() {}
+
+namespace Gwenview {
+#endif
+
+MNG::MNG() { gvInitMngIO(); }
+} // namespace
diff --git a/src/gvcore/mngformattype.h b/src/gvcore/mngformattype.h
new file mode 100644
index 0000000..9e60b64
--- /dev/null
+++ b/src/gvcore/mngformattype.h
@@ -0,0 +1,55 @@
+// this code is copied from Qt, with fixes for not finishing decoding
+// prematurely
+
+/****************************************************************************
+**
+**
+** Definition of MNG QImage IOHandler
+**
+** Created : 970521
+**
+** Copyright (C) 2000 Trolltech AS. All rights reserved.
+**
+** This file is part of the kernel module of the Qt GUI Toolkit.
+**
+** This file may be distributed under the terms of the Q Public License
+** as defined by Trolltech AS of Norway and appearing in the file
+** LICENSE.QPL included in the packaging of this file.
+**
+** This file may be distributed and/or modified under the terms of the
+** GNU General Public License version 2 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file.
+**
+** Licensees holding valid Qt Enterprise Edition or Qt Professional Edition
+** licenses may use this file in accordance with the Qt Commercial License
+** Agreement provided with the Software.
+**
+** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
+** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+**
+** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for
+** information about Qt Commercial License Agreements.
+** See http://www.trolltech.com/qpl/ for QPL licensing information.
+** See http://www.trolltech.com/gpl/ for GPL licensing information.
+**
+** Contact info@trolltech.com if any conditions of this licensing are
+** not clear to you.
+**
+**********************************************************************/
+
+#ifndef QMNGIO_H
+#define QMNGIO_H
+
+#ifndef QT_H
+#endif // QT_H
+namespace Gwenview {
+
+class MNG
+{
+public:
+ MNG();
+};
+
+} // namespace
+#endif // QMNGIO_H
diff --git a/src/gvcore/pngformattype.cpp b/src/gvcore/pngformattype.cpp
new file mode 100644
index 0000000..36c1064
--- /dev/null
+++ b/src/gvcore/pngformattype.cpp
@@ -0,0 +1,559 @@
+// this code is copied from Qt, with code added to actually call consumer
+// methods that inform about the progress of loading
+
+/****************************************************************************
+**
+**
+** Implementation of PNG QImage IOHandler
+**
+** Created : 970521
+**
+** Copyright (C) 1992-2003 Trolltech AS. All rights reserved.
+**
+** This file is part of the kernel module of the Qt GUI Toolkit.
+**
+** This file may be distributed under the terms of the Q Public License
+** as defined by Trolltech AS of Norway and appearing in the file
+** LICENSE.QPL included in the packaging of this file.
+**
+** This file may be distributed and/or modified under the terms of the
+** GNU General Public License version 2 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file.
+**
+** Licensees holding valid Qt Enterprise Edition or Qt Professional Edition
+** licenses may use this file in accordance with the Qt Commercial License
+** Agreement provided with the Software.
+**
+** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
+** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+**
+** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for
+** information about Qt Commercial License Agreements.
+** See http://www.trolltech.com/qpl/ for QPL licensing information.
+** See http://www.trolltech.com/gpl/ for GPL licensing information.
+**
+** Contact info@trolltech.com if any conditions of this licensing are
+** not clear to you.
+**
+**********************************************************************/
+
+#include "pngformattype.h"
+
+#include <png.h>
+
+namespace Gwenview {
+
+class PNGFormat : public QImageFormat {
+public:
+ PNGFormat();
+ virtual ~PNGFormat();
+
+ int decode(QImage& img, QImageConsumer* consumer,
+ const uchar* buffer, int length);
+
+ void info(png_structp png_ptr, png_infop info);
+ void row(png_structp png_ptr, png_bytep new_row,
+ png_uint_32 row_num, int pass);
+ void end(png_structp png_ptr, png_infop info);
+#ifdef PNG_USER_CHUNKS_SUPPORTED
+ int user_chunk(png_structp png_ptr,
+ png_bytep data, png_uint_32 length);
+#endif
+
+private:
+ // Animation-level information
+ enum { MovieStart, FrameStart, Inside, End } state;
+ int first_frame;
+ int base_offx;
+ int base_offy;
+
+ // Image-level information
+ png_structp png_ptr;
+ png_infop info_ptr;
+
+ // Temporary locals during single data-chunk processing
+ QImageConsumer* consumer;
+ QImage* image;
+ int unused_data;
+ QRect changed_rect;
+};
+
+/*
+ \class QPNGFormat qpngio.h
+ \brief The QPNGFormat class provides an incremental image decoder for PNG
+ image format.
+
+ \ingroup images
+ \ingroup graphics
+
+ This subclass of QImageFormat decodes PNG format images,
+ including animated PNGs.
+
+ Animated PNG images are standard PNG images. The PNG standard
+ defines two extension chunks that are useful for animations:
+
+ \list
+ \i gIFg - GIF-like Graphic Control Extension.
+ This includes frame disposal, user input flag (we ignore this),
+ and inter-frame delay.
+ \i gIFx - GIF-like Application Extension.
+ This is multi-purpose, but we just use the Netscape extension
+ which specifies looping.
+ \endlist
+
+ The subimages usually contain a offset chunk (oFFs) but need not.
+
+ The first image defines the "screen" size. Any subsequent image that
+ doesn't fit is clipped.
+*/
+/* ###TODO: decide on this point. gIFg gives disposal types, so it can be done.
+ All images paste (\e not composite, just place all-channel copying)
+ over the previous image to produce a subsequent frame.
+*/
+
+/*
+ \class QPNGFormatType qasyncimageio.h
+ \brief The QPNGFormatType class provides an incremental image decoder
+ for PNG image format.
+
+ \ingroup images
+ \ingroup graphics
+ \ingroup io
+
+ This subclass of QImageFormatType recognizes PNG format images, creating
+ a QPNGFormat when required. An instance of this class is created
+ automatically before any other factories, so you should have no need for
+ such objects.
+*/
+
+QImageFormat* PNGFormatType::decoderFor(
+ const uchar* buffer, int length)
+{
+ if (length < 8) return 0;
+ if (buffer[0]==137
+ && buffer[1]=='P'
+ && buffer[2]=='N'
+ && buffer[3]=='G'
+ && buffer[4]==13
+ && buffer[5]==10
+ && buffer[6]==26
+ && buffer[7]==10)
+ return new PNGFormat;
+ return 0;
+}
+
+const char* PNGFormatType::formatName() const
+{
+ return "PNG";
+}
+
+extern "C" {
+
+static void
+info_callback(png_structp png_ptr, png_infop info)
+{
+ PNGFormat* that = (PNGFormat*)png_get_progressive_ptr(png_ptr);
+ that->info(png_ptr,info);
+}
+
+static void
+row_callback(png_structp png_ptr, png_bytep new_row,
+ png_uint_32 row_num, int pass)
+{
+ PNGFormat* that = (PNGFormat*)png_get_progressive_ptr(png_ptr);
+ that->row(png_ptr,new_row,row_num,pass);
+}
+
+static void
+end_callback(png_structp png_ptr, png_infop info)
+{
+ PNGFormat* that = (PNGFormat*)png_get_progressive_ptr(png_ptr);
+ that->end(png_ptr,info);
+}
+
+#if 0
+#ifdef PNG_USER_CHUNKS_SUPPORTED
+static int
+CALLBACK_CALL_TYPE user_chunk_callback(png_structp png_ptr,
+ png_unknown_chunkp chunk)
+{
+ PNGFormat* that = (PNGFormat*)png_get_progressive_ptr(png_ptr);
+ return that->user_chunk(png_ptr,chunk->data,chunk->size);
+}
+#endif
+#endif
+
+static void qt_png_warning(png_structp /*png_ptr*/, png_const_charp message)
+{
+ qWarning("libpng warning: %s", message);
+}
+
+}
+
+static
+void setup_qt( QImage& image, png_structp png_ptr, png_infop info_ptr )
+{
+ // For now, we will use the PC monitor gamma, if you own a Mac, you'd better use 1.8
+ const double SCREEN_GAMMA=2.2;
+ if ( png_get_valid(png_ptr, info_ptr, PNG_INFO_gAMA) ) {
+ double file_gamma;
+ png_get_gAMA(png_ptr, info_ptr, &file_gamma);
+ png_set_gamma( png_ptr, SCREEN_GAMMA, file_gamma );
+ }
+
+ png_uint_32 width;
+ png_uint_32 height;
+ int bit_depth;
+ int color_type;
+ png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type,
+ 0, 0, 0);
+
+ if ( color_type == PNG_COLOR_TYPE_GRAY ) {
+ // Black & White or 8-bit grayscale
+ if ( bit_depth == 1 && info_ptr->channels == 1 ) {
+ png_set_invert_mono( png_ptr );
+ png_read_update_info( png_ptr, info_ptr );
+ if (!image.create( width, height, 1, 2, QImage::BigEndian ))
+ return;
+ image.setColor( 1, qRgb(0,0,0) );
+ image.setColor( 0, qRgb(255,255,255) );
+ } else if (bit_depth == 16 && png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
+ png_set_expand(png_ptr);
+ png_set_strip_16(png_ptr);
+ png_set_gray_to_rgb(png_ptr);
+
+ if (!image.create(width, height, 32))
+ return;
+ image.setAlphaBuffer(TRUE);
+
+ if (QImage::systemByteOrder() == QImage::BigEndian)
+ png_set_swap_alpha(png_ptr);
+
+ png_read_update_info(png_ptr, info_ptr);
+ } else {
+ if ( bit_depth == 16 )
+ png_set_strip_16(png_ptr);
+ else if ( bit_depth < 8 )
+ png_set_packing(png_ptr);
+ int ncols = bit_depth < 8 ? 1 << bit_depth : 256;
+ png_read_update_info(png_ptr, info_ptr);
+ if (!image.create(width, height, 8, ncols))
+ return;
+ for (int i=0; i<ncols; i++) {
+ int c = i*255/(ncols-1);
+ image.setColor( i, qRgba(c,c,c,0xff) );
+ }
+ if ( png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS) ) {
+ const int g = info_ptr->trans_values.gray;
+ if (g < ncols) {
+ image.setAlphaBuffer(TRUE);
+ image.setColor(g, image.color(g) & RGB_MASK);
+ }
+ }
+ }
+ } else if ( color_type == PNG_COLOR_TYPE_PALETTE
+ && png_get_valid(png_ptr, info_ptr, PNG_INFO_PLTE)
+ && info_ptr->num_palette <= 256 )
+ {
+ // 1-bit and 8-bit color
+ if ( bit_depth != 1 )
+ png_set_packing( png_ptr );
+ png_read_update_info( png_ptr, info_ptr );
+ png_get_IHDR(png_ptr, info_ptr,
+ &width, &height, &bit_depth, &color_type, 0, 0, 0);
+ if (!image.create(width, height, bit_depth, info_ptr->num_palette,
+ QImage::BigEndian))
+ return;
+ int i = 0;
+ if ( png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS) ) {
+ image.setAlphaBuffer( TRUE );
+ while ( i < info_ptr->num_trans ) {
+ image.setColor(i, qRgba(
+ info_ptr->palette[i].red,
+ info_ptr->palette[i].green,
+ info_ptr->palette[i].blue,
+ info_ptr->trans[i]
+ )
+ );
+ i++;
+ }
+ }
+ while ( i < info_ptr->num_palette ) {
+ image.setColor(i, qRgba(
+ info_ptr->palette[i].red,
+ info_ptr->palette[i].green,
+ info_ptr->palette[i].blue,
+ 0xff
+ )
+ );
+ i++;
+ }
+ } else {
+ // 32-bit
+ if ( bit_depth == 16 )
+ png_set_strip_16(png_ptr);
+
+ png_set_expand(png_ptr);
+
+ if ( color_type == PNG_COLOR_TYPE_GRAY_ALPHA )
+ png_set_gray_to_rgb(png_ptr);
+
+ if (!image.create(width, height, 32))
+ return;
+
+ // Only add filler if no alpha, or we can get 5 channel data.
+ if (!(color_type & PNG_COLOR_MASK_ALPHA)
+ && !png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
+ png_set_filler(png_ptr, 0xff,
+ QImage::systemByteOrder() == QImage::BigEndian ?
+ PNG_FILLER_BEFORE : PNG_FILLER_AFTER);
+ // We want 4 bytes, but it isn't an alpha channel
+ } else {
+ image.setAlphaBuffer(TRUE);
+ }
+
+ if ( QImage::systemByteOrder() == QImage::BigEndian ) {
+ png_set_swap_alpha(png_ptr);
+ }
+
+ png_read_update_info(png_ptr, info_ptr);
+ }
+
+ // Qt==ARGB==Big(ARGB)==Little(BGRA)
+ if ( QImage::systemByteOrder() == QImage::LittleEndian ) {
+ png_set_bgr(png_ptr);
+ }
+}
+
+
+
+/*!
+ Constructs a QPNGFormat object.
+*/
+PNGFormat::PNGFormat()
+{
+ state = MovieStart;
+ first_frame = 1;
+ base_offx = 0;
+ base_offy = 0;
+ png_ptr = 0;
+ info_ptr = 0;
+}
+
+
+/*!
+ Destroys a QPNGFormat object.
+*/
+PNGFormat::~PNGFormat()
+{
+ if ( png_ptr )
+ png_destroy_read_struct(&png_ptr, &info_ptr, 0);
+}
+
+
+/*!
+ This function decodes some data into image changes.
+
+ Returns the number of bytes consumed.
+*/
+int PNGFormat::decode(QImage& img, QImageConsumer* cons,
+ const uchar* buffer, int length)
+{
+ consumer = cons;
+ image = &img;
+
+ if ( state != Inside ) {
+ png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
+ if (!png_ptr) {
+ info_ptr = 0;
+ image = 0;
+ return -1;
+ }
+
+ png_set_error_fn(png_ptr, 0, 0, qt_png_warning);
+ png_set_compression_level(png_ptr, 9);
+
+ info_ptr = png_create_info_struct(png_ptr);
+ if (!info_ptr) {
+ png_destroy_read_struct(&png_ptr, &info_ptr, 0);
+ image = 0;
+ return -1;
+ }
+
+ if (setjmp((png_ptr)->jmpbuf)) {
+ png_destroy_read_struct(&png_ptr, &info_ptr, 0);
+ image = 0;
+ return -1;
+ }
+
+ png_set_progressive_read_fn(png_ptr, (void *)this,
+ info_callback, row_callback, end_callback);
+
+#ifdef PNG_USER_CHUNKS_SUPPORTED
+ // Can't do this yet. libpng has a crash bug with unknown (user) chunks.
+ // Warwick has sent them a patch.
+ // png_set_read_user_chunk_fn(png_ptr, 0, user_chunk_callback);
+ // png_set_keep_unknown_chunks(png_ptr, 2/*HANDLE_CHUNK_IF_SAFE*/, 0, 0);
+#endif
+
+ if ( state != MovieStart && *buffer != 0211 ) {
+ // Good, no signature - the preferred way to concat PNG images.
+ // Skip them.
+ png_set_sig_bytes(png_ptr, 8);
+ }
+
+ state = Inside;
+ changed_rect = QRect();
+ }
+
+ if ( !png_ptr ) return 0;
+
+ if (setjmp(png_ptr->jmpbuf)) {
+ png_destroy_read_struct(&png_ptr, &info_ptr, 0);
+ image = 0;
+ state = MovieStart;
+ return -1;
+ }
+ unused_data = 0;
+ png_process_data(png_ptr, info_ptr, (png_bytep)buffer, length);
+ int l = length - unused_data;
+
+ if( !changed_rect.isNull()) {
+ consumer->changed( changed_rect );
+ changed_rect = QRect();
+ }
+
+ if ( state != Inside ) {
+ if ( png_ptr )
+ png_destroy_read_struct(&png_ptr, &info_ptr, 0);
+ }
+
+ image = 0;
+ return l;
+}
+
+void PNGFormat::info(png_structp png, png_infop)
+{
+ png_set_interlace_handling(png);
+ setup_qt(*image, png, info_ptr);
+ consumer->setSize( image->width(), image->height());
+}
+
+void PNGFormat::row(png_structp png, png_bytep new_row,
+ png_uint_32 row_num, int)
+{
+ uchar* old_row = image->scanLine(row_num);
+ png_progressive_combine_row(png, old_row, new_row);
+ changed_rect |= QRect( 0, row_num, image->width(), 1 );
+}
+
+
+void PNGFormat::end(png_structp png, png_infop info)
+{
+ int offx = png_get_x_offset_pixels(png,info) - base_offx;
+ int offy = png_get_y_offset_pixels(png,info) - base_offy;
+ if ( first_frame ) {
+ base_offx = offx;
+ base_offy = offy;
+ first_frame = 0;
+ }
+ image->setOffset(QPoint(offx,offy));
+ image->setDotsPerMeterX(png_get_x_pixels_per_meter(png,info));
+ image->setDotsPerMeterY(png_get_y_pixels_per_meter(png,info));
+ png_textp text_ptr;
+ int num_text=0;
+ png_get_text(png,info,&text_ptr,&num_text);
+ while (num_text--) {
+ image->setText(text_ptr->key,0,text_ptr->text);
+ text_ptr++;
+ }
+ if( !changed_rect.isNull()) {
+ consumer->changed( changed_rect );
+ changed_rect = QRect();
+ }
+ QRect r(0,0,image->width(),image->height());
+ consumer->frameDone(QPoint(offx,offy),r);
+ consumer->end();
+ state = FrameStart;
+ unused_data = (int)png->buffer_size; // Since libpng doesn't tell us
+}
+
+#ifdef PNG_USER_CHUNKS_SUPPORTED
+
+/*
+#ifndef QT_NO_IMAGE_TEXT
+static bool skip(png_uint_32& max, png_bytep& data)
+{
+ while (*data) {
+ if ( !max ) return FALSE;
+ max--;
+ data++;
+ }
+ if ( !max ) return FALSE;
+ max--;
+ data++; // skip to after NUL
+ return TRUE;
+}
+#endif
+*/
+
+int PNGFormat::user_chunk(png_structp png,
+ png_bytep data, png_uint_32 length)
+{
+#if 0 // NOT SUPPORTED: experimental PNG animation.
+ // qDebug("Got %ld-byte %s chunk", length, png->chunk_name);
+ if ( 0==qstrcmp((char*)png->chunk_name, "gIFg")
+ && length == 4 ) {
+
+ //QPNGImageWriter::DisposalMethod disposal =
+ // (QPNGImageWriter::DisposalMethod)data[0];
+ // ### TODO: use the disposal method
+ int ms_delay = ((data[2] << 8) | data[3])*10;
+ consumer->setFramePeriod(ms_delay);
+ return 1;
+ } else if ( 0==qstrcmp((char*)png->chunk_name, "gIFx")
+ && length == 13 ) {
+ if ( qstrncmp((char*)data,"NETSCAPE2.0",11)==0 ) {
+ int looping = (data[0xC]<<8)|data[0xB];
+ consumer->setLooping(looping);
+ return 1;
+ }
+ }
+#else
+ Q_UNUSED( png )
+ Q_UNUSED( data )
+ Q_UNUSED( length )
+#endif
+
+ /*
+
+ libpng now supports this chunk.
+
+
+ if ( 0==qstrcmp((char*)png->chunk_name, "iTXt") && length>=6 ) {
+ const char* keyword = (const char*)data;
+ if ( !skip(length,data) ) return 0;
+ if ( length >= 4 ) {
+ char compression_flag = *data++;
+ char compression_method = *data++;
+ if ( compression_flag == compression_method ) {
+ // fool the compiler into thinking they're used
+ }
+ const char* lang = (const char*)data;
+ if ( !skip(length,data) ) return 0;
+ // const char* keyword_utf8 = (const char*)data;
+ if ( !skip(length,data) ) return 0;
+ const char* text_utf8 = (const char*)data;
+ if ( !skip(length,data) ) return 0;
+ QString text = QString::fromUtf8(text_utf8);
+ image->setText(keyword,lang[0] ? lang : 0,text);
+ return 1;
+ }
+ }
+ */
+
+ return 0;
+}
+} // namespace
+#endif
diff --git a/src/gvcore/pngformattype.h b/src/gvcore/pngformattype.h
new file mode 100644
index 0000000..920ee0c
--- /dev/null
+++ b/src/gvcore/pngformattype.h
@@ -0,0 +1,63 @@
+// this code is copied from Qt, with code added to actually call consumer
+// methods that inform about the progress of loading
+
+/****************************************************************************
+**
+**
+** Implementation of PNG QImage IOHandler
+**
+** Created : 970521
+**
+** Copyright (C) 1992-2003 Trolltech AS. All rights reserved.
+**
+** This file is part of the kernel module of the Qt GUI Toolkit.
+**
+** This file may be distributed under the terms of the Q Public License
+** as defined by Trolltech AS of Norway and appearing in the file
+** LICENSE.QPL included in the packaging of this file.
+**
+** This file may be distributed and/or modified under the terms of the
+** GNU General Public License version 2 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file.
+**
+** Licensees holding valid Qt Enterprise Edition or Qt Professional Edition
+** licenses may use this file in accordance with the Qt Commercial License
+** Agreement provided with the Software.
+**
+** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
+** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+**
+** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for
+** information about Qt Commercial License Agreements.
+** See http://www.trolltech.com/qpl/ for QPL licensing information.
+** See http://www.trolltech.com/gpl/ for GPL licensing information.
+**
+** Contact info@trolltech.com if any conditions of this licensing are
+** not clear to you.
+**
+**********************************************************************/
+
+#ifndef gvpngformattype_h
+#define gvpngformattype_h
+
+#include <qasyncimageio.h>
+
+namespace Gwenview {
+
+/**
+ * @internal
+ *
+ * An incremental loader factory for PNG's.
+ */
+class PNGFormatType : public QImageFormatType {
+public:
+ QImageFormat* decoderFor(const uchar* buffer, int length);
+ const char* formatName() const;
+};
+
+} // namespace
+
+// -----------------------------------------------------------------------------
+
+#endif // gvpngformattype_h
diff --git a/src/gvcore/printdialog.cpp b/src/gvcore/printdialog.cpp
new file mode 100644
index 0000000..6e3a162
--- /dev/null
+++ b/src/gvcore/printdialog.cpp
@@ -0,0 +1,297 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/*
+Gwenview - printing support
+Copyright (c) 2003 Angelo Naselli
+
+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.
+
+*/
+// Qt
+#include <qcheckbox.h>
+#include <qlayout.h>
+#include <qpainter.h>
+#include <qradiobutton.h>
+#include <qvbuttongroup.h>
+
+// KDE
+#include <kcombobox.h>
+#include <kdebug.h>
+#include <kdialog.h>
+#include <klocale.h>
+#include <knuminput.h>
+#include <kprinter.h>
+
+// Local
+#include "document.h"
+#include "printdialogpagebase.h"
+#include "printdialog.moc"
+
+namespace Gwenview {
+
+
+const char* STR_TRUE="true";
+const char* STR_FALSE="false";
+
+static inline Unit stringToUnit(const QString& unit) {
+ if (unit == i18n("Millimeters")) {
+ return GV_MILLIMETERS;
+ } else if (unit == i18n("Centimeters")) {
+ return GV_CENTIMETERS;
+ } else {//Inches
+ return GV_INCHES;
+ }
+}
+
+static inline QString unitToString(Unit unit) {
+ if (unit == GV_MILLIMETERS) {
+ return i18n("Millimeters");
+ } else if (unit == GV_CENTIMETERS) {
+ return i18n("Centimeters");
+ } else { //GV_INCHES
+ return i18n("Inches");
+ }
+}
+
+
+static inline double unitToMM(Unit unit) {
+ if (unit == GV_MILLIMETERS) {
+ return 1.;
+ } else if (unit == GV_CENTIMETERS) {
+ return 10.;
+ } else { //GV_INCHES
+ return 25.4;
+ }
+}
+
+
+PrintDialogPage::PrintDialogPage( Document* document, QWidget *parent, const char *name )
+ : KPrintDialogPage( parent, name ) {
+ mDocument = document;
+ mContent = new PrintDialogPageBase(this);
+ setTitle( mContent->caption() );
+
+ QVBoxLayout *layout = new QVBoxLayout( this );
+ layout->addWidget( mContent );
+
+ connect(mContent->mWidth, SIGNAL( valueChanged( double )), SLOT( slotWidthChanged( double )));
+ connect(mContent->mHeight, SIGNAL( valueChanged( double )), SLOT( slotHeightChanged( double )));
+ connect(mContent->mKeepRatio, SIGNAL( toggled( bool )), SLOT( toggleRatio( bool )));
+ connect(mContent->mUnit, SIGNAL(activated(const QString &)), SLOT(slotUnitChanged(const QString& )));
+
+ mPreviousUnit = GV_MILLIMETERS;
+}
+
+PrintDialogPage::~PrintDialogPage() {}
+
+void PrintDialogPage::getOptions( QMap<QString,QString>& opts, bool /*incldef*/ ) {
+ opts["app-gwenview-position"] = QString::number(getPosition(mContent->mPosition->currentText()));
+ opts["app-gwenview-printFilename"] = mContent->mAddFileName->isChecked() ? STR_TRUE : STR_FALSE;
+ opts["app-gwenview-printComment"] = mContent->mAddComment->isChecked() ? STR_TRUE : STR_FALSE;
+ opts["app-gwenview-scale"] = QString::number(
+ mContent->mNoScale->isChecked() ? GV_NOSCALE
+ : mContent->mFitToPage->isChecked() ? GV_FITTOPAGE
+ : GV_SCALE);
+ opts["app-gwenview-fitToPage"] = mContent->mFitToPage->isChecked() ? STR_TRUE : STR_FALSE;
+ opts["app-gwenview-enlargeToFit"] = mContent->mEnlargeToFit->isChecked() ? STR_TRUE : STR_FALSE;
+
+ opts["app-gwenview-scaleKeepRatio"] = mContent->mKeepRatio->isChecked() ? STR_TRUE : STR_FALSE;
+ opts["app-gwenview-scaleUnit"] = QString::number(stringToUnit(mContent->mUnit->currentText()));
+ opts["app-gwenview-scaleWidth"] = QString::number( scaleWidth() );
+ opts["app-gwenview-scaleHeight"] = QString::number( scaleHeight() );
+
+}
+
+void PrintDialogPage::setOptions( const QMap<QString,QString>& opts ) {
+ int val;
+ bool ok;
+ QString stVal;
+
+ val = opts["app-gwenview-position"].toInt( &ok );
+ if (ok) {
+ stVal = setPosition(val);
+ mContent->mPosition->setCurrentItem(stVal);
+ }
+
+ mContent->mAddFileName->setChecked( opts["app-gwenview-printFilename"] == STR_TRUE );
+ mContent->mAddComment->setChecked( opts["app-gwenview-printComment"] == STR_TRUE );
+ // Starts from id 1 because 0 is returned if not ok, and seems to have a weird
+ // problem with id 2 (last id) otherwise :(
+ ScaleId scaleButtonId = static_cast<ScaleId>( opts["app-gwenview-scale"].toInt( &ok ) );
+ if (ok) {
+ mContent->mScaleGroup->setButton( scaleButtonId );
+ } else {
+ mContent->mScaleGroup->setButton( GV_NOSCALE );
+ }
+ mContent->mEnlargeToFit->setChecked( opts["app-gwenview-enlargeToFit"] == STR_TRUE );
+
+ Unit unit = static_cast<Unit>( opts["app-gwenview-scaleUnit"].toInt( &ok ) );
+ if (ok) {
+ stVal = unitToString(unit);
+ mContent->mUnit->setCurrentItem(stVal);
+ mPreviousUnit = unit;
+ }
+
+ mContent->mKeepRatio->setChecked( opts["app-gwenview-scaleKeepRatio"] == STR_TRUE );
+
+ double dbl;
+ dbl = opts["app-gwenview-scaleWidth"].toDouble( &ok );
+ if ( ok ) setScaleWidth( dbl );
+ dbl = opts["app-gwenview-scaleHeight"].toDouble( &ok );
+ if ( ok ) setScaleHeight( dbl );
+}
+
+double PrintDialogPage::scaleWidth() const {
+ return mContent->mWidth->value();
+}
+
+double PrintDialogPage::scaleHeight() const {
+ return mContent->mHeight->value();
+}
+
+void PrintDialogPage::setScaleWidth( double value ) {
+ mContent->mWidth->setValue(value);
+}
+
+void PrintDialogPage::setScaleHeight( double value ) {
+ mContent->mHeight->setValue(value);
+}
+
+int PrintDialogPage::getPosition(const QString& align) {
+ int alignment;
+
+ if (align == i18n("Central-Left")) {
+ alignment = Qt::AlignLeft | Qt::AlignVCenter;
+ } else if (align == i18n("Central-Right")) {
+ alignment = Qt::AlignRight | Qt::AlignVCenter;
+ } else if (align == i18n("Top-Left")) {
+ alignment = Qt::AlignTop | Qt::AlignLeft;
+ } else if (align == i18n("Top-Right")) {
+ alignment = Qt::AlignTop | Qt::AlignRight;
+ } else if (align == i18n("Bottom-Left")) {
+ alignment = Qt::AlignBottom | Qt::AlignLeft;
+ } else if (align == i18n("Bottom-Right")) {
+ alignment = Qt::AlignBottom | Qt::AlignRight;
+ } else if (align == i18n("Top-Central")) {
+ alignment = Qt::AlignTop | Qt::AlignHCenter;
+ } else if (align == i18n("Bottom-Central")) {
+ alignment = Qt::AlignBottom | Qt::AlignHCenter;
+ } else {
+ // Central
+ alignment = Qt::AlignCenter; // Qt::AlignHCenter || Qt::AlignVCenter
+ }
+
+ return alignment;
+}
+
+QString PrintDialogPage::setPosition(int align) {
+ QString alignment;
+
+ if (align == (Qt::AlignLeft | Qt::AlignVCenter)) {
+ alignment = i18n("Central-Left");
+ } else if (align == (Qt::AlignRight | Qt::AlignVCenter)) {
+ alignment = i18n("Central-Right");
+ } else if (align == (Qt::AlignTop | Qt::AlignLeft)) {
+ alignment = i18n("Top-Left");
+ } else if (align == (Qt::AlignTop | Qt::AlignRight)) {
+ alignment = i18n("Top-Right");
+ } else if (align == (Qt::AlignBottom | Qt::AlignLeft)) {
+ alignment = i18n("Bottom-Left");
+ } else if (align == (Qt::AlignBottom | Qt::AlignRight)) {
+ alignment = i18n("Bottom-Right");
+ } else if (align == (Qt::AlignTop | Qt::AlignHCenter)) {
+ alignment = i18n("Top-Central");
+ } else if (align == (Qt::AlignBottom | Qt::AlignHCenter)) {
+ alignment = i18n("Bottom-Central");
+ } else {
+ // Central: Qt::AlignCenter or (Qt::AlignHCenter || Qt::AlignVCenter)
+ alignment = i18n("Central");
+ }
+
+ return alignment;
+}
+
+// SLOTS
+void PrintDialogPage::slotHeightChanged (double value) {
+ mContent->mWidth->blockSignals(true);
+ mContent->mHeight->blockSignals(true);
+
+ if (mContent->mKeepRatio->isChecked()) {
+ double width = (mDocument->width() * value) / mDocument->height();
+ mContent->mWidth->setValue( width ? width : 1.);
+ }
+ mContent->mHeight->setValue(value);
+
+ mContent->mWidth->blockSignals(false);
+ mContent->mHeight->blockSignals(false);
+
+}
+
+void PrintDialogPage::slotWidthChanged (double value) {
+ mContent->mWidth->blockSignals(true);
+ mContent->mHeight->blockSignals(true);
+ if (mContent->mKeepRatio->isChecked()) {
+ double height = (mDocument->height() * value) / mDocument->width();
+ mContent->mHeight->setValue( height ? height : 1);
+ }
+ mContent->mWidth->setValue(value);
+ mContent->mWidth->blockSignals(false);
+ mContent->mHeight->blockSignals(false);
+}
+
+void PrintDialogPage::toggleRatio(bool enable) {
+ if (!enable) return;
+ // choosing a startup value of 15x10 cm (common photo dimention)
+ // mContent->mHeight->value() or mContent->mWidth->value()
+ // are usually empty at startup and hxw (0x0) isn't good IMO keeping ratio
+ double hValue, wValue;
+ if (mDocument->height() > mDocument->width()) {
+ hValue = mContent->mHeight->value();
+ if (!hValue) hValue = 150*unitToMM(mPreviousUnit);
+ wValue = (mDocument->width() * hValue)/ mDocument->height();
+ } else {
+ wValue = mContent->mWidth->value();
+ if (!wValue) wValue = 150*unitToMM(mPreviousUnit);
+ hValue = (mDocument->height() * wValue)/ mDocument->width();
+ }
+
+ mContent->mWidth->blockSignals(true);
+ mContent->mHeight->blockSignals(true);
+ mContent->mWidth->setValue(wValue);
+ mContent->mHeight->setValue(hValue);
+ mContent->mWidth->blockSignals(false);
+ mContent->mHeight->blockSignals(false);
+}
+
+
+void PrintDialogPage::slotUnitChanged(const QString& string) {
+ Unit newUnit = stringToUnit(string);
+ double ratio = unitToMM(mPreviousUnit) / unitToMM(newUnit);
+
+ mContent->mWidth->blockSignals(true);
+ mContent->mHeight->blockSignals(true);
+
+ mContent->mWidth->setValue( mContent->mWidth->value() * ratio);
+ mContent->mHeight->setValue( mContent->mHeight->value() * ratio);
+
+ mContent->mWidth->blockSignals(false);
+ mContent->mHeight->blockSignals(false);
+
+ mPreviousUnit = newUnit;
+}
+
+
+
+
+} // namespace
diff --git a/src/gvcore/printdialog.h b/src/gvcore/printdialog.h
new file mode 100644
index 0000000..261583f
--- /dev/null
+++ b/src/gvcore/printdialog.h
@@ -0,0 +1,80 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/*
+Gwenview - printing support
+Copyright (c) 2003 Angelo Naselli
+
+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.
+
+*/
+#ifndef PRINTDIALOG_H
+#define PRINTDIALOG_H
+
+//Qt
+#include <qfontmetrics.h>
+#include <qstring.h>
+
+// KDE
+#include <kdockwidget.h>
+#include <kdeprint/kprintdialogpage.h>
+
+#include "libgwenview_export.h"
+class PrintDialogPageBase;
+namespace Gwenview {
+class Document;
+
+enum Unit {
+ GV_MILLIMETERS = 1,
+ GV_CENTIMETERS,
+ GV_INCHES
+};
+
+enum ScaleId {
+ GV_NOSCALE=1,
+ GV_FITTOPAGE,
+ GV_SCALE
+};
+
+class LIBGWENVIEW_EXPORT PrintDialogPage : public KPrintDialogPage {
+ Q_OBJECT
+
+public:
+ PrintDialogPage( Document* document, QWidget *parent = 0L, const char *name = 0 );
+ ~PrintDialogPage();
+
+ virtual void getOptions(QMap<QString,QString>& opts, bool incldef = false);
+ virtual void setOptions(const QMap<QString,QString>& opts);
+
+private slots:
+ void toggleRatio(bool enable);
+ void slotUnitChanged(const QString& string);
+ void slotHeightChanged(double value);
+ void slotWidthChanged(double value);
+
+private:
+ double scaleWidth() const;
+ double scaleHeight() const;
+ void setScaleWidth(double pixels);
+ void setScaleHeight(double pixels);
+ int getPosition(const QString& align);
+ QString setPosition(int align);
+
+ Document *mDocument;
+ PrintDialogPageBase* mContent;
+ Unit mPreviousUnit;
+};
+
+} // namespace
+#endif
+
diff --git a/src/gvcore/printdialogpagebase.ui b/src/gvcore/printdialogpagebase.ui
new file mode 100644
index 0000000..f5d5c8c
--- /dev/null
+++ b/src/gvcore/printdialogpagebase.ui
@@ -0,0 +1,408 @@
+<!DOCTYPE UI><UI version="3.2" stdsetdef="1">
+<class>PrintDialogPageBase</class>
+<widget class="QWidget">
+ <property name="name">
+ <cstring>PrintDialogPageBase</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>511</width>
+ <height>260</height>
+ </rect>
+ </property>
+ <property name="caption">
+ <string>Image Settings</string>
+ </property>
+ <vbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <widget class="QLayoutWidget">
+ <property name="name">
+ <cstring>layout2</cstring>
+ </property>
+ <hbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QLabel">
+ <property name="name">
+ <cstring>textLabel1</cstring>
+ </property>
+ <property name="text">
+ <string>Image position:</string>
+ </property>
+ </widget>
+ <widget class="KComboBox">
+ <item>
+ <property name="text">
+ <string>Top-Left</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Top-Central</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Top-Right</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Central-Left</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Central</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Central-Right</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Bottom-Left</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Bottom-Central</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Bottom-Right</string>
+ </property>
+ </item>
+ <property name="name">
+ <cstring>mPosition</cstring>
+ </property>
+ </widget>
+ <spacer>
+ <property name="name">
+ <cstring>spacer1</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Expanding</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>101</width>
+ <height>21</height>
+ </size>
+ </property>
+ </spacer>
+ </hbox>
+ </widget>
+ <widget class="QCheckBox">
+ <property name="name">
+ <cstring>mAddFileName</cstring>
+ </property>
+ <property name="text">
+ <string>Print fi&amp;lename below image</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ <widget class="QCheckBox">
+ <property name="name">
+ <cstring>mAddComment</cstring>
+ </property>
+ <property name="text">
+ <string>Print image comment</string>
+ </property>
+ <property name="accel">
+ <string></string>
+ </property>
+ </widget>
+ <widget class="QButtonGroup">
+ <property name="name">
+ <cstring>mScaleGroup</cstring>
+ </property>
+ <property name="title">
+ <string>Scaling</string>
+ </property>
+ <vbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QRadioButton">
+ <property name="name">
+ <cstring>mNoScale</cstring>
+ </property>
+ <property name="text">
+ <string>&amp;No scaling</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ <property name="buttonGroupId">
+ <number>1</number>
+ </property>
+ </widget>
+ <widget class="QRadioButton">
+ <property name="name">
+ <cstring>mFitToPage</cstring>
+ </property>
+ <property name="text">
+ <string>&amp;Fit image to page</string>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ <property name="buttonGroupId">
+ <number>2</number>
+ </property>
+ </widget>
+ <widget class="QLayoutWidget">
+ <property name="name">
+ <cstring>layout4</cstring>
+ </property>
+ <hbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <spacer>
+ <property name="name">
+ <cstring>spacer4</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Fixed</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ <widget class="QCheckBox">
+ <property name="name">
+ <cstring>mEnlargeToFit</cstring>
+ </property>
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Enlarge smaller images</string>
+ </property>
+ </widget>
+ <spacer>
+ <property name="name">
+ <cstring>spacer6</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Expanding</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>240</width>
+ <height>21</height>
+ </size>
+ </property>
+ </spacer>
+ </hbox>
+ </widget>
+ <widget class="QRadioButton">
+ <property name="name">
+ <cstring>mScale</cstring>
+ </property>
+ <property name="text">
+ <string>&amp;Scale to:</string>
+ </property>
+ <property name="buttonGroupId">
+ <number>3</number>
+ </property>
+ </widget>
+ <widget class="QLayoutWidget">
+ <property name="name">
+ <cstring>layout4</cstring>
+ </property>
+ <hbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <spacer>
+ <property name="name">
+ <cstring>spacer4_2</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Fixed</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ <widget class="KDoubleSpinBox">
+ <property name="name">
+ <cstring>mWidth</cstring>
+ </property>
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="maxValue">
+ <number>1e+06</number>
+ </property>
+ <property name="lineStep">
+ <number>1</number>
+ </property>
+ <property name="acceptLocalizedNumbers">
+ <bool>false</bool>
+ </property>
+ <property name="precision">
+ <number>2</number>
+ </property>
+ </widget>
+ <widget class="QLabel">
+ <property name="name">
+ <cstring>textLabel2</cstring>
+ </property>
+ <property name="text">
+ <string>x</string>
+ </property>
+ </widget>
+ <widget class="KDoubleSpinBox">
+ <property name="name">
+ <cstring>mHeight</cstring>
+ </property>
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="maxValue">
+ <number>1e+06</number>
+ </property>
+ <property name="lineStep">
+ <number>1</number>
+ </property>
+ <property name="acceptLocalizedNumbers">
+ <bool>false</bool>
+ </property>
+ <property name="precision">
+ <number>2</number>
+ </property>
+ </widget>
+ <widget class="KComboBox">
+ <item>
+ <property name="text">
+ <string>Millimeters</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Centimeters</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Inches</string>
+ </property>
+ </item>
+ <property name="name">
+ <cstring>mUnit</cstring>
+ </property>
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ </widget>
+ <widget class="QCheckBox">
+ <property name="name">
+ <cstring>mKeepRatio</cstring>
+ </property>
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Keep ratio</string>
+ </property>
+ </widget>
+ <spacer>
+ <property name="name">
+ <cstring>spacer2</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Expanding</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>16</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </hbox>
+ </widget>
+ </vbox>
+ </widget>
+ </vbox>
+</widget>
+<customwidgets>
+</customwidgets>
+<connections>
+ <connection>
+ <sender>mScale</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>mUnit</receiver>
+ <slot>setEnabled(bool)</slot>
+ </connection>
+ <connection>
+ <sender>mScale</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>mKeepRatio</receiver>
+ <slot>setEnabled(bool)</slot>
+ </connection>
+ <connection>
+ <sender>mFitToPage</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>mEnlargeToFit</receiver>
+ <slot>setEnabled(bool)</slot>
+ </connection>
+ <connection>
+ <sender>mScale</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>mWidth</receiver>
+ <slot>setEnabled(bool)</slot>
+ </connection>
+ <connection>
+ <sender>mScale</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>mHeight</receiver>
+ <slot>setEnabled(bool)</slot>
+ </connection>
+</connections>
+<layoutdefaults spacing="6" margin="11"/>
+<includehints>
+ <includehint>kcombobox.h</includehint>
+ <includehint>knuminput.h</includehint>
+ <includehint>knuminput.h</includehint>
+ <includehint>kcombobox.h</includehint>
+</includehints>
+</UI>
diff --git a/src/gvcore/qxcfi.cpp b/src/gvcore/qxcfi.cpp
new file mode 100644
index 0000000..2a771bc
--- /dev/null
+++ b/src/gvcore/qxcfi.cpp
@@ -0,0 +1,2405 @@
+/*
+ * qxcfi.cpp: A Qt 3 plug-in for reading GIMP XCF image files
+ * Copyright (C) 2001 lignum Computing, Inc. <allen@lignumcomputing.com>
+ * $Id: qxcfi.cpp 531593 2006-04-19 15:46:52Z gateau $
+ *
+ * This plug-in is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <qiodevice.h>
+#include <kdeversion.h>
+#include <stdlib.h>
+#include "qxcfi.h"
+
+
+// Change a QRgb value's alpha only. (an optimization)
+inline QRgb qRgba ( QRgb rgb, int a )
+{
+ return ( ( a & 0xff ) << 24 | ( rgb & RGB_MASK ) );
+}
+
+
+namespace Gwenview {
+
+int SafeDataStream::at() const {
+ return mDevice->at();
+}
+
+const float INCHESPERMETER = (100. / 2.54);
+
+// Static global values
+
+int XCFImageFormat::random_table[RANDOM_TABLE_SIZE];
+
+int XCFImageFormat::add_lut[256][256];
+
+XCFImageFormat::LayerModes XCFImageFormat::layer_modes[] = {
+ { true }, // NORMAL_MODE
+ { true }, // DISSOLVE_MODE
+ { true }, // BEHIND_MODE
+ { false }, // MULTIPLY_MODE
+ { false }, // SCREEN_MODE
+ { false }, // OVERLAY_MODE
+ { false }, // DIFFERENCE_MODE
+ { false }, // ADDITION_MODE
+ { false }, // SUBTRACT_MODE
+ { false }, // DARKEN_ONLY_MODE
+ { false }, // LIGHTEN_ONLY_MODE
+ { false }, // HUE_MODE
+ { false }, // SATURATION_MODE
+ { false }, // COLOR_MODE
+ { false }, // VALUE_MODE
+ { false }, // DIVIDE_MODE
+ { true }, // ERASE_MODE
+ { true }, // REPLACE_MODE
+ { true }, // ANTI_ERASE_MODE
+};
+
+//////////////////////////////////////////////////////////////////////////////////
+// From GIMP "paint_funcs.c" v1.2
+
+/*!
+ * Multiply two color components. Really expects the arguments to be
+ * 8-bit quantities.
+ * \param a first minuend.
+ * \param b second minuend.
+ * \return product of arguments.
+ */
+inline int INT_MULT ( int a, int b )
+{
+ int c = a * b + 0x80;
+ return ( ( c >> 8 ) + c ) >> 8;
+}
+
+/*!
+ * Blend the two color components in the proportion alpha:
+ *
+ * result = alpha a + ( 1 - alpha b)
+ *
+ * \param a first component.
+ * \param b second component.
+ * \param alpha blend proportion.
+ * \return blended color components.
+ */
+
+inline int INT_BLEND ( int a, int b, int alpha )
+{
+ return INT_MULT( a - b, alpha ) + b;
+}
+
+// Actually from GLIB
+
+inline int MIN ( int a, int b )
+{
+ return ( a < b ? a : b );
+}
+
+inline int MAX ( int a, int b )
+{
+ return ( a > b ? a : b );
+}
+
+// From GIMP "gimpcolorspace.c" v1.2
+
+/*!
+ * Convert a color in RGB space to HSV space (Hue, Saturation, Value).
+ * \param red the red component (modified in place).
+ * \param green the green component (modified in place).
+ * \param blue the blue component (modified in place).
+ */
+void RGBTOHSV ( uchar& red, uchar& green, uchar& blue )
+{
+ int r, g, b;
+ double h, s, v;
+ int min, max;
+
+ h = 0.;
+
+ r = red;
+ g = green;
+ b = blue;
+
+ if ( r > g ) {
+ max = MAX( r, b );
+ min = MIN( g, b );
+ }
+ else {
+ max = MAX( g, b );
+ min = MIN( r, b );
+ }
+
+ v = max;
+
+ if ( max != 0 )
+ s = ( ( max - min ) * 255 ) / (double)max;
+ else
+ s = 0;
+
+ if ( s == 0 )
+ h = 0;
+ else {
+ int delta = max - min;
+ if ( r == max )
+ h = ( g - b ) / (double)delta;
+ else if ( g == max )
+ h = 2 + ( b - r ) / (double)delta;
+ else if ( b == max )
+ h = 4 + ( r - g ) / (double)delta;
+ h *= 42.5;
+
+ if ( h < 0 )
+ h += 255;
+ if ( h > 255 )
+ h -= 255;
+ }
+
+ red = (uchar)h;
+ green = (uchar)s;
+ blue = (uchar)v;
+}
+
+/*!
+ * Convert a color in HSV space to RGB space.
+ * \param hue the hue component (modified in place).
+ * \param saturation the saturation component (modified in place).
+ * \param value the value component (modified in place).
+ */
+void HSVTORGB ( uchar& hue, uchar& saturation, uchar& value )
+{
+ if ( saturation == 0 ) {
+ hue = value;
+ saturation = value;
+ value = value;
+ }
+ else {
+ double h = hue * 6. / 255.;
+ double s = saturation / 255.;
+ double v = value / 255.;
+
+ double f = h - (int)h;
+ double p = v * ( 1. - s );
+ double q = v * ( 1. - ( s * f ) );
+ double t = v * ( 1. - ( s * ( 1. - f ) ) );
+
+ // Worth a note here that gcc 2.96 will generate different results
+ // depending on optimization mode on i386.
+
+ switch ((int)h) {
+ case 0:
+ hue = (uchar)( v * 255 );
+ saturation = (uchar)( t * 255 );
+ value = (uchar)( p * 255 );
+ break;
+ case 1:
+ hue = (uchar)( q * 255 );
+ saturation = (uchar)( v * 255 );
+ value = (uchar)( p * 255 );
+ break;
+ case 2:
+ hue = (uchar)( p * 255 );
+ saturation = (uchar)( v * 255 );
+ value = (uchar)( t * 255 );
+ break;
+ case 3:
+ hue = (uchar)( p * 255 );
+ saturation = (uchar)( q * 255 );
+ value = (uchar)( v * 255 );
+ break;
+ case 4:
+ hue = (uchar)( t * 255 );
+ saturation = (uchar)( p * 255 );
+ value = (uchar)( v * 255 );
+ break;
+ case 5:
+ hue = (uchar)( v * 255 );
+ saturation = (uchar)( p * 255 );
+ value = (uchar)( q * 255 );
+ }
+ }
+}
+
+/*!
+ * Convert a color in RGB space to HLS space (Hue, Lightness, Saturation).
+ * \param red the red component (modified in place).
+ * \param green the green component (modified in place).
+ * \param blue the blue component (modified in place).
+ */
+void RGBTOHLS ( uchar& red, uchar& green, uchar& blue )
+{
+ int r = red;
+ int g = green;
+ int b = blue;
+
+ int min, max;
+
+ if ( r > g ) {
+ max = MAX( r, b );
+ min = MIN( g, b );
+ }
+ else {
+ max = MAX( g, b );
+ min = MIN( r, b );
+ }
+
+ double h;
+ double l = ( max + min ) / 2.;
+ double s;
+
+ if ( max == min ) {
+ s = 0.;
+ h = 0.;
+ }
+ else {
+ int delta = max - min;
+
+ if ( l < 128 )
+ s = 255 * (double)delta / (double)( max + min );
+ else
+ s = 255 * (double)delta / (double)( 511 - max - min );
+
+ if ( r == max )
+ h = ( g - b ) / (double)delta;
+ else if ( g == max )
+ h = 2 + ( b - r ) / (double)delta;
+ else
+ h = 4 + ( r - g ) / (double)delta;
+
+ h *= 42.5;
+
+ if ( h < 0 )
+ h += 255;
+ else if ( h > 255 )
+ h -= 255;
+ }
+
+ red = (uchar)h;
+ green = (uchar)l;
+ blue = (uchar)s;
+}
+
+/*!
+ * Implement the HLS "double hex-cone".
+ * \param n1 lightness fraction (?)
+ * \param n2 saturation fraction (?)
+ * \param hue hue "angle".
+ * \return HLS value.
+ */
+int HLSVALUE ( double n1, double n2, double hue )
+{
+ double value;
+
+ if ( hue > 255 )
+ hue -= 255;
+ else if ( hue < 0 )
+ hue += 255;
+
+ if ( hue < 42.5 )
+ value = n1 + ( n2 - n1 ) * ( hue / 42.5 );
+ else if ( hue < 127.5 )
+ value = n2;
+ else if ( hue < 170 )
+ value = n1 + ( n2 - n1 ) * ( ( 170 - hue ) / 42.5 );
+ else
+ value = n1;
+
+ return (int)( value * 255 );
+}
+
+/*!
+ * Convert a color in HLS space to RGB space.
+ * \param hue the hue component (modified in place).
+ * \param lightness the lightness component (modified in place).
+ * \param saturation the saturation component (modified in place).
+ */
+void HLSTORGB ( uchar& hue, uchar& lightness, uchar& saturation )
+{
+ double h = hue;
+ double l = lightness;
+ double s = saturation;
+
+ if ( s == 0 ) {
+ hue = (uchar)l;
+ lightness = (uchar)l;
+ saturation = (uchar)l;
+ }
+ else {
+ double m1, m2;
+
+ if ( l < 128 )
+ m2 = ( l * ( 255 + s ) ) / 65025.;
+ else
+ m2 = ( l + s - ( l * s ) / 255. ) / 255.;
+
+ m1 = ( l / 127.5 ) - m2;
+
+ hue = HLSVALUE( m1, m2, h + 85 );
+ lightness = HLSVALUE( m1, m2, h );
+ saturation = HLSVALUE( m1, m2, h - 85 );
+ }
+}
+//////////////////////////////////////////////////////////////////////////////////
+
+
+XCFImageFormat::XCFImageFormat() {
+ // From GIMP "paint_funcs.c" v1.2
+ srand( RANDOM_SEED );
+
+ for ( int i = 0; i < RANDOM_TABLE_SIZE; i++ )
+ random_table[i] = rand();
+
+ for ( int i = 0; i < RANDOM_TABLE_SIZE; i++ ) {
+ int tmp;
+ int swap = i + rand() % ( RANDOM_TABLE_SIZE - i );
+ tmp = random_table[i];
+ random_table[i] = random_table[swap];
+ random_table[swap] = tmp;
+ }
+
+ for ( int j = 0; j < 256; j++ ) {
+ for ( int k = 0; k < 256; k++ ) {
+ int tmp_sum = j + k;
+ if ( tmp_sum > 255 )
+ tmp_sum = 255;
+ add_lut[j][k] = tmp_sum;
+ }
+ }
+ }
+
+
+bool XCFImageFormat::installIOHandler ( const QString& ) {
+ QImageIO::defineIOHandler( "XCF", "gimp xcf", 0,
+ &XCFImageFormat::readXCF,
+#ifdef TMP_WRITE
+ &XCFImageFormat::writeXCF );
+#else
+ 0 );
+#endif
+ return true;
+}
+
+
+void XCFImageFormat::registerFormat() {
+ QImageIO::defineIOHandler( "XCF","^gimp xcf",
+ 0,XCFImageFormat::readXCF,0L);
+}
+
+
+/*!
+ * The Qt QImageIO architecture invokes this routine to read the image.
+ * The file (or other data stream) is already open and the
+ * initial string indicating a XCF file has been matched (but the stream
+ * is positioned at its beginning).
+ *
+ * The XCF file is binary and is stored in big endian format. The
+ * SafeDataStream class is used to read the file. Even though the XCF file
+ * was not written with SafeDataStream, there is still a good match. At least
+ * in version 001 of XCF and version 4 of SafeDataStream. Any other combination
+ * is suspect.
+ *
+ * Any failures while reading the XCF image are reported by the
+ * QImage::status() method.
+ *
+ * \param image_io the QImageIO object connected to the XCF image.
+ */
+void XCFImageFormat::readXCF ( QImageIO* image_io )
+{
+ XCFImage xcf_image;
+
+ // The XCF data is stored in big endian format, which SafeDataStream handles
+ // very well.
+
+ SafeDataStream xcf_io( image_io->ioDevice() );
+
+ char tag[14];
+ xcf_io.readRawBytes( tag, sizeof(tag) );
+
+ if ( xcf_io.failed() ) {
+ qDebug( "XCF: read failure on header tag" );
+ return;
+ }
+
+ xcf_io >> xcf_image.width >> xcf_image.height >> xcf_image.type;
+
+ if ( xcf_io.failed() ) {
+ qDebug( "XCF: read failure on image info" );
+ return;
+ }
+
+ if ( !loadImageProperties( xcf_io, xcf_image ) ) return;
+
+ // The layers appear to be stored in top-to-bottom order. This is
+ // the reverse of how a merged image must be computed. So, the layer
+ // offsets are pushed onto a LIFO stack (thus, we don't have to load
+ // all the data of all layers before beginning to construct the
+ // merged image).
+
+ QValueStack< Q_INT32 > layer_offsets;
+
+ while ( true ) {
+ Q_INT32 layer_offset;
+
+ xcf_io >> layer_offset;
+
+ if ( xcf_io.failed() ) {
+ qDebug( "XCF: read failure on layer offsets" );
+ return;
+ }
+
+ if ( layer_offset == 0 ) break;
+
+ layer_offsets.push( layer_offset );
+ }
+
+ xcf_image.num_layers = layer_offsets.size();
+
+ if ( layer_offsets.size() == 0 ) {
+ qDebug( "XCF: no layers!" );
+ return;
+ }
+
+ // Load each layer and add it to the image
+
+ while ( !layer_offsets.isEmpty() ) {
+ Q_INT32 layer_offset = layer_offsets.pop();
+
+ xcf_io.device()->at( layer_offset );
+
+ if ( !loadLayer( xcf_io, xcf_image ) ) return;
+ }
+
+ if ( !xcf_image.initialized ) {
+ qDebug( "XCF: no visible layers!" );
+ return;
+ }
+
+ image_io->setImage( xcf_image.image );
+ image_io->setStatus( 0 );
+}
+
+/*!
+ * Construct the QImage which will eventually be returned to the QImage
+ * loader.
+ *
+ * There are a couple of situations which require that the QImage is not
+ * exactly the same as The GIMP's representation. The full table is:
+ * \verbatim
+ * Grayscale opaque : 8 bpp indexed
+ * Grayscale translucent : 32 bpp + alpha
+ * Indexed opaque : 1 bpp if num_colors <= 2
+ * : 8 bpp indexed otherwise
+ * Indexed translucent : 8 bpp indexed + alpha if num_colors < 256
+ * : 32 bpp + alpha otherwise
+ * RGB opaque : 32 bpp
+ * RGBA translucent : 32 bpp + alpha
+ * \endverbatim
+ * Whether the image is translucent or not is determined by the bottom layer's
+ * alpha channel. However, even if the bottom layer lacks an alpha channel,
+ * it can still have an opacity < 1. In this case, the QImage is promoted
+ * to 32-bit. (Note this is different from the output from the GIMP image
+ * exporter, which seems to ignore this attribute.)
+ *
+ * Independently, higher layers can be translucent, but the background of
+ * the image will not show through if the bottom layer is opaque.
+ *
+ * For indexed images, translucency is an all or nothing effect.
+ * \param xcf_image contains image info and bottom-most layer.
+ */
+void XCFImageFormat::initializeImage ( XCFImage& xcf_image )
+{
+ // (Aliases to make the code look a little better.)
+ Layer& layer( xcf_image.layer );
+ QImage& image( xcf_image.image );
+
+ switch ( layer.type ) {
+ case RGB_GIMAGE:
+ if ( layer.opacity == OPAQUE_OPACITY ) {
+ image.create( xcf_image.width, xcf_image.height, 32 );
+ image.fill( qRgb( 255, 255, 255 ) );
+ break;
+ } // else, fall through to 32-bit representation
+
+ case RGBA_GIMAGE:
+ image.create( xcf_image.width, xcf_image.height, 32 );
+ image.fill( qRgba( 255, 255, 255, 0 ) );
+ // Turning this on prevents fill() from affecting the alpha channel,
+ // by the way.
+ image.setAlphaBuffer( true );
+ break;
+
+ case GRAY_GIMAGE:
+ if ( layer.opacity == OPAQUE_OPACITY ) {
+ image.create( xcf_image.width, xcf_image.height, 8, 256 );
+ setGrayPalette( image );
+ image.fill( 255 );
+ break;
+ } // else, fall through to 32-bit representation
+
+ case GRAYA_GIMAGE:
+ image.create( xcf_image.width, xcf_image.height, 32 );
+ image.fill( qRgba( 255, 255, 255, 0 ) );
+ image.setAlphaBuffer( true );
+ break;
+
+ case INDEXED_GIMAGE:
+ // As noted in the table above, there are quite a few combinations
+ // which are possible with indexed images, depending on the
+ // presence of transparency (note: not translucency, which is not
+ // supported by The GIMP for indexed images) and the number of
+ // individual colors.
+
+ // Note: Qt treats a bitmap with a Black and White color palette
+ // as a mask, so only the "on" bits are drawn, regardless of the
+ // order color table entries. Otherwise (i.e., at least one of the
+ // color table entries is not black or white), it obeys the one-
+ // or two-color palette. Have to ask about this...
+
+ if ( xcf_image.num_colors <= 2 ) {
+ image.create( xcf_image.width, xcf_image.height,
+ 1, xcf_image.num_colors,
+ QImage::LittleEndian );
+ image.fill( 0 );
+ setPalette( xcf_image, image );
+ }
+
+ else if ( xcf_image.num_colors <= 256 ) {
+ image.create( xcf_image.width, xcf_image.height,
+ 8, xcf_image.num_colors,
+ QImage::LittleEndian );
+ image.fill( 0 );
+ setPalette( xcf_image, image );
+ }
+
+ break;
+
+ case INDEXEDA_GIMAGE:
+
+ if ( xcf_image.num_colors == 1 ) {
+
+ // Plenty(!) of room to add a transparent color
+
+ xcf_image.num_colors++;
+ xcf_image.palette.resize( xcf_image.num_colors );
+ xcf_image.palette[1] = xcf_image.palette[0];
+ xcf_image.palette[0] = qRgba( 255, 255, 255, 0 );
+
+ image.create( xcf_image.width, xcf_image.height,
+ 1, xcf_image.num_colors,
+ QImage::LittleEndian );
+ image.fill( 0 );
+ setPalette( xcf_image, image );
+ image.setAlphaBuffer( true );
+ }
+
+ else if ( xcf_image.num_colors < 256 ) {
+
+ // Plenty of room to add a transparent color
+
+ xcf_image.num_colors++;
+ xcf_image.palette.resize( xcf_image.num_colors );
+ for ( int c = xcf_image.num_colors-1; c >= 1; c-- )
+ xcf_image.palette[c] = xcf_image.palette[c-1];
+ xcf_image.palette[0] = qRgba( 255, 255, 255, 0 );
+
+ image.create( xcf_image.width, xcf_image.height,
+ 8, xcf_image.num_colors );
+ image.fill( 0 );
+ setPalette( xcf_image, image );
+ image.setAlphaBuffer( true );
+ }
+
+ else {
+ // No room for a transparent color, so this has to be promoted to
+ // true color. (There is no equivalent PNG representation output
+ // from The GIMP as of v1.2.)
+ image.create( xcf_image.width, xcf_image.height, 32 );
+ image.fill( qRgba( 255, 255, 255, 0 ) );
+ image.setAlphaBuffer( true );
+ }
+
+ break;
+ }
+
+ image.setDotsPerMeterX( (int)( xcf_image.x_resolution * INCHESPERMETER ) );
+ image.setDotsPerMeterY( (int)( xcf_image.y_resolution * INCHESPERMETER ) );
+}
+
+/*!
+ * Compute the number of tiles in the current layer and allocate
+ * QImage structures for each of them.
+ * \param xcf_image contains the current layer.
+ */
+void XCFImageFormat::composeTiles ( XCFImage& xcf_image )
+{
+ Layer& layer( xcf_image.layer );
+
+ layer.nrows = ( layer.height + TILE_HEIGHT - 1 ) / TILE_HEIGHT;
+ layer.ncols = ( layer.width + TILE_WIDTH - 1 ) / TILE_WIDTH;
+
+ layer.image_tiles.resize( layer.nrows );
+
+ if ( layer.type == GRAYA_GIMAGE || layer.type == INDEXEDA_GIMAGE )
+ layer.alpha_tiles.resize( layer.nrows );
+
+ if ( layer.mask_offset != 0 )
+ layer.mask_tiles.resize( layer.nrows );
+
+ for ( uint j = 0; j < layer.nrows; j++ ) {
+ layer.image_tiles[j].resize( layer.ncols );
+
+ if ( layer.type == GRAYA_GIMAGE || layer.type == INDEXEDA_GIMAGE )
+ layer.alpha_tiles[j].resize( layer.ncols );
+
+ if ( layer.mask_offset != 0 )
+ layer.mask_tiles[j].resize( layer.ncols );
+ }
+
+ for ( uint j = 0; j < layer.nrows; j++ ) {
+ for ( uint i = 0; i < layer.ncols; i++ ) {
+
+ uint tile_width = (i+1) * TILE_WIDTH <= layer.width ?
+ TILE_WIDTH : layer.width - i*TILE_WIDTH;
+
+ uint tile_height = (j+1) * TILE_HEIGHT <= layer.height ?
+ TILE_HEIGHT : layer.height - j*TILE_HEIGHT;
+
+ // Try to create the most appropriate QImage (each GIMP layer
+ // type is treated slightly differently)
+
+ switch ( layer.type ) {
+ case RGB_GIMAGE:
+ layer.image_tiles[j][i] = QImage( tile_width, tile_height, 32, 0 );
+ layer.image_tiles[j][i].setAlphaBuffer( false );
+ break;
+
+ case RGBA_GIMAGE:
+ layer.image_tiles[j][i] = QImage( tile_width, tile_height, 32, 0 );
+ layer.image_tiles[j][i].setAlphaBuffer( true );
+ break;
+
+ case GRAY_GIMAGE:
+ layer.image_tiles[j][i] = QImage( tile_width, tile_height, 8, 256 );
+ setGrayPalette( layer.image_tiles[j][i] );
+ break;
+
+ case GRAYA_GIMAGE:
+ layer.image_tiles[j][i] = QImage( tile_width, tile_height, 8, 256 );
+ setGrayPalette( layer.image_tiles[j][i] );
+
+ layer.alpha_tiles[j][i] = QImage( tile_width, tile_height, 8, 256 );
+ setGrayPalette( layer.alpha_tiles[j][i] );
+ break;
+
+ case INDEXED_GIMAGE:
+ layer.image_tiles[j][i] = QImage( tile_width, tile_height, 8,
+ xcf_image.num_colors );
+ setPalette( xcf_image, layer.image_tiles[j][i] );
+ break;
+
+ case INDEXEDA_GIMAGE:
+ layer.image_tiles[j][i] = QImage( tile_width, tile_height, 8,
+ xcf_image.num_colors );
+ setPalette( xcf_image, layer.image_tiles[j][i] );
+
+ layer.alpha_tiles[j][i] = QImage( tile_width, tile_height, 8, 256 );
+ setGrayPalette( layer.alpha_tiles[j][i] );
+ }
+
+ if ( layer.mask_offset != 0 ) {
+ layer.mask_tiles[j][i] = QImage( tile_width, tile_height, 8, 256 );
+ setGrayPalette( layer.mask_tiles[j][i] );
+ }
+ }
+ }
+}
+
+/*!
+ * Apply a grayscale palette to the QImage. Note that Qt does not distinguish
+ * between grayscale and indexed images. A grayscale image is just
+ * an indexed image with a 256-color, grayscale palette.
+ * \param image image to set to a grayscale palette.
+ */
+void XCFImageFormat::setGrayPalette ( QImage& image )
+{
+ for ( int i = 0; i < 256; i++ )
+ image.setColor( i, qRgb(i,i,i) );
+}
+
+/*!
+ * Copy the indexed palette from the XCF image into the QImage.
+ * \param xcf_image XCF image containing the palette read from the data stream.
+ * \param image image to apply the palette to.
+ */
+void XCFImageFormat::setPalette ( XCFImage& xcf_image, QImage& image )
+{
+ for ( int i = 0; i < xcf_image.num_colors; i++ )
+ image.setColor( i, xcf_image.palette[i] );
+}
+
+/*!
+ * An XCF file can contain an arbitrary number of properties associated
+ * with the image (and layer and mask).
+ * \param xcf_io the data stream connected to the XCF image
+ * \param xcf_image XCF image data.
+ * \return true if there were no I/O errors.
+ */
+bool XCFImageFormat::loadImageProperties ( SafeDataStream& xcf_io,
+ XCFImage& xcf_image )
+{
+ while ( true ) {
+ PropType type;
+ QByteArray bytes;
+
+ if ( !loadProperty( xcf_io, type, bytes ) ) {
+ qDebug( "XCF: error loading global image properties" );
+ return false;
+ }
+
+ QDataStream property( bytes, IO_ReadOnly );
+
+ switch ( type ) {
+ case PROP_END:
+ return true;
+
+ case PROP_COMPRESSION:
+ property >> xcf_image.compression;
+ break;
+
+ case PROP_GUIDES:
+ // This property is ignored.
+ break;
+
+ case PROP_RESOLUTION:
+ property >> xcf_image.x_resolution >> xcf_image.y_resolution;
+ break;
+
+ case PROP_TATTOO:
+ property >> xcf_image.tattoo;
+ break;
+
+ case PROP_PARASITES:
+ while ( !property.atEnd() ) {
+ char* tag;
+ Q_UINT32 size;
+
+ property.readBytes( tag, size );
+
+ Q_UINT32 flags;
+ char* data;
+ property >> flags >> data;
+
+ if ( strcmp( tag, "gimp-comment" ) == 0 )
+ xcf_image.image.setText( "Comment", 0, data );
+
+ delete[] tag;
+ delete[] data;
+ }
+ break;
+
+ case PROP_UNIT:
+ property >> xcf_image.unit;
+ break;
+
+ case PROP_PATHS:
+ // This property is ignored.
+ break;
+
+ case PROP_USER_UNIT:
+ // This property is ignored.
+ break;
+
+ case PROP_COLORMAP:
+ property >> xcf_image.num_colors;
+
+ xcf_image.palette.reserve( xcf_image.num_colors );
+
+ for ( int i = 0; i < xcf_image.num_colors; i++ ) {
+ uchar r, g, b;
+ property >> r >> g >> b;
+ xcf_image.palette.push_back( qRgb(r,g,b) );
+ }
+ break;
+
+ default:
+ qDebug( "XCF: unimplemented image property %d, size %d", type, bytes.size() );
+ }
+ }
+}
+
+/*!
+ * Load a layer from the XCF file. The data stream must be positioned at
+ * the beginning of the layer data.
+ * \param xcf_io the image file data stream.
+ * \param xcf_image contains the layer and the color table
+ * (if the image is indexed).
+ * \return true if there were no I/O errors.
+ */
+bool XCFImageFormat::loadLayer ( SafeDataStream& xcf_io, XCFImage& xcf_image )
+{
+ Layer& layer( xcf_image.layer );
+
+ if ( layer.name != 0 ) delete[] layer.name;
+
+ xcf_io >> layer.width >> layer.height >> layer.type >> layer.name;
+
+ if ( xcf_io.failed() ) {
+ qDebug( "XCF: read failure on layer" );
+ return false;
+ }
+
+ if ( !loadLayerProperties( xcf_io, layer ) ) return false;
+#if 0
+ cout << "layer: \"" << layer.name << "\", size: " << layer.width << " x "
+ << layer.height << ", type: " << layer.type << ", mode: " << layer.mode
+ << ", opacity: " << layer.opacity << ", visible: " << layer.visible
+ << ", offset: " << layer.x_offset << ", " << layer.y_offset << endl;
+#endif
+ // Skip reading the rest of it if it is not visible. Typically, when
+ // you export an image from the The GIMP it flattens (or merges) only
+ // the visible layers into the output image.
+
+ if ( layer.visible == 0 ) return true;
+
+ // If there are any more layers, merge them into the final QImage.
+
+ xcf_io >> layer.hierarchy_offset >> layer.mask_offset;
+
+ if ( xcf_io.failed() ) {
+ qDebug( "XCF: read failure on layer image offsets" );
+ return false;
+ }
+
+ // Allocate the individual tile QImages based on the size and type
+ // of this layer.
+
+ composeTiles( xcf_image );
+
+ xcf_io.device()->at( layer.hierarchy_offset );
+
+ // As tiles are loaded, they are copied into the layers tiles by
+ // this routine. (loadMask(), below, uses a slightly different
+ // version of assignBytes().)
+
+ layer.assignBytes = assignImageBytes;
+
+ if ( !loadHierarchy( xcf_io, layer ) ) return false;
+
+ if ( layer.mask_offset != 0 ) {
+ xcf_io.device()->at( layer.mask_offset );
+
+ if ( !loadMask( xcf_io, layer ) ) return false;
+ }
+
+ // Now we should have enough information to initialize the final
+ // QImage. The first visible layer determines the attributes
+ // of the QImage.
+
+ if ( !xcf_image.initialized ) {
+ initializeImage( xcf_image );
+
+ copyLayerToImage( xcf_image );
+
+ xcf_image.initialized = true;
+ }
+ else
+ mergeLayerIntoImage( xcf_image );
+
+ return true;
+}
+
+/*!
+ * An XCF file can contain an arbitrary number of properties associated
+ * with a layer.
+ * \param xcf_io the data stream connected to the XCF image.
+ * \param layer layer to collect the properties.
+ * \return true if there were no I/O errors.
+ */
+bool XCFImageFormat::loadLayerProperties ( SafeDataStream& xcf_io, Layer& layer )
+{
+ while ( true ) {
+ PropType type;
+ QByteArray bytes;
+
+ if ( !loadProperty( xcf_io, type, bytes ) ) {
+ qDebug( "XCF: error loading layer properties" );
+ return false;
+ }
+
+ QDataStream property( bytes, IO_ReadOnly );
+
+ switch ( type ) {
+ case PROP_END:
+ return true;
+
+ case PROP_ACTIVE_LAYER:
+ layer.active = true;
+ break;
+
+ case PROP_OPACITY:
+ property >> layer.opacity;
+ break;
+
+ case PROP_VISIBLE:
+ property >> layer.visible;
+ break;
+
+ case PROP_LINKED:
+ property >> layer.linked;
+ break;
+
+ case PROP_PRESERVE_TRANSPARENCY:
+ property >> layer.preserve_transparency;
+ break;
+
+ case PROP_APPLY_MASK:
+ property >> layer.apply_mask;
+ break;
+
+ case PROP_EDIT_MASK:
+ property >> layer.edit_mask;
+ break;
+
+ case PROP_SHOW_MASK:
+ property >> layer.show_mask;
+ break;
+
+ case PROP_OFFSETS:
+ property >> layer.x_offset >> layer.y_offset;
+ break;
+
+ case PROP_MODE:
+ property >> layer.mode;
+ break;
+
+ case PROP_TATTOO:
+ property >> layer.tattoo;
+ break;
+
+ default:
+ qDebug( "XCF: unimplemented layer property %d, size %d", type, bytes.size() );
+ }
+ }
+}
+
+/*!
+ * An XCF file can contain an arbitrary number of properties associated
+ * with a channel. Note that this routine only reads mask channel properties.
+ * \param xcf_io the data stream connected to the XCF image.
+ * \param layer layer containing the mask channel to collect the properties.
+ * \return true if there were no I/O errors.
+ */
+bool XCFImageFormat::loadChannelProperties ( SafeDataStream& xcf_io, Layer& layer )
+{
+ while ( true ) {
+ PropType type;
+ QByteArray bytes;
+
+ if ( !loadProperty( xcf_io, type, bytes ) ) {
+ qDebug( "XCF: error loading channel properties" );
+ return false;
+ }
+
+ QDataStream property( bytes, IO_ReadOnly );
+
+ switch ( type ) {
+ case PROP_END:
+ return true;
+
+ case PROP_OPACITY:
+ property >> layer.mask_channel.opacity;
+ break;
+
+ case PROP_VISIBLE:
+ property >> layer.mask_channel.visible;
+ break;
+
+ case PROP_SHOW_MASKED:
+ property >> layer.mask_channel.show_masked;
+ break;
+
+ case PROP_COLOR:
+ property >> layer.mask_channel.red >> layer.mask_channel.green
+ >> layer.mask_channel.blue;
+ break;
+
+ case PROP_TATTOO:
+ property >> layer.mask_channel.tattoo;
+ break;
+
+ default:
+ qDebug( "XCF: unimplemented channel property %d, size %d", type, bytes.size() );
+ }
+ }
+}
+
+/*!
+ * The GIMP stores images in a "mipmap"-like hierarchy. As far as the QImage
+ * is concerned, however, only the top level (i.e., the full resolution image)
+ * is used.
+ * \param xcf_io the data stream connected to the XCF image.
+ * \param layer the layer to collect the image.
+ * \return true if there were no I/O errors.
+ */
+bool XCFImageFormat::loadHierarchy ( SafeDataStream& xcf_io, Layer& layer )
+{
+ Q_INT32 width;
+ Q_INT32 height;
+ Q_INT32 bpp;
+ Q_UINT32 offset;
+
+ xcf_io >> width >> height >> bpp >> offset;
+
+ if ( xcf_io.failed() ) {
+ qDebug( "XCF: read failure on layer %s image header", layer.name );
+ return false;
+ }
+
+ // GIMP stores images in a "mipmap"-like format (multiple levels of
+ // increasingly lower resolution). Only the top level is used here,
+ // however.
+
+ Q_UINT32 junk;
+ do {
+ xcf_io >> junk;
+
+ if ( xcf_io.failed() ) {
+ qDebug( "XCF: read failure on layer %s level offsets", layer.name );
+ return false;
+ }
+ } while ( junk != 0 );
+
+ QIODevice::Offset saved_pos = xcf_io.device()->at();
+
+ xcf_io.device()->at( offset );
+
+ if ( !loadLevel( xcf_io, layer, bpp ) ) return false;
+
+ xcf_io.device()->at( saved_pos );
+
+ return true;
+}
+
+/*!
+ * Load one level of the image hierarchy (but only the top level is ever used).
+ * \param xcf_io the data stream connected to the XCF image.
+ * \param layer the layer to collect the image.
+ * \param bpp the number of bytes in a pixel.
+ * \return true if there were no I/O errors.
+ * \sa loadTileRLE().
+ */
+bool XCFImageFormat::loadLevel ( SafeDataStream& xcf_io, Layer& layer, Q_INT32 bpp )
+{
+ Q_INT32 width;
+ Q_INT32 height;
+ Q_UINT32 offset;
+
+ xcf_io >> width >> height >> offset;
+
+ if ( xcf_io.failed() ) {
+ qDebug( "XCF: read failure on layer %s level info", layer.name );
+ return false;
+ }
+
+ if ( offset == 0 ) return true;
+
+ for ( uint j = 0; j < layer.nrows; j++ ) {
+ for ( uint i = 0; i < layer.ncols; i++ ) {
+
+ if ( offset == 0 ) {
+ qDebug( "XCF: incorrect number of tiles in layer %s", layer.name );
+ return false;
+ }
+
+ QIODevice::Offset saved_pos = xcf_io.device()->at();
+
+ Q_UINT32 offset2;
+
+ xcf_io >> offset2;
+
+ if ( xcf_io.failed() ) {
+ qDebug( "XCF: read failure on layer %s level offset look-ahead",
+ layer.name );
+ return false;
+ }
+
+ // Evidently, RLE can occasionally expand a tile instead of compressing it!
+
+ if ( offset2 == 0 )
+ offset2 = offset + (uint)( TILE_WIDTH * TILE_HEIGHT * 4 * 1.5 );
+
+ xcf_io.device()->at( offset );
+
+ int size = layer.image_tiles[j][i].width() * layer.image_tiles[j][i].height();
+
+ if ( !loadTileRLE( xcf_io, layer.tile, size, offset2 - offset, bpp ) )
+ return false;
+
+ // The bytes in the layer tile are juggled differently depending on
+ // the target QImage. The caller has set layer.assignBytes to the
+ // appropriate routine.
+
+ layer.assignBytes( layer, i, j );
+
+ xcf_io.device()->at( saved_pos );
+
+ xcf_io >> offset;
+
+ if ( xcf_io.failed() ) {
+ qDebug( "XCF: read failure on layer %s level offset", layer.name );
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+/*!
+ * A layer can have a one channel image which is used as a mask.
+ * \param xcf_io the data stream connected to the XCF image.
+ * \param layer the layer to collect the mask image.
+ * \return true if there were no I/O errors.
+ */
+bool XCFImageFormat::loadMask ( SafeDataStream& xcf_io, Layer& layer )
+{
+ Q_INT32 width;
+ Q_INT32 height;
+ char* name;
+
+ xcf_io >> width >> height >> name;
+
+ if ( xcf_io.failed() ) {
+ qDebug( "XCF: read failure on mask info" );
+ return false;
+ }
+
+ delete name;
+
+ if ( !loadChannelProperties( xcf_io, layer ) ) return false;
+
+ Q_UINT32 hierarchy_offset;
+
+ xcf_io >> hierarchy_offset;
+
+ if ( xcf_io.failed() ) {
+ qDebug( "XCF: read failure on mask image offset" );
+ return false;
+ }
+
+ xcf_io.device()->at( hierarchy_offset );
+
+ layer.assignBytes = assignMaskBytes;
+
+ if ( !loadHierarchy( xcf_io, layer ) ) return false;
+
+ return true;
+}
+
+/*!
+ * This is the routine for which all the other code is simply
+ * infrastructure. Read the image bytes out of the file and
+ * store them in the tile buffer. This is passed a full 32-bit deep
+ * buffer, even if bpp is smaller. The caller can figure out what to
+ * do with the bytes.
+ *
+ * The tile is stored in "channels", i.e. the red component of all
+ * pixels, then the green component of all pixels, then blue then
+ * alpha, or, for indexed images, the color indices of all pixels then
+ * the alpha of all pixels.
+ *
+ * The data is compressed with "run length encoding". Some simple data
+ * integrity checks are made.
+ *
+ * \param xcf_io the data stream connected to the XCF image.
+ * \param tile the buffer to expand the RLE into.
+ * \param image_size number of bytes expected to be in the image tile.
+ * \param data_length number of bytes expected in the RLE.
+ * \param bpp number of bytes per pixel.
+ * \return true if there were no I/O errors and no obvious corruption of
+ * the RLE data.
+ */
+bool XCFImageFormat::loadTileRLE ( SafeDataStream& xcf_io, uchar* tile, int image_size,
+ int data_length, Q_INT32 bpp )
+{
+ uchar* data;
+
+ uchar* xcfdata;
+ uchar* xcfodata;
+ uchar* xcfdatalimit;
+
+ xcfdata = xcfodata = new uchar[data_length];
+
+ int read_length=xcf_io.device()->readBlock( (char*)xcfdata, data_length );
+
+ if ( read_length<=0 ) {
+ delete[] xcfodata;
+ qDebug( "XCF: read failure on tile" );
+ return false;
+ }
+
+ xcfdatalimit = &xcfodata[read_length-1];
+
+ for ( int i = 0; i < bpp; ++i ) {
+
+ data = tile + i;
+
+ int count = 0;
+ int size = image_size;
+
+ while ( size > 0 ) {
+ if ( xcfdata > xcfdatalimit )
+ goto bogus_rle;
+
+ uchar val = *xcfdata++;
+
+ uint length = val;
+
+ if ( length >= 128 ) {
+ length = 255 - ( length - 1 );
+ if ( length == 128 ) {
+ if ( xcfdata >= xcfdatalimit )
+ goto bogus_rle;
+
+ length = ( *xcfdata << 8 ) + xcfdata[1];
+
+ xcfdata += 2;
+ }
+
+ count += length;
+ size -= length;
+
+ if ( size < 0 )
+ goto bogus_rle;
+
+ if ( &xcfdata[length-1] > xcfdatalimit )
+ goto bogus_rle;
+
+ while ( length-- > 0 ) {
+ *data = *xcfdata++;
+ data += sizeof(QRgb);
+ }
+ }
+ else {
+ length += 1;
+ if ( length == 128 ) {
+
+ if ( xcfdata >= xcfdatalimit )
+ goto bogus_rle;
+
+ length = ( *xcfdata << 8 ) + xcfdata[1];
+ xcfdata += 2;
+ }
+
+ count += length;
+ size -= length;
+
+ if ( size < 0 )
+ goto bogus_rle;
+
+ if ( xcfdata > xcfdatalimit )
+ goto bogus_rle;
+
+ val = *xcfdata++;
+
+ while ( length-- > 0 ) {
+ *data = val;
+ data += sizeof(QRgb);
+ }
+ }
+ }
+ }
+
+ delete[] xcfodata;
+ return true;
+
+ bogus_rle:
+
+ qDebug( "The run length encoding could not be decoded properly" );
+ delete[] xcfodata;
+ return false;
+}
+
+/*!
+ * Copy the bytes from the tile buffer into the image tile QImage, taking into
+ * account all the myriad different modes.
+ * \param layer layer containing the tile buffer and the image tile matrix.
+ * \param i column index of current tile.
+ * \param j row index of current tile.
+ */
+void XCFImageFormat::assignImageBytes ( Layer& layer, uint i, uint j )
+{
+ uchar* tile = layer.tile;
+
+ switch ( layer.type ) {
+ case RGB_GIMAGE:
+ for ( int l = 0; l < layer.image_tiles[j][i].height(); l++ ) {
+ for ( int k = 0; k < layer.image_tiles[j][i].width(); k++ ) {
+ layer.image_tiles[j][i].setPixel( k, l, qRgb( tile[0], tile[1], tile[2] ) );
+ tile += sizeof(QRgb);
+ }
+ }
+ break;
+
+ case RGBA_GIMAGE:
+ for ( int l = 0; l < layer.image_tiles[j][i].height(); l++ ) {
+ for ( int k = 0; k < layer.image_tiles[j][i].width(); k++ ) {
+ layer.image_tiles[j][i].setPixel( k, l,
+ qRgba( tile[0], tile[1], tile[2], tile[3] ) );
+ tile += sizeof(QRgb);
+ }
+ }
+ break;
+
+ case GRAY_GIMAGE:
+ case INDEXED_GIMAGE:
+ for ( int l = 0; l < layer.image_tiles[j][i].height(); l++ ) {
+ for ( int k = 0; k < layer.image_tiles[j][i].width(); k++ ) {
+ layer.image_tiles[j][i].setPixel( k, l, tile[0] );
+ tile += sizeof(QRgb);
+ }
+ }
+ break;
+
+ case GRAYA_GIMAGE:
+ case INDEXEDA_GIMAGE:
+ for ( int l = 0; l < layer.image_tiles[j][i].height(); l++ ) {
+ for ( int k = 0; k < layer.image_tiles[j][i].width(); k++ ) {
+
+ // The "if" here should not be necessary, but apparently there
+ // are some cases where the image can contain larger indices
+ // than there are colors in the palette. (A bug in The GIMP?)
+
+ if ( tile[0] < layer.image_tiles[j][i].numColors() )
+ layer.image_tiles[j][i].setPixel( k, l, tile[0] );
+
+ layer.alpha_tiles[j][i].setPixel( k, l, tile[1] );
+ tile += sizeof(QRgb);
+ }
+ }
+ break;
+ }
+}
+
+/*!
+ * Copy the bytes from the tile buffer into the mask tile QImage.
+ * \param layer layer containing the tile buffer and the mask tile matrix.
+ * \param i column index of current tile.
+ * \param j row index of current tile.
+ */
+void XCFImageFormat::assignMaskBytes ( Layer& layer, uint i, uint j )
+{
+ uchar* tile = layer.tile;
+
+ for ( int l = 0; l < layer.image_tiles[j][i].height(); l++ ) {
+ for ( int k = 0; k < layer.image_tiles[j][i].width(); k++ ) {
+ layer.mask_tiles[j][i].setPixel( k, l, tile[0] );
+ tile += sizeof(QRgb);
+ }
+ }
+}
+
+/*!
+ * Read a single property from the image file. The property type is returned
+ * in type and the data is returned in bytes.
+ * \param xcf the image file data stream.
+ * \param type returns with the property type.
+ * \param bytes returns with the property data.
+ * \return true if there were no IO errors. */
+bool XCFImageFormat::loadProperty ( SafeDataStream& xcf_io, PropType& type,
+ QByteArray& bytes )
+{
+ Q_UINT32 tmp;
+ xcf_io >> tmp;
+ type=static_cast<PropType>(tmp);
+
+ if ( xcf_io.failed() ) {
+ qDebug( "XCF: read failure on property type" );
+ return false;
+ }
+
+ char* data;
+ Q_UINT32 size;
+
+ // The COLORMAP property is tricky: in version of GIMP older than 2.0.2, the
+ // property size was wrong (it was 4 + ncolors instead of 4 + 3*ncolors).
+ // This has been fixed in 2.0.2 (*), but the XCF format version has not been
+ // increased, so we can't rely on the property size. The UINT32 after the
+ // property size is the number of colors, which has always been correct, so
+ // we read it, compute the size from it and put it back in the stream.
+ //
+ // * See http://bugzilla.gnome.org/show_bug.cgi?id=142149 and
+ // gimp/app/xcf-save.c, revision 1.42
+ if ( type == PROP_COLORMAP ) {
+ Q_UINT32 ignoredSize, ncolors;
+ xcf_io >> ignoredSize;
+ if ( xcf_io.failed() ) {
+ qDebug( "XCF: read failure on property %d size", type );
+ return false;
+ }
+
+ xcf_io >> ncolors;
+ if ( xcf_io.failed() ) {
+ qDebug( "XCF: read failure on property %d size", type );
+ return false;
+ }
+ xcf_io.device()->ungetch( ncolors & 0xff);
+ xcf_io.device()->ungetch( (ncolors>> 8) & 0xff );
+ xcf_io.device()->ungetch( (ncolors>>16) & 0xff );
+ xcf_io.device()->ungetch( (ncolors>>24) & 0xff );
+
+ size=4 + 3 * ncolors;
+ data = new char[size];
+
+ xcf_io.readRawBytes( data, size );
+ }
+
+ // The USER UNIT property size is not correct. I'm not sure why, though.
+
+ else if ( type == PROP_USER_UNIT ) {
+ float factor;
+ Q_INT32 digits;
+ char* unit_strings;
+
+ xcf_io >> size >> factor >> digits;
+
+ if ( xcf_io.failed() ) {
+ qDebug( "XCF: read failure on property %d", type );
+ return false;
+ }
+
+ for ( int i = 0; i < 5; i++ ) {
+ xcf_io >> unit_strings;
+
+ if ( xcf_io.failed() ) {
+ qDebug( "XCF: read failure on property %d", type );
+ return false;
+ }
+
+ delete[] unit_strings;
+ }
+
+ size = 0;
+ }
+
+ else
+ xcf_io.readBytes( data, size );
+
+ if ( xcf_io.failed() ) {
+ qDebug( "XCF: read failure on property %d data, size %d", type, size );
+ return false;
+ }
+
+ if ( size != 0 ) {
+ bytes.resize( size );
+
+ for ( uint i = 0; i < size; i++ ) bytes[i] = data[i];
+
+ delete[] data;
+ }
+
+ return true;
+}
+
+/*!
+ * Copy a layer into an image, taking account of the manifold modes. The
+ * contents of the image are replaced.
+ * \param xcf_image contains the layer and image to be replaced.
+ */
+void XCFImageFormat::copyLayerToImage ( XCFImage& xcf_image )
+{
+ Layer& layer( xcf_image.layer );
+ QImage& image( xcf_image.image );
+
+ PixelCopyOperation copy = 0;
+
+ switch ( layer.type ) {
+ case RGB_GIMAGE:
+ case RGBA_GIMAGE:
+ copy = copyRGBToRGB; break;
+ case GRAY_GIMAGE:
+ if ( layer.opacity == OPAQUE_OPACITY )
+ copy = copyGrayToGray;
+ else
+ copy = copyGrayToRGB;
+ break;
+ case GRAYA_GIMAGE:
+ copy = copyGrayAToRGB; break;
+ case INDEXED_GIMAGE:
+ copy = copyIndexedToIndexed; break;
+ case INDEXEDA_GIMAGE:
+ if ( xcf_image.image.depth() <= 8 )
+ copy = copyIndexedAToIndexed;
+ else
+ copy = copyIndexedAToRGB;
+ }
+
+ // For each tile...
+
+ for ( uint j = 0; j < layer.nrows; j++ ) {
+ uint y = j * TILE_HEIGHT;
+
+ for ( uint i = 0; i < layer.ncols; i++ ) {
+ uint x = i * TILE_WIDTH;
+
+ // This seems the best place to apply the dissolve because it
+ // depends on the global position of each tile's
+ // pixels. Apparently it's the only mode which can apply to a
+ // single layer.
+
+ if ( layer.mode == DISSOLVE_MODE ) {
+ if ( layer.type == RGBA_GIMAGE )
+ dissolveRGBPixels( layer.image_tiles[j][i], x, y );
+
+ else if ( layer.type == GRAYA_GIMAGE )
+ dissolveAlphaPixels( layer.alpha_tiles[j][i], x, y );
+ }
+
+ for ( int l = 0; l < layer.image_tiles[j][i].height(); l++ ) {
+ for ( int k = 0; k < layer.image_tiles[j][i].width(); k++ ) {
+
+ int m = x + k + layer.x_offset;
+ int n = y + l + layer.y_offset;
+
+ if ( m < 0 || m >= image.width() || n < 0 || n >= image.height() )
+ continue;
+
+ (*copy)( layer, i, j, k, l, image, m, n );
+ }
+ }
+ }
+ }
+}
+
+/*!
+ * Copy an RGB pixel from the layer to the RGB image. Straight-forward.
+ * The only thing this has to take account of is the opacity of the
+ * layer. Evidently, the GIMP exporter itself does not actually do this.
+ * \param layer source layer.
+ * \param i x tile index.
+ * \param j y tile index.
+ * \param k x pixel index of tile i,j.
+ * \param l y pixel index of tile i,j.
+ * \param image destination image.
+ * \param m x pixel of destination image.
+ * \param n y pixel of destination image.
+ */
+void XCFImageFormat::copyRGBToRGB ( Layer& layer, uint i, uint j, int k, int l,
+ QImage& image, int m, int n )
+{
+ QRgb src = layer.image_tiles[j][i].pixel( k, l );
+
+ uchar src_a = layer.opacity;
+
+ if ( layer.type == RGBA_GIMAGE )
+ src_a = INT_MULT( src_a, qAlpha( src ) );
+
+ // Apply the mask (if any)
+
+ if ( layer.apply_mask == 1 && layer.mask_tiles.size() > j &&
+ layer.mask_tiles[j].size() > i )
+ src_a = INT_MULT( src_a, layer.mask_tiles[j][i].pixelIndex( k, l ) );
+
+ image.setPixel( m, n, qRgba( src, src_a ) );
+}
+
+/*!
+ * Copy a Gray pixel from the layer to the Gray image. Straight-forward.
+ * \param layer source layer.
+ * \param i x tile index.
+ * \param j y tile index.
+ * \param k x pixel index of tile i,j.
+ * \param l y pixel index of tile i,j.
+ * \param image destination image.
+ * \param m x pixel of destination image.
+ * \param n y pixel of destination image.
+ */
+void XCFImageFormat::copyGrayToGray ( Layer& layer, uint i, uint j, int k, int l,
+ QImage& image, int m, int n )
+{
+ int src = layer.image_tiles[j][i].pixelIndex( k, l );
+
+ image.setPixel( m, n, src );
+}
+
+/*!
+ * Copy a Gray pixel from the layer to an RGB image. Straight-forward.
+ * The only thing this has to take account of is the opacity of the
+ * layer. Evidently, the GIMP exporter itself does not actually do this.
+ * \param layer source layer.
+ * \param i x tile index.
+ * \param j y tile index.
+ * \param k x pixel index of tile i,j.
+ * \param l y pixel index of tile i,j.
+ * \param image destination image.
+ * \param m x pixel of destination image.
+ * \param n y pixel of destination image.
+ */
+void XCFImageFormat::copyGrayToRGB ( Layer& layer, uint i, uint j, int k, int l,
+ QImage& image, int m, int n )
+{
+ QRgb src = layer.image_tiles[j][i].pixel( k, l );
+
+ uchar src_a = layer.opacity;
+
+ image.setPixel( m, n, qRgba( src, src_a ) );
+}
+
+/*!
+ * Copy a GrayA pixel from the layer to an RGB image. Straight-forward.
+ * The only thing this has to take account of is the opacity of the
+ * layer. Evidently, the GIMP exporter itself does not actually do this.
+ * \param layer source layer.
+ * \param i x tile index.
+ * \param j y tile index.
+ * \param k x pixel index of tile i,j.
+ * \param l y pixel index of tile i,j.
+ * \param image destination image.
+ * \param m x pixel of destination image.
+ * \param n y pixel of destination image.
+ */
+void XCFImageFormat::copyGrayAToRGB ( Layer& layer, uint i, uint j, int k, int l,
+ QImage& image, int m, int n )
+{
+ QRgb src = layer.image_tiles[j][i].pixel( k, l );
+
+ uchar src_a = layer.alpha_tiles[j][i].pixelIndex( k, l );
+
+ src_a = INT_MULT( src_a, layer.opacity );
+
+ // Apply the mask (if any)
+
+ if ( layer.apply_mask == 1 && layer.mask_tiles.size() > j &&
+ layer.mask_tiles[j].size() > i )
+ src_a = INT_MULT( src_a, layer.mask_tiles[j][i].pixelIndex( k, l ) );
+
+ image.setPixel( m, n, qRgba( src, src_a ) );
+}
+
+/*!
+ * Copy an Indexed pixel from the layer to the Indexed image. Straight-forward.
+ * \param layer source layer.
+ * \param i x tile index.
+ * \param j y tile index.
+ * \param k x pixel index of tile i,j.
+ * \param l y pixel index of tile i,j.
+ * \param image destination image.
+ * \param m x pixel of destination image.
+ * \param n y pixel of destination image.
+ */
+void XCFImageFormat::copyIndexedToIndexed ( Layer& layer, uint i,uint j,int k,int l,
+ QImage& image, int m, int n )
+{
+ int src = layer.image_tiles[j][i].pixelIndex( k, l );
+
+ image.setPixel( m, n, src );
+}
+
+/*!
+ * Copy an IndexedA pixel from the layer to the Indexed image. Straight-forward.
+ * \param layer source layer.
+ * \param i x tile index.
+ * \param j y tile index.
+ * \param k x pixel index of tile i,j.
+ * \param l y pixel index of tile i,j.
+ * \param image destination image.
+ * \param m x pixel of destination image.
+ * \param n y pixel of destination image.
+ */
+void XCFImageFormat::copyIndexedAToIndexed ( Layer& layer,uint i,uint j,int k,int l,
+ QImage& image, int m, int n )
+{
+ uchar src = layer.image_tiles[j][i].pixelIndex( k, l );
+
+ uchar src_a = layer.alpha_tiles[j][i].pixelIndex( k, l );
+
+ src_a = INT_MULT( src_a, layer.opacity );
+
+ if ( layer.apply_mask == 1 &&
+ layer.mask_tiles.size() > j &&
+ layer.mask_tiles[j].size() > i )
+ src_a = INT_MULT( src_a,
+ layer.mask_tiles[j][i].pixelIndex( k, l ) );
+
+ if ( src_a > 127 )
+ src++;
+ else
+ src = 0;
+
+ image.setPixel( m, n, src );
+}
+
+/*!
+ * Copy an IndexedA pixel from the layer to an RGB image. Straight-forward.
+ * The only thing this has to take account of is the opacity of the
+ * layer. Evidently, the GIMP exporter itself does not actually do this.
+ * \param layer source layer.
+ * \param i x tile index.
+ * \param j y tile index.
+ * \param k x pixel index of tile i,j.
+ * \param l y pixel index of tile i,j.
+ * \param image destination image.
+ * \param m x pixel of destination image.
+ * \param n y pixel of destination image.
+ */
+void XCFImageFormat::copyIndexedAToRGB ( Layer& layer, uint i, uint j, int k, int l,
+ QImage& image, int m, int n )
+{
+ QRgb src = layer.image_tiles[j][i].pixel( k, l );
+
+ uchar src_a = layer.alpha_tiles[j][i].pixelIndex( k, l );
+
+ src_a = INT_MULT( src_a, layer.opacity );
+
+ // Apply the mask (if any)
+
+ if ( layer.apply_mask == 1 && layer.mask_tiles.size() > j &&
+ layer.mask_tiles[j].size() > i )
+ src_a = INT_MULT( src_a, layer.mask_tiles[j][i].pixelIndex( k, l ) );
+
+ // This is what appears in the GIMP window
+
+ if ( src_a <= 127 )
+ src_a = 0;
+ else
+ src_a = OPAQUE_OPACITY;
+
+ image.setPixel( m, n, qRgba( src, src_a ) );
+}
+
+/*!
+ * Merge a layer into an image, taking account of the manifold modes.
+ * \param xcf_image contains the layer and image to merge.
+ */
+void XCFImageFormat::mergeLayerIntoImage ( XCFImage& xcf_image )
+{
+ Layer& layer( xcf_image.layer );
+ QImage& image( xcf_image.image );
+
+ PixelMergeOperation merge = 0;
+
+ switch ( layer.type ) {
+ case RGB_GIMAGE:
+ case RGBA_GIMAGE:
+ merge = mergeRGBToRGB; break;
+ case GRAY_GIMAGE:
+ if ( layer.opacity == OPAQUE_OPACITY )
+ merge = mergeGrayToGray;
+ else
+ merge = mergeGrayToRGB;
+ break;
+ case GRAYA_GIMAGE:
+ if ( xcf_image.image.depth() <= 8 )
+ merge = mergeGrayAToGray;
+ else
+ merge = mergeGrayAToRGB;
+ break;
+ case INDEXED_GIMAGE:
+ merge = mergeIndexedToIndexed; break;
+ case INDEXEDA_GIMAGE:
+ if ( xcf_image.image.depth() <= 8 )
+ merge = mergeIndexedAToIndexed;
+ else
+ merge = mergeIndexedAToRGB;
+ }
+
+ for ( uint j = 0; j < layer.nrows; j++ ) {
+ uint y = j * TILE_HEIGHT;
+
+ for ( uint i = 0; i < layer.ncols; i++ ) {
+ uint x = i * TILE_WIDTH;
+
+ // This seems the best place to apply the dissolve because it
+ // depends on the global position of each tile's
+ // pixels. Apparently it's the only mode which can apply to a
+ // single layer.
+
+ if ( layer.mode == DISSOLVE_MODE ) {
+ if ( layer.type == RGBA_GIMAGE )
+ dissolveRGBPixels( layer.image_tiles[j][i], x, y );
+
+ else if ( layer.type == GRAYA_GIMAGE )
+ dissolveAlphaPixels( layer.alpha_tiles[j][i], x, y );
+ }
+
+ for ( int l = 0; l < layer.image_tiles[j][i].height(); l++ ) {
+ for ( int k = 0; k < layer.image_tiles[j][i].width(); k++ ) {
+
+ int m = x + k + layer.x_offset;
+ int n = y + l + layer.y_offset;
+
+ if ( m < 0 || m >= image.width() || n < 0 || n >= image.height() )
+ continue;
+
+ (*merge)( layer, i, j, k, l, image, m, n );
+ }
+ }
+ }
+ }
+}
+
+/*!
+ * Merge an RGB pixel from the layer to the RGB image. Straight-forward.
+ * The only thing this has to take account of is the opacity of the
+ * layer. Evidently, the GIMP exporter itself does not actually do this.
+ * \param layer source layer.
+ * \param i x tile index.
+ * \param j y tile index.
+ * \param k x pixel index of tile i,j.
+ * \param l y pixel index of tile i,j.
+ * \param image destination image.
+ * \param m x pixel of destination image.
+ * \param n y pixel of destination image.
+ */
+void XCFImageFormat::mergeRGBToRGB ( Layer& layer, uint i, uint j, int k, int l,
+ QImage& image, int m, int n )
+{
+ QRgb src = layer.image_tiles[j][i].pixel( k, l );
+ QRgb dst = image.pixel( m, n );
+
+ uchar src_r = qRed( src );
+ uchar src_g = qGreen( src );
+ uchar src_b = qBlue( src );
+ uchar src_a = qAlpha( src );
+
+ uchar dst_r = qRed( dst );
+ uchar dst_g = qGreen( dst );
+ uchar dst_b = qBlue( dst );
+ uchar dst_a = qAlpha( dst );
+
+ switch ( layer.mode ) {
+ case MULTIPLY_MODE: {
+ src_r = INT_MULT( src_r, dst_r );
+ src_g = INT_MULT( src_g, dst_g );
+ src_b = INT_MULT( src_b, dst_b );
+ src_a = MIN( src_a, dst_a );
+ }
+ break;
+ case DIVIDE_MODE: {
+ src_r = MIN( ( dst_r * 256 ) / ( 1 + src_r ), 255 );
+ src_g = MIN( ( dst_g * 256 ) / ( 1 + src_g ), 255 );
+ src_b = MIN( ( dst_b * 256 ) / ( 1 + src_b ), 255 );
+ src_a = MIN( src_a, dst_a );
+ }
+ break;
+ case SCREEN_MODE: {
+ src_r = 255 - INT_MULT( 255 - dst_r, 255 - src_r );
+ src_g = 255 - INT_MULT( 255 - dst_g, 255 - src_g );
+ src_b = 255 - INT_MULT( 255 - dst_b, 255 - src_b );
+ src_a = MIN( src_a, dst_a );
+ }
+ break;
+ case OVERLAY_MODE: {
+ src_r = INT_MULT( dst_r, dst_r + INT_MULT( 2 * src_r, 255 - dst_r ) );
+ src_g = INT_MULT( dst_g, dst_g + INT_MULT( 2 * src_g, 255 - dst_g ) );
+ src_b = INT_MULT( dst_b, dst_b + INT_MULT( 2 * src_b, 255 - dst_b ) );
+ src_a = MIN( src_a, dst_a );
+ }
+ break;
+ case DIFFERENCE_MODE: {
+ src_r = dst_r > src_r ? dst_r - src_r : src_r - dst_r;
+ src_g = dst_g > src_g ? dst_g - src_g : src_g - dst_g;
+ src_b = dst_b > src_b ? dst_b - src_b : src_b - dst_b;
+ src_a = MIN( src_a, dst_a );
+ }
+ break;
+ case ADDITION_MODE: {
+ src_r = add_lut[dst_r][src_r];
+ src_g = add_lut[dst_g][src_g];
+ src_b = add_lut[dst_b][src_b];
+ src_a = MIN( src_a, dst_a );
+ }
+ break;
+ case SUBTRACT_MODE: {
+ src_r = dst_r > src_r ? dst_r - src_r : 0;
+ src_g = dst_g > src_g ? dst_g - src_g : 0;
+ src_b = dst_b > src_b ? dst_b - src_b : 0;
+ src_a = MIN( src_a, dst_a );
+ }
+ break;
+ case DARKEN_ONLY_MODE: {
+ src_r = dst_r < src_r ? dst_r : src_r;
+ src_g = dst_g < src_g ? dst_g : src_g;
+ src_b = dst_b < src_b ? dst_b : src_b;
+ src_a = MIN( src_a, dst_a );
+ }
+ break;
+ case LIGHTEN_ONLY_MODE: {
+ src_r = dst_r < src_r ? src_r : dst_r;
+ src_g = dst_g < src_g ? src_g : dst_g;
+ src_b = dst_b < src_b ? src_b : dst_b;
+ src_a = MIN( src_a, dst_a );
+ }
+ break;
+ case HUE_MODE: {
+ uchar new_r = dst_r;
+ uchar new_g = dst_g;
+ uchar new_b = dst_b;
+
+ RGBTOHSV( src_r, src_g, src_b );
+ RGBTOHSV( new_r, new_g, new_b );
+
+ new_r = src_r;
+
+ HSVTORGB( new_r, new_g, new_b );
+
+ src_r = new_r;
+ src_g = new_g;
+ src_b = new_b;
+ src_a = MIN( src_a, dst_a );
+ }
+ break;
+ case SATURATION_MODE: {
+ uchar new_r = dst_r;
+ uchar new_g = dst_g;
+ uchar new_b = dst_b;
+
+ RGBTOHSV( src_r, src_g, src_b );
+ RGBTOHSV( new_r, new_g, new_b );
+
+ new_g = src_g;
+
+ HSVTORGB( new_r, new_g, new_b );
+
+ src_r = new_r;
+ src_g = new_g;
+ src_b = new_b;
+ src_a = MIN( src_a, dst_a );
+ }
+ break;
+ case VALUE_MODE: {
+ uchar new_r = dst_r;
+ uchar new_g = dst_g;
+ uchar new_b = dst_b;
+
+ RGBTOHSV( src_r, src_g, src_b );
+ RGBTOHSV( new_r, new_g, new_b );
+
+ new_b = src_b;
+
+ HSVTORGB( new_r, new_g, new_b );
+
+ src_r = new_r;
+ src_g = new_g;
+ src_b = new_b;
+ src_a = MIN( src_a, dst_a );
+ }
+ break;
+ case COLOR_MODE: {
+ uchar new_r = dst_r;
+ uchar new_g = dst_g;
+ uchar new_b = dst_b;
+
+ RGBTOHLS( src_r, src_g, src_b );
+ RGBTOHLS( new_r, new_g, new_b );
+
+ new_r = src_r;
+ new_b = src_b;
+
+ HLSTORGB( new_r, new_g, new_b );
+
+ src_r = new_r;
+ src_g = new_g;
+ src_b = new_b;
+ src_a = MIN( src_a, dst_a );
+ }
+ break;
+ }
+
+ src_a = INT_MULT( src_a, layer.opacity );
+
+ // Apply the mask (if any)
+
+ if ( layer.apply_mask == 1 && layer.mask_tiles.size() > j &&
+ layer.mask_tiles[j].size() > i )
+ src_a = INT_MULT( src_a, layer.mask_tiles[j][i].pixelIndex( k, l ) );
+
+ uchar new_r, new_g, new_b, new_a;
+
+ new_a = dst_a + INT_MULT( OPAQUE_OPACITY - dst_a, src_a );
+
+ float src_ratio = (float)src_a / new_a;
+ float dst_ratio = 1. - src_ratio;
+
+ new_r = (uchar)( src_ratio * src_r + dst_ratio * dst_r + EPSILON );
+ new_g = (uchar)( src_ratio * src_g + dst_ratio * dst_g + EPSILON );
+ new_b = (uchar)( src_ratio * src_b + dst_ratio * dst_b + EPSILON );
+
+ if ( !layer_modes[layer.mode].affect_alpha )
+ new_a = dst_a;
+
+ image.setPixel( m, n, qRgba( new_r, new_g, new_b, new_a ) );
+}
+
+/*!
+ * Merge a Gray pixel from the layer to the Gray image. Straight-forward.
+ * \param layer source layer.
+ * \param i x tile index.
+ * \param j y tile index.
+ * \param k x pixel index of tile i,j.
+ * \param l y pixel index of tile i,j.
+ * \param image destination image.
+ * \param m x pixel of destination image.
+ * \param n y pixel of destination image.
+ */
+void XCFImageFormat::mergeGrayToGray ( Layer& layer, uint i, uint j, int k, int l,
+ QImage& image, int m, int n )
+{
+ int src = layer.image_tiles[j][i].pixelIndex( k, l );
+
+ image.setPixel( m, n, src );
+}
+
+/*!
+ * Merge a GrayA pixel from the layer to the Gray image. Straight-forward.
+ * \param layer source layer.
+ * \param i x tile index.
+ * \param j y tile index.
+ * \param k x pixel index of tile i,j.
+ * \param l y pixel index of tile i,j.
+ * \param image destination image.
+ * \param m x pixel of destination image.
+ * \param n y pixel of destination image.
+ */
+void XCFImageFormat::mergeGrayAToGray ( Layer& layer, uint i, uint j, int k, int l,
+ QImage& image, int m, int n )
+{
+ int src = qGray( layer.image_tiles[j][i].pixel( k, l ) );
+ int dst = image.pixelIndex( m, n );
+
+ uchar src_a = layer.alpha_tiles[j][i].pixelIndex( k, l );
+
+ switch ( layer.mode ) {
+ case MULTIPLY_MODE: {
+ src = INT_MULT( src, dst );
+ }
+ break;
+ case DIVIDE_MODE: {
+ src = MIN( ( dst * 256 ) / ( 1 + src ), 255 );
+ }
+ break;
+ case SCREEN_MODE: {
+ src = 255 - INT_MULT( 255 - dst, 255 - src );
+ }
+ break;
+ case OVERLAY_MODE: {
+ src = INT_MULT( dst, dst + INT_MULT( 2 * src, 255 - dst ) );
+ }
+ break;
+ case DIFFERENCE_MODE: {
+ src = dst > src ? dst - src : src - dst;
+ }
+ break;
+ case ADDITION_MODE: {
+ src = add_lut[dst][src];
+ }
+ break;
+ case SUBTRACT_MODE: {
+ src = dst > src ? dst - src : 0;
+ }
+ break;
+ case DARKEN_ONLY_MODE: {
+ src = dst < src ? dst : src;
+ }
+ break;
+ case LIGHTEN_ONLY_MODE: {
+ src = dst < src ? src : dst;
+ }
+ break;
+ }
+
+ src_a = INT_MULT( src_a, layer.opacity );
+
+ // Apply the mask (if any)
+
+ if ( layer.apply_mask == 1 && layer.mask_tiles.size() > j &&
+ layer.mask_tiles[j].size() > i )
+ src_a = INT_MULT( src_a, layer.mask_tiles[j][i].pixelIndex( k, l ) );
+
+ uchar new_a = OPAQUE_OPACITY;
+
+ float src_ratio = (float)src_a / new_a;
+ float dst_ratio = 1. - src_ratio;
+
+ uchar new_g = (uchar)( src_ratio * src + dst_ratio * dst + EPSILON );
+
+ image.setPixel( m, n, new_g );
+}
+
+/*!
+ * Merge a Gray pixel from the layer to an RGB image. Straight-forward.
+ * The only thing this has to take account of is the opacity of the
+ * layer. Evidently, the GIMP exporter itself does not actually do this.
+ * \param layer source layer.
+ * \param i x tile index.
+ * \param j y tile index.
+ * \param k x pixel index of tile i,j.
+ * \param l y pixel index of tile i,j.
+ * \param image destination image.
+ * \param m x pixel of destination image.
+ * \param n y pixel of destination image.
+ */
+void XCFImageFormat::mergeGrayToRGB ( Layer& layer, uint i, uint j, int k, int l,
+ QImage& image, int m, int n )
+{
+ QRgb src = layer.image_tiles[j][i].pixel( k, l );
+
+ uchar src_a = layer.opacity;
+
+ image.setPixel( m, n, qRgba( src, src_a ) );
+}
+
+/*!
+ * Merge a GrayA pixel from the layer to an RGB image. Straight-forward.
+ * The only thing this has to take account of is the opacity of the
+ * layer. Evidently, the GIMP exporter itself does not actually do this.
+ * \param layer source layer.
+ * \param i x tile index.
+ * \param j y tile index.
+ * \param k x pixel index of tile i,j.
+ * \param l y pixel index of tile i,j.
+ * \param image destination image.
+ * \param m x pixel of destination image.
+ * \param n y pixel of destination image.
+ */
+void XCFImageFormat::mergeGrayAToRGB ( Layer& layer, uint i, uint j, int k, int l,
+ QImage& image, int m, int n )
+{
+ int src = qGray( layer.image_tiles[j][i].pixel( k, l ) );
+ int dst = qGray( image.pixel( m, n ) );
+
+ uchar src_a = layer.alpha_tiles[j][i].pixelIndex( k, l );
+ uchar dst_a = qAlpha( image.pixel( m, n ) );
+
+ switch ( layer.mode ) {
+ case MULTIPLY_MODE: {
+ src = INT_MULT( src, dst );
+ src_a = MIN( src_a, dst_a );
+ }
+ break;
+ case DIVIDE_MODE: {
+ src = MIN( ( dst * 256 ) / ( 1 + src ), 255 );
+ src_a = MIN( src_a, dst_a );
+ }
+ break;
+ case SCREEN_MODE: {
+ src = 255 - INT_MULT( 255 - dst, 255 - src );
+ src_a = MIN( src_a, dst_a );
+ }
+ break;
+ case OVERLAY_MODE: {
+ src = INT_MULT( dst, dst + INT_MULT( 2 * src, 255 - dst ) );
+ src_a = MIN( src_a, dst_a );
+ }
+ break;
+ case DIFFERENCE_MODE: {
+ src = dst > src ? dst - src : src - dst;
+ src_a = MIN( src_a, dst_a );
+ }
+ break;
+ case ADDITION_MODE: {
+ src = add_lut[dst][src];
+ src_a = MIN( src_a, dst_a );
+ }
+ break;
+ case SUBTRACT_MODE: {
+ src = dst > src ? dst - src : 0;
+ src_a = MIN( src_a, dst_a );
+ }
+ break;
+ case DARKEN_ONLY_MODE: {
+ src = dst < src ? dst : src;
+ src_a = MIN( src_a, dst_a );
+ }
+ break;
+ case LIGHTEN_ONLY_MODE: {
+ src = dst < src ? src : dst;
+ src_a = MIN( src_a, dst_a );
+ }
+ break;
+ }
+
+ src_a = INT_MULT( src_a, layer.opacity );
+
+ // Apply the mask (if any)
+
+ if ( layer.apply_mask == 1 && layer.mask_tiles.size() > j &&
+ layer.mask_tiles[j].size() > i )
+ src_a = INT_MULT( src_a, layer.mask_tiles[j][i].pixelIndex( k, l ) );
+
+ uchar new_a = dst_a + INT_MULT( OPAQUE_OPACITY - dst_a, src_a );
+
+ float src_ratio = (float)src_a / new_a;
+ float dst_ratio = 1. - src_ratio;
+
+ uchar new_g = (uchar)( src_ratio * src + dst_ratio * dst + EPSILON );
+
+ if ( !layer_modes[layer.mode].affect_alpha )
+ new_a = dst_a;
+
+ image.setPixel( m, n, qRgba( new_g, new_g, new_g, new_a ) );
+}
+
+/*!
+ * Merge an Indexed pixel from the layer to the Indexed image. Straight-forward.
+ * \param layer source layer.
+ * \param i x tile index.
+ * \param j y tile index.
+ * \param k x pixel index of tile i,j.
+ * \param l y pixel index of tile i,j.
+ * \param image destination image.
+ * \param m x pixel of destination image.
+ * \param n y pixel of destination image.
+ */
+void XCFImageFormat::mergeIndexedToIndexed ( Layer& layer, uint i,uint j,int k,int l,
+ QImage& image, int m, int n )
+{
+ int src = layer.image_tiles[j][i].pixelIndex( k, l );
+
+ image.setPixel( m, n, src );
+}
+
+/*!
+ * Merge an IndexedA pixel from the layer to the Indexed image. Straight-forward.
+ * \param layer source layer.
+ * \param i x tile index.
+ * \param j y tile index.
+ * \param k x pixel index of tile i,j.
+ * \param l y pixel index of tile i,j.
+ * \param image destination image.
+ * \param m x pixel of destination image.
+ * \param n y pixel of destination image.
+ */
+void XCFImageFormat::mergeIndexedAToIndexed ( Layer& layer,uint i,uint j,int k,int l,
+ QImage& image, int m, int n )
+{
+ uchar src = layer.image_tiles[j][i].pixelIndex( k, l );
+
+ uchar src_a = layer.alpha_tiles[j][i].pixelIndex( k, l );
+
+ src_a = INT_MULT( src_a, layer.opacity );
+
+ if ( layer.apply_mask == 1 &&
+ layer.mask_tiles.size() > j &&
+ layer.mask_tiles[j].size() > i )
+ src_a = INT_MULT( src_a,
+ layer.mask_tiles[j][i].pixelIndex( k, l ) );
+
+ if ( src_a > 127 ) {
+ src++;
+ image.setPixel( m, n, src );
+ }
+}
+
+/*!
+ * Merge an IndexedA pixel from the layer to an RGB image. Straight-forward.
+ * The only thing this has to take account of is the opacity of the
+ * layer. Evidently, the GIMP exporter itself does not actually do this.
+ * \param layer source layer.
+ * \param i x tile index.
+ * \param j y tile index.
+ * \param k x pixel index of tile i,j.
+ * \param l y pixel index of tile i,j.
+ * \param image destination image.
+ * \param m x pixel of destination image.
+ * \param n y pixel of destination image.
+ */
+void XCFImageFormat::mergeIndexedAToRGB ( Layer& layer, uint i, uint j, int k, int l,
+ QImage& image, int m, int n )
+{
+ QRgb src = layer.image_tiles[j][i].pixel( k, l );
+
+ uchar src_a = layer.alpha_tiles[j][i].pixelIndex( k, l );
+
+ src_a = INT_MULT( src_a, layer.opacity );
+
+ // Apply the mask (if any)
+
+ if ( layer.apply_mask == 1 && layer.mask_tiles.size() > j &&
+ layer.mask_tiles[j].size() > i )
+ src_a = INT_MULT( src_a, layer.mask_tiles[j][i].pixelIndex( k, l ) );
+
+ // This is what appears in the GIMP window
+
+ if ( src_a <= 127 )
+ src_a = 0;
+ else
+ src_a = OPAQUE_OPACITY;
+
+ image.setPixel( m, n, qRgba( src, src_a ) );
+}
+
+/*!
+ * Dissolving pixels: pick a random number between 0 and 255. If the pixel's
+ * alpha is less than that, make it transparent.
+ * \param image the image tile to dissolve.
+ * \param x the global x position of the tile.
+ * \param y the global y position of the tile.
+ */
+void XCFImageFormat::dissolveRGBPixels ( QImage& image, int x, int y )
+{
+ // The apparently spurious rand() calls are to wind the random
+ // numbers up to the same point for each tile.
+
+ for ( int l = 0; l < image.height(); l++ ) {
+ srand( random_table[( l + y ) % RANDOM_TABLE_SIZE] );
+
+ for ( int k = 0; k < x; k++ )
+ rand();
+
+ for ( int k = 0; k < image.width(); k++ ) {
+ int rand_val = rand() & 0xff;
+ QRgb pixel = image.pixel( k, l );
+
+ if ( rand_val > qAlpha( pixel ) ) {
+ image.setPixel( k, l, qRgba( pixel, 0 ) );
+ }
+ }
+ }
+}
+
+/*!
+ * Dissolving pixels: pick a random number between 0 and 255. If the pixel's
+ * alpha is less than that, make it transparent. This routine works for
+ * the GRAYA and INDEXEDA image types where the pixel alpha's are stored
+ * separately from the pixel themselves.
+ * \param image the alpha tile to dissolve.
+ * \param x the global x position of the tile.
+ * \param y the global y position of the tile.
+ */
+void XCFImageFormat::dissolveAlphaPixels ( QImage& image, int x, int y )
+{
+ // The apparently spurious rand() calls are to wind the random
+ // numbers up to the same point for each tile.
+
+ for ( int l = 0; l < image.height(); l++ ) {
+ srand( random_table[( l + y ) % RANDOM_TABLE_SIZE] );
+
+ for ( int k = 0; k < x; k++ )
+ rand();
+
+ for ( int k = 0; k < image.width(); k++ ) {
+ int rand_val = rand() & 0xff;
+ uchar alpha = image.pixelIndex( k, l );
+
+ if ( rand_val > alpha ) {
+ image.setPixel( k, l, 0 );
+ }
+ }
+ }
+}
+
+KDE_Q_EXPORT_PLUGIN( XCFImageFormat )
+
+} // namespace
diff --git a/src/gvcore/qxcfi.h b/src/gvcore/qxcfi.h
new file mode 100644
index 0000000..ac82173
--- /dev/null
+++ b/src/gvcore/qxcfi.h
@@ -0,0 +1,332 @@
+#ifndef QXCFI_H
+#define QXCFI_H
+
+#include <qimage.h>
+#include <qimageformatplugin.h>
+#include <qvaluestack.h>
+#include <qvaluevector.h>
+
+#include "gimp.h"
+namespace Gwenview {
+
+// Safe readBlock helper functions
+class SafeDataStream {
+public:
+ SafeDataStream(QIODevice* device)
+ : mDevice(device), mFailed(false) {}
+
+ bool failed() const { return mFailed; }
+ QIODevice* device() const { return mDevice; }
+
+ SafeDataStream& readRawBytes(char* data, uint length) {
+ if (mFailed) return *this;
+ int read_length=mDevice->readBlock(data, length);
+ if (read_length==-1) mFailed=true;
+ if ((uint)read_length!=length) mFailed=true;
+ return *this;
+ }
+
+ SafeDataStream& operator>>(Q_INT8& value) {
+ return readRawBytes((char*)&value, 1);
+ }
+
+ SafeDataStream& operator>>(Q_UINT32& value) {
+ if (mFailed) return *this;
+ uchar *p = (uchar *)(&value);
+ char b[4];
+ if (mDevice->readBlock( b, 4 )==4) {
+ *p++ = b[3];
+ *p++ = b[2];
+ *p++ = b[1];
+ *p = b[0];
+ } else {
+ mFailed=true;
+ }
+ return *this;
+ }
+
+ SafeDataStream& operator>>(Q_INT32& value) {
+ return *this >>((Q_UINT32&)value);
+ }
+
+ SafeDataStream& operator>>(float& value) {
+ return *this >>((Q_UINT32&)value);
+ }
+
+ SafeDataStream& operator>>(char*& value) {
+ if (mFailed) return *this;
+
+ Q_UINT32 len;
+ *this >> len;
+ if (mFailed) return *this;
+ if ( len == 0 ) {
+ value = 0;
+ return *this;
+ }
+ if (mDevice->atEnd() ) {
+ value = 0;
+ mFailed=true;
+ return *this;
+ }
+ value = new char[len];
+ Q_CHECK_PTR( value );
+ if ( !value ) {
+ mFailed=true;
+ return *this;
+ }
+ return readRawBytes(value, len);
+ }
+
+ SafeDataStream& readBytes(char*& data, uint& len) {
+ if (mFailed) return *this;
+
+ *this >> len;
+ if (mFailed) return *this;
+ data=new char[len];
+ Q_CHECK_PTR( data );
+ if ( !data ) {
+ mFailed=true;
+ return *this;
+ }
+ return readRawBytes(data, len);
+ }
+
+ // This method is usefull to debug with gdb. Do not inline it!
+ int at() const;
+
+private:
+ QIODevice* mDevice;
+ bool mFailed;
+};
+
+//! Plug-in for loading a GIMP XCF image file directly.
+/*!
+ * This class uses the Qt 3.0 Image format plug-in loader to provide
+ * the ability to read The GIMP XCF image files. This plug-in will
+ * be dynamically loaded as needed.
+ */
+class XCFImageFormat : public QImageFormatPlugin {
+
+ /*!
+ * Each layer in an XCF file is stored as a matrix of
+ * 64-pixel by 64-pixel images. The GIMP has a sophisticated
+ * method of handling very large images as well as implementing
+ * parallel processing on a tile-by-tile basis. Here, though,
+ * we just read them in en-masse and store them in a matrix.
+ */
+ typedef QValueVector< QValueVector< QImage > > Tiles;
+
+ /*!
+ * Each GIMP image is composed of one or more layers. A layer can
+ * be one of any three basic types: RGB, grayscale or indexed. With an
+ * optional alpha channel, there are six possible types altogether.
+ *
+ * Note: there is only ever one instance of this structure. The
+ * layer info is discarded after it is merged into the final QImage.
+ */
+ struct Layer {
+ Q_UINT32 width; //!< Width of the layer
+ Q_UINT32 height; //!< Height of the layer
+ Q_INT32 type; //!< Type of the layer (GimpImageType)
+ char* name; //!< Name of the layer
+ Q_UINT32 hierarchy_offset; //!< File position of Tile hierarchy
+ Q_UINT32 mask_offset; //!< File position of mask image
+
+ uint nrows; //!< Number of rows of tiles (y direction)
+ uint ncols; //!< Number of columns of tiles (x direction)
+
+ Tiles image_tiles; //!< The basic image
+ //! For Grayscale and Indexed images, the alpha channel is stored
+ //! separately (in this data structure, anyway).
+ Tiles alpha_tiles;
+ Tiles mask_tiles; //!< The layer mask (optional)
+
+ //! Additional information about a layer mask.
+ struct {
+ Q_UINT32 opacity;
+ Q_UINT32 visible;
+ Q_UINT32 show_masked;
+ uchar red, green, blue;
+ Q_UINT32 tattoo;
+ } mask_channel;
+
+ bool active; //!< Is this layer the active layer?
+ Q_UINT32 opacity; //!< The opacity of the layer
+ Q_UINT32 visible; //!< Is the layer visible?
+ Q_UINT32 linked; //!< Is this layer linked (geometrically)
+ Q_UINT32 preserve_transparency; //!< Preserve alpha when drawing on layer?
+ Q_UINT32 apply_mask; //!< Apply the layer mask?
+ Q_UINT32 edit_mask; //!< Is the layer mask the being edited?
+ Q_UINT32 show_mask; //!< Show the layer mask rather than the image?
+ Q_INT32 x_offset; //!< x offset of the layer relative to the image
+ Q_INT32 y_offset; //!< y offset of the layer relative to the image
+ Q_UINT32 mode; //!< Combining mode of layer (LayerModeEffects)
+ Q_UINT32 tattoo; //!< (unique identifier?)
+
+ //! As each tile is read from the file, it is buffered here.
+ uchar tile[TILE_WIDTH * TILE_HEIGHT * sizeof(QRgb)];
+
+ //! The data from tile buffer is copied to the Tile by this
+ //! method. Depending on the type of the tile (RGB, Grayscale,
+ //! Indexed) and use (image or mask), the bytes in the buffer are
+ //! copied in different ways.
+ void (*assignBytes)( Layer& layer, uint i, uint j );
+
+ //! Construct a layer.
+ Layer ( void ) : name( 0 ) {}
+ //! Destruct the layer.
+ ~Layer ( void ) { if ( name != 0 ) delete[] name; }
+ };
+
+ /*!
+ * The in-memory representation of the XCF Image. It contains a few
+ * metadata items, but is mostly a container for the layer information.
+ */
+ struct XCFImage {
+ Q_UINT32 width; //!< width of the XCF image
+ Q_UINT32 height; //!< height of the XCF image
+ Q_INT32 type; //!< type of the XCF image (GimpImageBaseType)
+
+ Q_UINT8 compression; //!< tile compression method (CompressionType)
+ float x_resolution; //!< x resolution in dots per inch
+ float y_resolution; //!< y resolution in dots per inch
+ Q_INT32 tattoo; //!< (unique identifier?)
+ Q_UINT32 unit; //!< Units of The GIMP (inch, mm, pica, etc...)
+ Q_INT32 num_colors; //!< number of colors in an indexed image
+ QValueVector< QRgb > palette; //!< indexed image color palette
+
+ int num_layers; //!< number of layers
+ Layer layer; //!< most recently read layer
+
+ bool initialized; //!< Is the QImage initialized?
+ QImage image; //!< final QImage
+
+ //! Simple constructor.
+ XCFImage ( void ) : initialized( false ) {}
+ };
+
+ //! The bottom-most layer is copied into the final QImage by this
+ //! routine.
+ typedef void (*PixelCopyOperation) ( Layer& layer, uint i, uint j, int k, int l,
+ QImage& image, int m, int n );
+
+ //! Higher layers are merged into the the final QImage by this routine.
+ typedef void (*PixelMergeOperation) ( Layer& layer, uint i, uint j, int k, int l,
+ QImage& image, int m, int n );
+
+ //! In layer DISSOLVE mode, a random number is chosen to compare to a
+ //! pixel's alpha. If the alpha is greater than the random number, the
+ //! pixel is drawn. This table merely contains the random number seeds
+ //! for each ROW of an image. Therefore, the random numbers chosen
+ //! are consistent from run to run.
+
+ static int random_table[RANDOM_TABLE_SIZE];
+
+ //! This table provides the add_pixel saturation values (i.e. 250 + 250 = 255).
+
+ static int add_lut[256][256];
+
+ //! Layer mode static data.
+ typedef struct {
+ bool affect_alpha; //!< Does this mode affect the source alpha?
+ } LayerModes;
+
+ //! Array of layer mode structures for the modes described by
+ //! LayerModeEffects.
+ static LayerModes layer_modes[];
+
+public:
+ /*!
+ * The constructor for the XCF image loader. This initializes the
+ * tables used in the layer merging routines.
+ */
+ XCFImageFormat ();
+
+
+ /*!
+ * The image loader makes no (direct) use of dynamic memory
+ * and the Qt infrastructure takes care of constructing and destructing
+ * the loader so there is not much to do here.
+ */
+ ~XCFImageFormat () {}
+
+ /*!
+ * You can query Qt about the types of image file formats it knows about
+ * via QImage::inputFormats or QImage::inputFormatList().
+ * This method returns "xcf".
+ */
+ QStringList keys () const {
+ return QStringList() << "XCF";
+ }
+
+ /*!
+ * This method installs the XCF reader on demand.
+ */
+ bool installIOHandler ( const QString& );
+
+ static void registerFormat();
+
+private:
+ static void readXCF ( QImageIO* image_io );
+#ifdef TMP_WRITE
+ static void writeXCF ( QImageIO* ) {}
+#endif
+ static void initializeImage ( XCFImage& xcf_image );
+ static void composeTiles ( XCFImage& xcf_image );
+ static bool loadImageProperties ( SafeDataStream& xcf_io, XCFImage& image );
+ static bool loadLayer ( SafeDataStream& xcf_io, XCFImage& xcf_image );
+ static bool loadLayerProperties ( SafeDataStream& xcf_io, Layer& layer );
+ static bool loadChannelProperties ( SafeDataStream& xcf_io, Layer& layer );
+ static bool loadHierarchy ( SafeDataStream& xcf_io, Layer& layer );
+ static bool loadMask ( SafeDataStream& xcf_io, Layer& layer );
+ static bool loadLevel ( SafeDataStream& xcf_io, Layer& layer, Q_INT32 bpp );
+ static bool loadTileRLE ( SafeDataStream& xcf_io, uchar* tile, int size,
+ int data_length, Q_INT32 bpp );
+ static bool loadProperty ( SafeDataStream& xcf_io, PropType& type,
+ QByteArray& bytes );
+ static void setGrayPalette ( QImage& image );
+ static void setPalette ( XCFImage& xcf_image, QImage& image );
+ static void assignImageBytes ( Layer& layer, uint i, uint j );
+ static void assignMaskBytes ( Layer& layer, uint i, uint j );
+
+ static void copyLayerToImage ( XCFImage& xcf_image );
+ static void copyRGBToRGB ( Layer& layer, uint i, uint j, int k, int l,
+ QImage& image, int m, int n );
+ static void copyGrayToGray ( Layer& layer, uint i, uint j, int k, int l,
+ QImage& image, int m, int n );
+ static void copyGrayToRGB ( Layer& layer, uint i, uint j, int k, int l,
+ QImage& image, int m, int n );
+ static void copyGrayAToRGB ( Layer& layer, uint i, uint j, int k, int l,
+ QImage& image, int m, int n );
+ static void copyIndexedToIndexed ( Layer& layer, uint i, uint j, int k, int l,
+ QImage& image, int m, int n );
+ static void copyIndexedAToIndexed ( Layer& layer, uint i, uint j, int k, int l,
+ QImage& image, int m, int n );
+ static void copyIndexedAToRGB ( Layer& layer, uint i, uint j, int k, int l,
+ QImage& image, int m, int n );
+
+ static void mergeLayerIntoImage ( XCFImage& xcf_image );
+ static void mergeRGBToRGB ( Layer& layer, uint i, uint j, int k, int l,
+ QImage& image, int m, int n );
+ static void mergeGrayToGray ( Layer& layer, uint i, uint j, int k, int l,
+ QImage& image, int m, int n );
+ static void mergeGrayAToGray ( Layer& layer, uint i, uint j, int k, int l,
+ QImage& image, int m, int n );
+ static void mergeGrayToRGB ( Layer& layer, uint i, uint j, int k, int l,
+ QImage& image, int m, int n );
+ static void mergeGrayAToRGB ( Layer& layer, uint i, uint j, int k, int l,
+ QImage& image, int m, int n );
+ static void mergeIndexedToIndexed ( Layer& layer, uint i, uint j, int k, int l,
+ QImage& image, int m, int n );
+ static void mergeIndexedAToIndexed ( Layer& layer, uint i, uint j, int k, int l,
+ QImage& image, int m, int n );
+ static void mergeIndexedAToRGB ( Layer& layer, uint i, uint j, int k, int l,
+ QImage& image, int m, int n );
+
+ static void dissolveRGBPixels ( QImage& image, int x, int y );
+ static void dissolveAlphaPixels ( QImage& image, int x, int y );
+};
+
+} // namespace
+#endif
+
diff --git a/src/gvcore/slideshow.cpp b/src/gvcore/slideshow.cpp
new file mode 100644
index 0000000..fb9442a
--- /dev/null
+++ b/src/gvcore/slideshow.cpp
@@ -0,0 +1,217 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+// kate: indent-mode csands; indent-width 4; replace-tabs-save off; replace-tabs off; replace-trailing-space-save off; space-indent off; tabs-indents on; tab-width 4;
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2000-2004 Aur�ien G�eau
+
+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.
+
+*/
+
+// STL
+#include <algorithm>
+
+// Qt
+#include <qtimer.h>
+
+// KDE
+#include <kconfig.h>
+#include <kdebug.h>
+
+// Local
+#include <../gvcore/slideshowconfig.h>
+#include "slideshow.moc"
+
+#include "document.h"
+#include "imageloader.h"
+#include "cache.h"
+
+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
+
+SlideShow::SlideShow(Document* document)
+: mDocument(document), mStarted(false), mPrefetch( NULL ) {
+ mTimer=new QTimer(this);
+ connect(mTimer, SIGNAL(timeout()),
+ this, SLOT(slotTimeout()) );
+ connect(mDocument, SIGNAL(loaded(const KURL&)),
+ this, SLOT(slotLoaded()) );
+}
+
+SlideShow::~SlideShow() {
+ if( !mPriorityURL.isEmpty()) Cache::instance()->setPriorityURL( mPriorityURL, false );
+}
+
+
+void SlideShow::slotSettingsChanged() {
+ if (mTimer->isActive()) {
+ mTimer->changeInterval(timerInterval());
+ }
+}
+
+
+int SlideShow::timerInterval() {
+ int documentDuration = mDocument->duration();
+ if (documentDuration != 0) {
+ return documentDuration * 1000;
+ } else {
+ return int(SlideShowConfig::delay()*1000);
+ }
+}
+
+
+void SlideShow::start(const KURL::List& urls) {
+ mURLs.resize(urls.size());
+ qCopy(urls.begin(), urls.end(), mURLs.begin());
+ if (SlideShowConfig::random()) {
+ std::random_shuffle(mURLs.begin(), mURLs.end());
+ }
+
+ mStartIt=qFind(mURLs.begin(), mURLs.end(), mDocument->url());
+ if (mStartIt==mURLs.end()) {
+ kdWarning() << k_funcinfo << "Current URL not found in list, aborting.\n";
+ return;
+ }
+
+ mTimer->start(timerInterval(), true);
+ mStarted=true;
+ prefetch();
+ emit stateChanged(true);
+}
+
+
+void SlideShow::stop() {
+ mTimer->stop();
+ mStarted=false;
+ emit stateChanged(false);
+ if( !mPriorityURL.isEmpty()) {
+ Cache::instance()->setPriorityURL( mPriorityURL, false );
+ mPriorityURL = KURL();
+ }
+}
+
+
+QValueVector<KURL>::ConstIterator SlideShow::findNextURL() const {
+ QValueVector<KURL>::ConstIterator it=qFind(mURLs.begin(), mURLs.end(), mDocument->url());
+ if (it==mURLs.end()) {
+ kdWarning() << k_funcinfo << "Current URL not found in list. This should not happen.\n";
+ return it;
+ }
+
+ ++it;
+ if (SlideShowConfig::loop()) {
+ // Looping, if we reach the end, start again
+ if (it==mURLs.end()) {
+ it = mURLs.begin();
+ }
+ } else {
+ // Not looping, have we reached the end?
+ if ((it==mURLs.end() && SlideShowConfig::stopAtEnd()) || it==mStartIt) {
+ it = mURLs.end();
+ }
+ }
+
+ return it;
+}
+
+
+void SlideShow::slotTimeout() {
+ LOG("");
+ // wait for prefetching to finish
+ if( mPrefetch != NULL ) {
+ LOG("mPrefetch is working");
+ return;
+ }
+
+ QValueVector<KURL>::ConstIterator it=findNextURL();
+ if (it==mURLs.end()) {
+ stop();
+ return;
+ }
+ emit nextURL(*it);
+}
+
+
+void SlideShow::slotLoaded() {
+ if (mStarted) {
+ mTimer->start(timerInterval(), true);
+ prefetch();
+ }
+}
+
+
+void SlideShow::prefetch() {
+ LOG("");
+ QValueVector<KURL>::ConstIterator it=findNextURL();
+ if (it==mURLs.end()) {
+ return;
+ }
+ LOG("url=" << (*it).pathOrURL());
+
+ if( mPrefetch != NULL ) mPrefetch->release( this );
+ // TODO don't use prefetching with disabled optimizations (and add that option ;) )
+ // (and also don't use prefetching in other places if the image won't fit in cache)
+ mPrefetch = ImageLoader::loader( *it, this, BUSY_PRELOADING );
+ if( !mPriorityURL.isEmpty()) Cache::instance()->setPriorityURL( mPriorityURL, false );
+ mPriorityURL = *it;
+ Cache::instance()->setPriorityURL( mPriorityURL, true ); // make sure it will stay in the cache
+ connect( mPrefetch, SIGNAL( urlKindDetermined()), SLOT( slotUrlKindDetermined()));
+ connect( mPrefetch, SIGNAL( imageLoaded( bool )), SLOT( prefetchDone()));
+
+ if (mPrefetch->urlKind()==MimeTypeUtils::KIND_FILE) {
+ // Prefetch is already done, and this is not a raster image
+ prefetchDone();
+ }
+}
+
+void SlideShow::slotUrlKindDetermined() {
+ LOG("");
+ if (!mPrefetch) return;
+
+ LOG("mPrefetch!=0");
+ if (mPrefetch->urlKind()==MimeTypeUtils::KIND_FILE) {
+ LOG("KIND_FILE");
+ // This is not a raster image, imageLoaded will not be emitted
+ prefetchDone();
+ }
+}
+
+
+void SlideShow::prefetchDone() {
+ LOG("");
+ if( mPrefetch != NULL ) {
+ LOG("mPrefetch!=0");
+ // don't call Cache::setPriorityURL( ... , false ) here - it will still take
+ // a short while to reuse the image from the cache
+ mPrefetch->release( this );
+ mPrefetch = NULL;
+ // prefetching completed and delay has already elapsed
+ if( mStarted && !mTimer->isActive()) {
+ LOG("Calling slotTimeout");
+ slotTimeout();
+ }
+ }
+}
+
+
+} // namespace
diff --git a/src/gvcore/slideshow.h b/src/gvcore/slideshow.h
new file mode 100644
index 0000000..fcb1d1a
--- /dev/null
+++ b/src/gvcore/slideshow.h
@@ -0,0 +1,86 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+// kate: indent-mode csands; indent-width 4; replace-tabs-save off; replace-tabs off; replace-trailing-space-save off; space-indent off; tabs-indents on; tab-width 4;
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2000-2004 Aur�ien G�eau
+
+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.
+
+*/
+
+#ifndef SLIDESHOW_H
+#define SLIDESHOW_H
+
+// Qt
+#include <qobject.h>
+#include <qvaluevector.h>
+
+// KDE
+#include <kurl.h>
+#include "libgwenview_export.h"
+class QTimer;
+
+class KConfig;
+
+namespace Gwenview {
+class Document;
+class ImageLoader;
+
+class LIBGWENVIEW_EXPORT SlideShow : public QObject
+{
+Q_OBJECT
+public:
+ SlideShow(Document* document);
+ virtual ~SlideShow();
+
+ void start(const KURL::List& urls);
+ void stop();
+
+ /** @return true if the slideshow is running */
+ bool isRunning() { return mStarted; }
+
+public slots:
+ void slotSettingsChanged();
+
+signals:
+ void nextURL( const KURL& );
+ /**
+ * Slideshow has been started or stopped
+ */
+ void stateChanged(bool running);
+
+private slots:
+ void slotTimeout();
+ void slotLoaded();
+ void slotUrlKindDetermined();
+ void prefetchDone();
+
+private:
+ QValueVector<KURL>::ConstIterator findNextURL() const;
+ void prefetch();
+ int timerInterval();
+
+ QTimer* mTimer;
+ Document* mDocument;
+ bool mStarted;
+ QValueVector<KURL> mURLs;
+ QValueVector<KURL>::ConstIterator mStartIt;
+ ImageLoader* mPrefetch;
+ KURL mPriorityURL;
+};
+
+} // namespace
+#endif // SLIDESHOW_H
+
diff --git a/src/gvcore/slideshowconfig.kcfg b/src/gvcore/slideshowconfig.kcfg
new file mode 100644
index 0000000..9f110d9
--- /dev/null
+++ b/src/gvcore/slideshowconfig.kcfg
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE kcfg SYSTEM "http://www.kde.org/standards/kcfg/1.0/kcfg.dtd">
+<kcfg>
+ <kcfgfile name="gwenviewrc"/>
+ <group name="slide show">
+ <entry name="random" type="Bool">
+ <label>Display slide show images in random order</label>
+ <default>false</default>
+ </entry>
+ <entry name="fullscreen" type="Bool">
+ <label>Show slideshow in fullscreen mode</label>
+ <default>true</default>
+ </entry>
+ <entry name="loop" type="Bool">
+ <label>Loop on images</label>
+ <default>false</default>
+ </entry>
+ <entry name="stopAtEnd" key="stop at end" type="Bool">
+ <label>Stop at last image of folder</label>
+ <default>false</default>
+ </entry>
+ <entry name="delay" type="Double">
+ <label>Delay between images (in seconds)</label>
+ <default>10.0</default>
+ </entry>
+ </group>
+</kcfg>
diff --git a/src/gvcore/slideshowconfig.kcfgc b/src/gvcore/slideshowconfig.kcfgc
new file mode 100644
index 0000000..85b2ff8
--- /dev/null
+++ b/src/gvcore/slideshowconfig.kcfgc
@@ -0,0 +1,7 @@
+File=slideshowconfig.kcfg
+ClassName=SlideShowConfig
+NameSpace=Gwenview
+Singleton=true
+Mutators=true
+IncludeFiles=gvcore/libgwenview_export.h
+Visibility=LIBGWENVIEW_EXPORT
diff --git a/src/gvcore/threadgate.cpp b/src/gvcore/threadgate.cpp
new file mode 100644
index 0000000..3d09b9f
--- /dev/null
+++ b/src/gvcore/threadgate.cpp
@@ -0,0 +1,60 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2000-2004 Aurélien Gâteau
+
+ 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 "threadgate.moc"
+
+#include "tsthread/tsthread.h"
+
+namespace Gwenview {
+
+// The trick is simple. This object connects its slot to its signal, then
+// emits the signal using emitSignal(), and the slot gets called in the main
+// thread. In the main thread the slot does everything that should be done
+// in the main thread, and returns the data using the signal/slot reference
+// arguments. As the thread is blocked waiting on the signal to finish,
+// there's even no need to do any locking.
+
+ThreadGate::ThreadGate() {
+ connect( this, SIGNAL( signalColor( QColor&, const char* )),
+ this, SLOT( slotColor( QColor&, const char* )));
+}
+
+ThreadGate* ThreadGate::instance() {
+ static ThreadGate gate;
+ return &gate;
+}
+
+QColor ThreadGate::color( const char* name ) {
+ if( name == NULL || name[ 0 ] == '\0' || name[ 0 ] == '#' )
+ return QColor( name );
+ // named color ... needs to be created in the main thread
+ if( TSThread::currentThread() == TSThread::mainThread())
+ return QColor( name );
+ QColor col;
+ TSThread::currentThread()->emitCancellableSignal( this, SIGNAL( signalColor( QColor&, const char* )), col, name );
+ return col;
+}
+
+void ThreadGate::slotColor( QColor& col, const char* name ) {
+ col = QColor( name );
+}
+
+} // namespace
diff --git a/src/gvcore/threadgate.h b/src/gvcore/threadgate.h
new file mode 100644
index 0000000..4783198
--- /dev/null
+++ b/src/gvcore/threadgate.h
@@ -0,0 +1,45 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2000-2004 Aurélien Gâteau
+
+ 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.
+
+*/
+
+#ifndef THREADGATE_H
+#define THREADGATE_H
+
+#include <qobject.h>
+#include <qcolor.h>
+namespace Gwenview {
+
+class ThreadGate : public QObject
+{
+Q_OBJECT
+public:
+ static ThreadGate* instance();
+ QColor color( const char* name );
+private:
+ ThreadGate();
+signals:
+ void signalColor( QColor&, const char* );
+private slots:
+ void slotColor( QColor&, const char* );
+};
+
+} // namespace
+#endif
+
diff --git a/src/gvcore/thumbnaildetailsdialog.cpp b/src/gvcore/thumbnaildetailsdialog.cpp
new file mode 100644
index 0000000..ed514ed
--- /dev/null
+++ b/src/gvcore/thumbnaildetailsdialog.cpp
@@ -0,0 +1,78 @@
+// vi: tabstop=4 shiftwidth=4 noexpandtab
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2005 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 "thumbnaildetailsdialog.moc"
+
+// Qt
+#include <qcheckbox.h>
+
+// Local
+#include "filethumbnailview.h"
+#include "thumbnaildetailsdialogbase.h"
+
+
+namespace Gwenview {
+
+struct ThumbnailDetailsDialog::Private {
+ FileThumbnailView* mView;
+ ThumbnailDetailsDialogBase* mContent;
+};
+
+ThumbnailDetailsDialog::ThumbnailDetailsDialog(FileThumbnailView* view)
+: KDialogBase(
+ view, 0, false /* modal */, QString::null, KDialogBase::Close,
+ KDialogBase::Close, true /* separator */)
+, d(new ThumbnailDetailsDialog::Private)
+{
+ d->mView=view;
+ d->mContent=new ThumbnailDetailsDialogBase(this);
+ setMainWidget(d->mContent);
+ setCaption(d->mContent->caption());
+
+ int details=d->mView->itemDetails();
+ d->mContent->mShowFileName->setChecked(details & FileThumbnailView::FILENAME);
+ d->mContent->mShowFileDate->setChecked(details & FileThumbnailView::FILEDATE);
+ d->mContent->mShowFileSize->setChecked(details & FileThumbnailView::FILESIZE);
+ d->mContent->mShowImageSize->setChecked(details & FileThumbnailView::IMAGESIZE);
+
+ connect(d->mContent->mShowFileName, SIGNAL(toggled(bool)), SLOT(applyChanges()) );
+ connect(d->mContent->mShowFileDate, SIGNAL(toggled(bool)), SLOT(applyChanges()) );
+ connect(d->mContent->mShowFileSize, SIGNAL(toggled(bool)), SLOT(applyChanges()) );
+ connect(d->mContent->mShowImageSize, SIGNAL(toggled(bool)), SLOT(applyChanges()) );
+}
+
+ThumbnailDetailsDialog::~ThumbnailDetailsDialog() {
+ delete d;
+}
+
+
+void ThumbnailDetailsDialog::applyChanges() {
+ int details=
+ (d->mContent->mShowFileName->isChecked() ? FileThumbnailView::FILENAME : 0)
+ | (d->mContent->mShowFileDate->isChecked() ? FileThumbnailView::FILEDATE : 0)
+ | (d->mContent->mShowFileSize->isChecked() ? FileThumbnailView::FILESIZE : 0)
+ | (d->mContent->mShowImageSize->isChecked() ? FileThumbnailView::IMAGESIZE : 0)
+ ;
+ d->mView->setItemDetails(details);
+}
+
+
+} // namespace
diff --git a/src/gvcore/thumbnaildetailsdialog.h b/src/gvcore/thumbnaildetailsdialog.h
new file mode 100644
index 0000000..9706c20
--- /dev/null
+++ b/src/gvcore/thumbnaildetailsdialog.h
@@ -0,0 +1,47 @@
+// vi: tabstop=4 shiftwidth=4 noexpandtab
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2005 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.
+
+*/
+#ifndef THUMBNAILDETAILSDIALOG_H
+#define THUMBNAILDETAILSDIALOG_H
+
+// KDE
+#include <kdialogbase.h>
+
+namespace Gwenview {
+
+class FileThumbnailView;
+
+class ThumbnailDetailsDialog : public KDialogBase {
+Q_OBJECT
+public:
+ ThumbnailDetailsDialog(FileThumbnailView* view);
+ ~ThumbnailDetailsDialog();
+
+private slots:
+ void applyChanges();
+
+private:
+ struct Private;
+ Private* d;
+};
+
+} // namespace
+
+#endif /* THUMBNAILDETAILSDIALOG_H */
diff --git a/src/gvcore/thumbnaildetailsdialogbase.ui b/src/gvcore/thumbnaildetailsdialogbase.ui
new file mode 100644
index 0000000..4057968
--- /dev/null
+++ b/src/gvcore/thumbnaildetailsdialogbase.ui
@@ -0,0 +1,117 @@
+<!DOCTYPE UI><UI version="3.1" stdsetdef="1">
+<class>ThumbnailDetailsDialogBase</class>
+<widget class="QWidget">
+ <property name="name">
+ <cstring>ThumbnailDetailsDialogBase</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>390</width>
+ <height>207</height>
+ </rect>
+ </property>
+ <property name="caption">
+ <string>Thumbnail Details</string>
+ </property>
+ <vbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QLabel">
+ <property name="name">
+ <cstring>textLabel1</cstring>
+ </property>
+ <property name="text">
+ <string>Information to display in the thumbnail text:</string>
+ </property>
+ </widget>
+ <widget class="QLayoutWidget">
+ <property name="name">
+ <cstring>layout6</cstring>
+ </property>
+ <grid>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <spacer row="1" column="0">
+ <property name="name">
+ <cstring>spacer7</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Fixed</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>21</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ <widget class="QCheckBox" row="0" column="1">
+ <property name="name">
+ <cstring>mShowFileName</cstring>
+ </property>
+ <property name="text">
+ <string>File name</string>
+ </property>
+ </widget>
+ <widget class="QCheckBox" row="3" column="1">
+ <property name="name">
+ <cstring>mShowImageSize</cstring>
+ </property>
+ <property name="text">
+ <string>Image size</string>
+ </property>
+ </widget>
+ <widget class="QCheckBox" row="2" column="1">
+ <property name="name">
+ <cstring>mShowFileSize</cstring>
+ </property>
+ <property name="text">
+ <string>File size</string>
+ </property>
+ </widget>
+ <widget class="QCheckBox" row="1" column="1">
+ <property name="name">
+ <cstring>mShowFileDate</cstring>
+ </property>
+ <property name="text">
+ <string>File date</string>
+ </property>
+ </widget>
+ </grid>
+ </widget>
+ <spacer>
+ <property name="name">
+ <cstring>spacer7_2</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Fixed</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>21</width>
+ <height>21</height>
+ </size>
+ </property>
+ </spacer>
+ <widget class="QLabel">
+ <property name="name">
+ <cstring>textLabel1_2</cstring>
+ </property>
+ <property name="text">
+ <string>&lt;i&gt;For more options, use the "Configure Gwenview" dialog&lt;/i&gt;</string>
+ </property>
+ </widget>
+ </vbox>
+</widget>
+<layoutdefaults spacing="6" margin="11"/>
+</UI>
diff --git a/src/gvcore/thumbnailloadjob.cpp b/src/gvcore/thumbnailloadjob.cpp
new file mode 100644
index 0000000..92290ac
--- /dev/null
+++ b/src/gvcore/thumbnailloadjob.cpp
@@ -0,0 +1,763 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab:
+/* Gwenview - A simple image viewer for KDE
+ Copyright 2000-2004 Aurélien Gâteau
+ This class is based on the ImagePreviewJob class from Konqueror.
+ Original copyright follows.
+*/
+/* This file is part of the KDE project
+ Copyright (C) 2000 David Faure <faure@kde.org>
+ 2000 Carsten Pfeiffer <pfeiffer@kde.org>
+
+ 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 "thumbnailloadjob.moc"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <assert.h>
+
+// Qt
+#include <qdir.h>
+#include <qfile.h>
+#include <qimage.h>
+#include <qpixmap.h>
+#include <qtimer.h>
+
+// KDE
+#include <kapplication.h>
+#include <kdebug.h>
+#include <kfileitem.h>
+#include <kiconloader.h>
+#include <kio/previewjob.h>
+#include <klargefile.h>
+#include <kmdcodec.h>
+#include <kstandarddirs.h>
+#include <ktempfile.h>
+
+// libjpeg
+#include <setjmp.h>
+#define XMD_H
+extern "C" {
+#include <jpeglib.h>
+}
+
+// Local
+#include "cache.h"
+#include "mimetypeutils.h"
+#include "miscconfig.h"
+#include "imageutils/jpegcontent.h"
+#include "imageutils/imageutils.h"
+#include "thumbnailsize.h"
+#include "fileviewconfig.h"
+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
+
+
+static QString generateOriginalURI(KURL url) {
+ // Don't include the password if any
+ url.setPass(QString::null);
+ return url.url();
+}
+
+
+static QString generateThumbnailPath(const QString& uri, int size) {
+ KMD5 md5( QFile::encodeName(uri) );
+ QString baseDir=ThumbnailLoadJob::thumbnailBaseDir(size);
+ return baseDir + QString(QFile::encodeName( md5.hexDigest())) + ".png";
+}
+
+//------------------------------------------------------------------------
+//
+// ThumbnailThread
+//
+//------------------------------------------------------------------------
+void ThumbnailThread::load(
+ const QString& originalURI, time_t originalTime, int originalSize, const QString& originalMimeType,
+ const QString& pixPath,
+ const QString& thumbnailPath,
+ int size, bool storeThumbnail)
+{
+ QMutexLocker lock( &mMutex );
+ assert( mPixPath.isNull());
+
+ mOriginalURI = TSDeepCopy(originalURI);
+ mOriginalTime = originalTime;
+ mOriginalSize = originalSize;
+ mOriginalMimeType = TSDeepCopy(originalMimeType);
+ mPixPath = TSDeepCopy(pixPath);
+ mThumbnailPath = TSDeepCopy(thumbnailPath);
+ mThumbnailSize = size;
+ mStoreThumbnailsInCache = storeThumbnail;
+ if(!running()) start();
+ mCond.wakeOne();
+}
+
+void ThumbnailThread::run() {
+ QMutexLocker lock( &mMutex );
+ while( !testCancel()) {
+ // empty mPixPath means nothing to do
+ while( mPixPath.isNull()) {
+ mCond.cancellableWait( &mMutex );
+ if( testCancel()) return;
+ }
+ loadThumbnail();
+ mPixPath = QString(); // done, ready for next
+ QSize size(mOriginalWidth, mOriginalHeight);
+ emitCancellableSignal( this, SIGNAL( done( const QImage&, const QSize&)), mImage, size);
+ }
+}
+
+void ThumbnailThread::loadThumbnail() {
+ mImage = QImage();
+ bool loaded=false;
+ bool needCaching=true;
+
+ // If it's a JPEG, try to load a small image directly from the file
+ if (isJPEG()) {
+ ImageUtils::JPEGContent content;
+ content.load(mPixPath);
+ mOriginalWidth = content.size().width();
+ mOriginalHeight = content.size().height();
+ mImage = content.thumbnail();
+
+ if( !mImage.isNull()
+ && ( mImage.width() >= mThumbnailSize // don't use small thumbnails
+ || mImage.height() >= mThumbnailSize )) {
+ loaded = true;
+ needCaching = false;
+ }
+ if(!loaded) {
+ loaded=loadJPEG();
+ }
+ if (loaded && MiscConfig::autoRotateImages()) {
+ // Rotate if necessary
+ ImageUtils::Orientation orientation = content.orientation();
+ mImage=ImageUtils::transform(mImage,orientation);
+ }
+ }
+ // File is not a JPEG, or JPEG optimized load failed, load file using Qt
+ if (!loaded) {
+ QImage originalImage;
+ if (originalImage.load(mPixPath)) {
+ mOriginalWidth=originalImage.width();
+ mOriginalHeight=originalImage.height();
+ int thumbSize=mThumbnailSize<=ThumbnailSize::NORMAL ? ThumbnailSize::NORMAL : ThumbnailSize::LARGE;
+
+ if( testCancel()) return;
+
+ if (QMAX(mOriginalWidth, mOriginalHeight)<=thumbSize ) {
+ mImage=originalImage;
+ needCaching = false;
+ } else {
+ mImage=ImageUtils::scale(originalImage,thumbSize,thumbSize,ImageUtils::SMOOTH_FAST,QImage::ScaleMin);
+ }
+ loaded = true;
+ }
+ }
+
+ if( testCancel()) return;
+
+ if( mStoreThumbnailsInCache && needCaching ) {
+ mImage.setText("Thumb::URI", 0, mOriginalURI);
+ mImage.setText("Thumb::MTime", 0, QString::number(mOriginalTime));
+ mImage.setText("Thumb::Size", 0, QString::number(mOriginalSize));
+ mImage.setText("Thumb::Mimetype", 0, mOriginalMimeType);
+ mImage.setText("Thumb::Image::Width", 0, QString::number(mOriginalWidth));
+ mImage.setText("Thumb::Image::Height", 0, QString::number(mOriginalHeight));
+ mImage.setText("Software", 0, "Gwenview");
+
+ QString thumbnailDir = ThumbnailLoadJob::thumbnailBaseDir(mThumbnailSize);
+ KStandardDirs::makeDir(thumbnailDir, 0700);
+
+ KTempFile tmp(thumbnailDir + "/gwenview", ".png");
+ tmp.setAutoDelete(true);
+ if (tmp.status()!=0) {
+ QString reason( strerror(tmp.status()) );
+ kdWarning() << "Could not create a temporary file.\nReason: " << reason << endl;
+ return;
+ }
+
+ if (!mImage.save(tmp.name(), "PNG")) {
+ kdWarning() << "Could not save thumbnail for file " << mOriginalURI << endl;
+ return;
+ }
+
+ rename(QFile::encodeName(tmp.name()), QFile::encodeName(mThumbnailPath));
+ }
+}
+
+
+bool ThumbnailThread::isJPEG() {
+ QString format=QImageIO::imageFormat(mPixPath);
+ return format=="JPEG";
+}
+
+
+
+struct JPEGFatalError : public jpeg_error_mgr {
+ jmp_buf mJmpBuffer;
+
+ static void handler(j_common_ptr cinfo) {
+ JPEGFatalError* error=static_cast<JPEGFatalError*>(cinfo->err);
+ (error->output_message)(cinfo);
+ longjmp(error->mJmpBuffer,1);
+ }
+};
+
+bool ThumbnailThread::loadJPEG() {
+ struct jpeg_decompress_struct cinfo;
+
+ // Open file
+ FILE* inputFile=fopen(QFile::encodeName( mPixPath ).data(), "rb");
+ if(!inputFile) return false;
+
+ // Error handling
+ struct JPEGFatalError jerr;
+ cinfo.err = jpeg_std_error(&jerr);
+ cinfo.err->error_exit = JPEGFatalError::handler;
+ if (setjmp(jerr.mJmpBuffer)) {
+ jpeg_destroy_decompress(&cinfo);
+ fclose(inputFile);
+ return false;
+ }
+
+ // Init decompression
+ jpeg_create_decompress(&cinfo);
+ jpeg_stdio_src(&cinfo, inputFile);
+ jpeg_read_header(&cinfo, TRUE);
+
+ // Get image size and check if we need a thumbnail
+ int size= mThumbnailSize <= ThumbnailSize::NORMAL ? ThumbnailSize::NORMAL : ThumbnailSize::LARGE;
+ int imgSize = QMAX(cinfo.image_width, cinfo.image_height);
+
+ if (imgSize<=size) {
+ fclose(inputFile);
+ return mImage.load(mPixPath);
+ }
+
+ // Compute scale value
+ int scale=1;
+ while(size*scale*2<=imgSize) {
+ scale*=2;
+ }
+ if(scale>8) scale=8;
+
+ cinfo.scale_num=1;
+ cinfo.scale_denom=scale;
+
+ // Create QImage
+ jpeg_start_decompress(&cinfo);
+
+ switch(cinfo.output_components) {
+ case 3:
+ case 4:
+ mImage.create( cinfo.output_width, cinfo.output_height, 32 );
+ break;
+ case 1: // B&W image
+ mImage.create( cinfo.output_width, cinfo.output_height, 8, 256 );
+ for (int i=0; i<256; i++)
+ mImage.setColor(i, qRgb(i,i,i));
+ break;
+ default:
+ jpeg_destroy_decompress(&cinfo);
+ fclose(inputFile);
+ return false;
+ }
+
+ uchar** lines = mImage.jumpTable();
+ while (cinfo.output_scanline < cinfo.output_height) {
+ jpeg_read_scanlines(&cinfo, lines + cinfo.output_scanline, cinfo.output_height);
+ }
+ jpeg_finish_decompress(&cinfo);
+
+// Expand 24->32 bpp
+ if ( cinfo.output_components == 3 ) {
+ for (uint j=0; j<cinfo.output_height; j++) {
+ uchar *in = mImage.scanLine(j) + cinfo.output_width*3;
+ QRgb *out = (QRgb*)( mImage.scanLine(j) );
+
+ for (uint i=cinfo.output_width; i--; ) {
+ in-=3;
+ out[i] = qRgb(in[0], in[1], in[2]);
+ }
+ }
+ }
+
+ int newMax = QMAX(cinfo.output_width, cinfo.output_height);
+ int newx = size*cinfo.output_width / newMax;
+ int newy = size*cinfo.output_height / newMax;
+
+ mImage=ImageUtils::scale(mImage,newx, newy,ImageUtils::SMOOTH_FAST);
+
+ jpeg_destroy_decompress(&cinfo);
+ fclose(inputFile);
+
+ return true;
+}
+
+
+//------------------------------------------------------------------------
+//
+// ThumbnailLoadJob static methods
+//
+//------------------------------------------------------------------------
+QString ThumbnailLoadJob::thumbnailBaseDir() {
+ static QString dir;
+ if (!dir.isEmpty()) return dir;
+ dir=QDir::homeDirPath() + "/.thumbnails/";
+ return dir;
+}
+
+
+QString ThumbnailLoadJob::thumbnailBaseDir(int size) {
+ QString dir = thumbnailBaseDir();
+ if (size<=ThumbnailSize::NORMAL) {
+ dir+="normal/";
+ } else {
+ dir+="large/";
+ }
+ return dir;
+}
+
+
+void ThumbnailLoadJob::deleteImageThumbnail(const KURL& url) {
+ QString uri=generateOriginalURI(url);
+ QFile::remove(generateThumbnailPath(uri, ThumbnailSize::NORMAL));
+ QFile::remove(generateThumbnailPath(uri, ThumbnailSize::LARGE));
+}
+
+
+//------------------------------------------------------------------------
+//
+// ThumbnailLoadJob implementation
+//
+//------------------------------------------------------------------------
+
+
+/*
+
+ This class tries to first generate the most important thumbnails, i.e.
+ first the currently selected one, then the ones that are visible, and then
+ the rest, the closer the the currently selected one the sooner
+
+ mAllItems contains all thumbnails
+ mItems contains pending thumbnails, in the priority order
+ mCurrentItem is currently processed thumbnail, already removed from mItems
+ mProcessedState needs to match mAllItems, and contains information about every
+ thumbnail whether it has been already processed
+
+ thumbnailIndex() returns index of a thumbnail in mAllItems, or -1
+ updateItemsOrder() builds mItems from mAllItems
+*/
+
+ThumbnailLoadJob::ThumbnailLoadJob(const QValueVector<const KFileItem*>* items, int size)
+: KIO::Job(false), mState( STATE_NEXTTHUMB ),
+ mCurrentVisibleIndex( -1 ), mFirstVisibleIndex( -1 ), mLastVisibleIndex( -1 ),
+ mThumbnailSize(size), mSuspended( false )
+{
+ LOG("");
+
+ mBrokenPixmap=KGlobal::iconLoader()->loadIcon("file_broken",
+ KIcon::NoGroup, ThumbnailSize::MIN);
+
+ // Look for images and store the items in our todo list
+ Q_ASSERT(!items->empty());
+ mAllItems=*items;
+ mProcessedState.resize( mAllItems.count());
+ qFill( mProcessedState.begin(), mProcessedState.end(), false );
+ mCurrentItem = NULL;
+
+ connect(&mThumbnailThread, SIGNAL(done(const QImage&, const QSize&)),
+ SLOT(thumbnailReady(const QImage&, const QSize&)) );
+ Cache::instance()->updateAge(); // see addThumbnail in Cache
+}
+
+
+ThumbnailLoadJob::~ThumbnailLoadJob() {
+ LOG("");
+ mThumbnailThread.cancel();
+ mThumbnailThread.wait();
+}
+
+
+void ThumbnailLoadJob::start() {
+ // build mItems from mAllItems if not done yet
+ if (mLastVisibleIndex == -1 ) {
+ setPriorityItems( NULL, NULL, NULL );
+ }
+ if (mItems.isEmpty()) {
+ LOG("Nothing to do");
+ emit result(this);
+ delete this;
+ return;
+ }
+
+ determineNextIcon();
+}
+
+void ThumbnailLoadJob::suspend() {
+ mSuspended = true;
+}
+
+void ThumbnailLoadJob::resume() {
+ if( !mSuspended ) return;
+ mSuspended = false;
+ if( mState == STATE_NEXTTHUMB ) // don't load next while already loading
+ determineNextIcon();
+}
+
+//-Internal--------------------------------------------------------------
+void ThumbnailLoadJob::appendItem(const KFileItem* item) {
+ int index = thumbnailIndex( item );
+ if( index >= 0 ) {
+ mProcessedState[ index ] = false;
+ return;
+ }
+ mAllItems.append(item);
+ mProcessedState.append( false );
+ updateItemsOrder();
+}
+
+
+void ThumbnailLoadJob::itemRemoved(const KFileItem* item) {
+ Q_ASSERT(item);
+
+ // If we are removing the next item, update to be the item after or the
+ // first if we removed the last item
+ mItems.remove( item );
+ int index = thumbnailIndex( item );
+ if( index >= 0 ) {
+ mAllItems.erase( mAllItems.begin() + index );
+ mProcessedState.erase( mProcessedState.begin() + index );
+ }
+
+ if (item == mCurrentItem) {
+ // Abort
+ mCurrentItem = NULL;
+ if (subjobs.first()) {
+ subjobs.first()->kill();
+ subjobs.removeFirst();
+ }
+ determineNextIcon();
+ }
+}
+
+
+void ThumbnailLoadJob::setPriorityItems(const KFileItem* current, const KFileItem* first, const KFileItem* last) {
+ if( mAllItems.isEmpty()) {
+ mCurrentVisibleIndex = mFirstVisibleIndex = mLastVisibleIndex = 0;
+ return;
+ }
+ mFirstVisibleIndex = -1;
+ mLastVisibleIndex = - 1;
+ mCurrentVisibleIndex = -1;
+ if( first != NULL ) mFirstVisibleIndex = thumbnailIndex( first );
+ if( last != NULL ) mLastVisibleIndex = thumbnailIndex( last );
+ if( current != NULL ) mCurrentVisibleIndex = thumbnailIndex( current );
+ if( mFirstVisibleIndex == -1 ) mFirstVisibleIndex = 0;
+ if( mLastVisibleIndex == -1 ) mLastVisibleIndex = mAllItems.count() - 1;
+ if( mCurrentVisibleIndex == -1 ) mCurrentVisibleIndex = mFirstVisibleIndex;
+ updateItemsOrder();
+}
+
+void ThumbnailLoadJob::updateItemsOrder() {
+ mItems.clear();
+ int forward = mCurrentVisibleIndex + 1;
+ int backward = mCurrentVisibleIndex;
+ int first = mFirstVisibleIndex;
+ int last = mLastVisibleIndex;
+ updateItemsOrderHelper( forward, backward, first, last );
+ if( first != 0 || last != int( mAllItems.count()) - 1 ) {
+ // add non-visible items
+ updateItemsOrderHelper( last + 1, first - 1, 0, mAllItems.count() - 1);
+ }
+}
+
+void ThumbnailLoadJob::updateItemsOrderHelper( int forward, int backward, int first, int last ) {
+ // start from the current item, add one following it, and one preceding it, for all visible items
+ while( forward <= last || backward >= first ) {
+ // start with backward - that's the curent item for the first time
+ while( backward >= first ) {
+ if( !mProcessedState[ backward ] ) {
+ mItems.append( mAllItems[ backward ] );
+ --backward;
+ break;
+ }
+ --backward;
+ }
+ while( forward <= last ) {
+ if( !mProcessedState[ forward ] ) {
+ mItems.append( mAllItems[ forward ] );
+ ++forward;
+ break;
+ }
+ ++forward;
+ }
+ }
+}
+
+void ThumbnailLoadJob::determineNextIcon() {
+ mState = STATE_NEXTTHUMB;
+ if( mSuspended ) {
+ return;
+ }
+
+ // No more items ?
+ if (mItems.isEmpty()) {
+ // Done
+ LOG("emitting result");
+ emit result(this);
+ delete this;
+ return;
+ }
+
+ mCurrentItem=mItems.first();
+ mItems.pop_front();
+ Q_ASSERT( !mProcessedState[ thumbnailIndex( mCurrentItem )] );
+ mProcessedState[ thumbnailIndex( mCurrentItem )] = true;
+
+ // First, stat the orig file
+ mState = STATE_STATORIG;
+ mOriginalTime = 0;
+ mCurrentURL = mCurrentItem->url();
+ mCurrentURL.cleanPath();
+
+ // Do direct stat instead of using KIO if the file is local (faster)
+ if( mCurrentURL.isLocalFile()
+ && !KIO::probably_slow_mounted( mCurrentURL.path())) {
+ KDE_struct_stat buff;
+ if ( KDE_stat( QFile::encodeName(mCurrentURL.path()), &buff ) == 0 ) {
+ mOriginalTime = buff.st_mtime;
+ QTimer::singleShot( 0, this, SLOT( checkThumbnail()));
+ }
+ }
+ if( mOriginalTime == 0 ) { // KIO must be used
+ KIO::Job* job = KIO::stat(mCurrentURL,false);
+ job->setWindow(KApplication::kApplication()->mainWidget());
+ LOG( "KIO::stat orig " << mCurrentURL.url() );
+ addSubjob(job);
+ }
+}
+
+
+void ThumbnailLoadJob::slotResult(KIO::Job * job) {
+ LOG(mState);
+ subjobs.remove(job);
+ Q_ASSERT(subjobs.isEmpty()); // We should have only one job at a time ...
+
+ switch (mState) {
+ case STATE_NEXTTHUMB:
+ Q_ASSERT(false);
+ determineNextIcon();
+ return;
+
+ case STATE_STATORIG: {
+ // Could not stat original, drop this one and move on to the next one
+ if (job->error()) {
+ emitThumbnailLoadingFailed();
+ determineNextIcon();
+ return;
+ }
+
+ // Get modification time of the original file
+ KIO::UDSEntry entry = static_cast<KIO::StatJob*>(job)->statResult();
+ KIO::UDSEntry::ConstIterator it= entry.begin();
+ mOriginalTime = 0;
+ for (; it!=entry.end(); ++it) {
+ if ((*it).m_uds == KIO::UDS_MODIFICATION_TIME) {
+ mOriginalTime = (time_t)((*it).m_long);
+ break;
+ }
+ }
+ checkThumbnail();
+ return;
+ }
+
+ case STATE_DOWNLOADORIG:
+ if (job->error()) {
+ emitThumbnailLoadingFailed();
+ LOG("Delete temp file " << mTempPath);
+ QFile::remove(mTempPath);
+ mTempPath = QString::null;
+ determineNextIcon();
+ } else {
+ startCreatingThumbnail(mTempPath);
+ }
+ return;
+
+ case STATE_PREVIEWJOB:
+ determineNextIcon();
+ return;
+ }
+}
+
+
+void ThumbnailLoadJob::thumbnailReady( const QImage& im, const QSize& _size) {
+ QImage img = TSDeepCopy( im );
+ QSize size = _size;
+ if ( !img.isNull()) {
+ emitThumbnailLoaded(img, size);
+ } else {
+ emitThumbnailLoadingFailed();
+ }
+ if( !mTempPath.isEmpty()) {
+ LOG("Delete temp file " << mTempPath);
+ QFile::remove(mTempPath);
+ mTempPath = QString::null;
+ }
+ determineNextIcon();
+}
+
+void ThumbnailLoadJob::checkThumbnail() {
+ // If we are in the thumbnail dir, just load the file
+ if (mCurrentURL.isLocalFile()
+ && mCurrentURL.directory(false).startsWith(thumbnailBaseDir()) )
+ {
+ QImage image(mCurrentURL.path());
+ emitThumbnailLoaded(image, image.size());
+ determineNextIcon();
+ return;
+ }
+ QSize imagesize;
+ if( mOriginalTime == time_t( Cache::instance()->timestamp( mCurrentURL ).toTime_t())) {
+ QPixmap cached = Cache::instance()->thumbnail( mCurrentURL, imagesize );
+ if( !cached.isNull()) {
+ emit thumbnailLoaded(mCurrentItem, cached, imagesize);
+ determineNextIcon();
+ return;
+ }
+ }
+
+ mOriginalURI=generateOriginalURI(mCurrentURL);
+ mThumbnailPath=generateThumbnailPath(mOriginalURI, mThumbnailSize);
+
+ LOG("Stat thumb " << mThumbnailPath);
+
+ QImage thumb;
+ if ( thumb.load(mThumbnailPath) ) {
+ if (thumb.text("Thumb::URI", 0) == mOriginalURI &&
+ thumb.text("Thumb::MTime", 0).toInt() == mOriginalTime )
+ {
+ int width=0, height=0;
+ QSize size;
+ bool ok;
+
+ width=thumb.text("Thumb::Image::Width", 0).toInt(&ok);
+ if (ok) height=thumb.text("Thumb::Image::Height", 0).toInt(&ok);
+ if (ok) {
+ size=QSize(width, height);
+ } else {
+ LOG("Thumbnail for " << mOriginalURI << " does not contain correct image size information");
+ KFileMetaInfo fmi(mCurrentURL);
+ if (fmi.isValid()) {
+ KFileMetaInfoItem item=fmi.item("Dimensions");
+ if (item.isValid()) {
+ size=item.value().toSize();
+ } else {
+ LOG("KFileMetaInfoItem for " << mOriginalURI << " did not get image size information");
+ }
+ } else {
+ LOG("Could not get a valid KFileMetaInfo instance for " << mOriginalURI);
+ }
+ }
+ emitThumbnailLoaded(thumb, size);
+ determineNextIcon();
+ return;
+ }
+ }
+
+ // Thumbnail not found or not valid
+ if ( MimeTypeUtils::rasterImageMimeTypes().contains(mCurrentItem->mimetype()) ) {
+ // This is a raster image
+ if (mCurrentURL.isLocalFile()) {
+ // Original is a local file, create the thumbnail
+ startCreatingThumbnail(mCurrentURL.path());
+ } else {
+ // Original is remote, download it
+ mState=STATE_DOWNLOADORIG;
+ KTempFile tmpFile;
+ mTempPath=tmpFile.name();
+ KURL url;
+ url.setPath(mTempPath);
+ KIO::Job* job=KIO::file_copy(mCurrentURL, url,-1,true,false,false);
+ job->setWindow(KApplication::kApplication()->mainWidget());
+ LOG("Download remote file " << mCurrentURL.prettyURL());
+ addSubjob(job);
+ }
+ } else {
+ // Not a raster image, use a KPreviewJob
+ mState=STATE_PREVIEWJOB;
+ KFileItemList list;
+ list.append(mCurrentItem);
+ KIO::Job* job=KIO::filePreview(list, mThumbnailSize);
+ job->setWindow(KApplication::kApplication()->mainWidget());
+ connect(job, SIGNAL(gotPreview(const KFileItem*, const QPixmap&)),
+ this, SLOT(slotGotPreview(const KFileItem*, const QPixmap&)) );
+ connect(job, SIGNAL(failed(const KFileItem*)),
+ this, SLOT(emitThumbnailLoadingFailed()) );
+ addSubjob(job);
+ return;
+ }
+}
+
+void ThumbnailLoadJob::startCreatingThumbnail(const QString& pixPath) {
+ LOG("Creating thumbnail from " << pixPath);
+ mThumbnailThread.load( mOriginalURI, mOriginalTime, mCurrentItem->size(),
+ mCurrentItem->mimetype(), pixPath, mThumbnailPath, mThumbnailSize,
+ FileViewConfig::storeThumbnailsInCache());
+}
+
+
+void ThumbnailLoadJob::slotGotPreview(const KFileItem* item, const QPixmap& pixmap) {
+ LOG("");
+ QSize size;
+ emit thumbnailLoaded(item, pixmap, size);
+}
+
+
+void ThumbnailLoadJob::emitThumbnailLoaded(const QImage& img, QSize size) {
+ int biggestDimension=QMAX(img.width(), img.height());
+
+ QImage thumbImg;
+ if (biggestDimension>mThumbnailSize) {
+ // Scale down thumbnail if necessary
+ thumbImg=ImageUtils::scale(img,mThumbnailSize, mThumbnailSize, ImageUtils::SMOOTH_FAST,QImage::ScaleMin);
+ } else {
+ thumbImg=img;
+ }
+ QDateTime tm;
+ tm.setTime_t( mOriginalTime );
+ QPixmap thumb( thumbImg ); // store as QPixmap in cache (faster to retrieve, no conversion needed)
+ Cache::instance()->addThumbnail( mCurrentURL, thumb, size, tm );
+ emit thumbnailLoaded(mCurrentItem, thumb, size);
+}
+
+
+void ThumbnailLoadJob::emitThumbnailLoadingFailed() {
+ QSize size;
+ emit thumbnailLoaded(mCurrentItem, mBrokenPixmap, size);
+}
+
+
+} // namespace
diff --git a/src/gvcore/thumbnailloadjob.h b/src/gvcore/thumbnailloadjob.h
new file mode 100644
index 0000000..331708d
--- /dev/null
+++ b/src/gvcore/thumbnailloadjob.h
@@ -0,0 +1,211 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab
+/* Gwenview - A simple image viewer for KDE
+ Copyright 2000-2004 Aurélien Gâteau
+ This class is based on the ImagePreviewJob class from Konqueror.
+ Original copyright follows.
+*/
+/* This file is part of the KDE project
+ Copyright (C) 2000 David Faure <faure@kde.org>
+
+ 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.
+*/
+
+#ifndef THUMBNAILLOADJOB_H
+#define THUMBNAILLOADJOB_H
+
+// Qt
+#include <qimage.h>
+#include <qpixmap.h>
+#include <qvaluelist.h>
+#include <qvaluevector.h>
+
+// KDE
+#include <kio/job.h>
+
+// Local
+#include "tsthread/tsthread.h"
+#include "tsthread/tswaitcondition.h"
+
+#include "libgwenview_export.h"
+class KConfig;
+class KFileItem;
+
+typedef QPtrList<KFileItem> KFileItemList;
+
+namespace Gwenview {
+class ThumbnailThread : public TSThread {
+Q_OBJECT
+public:
+ void load(
+ const QString& originalURI,
+ time_t originalTime,
+ int originalSize,
+ const QString& originalMimeType,
+ const QString& pixPath,
+ const QString& thumbnailPath,
+ int size,
+ bool storeThumbnail);
+protected:
+ virtual void run();
+signals:
+ void done( const QImage&, const QSize&);
+private:
+ bool isJPEG();
+ bool loadJPEG();
+ void loadThumbnail();
+ QImage mImage;
+ QString mPixPath;
+ QString mThumbnailPath;
+ QString mOriginalURI;
+ time_t mOriginalTime;
+ int mOriginalSize;
+ QString mOriginalMimeType;
+ int mOriginalWidth;
+ int mOriginalHeight;
+ QMutex mMutex;
+ TSWaitCondition mCond;
+ int mThumbnailSize;
+ bool mStoreThumbnailsInCache;
+};
+
+/**
+ * A job that determines the thumbnails for the images in the current directory
+ */
+class LIBGWENVIEW_EXPORT ThumbnailLoadJob : public KIO::Job {
+Q_OBJECT
+public:
+ /**
+ * Create a job for determining the pixmaps of the images in the @p itemList
+ */
+ ThumbnailLoadJob(const QValueVector<const KFileItem*>* itemList, int size);
+ virtual ~ThumbnailLoadJob();
+
+ /**
+ * Call this to get started
+ */
+ void start();
+
+ /**
+ * To be called whenever an item is removed from the view
+ */
+ void itemRemoved(const KFileItem* item);
+
+
+ /**
+ * Add an item to a running job
+ */
+ void appendItem(const KFileItem* item);
+
+
+ /**
+ * Sets items in range first..last to be generated first, starting with current.
+ */
+ void setPriorityItems(const KFileItem* current, const KFileItem* first, const KFileItem* last);
+
+ /**
+ * Temporarily suspends loading. Used if there's a more
+ * important action going on (loading an image etc.).
+ */
+ void suspend();
+
+ /**
+ * Resumes loading if suspended.
+ */
+ void resume();
+
+ /**
+ * Returns the thumbnail base dir, independent of the thumbnail size
+ */
+ static QString thumbnailBaseDir();
+
+ /**
+ * Returns the thumbnail base dir, for the @p size
+ */
+ static QString thumbnailBaseDir(int size);
+
+
+ /**
+ * Delete the thumbnail for the @p url
+ */
+ static void deleteImageThumbnail(const KURL& url);
+
+signals:
+ /**
+ * Emitted when the thumbnail for the @p item has been loaded
+ */
+ void thumbnailLoaded(const KFileItem* item, const QPixmap&, const QSize&);
+
+private slots:
+ void slotResult( KIO::Job *job );
+ void slotGotPreview(const KFileItem*, const QPixmap&);
+ void checkThumbnail();
+ void thumbnailReady(const QImage& im, const QSize&);
+ void emitThumbnailLoadingFailed();
+
+private:
+ enum { STATE_STATORIG, STATE_DOWNLOADORIG, STATE_PREVIEWJOB, STATE_NEXTTHUMB } mState;
+
+ QValueList<const KFileItem*> mItems;
+ QValueVector<const KFileItem* > mAllItems;
+ QValueVector< bool > mProcessedState;
+ const KFileItem *mCurrentItem;
+ int thumbnailIndex( const KFileItem* item ) const;
+ void updateItemsOrder();
+
+ // indexes of the current, fist and last visible thumbnails
+ int mCurrentVisibleIndex, mFirstVisibleIndex, mLastVisibleIndex;
+
+ // The URL of the current item (always equivalent to m_items.first()->item()->url())
+ KURL mCurrentURL;
+
+ // The URI of the original image (might be different from mCurrentURL.url())
+ QString mOriginalURI;
+
+ // The modification time of the original image
+ time_t mOriginalTime;
+
+ // The thumbnail path
+ QString mThumbnailPath;
+
+ // The temporary path for remote urls
+ QString mTempPath;
+
+ // Thumbnail size
+ int mThumbnailSize;
+
+ QPixmap mBrokenPixmap;
+
+ bool mSuspended;
+
+ ThumbnailThread mThumbnailThread;
+
+ void determineNextIcon();
+ void startCreatingThumbnail(const QString& path);
+
+ void emitThumbnailLoaded(const QImage& img, QSize size);
+
+ void updateItemsOrderHelper( int forward, int backward, int first, int last );
+};
+
+inline
+int ThumbnailLoadJob::thumbnailIndex( const KFileItem* item ) const {
+ QValueVector<const KFileItem* >::ConstIterator pos = qFind( mAllItems.begin(), mAllItems.end(), item );
+ if( pos != mAllItems.end()) return pos - mAllItems.begin();
+ return -1;
+}
+
+} // namespace
+#endif
+
diff --git a/src/gvcore/thumbnailsize.h b/src/gvcore/thumbnailsize.h
new file mode 100644
index 0000000..400c22e
--- /dev/null
+++ b/src/gvcore/thumbnailsize.h
@@ -0,0 +1,42 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab:
+/* Gwenview - A simple image viewer for KDE
+ Copyright 2000-2004 Aurélien Gâteau
+ This class is based on the ImagePreviewJob class from Konqueror.
+ Original copyright follows.
+*/
+/* This file is part of the KDE project
+ Copyright (C) 2000 David Faure <faure@kde.org>
+ 2000 Carsten Pfeiffer <pfeiffer@kde.org>
+
+ 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.
+*/
+#ifndef THUMBNAILSIZE_H
+#define THUMBNAILSIZE_H
+
+namespace Gwenview {
+
+/**
+ * This namespace stores constants used by several files. It avoids multiple
+ * includes.
+ */
+namespace ThumbnailSize {
+ const int MIN=48;
+ const int NORMAL=128;
+ const int LARGE=256;
+}
+
+} // namespace
+#endif /* THUMBNAILSIZE_H */
+
diff --git a/src/gvcore/timeutils.cpp b/src/gvcore/timeutils.cpp
new file mode 100644
index 0000000..d79c24d
--- /dev/null
+++ b/src/gvcore/timeutils.cpp
@@ -0,0 +1,53 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab:
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2000-2006 Aurélien Gâteau
+
+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 "timeutils.h"
+
+// KDE
+#include <kdebug.h>
+#include <kfileitem.h>
+#include <kfilemetainfo.h>
+#include <kglobal.h>
+#include <klocale.h>
+
+namespace Gwenview {
+namespace TimeUtils {
+
+time_t getTime(const KFileItem* item) {
+ const KFileMetaInfo& info = item->metaInfo();
+ if (info.isValid()) {
+ QVariant value = info.value("Date/time");
+ QDateTime dt = value.toDateTime();
+ if (dt.isValid()) {
+ return dt.toTime_t();
+ }
+ }
+ return item->time(KIO::UDS_MODIFICATION_TIME);
+}
+
+QString formatTime(time_t time) {
+ QDateTime dt;
+ dt.setTime_t(time);
+ return KGlobal::locale()->formatDateTime(dt);
+}
+
+
+} // namespace TimeUtils
+} // namespace Gwenview
diff --git a/src/gvcore/timeutils.h b/src/gvcore/timeutils.h
new file mode 100644
index 0000000..5815965
--- /dev/null
+++ b/src/gvcore/timeutils.h
@@ -0,0 +1,44 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab:
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2000-2006 Aurélien Gâteau
+
+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.
+
+*/
+#ifndef TIMEUTILS_H
+#define TIMEUTILS_H
+
+#include <time.h>
+
+class KFileItem;
+class QString;
+
+namespace Gwenview {
+namespace TimeUtils {
+
+/**
+ * Returns the time of an item, using EXIF info if available
+ */
+time_t getTime(const KFileItem*);
+
+QString formatTime(time_t);
+
+} // namespace TimeUtils
+} // namespace Gwenview
+
+
+
+#endif /* TIMEUTILS_H */
diff --git a/src/gvcore/xcursor.cpp b/src/gvcore/xcursor.cpp
new file mode 100644
index 0000000..79df377
--- /dev/null
+++ b/src/gvcore/xcursor.cpp
@@ -0,0 +1,190 @@
+/* This file is part of the KDE libraries
+ Copyright (C) 2003 Fredrik Höglund <fredrik@kde.org>
+ Copyright (C) 2005 Lubos Lunak <l.lunak@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include <config.h>
+
+#include <assert.h>
+
+#include "xcursor.h"
+
+#include <qimage.h>
+#include <kdebug.h>
+#include <qvaluevector.h>
+
+#ifdef GV_HAVE_XCURSOR
+#include <X11/Xlib.h>
+#include <X11/Xcursor/Xcursor.h>
+#include <fixx11h.h>
+#endif
+
+namespace Gwenview {
+
+#ifdef GV_HAVE_XCURSOR
+class XCursorFormat : public QImageFormat {
+public:
+ XCursorFormat();
+ virtual int decode(QImage& img, QImageConsumer* consumer,
+ const uchar* buffer, int length);
+ QByteArray array;
+ int pos;
+ bool was_seek_error;
+};
+
+extern "C"
+int xcursor_read( XcursorFile* file, unsigned char* buf, int len )
+{
+ XCursorFormat* data = reinterpret_cast< XCursorFormat* >( file->closure );
+ if( int( data->array.size()) - data->pos < len )
+ {
+ data->was_seek_error = true;
+ len = data->array.size() - data->pos;
+ }
+ memcpy( buf, data->array.data() + data->pos, len );
+ data->pos += len;
+ return len;
+}
+
+extern "C"
+int xcursor_write( XcursorFile*, unsigned char*, int )
+{
+ assert( false ); // not write support
+ return 0;
+}
+
+extern "C"
+int xcursor_seek( XcursorFile* file, long offset, int whence )
+{
+ XCursorFormat* data = reinterpret_cast< XCursorFormat* >( file->closure );
+ if( whence == SEEK_CUR )
+ offset += data->pos;
+ else if( whence == SEEK_END )
+ offset = data->array.size() + offset;
+ if( offset < 0 || offset >= int( data->array.size()))
+ {
+ data->was_seek_error = true;
+ return -1;
+ }
+ data->pos = offset;
+ return 0;
+}
+
+XCursorFormat::XCursorFormat()
+{
+}
+
+int XCursorFormat::decode(QImage& img, QImageConsumer* cons,
+ const uchar* buffer, int length)
+{
+ if( length > 0 )
+ {
+ int old_size = array.size();
+ array.resize( old_size + length );
+ memcpy( array.data() + old_size, buffer, length );
+ }
+// Qt's image async IO and Xcursor library have so nicely incompatible data reading :-/
+// Xcursor can read a file only as a whole. One can provide XcursorFile with functions
+// for reading, writing and seeking. And seeking is the stupid part on Xcursor's side,
+// as there's no way to suspend reading until more data is available. This means we
+// basically have to read the whole file first. However, Qt's image async IO just keeps
+// feeding data and when there's end of the data it just does nothing and doesn't tell
+// the decoder.
+// So the trick will be calling XcursorXcFileLoadImage() but whenever there will be
+// a seeking/reading error, then the function will fail and we'll try again when there's
+// more data. Also, reading everything first means we can't return without processing
+// further data after reaching end of a frame in the input as decode() doc says, but oh well.
+ XcursorFile file;
+ file.closure = this;
+ file.read = xcursor_read;
+ file.write = xcursor_write;
+ file.seek = xcursor_seek;
+ pos = 0;
+ was_seek_error = false;
+ XcursorImages *cursors = XcursorXcFileLoadImages( &file, 1024 ); // largest possible cursor size
+ if ( cursors )
+ {
+ for( int cur = 0;
+ cur < cursors->nimage;
+ ++cur )
+ {
+ XcursorImage* cursor = cursors->images[ cur ];
+ img = QImage( reinterpret_cast<uchar *>( cursor->pixels ),
+ cursor->width, cursor->height, 32, NULL, 0, QImage::BigEndian );
+ img.setAlphaBuffer( true );
+ // Convert the image to non-premultiplied alpha
+ Q_UINT32 *pixels = reinterpret_cast<Q_UINT32 *>( img.bits() );
+ for ( int i = 0; i < (img.width() * img.height()); i++ )
+ {
+ float alpha = qAlpha( pixels[i] ) / 255.0;
+ if ( alpha > 0.0 && alpha < 1.0 )
+ pixels[i] = qRgba( int( qRed(pixels[i]) / alpha ),
+ int( qGreen(pixels[i]) / alpha ),
+ int( qBlue(pixels[i]) / alpha ),
+ qAlpha(pixels[i]) );
+ }
+ // Create a deep copy of the image so the image data is preserved
+ img = img.copy();
+ if( cons )
+ {
+ if( cur == 0 )
+ {
+ cons->setSize( img.width(), img.height());
+ if( cursors->nimage > 1 )
+ cons->setLooping( 0 );
+ }
+ cons->changed( QRect( QPoint( 0, 0 ), img.size()));
+ cons->frameDone();
+ cons->setFramePeriod( cursor->delay );
+ }
+ }
+ XcursorImagesDestroy( cursors );
+ if( cons )
+ cons->end();
+ return length;
+ }
+ else if( was_seek_error ) // try again next time
+ return length;
+ return -1; // failure
+}
+#endif
+
+QImageFormat* XCursorFormatType::decoderFor(
+ const uchar* buffer, int length)
+{
+#ifdef GV_HAVE_XCURSOR
+ if (length < 4) return 0;
+ if (buffer[0]=='X'
+ && buffer[1]=='c'
+ && buffer[2]=='u'
+ && buffer[3]=='r')
+ return new XCursorFormat;
+#else
+ Q_UNUSED( buffer );
+ Q_UNUSED( length );
+#endif
+ return 0;
+}
+
+const char* XCursorFormatType::formatName() const
+{
+ return "XCursor";
+}
+
+
+} // namespace
diff --git a/src/gvcore/xcursor.h b/src/gvcore/xcursor.h
new file mode 100644
index 0000000..27bec9a
--- /dev/null
+++ b/src/gvcore/xcursor.h
@@ -0,0 +1,37 @@
+/* This file is part of the KDE libraries
+ Copyright (C) 2003 Fredrik Höglund <fredrik@kde.org>
+ Copyright (C) 2005 Lubos Lunak <l.lunak@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#ifndef gvxcursor_h
+#define gvxcursor_h
+
+#include <qasyncimageio.h>
+
+namespace Gwenview {
+
+class XCursorFormatType : public QImageFormatType {
+public:
+ QImageFormat* decoderFor(const uchar* buffer, int length);
+ const char* formatName() const;
+};
+
+// -----------------------------------------------------------------------------
+} // namespace
+
+#endif // gvxcursor_h
diff --git a/src/gvcore/xpm.cpp b/src/gvcore/xpm.cpp
new file mode 100644
index 0000000..ce7656f
--- /dev/null
+++ b/src/gvcore/xpm.cpp
@@ -0,0 +1,439 @@
+/* This file is based on qt-3.3.2/src/qimage.cpp , plus it includes
+ * a fix from qt-bugs@ issue #49934. Original copyright follows.
+ */
+/****************************************************************************
+**
+**
+** Implementation of QImage and QImageIO classes
+**
+** Created : 950207
+**
+** Copyright (C) 1992-2003 Trolltech AS. All rights reserved.
+**
+** This file is part of the kernel module of the Qt GUI Toolkit.
+**
+** This file may be distributed under the terms of the Q Public License
+** as defined by Trolltech AS of Norway and appearing in the file
+** LICENSE.QPL included in the packaging of this file.
+**
+** This file may be distributed and/or modified under the terms of the
+** GNU General Public License version 2 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file.
+**
+** Licensees holding valid Qt Enterprise Edition or Qt Professional Edition
+** licenses may use this file in accordance with the Qt Commercial License
+** Agreement provided with the Software.
+**
+** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
+** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+**
+** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for
+** information about Qt Commercial License Agreements.
+** See http://www.trolltech.com/qpl/ for QPL licensing information.
+** See http://www.trolltech.com/gpl/ for GPL licensing information.
+**
+** Contact info@trolltech.com if any conditions of this licensing are
+** not clear to you.
+**
+**********************************************************************/
+
+/*
+
+ This code is xpm loader copied from qimage.cpp, with changes making it
+ possible to use this class from another thread. The problem is that
+ QColor::setNamedColor() isn't reentrant without thread-safe Xlib,
+ i.e. without XInitThreads() called. Current KDE applications
+ don't call XInitThreads(), and since it needs to be the very first
+ Xlib function called, and Gwenview is also a KPart, it's not possible
+ to rely on Xlib being thread-safe.
+
+ Changes are marked using #ifdef GV_XPM_CHANGES.
+
+*/
+
+#define GV_XPM_CHANGES
+
+#include "xpm.h"
+
+#include <qimage.h>
+#include <qmap.h>
+#include <qregexp.h>
+
+#include "threadgate.h"
+
+namespace Gwenview {
+
+// Qt code start ---------------------------
+static QString fbname( const QString &fileName ) // get file basename (sort of)
+{
+ QString s = fileName;
+ if ( !s.isEmpty() ) {
+ int i;
+ if ( (i = s.findRev('/')) >= 0 )
+ s = s.mid( i );
+ if ( (i = s.findRev('\\')) >= 0 )
+ s = s.mid( i );
+ QRegExp r( QString::fromLatin1("[a-zA-Z][a-zA-Z0-9_]*") );
+ int p = r.search( s );
+ if ( p == -1 )
+ s.truncate( 0 );
+ else
+ s = s.mid( p, r.matchedLength() );
+ }
+ if ( s.isEmpty() )
+ s = QString::fromLatin1( "dummy" );
+ return s;
+}
+
+/*****************************************************************************
+ XPM image read/write functions
+ *****************************************************************************/
+
+
+// Skip until ", read until the next ", return the rest in *buf
+// Returns FALSE on error, TRUE on success
+
+static bool read_xpm_string( QCString &buf, QIODevice *d,
+ const char * const *source, int &index )
+{
+ if ( source ) {
+ buf = source[index++];
+ return TRUE;
+ }
+
+ if ( buf.size() < 69 ) //# just an approximation
+ buf.resize( 123 );
+
+ buf[0] = '\0';
+ int c;
+ int i;
+ while ( (c=d->getch()) != EOF && c != '"' ) { }
+ if ( c == EOF ) {
+ return FALSE;
+ }
+ i = 0;
+ while ( (c=d->getch()) != EOF && c != '"' ) {
+ if ( i == (int)buf.size() )
+ buf.resize( i*2+42 );
+ buf[i++] = c;
+ }
+ if ( c == EOF ) {
+ return FALSE;
+ }
+
+ if ( i == (int)buf.size() ) // always use a 0 terminator
+ buf.resize( i+1 );
+ buf[i] = '\0';
+ return TRUE;
+}
+
+
+
+static int nextColorSpec(const QCString & buf)
+{
+ int i = buf.find(" c ");
+ if (i < 0)
+ i = buf.find(" g ");
+ if (i < 0)
+ i = buf.find(" g4 ");
+ if (i < 0)
+ i = buf.find(" m ");
+ if (i < 0)
+ i = buf.find(" s ");
+ return i;
+}
+
+//
+// INTERNAL
+//
+// Reads an .xpm from either the QImageIO or from the QString *.
+// One of the two HAS to be 0, the other one is used.
+//
+
+static void read_xpm_image_or_array( QImageIO * iio, const char * const * source,
+ QImage & image)
+{
+ QCString buf;
+ QIODevice *d = 0;
+ buf.resize( 200 );
+
+ int i, cpp, ncols, w, h, index = 0;
+
+ if ( iio ) {
+ iio->setStatus( 1 );
+ d = iio ? iio->ioDevice() : 0;
+ d->readLine( buf.data(), buf.size() ); // "/* XPM */"
+ QRegExp r( QString::fromLatin1("/\\*.XPM.\\*/") );
+ if ( buf.find(r) == -1 )
+ return; // bad magic
+ } else if ( !source ) {
+ return;
+ }
+
+ if ( !read_xpm_string( buf, d, source, index ) )
+ return;
+
+ if ( sscanf( buf, "%d %d %d %d", &w, &h, &ncols, &cpp ) < 4 )
+ return; // < 4 numbers parsed
+
+ if ( cpp > 15 )
+ return;
+
+ if ( ncols > 256 ) {
+ image.create( w, h, 32 );
+ } else {
+ image.create( w, h, 8, ncols );
+ }
+
+ if (image.isNull())
+ return;
+
+ QMap<QString, int> colorMap;
+ int currentColor;
+
+ for( currentColor=0; currentColor < ncols; ++currentColor ) {
+ if ( !read_xpm_string( buf, d, source, index ) ) {
+#if defined(QT_CHECK_RANGE)
+ qWarning( "QImage: XPM color specification missing");
+#endif
+ return;
+ }
+ QString index;
+ index = buf.left( cpp );
+ buf = buf.mid( cpp ).simplifyWhiteSpace().lower();
+ buf.prepend( " " );
+ i = nextColorSpec(buf);
+ if ( i < 0 ) {
+#if defined(QT_CHECK_RANGE)
+ qWarning( "QImage: XPM color specification is missing: %s", buf.data());
+#endif
+ return; // no c/g/g4/m/s specification at all
+ }
+ buf = buf.mid( i+3 );
+ // Strip any other colorspec
+ int end = nextColorSpec(buf);
+ if (end != -1)
+ buf.truncate(end);
+ buf = buf.stripWhiteSpace();
+ if ( buf == "none" ) {
+ image.setAlphaBuffer( TRUE );
+ int transparentColor = currentColor;
+ if ( image.depth() == 8 ) {
+ image.setColor( transparentColor,
+ RGB_MASK & qRgb(198,198,198) );
+ colorMap.insert( index, transparentColor );
+ } else {
+ QRgb rgb = RGB_MASK & qRgb(198,198,198);
+ colorMap.insert( index, rgb );
+ }
+ } else {
+ if ( ((buf.length()-1) % 3) && (buf[0] == '#') ) {
+ buf.truncate (((buf.length()-1) / 4 * 3) + 1); // remove alpha channel left by imagemagick
+ }
+#ifdef GV_XPM_CHANGES
+ QColor c = ThreadGate::instance()->color( buf.data());
+#else
+ QColor c( buf.data() );
+#endif
+ if ( image.depth() == 8 ) {
+ image.setColor( currentColor, 0xff000000 | c.rgb() );
+ colorMap.insert( index, currentColor );
+ } else {
+ QRgb rgb = 0xff000000 | c.rgb();
+ colorMap.insert( index, rgb );
+ }
+ }
+ }
+
+ // Read pixels
+ for( int y=0; y<h; y++ ) {
+ if ( !read_xpm_string( buf, d, source, index ) ) {
+#if defined(QT_CHECK_RANGE)
+ qWarning( "QImage: XPM pixels missing on image line %d", y);
+#endif
+ return;
+ }
+ if ( image.depth() == 8 ) {
+ uchar *p = image.scanLine(y);
+ uchar *d = (uchar *)buf.data();
+ uchar *end = d + buf.length();
+ int x;
+ if ( cpp == 1 ) {
+ char b[2];
+ b[1] = '\0';
+ for ( x=0; x<w && d<end; x++ ) {
+ b[0] = *d++;
+ *p++ = (uchar)colorMap[b];
+ }
+ } else {
+ char b[16];
+ b[cpp] = '\0';
+ for ( x=0; x<w && d<end; x++ ) {
+ strncpy( b, (char *)d, cpp );
+ *p++ = (uchar)colorMap[b];
+ d += cpp;
+ }
+ }
+ } else {
+ QRgb *p = (QRgb*)image.scanLine(y);
+ uchar *d = (uchar *)buf.data();
+ uchar *end = d + buf.length();
+ int x;
+ char b[16];
+ b[cpp] = '\0';
+ for ( x=0; x<w && d<end; x++ ) {
+ strncpy( b, (char *)d, cpp );
+ *p++ = (QRgb)colorMap[b];
+ d += cpp;
+ }
+ }
+ }
+ if ( iio ) {
+ iio->setImage( image );
+ iio->setStatus( 0 ); // image ok
+ }
+}
+
+
+static void read_xpm_image( QImageIO * iio )
+{
+ QImage i;
+ (void)read_xpm_image_or_array( iio, 0, i );
+ return;
+}
+
+
+static const char* xpm_color_name( int cpp, int index )
+{
+ static char returnable[5];
+ static const char code[] = ".#abcdefghijklmnopqrstuvwxyzABCD"
+ "EFGHIJKLMNOPQRSTUVWXYZ0123456789";
+ // cpp is limited to 4 and index is limited to 64^cpp
+ if ( cpp > 1 ) {
+ if ( cpp > 2 ) {
+ if ( cpp > 3 ) {
+ returnable[3] = code[index % 64];
+ index /= 64;
+ } else
+ returnable[3] = '\0';
+ returnable[2] = code[index % 64];
+ index /= 64;
+ } else
+ returnable[2] = '\0';
+ // the following 4 lines are a joke!
+ if ( index == 0 )
+ index = 64*44+21;
+ else if ( index == 64*44+21 )
+ index = 0;
+ returnable[1] = code[index % 64];
+ index /= 64;
+ } else
+ returnable[1] = '\0';
+ returnable[0] = code[index];
+
+ return returnable;
+}
+
+
+// write XPM image data
+static void write_xpm_image( QImageIO * iio )
+{
+ if ( iio )
+ iio->setStatus( 1 );
+ else
+ return;
+
+ // ### 8-bit case could be made faster
+ QImage image;
+ if ( iio->image().depth() != 32 )
+ image = iio->image().convertDepth( 32 );
+ else
+ image = iio->image();
+
+ QMap<QRgb, int> colorMap;
+
+ int w = image.width(), h = image.height(), ncolors = 0;
+ int x, y;
+
+ // build color table
+ for( y=0; y<h; y++ ) {
+ QRgb * yp = (QRgb *)image.scanLine( y );
+ for( x=0; x<w; x++ ) {
+ QRgb color = *(yp + x);
+ if ( !colorMap.contains(color) )
+ colorMap.insert( color, ncolors++ );
+ }
+ }
+
+ // number of 64-bit characters per pixel needed to encode all colors
+ int cpp = 1;
+ for ( int k = 64; ncolors > k; k *= 64 ) {
+ ++cpp;
+ // limit to 4 characters per pixel
+ // 64^4 colors is enough for a 4096x4096 image
+ if ( cpp > 4)
+ break;
+ }
+
+ QString line;
+
+ // write header
+ QTextStream s( iio->ioDevice() );
+ s << "/* XPM */" << endl
+ << "static char *" << fbname(iio->fileName()) << "[]={" << endl
+ << "\"" << w << " " << h << " " << ncolors << " " << cpp << "\"";
+
+ // write palette
+ QMap<QRgb, int>::Iterator c = colorMap.begin();
+ while ( c != colorMap.end() ) {
+ QRgb color = c.key();
+ if ( image.hasAlphaBuffer() && color == (color & RGB_MASK) )
+ line.sprintf( "\"%s c None\"",
+ xpm_color_name(cpp, *c) );
+ else
+ line.sprintf( "\"%s c #%02x%02x%02x\"",
+ xpm_color_name(cpp, *c),
+ qRed(color),
+ qGreen(color),
+ qBlue(color) );
+ ++c;
+ s << "," << endl << line;
+ }
+
+ // write pixels, limit to 4 characters per pixel
+ line.truncate( cpp*w );
+ for( y=0; y<h; y++ ) {
+ QRgb * yp = (QRgb *) image.scanLine( y );
+ int cc = 0;
+ for( x=0; x<w; x++ ) {
+ int color = (int)(*(yp + x));
+ QCString chars = xpm_color_name( cpp, colorMap[color] );
+ line[cc++] = chars[0];
+ if ( cpp > 1 ) {
+ line[cc++] = chars[1];
+ if ( cpp > 2 ) {
+ line[cc++] = chars[2];
+ if ( cpp > 3 ) {
+ line[cc++] = chars[3];
+ }
+ }
+ }
+ }
+ s << "," << endl << "\"" << line << "\"";
+ }
+ s << "};" << endl;
+
+ iio->setStatus( 0 );
+}
+
+// Qt code end ---------------------------
+
+XPM::XPM()
+{
+ QImageIO::inputFormats(); // trigger registration of Qt's handlers
+ QImageIO::defineIOHandler( "XPM", "/\\*.XPM.\\*/", "T",
+ read_xpm_image, write_xpm_image );
+}
+
+} // namespace
diff --git a/src/gvcore/xpm.h b/src/gvcore/xpm.h
new file mode 100644
index 0000000..f506ea6
--- /dev/null
+++ b/src/gvcore/xpm.h
@@ -0,0 +1,54 @@
+/* This file is based on qt-3.3.2/src/qimage.cpp . Original
+ * copyright follows.
+ */
+/****************************************************************************
+**
+**
+** Implementation of QImage and QImageIO classes
+**
+** Created : 950207
+**
+** Copyright (C) 1992-2003 Trolltech AS. All rights reserved.
+**
+** This file is part of the kernel module of the Qt GUI Toolkit.
+**
+** This file may be distributed under the terms of the Q Public License
+** as defined by Trolltech AS of Norway and appearing in the file
+** LICENSE.QPL included in the packaging of this file.
+**
+** This file may be distributed and/or modified under the terms of the
+** GNU General Public License version 2 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file.
+**
+** Licensees holding valid Qt Enterprise Edition or Qt Professional Edition
+** licenses may use this file in accordance with the Qt Commercial License
+** Agreement provided with the Software.
+**
+** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
+** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+**
+** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for
+** information about Qt Commercial License Agreements.
+** See http://www.trolltech.com/qpl/ for QPL licensing information.
+** See http://www.trolltech.com/gpl/ for GPL licensing information.
+**
+** Contact info@trolltech.com if any conditions of this licensing are
+** not clear to you.
+**
+**********************************************************************/
+
+#ifndef gvxpm_h
+#define gvxpm_h
+
+namespace Gwenview {
+
+class XPM {
+public:
+ XPM();
+};
+
+// -----------------------------------------------------------------------------
+} // namespace
+
+#endif // gvxpm_h