diff options
Diffstat (limited to 'src')
272 files changed, 39557 insertions, 0 deletions
diff --git a/src/CREDITS b/src/CREDITS new file mode 100644 index 0000000..4dc9531 --- /dev/null +++ b/src/CREDITS @@ -0,0 +1,88 @@ +CURRENT PACKAGERS +----------------- + +ASP Linux: + Andrew "nording" Chernyak <[email protected]> + +Debian: + Christopher Martin <[email protected]> + +Fedora Core: + Domenico Cotroneo <[email protected]> + +Mandriva: + Angelo Naselli <[email protected]> + +Red Hat: + Bobby Rockers <[email protected]> + +SuSE: + Sarah "cokie" Jaromij <[email protected]> + +Slackware: + Mark Tucker <[email protected]> + + +PAST PACKAGERS +-------------- + +These fine people contributed binary packages: + +Red Hat: + Ian Koenig <[email protected]> + +SuSE 7.2: + Dario Abatianni <[email protected]> + +SuSE 9.0: + Marco Puszina <[email protected]> + +Debian: + Michael Spanier <[email protected]> + +Slackware: + Luca Cavalli <[email protected]> + + +TRANSLATORS +----------- + +Back in the days when Gwenview was not in KDE Extra Gear, the following nice +people contributed translations. + +Dutch: + Rindert Vonk <[email protected]> + +German: + Robert Gogolok <[email protected]> + +Hungarian: + Arpad Biro <[email protected]> + +Italian: + Marco De Simone <[email protected]> + +Japanese: + Ryota Simamoto <[email protected]> + +Korean: + Park Yu-Chan <[email protected]> + +Norwegian: + Rune Nordvik <[email protected]> + +Polish: + Szymon Janc <[email protected]> + +Spanish: + Daniel R. Ome <[email protected]> + +Swedish: + Karolina Lindqvist <[email protected]> + + +Now translations are done by the KDE translation teams, so I don't really know +how are the translators. I guess some of the people I quoted here are still +working on translating Gwenview. + +Thanks to all of you. diff --git a/src/DESIGN b/src/DESIGN new file mode 100644 index 0000000..fcb0e3f --- /dev/null +++ b/src/DESIGN @@ -0,0 +1,142 @@ +# Folders + +Here is a description of each folder: + +* app/ + Code specific to the standalone application (== not the KParts) + +* desktopfiles/ + Desktop files, to start the standalone application, from the menu and from + within Konqueror. + +* doc/ + Man page. This is not the HTML doc. The HTML doc is in + extragear/graphics/doc/gwenview. + +* gvcore/ + Code shared between the application and the KParts. + +* gvdirpart/ + The Gwenview KPart used to browse folders. + +* gvimagepart/ + The Gwenview KPart to display images. + +* imageutils/ + Various image code, like rotation (lossless for JPEG, classic for others), + scaling... + +* pics/ + Icons. + +* spec/ + .spec files to build RPM (probably outdated) + +* tools/ + Desktop files describing external tools. + +* tsthread/ + A thread library written by Lubos Lunak. + +* updates/ + Update scripts to migrate configuration files within versions. + + +# Code + +In this part we describe the main classes, not getting too deeply into details +so that this document doesn't get obsoleted too fast :-) + + +## gvcore +### Document classes +document.h +document*impl.h + +This is the heart of Gwenview. The Document class represents a media file. It +uses the "State" design pattern to adapt its behavior depending on its state. +The various states are implemented in the Document*Impl classes. +It knows how to load, save, edit a comment, rotate and mirror a document. + + +### Image loader and cache +imageloader.h +cache.h + +Image loading is done by the ImageLoader classes, which work together with the +cache to avoid loading an image multiple times. +A loader may be shared, for example if an image is selected and a thumbnail of +it is being generated, the same loader will be used by both. + + +### File views +fileviewcontroller.h +filedetailview*.h +filethumbnailview*.h +fileviewbase.h + +The main class is FileViewController, which contains the KDirLister responsible +for listing folder content. It also contains a QWidgetStack which contains +both the FileDetailView and the FileThumbnailView. +FileViewBase is an abstract class, which inherits from KFileView and adds the +concept of a "shown item": the item currently viewed in the image view. + + +### Image view +imageview.h +imageviewtools.h + +The ImageView class inherits from QScrollView to show the current image. It +implements zooming and panning. +The ImageViewTools implements the different behaviors: for example when you +use the mouse wheel you will scroll (or browse): This is implemented by the +ImageView::ScrollTool class. If you press Ctrl and use the mouse wheel, you +will zoom in the image: This is implemented by the ImageView::ZoomTool. + + +### ImageViewController + +ImageViewController contains a stack which contains an ImageView and a KPart. +The ImageView class is used to show raster images. For SVG images and movies, +the ImageViewController loads the corresponding KPart. + + +### File operations +fileoperation.h +fileopobject.h + +These classes implements file operations: from the user interface to the actual +operation. + + +## app + +### Main window +mainwindow.h + +The MainWindow class is responsible for gluing together all components of the +application. It is made of a QWidgetStack which contains two pages: one for +the docked windows (==Browse mode) and another for the ImageViewController +(==View and Fullscreen modes). +There is only one instance of ImageViewController. It is 'reparent'ed when the +QWidgetStack switch between modes. + + +### Folder view +dirviewcontroller.h +vtabwidget.h +bookmarkviewcontroller.h +treeview.h + +These classes implement the folder view. The main class is DirViewController. +It contains a VTabWidget, which contains an instance of the +BoookmarkViewController and TreeView classes. + + +## gvdirpart + +This KPart is made of a splitter, a FileThumbnailView and an ImageView. + +## gvimagepart + +This KPart is simply an ImageView. diff --git a/src/HACKING b/src/HACKING new file mode 100644 index 0000000..599dd3b --- /dev/null +++ b/src/HACKING @@ -0,0 +1,95 @@ +This file describes the coding conventions used in Gwenview. + + +Naming +------ + +Classes should be named like this: MyClass. The class MyClass should be defined +in the myclass.h file and implemented in myclass.cpp. + +Variables and functions should be named like this: myVariable. Static and +member variables are identified by a prefix (s or m), without underscore. + +int sStaticVariable. + +class MyClass { + int mMemberVariable; + + void doSomething() { + int localVariable; + } +}; + + +Enum items and consts are spelled like this: + +const int MY_CONST_VALUE=120; +enum AnEnum { ITEM1, ITEM2, ITEM3 }; + + +Namespace +--------- + +All code should be enclosed in the "Gwenview" namespace. Make sure the closing +curly bracket of the namespace looks like this : + +} // namespace + +This avoids wondering why the curly bracket is here. + + +Code layout +----------- + +Use tabs, not spaces. + +The opening brace follows the function/class/for/while. Insert a space after +commas and after for/while keywords. + +void myFunction() { + int v1; + int v2=12; + for (v1=0; v1<10; ++v1) { + doSomething(v1, v2); + } +} + +class MyClass { +}; + + +If the if content is only one line long, you can place it on the same line and +omit the curly braces, but DO NOT omit them if you place the content after the +if. + +// Ok +if (!data) { + return; +} + +// Ok too +if (!data) return; + +// Bad +if (!data) + return; + + +Include files +------------- + +Group include files in the Qt, KDE or local groups and sort them +alphabetically. When writing the implementation file of a class which is a +Q_OBJECT, make sure you include the .moc file, not the .h. + +// Qt +#include <qobject.h> +#include <qwidget.h> + +// KDE +#include <kiconview.h> +#include <klistview.h> + +// Local +#include "aclass.h" +#include "myclass.moc" diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..81a3b05 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,8 @@ +SUBDIRS = tsthread imageutils gvcore pics desktopfiles doc tools updates gvimagepart gvdirpart app + +EXTRA_DIST = AUTHORS ChangeLog COPYING INSTALL NEWS README TODO CREDITS DESIGN + +messages: rc.cpp + $(EXTRACTRC) `find . -name "*.ui"` >> rc.cpp + $(EXTRACTRC) `find . -name "*.rc"` >> rc.cpp + $(XGETTEXT) `find . -name "*.cpp"` `find . -name "*.h"` -o $(podir)/gwenview.pot diff --git a/src/app/.vimrc b/src/app/.vimrc new file mode 100644 index 0000000..a37475c --- /dev/null +++ b/src/app/.vimrc @@ -0,0 +1,4 @@ +set tabstop=4 +set shiftwidth=4 +set noexpandtab +set makeprg=unsermake diff --git a/src/app/Makefile.am b/src/app/Makefile.am new file mode 100644 index 0000000..037933a --- /dev/null +++ b/src/app/Makefile.am @@ -0,0 +1,56 @@ +bin_PROGRAMS= +lib_LTLIBRARIES= + +kdeinit_LTLIBRARIES = gwenview.la + +# -D_LARGEFILE64_SOURCE is necessary on Debian Woody +AM_CPPFLAGS = -D_LARGEFILE64_SOURCE + +noinst_LTLIBRARIES = libgwenshared.la + +libgwenshared_la_SOURCES = vtabwidget.cpp + +gwenview_COMPILE_FIRST = ../gvcore/miscconfig.h ../gvcore/slideshowconfig.h ../gvcore/fileoperationconfig.h ../gvcore/fullscreenconfig.h ../gvcore/imageviewconfig.h ../gvcore/fileviewconfig.h + +gwenview_la_SOURCES = \ + bookmarkowner.cpp \ + bookmarkviewcontroller.cpp \ + bookmarkdialogbase.ui \ + bookmarkdialog.cpp \ + configfileoperationspage.ui \ + configfullscreenpage.ui \ + configimagelistpage.ui \ + configimageviewpage.ui \ + configmiscpage.ui \ + configslideshowpage.ui \ + kipiinterface.cpp \ + mainwindow.cpp \ + metaedit.cpp \ + treeview.cpp \ + dirviewcontroller.cpp \ + configdialog.cpp \ + history.cpp \ + main.cpp + +gwenview_la_LIBADD = libgwenshared.la ../gvcore/libgwenviewcore.la $(GV_LIB_KIPI) $(LIB_KUTILS) + +# the library search path. +gwenview_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) + + +# set the include path for X, qt and KDE +INCLUDES = -I$(srcdir)/.. $(all_includes) + +METASOURCES = AUTO + +rcdir = $(kde_datadir)/gwenview +rc_DATA = gwenviewui.rc + + +check_PROGRAMS = testvtabwidget +testvtabwidget_SOURCES = testvtabwidget.cpp +testvtabwidget_LDADD = \ + libgwenshared.la \ + $(LIB_KUTILS) $(LIB_KFILE) $(LIB_KDEUI) $(LIB_KDECORE) $(LIB_KDEPRINT) $(LIB_QT) \ + $(LIBJPEG) +testvtabwidget_LDFLAGS = $(all_libraries) diff --git a/src/app/bookmarkdialog.cpp b/src/app/bookmarkdialog.cpp new file mode 100644 index 0000000..0572fc2 --- /dev/null +++ b/src/app/bookmarkdialog.cpp @@ -0,0 +1,115 @@ +// 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 "bookmarkdialog.moc" +// Qt +#include <qlabel.h> + +// KDE +#include <kfile.h> +#include <kicondialog.h> +#include <klineedit.h> +#include <klocale.h> +#include <kurlrequester.h> + +// Local +#include "bookmarkdialogbase.h" +namespace Gwenview { + +class BookmarkDialogPrivate { +public: + BookmarkDialogBase* mContent; + BookmarkDialog::Mode mMode; +}; + +BookmarkDialog::BookmarkDialog(QWidget* parent, BookmarkDialog::Mode mode) +: KDialogBase(parent,"folderconfig",true,QString::null,Ok|Cancel) +{ + d=new BookmarkDialogPrivate; + d->mContent=new BookmarkDialogBase(this); + d->mMode=mode; + + setMainWidget(d->mContent); + setCaption(d->mContent->caption()); + d->mContent->mUrl->setMode(KFile::Directory); + d->mContent->mIcon->setIcon("folder"); + + connect(d->mContent->mTitle,SIGNAL(textChanged(const QString&)), + this, SLOT(updateOk())); + connect(d->mContent->mIcon,SIGNAL(iconChanged(QString)), + this, SLOT(updateOk())); + + if (mode==BOOKMARK_GROUP) { + d->mContent->mUrlLabel->hide(); + d->mContent->mUrl->hide(); + } else { + connect(d->mContent->mUrl,SIGNAL(textChanged(const QString&)), + this, SLOT(updateOk())); + } + + switch (mode) { + case BOOKMARK_GROUP: + setCaption( i18n("Add/Edit Bookmark Folder") ); + break; + case BOOKMARK: + setCaption( i18n("Add/Edit Bookmark") ); + break; + } + + updateOk(); +} + +BookmarkDialog::~BookmarkDialog() { + delete d; +} + +void BookmarkDialog::updateOk() { + bool enabled= + !d->mContent->mTitle->text().isEmpty() + && (d->mMode==BOOKMARK_GROUP || !d->mContent->mUrl->url().isEmpty()); + + enableButton(Ok, enabled); +} + +void BookmarkDialog::setIcon(const QString& icon) { + d->mContent->mIcon->setIcon(icon); +} + +QString BookmarkDialog::icon() const { + return d->mContent->mIcon->icon(); +} + +void BookmarkDialog::setTitle(const QString& title) { + d->mContent->mTitle->setText(title); +} + +QString BookmarkDialog::title() const { + return d->mContent->mTitle->text(); +} + +void BookmarkDialog::setURL(const QString& url) { + d->mContent->mUrl->setURL(url); +} + +QString BookmarkDialog::url() const { + return d->mContent->mUrl->url(); +} + +} // namespace diff --git a/src/app/bookmarkdialog.h b/src/app/bookmarkdialog.h new file mode 100644 index 0000000..46f4aaf --- /dev/null +++ b/src/app/bookmarkdialog.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 BOOKMARKDIALOG_H +#define BOOKMARKDIALOG_H + +// KDE includes +#include <kdialogbase.h> +namespace Gwenview { + +class BookmarkDialogPrivate; + +class BookmarkDialog : public KDialogBase { +Q_OBJECT +public: + enum Mode { BOOKMARK_GROUP, BOOKMARK }; + BookmarkDialog(QWidget* parent, Mode mode); + ~BookmarkDialog(); + + + void setIcon(const QString&); + QString icon() const; + void setTitle(const QString&); + QString title() const; + void setURL(const QString&); + QString url() const; + +protected slots: + void updateOk(); + +private: + BookmarkDialogPrivate* d; +}; + +} // namespace +#endif + diff --git a/src/app/bookmarkdialogbase.ui b/src/app/bookmarkdialogbase.ui new file mode 100644 index 0000000..4768e4f --- /dev/null +++ b/src/app/bookmarkdialogbase.ui @@ -0,0 +1,109 @@ +<!DOCTYPE UI><UI version="3.2" stdsetdef="1"> +<class>BookmarkDialogBase</class> +<widget class="QWidget"> + <property name="name"> + <cstring>BookmarkDialogBase</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>500</width> + <height>90</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>1</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>400</width> + <height>0</height> + </size> + </property> + <property name="caption"> + <string>Add New Branch</string> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <widget class="QLabel" row="2" column="0"> + <property name="name"> + <cstring>mUrlLabel</cstring> + </property> + <property name="text"> + <string>URL:</string> + </property> + </widget> + <widget class="KLineEdit" row="1" column="1" rowspan="1" colspan="2"> + <property name="name"> + <cstring>mTitle</cstring> + </property> + </widget> + <widget class="KURLRequester" row="2" column="1" rowspan="1" colspan="2"> + <property name="name"> + <cstring>mUrl</cstring> + </property> + </widget> + <widget class="QLabel" row="1" column="0"> + <property name="name"> + <cstring>textLabel2</cstring> + </property> + <property name="text"> + <string>Title:</string> + </property> + </widget> + <widget class="QLabel" row="0" column="0"> + <property name="name"> + <cstring>textLabel2_2</cstring> + </property> + <property name="text"> + <string>Icon:</string> + </property> + </widget> + <widget class="KIconButton" row="0" column="1"> + <property name="name"> + <cstring>mIcon</cstring> + </property> + <property name="text"> + <string></string> + </property> + </widget> + <spacer row="0" column="2"> + <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>383</width> + <height>16</height> + </size> + </property> + </spacer> + </grid> +</widget> +<customwidgets> +</customwidgets> +<layoutdefaults spacing="6" margin="11"/> +<includehints> + <includehint>klineedit.h</includehint> + <includehint>kurlrequester.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>kpushbutton.h</includehint> +</includehints> +</UI> diff --git a/src/app/bookmarkowner.cpp b/src/app/bookmarkowner.cpp new file mode 100644 index 0000000..ac484e7 --- /dev/null +++ b/src/app/bookmarkowner.cpp @@ -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. + +*/ + +// KDE +#include <kbookmarkmenu.h> + +// Local +#include "bookmarkowner.moc" +namespace Gwenview { + + +BookmarkOwner::BookmarkOwner(QWidget* parent) +: QObject(parent) +{} + + +void BookmarkOwner::openBookmarkURL(const QString& strURL) +{ + KURL url(strURL); + emit openURL(url); +} + + +QString BookmarkOwner::currentURL() const +{ + return mURL.prettyURL(); +} + + +void BookmarkOwner::setURL(const KURL& url) +{ + mURL=url; +} + +} // namespace diff --git a/src/app/bookmarkowner.h b/src/app/bookmarkowner.h new file mode 100644 index 0000000..85dab1b --- /dev/null +++ b/src/app/bookmarkowner.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 BOOKMARKOWNER_H +#define BOOKMARKOWNER_H + + +// KDE includes +#include <kbookmarkmanager.h> +#include <kurl.h> +namespace Gwenview { + + +class BookmarkOwner : public QObject, public KBookmarkOwner { +Q_OBJECT +public: + BookmarkOwner(QWidget* parent); + + // KBookmarkOwner interface + void openBookmarkURL(const QString&); + QString currentURL() const; + +public slots: + void setURL(const KURL&); + +signals: + void openURL(const KURL&); + +private: + KURL mURL; +}; + + +} // namespace +#endif + diff --git a/src/app/bookmarkviewcontroller.cpp b/src/app/bookmarkviewcontroller.cpp new file mode 100644 index 0000000..7fb4daa --- /dev/null +++ b/src/app/bookmarkviewcontroller.cpp @@ -0,0 +1,415 @@ +// 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 "bookmarkviewcontroller.moc" + +#include <memory> + +// Qt +#include <qcursor.h> +#include <qheader.h> +#include <qpopupmenu.h> +#include <qtooltip.h> +#include <qvbox.h> + +// KDE +#include <kaction.h> +#include <kactioncollection.h> +#include <kbookmarkmanager.h> +#include <kdebug.h> +#include <kdeversion.h> +#include <kiconloader.h> +#include <klistview.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <kmimetype.h> +#include <ktoolbar.h> +#include <kurl.h> +#include <kurldrag.h> + +// Local +#include "bookmarkdialog.h" +#include "../gvcore/fileoperation.h" + +namespace Gwenview { + +// URLDropListView +URLDropListView::URLDropListView(QWidget* parent) +: KListView(parent) { + setAcceptDrops(true); +} + + +void URLDropListView::contentsDragMoveEvent(QDragMoveEvent* event) { + if (KURLDrag::canDecode(event)) { + event->accept(); + } else { + event->ignore(); + } +} + + + + +struct BookmarkItem : public KListViewItem { + template <class ItemParent> + BookmarkItem(ItemParent* parent, const KBookmark& bookmark) + : KListViewItem(parent) + , mBookmark(bookmark) + { + refresh(); + } + + void refresh() { + setText(0, mBookmark.text() ); + setPixmap(0, SmallIcon(mBookmark.icon()) ); + } + + KBookmark mBookmark; +}; + + +class BookmarkToolTip : public QToolTip { +public: + BookmarkToolTip(KListView* lv) + : QToolTip(lv->viewport()) + , mListView(lv) {} + + void maybeTip(const QPoint& pos) { + BookmarkItem *item = static_cast<BookmarkItem*>( mListView->itemAt(pos) ); + if ( !item) return; + if (item->mBookmark.isGroup()) return; + + QRect rect=mListView->itemRect(item); + tip(rect, item->mBookmark.url().prettyURL()); + }; + + KListView* mListView; +}; + + +struct BookmarkViewController::Private { + QVBox* mBox; + KListView* mListView; + KBookmarkManager* mManager; + KURL mCurrentURL; + std::auto_ptr<BookmarkToolTip> mToolTip; + KActionCollection* mActionCollection; + KURL mDroppedURL; + + template <class ItemParent> + void addGroup(ItemParent* itemParent, const KBookmarkGroup& group) { + KBookmark bookmark=group.first(); + BookmarkItem* previousItem=0; + BookmarkItem* item=0; + for (;!bookmark.isNull(); bookmark=group.next(bookmark) ) { + if (bookmark.isSeparator()) continue; + + // Create the item and make sure it's placed at the end + previousItem=item; + item=new BookmarkItem(itemParent, bookmark); + if (previousItem) { + item->moveItem(previousItem); + } + + if (bookmark.isGroup()) { + addGroup(item, static_cast<const KBookmarkGroup&>(bookmark) ); + } + } + } + + KBookmarkGroup findBestParentGroup() { + KBookmarkGroup parentGroup; + BookmarkItem* item=static_cast<BookmarkItem*>( mListView->currentItem() ); + if (item) { + if (item->mBookmark.isGroup()) { + parentGroup=item->mBookmark.toGroup(); + } else { + parentGroup=item->mBookmark.parentGroup(); + } + } else { + parentGroup=mManager->root(); + } + + return parentGroup; + } + + void bookmarkURL(const KURL& url) { + BookmarkDialog dialog(mListView, BookmarkDialog::BOOKMARK); + dialog.setTitle(url.fileName()); + dialog.setURL(url.prettyURL()); + dialog.setIcon(KMimeType::iconForURL(url)); + if (dialog.exec()==QDialog::Rejected) return; + + KBookmarkGroup parentGroup=findBestParentGroup(); + parentGroup.addBookmark(mManager, dialog.title(), dialog.url(), dialog.icon()); + mManager->emitChanged(parentGroup); + } +}; + + +void URLDropListView::contentsDropEvent(QDropEvent* event) { + KURL::List urls; + if (!KURLDrag::decode(event, urls)) return; + emit urlDropped(event, urls); +} + + +BookmarkViewController::BookmarkViewController(QWidget* parent) +: QObject(parent) +{ + d=new Private; + d->mManager=0; + + d->mBox=new QVBox(parent); + + // Init listview + d->mListView=new URLDropListView(d->mBox); + d->mToolTip.reset(new BookmarkToolTip(d->mListView) ); + d->mActionCollection=new KActionCollection(d->mListView); + + d->mListView->header()->hide(); + d->mListView->setRootIsDecorated(true); + d->mListView->addColumn(QString::null); + d->mListView->setSorting(-1); + d->mListView->setShowToolTips(false); + d->mListView->setFullWidth(true); + + connect(d->mListView, SIGNAL(clicked(QListViewItem*)), + this, SLOT(slotOpenBookmark(QListViewItem*)) ); + connect(d->mListView, SIGNAL(returnPressed(QListViewItem*)), + this, SLOT(slotOpenBookmark(QListViewItem*)) ); + connect(d->mListView, SIGNAL(contextMenuRequested(QListViewItem*, const QPoint&, int)), + this, SLOT(slotContextMenu(QListViewItem*)) ); + connect(d->mListView, SIGNAL(urlDropped(QDropEvent*, const KURL::List&)), + this, SLOT(slotURLDropped(QDropEvent*, const KURL::List&)) ); + + // Init toolbar + KToolBar* toolbar=new KToolBar(d->mBox, "", true); + KAction* action; + toolbar->setIconText(KToolBar::IconTextRight); + action=new KAction(i18n("Add a bookmark (keep it short)", "Add"), "bookmark_add", 0, + this, SLOT(bookmarkCurrentURL()), d->mActionCollection); + action->plug(toolbar); + action=new KAction(i18n("Remove a bookmark (keep it short)", "Remove"), "editdelete", 0, + this, SLOT(deleteCurrentBookmark()), d->mActionCollection); + action->plug(toolbar); +} + + +BookmarkViewController::~BookmarkViewController() { + delete d; +} + + +void BookmarkViewController::init(KBookmarkManager* manager) { + // This method should not be called twice + Q_ASSERT(!d->mManager); + + d->mManager=manager; + // For now, we ignore the caller parameter and just refresh the full list on update + connect(d->mManager, SIGNAL(changed(const QString&, const QString&)), + this, SLOT(fill()) ); + fill(); +} + + +void BookmarkViewController::setURL(const KURL& url) { + d->mCurrentURL=url; +} + + +QWidget* BookmarkViewController::widget() const { + return d->mBox; +} + + +void BookmarkViewController::fill() { + d->mListView->clear(); + KBookmarkGroup root=d->mManager->root(); + d->addGroup(d->mListView, root); +} + + +void BookmarkViewController::slotURLDropped(QDropEvent* event, const KURL::List& urls) { + // Get a pointer to the drop item + QPoint point(0,event->pos().y()); + KListView* lst=d->mListView; + BookmarkItem* item=static_cast<BookmarkItem*>( lst->itemAt(lst->contentsToViewport(point)) ); + + QPopupMenu menu(lst); + int addBookmarkID=menu.insertItem( SmallIcon("bookmark_add"), i18n("&Add Bookmark"), + this, SLOT(slotBookmarkDroppedURL()) ); + if (urls.count()==1) { + d->mDroppedURL=*urls.begin(); + } else { + menu.setItemEnabled(addBookmarkID, false); + } + + if (item) { + menu.insertSeparator(); + KURL dest=item->mBookmark.url(); + FileOperation::fillDropURLMenu(&menu, urls, dest); + } + + menu.insertSeparator(); + menu.insertItem( SmallIcon("cancel"), i18n("Cancel") ); + menu.exec(QCursor::pos()); +} + + +void BookmarkViewController::slotBookmarkDroppedURL() { + d->bookmarkURL(d->mDroppedURL); +} + + +void BookmarkViewController::slotOpenBookmark(QListViewItem* item_) { + if (!item_) return; + BookmarkItem* item=static_cast<BookmarkItem*>(item_); + const KURL& url=item->mBookmark.url(); + if (!url.isValid()) return; + emit openURL(url); +} + + +void BookmarkViewController::slotContextMenu(QListViewItem* item_) { + BookmarkItem* item=static_cast<BookmarkItem*>(item_); + QPopupMenu menu(d->mListView); + menu.insertItem(SmallIcon("bookmark_add"), i18n("Add Bookmark..."), + this, SLOT(bookmarkCurrentURL())); + menu.insertItem(SmallIcon("bookmark_folder"), i18n("Add Bookmark Folder..."), + this, SLOT(addBookmarkGroup())); + + if (item) { + menu.insertSeparator(); + menu.insertItem(SmallIcon("edit"), i18n("Edit..."), + this, SLOT(editCurrentBookmark())); + menu.insertItem(SmallIcon("editdelete"), i18n("Delete"), + this, SLOT(deleteCurrentBookmark())); + } + menu.exec(QCursor::pos()); +} + + +void BookmarkViewController::bookmarkCurrentURL() { + d->bookmarkURL(d->mCurrentURL); +} + + +void BookmarkViewController::addBookmarkGroup() { + BookmarkDialog dialog(d->mListView, BookmarkDialog::BOOKMARK_GROUP); + if (dialog.exec()==QDialog::Rejected) return; + + KBookmarkGroup parentGroup=d->findBestParentGroup(); + KBookmarkGroup newGroup=parentGroup.createNewFolder(d->mManager, dialog.title()); + newGroup.internalElement().setAttribute("icon", dialog.icon()); + d->mManager->emitChanged(parentGroup); + QListViewItem* item=d->mListView->currentItem(); + if (item) { + item->setOpen(true); + } +} + + +void BookmarkViewController::editCurrentBookmark() { + BookmarkItem* item=static_cast<BookmarkItem*>( d->mListView->currentItem() ); + Q_ASSERT(item); + if (!item) return; + KBookmark bookmark=item->mBookmark; + bool isGroup=bookmark.isGroup(); + + BookmarkDialog dialog(d->mListView, + isGroup ? BookmarkDialog::BOOKMARK_GROUP : BookmarkDialog::BOOKMARK); + + dialog.setIcon(bookmark.icon()); + dialog.setTitle(bookmark.text()); + if (!isGroup) { + dialog.setURL(bookmark.url().prettyURL()); + } + if (dialog.exec()==QDialog::Rejected) return; + + QDomElement element=bookmark.internalElement(); + element.setAttribute("icon", dialog.icon()); + if (!isGroup) { + element.setAttribute("href", dialog.url()); + } + + // Find title element (or create it if it does not exist) + QDomElement titleElement; + QDomNode tmp=element.namedItem("title"); + if (tmp.isNull()) { + titleElement=element.ownerDocument().createElement("title"); + element.appendChild(titleElement); + } else { + titleElement=tmp.toElement(); + } + Q_ASSERT(!titleElement.isNull()); + + // Get title element content (or create) + QDomText titleText; + tmp=titleElement.firstChild(); + if (tmp.isNull()) { + titleText=element.ownerDocument().createTextNode(""); + titleElement.appendChild(titleText); + } else { + titleText=tmp.toText(); + } + Q_ASSERT(!titleText.isNull()); + + // Set title (at last!) + titleText.setData(dialog.title()); + + KBookmarkGroup group=bookmark.parentGroup(); + d->mManager->emitChanged(group); +} + + +void BookmarkViewController::deleteCurrentBookmark() { + BookmarkItem* item=static_cast<BookmarkItem*>( d->mListView->currentItem() ); + Q_ASSERT(item); + if (!item) return; + KBookmark bookmark=item->mBookmark; + + QString msg; + QString title; + if (bookmark.isGroup()) { + msg=i18n("Are you sure you want to delete the bookmark folder <b>%1</b>?<br>This will delete the folder and all the bookmarks in it.") + .arg(bookmark.text()); + title=i18n("Delete Bookmark &Folder"); + } else { + msg=i18n("Are you sure you want to delete the bookmark <b>%1</b>?") + .arg(bookmark.text()); + title=i18n("Delete &Bookmark"); + } + + int response=KMessageBox::warningContinueCancel(d->mListView, + "<qt>" + msg + "</qt>", title, + KGuiItem(title, "editdelete") + ); + if (response==KMessageBox::Cancel) return; + + KBookmarkGroup group=bookmark.parentGroup(); + group.deleteBookmark(bookmark); + d->mManager->emitChanged(group); +} + + +} // namespace diff --git a/src/app/bookmarkviewcontroller.h b/src/app/bookmarkviewcontroller.h new file mode 100644 index 0000000..bdc8050 --- /dev/null +++ b/src/app/bookmarkviewcontroller.h @@ -0,0 +1,93 @@ +// 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 BOOKMARKVIEWCONTROLLER_H +#define BOOKMARKVIEWCONTROLLER_H + +// Qt +#include <qobject.h> + +// KDE +#include <klistview.h> +#include <kurl.h> + +class QDragMoveEvent; +class QDropEvent; +class QListViewItem; +class QPoint; +class KBookmarkManager; +class KURL; + +namespace Gwenview { + + +/** + * A listview on which the user can drop urls + */ +class URLDropListView : public KListView { +Q_OBJECT +public: + URLDropListView(QWidget* parent); + +signals: + void urlDropped(QDropEvent*, const KURL::List&); + +protected: + virtual void contentsDragMoveEvent(QDragMoveEvent* event); + virtual void contentsDropEvent(QDropEvent* event); +}; + + +class BookmarkViewController : public QObject { +Q_OBJECT +public: + BookmarkViewController(QWidget*); + ~BookmarkViewController(); + + void init(KBookmarkManager*); + + QWidget* widget() const; + +signals: + void openURL(const KURL&); + +public slots: + void setURL(const KURL&); + +private slots: + void slotOpenBookmark(QListViewItem*); + void fill(); + void slotContextMenu(QListViewItem*); + void slotURLDropped(QDropEvent*, const KURL::List&); + void slotBookmarkDroppedURL(); + void bookmarkCurrentURL(); + void addBookmarkGroup(); + void editCurrentBookmark(); + void deleteCurrentBookmark(); + +private: + struct Private; + Private* d; +}; + + +} // namespace +#endif + diff --git a/src/app/configdialog.cpp b/src/app/configdialog.cpp new file mode 100644 index 0000000..571b6d6 --- /dev/null +++ b/src/app/configdialog.cpp @@ -0,0 +1,306 @@ +// 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. + +*/ +// Qt +#include <qbuttongroup.h> +#include <qcheckbox.h> +#include <qlayout.h> +#include <qlineedit.h> +#include <qmap.h> +#include <qradiobutton.h> +#include <qspinbox.h> +#include <qstylesheet.h> +#include <qtextedit.h> + +// KDE +#include <kcolorbutton.h> +#include <kconfigdialogmanager.h> +#include <kdeversion.h> +#include <kdirsize.h> +#include <kfiledialog.h> +#include <kiconloader.h> +#include <klocale.h> +#include <kio/netaccess.h> +#include <kmessagebox.h> +#include <kurllabel.h> +#include <kurlrequester.h> + +#include <config.h> +// KIPI +#ifdef GV_HAVE_KIPI +#include <libkipi/pluginloader.h> +#endif + +// Local +#include "configfileoperationspage.h" +#include "configfullscreenpage.h" +#include "configimagelistpage.h" +#include "configimageviewpage.h" +#include "configmiscpage.h" +#include "configslideshowpage.h" +#include "gvcore/captionformatter.h" +#include "gvcore/filethumbnailview.h" +// This path is different because it's a generated file, so it's stored in builddir +#include <../gvcore/miscconfig.h> +#include <../gvcore/slideshowconfig.h> +#include <../gvcore/fileoperationconfig.h> +#include <../gvcore/fullscreenconfig.h> +#include <../gvcore/imageviewconfig.h> +#include <../gvcore/fileviewconfig.h> +#include "gvcore/thumbnailloadjob.h" + +#include "configdialog.moc" +namespace Gwenview { + +typedef QValueList<KConfigDialogManager*> ConfigManagerList; + +class ConfigDialogPrivate { +public: + ConfigImageViewPage* mImageViewPage; + ConfigImageListPage* mImageListPage; + ConfigFullScreenPage* mFullScreenPage; + ConfigFileOperationsPage* mFileOperationsPage; + ConfigMiscPage* mMiscPage; + ConfigSlideshowPage* mSlideShowPage; +#ifdef GV_HAVE_KIPI + KIPI::ConfigWidget* mKIPIConfigWidget; +#endif + + ConfigManagerList mManagers; +}; + + +// Two helper functions to create the config pages +template<class T> +void addConfigPage(KDialogBase* dialog, T* content, const QString& header, const QString& name, const char* iconName) { + QFrame* page=dialog->addPage(name, header, BarIcon(iconName, 32)); + content->reparent(page, QPoint(0,0)); + QVBoxLayout* layout=new QVBoxLayout(page, 0, KDialog::spacingHint()); + layout->addWidget(content); + layout->addStretch(); +} + +template<class T> +T* addConfigPage(KDialogBase* dialog, const QString& header, const QString& name, const char* iconName) { + T* content=new T; + addConfigPage(dialog, content, header, name, iconName); + return content; +} + + +ConfigDialog::ConfigDialog(QWidget* parent, KIPI::PluginLoader* pluginLoader) +: KDialogBase( + KDialogBase::IconList, + i18n("Configure"), + KDialogBase::Ok | KDialogBase::Cancel | KDialogBase::Apply, + KDialogBase::Ok, + parent, + "ConfigDialog", + true, + true) +{ + d=new ConfigDialogPrivate; + + // Create dialog pages + d->mImageListPage = addConfigPage<ConfigImageListPage>( + this, i18n("Configure Image List"), i18n("Image List"), "view_icon"); + d->mManagers << new KConfigDialogManager(d->mImageListPage, FileViewConfig::self()); + + d->mImageViewPage = addConfigPage<ConfigImageViewPage>( + this, i18n("Configure Image View"), i18n("Image View"), "looknfeel"); + d->mManagers << new KConfigDialogManager(d->mImageViewPage, ImageViewConfig::self()); + + d->mFullScreenPage = addConfigPage<ConfigFullScreenPage>( + this, i18n("Configure Full Screen Mode"), i18n("Full Screen"), "window_fullscreen"); + d->mManagers << new KConfigDialogManager(d->mFullScreenPage, FullScreenConfig::self()); + + d->mFileOperationsPage = addConfigPage<ConfigFileOperationsPage>( + this, i18n("Configure File Operations"), i18n("File Operations"), "folder"); + d->mManagers << new KConfigDialogManager(d->mFileOperationsPage, FileOperationConfig::self()); + + d->mSlideShowPage = addConfigPage<ConfigSlideshowPage>( + this, i18n("SlideShow"), i18n("SlideShow"), "slideshow_play"); + d->mManagers << new KConfigDialogManager(d->mSlideShowPage, SlideShowConfig::self()); + +#ifdef GV_HAVE_KIPI + Q_ASSERT(pluginLoader); + d->mKIPIConfigWidget = pluginLoader->configWidget(this); + addConfigPage( + this, d->mKIPIConfigWidget, i18n("Configure KIPI Plugins"), i18n("KIPI Plugins"), "kipi"); +#else + // Avoid "unused parameter" warning + pluginLoader=pluginLoader; +#endif + + d->mMiscPage = addConfigPage<ConfigMiscPage>( + this, i18n("Miscellaneous Settings"), i18n("Misc"), "gear"); + d->mManagers << new KConfigDialogManager(d->mMiscPage, MiscConfig::self()); + // Read config, because the modified behavior might have changed + MiscConfig::self()->readConfig(); + + // Image List tab + int details=FileViewConfig::thumbnailDetails(); + d->mImageListPage->mShowFileName->setChecked(details & FileThumbnailView::FILENAME); + d->mImageListPage->mShowFileDate->setChecked(details & FileThumbnailView::FILEDATE); + d->mImageListPage->mShowFileSize->setChecked(details & FileThumbnailView::FILESIZE); + d->mImageListPage->mShowImageSize->setChecked(details & FileThumbnailView::IMAGESIZE); + + connect(d->mImageListPage->mCalculateCacheSize,SIGNAL(clicked()), + this,SLOT(calculateCacheSize())); + connect(d->mImageListPage->mEmptyCache,SIGNAL(clicked()), + this,SLOT(emptyCache())); + + // Image View tab + d->mImageViewPage->mMouseWheelGroup->setButton(ImageViewConfig::mouseWheelScroll()?1:0); + + // Full Screen tab + QTextEdit* edit=d->mFullScreenPage->kcfg_osdFormat; + edit->setMaximumHeight(edit->fontMetrics().height()*3); + connect(edit, SIGNAL(textChanged()), SLOT(updateOSDPreview()) ); + + // File Operations tab + d->mFileOperationsPage->kcfg_destDir->fileDialog()->setMode( + static_cast<KFile::Mode>(KFile::Directory | KFile::ExistingOnly | KFile::LocalOnly)); + + d->mFileOperationsPage->mDeleteGroup->setButton(FileOperationConfig::deleteToTrash()?1:0); + + ConfigManagerList::Iterator it(d->mManagers.begin()); + for (;it!=d->mManagers.end(); ++it) { + (*it)->updateWidgets(); + } + updateOSDPreview(); +} + + + +ConfigDialog::~ConfigDialog() { + delete d; +} + + +void ConfigDialog::slotOk() { + slotApply(); + accept(); +} + + +void ConfigDialog::slotApply() { + bool needSignal=false; + + // Image List tab + int details= + (d->mImageListPage->mShowFileName->isChecked() ? FileThumbnailView::FILENAME : 0) + | (d->mImageListPage->mShowFileDate->isChecked() ? FileThumbnailView::FILEDATE : 0) + | (d->mImageListPage->mShowFileSize->isChecked() ? FileThumbnailView::FILESIZE : 0) + | (d->mImageListPage->mShowImageSize->isChecked() ? FileThumbnailView::IMAGESIZE : 0) + ; + if (details!=FileViewConfig::thumbnailDetails()) { + FileViewConfig::setThumbnailDetails(details); + needSignal=true; + } + + // Image View tab + ImageViewConfig::setMouseWheelScroll( + d->mImageViewPage->mMouseWheelGroup->selected()==d->mImageViewPage->mMouseWheelScroll); + + // File Operations tab + FileOperationConfig::setDeleteToTrash( + d->mFileOperationsPage->mDeleteGroup->selected()==d->mFileOperationsPage->mDeleteToTrash); + + // KIPI tab +#ifdef GV_HAVE_KIPI + d->mKIPIConfigWidget->apply(); +#endif + + ConfigManagerList::Iterator it(d->mManagers.begin()); + for (;it!=d->mManagers.end(); ++it) { + if ((*it)->hasChanged()) { + needSignal=true; + } + (*it)->updateSettings(); + } + if (needSignal) { + emit settingsChanged(); + } +} + + +void ConfigDialog::calculateCacheSize() { + KURL url; + url.setPath(ThumbnailLoadJob::thumbnailBaseDir()); + unsigned long size=KDirSize::dirSize(url); + KMessageBox::information( this,i18n("Cache size is %1").arg(KIO::convertSize(size)) ); +} + + +void ConfigDialog::updateOSDPreview() { + CaptionFormatter formatter; + KURL url; + url.setPath(i18n("/path/to/some/image.jpg")); + formatter.mPath=url.path(); + formatter.mFileName=url.fileName(); + formatter.mComment=i18n("A comment"); + formatter.mImageSize=QSize(1600, 1200); + formatter.mPosition=4; + formatter.mCount=12; + formatter.mAperture="F2.8"; + formatter.mExposureTime="1/60 s"; + formatter.mIso="100"; + formatter.mFocalLength="8.88 mm"; + + QString txt=formatter.format( d->mFullScreenPage->kcfg_osdFormat->text() ); + d->mFullScreenPage->mOSDPreviewLabel->setText(txt); +} + + +void ConfigDialog::emptyCache() { + QString dir=ThumbnailLoadJob::thumbnailBaseDir(); + + if (!QFile::exists(dir)) { + KMessageBox::information( this,i18n("Cache is already empty.") ); + return; + } + + int response=KMessageBox::warningContinueCancel(this, + "<qt>" + i18n("Are you sure you want to empty the thumbnail cache?" + " This will delete the folder <b>%1</b>.").arg(QStyleSheet::escape(dir)) + "</qt>", + QString::null, + KStdGuiItem::del()); + + if (response==KMessageBox::Cancel) return; + + KURL url; + url.setPath(dir); + if (KIO::NetAccess::del(url, topLevelWidget()) ) { + KMessageBox::information( this,i18n("Cache emptied.") ); + } +} + + +void ConfigDialog::onCacheEmptied(KIO::Job* job) { + if ( job->error() ) { + job->showErrorDialog(this); + return; + } + KMessageBox::information( this,i18n("Cache emptied.") ); +} + +} // namespace diff --git a/src/app/configdialog.h b/src/app/configdialog.h new file mode 100644 index 0000000..58a479a --- /dev/null +++ b/src/app/configdialog.h @@ -0,0 +1,65 @@ +// 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 CONFIGDIALOG_H +#define CONFIGDIALOG_H + +// KDE +#include <kdialogbase.h> + +// KIPI +namespace KIPI { +class PluginLoader; +} + + +namespace Gwenview { + +class ConfigDialogPrivate; + +class ConfigDialog : public KDialogBase { +Q_OBJECT +public: + ConfigDialog(QWidget*, KIPI::PluginLoader*); + ~ConfigDialog(); + +signals: + void settingsChanged(); + +protected slots: + void slotOk(); + void slotApply(); + +private slots: + void updateOSDPreview(); + void calculateCacheSize(); + void emptyCache(); + void onCacheEmptied(KIO::Job*); + +private: + ConfigDialogPrivate* d; +}; + + + +} // namespace +#endif + diff --git a/src/app/configfileoperationspage.ui b/src/app/configfileoperationspage.ui new file mode 100644 index 0000000..b3d2f39 --- /dev/null +++ b/src/app/configfileoperationspage.ui @@ -0,0 +1,173 @@ +<!DOCTYPE UI><UI version="3.2" stdsetdef="1"> +<class>ConfigFileOperationsPage</class> +<widget class="QWidget"> + <property name="name"> + <cstring>ConfigFileOperationsPage</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>308</width> + <height>252</height> + </rect> + </property> + <property name="caption"> + <string>Configure File Operations</string> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>textLabel2_2_2</cstring> + </property> + <property name="text"> + <string><b>Moving & Copying Files</b></string> + </property> + </widget> + <widget class="QCheckBox"> + <property name="name"> + <cstring>kcfg_confirmCopy</cstring> + </property> + <property name="text"> + <string>Show copy dialog</string> + </property> + </widget> + <widget class="QCheckBox"> + <property name="name"> + <cstring>kcfg_confirmMove</cstring> + </property> + <property name="text"> + <string>Show move dialog</string> + </property> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>Layout2</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <property name="spacing"> + <number>6</number> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>TextLabel2</cstring> + </property> + <property name="text"> + <string>Default destination folder:</string> + </property> + </widget> + <widget class="KURLRequester"> + <property name="name"> + <cstring>kcfg_destDir</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>3</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </hbox> + </widget> + <spacer> + <property name="name"> + <cstring>spacer10_2_2_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>16</height> + </size> + </property> + </spacer> + <widget class="QLabel"> + <property name="name"> + <cstring>textLabel2_2</cstring> + </property> + <property name="text"> + <string><b>Deleting Files</b></string> + </property> + </widget> + <widget class="QCheckBox"> + <property name="name"> + <cstring>kcfg_confirmDelete</cstring> + </property> + <property name="text"> + <string>Ask for confirmation</string> + </property> + </widget> + <widget class="QButtonGroup"> + <property name="name"> + <cstring>mDeleteGroup</cstring> + </property> + <property name="frameShape"> + <enum>NoFrame</enum> + </property> + <property name="title"> + <string></string> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <property name="spacing"> + <number>6</number> + </property> + <widget class="QRadioButton"> + <property name="name"> + <cstring>mDeleteToTrash</cstring> + </property> + <property name="text"> + <string>Move deleted files to the trash</string> + </property> + <property name="buttonGroupId"> + <number>1</number> + </property> + </widget> + <widget class="QRadioButton"> + <property name="name"> + <cstring>mReallyDelete</cstring> + </property> + <property name="text"> + <string>Really delete files (dangerous)</string> + </property> + <property name="buttonGroupId"> + <number>0</number> + </property> + </widget> + </vbox> + </widget> + </vbox> +</widget> +<customwidgets> +</customwidgets> +<layoutdefaults spacing="6" margin="11"/> +<includehints> + <includehint>kurlrequester.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>kpushbutton.h</includehint> +</includehints> +</UI> diff --git a/src/app/configfullscreenpage.ui b/src/app/configfullscreenpage.ui new file mode 100644 index 0000000..c0eaeba --- /dev/null +++ b/src/app/configfullscreenpage.ui @@ -0,0 +1,144 @@ +<!DOCTYPE UI><UI version="3.2" stdsetdef="1"> +<class>ConfigFullScreenPage</class> +<widget class="QWidget"> + <property name="name"> + <cstring>ConfigFullScreenPage</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>516</width> + <height>411</height> + </rect> + </property> + <property name="caption"> + <string>Configure Full Screen Mode</string> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <widget class="QCheckBox"> + <property name="name"> + <cstring>kcfg_showBusyPtr</cstring> + </property> + <property name="text"> + <string>Show busy mouse pointer when loading an image</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + <spacer> + <property name="name"> + <cstring>spacer10_2_3_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>16</height> + </size> + </property> + </spacer> + <widget class="QLabel"> + <property name="name"> + <cstring>textLabel3_2</cstring> + </property> + <property name="text"> + <string><b>On Screen Display</b></string> + </property> + </widget> + <widget class="QTextEdit"> + <property name="name"> + <cstring>kcfg_osdFormat</cstring> + </property> + <property name="textFormat"> + <enum>PlainText</enum> + </property> + <property name="text"> + <string>%f - %n/%N +%c</string> + </property> + <property name="wordWrap"> + <enum>WidgetWidth</enum> + </property> + <property name="autoFormatting"> + <set>AutoNone</set> + </property> + </widget> + <widget class="QLabel"> + <property name="name"> + <cstring>textLabel2</cstring> + </property> + <property name="text"> + <string>Preview:</string> + </property> + </widget> + <widget class="QLabel"> + <property name="name"> + <cstring>mOSDPreviewLabel</cstring> + </property> + <property name="frameShape"> + <enum>LineEditPanel</enum> + </property> + <property name="frameShadow"> + <enum>Sunken</enum> + </property> + <property name="text"> + <string>file.jpg - 1024x768 +The file comment</string> + </property> + </widget> + <widget class="QLabel"> + <property name="name"> + <cstring>textLabel1</cstring> + </property> + <property name="text"> + <string><qt> +You can use the following keywords to format the On Screen Display: +<ul> +<li>%f: filename</li> +<li>%p: filepath</li> +<li>%c: comment</li> +<li>%r: resolution</li> +<li>%n: current image position</li> +<li>%N: image count</li> +<li>%a: aperture</li> +<li>%t: exposure time</li> +<li>%i: iso</li> +<li>%l: focal length</li> +</ul> +</qt></string> + </property> + </widget> + <spacer> + <property name="name"> + <cstring>spacer10_2_3</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>21</width> + <height>18</height> + </size> + </property> + </spacer> + </vbox> +</widget> +<layoutdefaults spacing="6" margin="11"/> +</UI> diff --git a/src/app/configimagelistpage.ui b/src/app/configimagelistpage.ui new file mode 100644 index 0000000..7b378c7 --- /dev/null +++ b/src/app/configimagelistpage.ui @@ -0,0 +1,294 @@ +<!DOCTYPE UI><UI version="3.2" stdsetdef="1"> +<class>ConfigImageListPage</class> +<widget class="QWidget"> + <property name="name"> + <cstring>ConfigImageListPage</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>352</width> + <height>437</height> + </rect> + </property> + <property name="caption"> + <string>Configure Image List</string> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <widget class="QCheckBox"> + <property name="name"> + <cstring>kcfg_showDirs</cstring> + </property> + <property name="text"> + <string>Show folders and archives</string> + </property> + </widget> + <spacer> + <property name="name"> + <cstring>spacer10_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>16</height> + </size> + </property> + </spacer> + <widget class="QLabel"> + <property name="name"> + <cstring>textLabel1_2_2</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>1</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string><b>Thumbnail View</b></string> + </property> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>Layout6</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <property name="spacing"> + <number>6</number> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>TextLabel1</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>1</hsizetype> + <vsizetype>1</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Margin between thumbnails:</string> + </property> + <property name="buddy" stdset="0"> + <cstring></cstring> + </property> + </widget> + <widget class="QSpinBox"> + <property name="name"> + <cstring>kcfg_thumbnailMarginSize</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="buttonSymbols"> + <enum>UpDownArrows</enum> + </property> + <property name="maxValue"> + <number>50</number> + </property> + <property name="minValue"> + <number>2</number> + </property> + <property name="lineStep"> + <number>1</number> + </property> + </widget> + <spacer> + <property name="name"> + <cstring>Spacer7</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>20</width> + <height>0</height> + </size> + </property> + </spacer> + </hbox> + </widget> + <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>spacer10</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Fixed</enum> + </property> + <property name="sizeHint"> + <size> + <width>21</width> + <height>16</height> + </size> + </property> + </spacer> + <widget class="QLabel"> + <property name="name"> + <cstring>textLabel1_2</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>1</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string><b>Thumbnail Cache</b></string> + </property> + </widget> + <widget class="QCheckBox"> + <property name="name"> + <cstring>kcfg_storeThumbnailsInCache</cstring> + </property> + <property name="text"> + <string>Store thumbnails in cache</string> + </property> + </widget> + <widget class="QCheckBox"> + <property name="name"> + <cstring>kcfg_deleteCacheOnExit</cstring> + </property> + <property name="text"> + <string>Automatically empty thumbnail cache on exit</string> + </property> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout7</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <property name="spacing"> + <number>6</number> + </property> + <widget class="QPushButton"> + <property name="name"> + <cstring>mCalculateCacheSize</cstring> + </property> + <property name="text"> + <string>Calculate Cache Size</string> + </property> + </widget> + <widget class="QPushButton"> + <property name="name"> + <cstring>mEmptyCache</cstring> + </property> + <property name="text"> + <string>Empty Cache</string> + </property> + </widget> + </hbox> + </widget> + </vbox> +</widget> +<layoutdefaults spacing="6" margin="11"/> +</UI> diff --git a/src/app/configimageviewpage.ui b/src/app/configimageviewpage.ui new file mode 100644 index 0000000..6abbf7b --- /dev/null +++ b/src/app/configimageviewpage.ui @@ -0,0 +1,330 @@ +<!DOCTYPE UI><UI version="3.2" stdsetdef="1"> +<class>ConfigImageViewPage</class> +<widget class="QWidget"> + <property name="name"> + <cstring>ConfigImageViewPage</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>387</width> + <height>404</height> + </rect> + </property> + <property name="caption"> + <string>Configure Image View</string> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <widget class="QCheckBox" row="0" column="0" rowspan="1" colspan="6"> + <property name="name"> + <cstring>kcfg_enlargeSmallImages</cstring> + </property> + <property name="text"> + <string>Enlarge small images when auto &zoom is activated</string> + </property> + </widget> + <widget class="QLabel" row="2" column="0" rowspan="1" colspan="2"> + <property name="name"> + <cstring>textLabel1</cstring> + </property> + <property name="text"> + <string>Background color:</string> + </property> + </widget> + <widget class="KColorButton" row="2" column="2"> + <property name="name"> + <cstring>kcfg_backgroundColor</cstring> + </property> + <property name="text"> + <string></string> + </property> + </widget> + <spacer row="2" column="3" rowspan="1" colspan="3"> + <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>181</width> + <height>21</height> + </size> + </property> + </spacer> + <widget class="QLabel" row="3" column="0" rowspan="1" colspan="6"> + <property name="name"> + <cstring>textLabel1_3_2</cstring> + </property> + <property name="text"> + <string><b>Smoothing</b></string> + </property> + </widget> + <widget class="QLayoutWidget" row="4" column="0" rowspan="1" colspan="6"> + <property name="name"> + <cstring>layout4</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QButtonGroup"> + <property name="name"> + <cstring>kcfg_smoothAlgorithm</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>1</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="frameShape"> + <enum>NoFrame</enum> + </property> + <property name="title"> + <string></string> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <widget class="QRadioButton"> + <property name="name"> + <cstring>mSmoothNone</cstring> + </property> + <property name="text"> + <string>None</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + <property name="buttonGroupId"> + <number>0</number> + </property> + </widget> + <widget class="QRadioButton"> + <property name="name"> + <cstring>mSmoothFast</cstring> + </property> + <property name="text"> + <string>Fast</string> + </property> + <property name="buttonGroupId"> + <number>1</number> + </property> + </widget> + <widget class="QRadioButton"> + <property name="name"> + <cstring>mSmoothNormal</cstring> + </property> + <property name="text"> + <string>Normal</string> + </property> + <property name="buttonGroupId"> + <number>2</number> + </property> + </widget> + <widget class="QRadioButton"> + <property name="name"> + <cstring>mSmoothBest</cstring> + </property> + <property name="text"> + <string>Best</string> + </property> + <property name="buttonGroupId"> + <number>3</number> + </property> + </widget> + </vbox> + </widget> + <widget class="Line"> + <property name="name"> + <cstring>line1</cstring> + </property> + <property name="frameShape"> + <enum>VLine</enum> + </property> + <property name="frameShadow"> + <enum>Sunken</enum> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + </widget> + <widget class="QFrame"> + <property name="name"> + <cstring>frame3</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>7</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="frameShape"> + <enum>NoFrame</enum> + </property> + <property name="frameShadow"> + <enum>Raised</enum> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <widget class="QCheckBox"> + <property name="name"> + <cstring>kcfg_delayedSmoothing</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Delayed smoothing</string> + </property> + </widget> + <widget class="QLabel"> + <property name="name"> + <cstring>textLabel1_4</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="margin"> + <number>3</number> + </property> + <property name="text"> + <string>Using this option, Gwenview will display the image as fast as possible, and smooth it after a short delay. +Use this option if your computer is not very fast.</string> + </property> + <property name="alignment"> + <set>WordBreak|AlignTop</set> + </property> + <property name="buddy" stdset="0"> + <cstring>mDelayedSmoothing</cstring> + </property> + </widget> + </vbox> + </widget> + </hbox> + </widget> + <spacer row="5" column="4"> + <property name="name"> + <cstring>spacer10_2_3_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>16</height> + </size> + </property> + </spacer> + <widget class="QButtonGroup" row="7" column="0" rowspan="1" colspan="6"> + <property name="name"> + <cstring>mMouseWheelGroup</cstring> + </property> + <property name="frameShape"> + <enum>NoFrame</enum> + </property> + <property name="title"> + <string></string> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <property name="spacing"> + <number>6</number> + </property> + <widget class="QRadioButton"> + <property name="name"> + <cstring>mMouseWheelScroll</cstring> + </property> + <property name="text"> + <string>Scroll current image</string> + </property> + <property name="buttonGroupId"> + <number>1</number> + </property> + </widget> + <widget class="QRadioButton"> + <property name="name"> + <cstring>mMouseWheelBrowse</cstring> + </property> + <property name="text"> + <string>Browse image list</string> + </property> + <property name="buttonGroupId"> + <number>0</number> + </property> + </widget> + </vbox> + </widget> + <widget class="QLabel" row="6" column="0" rowspan="1" colspan="6"> + <property name="name"> + <cstring>textLabel1_3</cstring> + </property> + <property name="text"> + <string><b>Mouse Wheel Behavior over Image</b></string> + </property> + </widget> + <widget class="QCheckBox" row="1" column="0" rowspan="1" colspan="6"> + <property name="name"> + <cstring>kcfg_showScrollBars</cstring> + </property> + <property name="text"> + <string>Show scroll bars</string> + </property> + </widget> + </grid> +</widget> +<customwidgets> +</customwidgets> +<connections> + <connection> + <sender>mSmoothNone</sender> + <signal>toggled(bool)</signal> + <receiver>kcfg_delayedSmoothing</receiver> + <slot>setDisabled(bool)</slot> + </connection> + <connection> + <sender>mSmoothNone</sender> + <signal>toggled(bool)</signal> + <receiver>textLabel1_4</receiver> + <slot>setDisabled(bool)</slot> + </connection> +</connections> +<layoutdefaults spacing="6" margin="11"/> +<includehints> + <includehint>kcolorbutton.h</includehint> +</includehints> +</UI> diff --git a/src/app/configmiscpage.ui b/src/app/configmiscpage.ui new file mode 100644 index 0000000..e4768f2 --- /dev/null +++ b/src/app/configmiscpage.ui @@ -0,0 +1,204 @@ +<!DOCTYPE UI><UI version="3.2" stdsetdef="1"> +<class>ConfigMiscPage</class> +<widget class="QWidget"> + <property name="name"> + <cstring>ConfigMiscPage</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>457</width> + <height>303</height> + </rect> + </property> + <property name="caption"> + <string>Miscellaneous Settings</string> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>textLabel1_2_2_2</cstring> + </property> + <property name="text"> + <string>What to do when leaving a modified image</string> + </property> + </widget> + <widget class="QButtonGroup"> + <property name="name"> + <cstring>kcfg_modifiedBehavior</cstring> + </property> + <property name="frameShape"> + <enum>NoFrame</enum> + </property> + <property name="title"> + <string></string> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QRadioButton"> + <property name="name"> + <cstring>mModifyAsk</cstring> + </property> + <property name="text"> + <string>Ask</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + <property name="buttonGroupId"> + <number>0</number> + </property> + </widget> + <widget class="QRadioButton"> + <property name="name"> + <cstring>mModifySave</cstring> + </property> + <property name="text"> + <string>Save silently</string> + </property> + <property name="buttonGroupId"> + <number>1</number> + </property> + </widget> + <widget class="QRadioButton"> + <property name="name"> + <cstring>mModifyDiscard</cstring> + </property> + <property name="text"> + <string>Discard changes</string> + </property> + <property name="buttonGroupId"> + <number>2</number> + </property> + </widget> + </vbox> + </widget> + <spacer> + <property name="name"> + <cstring>spacer1</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Fixed</enum> + </property> + <property name="sizeHint"> + <size> + <width>20</width> + <height>24</height> + </size> + </property> + </spacer> + <widget class="QCheckBox"> + <property name="name"> + <cstring>kcfg_autoRotateImages</cstring> + </property> + <property name="text"> + <string>Automatically rotate images on load</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + <spacer> + <property name="name"> + <cstring>spacer1_2</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Fixed</enum> + </property> + <property name="sizeHint"> + <size> + <width>20</width> + <height>24</height> + </size> + </property> + </spacer> + <widget class="QLabel"> + <property name="name"> + <cstring>textLabel1</cstring> + </property> + <property name="text"> + <string>Which settings should be remembered next time you start Gwenview</string> + </property> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout1</cstring> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <spacer row="1" column="0"> + <property name="name"> + <cstring>spacer6</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Fixed</enum> + </property> + <property name="sizeHint"> + <size> + <width>11</width> + <height>20</height> + </size> + </property> + </spacer> + <widget class="QCheckBox" row="1" column="1"> + <property name="name"> + <cstring>kcfg_rememberURL</cstring> + </property> + <property name="text"> + <string>Last opened URL</string> + </property> + </widget> + <widget class="QCheckBox" row="0" column="1"> + <property name="name"> + <cstring>kcfg_rememberFilter</cstring> + </property> + <property name="text"> + <string>State of filter</string> + </property> + <property name="accel"> + <string></string> + </property> + </widget> + </grid> + </widget> + <spacer> + <property name="name"> + <cstring>spacer5</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>21</width> + <height>41</height> + </size> + </property> + </spacer> + </vbox> +</widget> +<layoutdefaults spacing="6" margin="11"/> +</UI> diff --git a/src/app/configslideshowpage.ui b/src/app/configslideshowpage.ui new file mode 100644 index 0000000..cb06835 --- /dev/null +++ b/src/app/configslideshowpage.ui @@ -0,0 +1,134 @@ +<!DOCTYPE UI><UI version="3.2" stdsetdef="1"> +<class>ConfigSlideshowPage</class> +<widget class="QWidget"> + <property name="name"> + <cstring>ConfigSlideshowPage</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>494</width> + <height>148</height> + </rect> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <widget class="QCheckBox" row="2" column="0" rowspan="1" colspan="3"> + <property name="name"> + <cstring>kcfg_loop</cstring> + </property> + <property name="text"> + <string>Loop</string> + </property> + </widget> + <widget class="QCheckBox" row="1" column="0" rowspan="1" colspan="3"> + <property name="name"> + <cstring>kcfg_random</cstring> + </property> + <property name="text"> + <string>Show images in random order</string> + </property> + </widget> + <widget class="QCheckBox" row="0" column="0" rowspan="1" colspan="3"> + <property name="name"> + <cstring>kcfg_fullscreen</cstring> + </property> + <property name="text"> + <string>Start in fullscreen mode</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + <widget class="QCheckBox" row="3" column="0" rowspan="1" colspan="3"> + <property name="name"> + <cstring>kcfg_stopAtEnd</cstring> + </property> + <property name="text"> + <string>Stop on the last image of the folder</string> + </property> + <property name="whatsThis" stdset="0"> + <string>By default, if you start the slideshow from the middle of a folder, the slideshow will show all images after the start image, then all images before the start image. + +When this option is enabled, the slideshow will stop on the last image of the folder.</string> + </property> + </widget> + <widget class="QLabel" row="4" column="0"> + <property name="name"> + <cstring>mDelayLabel</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>1</hsizetype> + <vsizetype>1</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Delay between images (in seconds):</string> + </property> + <property name="buddy" stdset="0"> + <cstring></cstring> + </property> + </widget> + <spacer row="4" column="2"> + <property name="name"> + <cstring>Spacer7_2</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>180</width> + <height>20</height> + </size> + </property> + </spacer> + <widget class="KDoubleSpinBox" row="4" column="1"> + <property name="name"> + <cstring>kcfg_delay</cstring> + </property> + <property name="maxValue"> + <number>100</number> + </property> + <property name="minValue"> + <number>0</number> + </property> + <property name="lineStep"> + <number>0.1</number> + </property> + <property name="acceptLocalizedNumbers"> + <bool>false</bool> + </property> + <property name="precision"> + <number>2</number> + </property> + </widget> + </grid> +</widget> +<customwidgets> +</customwidgets> +<connections> + <connection> + <sender>kcfg_loop</sender> + <signal>toggled(bool)</signal> + <receiver>kcfg_stopAtEnd</receiver> + <slot>setDisabled(bool)</slot> + </connection> +</connections> +<layoutdefaults spacing="6" margin="11"/> +<includehints> + <includehint>knuminput.h</includehint> +</includehints> +</UI> diff --git a/src/app/dirviewcontroller.cpp b/src/app/dirviewcontroller.cpp new file mode 100644 index 0000000..c5fc538 --- /dev/null +++ b/src/app/dirviewcontroller.cpp @@ -0,0 +1,143 @@ +// 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 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. + +*/ +// Self +#include "dirviewcontroller.moc" + +// Qt +#include <qpopupmenu.h> + +// KDE +#include <kdebug.h> +#include <kiconloader.h> +#include <kinputdialog.h> +#include <klocale.h> +#include <kpropsdlg.h> + +// Local +#include <treeview.h> +#include <gvcore/fileoperation.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 + +struct DirViewController::Private { + TreeView* mTreeView; +}; + + +DirViewController::DirViewController(QWidget* parent) +: QObject(parent) +{ + d=new Private; + + d->mTreeView=new TreeView(parent); + + connect(d->mTreeView, SIGNAL(selectionChanged(QListViewItem*)), + this, SLOT(slotTreeViewSelectionChanged(QListViewItem*)) ); + + connect(d->mTreeView, SIGNAL(contextMenu(KListView*, QListViewItem*, const QPoint&)), + this, SLOT(slotTreeViewContextMenu(KListView*, QListViewItem*, const QPoint&)) ); +} + + +DirViewController::~DirViewController() { + delete d; +} + + +QWidget* DirViewController::widget() const { + return d->mTreeView; +} + + +void DirViewController::setURL(const KURL& url) { + d->mTreeView->setURL(url); +} + + +void DirViewController::slotTreeViewSelectionChanged(QListViewItem* item) { + if (!item) return; + LOG(d->mTreeView->currentURL()); + emit urlChanged(d->mTreeView->currentURL()); +} + + +void DirViewController::slotTreeViewContextMenu(KListView*, QListViewItem*, const QPoint& pos) { + QPopupMenu menu(d->mTreeView); + menu.insertItem(SmallIcon("folder_new"),i18n("New Folder..."),this,SLOT(makeDir())); + menu.insertSeparator(); + menu.insertItem(i18n("Rename..."),this,SLOT(renameDir())); + menu.insertItem(SmallIcon("editdelete"),i18n("Delete"),this,SLOT(removeDir())); + menu.insertSeparator(); + menu.insertItem(i18n("Properties"),this,SLOT(showPropertiesDialog())); + + menu.exec(pos); +} + + +void DirViewController::makeDir() { + if (!d->mTreeView->currentItem()) return; + FileOperation::makeDir(d->mTreeView->currentURL(), d->mTreeView, this, SLOT(slotDirMade()) ); +} + + +void DirViewController::slotDirMade() { + if (!d->mTreeView->currentItem()) return; + d->mTreeView->currentItem()->setOpen(true); +} + + +void DirViewController::renameDir() { + if (!d->mTreeView->currentItem()) return; + FileOperation::rename(d->mTreeView->currentURL(), d->mTreeView); +} + + +void DirViewController::removeDir() { + if (!d->mTreeView->currentItem()) return; + + KURL::List list; + list << d->mTreeView->currentURL(); + FileOperation::del(list, d->mTreeView); + + QListViewItem* item=d->mTreeView->currentItem(); + if (!item) return; + item=item->parent(); + if (!item) return; + d->mTreeView->setCurrentItem(item); +} + + +void DirViewController::showPropertiesDialog() { + (void)new KPropertiesDialog(d->mTreeView->currentURL(), d->mTreeView); +} + +} // namespace diff --git a/src/app/dirviewcontroller.h b/src/app/dirviewcontroller.h new file mode 100644 index 0000000..129cc8f --- /dev/null +++ b/src/app/dirviewcontroller.h @@ -0,0 +1,71 @@ +// 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 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 DIRVIEWCONTROLLER_H +#define DIRVIEWCONTROLLER_H + +// Qt +#include "qobject.h" + +class QListViewItem; +class QPoint; +class QWidget; +class KListView; +class KURL; + +namespace KIO { +class Job; +} + +namespace Gwenview { + +class DirViewController : public QObject { + Q_OBJECT +public: + DirViewController(QWidget* parent); + virtual ~DirViewController(); + QWidget* widget() const; + +public slots: + void setURL(const KURL&); + +signals: + void urlChanged(const KURL&); + void urlRenamed(const KURL& from, const KURL& to); + +private: + struct Private; + Private* d; + +private slots: + void slotTreeViewSelectionChanged(QListViewItem*); + void slotTreeViewContextMenu(KListView*, QListViewItem*, const QPoint&); + + void makeDir(); + void slotDirMade(); + void renameDir(); + void removeDir(); + void showPropertiesDialog(); +}; + +} // namespace + +#endif /* DIRVIEWCONTROLLER_H */ diff --git a/src/app/gwenviewui.rc b/src/app/gwenviewui.rc new file mode 100644 index 0000000..6e165ad --- /dev/null +++ b/src/app/gwenviewui.rc @@ -0,0 +1,133 @@ +<?xml version="1.0"?> +<!DOCTYPE gui SYSTEM "kpartgui.dtd"> +<kpartgui name="Gwenview" version="27"> +<MenuBar> + <Menu name="file"> + <Action name="file_rename"/> + <Action name="file_copy"/> + <Action name="file_move"/> + <Action name="file_link"/> + <Action name="file_delete"/> + <Separator/> + <Action name="file_properties"/> + </Menu> + <Menu name="edit"> + <Action name="rotate_left"/> + <Action name="rotate_right"/> + <Action name="mirror"/> + <Action name="flip"/> + </Menu> + <Menu name="view" noMerge="1"> + <text>&View</text> + <Action name="reload"/> + <Separator/> + <Action name="switch_to_view_mode" /> + <Action name="switch_to_browse_mode" /> + <Action name="fullscreen"/> + <Action name="slideshow"/> + <Separator/> + <Action name="list_mode" /> + <Action name="side_thumbnail_mode" /> + <Action name="bottom_thumbnail_mode" /> + <Action name="show_dot_files"/> + <Separator/> + <Menu name="colors"> + <text>&Colors</text> + <Action name="increase_gamma"/> + <Action name="decrease_gamma"/> + <Action name="increase_brightness"/> + <Action name="decrease_brightness"/> + <Action name="increase_contrast"/> + <Action name="decrease_contrast"/> + <Action name="adjust_bcg"/> + </Menu> + <Action name="view_zoom_to_fit"/> + <Action name="view_zoom_to_width"/> + <Action name="view_zoom_to_height"/> + <Action name="view_zoom_in"/> + <Action name="view_zoom_out"/> + <Action name="view_actual_size"/> + <Action name="view_zoom_lock"/> + </Menu> + <Menu name="go_web" noMerge="1"> + <text>&Go</text> + <Action name="first"/> + <Action name="previous"/> + <Action name="next"/> + <Action name="last"/> + <Separator/> + <Action name="go_back"/> + <Action name="go_forward"/> + <Action name="go_home"/> + <Separator/> + <Action name="go_up"/> + <Action name="first_sub_folder"/> + <Action name="previous_folder"/> + <Action name="next_folder"/> + </Menu> + <Action name="bookmarks"/> + <Menu name="settings"> + <Action name="configure_tools" append="configure_merge"/> + </Menu> + <Menu name="plugins"> + <text>&Plugins</text> + <Menu name="kipi_images"> + <text>Images</text> + <ActionList name="image_actions"/> + </Menu> + <Menu name="kipi_effects"> + <text>Effects</text> + <ActionList name="effect_actions"/> + </Menu> + <Menu name="kipi_tools"> + <text>Tools</text> + <ActionList name="tool_actions"/> + </Menu> + <Menu name="kipi_batch"> + <text>Batch Processing</text> + <ActionList name="batch_actions"/> + </Menu> + <Menu name="kipi_import"> + <text>Import</text> + <ActionList name="import_actions"/> + </Menu> + <Menu name="kipi_export"> + <text>Export</text> + <ActionList name="export_actions"/> + </Menu> + <Menu name="kipi_collections"> + <text>Collections</text> + <ActionList name="collection_actions"/> + </Menu> + </Menu> + <Menu name="window"> + <text>&Window</text> + <ActionList name="winlist"/> + <Separator/> + <Action name="reset_dock_widgets"/> + </Menu> +</MenuBar> + +<ToolBar name="mainToolBar" noMerge="1" iconText="icontextright"> + <text>Main Toolbar</text> + <Action name="switch_to_browse_mode"/> + <Action name="switch_to_view_mode"/> + <Action name="fullscreen"/> + <Action name="slideshow"/> +</ToolBar> + +<ToolBar name="locationToolBar"> + <text>Location Toolbar</text> + <Action name="go_back" /> + <Action name="go_forward" /> + <Action name="go_up" /> + <Action name="go_home" /> + <Action name="clear_location" /> + <Action name="location_label" /> + <Action name="location_url" /> + <Action name="location_go" /> +</ToolBar> + +<StatusBar/> + +</kpartgui> diff --git a/src/app/history.cpp b/src/app/history.cpp new file mode 100644 index 0000000..d9cb30e --- /dev/null +++ b/src/app/history.cpp @@ -0,0 +1,137 @@ +// vim: set tabstop=4 shiftwidth=4 noexpandtab: +/* +Gwenview - A simple image viewer for KDE +Copyright 2000-2004 Aur�lien G�teau +Copyright 2003 Tudor Calin + +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 <kaction.h> +#include <klocale.h> +#include <kpopupmenu.h> +#include <kdeversion.h> +#include <kstdaccel.h> +#include <kstdguiitem.h> + +// Local +#include "history.moc" +namespace Gwenview { + + +const unsigned int MAX_HISTORY_SIZE=12; + +History::History(KActionCollection* actionCollection) { + mPosition=mHistoryList.end(); + mMovingInHistory=false; + + // Actions + QPair<KGuiItem, KGuiItem> backForward = KStdGuiItem::backAndForward(); + mGoBack=new KToolBarPopupAction(backForward.first, + KStdAccel::shortcut(KStdAccel::Back), + this, SLOT(goBack()), actionCollection, "go_back"); + mGoForward=new KToolBarPopupAction(backForward.second, + KStdAccel::shortcut(KStdAccel::Forward), + this, SLOT(goForward()), actionCollection, "go_forward"); + + // Connections + connect(mGoBack->popupMenu(),SIGNAL(activated(int)), + this,SLOT(goBackTo(int)) ); + connect(mGoForward->popupMenu(),SIGNAL(activated(int)), + this,SLOT(goForwardTo(int)) ); + + connect(mGoBack->popupMenu(), SIGNAL(aboutToShow()), + this, SLOT(fillGoBackMenu()) ); + connect(mGoForward->popupMenu(), SIGNAL(aboutToShow()), + this, SLOT(fillGoForwardMenu()) ); +} + + +History::~History() { +} + + +void History::addURLToHistory(const KURL& url2) { + KURL url( url2 ); + url.setFileName( QString::null ); + if (!mMovingInHistory) { + if (mPosition!=mHistoryList.end() && url.equals(*mPosition, true)) return; + + // Drop everything after current + HistoryList::iterator it=mPosition; + ++it; + mHistoryList.erase(it, mHistoryList.end()); + + mHistoryList.append(url); + if(mHistoryList.count()==MAX_HISTORY_SIZE) mHistoryList.pop_front(); + mPosition=mHistoryList.fromLast(); + } + + mGoBack->setEnabled(mPosition!=mHistoryList.begin()); + mGoForward->setEnabled(mPosition!=mHistoryList.fromLast()); +} + + +void History::fillGoBackMenu() { + QPopupMenu* menu=mGoBack->popupMenu(); + menu->clear(); + HistoryList::ConstIterator it; + + int pos=1; + for(it=mHistoryList.begin(); it!=mPosition; ++it, ++pos) { + menu->insertItem( (*it).prettyURL(-1), pos, 0); + } +} + +void History::fillGoForwardMenu() { + QPopupMenu* menu=mGoForward->popupMenu(); + menu->clear(); + HistoryList::ConstIterator it=mPosition; + ++it; + + int pos=1; + for(; it!=mHistoryList.end(); ++it, ++pos) { + menu->insertItem( (*it).prettyURL(-1), pos, -1); + } +} + +void History::goBack() { + goBackTo(1); +} + + +void History::goForward() { + goForwardTo(1); +} + + +void History::goBackTo(int id) { + for (;id>0; --id) --mPosition; + mMovingInHistory=true; + emit urlChanged(*mPosition); + mMovingInHistory=false; +} + + +void History::goForwardTo(int id) { + for (;id>0; --id) ++mPosition; + mMovingInHistory=true; + emit urlChanged(*mPosition); + mMovingInHistory=false; +} + +} // namespace diff --git a/src/app/history.h b/src/app/history.h new file mode 100644 index 0000000..38e912d --- /dev/null +++ b/src/app/history.h @@ -0,0 +1,71 @@ +// vim: set tabstop=4 shiftwidth=4 noexpandtab: +/* +Gwenview - A simple image viewer for KDE +Copyright 2000-2004 Aur�lien G�teau +Copyright 2003 Tudor Calin + +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 HISTORY_H +#define HISTORY_H + +// Qt +#include <qobject.h> +#include <qvaluelist.h> + +// KDE +#include <kurl.h> + +class KToolBarPopupAction; +class KActionCollection; + +typedef QValueList<KURL> HistoryList; + +namespace Gwenview { +class History : public QObject { +Q_OBJECT + +public: + History(KActionCollection*); + ~History(); + +signals: + void urlChanged(const KURL&); + +public slots: + void addURLToHistory(const KURL&); + +private: + KToolBarPopupAction* mGoBack; + KToolBarPopupAction* mGoForward; + HistoryList mHistoryList; + HistoryList::Iterator mPosition; + bool mMovingInHistory; + +private slots: + void fillGoBackMenu(); + void fillGoForwardMenu(); + void goBack(); + void goForward(); + void goBackTo(int); + void goForwardTo(int); +}; + +} // namespace +#endif + + + diff --git a/src/app/kipiinterface.cpp b/src/app/kipiinterface.cpp new file mode 100644 index 0000000..94b2455 --- /dev/null +++ b/src/app/kipiinterface.cpp @@ -0,0 +1,224 @@ +// 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 <config.h> +#ifdef GV_HAVE_KIPI + +// Qt +#include <qdir.h> +#include <qregexp.h> +#include <qtimer.h> + +// KDE +#include <kdebug.h> +#include <kfileitem.h> +#include <klocale.h> +#include <kurl.h> + +// KIPI +#include <libkipi/imagecollectionshared.h> +#include <libkipi/imageinfoshared.h> + +// Local +#include "gvcore/archive.h" +#include "gvcore/cache.h" +#include "gvcore/fileviewbase.h" +#include "gvcore/fileviewcontroller.h" +#include "imageutils/jpegcontent.h" +#include "kipiinterface.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 ImageCollection : public KIPI::ImageCollectionShared { +public: + ImageCollection(KURL dirURL, const QString& name, const KURL::List& images) + : KIPI::ImageCollectionShared() + , mDirURL(dirURL) + , mName(name) + , mImages(images) {} + + QString name() { return mName; } + QString comment() { return QString::null; } + KURL::List images() { return mImages; } + KURL uploadRoot() { return KURL("/"); } + KURL uploadPath() { return mDirURL; } + QString uploadRootName() { return "/"; } + bool isDirectory() { return true; } + +private: + KURL mDirURL; + QString mName; + KURL::List mImages; +}; + + + +class ImageInfo : public KIPI::ImageInfoShared { + static const QRegExp sExtensionRE; +public: + ImageInfo(KIPI::Interface* interface, const KURL& url) : KIPI::ImageInfoShared(interface, url) {} + + QString title() { + QString txt=_url.fileName(); + txt.replace("_", " "); + txt.replace(sExtensionRE, ""); + return txt; + } + + QString description() { + if (!_url.isLocalFile()) return QString::null; + + ImageUtils::JPEGContent content; + bool ok=content.load(_url.path()); + if (!ok) return QString::null; + + return content.comment(); + } + + void setDescription(const QString&) {} + + QMap<QString,QVariant> attributes() { + return QMap<QString,QVariant>(); + } + + void clearAttributes() {} + + void addAttributes(const QMap<QString, QVariant>&) {} +}; + +const QRegExp ImageInfo::sExtensionRE("\\.[a-z0-9]+$", false /*caseSensitive*/); + + +struct KIPIInterfacePrivate { + FileViewController* mFileView; +}; + + +KIPIInterface::KIPIInterface( QWidget* parent, FileViewController* fileView) +:KIPI::Interface(parent, "Gwenview kipi interface") { + d=new KIPIInterfacePrivate; + d->mFileView=fileView; + + connect(d->mFileView, SIGNAL(selectionChanged()), + this, SLOT(slotSelectionChanged()) ); + + connect(d->mFileView, SIGNAL(completed()), + this, SLOT(slotDirectoryChanged()) ); +// delay a bit, so that it's called after loadPlugins() + QTimer::singleShot( 0, this, SLOT( init())); +} + + +KIPIInterface::~KIPIInterface() { + delete d; +} + + +void KIPIInterface::init() { + slotDirectoryChanged(); + slotSelectionChanged(); +} + +KIPI::ImageCollection KIPIInterface::currentAlbum() { + LOG(""); + KURL::List list; + KFileItemListIterator it( *d->mFileView->currentFileView()->items() ); + for ( ; it.current(); ++it ) { + KFileItem* item=it.current(); + if (!Archive::fileItemIsDirOrArchive(item)) { + list.append(it.current()->url()); + } + } + KURL url=d->mFileView->dirURL(); + return KIPI::ImageCollection(new ImageCollection(url, url.fileName(), list)); +} + + +KIPI::ImageCollection KIPIInterface::currentSelection() { + LOG(""); + KURL::List list=d->mFileView->selectedImageURLs(); + KURL url=d->mFileView->dirURL(); + return KIPI::ImageCollection(new ImageCollection(url, i18n("%1 (Selected Images)").arg(url.fileName()), list)); +} + + +QValueList<KIPI::ImageCollection> KIPIInterface::allAlbums() { + LOG(""); + QValueList<KIPI::ImageCollection> list; + list << currentAlbum() << currentSelection(); + return list; +} + + +KIPI::ImageInfo KIPIInterface::info(const KURL& url) { + LOG(""); + return KIPI::ImageInfo( new ImageInfo(this, url) ); +} + +int KIPIInterface::features() const { + return KIPI::AcceptNewImages; +} + +/** + * KDirLister will pick up the image if necessary, so no updating is needed + * here, it is however necessary to discard caches if the plugin preserves timestamp + */ +bool KIPIInterface::addImage(const KURL& url, QString&) { + Cache::instance()->invalidate( url ); + return true; +} + +void KIPIInterface::delImage(const KURL& url) { + Cache::instance()->invalidate( url ); +} + +// TODO currently KDirWatch doesn't have watching of files in a directory +// implemented, so KDirLister will not inform when a file changes +void KIPIInterface::refreshImages( const KURL::List& urls ) { + for( KURL::List::ConstIterator it = urls.begin(); + it != urls.end(); + ++it ) { + Cache::instance()->invalidate( *it ); + } + d->mFileView->refreshItems( urls ); +} + + +void KIPIInterface::slotSelectionChanged() { + emit selectionChanged(d->mFileView->selectionSize() > 0); +} + + +void KIPIInterface::slotDirectoryChanged() { + emit currentAlbumChanged(d->mFileView->fileCount() > 0); +} + + +} // namespace + +#endif /* GV_HAVE_KIPI */ diff --git a/src/app/kipiinterface.h b/src/app/kipiinterface.h new file mode 100644 index 0000000..c664409 --- /dev/null +++ b/src/app/kipiinterface.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 KIPIINTERFACE_H +#define KIPIINTERFACE_H + +#include <config.h> +#ifdef GV_HAVE_KIPI + +#include <libkipi/interface.h> +namespace Gwenview { + +class KIPIInterfacePrivate; + +class FileViewController; + +class KIPIInterface :public KIPI::Interface { + Q_OBJECT + +public: + KIPIInterface( QWidget* parent, FileViewController*); + virtual ~KIPIInterface(); + + KIPI::ImageCollection currentAlbum(); + KIPI::ImageCollection currentSelection(); + QValueList<KIPI::ImageCollection> allAlbums(); + KIPI::ImageInfo info( const KURL& ); + int features() const; + virtual bool addImage(const KURL&, QString& err); + virtual void delImage( const KURL& ); + virtual void refreshImages( const KURL::List& urls ); + +private: + KIPIInterfacePrivate* d; + +private slots: + void slotSelectionChanged(); + void slotDirectoryChanged(); + void init(); +}; + +#endif /* GV_HAVE_KIPI */ +} // namespace +#endif /* KIPIINTERFACE_H */ + diff --git a/src/app/main.cpp b/src/app/main.cpp new file mode 100644 index 0000000..9fe8cb2 --- /dev/null +++ b/src/app/main.cpp @@ -0,0 +1,163 @@ +// 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 <qdir.h> + +#include <kaboutdata.h> +#include <kapplication.h> +#include <kcmdlineargs.h> +#include <kdebug.h> +#include <klocale.h> + +#include "gvcore/cache.h" +#include "gvcore/fileviewcontroller.h" +#include <../gvcore/fileviewconfig.h> +#include <../gvcore/miscconfig.h> +#include "mainwindow.h" +namespace Gwenview { + +static KCmdLineOptions options[] = { + { "f", I18N_NOOP("Start in fullscreen mode"), 0 }, + { "filter-type <all,images,videos>", I18N_NOOP("Filter by file type"), 0 }, + { "filter-name <pattern>", I18N_NOOP("Filter by file pattern (*.jpg, 01*...)"), 0 }, + { "filter-from <date>", I18N_NOOP("Only show files newer or equal to <date>"), 0 }, + { "filter-to <date>", I18N_NOOP("Only show files older or equal to <date>"), 0 }, + { "+[file or folder]", I18N_NOOP("A starting file or folder"), 0 }, + KCmdLineLastOption +}; + +static const char version[] = "1.4.2"; + + +void applyFilterArgs(KCmdLineArgs* args, FileViewController* controller) { + QString filterType = args->getOption("filter-type"); + QString filterName = args->getOption("filter-name"); + QString filterFrom = args->getOption("filter-from"); + QString filterTo = args->getOption("filter-to"); + // Do nothing if there is no filter + if (filterType.isEmpty() && filterName.isEmpty() + && filterFrom.isEmpty() && filterTo.isEmpty()) + { + return; + } + + QStringList typeList; + typeList << "all" << "images" << "videos"; + int mode = typeList.findIndex(filterType); + if (mode == -1) { + // Default to "all" + controller->setFilterMode(0); + } else { + controller->setFilterMode(mode); + } + + controller->setShowFilterBar( + !filterName.isEmpty() + || !filterFrom.isEmpty() + || !filterTo.isEmpty() ); + + controller->setFilterName(filterName); + + bool ok = false; + QDate date; + if (!filterFrom.isEmpty()) { + date = KGlobal::locale()->readDate(filterFrom, &ok); + if (!ok) { + kdWarning() << "Invalid value for filter-from option\n"; + } + } + controller->setFilterFromDate(date); + + date=QDate(); + if (!filterTo.isEmpty()) { + date = KGlobal::locale()->readDate(filterTo, &ok); + if (!ok) { + kdWarning() << "Invalid value for filter-to option\n"; + } + } + controller->setFilterToDate(date); + + controller->applyFilter(); +} + + +#ifndef __KDE_HAVE_GCC_VISIBILITY +#undef KDE_EXPORT +#define KDE_EXPORT +#endif + +extern "C" +KDE_EXPORT int kdemain (int argc, char *argv[]) { + KAboutData aboutData("gwenview", I18N_NOOP("Gwenview"), + version, I18N_NOOP("An image viewer for KDE"), KAboutData::License_GPL, + "Copyright 2000-2006, The Gwenview developers",0,"http://gwenview.sourceforge.net"); + aboutData.addAuthor("Aurélien Gâteau", I18N_NOOP("Main developer"), "[email protected]"); + aboutData.addAuthor("Luboš Luňák", I18N_NOOP("Developer"), "[email protected]"); + + aboutData.addCredit("Frank Becker", I18N_NOOP("Fast JPEG thumbnail generation (v0.13.0)"), "[email protected]"); + aboutData.addCredit("Tudor Calin", I18N_NOOP("Address bar (v0.16.0)\nHistory support (v1.0.0)"), "[email protected]"); + aboutData.addCredit("Avinash Chopde", I18N_NOOP("File operation patch (v0.9.2)"), "[email protected]"); + aboutData.addCredit("Marco Gazzetta", I18N_NOOP("Fixed crash when trying to generate a thumbnail for a broken JPEG file (v0.16.0)"), "[email protected]"); + aboutData.addCredit("GeniusR13", I18N_NOOP("Fixed compilation on KDE 3.0 (v0.16.1)"), "[email protected]"); + aboutData.addCredit("Ian Koenig", I18N_NOOP("First RPM spec file"), "[email protected]"); + aboutData.addCredit("Meni Livne", I18N_NOOP("Toolbar layout patch for RTL languages (v0.16.0)"), "[email protected]"); + aboutData.addCredit("Angelo Naselli", I18N_NOOP("Printing support (v1.0.0)"), "[email protected]"); + aboutData.addCredit("Jos van den Oever", I18N_NOOP("File info view (v1.0.0)\nPatch to toggle auto-zoom on click (v1.0.0)"), "[email protected]"); + aboutData.addCredit("Jeroen Peters", I18N_NOOP("Configurable mouse wheel behavior (v1.1.1)"), "[email protected]"); + aboutData.addCredit("Andreas Pfaller", I18N_NOOP("Option to prevent Gwenview from automatically loading the first image of a folder (v0.15.0)"), "[email protected]"); + aboutData.addCredit("Renchi Raju", I18N_NOOP("Fixed thumbnail generation to share the thumbnail folder of Konqueror v3 (v0.15.0)"), "[email protected]"); + aboutData.addCredit("Michael Spanier", I18N_NOOP("Patch for mouse navigation (v0.7.0)"), "[email protected]"); + aboutData.addCredit("Christian A Strømmen", I18N_NOOP("Integration in Konqueror folder context menu"), "[email protected]"); + + KCmdLineArgs::init( argc, argv, &aboutData ); + KCmdLineArgs::addCmdLineOptions( options ); + + KApplication kapplication; + + if (kapplication.isRestored()) { + RESTORE(MainWindow) + } else { + KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); + + MainWindow *mainWindow = new MainWindow; + applyFilterArgs(args, mainWindow->fileViewController()); + + bool fullscreen=args->isSet("f"); + if (fullscreen) mainWindow->setFullScreen(true); + + KURL url; + if (args->count()>0) { + url=args->url(0); + } else { + if (MiscConfig::rememberURL() && MiscConfig::history().count() > 0) { + url = KURL(MiscConfig::history()[0]); + } else { + url.setPath( QDir::currentDirPath() ); + } + } + mainWindow->openURL(url); + + mainWindow->show(); + } + + return kapplication.exec(); +} + +} // namespace diff --git a/src/app/mainwindow.cpp b/src/app/mainwindow.cpp new file mode 100644 index 0000000..2cd873a --- /dev/null +++ b/src/app/mainwindow.cpp @@ -0,0 +1,1371 @@ +// 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. + +*/ +#include "mainwindow.moc" + +// Qt +#include <qdir.h> +#include <qdockarea.h> +#include <qwidgetstack.h> + +// KDE +#include <kaboutdata.h> +#include <kaccel.h> +#include <kaction.h> +#include <kapplication.h> +#include <kbookmarkmanager.h> +#include <kbookmarkmenu.h> +#include <kcombobox.h> +#include <kconfig.h> +#include <kcursor.h> +#include <kdebug.h> +#include <kdeversion.h> +#include <kdockwidget.h> +#include <kedittoolbar.h> +#include <kfiledialog.h> +#include <kglobal.h> +#include <khelpmenu.h> +#include <kiconloader.h> +#include <kio/netaccess.h> +#include <kkeydialog.h> +#include <klargefile.h> +#include <klistview.h> +#include <klocale.h> +#include <kmenubar.h> +#include <kmessagebox.h> +#include <kpopupmenu.h> +#include <kpropsdlg.h> +#include <kprotocolinfo.h> +#include <kstandarddirs.h> +#include <kstatusbar.h> +#include <kstdaccel.h> +#include <kstdaction.h> +#include <ktoolbarlabelaction.h> +#include <kurlcompletion.h> +#include <kurlpixmapprovider.h> +#include <kprinter.h> + +#include <config.h> +// KIPI +#ifdef GV_HAVE_KIPI +#include <libkipi/plugin.h> +#include <libkipi/pluginloader.h> +#endif + +// Local +#include "bookmarkowner.h" +#include "bookmarkviewcontroller.h" +#include "configdialog.h" +#include "dirviewcontroller.h" +#include "history.h" +#include "metaedit.h" +#include "truncatedtextlabel.h" +#include "vtabwidget.h" + +#include "gvcore/externaltoolcontext.h" +#include "gvcore/externaltoolmanager.h" +#include "gvcore/fileoperation.h" +#include "gvcore/archive.h" +#include "gvcore/captionformatter.h" +#include "gvcore/document.h" +#include "gvcore/externaltooldialog.h" +#include "gvcore/fileviewbase.h" +#include "gvcore/fileviewcontroller.h" +#include "gvcore/imageview.h" +#include "gvcore/imageviewcontroller.h" +#include "gvcore/slideshow.h" +#include "gvcore/printdialog.h" +#include "gvcore/cache.h" +#include "gvcore/thumbnailloadjob.h" +#include <../gvcore/slideshowconfig.h> +#include <../gvcore/fullscreenconfig.h> +#include <../gvcore/fileviewconfig.h> +#include <../gvcore/miscconfig.h> + +#include "config.h" + +#ifdef GV_HAVE_KIPI +#include "kipiinterface.h" +#endif + +namespace Gwenview { + +const char CONFIG_DOCK_GROUP[]="dock"; +const char CONFIG_DIRWIDGET_GROUP[]="dir widget"; +const char CONFIG_PIXMAPWIDGET_GROUP[]="pixmap widget"; +const char CONFIG_CACHE_GROUP[]="cache"; + +const char CONFIG_GWENVIEW_DOCK_VERSION[]="Gwenview version"; + +const char CONFIG_SESSION_URL[] = "url"; + +// This version is here to avoid configuration migration troubles when changes +// are made to the dock behavior +const int GWENVIEW_DOCK_VERSION=2; + +// The timeout before an hint in the statusbar disappear (in msec) +const int HINT_TIMEOUT=10000; + +// How many items should be stored in history +const int HISTORY_MAX_COUNT=20; + +#undef ENABLE_LOG +#undef LOG +//#define ENABLE_LOG +#ifdef ENABLE_LOG +#define LOG(x) kdDebug() << k_funcinfo << x << endl +#else +#define LOG(x) ; +#endif + +enum { StackIDBrowse, StackIDView }; + + +static bool urlIsDirectory(QWidget* parent, const KURL& url) { + if( url.filename( false ).isEmpty()) return true; // file:/somewhere/<nothing here> + // Do direct stat instead of using KIO if the file is local (faster) + if( url.isLocalFile() + && !KIO::probably_slow_mounted( url.path())) { + KDE_struct_stat buff; + if ( KDE_stat( QFile::encodeName(url.path()), &buff ) == 0 ) { + return S_ISDIR( buff.st_mode ); + } + } + KIO::UDSEntry entry; + if( KIO::NetAccess::stat( url, entry, parent)) { + KIO::UDSEntry::ConstIterator it; + for(it=entry.begin();it!=entry.end();++it) { + if ((*it).m_uds==KIO::UDS_FILE_TYPE) { + return S_ISDIR( (*it).m_long ); + } + } + } + return false; +} + + +MainWindow::MainWindow() +: KMainWindow() +#ifdef GV_HAVE_KIPI +, mPluginLoader(0) +#endif +{ + // Backend + mDocument=new Document(this); + mHistory=new History(actionCollection()); + // GUI + createActions(); + createWidgets(); + createLocationToolBar(); + createObjectInteractions(); + + setStandardToolBarMenuEnabled(true); + createGUI("gwenviewui.rc", false); + + createConnections(); + mWindowListActions.setAutoDelete(true); + updateWindowActions(); + KMainWindow::applyMainWindowSettings(KGlobal::config(), "MainWindow"); +} + + +void MainWindow::setFullScreen(bool value) { + if (value != mToggleFullScreen->isChecked()) { + mToggleFullScreen->activate(); + } +} + + +bool MainWindow::queryClose() { + mDocument->saveBeforeClosing(); + + KConfig* config=KGlobal::config(); + + // Don't store dock layout if only the image dock is visible. This avoid + // saving layout when in "fullscreen" or "image only" mode. + if (mFileViewController->isVisible() || mDirViewController->widget()->isVisible()) { + mDockArea->writeDockConfig(config,CONFIG_DOCK_GROUP); + } + + if (FileViewConfig::deleteCacheOnExit()) { + QString dir=ThumbnailLoadJob::thumbnailBaseDir(); + + if (QFile::exists(dir)) { + KURL url; + url.setPath(dir); + KIO::NetAccess::del(url, this); + } + } + + if (!mToggleFullScreen->isChecked()) { + saveMainWindowSettings(KGlobal::config(), "MainWindow"); + } + MiscConfig::setHistory( mURLEdit->historyItems() ); + MiscConfig::writeConfig(); + return true; +} + +void MainWindow::saveProperties( KConfig* cfg ) { + cfg->writeEntry( CONFIG_SESSION_URL, mFileViewController->url().url()); +} + +void MainWindow::readProperties( KConfig* cfg ) { + KURL url(cfg->readEntry(CONFIG_SESSION_URL)); + openURL(url); +} + +//----------------------------------------------------------------------- +// +// Public slots +// +//----------------------------------------------------------------------- +void MainWindow::openURL(const KURL& url) { + bool isDir = urlIsDirectory(this, url); + LOG("url=" << url.prettyURL() << ", isDir=" << isDir); + + if (isDir) { + mFileViewController->setDirURL(url); + mFileViewController->setFocus(); + } else { + mDocument->setURL(url); + mFileViewController->setDirURL(url.upURL()); + mFileViewController->setFileNameToSelect(url.filename()); + mImageViewController->setFocus(); + } + + if (!mToggleFullScreen->isChecked() && !isDir && !mSwitchToViewMode->isChecked()) { + mSwitchToViewMode->activate(); + } +} + +void MainWindow::slotRenamed(const QString& fileName) { + KURL url = mDocument->url(); + url.setFileName(fileName); + mDocument->setURL(url); +} + +void MainWindow::slotDirURLChanged(const KURL& dirURL) { + LOG(dirURL.prettyURL(0,KURL::StripFileProtocol)); + + mGoUp->setEnabled(dirURL.path()!="/"); + + updateStatusInfo(); + updateImageActions(); + updateLocationURL(); +} + +void MainWindow::updateLocationURL() { + LOG(""); + KURL url; + if (mSwitchToBrowseMode->isChecked()) { + url=mFileViewController->dirURL(); + if (!url.isValid()) { + url=mDocument->url(); + } + } else { + url=mDocument->url(); + } + LOG(url.prettyURL()); + mURLEdit->setEditText(url.pathOrURL()); + mURLEdit->addToHistory(url.pathOrURL()); +} + +void MainWindow::goUp() { + KURL url = mFileViewController->dirURL(); + mFileViewController->setDirURL(url.upURL()); + mFileViewController->setFileNameToSelect(url.fileName()); +} + +void MainWindow::updateFullScreenLabel() { + CaptionFormatter formatter; + formatter.mPath=mDocument->url().path(); + formatter.mFileName=mDocument->url().fileName(); + formatter.mComment=mDocument->comment(); + formatter.mImageSize=mDocument->image().size(); + formatter.mPosition=mFileViewController->shownFilePosition()+1; + formatter.mCount=mFileViewController->fileCount(); + formatter.mAperture=mDocument->aperture(); + formatter.mExposureTime=mDocument->exposureTime(); + formatter.mIso=mDocument->iso(); + formatter.mFocalLength=mDocument->focalLength(); + + QString txt=formatter.format( FullScreenConfig::osdFormat() ); + mFullScreenLabelAction->label()->setText(txt); +} + +void MainWindow::goUpTo(int id) { + KPopupMenu* menu=mGoUp->popupMenu(); + KURL url(menu->text(id)); + KURL childURL; + int index=menu->indexOf(id); + if (index>0) { + childURL=KURL(menu->text(menu->idAt(index-1))); + } else { + childURL=mDocument->dirURL(); + } + mFileViewController->setDirURL(url); + mFileViewController->setFileNameToSelect(childURL.fileName()); +} + +void MainWindow::fillGoUpMenu() { + QPopupMenu* menu = mGoUp->popupMenu(); + menu->clear(); + int pos = 0; + KURL url = mFileViewController->dirURL().upURL(); + for (; url.hasPath() && pos<10; url=url.upURL(), ++pos) { + menu->insertItem(url.pathOrURL()); + if (url.path()=="/") break; + } +} + + +//----------------------------------------------------------------------- +// +// File operations +// +//----------------------------------------------------------------------- +void MainWindow::goHome() { + KURL url; + url.setPath( QDir::homeDirPath() ); + mFileViewController->setDirURL(url); +} + + +void MainWindow::renameFile() { + KURL url; + if (mFileViewController->isVisible()) { + KURL::List list = mFileViewController->selectedURLs(); + Q_ASSERT(list.count()==1); + if (list.count()!=1) return; + url = list.first(); + } else { + url = mDocument->url(); + } + FileOperation::rename(url, this, this, SLOT(slotRenamed(const QString &))); +} + + +void MainWindow::copyFiles() { + KURL::List list; + if (mFileViewController->isVisible()) { + list = mFileViewController->selectedURLs(); + } else { + list << mDocument->url(); + } + FileOperation::copyTo(list, this); +} + +void MainWindow::linkFiles() { + KURL::List list; + if (mFileViewController->isVisible()) { + list = mFileViewController->selectedURLs(); + } else { + list << mDocument->url(); + } + FileOperation::linkTo(list, this); +} + + +void MainWindow::moveFiles() { + KURL::List list; + if (mFileViewController->isVisible()) { + list = mFileViewController->selectedURLs(); + } else { + list << mDocument->url(); + } + FileOperation::moveTo(list, this); +} + + +void MainWindow::deleteFiles() { + KURL::List list; + if (mFileViewController->isVisible()) { + list = mFileViewController->selectedURLs(); + } else { + list << mDocument->url(); + } + FileOperation::del(list, this); +} + + +void MainWindow::makeDir() { + FileOperation::makeDir(mFileViewController->dirURL(), this); +} + + +void MainWindow::showFileProperties() { + if (mFileViewController->isVisible()) { + const KFileItemList* list = mFileViewController->currentFileView()->selectedItems(); + if (list->count() > 0) { + (void)new KPropertiesDialog(*list, this); + } else { + (void)new KPropertiesDialog(mFileViewController->dirURL(), this); + } + } else { + (void)new KPropertiesDialog(mDocument->url(), this); + } +} + + +void MainWindow::rotateLeft() { + mDocument->transform(ImageUtils::ROT_270); +} + +void MainWindow::rotateRight() { + mDocument->transform(ImageUtils::ROT_90); +} + +void MainWindow::mirror() { + mDocument->transform(ImageUtils::HFLIP); +} + +void MainWindow::flip() { + mDocument->transform(ImageUtils::VFLIP); +} + +void MainWindow::showFileDialog() { + KURL url=KFileDialog::getOpenURL(); + if (!url.isValid()) return; + + openURL(url); +} + + +void MainWindow::printFile() { + KPrinter printer; + + printer.setDocName(mDocument->filename()); + const KAboutData* pAbout = KApplication::kApplication()->aboutData(); + QString nm = pAbout->appName(); + nm += "-"; + nm += pAbout->version(); + printer.setCreator( nm ); + + KPrinter::addDialogPage( new PrintDialogPage( mDocument, this, " page")); + + if (printer.setup(this, QString::null, true)) { + mDocument->print(&printer); + } +} + + +//----------------------------------------------------------------------- +// +// Private slots +// +//----------------------------------------------------------------------- +void MainWindow::openFileViewControllerContextMenu(const QPoint& pos, bool onItem) { + int selectionSize; + ExternalToolContext* externalToolContext; + + if (onItem) { + const KFileItemList* items = mFileViewController->currentFileView()->selectedItems(); + selectionSize = items->count(); + externalToolContext = + ExternalToolManager::instance()->createContext(this, items); + } else { + selectionSize = 0; + externalToolContext = + ExternalToolManager::instance()->createContext(this, mFileViewController->dirURL()); + } + + QPopupMenu menu(this); + + menu.insertItem( + i18n("External Tools"), externalToolContext->popupMenu()); + + actionCollection()->action("view_sort")->plug(&menu); + mGoUp->plug(&menu); + + menu.insertItem(SmallIcon("folder_new"), i18n("New Folder..."), this, SLOT(makeDir())); + + menu.insertSeparator(); + + if (selectionSize==1) { + mRenameFile->plug(&menu); + } + + if (selectionSize>=1) { + mCopyFiles->plug(&menu); + mMoveFiles->plug(&menu); + mLinkFiles->plug(&menu); + mDeleteFiles->plug(&menu); + menu.insertSeparator(); + } + + mShowFileProperties->plug(&menu); + menu.exec(pos); +} + + +void MainWindow::slotImageLoading() { + if (FullScreenConfig::showBusyPtr() || !mToggleFullScreen->isChecked()) { + kapp->setOverrideCursor(KCursor::workingCursor(), true /* replace */); + } +} + + +void MainWindow::slotImageLoaded() { + // Reciproc of slotImageLoading + if (FullScreenConfig::showBusyPtr() || !mToggleFullScreen->isChecked()) { + kapp->restoreOverrideCursor(); + } + updateStatusInfo(); + updateImageActions(); + updateLocationURL(); + if (mToggleFullScreen->isChecked()) { + updateFullScreenLabel(); + } +} + + +void MainWindow::hideToolBars() { + QPtrListIterator<KToolBar> it=toolBarIterator(); + KToolBar* bar; + + for(;it.current()!=0L; ++it) { + bar=it.current(); + if (bar->area()) { + bar->area()->hide(); + } else { + bar->hide(); + } + } +} + + +void MainWindow::showToolBars() { + QPtrListIterator<KToolBar> it=toolBarIterator(); + + KToolBar* bar; + + for(;it.current()!=0L; ++it) { + bar=it.current(); + if (bar->area()) { + bar->area()->show(); + } else { + bar->show(); + } + } +} + + +void MainWindow::toggleFullScreen() { + if (mToggleFullScreen->isChecked()) { + saveMainWindowSettings(KGlobal::config(), "MainWindow"); + + showFullScreen(); + menuBar()->hide(); + statusBar()->hide(); + + /* Hide toolbar + * If the toolbar is docked we hide the DockArea to avoid + * having a one pixel band remaining + * For the same reason, we hide all the empty DockAreas + * + * NOTE: This does not work really well if the toolbar is in + * the left or right dock area. + */ + hideToolBars(); + if (leftDock()->isEmpty()) leftDock()->hide(); + if (rightDock()->isEmpty()) rightDock()->hide(); + if (topDock()->isEmpty()) topDock()->hide(); + if (bottomDock()->isEmpty()) bottomDock()->hide(); + + if (mSwitchToBrowseMode->isChecked()) { + mImageViewController->widget()->reparent(mViewModeWidget, QPoint(0,0)); + mCentralStack->raiseWidget(StackIDView); + } + updateFullScreenLabel(); + mImageViewController->setFullScreen(true); + mImageViewController->setFocus(); + } else { + // Stop the slideshow if it's running + if (mSlideShow->isRunning()) { + mToggleSlideShow->activate(); + } + + // Make sure the file view points to the right URL, it might not be the + // case if we are getting out of a slideshow + mFileViewController->setDirURL(mDocument->url().upURL()); + mFileViewController->setFileNameToSelect(mDocument->url().fileName()); + + showNormal(); + menuBar()->show(); + + showToolBars(); + leftDock()->show(); + rightDock()->show(); + topDock()->show(); + bottomDock()->show(); + + statusBar()->show(); + mImageViewController->setFullScreen(false); + + if (mSwitchToBrowseMode->isChecked()) { + mImageDock->setWidget(mImageViewController->widget()); + mCentralStack->raiseWidget(StackIDBrowse); + mFileViewController->setFocus(); + } + } +} + + +void MainWindow::toggleSlideShow() { + if (mSlideShow->isRunning()) { + mSlideShow->stop(); + return; + } + + KURL::List list; + KFileItemListIterator it( *mFileViewController->currentFileView()->items() ); + for ( ; it.current(); ++it ) { + KFileItem* item=it.current(); + if (!item->isDir() && !Archive::fileItemIsArchive(item)) { + list.append(item->url()); + } + } + if (list.count()==0) { + return; + } + + if (SlideShowConfig::fullscreen() && !mToggleFullScreen->isChecked()) { + mToggleFullScreen->activate(); + } + mSlideShow->start(list); + } + + +void MainWindow::slotSlideShowChanged(bool running) { + mToggleSlideShow->setIcon(running ? "slideshow_pause" : "slideshow_play"); +} + + +void MainWindow::showConfigDialog() { +#ifdef GV_HAVE_KIPI + if (!mPluginLoader) loadPlugins(); + ConfigDialog dialog(this, mPluginLoader); +#else + ConfigDialog dialog(this, 0); +#endif + connect(&dialog, SIGNAL(settingsChanged()), + mSlideShow, SLOT(slotSettingsChanged()) ); + connect(&dialog, SIGNAL(settingsChanged()), + mImageViewController, SLOT(updateFromSettings()) ); + connect(&dialog, SIGNAL(settingsChanged()), + mFileViewController, SLOT(updateFromSettings()) ); + dialog.exec(); +} + + +void MainWindow::showExternalToolDialog() { + ExternalToolDialog* dialog=new ExternalToolDialog(this); + dialog->show(); +} + + +void MainWindow::showKeyDialog() { + KKeyDialog dialog(true, this); + dialog.insert(actionCollection()); + dialog.configure(true); +} + + +void MainWindow::showToolBarDialog() { + saveMainWindowSettings(KGlobal::config(), "MainWindow"); + KEditToolbar dlg(actionCollection()); + connect(&dlg,SIGNAL(newToolbarConfig()),this,SLOT(applyMainWindowSettings())); + dlg.exec(); +} + +void MainWindow::applyMainWindowSettings() { + createGUI(); + KMainWindow::applyMainWindowSettings(KGlobal::config(), "MainWindow"); +} + + + +void MainWindow::escapePressed() { + if (mToggleFullScreen->isChecked()) { + mToggleFullScreen->activate(); + } +} + + +void MainWindow::slotDirRenamed(const KURL& oldURL, const KURL& newURL) { + LOG(oldURL.prettyURL(0,KURL::StripFileProtocol) << " to " << newURL.prettyURL(0,KURL::StripFileProtocol)); + + KURL url(mFileViewController->dirURL()); + if (!oldURL.isParentOf(url) ) { + LOG(oldURL.prettyURL() << " is not a parent of " << url.prettyURL()); + return; + } + + QString oldPath=oldURL.path(); + LOG("current path: " << url.path() ); + QString path=newURL.path() + url.path().mid(oldPath.length()); + LOG("new path: " << path); + url.setPath(path); + mFileViewController->setDirURL(url); +} + + +void MainWindow::slotGo() { + KURL url(mURLEditCompletion->replacedPath(mURLEdit->currentText())); + LOG(url.prettyURL()); + openURL(url); + mFileViewController->setFocus(); +} + +void MainWindow::slotShownFileItemRefreshed(const KFileItem*) { + LOG(""); + mDocument->reload(); +} + + +void MainWindow::slotToggleCentralStack() { + LOG(""); + if (mSwitchToBrowseMode->isChecked()) { + mImageDock->setWidget(mImageViewController->widget()); + mCentralStack->raiseWidget(StackIDBrowse); + mFileViewController->setSilentMode( false ); + // force re-reading the directory to show the error + if( mFileViewController->lastURLError()) mFileViewController->retryURL(); + } else { + mImageViewController->widget()->reparent(mViewModeWidget, QPoint(0,0)); + mCentralStack->raiseWidget(StackIDView); + mFileViewController->setSilentMode( true ); + } + + // Make sure the window list actions are disabled if we are in view mode, + // otherwise weird things happens when we go back to browse mode + QPtrListIterator<KAction> it(mWindowListActions); + for (;it.current(); ++it) { + it.current()->setEnabled(mSwitchToBrowseMode->isChecked()); + } + updateImageActions(); + updateLocationURL(); +} + + +void MainWindow::resetDockWidgets() { + int answer=KMessageBox::warningContinueCancel(this, + i18n("You are about to revert the window setup to factory defaults, are you sure?"), + QString::null /* caption */, + i18n("Reset")); + if (answer==KMessageBox::Cancel) return; + + mFolderDock->undock(); + mImageDock->undock(); + mMetaDock->undock(); + + mFolderDock->manualDock(mFileDock, KDockWidget::DockLeft, 4000); + mImageDock->manualDock(mFolderDock, KDockWidget::DockBottom, 3734); + mMetaDock->manualDock(mImageDock, KDockWidget::DockBottom, 8560); +} + + +/** + * Display a hint as a temporary message in the status bar + */ +void MainWindow::showHint(const QString& hint) { + mSBHintLabel->setText(hint); + + mSBHintLabel->show(); + mHintTimer->start(HINT_TIMEOUT, true); +} + + +//----------------------------------------------------------------------- +// +// GUI +// +//----------------------------------------------------------------------- +void MainWindow::updateStatusInfo() { + QStringList tokens; + + if ( KProtocolInfo::supportsListing(mFileViewController->url()) ) { + int pos = mFileViewController->shownFilePosition(); + uint count = mFileViewController->fileCount(); + if (count > 0) { + tokens << i18n("%1/%2").arg(pos+1).arg(count); + } else { + tokens << i18n("No images"); + } + } + + QString filename = mDocument->filename(); + + QSize size = mDocument->image().size(); + if (!size.isEmpty()) { + tokens << i18n("%1 x %2 pixels").arg(size.width()).arg(size.height()); + } + + mSBDetailLabel->setText(tokens.join(" - ")); + setCaption(filename); +} + + +void MainWindow::updateImageActions() { + mToggleSlideShow->setEnabled(mDocument->urlKind()!=MimeTypeUtils::KIND_UNKNOWN); + + bool imageActionsEnabled = !mDocument->isNull(); + + mRotateLeft->setEnabled(imageActionsEnabled); + mRotateRight->setEnabled(imageActionsEnabled); + mMirror->setEnabled(imageActionsEnabled); + mFlip->setEnabled(imageActionsEnabled); + mSaveFile->setEnabled(imageActionsEnabled); + mSaveFileAs->setEnabled(imageActionsEnabled); + mFilePrint->setEnabled(imageActionsEnabled); + mReload->setEnabled(imageActionsEnabled); + + bool fileActionsEnabled = + imageActionsEnabled + || (mFileViewController->isVisible() && mFileViewController->selectionSize()>0); + + mRenameFile->setEnabled(fileActionsEnabled); + mCopyFiles->setEnabled(fileActionsEnabled); + mMoveFiles->setEnabled(fileActionsEnabled); + mLinkFiles->setEnabled(fileActionsEnabled); + mDeleteFiles->setEnabled(fileActionsEnabled); + mShowFileProperties->setEnabled(fileActionsEnabled); +} + + +/** + * This method creates all the widgets. Interactions between them and with + * actions are created in createObjectInteractions + */ +void MainWindow::createWidgets() { + KConfig* config=KGlobal::config(); + + mCentralStack=new QWidgetStack(this); + setCentralWidget(mCentralStack); + + mDockArea=new KDockArea(mCentralStack); + mCentralStack->addWidget(mDockArea, StackIDBrowse); + mDockArea->manager()->setSplitterHighResolution(true); + mDockArea->manager()->setSplitterOpaqueResize(true); + + mViewModeWidget=new QWidget(mCentralStack); + QVBoxLayout* layout=new QVBoxLayout(mViewModeWidget); + layout->setAutoAdd(true); + mCentralStack->addWidget(mViewModeWidget); + + // Status bar + mSBDetailLabel=new QLabel("", statusBar()); + + mSBHintLabel=new TruncatedTextLabel(statusBar()); + QFont font=mSBHintLabel->font(); + font.setItalic(true); + mSBHintLabel->setFont(font); + + statusBar()->addWidget(mSBDetailLabel, 0); + statusBar()->addWidget(mSBHintLabel, 1); + mHintTimer=new QTimer(this); + connect(mHintTimer, SIGNAL(timeout()), + mSBHintLabel, SLOT(clear()) ); + + // Pixmap widget + mImageDock = mDockArea->createDockWidget("Image",SmallIcon("gwenview"),NULL,i18n("Image")); + mImageViewController=new ImageViewController(mImageDock, mDocument, actionCollection()); + mImageDock->setWidget(mImageViewController->widget()); + connect(mImageViewController, SIGNAL(requestHintDisplay(const QString&)), + this, SLOT(showHint(const QString&)) ); + + // Folder widget + mFolderDock = mDockArea->createDockWidget("Folders",SmallIcon("folder_open"),NULL,i18n("Folders")); + VTabWidget* vtabWidget=new VTabWidget(mFolderDock); + mFolderDock->setWidget(vtabWidget); + + mDirViewController=new DirViewController(vtabWidget); + vtabWidget->addTab(mDirViewController->widget(), SmallIcon("folder"), i18n("Folders")); + + mBookmarkViewController=new BookmarkViewController(vtabWidget); + vtabWidget->addTab(mBookmarkViewController->widget(), SmallIcon("bookmark"), i18n("Bookmarks")); + + // File widget + mFileDock = mDockArea->createDockWidget("Files",SmallIcon("image"),NULL,i18n("Files")); + mFileViewController=new FileViewController(this, actionCollection()); + mFileDock->setWidget(mFileViewController); + mFileDock->setEnableDocking(KDockWidget::DockNone); + mDockArea->setMainDockWidget(mFileDock); + + // Meta info edit widget + mMetaDock = mDockArea->createDockWidget("File Attributes", SmallIcon("info"),NULL, + i18n("Image Comment")); + mMetaEdit = new MetaEdit(mMetaDock, mDocument); + mMetaDock->setWidget(mMetaEdit); + + // Slide show controller (not really a widget) + mSlideShow=new SlideShow(mDocument); + + // Default position on desktop + setGeometry(20,20,720,520); + + // Default dock config + // (The "magic numbers" were found by adjusting the layout from within the + // app and looking at the result in the configuration file) + mFolderDock->manualDock(mFileDock, KDockWidget::DockLeft, 4000); + mImageDock->manualDock(mFolderDock, KDockWidget::DockBottom, 3734); + mMetaDock->manualDock(mImageDock, KDockWidget::DockBottom, 8560); + + // Load dock config if up to date + if (config->hasGroup(CONFIG_DOCK_GROUP)) { + config->setGroup(CONFIG_DOCK_GROUP); + if (config->readNumEntry(CONFIG_GWENVIEW_DOCK_VERSION, 1)==GWENVIEW_DOCK_VERSION) { + mDockArea->readDockConfig(config,CONFIG_DOCK_GROUP); + } else { + KMessageBox::sorry(this, i18n( + "<qt><b>Configuration update</b><br>" + "Due to some changes in the dock behavior, your old dock configuration has been discarded. " + "Please adjust your docks again.</qt>") + ); + // Store the default dock config and create the + // GWENVIEW_DOCK_VERSION entry + mDockArea->writeDockConfig(config,CONFIG_DOCK_GROUP); + config->writeEntry(CONFIG_GWENVIEW_DOCK_VERSION, GWENVIEW_DOCK_VERSION); + config->sync(); + } + } else { + // There was no dock config, lets create the GWENVIEW_DOCK_VERSION entry + config->setGroup(CONFIG_DOCK_GROUP); + config->writeEntry(CONFIG_GWENVIEW_DOCK_VERSION, GWENVIEW_DOCK_VERSION); + config->sync(); + } + + // Load config + Cache::instance()->readConfig(config,CONFIG_CACHE_GROUP); +} + + +/** + * This method creates all the actions Interactions between them and with + * widgets are created in createObjectInteractions + */ +void MainWindow::createActions() { + // Stack + mSwitchToBrowseMode=new KRadioAction(i18n("Browse"), "folder_image", CTRL + Key_Return, this, SLOT(slotToggleCentralStack()), actionCollection(), "switch_to_browse_mode"); + mSwitchToBrowseMode->setExclusiveGroup("centralStackMode"); + mSwitchToBrowseMode->setChecked(true); + mSwitchToViewMode=new KRadioAction(i18n("View Image"), "image", 0, this, SLOT(slotToggleCentralStack()), actionCollection(), "switch_to_view_mode"); + mSwitchToViewMode->setExclusiveGroup("centralStackMode"); + + // File + KStdAction::open(this,SLOT(showFileDialog()),actionCollection() ); + mSaveFile=KStdAction::save(mDocument,SLOT(save()),actionCollection() ); + mSaveFileAs=KStdAction::saveAs(mDocument,SLOT(saveAs()),actionCollection() ); + mFilePrint = KStdAction::print(this, SLOT(printFile()), actionCollection()); + mRenameFile=new KAction(i18n("&Rename..."),Key_F2,this,SLOT(renameFile()),actionCollection(),"file_rename"); + mCopyFiles=new KAction(i18n("&Copy To..."),Key_F7,this,SLOT(copyFiles()),actionCollection(),"file_copy"); + mMoveFiles=new KAction(i18n("&Move To..."),Key_F8,this,SLOT(moveFiles()),actionCollection(),"file_move"); + mLinkFiles=new KAction(i18n("&Link To..."),Key_F9,this,SLOT(linkFiles()),actionCollection(),"file_link"); + mDeleteFiles=new KAction(i18n("&Delete"),"editdelete",Key_Delete,this,SLOT(deleteFiles()),actionCollection(),"file_delete"); + mShowFileProperties=new KAction(i18n("Properties"),0,this,SLOT(showFileProperties()),actionCollection(),"file_properties"); + KStdAction::quit( kapp, SLOT (closeAllWindows()), actionCollection() ); + + // Edit + mRotateLeft=new KAction(i18n("Rotate &Left"),"rotate_left",CTRL + Key_L, this, SLOT(rotateLeft()),actionCollection(),"rotate_left"); + mRotateRight=new KAction(i18n("Rotate &Right"),"rotate_right",CTRL + Key_R, this, SLOT(rotateRight()),actionCollection(),"rotate_right"); + mMirror=new KAction(i18n("&Mirror"),"mirror",0, this, SLOT(mirror()),actionCollection(),"mirror"); + mFlip=new KAction(i18n("&Flip"),"flip",0, this, SLOT(flip()),actionCollection(),"flip"); + + // View + mReload=new KAction(i18n("Reload"), "reload", Key_F5, mDocument, SLOT(reload()), actionCollection(), "reload"); + mReload->setEnabled(false); + + mToggleFullScreen= KStdAction::fullScreen(this,SLOT(toggleFullScreen()),actionCollection(),0); + mToggleSlideShow=new KAction(i18n("Slide Show"),"slideshow_play",0,this,SLOT(toggleSlideShow()),actionCollection(),"slideshow"); + mFullScreenLabelAction=new KToolBarLabelAction("", 0, 0, 0, actionCollection(), "fullscreen_label"); + + // Go + mGoUp=new KToolBarPopupAction(i18n("Up"), "up", ALT + Key_Up, this, SLOT(goUp()), actionCollection(), "go_up"); + new KAction( i18n( "Home" ), "gohome", KStdAccel::shortcut(KStdAccel::Home), this, SLOT(goHome()), actionCollection(), "go_home"); + + // Window + mResetDockWidgets = new KAction(i18n("Reset"), 0, this, SLOT(resetDockWidgets()), actionCollection(), "reset_dock_widgets"); + + // Settings + mShowConfigDialog= + KStdAction::preferences(this, SLOT(showConfigDialog()), actionCollection() ); + mShowKeyDialog= + KStdAction::keyBindings(this, SLOT(showKeyDialog()), actionCollection() ); + (void)new KAction(i18n("Configure External Tools..."), "configure", 0, + this, SLOT(showExternalToolDialog()), actionCollection(), "configure_tools"); + (void)KStdAction::configureToolbars( + this, SLOT(showToolBarDialog()), actionCollection() ); + + actionCollection()->readShortcutSettings(); +} + + +/** + * This method creates the interactions between objects, when it's called, all + * widgets and actions have already been created + */ +void MainWindow::createObjectInteractions() { + // Actions in image view + { + KActionPtrList actions; + actions + << mToggleFullScreen + << mToggleSlideShow + << mFileViewController->selectPrevious() + << mFileViewController->selectNext() + << mRotateLeft + << mRotateRight + << mFullScreenLabelAction + ; + mImageViewController->setFullScreenCommonActions(actions); + } + + { + KActionPtrList actions; + actions + << mFileViewController->selectPrevious() + << mFileViewController->selectNext() + << mReload + ; + mImageViewController->setNormalCommonActions(actions); + } + + { + KActionPtrList actions; + actions + << actionCollection()->action("view_zoom_in") + << actionCollection()->action("view_zoom_to") + << actionCollection()->action("view_zoom_out") + << mRotateLeft + << mRotateRight + ; + mImageViewController->setImageViewActions(actions); + } + + // Make sure file actions are correctly updated + connect(mFileViewController, SIGNAL(selectionChanged()), + this, SLOT(updateImageActions()) ); + + connect(mFileViewController, SIGNAL(requestContextMenu(const QPoint&, bool)), + this, SLOT(openFileViewControllerContextMenu(const QPoint&, bool)) ); + + // Bookmarks + QString file = locate( "data", "kfile/bookmarks.xml" ); + if (file.isEmpty()) { + file = locateLocal( "data", "kfile/bookmarks.xml" ); + } + + KBookmarkManager* manager=KBookmarkManager::managerForFile(file,false); + manager->setUpdate(true); + manager->setShowNSBookmarks(false); + mBookmarkViewController->init(manager); + + BookmarkOwner* bookmarkOwner=new BookmarkOwner(this); + + KActionMenu* bookmark=new KActionMenu(i18n( "&Bookmarks" ), "bookmark", actionCollection(), "bookmarks" ); + new KBookmarkMenu(manager, bookmarkOwner, bookmark->popupMenu(), 0, true); + + connect(bookmarkOwner,SIGNAL(openURL(const KURL&)), + mFileViewController,SLOT(setDirURL(const KURL&)) ); + + connect(mFileViewController,SIGNAL(directoryChanged(const KURL&)), + bookmarkOwner,SLOT(setURL(const KURL&)) ); +} + + +void MainWindow::createHideShowAction(KDockWidget* dock) { + QString caption; + if (dock->mayBeHide()) { + caption=i18n("Hide %1").arg(dock->caption()); + } else { + caption=i18n("Show %1").arg(dock->caption()); + } + + KAction* action=new KAction(caption, 0, dock, SLOT(changeHideShowState()), (QObject*)0 ); + if (dock->icon()) { + action->setIconSet( QIconSet(*dock->icon()) ); + } + mWindowListActions.append(action); +} + + +void MainWindow::updateWindowActions() { + unplugActionList("winlist"); + mWindowListActions.clear(); + createHideShowAction(mFolderDock); + createHideShowAction(mImageDock); + createHideShowAction(mMetaDock); + plugActionList("winlist", mWindowListActions); +} + + +void MainWindow::createConnections() { + connect(mGoUp->popupMenu(), SIGNAL(aboutToShow()), + this,SLOT(fillGoUpMenu())); + + connect(mGoUp->popupMenu(), SIGNAL(activated(int)), + this,SLOT(goUpTo(int))); + + // Slideshow connections + connect( mSlideShow, SIGNAL(nextURL(const KURL&)), + SLOT( openURL(const KURL&)) ); + connect( mSlideShow, SIGNAL( stateChanged(bool)), + SLOT( slotSlideShowChanged(bool)) ); + + // Dir view connections + connect(mDirViewController, SIGNAL(urlChanged(const KURL&)), + mFileViewController, SLOT(setDirURL(const KURL&)) ); + connect(mDirViewController, SIGNAL(urlRenamed(const KURL&, const KURL&)), + this, SLOT(slotDirRenamed(const KURL&, const KURL&)) ); + + // Bookmark view connections + connect(mBookmarkViewController, SIGNAL(openURL(const KURL&)), + mFileViewController,SLOT(setDirURL(const KURL&)) ); + connect(mFileViewController, SIGNAL(directoryChanged(const KURL&)), + mBookmarkViewController, SLOT(setURL(const KURL&)) ); + + // Pixmap view connections + connect(mImageViewController, SIGNAL(selectPrevious()), + mFileViewController, SLOT(slotSelectPrevious()) ); + connect(mImageViewController, SIGNAL(selectNext()), + mFileViewController, SLOT(slotSelectNext()) ); + connect(mImageViewController, SIGNAL(doubleClicked()), + mToggleFullScreen, SLOT(activate()) ); + + // File view connections + connect(mFileViewController,SIGNAL(urlChanged(const KURL&)), + mDocument,SLOT(setURL(const KURL&)) ); + connect(mFileViewController,SIGNAL(directoryChanged(const KURL&)), + this,SLOT(slotDirURLChanged(const KURL&)) ); + connect(mFileViewController,SIGNAL(directoryChanged(const KURL&)), + mDirViewController,SLOT(setURL(const KURL&)) ); + connect(mFileViewController,SIGNAL(directoryChanged(const KURL&)), + mHistory,SLOT(addURLToHistory(const KURL&)) ); + + connect(mFileViewController,SIGNAL(completed()), + this,SLOT(updateStatusInfo()) ); + connect(mFileViewController,SIGNAL(canceled()), + this,SLOT(updateStatusInfo()) ); + connect(mFileViewController,SIGNAL(imageDoubleClicked()), + mToggleFullScreen,SLOT(activate()) ); + connect(mFileViewController,SIGNAL(shownFileItemRefreshed(const KFileItem*)), + this,SLOT(slotShownFileItemRefreshed(const KFileItem*)) ); + connect(mFileViewController,SIGNAL(sortingChanged()), + this, SLOT(updateStatusInfo()) ); + + // History connections + connect(mHistory, SIGNAL(urlChanged(const KURL&)), + mFileViewController, SLOT(setDirURL(const KURL&)) ); + + // Document connections + connect(mDocument,SIGNAL(loading()), + this,SLOT(slotImageLoading()) ); + connect(mDocument,SIGNAL(loaded(const KURL&)), + this,SLOT(slotImageLoaded()) ); + connect(mDocument,SIGNAL(saved(const KURL&)), + mFileViewController,SLOT(updateThumbnail(const KURL&)) ); + connect(mDocument,SIGNAL(reloaded(const KURL&)), + mFileViewController,SLOT(updateThumbnail(const KURL&)) ); + + // Location bar + connect(mURLEdit, SIGNAL(activated(const QString &)), + this,SLOT(slotGo()) ); + connect(mURLEdit, SIGNAL(returnPressed()), + this,SLOT(slotGo()) ); + + // Non configurable stop-fullscreen accel + QAccel* accel=new QAccel(this); + accel->connectItem(accel->insertItem(Key_Escape),this,SLOT(escapePressed())); + + // Dock related + connect(mDockArea->manager(), SIGNAL(change()), + this, SLOT(updateWindowActions()) ); + + // Plugin menu + QPopupMenu *popup = static_cast<QPopupMenu*>( + factory()->container( "plugins", this)); + connect(popup, SIGNAL(aboutToShow()), this, SLOT(loadPlugins()) ); +} + + +void MainWindow::createLocationToolBar() { + // URL Combo + mURLEdit=new KHistoryCombo(); + mURLEdit->setDuplicatesEnabled(false); + mURLEdit->setPixmapProvider(new KURLPixmapProvider); + mURLEdit->setMaxCount(HISTORY_MAX_COUNT); + mURLEdit->setHistoryItems(MiscConfig::history()); + + // Do not let the combobox get wider than available space, as this would + // hide the toolbuttons after it + mURLEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + + // Avoid stealing focus + mURLEdit->setFocusPolicy(ClickFocus); + + mURLEditCompletion=new KURLCompletion(); + + mURLEdit->setCompletionObject(mURLEditCompletion); + mURLEdit->setAutoDeleteCompletionObject(true); + + KWidgetAction* comboAction=new KWidgetAction( mURLEdit, i18n("Location Bar"), 0, + 0, 0, actionCollection(), "location_url"); + comboAction->setShortcutConfigurable(false); + comboAction->setAutoSized(true); + + // Clear button + (void)new KAction( i18n("Clear Location Bar"), + QApplication::reverseLayout()?"clear_left" : "locationbar_erase", + 0, this, SLOT(clearLocationLabel()), actionCollection(), "clear_location"); + + // URL Label + KToolBarLabelAction* locationAction = new KToolBarLabelAction(i18n("L&ocation:"), + Key_F6, this, SLOT( activateLocationLabel()), actionCollection(), "location_label"); + locationAction->setBuddy(mURLEdit); + + // Go button + (void)new KAction(i18n("Go"), "key_enter", 0, this, SLOT(slotGo()), actionCollection(), "location_go"); + +} + + +void MainWindow::clearLocationLabel() { + mURLEdit->clearEdit(); + mURLEdit->setFocus(); +} + + +void MainWindow::activateLocationLabel() { + mURLEdit->setFocus(); + mURLEdit->lineEdit()->selectAll(); +} + + +#ifdef GV_HAVE_KIPI +void MainWindow::loadPlugins() { + // Already done + if (mPluginLoader) return; + + LOG("Load plugins"); + // Sets up the plugin interface, and load the plugins + KIPIInterface* interface = new KIPIInterface(this, mFileViewController); + mPluginLoader = new KIPI::PluginLoader(QStringList(), interface ); + connect( mPluginLoader, SIGNAL( replug() ), this, SLOT( slotReplug() ) ); + mPluginLoader->loadPlugins(); +} + + +// Helper class for slotReplug(), gcc does not want to instantiate templates +// with local classes, so this is declared outside of slotReplug() +struct MenuInfo { + QString mName; + QPtrList<KAction> mActions; + MenuInfo() {} + MenuInfo(const QString& name) : mName(name) {} +}; + +void MainWindow::slotReplug() { + typedef QMap<KIPI::Category, MenuInfo> CategoryMap; + CategoryMap categoryMap; + categoryMap[KIPI::IMAGESPLUGIN]=MenuInfo("image_actions"); + categoryMap[KIPI::EFFECTSPLUGIN]=MenuInfo("effect_actions"); + categoryMap[KIPI::TOOLSPLUGIN]=MenuInfo("tool_actions"); + categoryMap[KIPI::IMPORTPLUGIN]=MenuInfo("import_actions"); + categoryMap[KIPI::EXPORTPLUGIN]=MenuInfo("export_actions"); + categoryMap[KIPI::BATCHPLUGIN]=MenuInfo("batch_actions"); + categoryMap[KIPI::COLLECTIONSPLUGIN]=MenuInfo("collection_actions"); + + // Fill the mActions + KIPI::PluginLoader::PluginList pluginList=mPluginLoader->pluginList(); + KIPI::PluginLoader::PluginList::ConstIterator it(pluginList.begin()); + KIPI::PluginLoader::PluginList::ConstIterator itEnd(pluginList.end()); + for( ; it!=itEnd; ++it ) { + if (!(*it)->shouldLoad()) continue; + KIPI::Plugin* plugin = (*it)->plugin(); + Q_ASSERT(plugin); + if (!plugin) continue; + + plugin->setup(this); + KActionPtrList actions = plugin->actions(); + KActionPtrList::ConstIterator actionIt=actions.begin(), end=actions.end(); + for (; actionIt!=end; ++actionIt) { + KIPI::Category category = plugin->category(*actionIt); + + if (!categoryMap.contains(category)) { + kdWarning() << "Unknown category '" << category << "'\n"; + continue; + } + + categoryMap[category].mActions.append(*actionIt); + } + plugin->actionCollection()->readShortcutSettings(); + } + + // Create a dummy "no plugin" action list + KAction* noPlugin=new KAction(i18n("No Plugin"), 0, 0, 0, actionCollection(), "no_plugin"); + noPlugin->setShortcutConfigurable(false); + noPlugin->setEnabled(false); + QPtrList<KAction> noPluginList; + noPluginList.append(noPlugin); + + // Fill the menu + CategoryMap::ConstIterator catIt=categoryMap.begin(), catItEnd=categoryMap.end(); + for (; catIt!=catItEnd; ++catIt) { + const MenuInfo& info=catIt.data(); + unplugActionList(info.mName); + if (info.mActions.count()>0) { + plugActionList(info.mName, info.mActions); + } else { + plugActionList(info.mName, noPluginList); + } + } +} +#else +void MainWindow::loadPlugins() { + // Create a dummy "no KIPI" action list + KAction* noPlugin=new KAction(i18n("No KIPI support"), 0, 0, 0, actionCollection(), "no_plugin"); + noPlugin->setShortcutConfigurable(false); + noPlugin->setEnabled(false); + QPtrList<KAction> noPluginList; + noPluginList.append(noPlugin); + + QStringList lst; + lst << "image_actions" + << "effect_actions" + << "tool_actions" + << "import_actions" + << "export_actions" + << "batch_actions" + << "collection_actions"; + + // Fill the menu + QStringList::ConstIterator catIt=lst.begin(), catItEnd=lst.end(); + for (; catIt!=catItEnd; ++catIt) { + plugActionList(*catIt, noPluginList); + } +} + +void MainWindow::slotReplug() { +} +#endif + + +} // namespace diff --git a/src/app/mainwindow.h b/src/app/mainwindow.h new file mode 100644 index 0000000..a61d8ea --- /dev/null +++ b/src/app/mainwindow.h @@ -0,0 +1,222 @@ +// 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 MAINWINDOW_H +#define MAINWINDOW_H + +// Qt +#include <qptrlist.h> + +// KDE +#include <kmainwindow.h> +#include <kurl.h> + +// Local +#include "config.h" +#ifdef GV_HAVE_KIPI +#include <libkipi/pluginloader.h> +#endif + +class QLabel; +class QTimer; +class QWidgetStack; + +class KAction; +class KDockArea; +class KDockWidget; +class KFileItem; +class KHistoryCombo; +class KListView; +class KRadioAction; +class KToggleAction; +class KToolBarLabelAction; +class KToolBarPopupAction; +class KURLCompletion; + +namespace Gwenview { +class BookmarkViewController; +class DirViewController; +class Document; +class FileViewController; +class History; +class MetaEdit; +class ImageViewController; +class SlideShow; + + +class MainWindow : public KMainWindow { +Q_OBJECT +public: + MainWindow(); + void setFullScreen(bool); + FileViewController* fileViewController() const { return mFileViewController; } + +public slots: + void openURL(const KURL&); + +protected: + bool queryClose(); + virtual void saveProperties( KConfig* ); + virtual void readProperties( KConfig* ); + +private: + QWidgetStack* mCentralStack; + QWidget* mViewModeWidget; + KDockArea* mDockArea; + KDockWidget* mFolderDock; + KDockWidget* mFileDock; + KDockWidget* mImageDock; + KDockWidget* mMetaDock; + QLabel* mSBDetailLabel; + QLabel* mSBHintLabel; + QTimer* mHintTimer; + + FileViewController* mFileViewController; + DirViewController* mDirViewController; + BookmarkViewController* mBookmarkViewController; + ImageViewController* mImageViewController; + MetaEdit *mMetaEdit; + + Document* mDocument; + History* mHistory; + SlideShow* mSlideShow; + + KRadioAction* mSwitchToBrowseMode; + KRadioAction* mSwitchToViewMode; + KToggleAction* mToggleFullScreen; + KToolBarLabelAction* mFullScreenLabelAction; + KAction* mRenameFile; + KAction* mCopyFiles; + KAction* mMoveFiles; + KAction* mLinkFiles; + KAction* mDeleteFiles; + KAction* mShowConfigDialog; + KAction* mShowKeyDialog; + KAction* mReload; + KToolBarPopupAction* mGoUp; + KAction* mShowFileProperties; + KAction* mToggleSlideShow; + KAction* mRotateLeft; + KAction* mRotateRight; + KAction* mMirror; + KAction* mFlip; + KAction* mSaveFile; + KAction* mSaveFileAs; + KAction* mFilePrint; + KAction* mResetDockWidgets; + + KHistoryCombo* mURLEdit; + KURLCompletion* mURLEditCompletion; + QPtrList<KAction> mWindowListActions; + +#ifdef GV_HAVE_KIPI + KIPI::PluginLoader* mPluginLoader; +#endif + + void hideToolBars(); + void showToolBars(); + void createWidgets(); + void createActions(); + void createLocationToolBar(); + void createObjectInteractions(); + void updateLocationURL(); + void updateFullScreenLabel(); + void createConnections(); + +private slots: + void goUp(); + void goUpTo(int); + + void makeDir(); + void goHome(); + void renameFile(); + void slotRenamed(const QString&); + void copyFiles(); + void moveFiles(); + void linkFiles(); + void deleteFiles(); + void showFileProperties(); + void showFileDialog(); + void printFile(); /** print the actual file */ + void clearLocationLabel(); + void activateLocationLabel(); + + void toggleFullScreen(); + void showConfigDialog(); + void showExternalToolDialog(); + void showKeyDialog(); + void showToolBarDialog(); + void applyMainWindowSettings(); + void slotImageLoading(); + void slotImageLoaded(); + void toggleSlideShow(); + void slotSlideShowChanged(bool); + void slotDirRenamed(const KURL& oldURL, const KURL& newURL); + void slotDirURLChanged(const KURL&); + void rotateLeft(); + void rotateRight(); + void mirror(); + void flip(); + void resetDockWidgets(); + + void slotToggleCentralStack(); + /** + * Update status bar and caption + */ + void updateStatusInfo(); + + /** + * Enable or disable image actions + */ + void updateImageActions(); + + void slotShownFileItemRefreshed(const KFileItem* item); + + /** + * Allow quitting full screen mode by pressing Escape key. + */ + void escapePressed(); + + /** + * Address bar related + */ + void slotGo(); + + void updateWindowActions(); + + void loadPlugins(); + + // Helper function for updateWindowActions() + void createHideShowAction(KDockWidget* dock); + + void slotReplug(); + + void showHint(const QString&); + + void fillGoUpMenu(); + + void openFileViewControllerContextMenu(const QPoint& pos, bool onItem); +}; + + +} // namespace +#endif + diff --git a/src/app/metaedit.cpp b/src/app/metaedit.cpp new file mode 100644 index 0000000..0d56f48 --- /dev/null +++ b/src/app/metaedit.cpp @@ -0,0 +1,138 @@ +// vim: set tabstop=4 shiftwidth=4 noexpandtab +/* +Copyright (c) 2003 Jos van den Oever + +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 <qlabel.h> +#include <qtextedit.h> +#include <qfileinfo.h> + +// KDE +#include <kdeversion.h> +#include <klocale.h> + +// Local +#include "gvcore/document.h" +#include "metaedit.moc" +namespace Gwenview { + +// FIXME: Why doesn't MetaEdit inherits from QTextEdit rather than QVBox? +MetaEdit::MetaEdit(QWidget *parent, Document *gvp, const char *name) +: QVBox(parent, name) +, mEmpty(true) +, mDocument(gvp) +{ + mCommentEdit=new QTextEdit(this); + mCommentEdit->installEventFilter(this); + connect(mCommentEdit, SIGNAL(modificationChanged(bool)), + this, SLOT(setModified(bool))); + connect(mDocument,SIGNAL(loaded(const KURL&)), + this,SLOT(updateContent()) ); + connect(mCommentEdit, SIGNAL(textChanged()), + this, SLOT(updateDoc()) ); + updateContent(); + mCommentEdit->setMinimumHeight(int (mCommentEdit->fontMetrics().height() * 1.5) ); +} + + +MetaEdit::~MetaEdit() { +} + + +bool MetaEdit::eventFilter(QObject*, QEvent *event) { + if (mEmpty + && (mDocument->commentState()==Document::WRITABLE) + && (event->type()==QEvent::FocusIn || event->type()==QEvent::FocusOut) + ) { + setEmptyText(); + } + return false; +} + + +void MetaEdit::setModified(bool m) { + if (m && mEmpty) { + mEmpty = false; + } +} + + +void MetaEdit::updateContent() { + if (mDocument->isNull()) { + setMessage(i18n("No image selected.")); + return; + } + + if (mDocument->commentState() == Document::NONE) { + setMessage(i18n("This image cannot be commented.")); + return; + } + + QString comment=mDocument->comment(); + mEmpty = comment.isEmpty(); + if (mEmpty) { + setEmptyText(); + return; + } + setComment(comment); +} + + +void MetaEdit::updateDoc() { + if ((mDocument->commentState()==Document::WRITABLE) && mCommentEdit->isModified()) { + mDocument->setComment(mCommentEdit->text()); + mCommentEdit->setModified(false); + } +} + + +void MetaEdit::setEmptyText() { + Q_ASSERT(mDocument->commentState()!=Document::NONE); + if (mDocument->commentState()==Document::WRITABLE) { + if (mCommentEdit->hasFocus()) { + setComment(""); + } else { + setMessage(i18n("Type here to add a comment to this image.")); + } + } else { + setMessage(i18n("No comment available.")); + } +} + + +/** + * Use mCommentEdit to show the comment and let the user edit it + */ +void MetaEdit::setComment(const QString& comment) { + Q_ASSERT(mDocument->commentState()!=Document::NONE); + mCommentEdit->setTextFormat(QTextEdit::PlainText); + mCommentEdit->setReadOnly(mDocument->commentState()==Document::READ_ONLY); + mCommentEdit->setText(comment); +} + + +/** + * Use mCommentEdit to display a read-only message + */ +void MetaEdit::setMessage(const QString& msg) { + mCommentEdit->setTextFormat(QTextEdit::RichText); + mCommentEdit->setReadOnly(true); + mCommentEdit->setText(QString("<i>%1</i>").arg(msg)); +} + +} // namespace diff --git a/src/app/metaedit.h b/src/app/metaedit.h new file mode 100644 index 0000000..c867eba --- /dev/null +++ b/src/app/metaedit.h @@ -0,0 +1,59 @@ +// vim: set tabstop=4 shiftwidth=4 noexpandtab +/* +Copyright (c) 2003 Jos van den Oever + +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 METAEDIT_H +#define METAEDIT_H + +// Qt +#include <qvbox.h> + +// KDE +#include <kfilemetainfo.h> +class QTextEdit; + +namespace Gwenview { + +class Document; + +class MetaEdit : public QVBox { +Q_OBJECT +public: + MetaEdit(QWidget *parent, Document*, const char *name=""); + ~MetaEdit(); +protected: + bool eventFilter(QObject *, QEvent *); +private slots: + void updateContent(); + void updateDoc(); + void setModified(bool); + +private: + bool mEmpty; + Document* mDocument; + QTextEdit* mCommentEdit; + + void setComment(const QString&); + void setMessage(const QString&); + void setEmptyText(); +}; + + +} // namespace +#endif + diff --git a/src/app/testvtabwidget.cpp b/src/app/testvtabwidget.cpp new file mode 100644 index 0000000..1e5d835 --- /dev/null +++ b/src/app/testvtabwidget.cpp @@ -0,0 +1,48 @@ +// 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 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. + +*/ +// Qt +#include <qlabel.h> + +// KDE +#include <kapplication.h> +#include <kaboutdata.h> +#include <kcmdlineargs.h> +#include <kiconloader.h> + +// Local +#include <vtabwidget.h> + +int main(int argc, char* argv[]) { + KAboutData aboutData("testvtabwidget", "testvtabwidget", "0"); + KCmdLineArgs::init( argc, argv, &aboutData ); + KApplication app; + + Gwenview::VTabWidget tabWidget(0); + QLabel* lbl=new QLabel("label 1", &tabWidget); + tabWidget.addTab(lbl, SmallIcon("text"), "tab1"); + lbl=new QLabel("label 2", &tabWidget); + tabWidget.addTab(lbl, SmallIcon("image"), "tab2"); + + app.setMainWidget(&tabWidget); + tabWidget.show(); + app.exec(); +} diff --git a/src/app/treeview.cpp b/src/app/treeview.cpp new file mode 100644 index 0000000..ddf24a5 --- /dev/null +++ b/src/app/treeview.cpp @@ -0,0 +1,342 @@ +// 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 "treeview.moc" + +// Qt +#include <qheader.h> +#include <qtimer.h> + +// KDE +#include <kdebug.h> +#include <kiconloader.h> +#include <kmimetype.h> +#include <kurldrag.h> + +// Local +#include "../gvcore/fileoperation.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 + +const int AUTO_OPEN_DELAY=1000; +const int DND_ICON_COUNT=8; +const char* DND_PREFIX="dnd"; + + +struct TreeView::Private { + TreeView* mView; + KFileTreeBranch* mBranch; + KFileTreeViewItem* mDropTarget; + QTimer* mAutoOpenTimer; + + KFileTreeViewItem* findViewItem(KFileTreeViewItem* parent,const QString& text) { + QListViewItem* item; + + for (item=parent->firstChild();item;item=item->nextSibling()) { + if (item->text(0)==text) { + return static_cast<KFileTreeViewItem*>(item); + } + } + return 0L; + } + + void setURLInternal(const KURL& url) { + LOG(url.prettyURL() ); + QString path=url.path(); + + if (!mBranch || !mBranch->rootUrl().isParentOf(url)) { + mView->createBranch(url); + return; + } + + // The request URL is a child of the branch, expand the branch to reach + // the child + LOG("Expanding to reach child"); + if(mBranch->rootUrl().path()!="/") { + path.remove(0,mBranch->rootUrl().path().length()); + } + LOG("Path=" << path); + + // Finds the deepest existing view item + QStringList folderParts=QStringList::split('/',path); + QStringList::Iterator folderIter=folderParts.begin(); + QStringList::Iterator endFolderIter=folderParts.end(); + KFileTreeViewItem* viewItem=mBranch->root(); + for(;folderIter!=endFolderIter;++folderIter) { + + KFileTreeViewItem* nextViewItem=findViewItem(viewItem,*folderIter); + if (!nextViewItem) break; + viewItem=nextViewItem; + } + LOG("Deepest existing view item=" << viewItem->url()); + + // If this is the wanted item, select it, + // otherwise set the url as the next to select + if (viewItem->url().equals(url,true)) { + LOG("We are done"); + mView->setCurrentItem(viewItem); + mView->ensureItemVisible(viewItem); + mView->slotSetNextUrlToSelect(KURL()); + } else { + LOG("We continue"); + mView->slotSetNextUrlToSelect(url); + } + + LOG("Opening deepest existing view item"); + viewItem->setOpen(true); + } +}; + + +TreeView::TreeView(QWidget* parent) +: KFileTreeView(parent) { + d=new Private; + d->mView=this; + d->mBranch=0; + d->mDropTarget=0; + d->mAutoOpenTimer=new QTimer(this); + + // Look + addColumn(QString::null); + header()->hide(); + setAllColumnsShowFocus(true); + setRootIsDecorated(false); + setFullWidth(true); + + // Drag'n'drop + setDragEnabled(true); + setDropVisualizer(false); + setDropHighlighter(true); + setAcceptDrops(true); + + connect(d->mAutoOpenTimer, SIGNAL(timeout()), + this, SLOT(autoOpenDropTarget())); +} + + +TreeView::~TreeView() { + delete d; +} + + +void TreeView::setURL(const KURL& url) { + LOG(url.prettyURL()); + if (currentURL().equals(url,true)) return; + if (m_nextUrlToSelect.equals(url,true)) return; + slotSetNextUrlToSelect(url); + + // Do not update the view if it's hidden, the url has been stored with + // slotSetNextUrlToSelect. The view will expand to it next time it's shown. + if (!isVisible()) { + LOG("We are hidden, just store the url"); + return; + } + + d->setURLInternal(url); +} + + +void TreeView::slotTreeViewPopulateFinished(KFileTreeViewItem* item) { + QListViewItem* child; + if (!item) return; + KURL url=item->url(); + + if (d->mDropTarget) { + startAnimation(d->mDropTarget,DND_PREFIX,DND_ICON_COUNT); + } + + LOG("itemURL=" << url); + LOG("m_nextUrlToSelect=" << m_nextUrlToSelect); + + // We reached the URL to select, get out + if (url.equals(m_nextUrlToSelect, true)) { + slotSetNextUrlToSelect(KURL()); + return; + } + + // This URL is not a parent of a wanted URL, get out + if (!url.isParentOf(m_nextUrlToSelect)) return; + + // Find the next child item and open it + LOG("Looking for next child"); + for (child=item->firstChild(); child; child=child->nextSibling()) { + url=static_cast<KFileTreeViewItem*>(child)->url(); + if (url.isParentOf(m_nextUrlToSelect)) { + LOG("Opening child with URL=" << url); + ensureItemVisible(child); + child->setOpen(true); + return; + } + } +} + + +void TreeView::createBranch(const KURL& url) { + if (d->mBranch) { + removeBranch(d->mBranch); + } + QString title=url.prettyURL(0, KURL::StripFileProtocol); + d->mBranch=addBranch(url, title, SmallIcon(KMimeType::iconForURL(url)) ); + setDirOnlyMode(d->mBranch, true); + d->mBranch->setChildRecurse(false); + d->mBranch->root()->setOpen(true); + + connect(d->mBranch, SIGNAL(populateFinished(KFileTreeViewItem*) ), + this, SLOT(slotTreeViewPopulateFinished(KFileTreeViewItem*)) ); +} + + + +/** + * Override this method to make sure that the item is selected, opened and + * visible + */ +void TreeView::slotNewTreeViewItems(KFileTreeBranch* branch, const KFileTreeViewItemList& itemList) { + if( ! branch ) return; + LOG(""); + if(m_nextUrlToSelect.isEmpty()) return; + + KFileTreeViewItemListIterator it( itemList ); + + for(;it.current(); ++it ) { + KURL url = (*it)->url(); + + // This is an URL to select + // (We block signals to avoid simulating a click on the dir item) + if (m_nextUrlToSelect.equals(url,true)) { + blockSignals(true); + setCurrentItem(*it); + blockSignals(false); + + ensureItemVisible(*it); + (*it)->setOpen(true); + m_nextUrlToSelect = KURL(); + return; + } + } +} + + +/** + * Override showEvent to make sure the view shows the correct + * dir when it's shown. Since the view doesn't update if it's + * hidden + */ +void TreeView::showEvent(QShowEvent* event) { + LOG("m_nextUrlToSelect=" << m_nextUrlToSelect.pathOrURL()); + if (m_nextUrlToSelect.isValid() && !currentURL().equals(m_nextUrlToSelect,true)) { + d->setURLInternal(m_nextUrlToSelect); + } + QWidget::showEvent(event); +} + + +void TreeView::contentsDragMoveEvent(QDragMoveEvent* event) { + if (!KURLDrag::canDecode(event)) { + event->ignore(); + return; + } + + // Get a pointer to the new drop item + QPoint point(0,event->pos().y()); + KFileTreeViewItem* newDropTarget=static_cast<KFileTreeViewItem*>( itemAt(contentsToViewport(point)) ); + if (!newDropTarget) { + event->ignore(); + d->mAutoOpenTimer->stop(); + if (d->mDropTarget) { + stopAnimation(d->mDropTarget); + d->mDropTarget=0L; + } + return; + } + + event->accept(); + if (newDropTarget==d->mDropTarget) return; + if (d->mDropTarget) { + stopAnimation(d->mDropTarget); + } + + // Restart auto open timer if we are over a new item + d->mAutoOpenTimer->stop(); + d->mDropTarget=newDropTarget; + startAnimation(newDropTarget,DND_PREFIX,DND_ICON_COUNT); + d->mAutoOpenTimer->start(AUTO_OPEN_DELAY,true); +} + + +void TreeView::contentsDragLeaveEvent(QDragLeaveEvent*) { + d->mAutoOpenTimer->stop(); + if (d->mDropTarget) { + stopAnimation(d->mDropTarget); + d->mDropTarget=0L; + } +} + + +void TreeView::contentsDropEvent(QDropEvent* event) { + d->mAutoOpenTimer->stop(); + + // Get data from drop (do it before showing menu to avoid mDropTarget changes) + if (!d->mDropTarget) return; + KURL dest=d->mDropTarget->url(); + + KURL::List urls; + if (!KURLDrag::decode(event,urls)) return; + + // Show popup + bool wasMoved; + FileOperation::openDropURLMenu(this, urls, dest, &wasMoved); + + if (wasMoved) { + // If the current url was in the list, set the drop target as the new + // current item + KURL current=currentURL(); + KURL::List::ConstIterator it=urls.begin(); + for (; it!=urls.end(); ++it) { + if (current.equals(*it,true)) { + setCurrentItem(d->mDropTarget); + break; + } + } + } + + // Reset drop target + if (d->mDropTarget) { + stopAnimation(d->mDropTarget); + d->mDropTarget=0L; + } +} + + +void TreeView::autoOpenDropTarget() { + if (d->mDropTarget) { + d->mDropTarget->setOpen(true); + } +} +} // namespace diff --git a/src/app/treeview.h b/src/app/treeview.h new file mode 100644 index 0000000..e0c41c2 --- /dev/null +++ b/src/app/treeview.h @@ -0,0 +1,70 @@ +// 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 TREEVIEW_H +#define TREEVIEW_H + +// KDE +#include <kfiletreeview.h> + +class KURL; +class QShowEvent; + +namespace Gwenview { + + +class TreeView : public KFileTreeView { +Q_OBJECT +public: + TreeView(QWidget* parent); + ~TreeView(); + +public slots: + void setURL(const KURL&); + void createBranch(const KURL&); + +signals: + void urlChanged(const KURL&); + +protected: + virtual void showEvent(QShowEvent*); + virtual void contentsDragMoveEvent(QDragMoveEvent*); + virtual void contentsDragLeaveEvent(QDragLeaveEvent*); + virtual void contentsDropEvent(QDropEvent*); + +protected slots: + virtual void slotNewTreeViewItems(KFileTreeBranch*, const KFileTreeViewItemList&); + +private: + struct Private; + Private* d; + friend class Private; + +private slots: + // Do not name this slot "slotPopulateFinished", it will clash with + // "KFileTreeView::slotPopulateFinished". + void slotTreeViewPopulateFinished(KFileTreeViewItem*); + + void autoOpenDropTarget(); +}; + + +} // namespace +#endif diff --git a/src/app/truncatedtextlabel.h b/src/app/truncatedtextlabel.h new file mode 100644 index 0000000..81298fd --- /dev/null +++ b/src/app/truncatedtextlabel.h @@ -0,0 +1,79 @@ +// 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 TRUNCATEDTEXTLABEL_H +#define TRUNCATEDTEXTLABEL_H + +// Qt +#include <qlabel.h> +#include <qtooltip.h> + +// KDE +#include <kwordwrap.h> + +namespace Gwenview { + +/** + * A label which truncates it's text if it's too long, drawing it using + * KWordWrap::drawFadeoutText() + */ +class TruncatedTextLabel : public QLabel { +public: + TruncatedTextLabel(QWidget* parent) + : QLabel(parent) {} + + QSize minimumSizeHint() const { + QSize size=QLabel::minimumSizeHint(); + size.setWidth(-1); + return size; + } + + QSize sizeHint() const { + return QSize(contentsRect().width(), QLabel::sizeHint().height()); + } + + void setText(const QString& text) { + QLabel::setText(text); + updateToolTip(); + } + +protected: + void drawContents(QPainter* painter) { + KWordWrap::drawFadeoutText(painter, 0, fontMetrics().ascent(), width(), text()); + } + + void resizeEvent(QResizeEvent*) { + updateToolTip(); + } + + void updateToolTip() { + QString txt=text(); + QToolTip::remove(this); + if ( width() < fontMetrics().width(txt) ) { + QToolTip::add(this, txt); + } else { + QToolTip::hide(); + } + } +}; + +} // namespace + +#endif /* TRUNCATEDTEXTLABEL_H */ diff --git a/src/app/vtabwidget.cpp b/src/app/vtabwidget.cpp new file mode 100644 index 0000000..974dc00 --- /dev/null +++ b/src/app/vtabwidget.cpp @@ -0,0 +1,85 @@ +// 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 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 "vtabwidget.moc" + +// Qt +#include <qwidgetstack.h> + +// KDE +#include <kmultitabbar.h> + +namespace Gwenview { + + +struct VTabWidget::Private { + KMultiTabBar* mTabBar; + QWidgetStack* mStack; + bool mEmpty; +}; + + +VTabWidget::VTabWidget(QWidget* parent) +: QWidget(parent) +{ + d=new Private; + d->mEmpty=true; + d->mTabBar=new KMultiTabBar(KMultiTabBar::Vertical, this); + d->mTabBar->setPosition(KMultiTabBar::Left); + d->mTabBar->setStyle(KMultiTabBar::KDEV3ICON); + d->mStack=new QWidgetStack(this); + QHBoxLayout* layout=new QHBoxLayout(this); + layout->add(d->mTabBar); + layout->add(d->mStack); +} + + +VTabWidget::~VTabWidget() { + delete d; +} + + +void VTabWidget::addTab(QWidget* child, const QPixmap& pix, const QString& label) { + int id=d->mStack->addWidget(child); + d->mTabBar->appendTab(pix, id, label); + connect(d->mTabBar->tab(id), SIGNAL(clicked(int)), + this, SLOT(slotClicked(int)) ); + + if (d->mEmpty) { + d->mTabBar->tab(id)->setOn(true); + d->mEmpty=false; + } +} + + +void VTabWidget::slotClicked(int id) { + d->mStack->raiseWidget(id); + QPtrList<KMultiTabBarTab>* tabs=d->mTabBar->tabs(); + QPtrListIterator<KMultiTabBarTab> it(*tabs); + for (; it.current(); ++it) { + KMultiTabBarTab* tab=it.current(); + tab->setOn(tab->id()==id); + } +} + + +} // namespace diff --git a/src/app/vtabwidget.h b/src/app/vtabwidget.h new file mode 100644 index 0000000..0f177ec --- /dev/null +++ b/src/app/vtabwidget.h @@ -0,0 +1,52 @@ +// 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 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 VTABWIDGET_H +#define VTABWIDGET_H + +// Qt +#include <qwidget.h> + +class QPixmap; + +namespace Gwenview { + + +class VTabWidget : public QWidget { +Q_OBJECT +public: + VTabWidget(QWidget* parent); + virtual ~VTabWidget(); + void addTab(QWidget*, const QPixmap&, const QString& label); + +private slots: + void slotClicked(int id); + +private: + struct Private; + Private* d; +}; + + +} // namespace + + +#endif /* VTABWIDGET_H */ diff --git a/src/configure.in.bot b/src/configure.in.bot new file mode 100644 index 0000000..ed010db --- /dev/null +++ b/src/configure.in.bot @@ -0,0 +1,11 @@ +if test "$want_kipi" = "yes" ; then + if test "$have_kipi" != "yes" ; then + echo "" + echo "KIPI is not installed. Gwenview will be build without KIPI support." + echo "" + fi +else + echo "" + echo "KIPI support has been disabled. Gwenview will be build without KIPI support." + echo "" +fi diff --git a/src/desktopfiles/Makefile.am b/src/desktopfiles/Makefile.am new file mode 100644 index 0000000..d36dea4 --- /dev/null +++ b/src/desktopfiles/Makefile.am @@ -0,0 +1,9 @@ +# this is where the kdelnk file will go +xdg_apps_DATA = gwenview.desktop + +# file for konqueror integration +konqservicedir = $(kde_datadir)/konqueror/servicemenus +konqservice_DATA = konqgwenview.desktop + + +EXTRA_DIST = $(xdg_apps_DATA) $(konqservice_DATA) diff --git a/src/desktopfiles/gwenview.desktop b/src/desktopfiles/gwenview.desktop new file mode 100644 index 0000000..9aec864 --- /dev/null +++ b/src/desktopfiles/gwenview.desktop @@ -0,0 +1,107 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=Gwenview +Name[hi]=ग्वेनव्यू +Name[ta]=gwen பார்வை +Name[tg]=НамоишиGwen +Name[xx]=xxGwenviewxx +GenericName=Image Viewer +GenericName[bg]=Преглед на изображения +GenericName[br]=Gweler ar skeudennoù +GenericName[bs]=Preglednik slika +GenericName[ca]=Visor d'imatges +GenericName[cs]=Prohlížeč obrázků +GenericName[cy]=Gwelydd Delwedd +GenericName[da]=Billedfremviser +GenericName[de]=Bildbetrachter +GenericName[el]=Προβολέας εικόνων +GenericName[es]=Visualizador de imágenes +GenericName[et]=Pildinäitaja +GenericName[fa]=مشاهدهگر تصویر +GenericName[fi]=Kuvankatseluohjelma +GenericName[fr]=Afficheur d'images +GenericName[gl]=Visor de Imaxes +GenericName[hi]=छवि प्रदर्शक +GenericName[hr]=Preglednik slika +GenericName[hu]=Képnézegető +GenericName[is]=Myndskoðari +GenericName[it]=Visualizzatore di immagini +GenericName[ja]=画像ビューア +GenericName[ka]=სურათების დამთვალიერებელი +GenericName[lt]=Paveikslėlių žiūriklis +GenericName[ms]=Pelihat Imej +GenericName[nds]=Bildkieker +GenericName[nl]=Afbeeldingweergaveprogramma +GenericName[pa]=ਚਿੱਤਰ ਦਰਸ਼ਕ +GenericName[pl]=Przeglądarka obrazków +GenericName[pt]=Visualizador de Imagens +GenericName[pt_BR]=Visualizador de Imagens +GenericName[ru]=Просмотр изображений +GenericName[rw]=Mugaraza Shusho +GenericName[sk]=Prehliadač obrázkov +GenericName[sl]=Pregledovalnik slik +GenericName[sr]=Приказивач слика +GenericName[sr@Latn]=Prikazivač slika +GenericName[sv]=Bildvisning +GenericName[tg]=Намоишгари тасвир +GenericName[tr]=Resim Gösterici +GenericName[uk]=Переглядач зображень +GenericName[vi]=Bộ xem ảnh +GenericName[xh]=Umboniseli Womfanekiso +GenericName[xx]=xxImage Viewerxx +GenericName[zh_CN]=图像查看器 +GenericName[zu]=Umbonisi Womfanekiso +Comment=A simple image viewer +Comment[ar]=مستعرض الصور البسيط +Comment[bg]=Малка програма за преглед на изображения +Comment[br]=Ur gweler skeudenn eeun +Comment[bs]=Jednostavan preglednik slika +Comment[ca]=Un visor d'imatges senzill +Comment[cs]=Jednoduchý prohlížeč obrázků +Comment[da]=En simpel billedfremviser +Comment[de]=Ein einfacher Bildbetrachter +Comment[el]=Ένας απλός προβολέας εικόνων +Comment[es]=Visor de mensajes sencillo +Comment[et]=Lihtne pildinäitaja +Comment[fa]=یک مشاهدهگر تصویر ساده +Comment[fi]=Yksinkertainen kuvankatseluohjelma +Comment[fr]=Un afficheur basique d'images +Comment[gl]=Un visor de imaxes +Comment[he]=מציג תמונות פשוט +Comment[hi]=एक सरल चित्र प्रदर्शक +Comment[hr]=Jednostavan preglednik slika +Comment[hu]=Egyszerű képnézegető +Comment[is]=Einfaldur myndskoðari +Comment[it]=Semplice visualizzatore di immagini +Comment[ja]=シンプルな画像ビューア +Comment[ka]=სურათების მარტივი დამთვალიერებელი +Comment[ms]=Pelihat imej ringkas +Comment[nds]=En eenfach Bildkieker +Comment[nl]=Een eenvoudig afbeeldingenweergaveprogramma +Comment[pa]=ਇੱਕ ਸਧਾਰਨ ਚਿੱਤਰ ਦਰਸ਼ਕ +Comment[pl]=Prosta przeglądarka obrazków +Comment[pt]=Um visualizador de imagens simples +Comment[pt_BR]=Um visualizador de imagens simples +Comment[ru]=Простая программа просмотра изображений +Comment[sk]=Jednoduchý prehliadač obrázkov +Comment[sl]=Preprost pregledovalnik slik +Comment[sr]=Једноставан приказивач слика +Comment[sr@Latn]=Jednostavan prikazivač slika +Comment[sv]=En enkel bildvisare +Comment[ta]=ஒரு சாதாரண உருவ பார்வையாளர் +Comment[tg]=Барномаи оддӣ барои намоиши тасвир +Comment[tr]=Basit bir resim izleyici +Comment[uk]=Простий переглядач зображень +Comment[vi]=Bộ xem ảnh đơn giản +Comment[xh]=Umboniseli olula womfanekiso +Comment[xx]=xxA simple image viewerxx +Comment[zh_CN]=一个简单的图像查看器 +Comment[zh_TW]=一個簡易的影像檢視器 +Comment[zu]=Umbukisi wesithombe esilula +Exec=gwenview %u -caption "%c" %i %m +Terminal=false +Icon=gwenview +Type=Application +Categories=Qt;KDE;Graphics; +MimeType=image/gif;image/x-xpm;image/x-xbm;image/jpeg;image/x-pcx;image/x-bmp;image/png;image/x-ico;image/x-portable-bitmap;image/x-portable-pixmap;image/x-portable-greymap;image/tiff;image/x-targa; +DocPath=gwenview/index.html diff --git a/src/desktopfiles/konqgwenview.desktop b/src/desktopfiles/konqgwenview.desktop new file mode 100644 index 0000000..cf6f2d8 --- /dev/null +++ b/src/desktopfiles/konqgwenview.desktop @@ -0,0 +1,55 @@ +[Desktop Entry] +Encoding=UTF-8 +ServiceTypes=inode/directory +Actions=gwenview; + +[Desktop Action gwenview] +Name=Browse with Gwenview +Name[ar]=تصفح بواسطة Gwenview +Name[bg]=Преглед с Gwenview +Name[br]=Furchal gant Gwenview +Name[bs]=Pregledaj sa Gwenview +Name[ca]=Navega amb el Gwenview +Name[cs]=Prohlížet pomocí Gwenview +Name[da]=Gennemse med Gwenview +Name[de]=Dateien mit Gwenview durchsehen +Name[el]=Εξερεύνηση με το Gwenview +Name[es]=Navegación con Gwenview +Name[et]=Lehitse kasutades Gwenview'd +Name[fa]=مرور با Gwenview +Name[fi]=Katsele Gwenviewilla +Name[fr]=Naviguer avec Gwenview +Name[gl]=Examinar con Gwenview +Name[he]=עיין בעזרת Gwenview +Name[hi]=ग्वेनव्यू के साथ ब्राउज़ करें +Name[hr]=Pretraži s Gwenview +Name[hu]=Böngészés a Gwenview-val +Name[is]=Skoða með Gwenview +Name[it]=Sfoglia con Gwenview +Name[ja]=Gwenview で閲覧 +Name[ka]=დათვალიერება Gwenview-ით +Name[ms]= Lungsur dengan Gwenview +Name[nds]=Dateien mit Gwenview dörkieken +Name[nl]=Bladeren met Gwenview +Name[pa]=Gwenview ਨਾਲ ਵੇਖੋ +Name[pl]=Przeglądaj za pomocą Gwenview +Name[pt]=Navegar com o Gwenview +Name[pt_BR]=Navegar com Gwenview +Name[ru]=Просмотреть в Gwenview +Name[sk]=Prehliadať s Gwenview +Name[sl]=Brskaj z Gwenview +Name[sr]=Прегледај помоћу Gwenview-а +Name[sr@Latn]=Pregledaj pomoću Gwenview-a +Name[sv]=Bläddra med Gwenview +Name[ta]=GWEN பார்வையை வைத்து தேடு +Name[tg]=Ҷустан бо НамоишиGwen +Name[tr]=Gwenview ile gözat +Name[uk]=Перегляд у Gwenview +Name[vi]=Duyệt bằng Gwenview +Name[xh]=Khangela nge Gwenview +Name[xx]=xxBrowse with Gwenviewxx +Name[zh_CN]=用 Gwenview 浏览 +Name[zh_TW]=以 Gwenview 瀏覽 +Name[zu]=Cinga nge-Gwenview +Icon=gwenview +Exec=gwenview %u diff --git a/src/doc/Makefile.am b/src/doc/Makefile.am new file mode 100644 index 0000000..a34ff18 --- /dev/null +++ b/src/doc/Makefile.am @@ -0,0 +1 @@ +man_MANS = gwenview.1 diff --git a/src/doc/gwenview.1 b/src/doc/gwenview.1 new file mode 100644 index 0000000..e39b41c --- /dev/null +++ b/src/doc/gwenview.1 @@ -0,0 +1,238 @@ +.TH GWENVIEW 1 "January 2005" "K Desktop Environment" "Image viewer for KDE" +.SH NAME +Gwenview \- An image viewer for KDE +.SH SYNOPSIS +.B gwenview +[\fIoptions\fR] [\fIfile or folder\fR] +.SH DESCRIPTION +Gwenview is an image viewer for KDE. It features a folder tree window and a file list window to provide easy navigation of your file hierarchy. Gwenview uses docked windows, so you can alter its layout any way you wish. You can also browse your images in full\-screen mode, or embedded within Konqueror using the Gwenview Image Browser View and Kpart. +.PP +Image loading is handled by the Qt library, so Gwenview supports all image formats your Qt installation supports. Gwenview correctly displays images with an alpha channel. +.PP +Gwenview supports the displaying and editing of EXIF comments, if the necessary JPEG kfile\-plugin is installed. This comes as part of the kdegraphics package. Lossless JPEG transforms are also supported. +.SH OPTIONS +.SS Arguments: +.TP +.B file or folder +An initial image file, or a folder containing images. +.SS Gwenview options: +.TP +.B \-f +Start in Full Screen mode. +.SS Generic options: +.TP +.B \-\-help +Show help about options. +.TP +.B \-\-help\-qt +Show Qt specific options. +.TP +.B \-\-help\-kde +Show KDE specific options. +.TP +.B \-\-help\-all +Show all options. +.TP +.B \-\-author +Show author information. +.TP +.B \-v\fR, \fB\-\-version +Show version information. +.TP +.B \-\-license +Show license information. +.TP +.B \-\- +End of options. +.SS Qt options: +.TP +.B \-\-display \fIdisplayname\fR +Use the X\-server display 'displayname'. +.TP +.B \-\-session \fIsessionId\fR +Restore the application for the given 'sessionId'. +.TP +.B \-\-cmap +Causes the application to install a private color +map on an 8\-bit display. +.TP +.B \-\-ncols \fIcount\fR +Limits the number of colors allocated in the color +cube on an 8\-bit display, if the application is +using the QApplication::ManyColor color +specification. +.TP +.B \-\-nograb +Tells Qt to never grab the mouse or the keyboard. +.TP +.B \-\-dograb +Running under a debugger can cause an implicit +\-nograb, use \-dograb to override. +.TP +.B \-\-sync +Switches to synchronous mode for debugging. +.TP +.B \-\-fn\fR, \fB\-\-font \fIfontname\fR +Defines the application font. +.TP +.B \-\-bg\fR, \fB\-\-background \fIcolor\fR +Sets the default background color and an +application palette (light and dark shades are +calculated). +.TP +.B \-\-fg\fR, \fB\-\-foreground \fIcolor\fR +Sets the default foreground color. +.TP +.B \-\-btn\fR, \fB\-\-button \fIcolor\fR +Sets the default button color. +.TP +.B \-\-name \fIname\fR +Sets the application name. +.TP +.B \-\-title \fItitle\fR +Sets the application title (caption). +.TP +.B \-\-visual TrueColor +Forces the application to use a TrueColor visual on +an 8\-bit display. +.TP +.B \-\-inputstyle \fIinputstyle\fR +Sets XIM (X Input Method) input style. Possible +values are onthespot, overthespot, offthespot and +root. +.TP +.B \-\-im \fIXIM server\fR +Set XIM server. +.TP +.B \-\-noxim +Disable XIM. +.TP +.B \-\-reverse +Mirrors the whole layout of widgets. +.SS KDE options: +.TP +.B \-\-caption \fIcaption\fR +Use 'caption' as name in the titlebar. +.TP +.B \-\-icon \fIicon\fR +Use 'icon' as the application icon. +.TP +.B \-\-miniicon \fIicon\fR +Use 'icon' as the icon in the titlebar. +.TP +.B \-\-config \fIfilename\fR +Use alternative configuration file. +.TP +.B \-\-dcopserver \fIserver\fR +Use the DCOP Server specified by 'server'. +.TP +.B \-\-nocrashhandler +Disable crash handler, to get core dumps. +.TP +.B \-\-waitforwm +Waits for a WM_NET compatible windowmanager. +.TP +.B \-\-style \fIstyle\fR +Sets the application GUI style. +.TP +.B \-\-geometry \fIgeometry\fR +Sets the client geometry of the main widget. +.SH INTERFACE +.SS Browse and View modes +By default, Gwenview opens in Browse mode, with the docked windows (showing +directory structure and contents) displayed. Starting Gwenview with a directory +as an argument also begins in Browse mode. Starting with an image as an argument, +however, opens Gwenview in View mode, with these docked windows hidden to give +full space to the image itself. +.PP +Once Gwenview is started, the user can toggle between Browse and View modes by +clicking the Browse button on the Main Toolbar. The same option can be found +in the View menu. Ctrl-Return will toggle these modes as well. +.PP +Whereas normally, in Browse mode, single-clicking on an image thumbnail or +listing is sufficient for Gwenview to display it, users also have the option of +double-clicking, which displays the image and switches Gwenview to View mode. +.SS Docked windows +The Gwenview interface is composed of a number of smaller docked windows. +Manipulating these windows can be difficult at first. To focus a particular +window in the Gwenview interface, simply click within it. +.PP +Each docked window has a small grip bar along the top, with a dock/undock button +resembling an arrow at its right end, as well as a close button resembling the +letter X. +.PP +Clicking the close button on a docked window will remove it from the interface. +To bring back a window, select it from the Window menu. +.PP +To resize docked windows, click on the border between the windows, and drag. +To re-arrange the docked windows, click on a window's grip bar along its top, and drag +the window over to the desired position relative to the other docked windows. +While dragging, a small box outline should appear that indicates the new position +that the window will take, when you release the mouse and end the drag. If you wish +to stack two windows, and use tabs to choose between them, drag the window to the +very center of the other window with which you wish to stack. +.PP +To undock a window, either click the dock/undock button in its top right corner, or +double-click on the window's grip bar. Double-clicking again, or clicking +on the dock/undock button, should return the window to its original position. +Otherwise, you can dock an undocked window by dragging its grip bar back to a +point within the general interface, just as if you were rearranging an already +docked window. +.PP +The file view window cannot be undocked or moved, but by moving other windows +around, it can itself be shifted about. +.PP +While dragging a window, press the Escape key to cancel the operation. To reset +the docked windows to the default Gwenview layout, use the Reset option in the +Window menu. +.SS Mouse operations +Browsing a directory of images can be done several ways, including mouse +gestures. Hold down the left mouse button over an image, then click the right +button to go to the next image. Hold down the right button, then click the +left button to load the previous image. +.PP +Clicking the right mouse button on an image brings up a context menu. +.PP +Clicking the middle mouse button will toggle the auto zoom on/off. +.PP +Double-clicking on an image toggles Full Screen mode. +.PP +Holding down the left mouse button on an image allows you to +scroll the image. By default, you can also scroll up and down an image by +using the mouse wheel. However, this can be configured to browse the images +in the current directory instead. +.PP +The mouse wheel, used while holding down the Shift key, will zoom the image +in and out. +.PP +The mouse wheel, used while holding down the Control or Alt keys, will scroll the image horizontally. +.SS Keybindings +Gwenview comes with a range of keyboard shortcuts, all of which can be viewed and remapped by +selecting "Configure Shortcuts" in the Settings menu. Note that in the Files and Folders windows, +all the normal KDE shortcuts are functional, unless otherwise remapped. +.PP +A few of the most useful default bindings are: +.PP + Ctrl-Shift-F Toggles Full Screen mode. + Escape Stop, exit from Full Screen mode. + + Home Displays the first image in the directory. + End Displays the last image in the directory. + + Space Displays the next image in the directory. + Backspace Displays the previous image in the directory. +.SH "SEE ALSO" +The Gwenview homepage can be found at \fIhttp://gwenview.sourceforge.net\fR. +.PP +A mailing list is also available at \fIhttp://lists.sourceforge.net/lists/listinfo/gwenview-general\fR. +.SH BUGS +To report a bug, please visit \fIhttp://bugs.kde.org\fR. +.SH AUTHOR +Gwenview was written by Aurelien Gateau <[email protected]>. +.PP +This manual page was written by Christopher Martin <[email protected]> for +Debian GNU/Linux, but may be used by others. +.PP +Permission is granted to copy, distribute and/or modify this document under the +terms of the GNU General Public License, Version 2, any later version published +by the Free Software Foundation. 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>&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>&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>&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 <[email protected]> + based on code by Cornelius Schumacher <[email protected]> + + 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 <[email protected]> + + 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 <[email protected]> + (C) 2006 by Ian Monroe <[email protected]> + (C) 2006 by Aurelien Gateau <[email protected]> +***************************************************************************/ + +/*************************************************************************** + * * + * 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 <[email protected]> + (C) 2006 by Ian Monroe <[email protected]> + (C) 2006 by Aurelien Gateau <[email protected]> +***************************************************************************/ + +/*************************************************************************** + * * + * 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>&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><qt><p>If this box is checked, items will be <b>permanently removed</b> instead of being placed in the trash bin.</p> + +<p><em>Use this option with caution</em>: Most filesystems are unable to reliably undelete deleted files.</p></qt></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>&Delete</string> + </property> + </widget> + <widget class="QPushButton" row="1" column="0"> + <property name="name"> + <cstring>mAddButton</cstring> + </property> + <property name="text"> + <string>&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><qt> +You can use keywords in the Command field: +<ul> +<li><tt>%u</tt>: Current URL.</li> +<li><tt>%U</tt>: Current URLs. Use this if the tool can handle multiple files.</li> +<li><tt>%f</tt>: Current file. Use this if the tool can't handle URLs.</li> +<li><tt>%F</tt>: Same as %f, but for multiple files.</li> +</ul> +</qt></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 <[email protected]> + 2000, 2001 Carsten Pfeiffer <[email protected]> + + 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 <[email protected]> + + 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 <[email protected]> + 2000, 2001 Carsten Pfeiffer <[email protected]> + + 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 <[email protected]> + + 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 <[email protected]> + + 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>&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. <[email protected]> + * + * 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 ([email protected]) + + 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 ([email protected]) + + 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 <[email protected]> + + 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 [email protected] 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 [email protected] 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 [email protected] 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 [email protected] 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 [email protected] 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 [email protected] 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 [email protected] 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 [email protected] 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&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>&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>&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>&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. <[email protected]> + * $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><i>For more options, use the "Configure Gwenview" dialog</i></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 <[email protected]> + 2000 Carsten Pfeiffer <[email protected]> + + 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 <[email protected]> + + 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 <[email protected]> + 2000 Carsten Pfeiffer <[email protected]> + + 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 <[email protected]> + Copyright (C) 2005 Lubos Lunak <[email protected]> + + 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 <[email protected]> + Copyright (C) 2005 Lubos Lunak <[email protected]> + + 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 [email protected] 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 [email protected] 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 [email protected] 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 [email protected] 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 diff --git a/src/gvdirpart/Makefile.am b/src/gvdirpart/Makefile.am new file mode 100644 index 0000000..039267d --- /dev/null +++ b/src/gvdirpart/Makefile.am @@ -0,0 +1,23 @@ +INCLUDES = -I$(srcdir)/.. $(all_includes) + +# These are not really libraries, but modules dynamically opened. +# So they should be installed in kde_module_dir, which is usually $kde_prefix/lib/kde3 +kde_module_LTLIBRARIES = libgvdirpart.la + +libgvdirpart_la_SOURCES = gvdirpart.cpp gvdirpartconfig.kcfgc +libgvdirpart_la_LIBADD = $(LIB_KPARTS) ../gvcore/libgwenviewcore.la +libgvdirpart_la_LDFLAGS = $(all_libraries) $(KDE_PLUGIN) + +# Automatically generate moc files +METASOURCES = AUTO + +# Install the XML-GUI file to its correct location +gvdir = $(kde_datadir)/gvdirpart +gv_DATA = gvdirpart.rc + +# Install the .desktop file into the kde_services directory +kde_services_DATA = gvdirpart.desktop + +KDE_ICON = AUTO + +kde_kcfg_DATA = gvdirpartconfig.kcfg diff --git a/src/gvdirpart/cr16-app-gvdirpart.png b/src/gvdirpart/cr16-app-gvdirpart.png Binary files differnew file mode 100644 index 0000000..d49bc5b --- /dev/null +++ b/src/gvdirpart/cr16-app-gvdirpart.png diff --git a/src/gvdirpart/cr22-app-gvdirpart.png b/src/gvdirpart/cr22-app-gvdirpart.png Binary files differnew file mode 100644 index 0000000..00eaab2 --- /dev/null +++ b/src/gvdirpart/cr22-app-gvdirpart.png diff --git a/src/gvdirpart/cr32-app-gvdirpart.png b/src/gvdirpart/cr32-app-gvdirpart.png Binary files differnew file mode 100644 index 0000000..1a68bf4 --- /dev/null +++ b/src/gvdirpart/cr32-app-gvdirpart.png diff --git a/src/gvdirpart/crsc-app-gvdirpart.svg b/src/gvdirpart/crsc-app-gvdirpart.svg new file mode 100644 index 0000000..c470051 --- /dev/null +++ b/src/gvdirpart/crsc-app-gvdirpart.svg @@ -0,0 +1,587 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" +"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> +<!-- Created with Sodipodi ("http://www.sodipodi.com/") --> +<svg + id="svg602" + sodipodi:version="0.34" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:xlink="http://www.w3.org/1999/xlink" + width="580pt" + height="580pt" + xml:space="preserve" + sodipodi:docbase="/home/aurelien/kde/trunk/extragear/graphics/gwenview/gvdirpart" + sodipodi:docname="/home/aurelien/kde/trunk/extragear/graphics/gwenview/gvdirpart/hisc-app-gvdirpart.svg"><defs + id="defs604"><linearGradient + id="linearGradient917"><stop + style="stop-color:#4ea4eb;stop-opacity:1;" + offset="0" + id="stop918" /><stop + style="stop-color:#0032a4;stop-opacity:1;" + offset="1" + id="stop919" /></linearGradient><linearGradient + id="linearGradient913"><stop + style="stop-color:#ffffff;stop-opacity:1;" + offset="0" + id="stop914" /><stop + style="stop-color:#cdcbd8;stop-opacity:1;" + offset="1" + id="stop915" /></linearGradient><linearGradient + id="linearGradient611"><stop + style="stop-color:#8aceff;stop-opacity:1;" + offset="0" + id="stop612" /><stop + style="stop-color:#0349ff;stop-opacity:1;" + offset="1" + id="stop613" /></linearGradient><linearGradient + xlink:href="#linearGradient611" + id="linearGradient614" /><linearGradient + xlink:href="#linearGradient913" + id="linearGradient912" + x1="0.42857143" + y1="0.4296875" + x2="1.21428573" + y2="1.1171875" /><linearGradient + xlink:href="#linearGradient917" + id="linearGradient916" + x1="4.06976789e-2" + y1="0.4193548" + x2="0.98837221" + y2="0.4258064" /><radialGradient + xlink:href="#linearGradient913" + id="radialGradient920" + cx="0.13736264" + cy="0.1328125" + r="2.06871057" + fx="0.13736264" + fy="0.1328125" /></defs><sodipodi:namedview + id="base" /><path + style="font-size:12;fill:#6a6a6a;fill-rule:evenodd;stroke:none;stroke-opacity:1;stroke-width:20;stroke-dasharray:none;fill-opacity:0.1722;" + d="M 132.3919 95.53742 C 87.65671 95.53742 51.69955 132.1676 51.69955 177.6573 L 51.69955 678.0391 L 713.1688 678.0391 L 713.1688 177.6573 C 713.1688 132.1676 677.1466 95.53742 632.4115 95.53742 L 132.3919 95.53742 z " + id="path617" + sodipodi:nodetypes="ccccccc" /><path + style="font-size:12;fill:#000000;fill-rule:evenodd;stroke:none;stroke-opacity:1;stroke-width:20;stroke-dasharray:none;fill-opacity:0.1833;" + d="M 112.5356 70.68678 C 67.80041 70.68678 31.84324 107.3168 31.84324 152.8065 L 31.84324 653.1883 L 693.3126 653.1883 L 693.3126 152.8065 C 693.3126 107.3168 657.2903 70.68678 612.5552 70.68678 L 112.5356 70.68678 z " + id="path616" + sodipodi:nodetypes="ccccccc" /><path + style="font-size:12;fill:url(#linearGradient614);fill-rule:evenodd;stroke:url(#linearGradient916);stroke-width:20.49727;" + d="M 74.43997 57.87444 C 30.25117 57.87444 9.820701 81.25965 9.820701 125.2094 L 11.32945 620.6578 L 664.7194 620.6578 L 664.7194 125.2094 C 664.7194 81.25965 641.2072 57.87444 597.0184 57.87444 L 74.43997 57.87444 z " + id="path610" + sodipodi:nodetypes="ccccccc" /><rect + style="font-size:12;fill:url(#radialGradient920);fill-rule:evenodd;stroke-width:1;fill-opacity:1;" + id="rect615" + width="634.247192" + height="459.997193" + x="20.5543118" + y="156.400013" /><g + id="g766" + transform="matrix(3.950886,0,0,3.950887,85.53951,222.098)"><g + id="Layer_1_2_"><g + id="g768"><linearGradient + id="XMLID_1_" + gradientUnits="userSpaceOnUse" + x1="20.8491" + y1="8.8062" + x2="20.8491" + y2="1.6043"><stop + offset="0" + style="stop-color:rgb(158,158,158)" + id="stop770" /><stop + offset="1" + style="stop-color:rgb(97,97,97)" + id="stop771" /></linearGradient><path + d="M20.849 8.063 C15.178 8.063 10.58 6.374 10.58 4.292 C10.58 2.209 15.178 0.521 20.849 0.521 C26.522 0.521 31.119 2.209 31.119 4.292 C31.119 6.375 26.521 8.063 20.849 8.063 z" + style="fill:url(#XMLID_1_)" + id="path772" /><linearGradient + id="XMLID_2_" + gradientUnits="userSpaceOnUse" + x1="30.7832" + y1="6.0205" + x2="121.325" + y2="105.692"><stop + offset="0.0337" + style="stop-color:rgb(255,255,255)" + id="stop774" /><stop + offset="1" + style="stop-color:rgb(171,171,171)" + id="stop775" /></linearGradient><path + d="M118.408 0.548 L67.143 0.548 C64.399 0.548 62.108 2.368 61.337 4.857 L6.655 4.857 C3.281 4.857 0.548 7.592 0.548 10.965 L0.548 75.284 C0.548 78.658 3.282 81.391 6.655 81.391 L118.408 81.391 C121.783 81.391 124.516 78.658 124.516 75.284 L124.516 6.656 C124.517 3.284 121.783 0.548 118.408 0.548 z" + style="fill:url(#XMLID_2_)" + id="path776" /><g + id="g777"><linearGradient + id="XMLID_3_" + gradientUnits="userSpaceOnUse" + x1="116.971" + y1="16.1201" + x2="103.088" + y2="3.4219"><stop + offset="0.0337" + style="stop-color:rgb(255,255,255)" + id="stop779" /><stop + offset="1" + style="stop-color:rgb(171,171,171)" + id="stop780" /></linearGradient><path + d="M118.078 14.981 C118.078 15.489 117.698 15.902 117.228 15.902 L105.329 15.902 C104.859 15.902 104.479 15.489 104.479 14.981 L104.479 6.846 C104.479 6.337 104.859 5.925 105.329 5.925 L117.228 5.925 C117.698 5.925 118.078 6.337 118.078 6.846 z" + style="fill:url(#XMLID_3_)" + id="path781" /><radialGradient + id="XMLID_4_" + cx="110.838" + cy="84.8354" + r="8.3752" + fx="110.838" + fy="84.8354" + gradientTransform="matrix(1 0 0 1.0845 0 -74.203)" + gradientUnits="userSpaceOnUse"><stop + offset="0" + style="stop-color:rgb(133,202,255)" + id="stop783" /><stop + offset="1" + style="stop-color:rgb(0,61,169)" + id="stop784" /></radialGradient><path + d="M117.567 14.674 C117.567 15.146 117.215 15.527 116.782 15.527 L105.776 15.527 C105.343 15.527 104.991 15.146 104.991 14.674 L104.991 7.153 C104.991 6.681 105.344 6.3 105.776 6.3 L116.782 6.3 C117.216 6.3 117.567 6.681 117.567 7.153 z" + style="fill:url(#XMLID_4_)" + id="path785" /><linearGradient + id="XMLID_5_" + gradientUnits="userSpaceOnUse" + x1="111.279" + y1="7.2524" + x2="111.279" + y2="14.6398"><stop + offset="0%" + style="stop-color:rgb(255,255,255)" + id="stop787" /><stop + offset="100%" + style="stop-opacity:0;stop-color:rgb(255,255,255)" + id="stop788" /></linearGradient><path + a:adobe-blending-mode="screen" + d="M116.373 6.565 L106.185 6.565 C105.782 6.565 105.456 6.919 105.456 7.353 L105.456 13.157 C106.719 13.297 108.938 13.184 111.189 11.348 C113.659 9.333 115.983 8.599 117.1 8.346 L117.1 7.353 C117.102 6.918 116.776 6.565 116.373 6.565 z" + style="fill:url(#XMLID_5_)" + id="path789" /></g><g + id="g790"><linearGradient + id="XMLID_6_" + gradientUnits="userSpaceOnUse" + x1="100.291" + y1="12.729" + x2="95.0383" + y2="7.9243"><stop + offset="0.0337" + style="stop-color:rgb(255,255,255)" + id="stop792" /><stop + offset="1" + style="stop-color:rgb(171,171,171)" + id="stop793" /></linearGradient><path + d="M98.136 7.922 C96.462 7.922 95.101 9.195 95.101 10.759 C95.101 12.324 96.462 13.595 98.136 13.595 C99.812 13.595 101.174 12.324 101.174 10.759 C101.174 9.195 99.812 7.922 98.136 7.922 z" + style="fill:url(#XMLID_6_)" + id="path794" /><g + id="g795"><g + id="g796"><linearGradient + id="XMLID_7_" + gradientUnits="userSpaceOnUse" + x1="98.1377" + y1="13.5942" + x2="98.1377" + y2="9.0691"><stop + offset="0" + style="stop-color:rgb(158,158,158)" + id="stop798" /><stop + offset="1" + style="stop-color:rgb(97,97,97)" + id="stop799" /></linearGradient><path + d="M100.558 10.759 C100.558 12.008 99.473 13.02 98.136 13.02 C96.801 13.02 95.717 12.008 95.717 10.759 C95.717 9.509 96.801 8.498 98.136 8.499 C99.474 8.499 100.558 9.51 100.558 10.759 z" + style="fill:url(#XMLID_7_)" + id="path800" /><path + d="M95.604 10.759 C95.604 12.066 96.74 13.127 98.136 13.127 C99.536 13.127 100.674 12.065 100.672 10.759 C100.674 9.453 99.536 8.388 98.136 8.388 C96.739 8.389 95.603 9.453 95.604 10.759 zM95.832 10.759 C95.832 9.572 96.865 8.605 98.136 8.605 C99.408 8.605 100.442 9.573 100.442 10.758 C100.442 11.946 99.409 12.911 98.136 12.911 C96.865 12.911 95.832 11.946 95.832 10.759 z" + style="fill:url(#XMLID_7_)" + id="path801" /><path + d="M98.147 10.823 C98.99 10.24 99.78 10.044 100.128 9.981 C99.794 9.277 99.04 8.787 98.163 8.787 C96.974 8.787 96.012 9.686 96.012 10.794 C96.012 10.969 96.034 11.137 96.078 11.3 C96.429 11.359 97.281 11.42 98.147 10.823 z" + style="fill:url(#XMLID_7_)" + id="path802" /></g><g + id="g803"><linearGradient + id="XMLID_10_" + gradientUnits="userSpaceOnUse" + x1="878.938" + y1="-1644.46" + x2="878.938" + y2="-1647.14" + gradientTransform="matrix(1 0 0 1 -780.801 1657.79)"><stop + offset="0" + style="stop-color:rgb(255,255,255)" + id="stop805" /><stop + offset="1" + style="stop-color:rgb(189,189,189)" + id="stop806" /></linearGradient><path + d="M100.558 10.759 C100.558 12.008 99.473 13.02 98.136 13.02 C96.801 13.02 95.717 12.008 95.717 10.759 C95.717 9.509 96.801 8.498 98.136 8.499 C99.474 8.499 100.558 9.51 100.558 10.759 z" + style="fill:url(#XMLID_10_)" + id="path807" /><linearGradient + id="XMLID_11_" + gradientUnits="userSpaceOnUse" + x1="98.1377" + y1="13.5942" + x2="98.1377" + y2="9.0692"><stop + offset="0" + style="stop-color:rgb(158,158,158)" + id="stop809" /><stop + offset="1" + style="stop-color:rgb(97,97,97)" + id="stop810" /><a:midPointStop + offset="0" + style="stop-color:#9E9E9E" + id="midPointStop811" /><a:midPointStop + offset="0.5" + style="stop-color:#9E9E9E" + id="midPointStop812" /><a:midPointStop + offset="1" + style="stop-color:#616161" + id="midPointStop813" /></linearGradient><path + d="M95.604 10.759 C95.604 12.066 96.74 13.127 98.136 13.127 C99.536 13.127 100.674 12.065 100.672 10.759 C100.674 9.453 99.536 8.388 98.136 8.388 C96.739 8.389 95.603 9.453 95.604 10.759 zM95.832 10.759 C95.832 9.572 96.865 8.605 98.136 8.605 C99.408 8.605 100.442 9.573 100.442 10.758 C100.442 11.946 99.409 12.911 98.136 12.911 C96.865 12.911 95.832 11.946 95.832 10.759 z" + style="fill:url(#XMLID_11_)" + id="path814" /><linearGradient + id="XMLID_12_" + gradientUnits="userSpaceOnUse" + x1="878.871" + y1="-1648.51" + x2="878.871" + y2="-1646.26" + gradientTransform="matrix(1 0 0 1 -780.801 1657.79)"><stop + offset="0" + style="stop-color:rgb(255,255,255)" + id="stop816" /><stop + offset="1" + style="stop-color:rgb(207,207,207)" + id="stop817" /><a:midPointStop + offset="0" + style="stop-color:#FFFFFF" + id="midPointStop818" /><a:midPointStop + offset="0.5" + style="stop-color:#FFFFFF" + id="midPointStop819" /><a:midPointStop + offset="1" + style="stop-color:#CFCFCF" + id="midPointStop820" /></linearGradient><path + d="M98.147 10.823 C98.99 10.24 99.78 10.044 100.128 9.981 C99.794 9.277 99.04 8.787 98.163 8.787 C96.974 8.787 96.012 9.686 96.012 10.794 C96.012 10.969 96.034 11.137 96.078 11.3 C96.429 11.359 97.281 11.42 98.147 10.823 z" + style="fill:url(#XMLID_12_)" + id="path821" /></g></g></g><linearGradient + id="XMLID_13_" + gradientUnits="userSpaceOnUse" + x1="1.9604" + y1="40.4526" + x2="94.542" + y2="40.9259"><stop + offset="0.0337" + style="stop-color:rgb(255,255,255)" + id="stop823" /><stop + offset="1" + style="stop-color:rgb(171,171,171)" + id="stop824" /></linearGradient><path + d="M0.598 72.876 L78.156 72.876 L78.156 8.411 L0.598 8.411 z" + style="fill:url(#XMLID_13_)" + id="path825" /><linearGradient + id="XMLID_14_" + gradientUnits="userSpaceOnUse" + x1="11.3472" + y1="4.8003" + x2="70.7822" + y2="64.236"><stop + offset="0" + style="stop-color:rgb(158,158,158)" + id="stop827" /><stop + offset="1" + style="stop-color:rgb(97,97,97)" + id="stop828" /></linearGradient><path + d="M76.78 40.929 L76.78 24.136 L1.048 24.136 L1.048 70.892 L123.866 70.892 L123.866 40.929 z" + style="opacity:0.7;fill:url(#XMLID_14_)" + id="path829" /><path + d="M33.185 23.986 L32.226 23.986 L32.226 70.83 L33.185 70.83 z" + style="opacity:0.5;fill:rgb(255,255,255)" + id="path830" /><linearGradient + id="XMLID_15_" + gradientUnits="userSpaceOnUse" + x1="18.3843" + y1="34.1831" + x2="35.1396" + y2="50.9386"><stop + offset="0" + style="stop-color:rgb(158,158,158)" + id="stop832" /><stop + offset="1" + style="stop-color:rgb(97,97,97)" + id="stop833" /></linearGradient><path + d="M32.089 23.986 L31.129 23.986 L31.129 70.83 L32.089 70.83 z" + style="opacity:0.7;fill:url(#XMLID_15_)" + id="path834" /><path + d="M124.769 72.857 L124.769 71.897 L0.594 71.897 L0.594 72.857 z" + style="opacity:0.5;fill:rgb(255,255,255)" + id="path835" /><linearGradient + id="XMLID_16_" + gradientUnits="userSpaceOnUse" + x1="406.499" + y1="-369.414" + x2="450.359" + y2="-325.553" + gradientTransform="matrix(0 1 -1 0 -272.114 -369.837)"><stop + offset="0" + style="stop-color:rgb(158,158,158)" + id="stop837" /><stop + offset="1" + style="stop-color:rgb(97,97,97)" + id="stop838" /></linearGradient><path + d="M124.769 71.76 L124.769 70.8 L0.594 70.8 L0.594 71.76 z" + style="opacity:0.7;fill:url(#XMLID_16_)" + id="path839" /><g + id="g840"><linearGradient + id="XMLID_17_" + gradientUnits="userSpaceOnUse" + x1="60.6079" + y1="23.2007" + x2="103.736" + y2="70.6773"><stop + offset="0.0337" + style="stop-color:rgb(255,255,255)" + id="stop842" /><stop + offset="1" + style="stop-color:rgb(171,171,171)" + id="stop843" /></linearGradient><path + d="M108.418 40.411 C108.418 58.182 94.014 72.588 76.24 72.588 C58.469 72.588 44.063 58.182 44.063 40.411 C44.063 22.638 58.47 8.231 76.24 8.231 C94.014 8.231 108.418 22.638 108.418 40.411 z" + style="fill:url(#XMLID_17_)" + id="path844" /><linearGradient + id="XMLID_18_" + gradientUnits="userSpaceOnUse" + x1="76.2402" + y1="79.2544" + x2="76.2402" + y2="17.2782"><stop + offset="0" + style="stop-color:rgb(158,158,158)" + id="stop846" /><stop + offset="1" + style="stop-color:rgb(97,97,97)" + id="stop847" /></linearGradient><path + d="M43.789 40.411 C43.789 58.304 58.347 72.862 76.24 72.862 C94.133 72.862 108.691 58.304 108.691 40.411 C108.691 22.516 94.133 7.958 76.24 7.958 C58.347 7.958 43.789 22.516 43.789 40.411 zM44.337 40.411 C44.337 22.818 58.649 8.505 76.24 8.505 C93.832 8.505 108.143 22.818 108.143 40.411 C108.143 58.002 93.831 72.314 76.24 72.314 C58.649 72.314 44.337 58.002 44.337 40.411 z" + style="fill:url(#XMLID_18_)" + id="path848" /><linearGradient + id="XMLID_19_" + gradientUnits="userSpaceOnUse" + x1="54.8203" + y1="40.3008" + x2="107.822" + y2="40.5717"><stop + offset="0.0337" + style="stop-color:rgb(255,255,255)" + id="stop850" /><stop + offset="1" + style="stop-color:rgb(171,171,171)" + id="stop851" /></linearGradient><path + d="M98.535 40.411 C98.535 52.724 88.554 62.704 76.24 62.704 C63.927 62.704 53.945 52.724 53.945 40.411 C53.945 28.096 63.927 18.116 76.24 18.116 C88.554 18.116 98.535 28.096 98.535 40.411 z" + style="fill:url(#XMLID_19_)" + id="path852" /><linearGradient + id="XMLID_20_" + gradientUnits="userSpaceOnUse" + x1="76.2402" + y1="67.395" + x2="76.2402" + y2="24.3415"><stop + offset="0" + style="stop-color:rgb(158,158,158)" + id="stop854" /><stop + offset="1" + style="stop-color:rgb(97,97,97)" + id="stop855" /><a:midPointStop + offset="0" + style="stop-color:#9E9E9E" + id="midPointStop856" /><a:midPointStop + offset="0.5" + style="stop-color:#9E9E9E" + id="midPointStop857" /><a:midPointStop + offset="1" + style="stop-color:#616161" + id="midPointStop858" /></linearGradient><path + d="M53.698 40.411 C53.698 52.841 63.811 62.953 76.24 62.953 C88.672 62.953 98.783 52.841 98.783 40.411 C98.783 27.98 88.672 17.866 76.24 17.866 C63.811 17.866 53.698 27.98 53.698 40.411 zM54.195 40.411 C54.195 28.253 64.085 18.364 76.24 18.364 C88.397 18.364 98.286 28.254 98.286 40.411 C98.286 52.567 88.397 62.456 76.24 62.456 C64.085 62.457 54.195 52.567 54.195 40.411 z" + style="fill:url(#XMLID_20_)" + id="path859" /><linearGradient + id="XMLID_21_" + gradientUnits="userSpaceOnUse" + x1="50.2085" + y1="58.3423" + x2="79.372" + y2="38.255"><stop + offset="0.0337" + style="stop-color:rgb(255,255,255)" + id="stop861" /><stop + offset="1" + style="stop-color:rgb(153,153,153)" + id="stop862" /></linearGradient><path + d="M95.883 40.411 C95.883 51.259 87.089 60.052 76.24 60.052 C65.392 60.052 56.599 51.259 56.599 40.411 C56.599 29.561 65.392 20.768 76.24 20.768 C87.089 20.768 95.883 29.561 95.883 40.411 z" + style="fill:url(#XMLID_21_)" + id="path863" /><linearGradient + id="XMLID_22_" + gradientUnits="userSpaceOnUse" + x1="60.4619" + y1="40.3301" + x2="99.5039" + y2="40.5297"><stop + offset="0.0337" + style="stop-color:rgb(255,255,255)" + id="stop865" /><stop + offset="1" + style="stop-color:rgb(171,171,171)" + id="stop866" /><a:midPointStop + offset="0.0337" + style="stop-color:#FFFFFF" + id="midPointStop867" /><a:midPointStop + offset="0.5" + style="stop-color:#FFFFFF" + id="midPointStop868" /><a:midPointStop + offset="1" + style="stop-color:#ABABAB" + id="midPointStop869" /></linearGradient><path + d="M92.663 40.411 C92.663 49.48 85.31 56.833 76.24 56.833 C67.172 56.833 59.818 49.479 59.818 40.411 C59.818 31.34 67.172 23.987 76.24 23.987 C85.311 23.987 92.663 31.34 92.663 40.411 z" + style="fill:url(#XMLID_22_)" + id="path870" /><linearGradient + id="XMLID_23_" + gradientUnits="userSpaceOnUse" + x1="76.2402" + y1="62.2417" + x2="76.2402" + y2="43.7143"><stop + offset="0.1685" + style="stop-color:rgb(255,255,255)" + id="stop872" /><stop + offset="1" + style="stop-color:rgb(54,54,54)" + id="stop873" /></linearGradient><path + d="M89.829 40.411 C89.829 47.916 83.748 53.998 76.24 53.998 C68.734 53.998 62.651 47.916 62.651 40.411 C62.651 32.904 68.734 26.82 76.24 26.82 C83.748 26.82 89.829 32.904 89.829 40.411 z" + style="fill:url(#XMLID_23_)" + id="path874" /><linearGradient + id="XMLID_24_" + gradientUnits="userSpaceOnUse" + x1="76.2402" + y1="60.3413" + x2="76.2402" + y2="43.4273"><stop + offset="0%" + style="stop-color:rgb(255,255,255)" + id="stop876" /><stop + offset="81.6092%" + style="stop-opacity:0;stop-color:rgb(255,255,255)" + id="stop877" /></linearGradient><path + d="M88.646 40.411 C88.646 47.262 83.095 52.816 76.241 52.816 C69.388 52.816 63.836 47.262 63.836 40.411 C63.836 33.558 69.388 28.004 76.241 28.004 C83.095 28.004 88.646 33.558 88.646 40.411 z" + style="fill:url(#XMLID_24_)" + id="path878" /><g + id="g879"><radialGradient + id="XMLID_25_" + cx="76.5068" + cy="49.4507" + r="14.8616" + fx="76.5068" + fy="49.4507" + gradientUnits="userSpaceOnUse"><stop + offset="0" + style="stop-color:rgb(187,235,255)" + id="stop881" /><stop + offset="1" + style="stop-color:rgb(0,61,215)" + id="stop882" /><a:midPointStop + offset="0" + style="stop-color:#BBEBFF" + id="midPointStop883" /><a:midPointStop + offset="0.5" + style="stop-color:#BBEBFF" + id="midPointStop884" /><a:midPointStop + offset="1" + style="stop-color:#003DD7" + id="midPointStop885" /></radialGradient><path + d="M84.212 40.41 C84.212 44.814 80.643 48.382 76.239 48.382 C71.839 48.382 68.266 44.814 68.266 40.41 C68.266 36.007 71.838 32.437 76.239 32.437 C80.643 32.438 84.212 36.007 84.212 40.41 z" + style="fill:url(#XMLID_25_)" + id="path886" /><linearGradient + id="XMLID_26_" + gradientUnits="userSpaceOnUse" + x1="76.2402" + y1="31.6812" + x2="76.2402" + y2="40.478"><stop + offset="8.04598%" + style="stop-color:rgb(255,255,255)" + id="stop888" /><stop + offset="100%" + style="stop-opacity:0;stop-color:rgb(255,255,255)" + id="stop889" /></linearGradient><path + a:adobe-blending-mode="screen" + d="M82.124 37.642 C82.124 40.253 79.489 42.37 76.239 42.37 C72.991 42.37 70.357 40.253 70.357 37.642 C70.357 35.029 72.991 32.911 76.239 32.911 C79.489 32.911 82.124 35.029 82.124 37.642 z" + style="fill:url(#XMLID_26_)" + id="path890" /></g></g><linearGradient + id="XMLID_27_" + gradientUnits="userSpaceOnUse" + x1="45.6553" + y1="22.3291" + x2="119.022" + y2="103.094"><stop + offset="0.0337" + style="stop-color:rgb(255,255,255)" + id="stop892" /><stop + offset="1" + style="stop-color:rgb(135,135,135)" + id="stop893" /><a:midPointStop + offset="0.0337" + style="stop-color:#FFFFFF" + id="midPointStop894" /><a:midPointStop + offset="0.5" + style="stop-color:#FFFFFF" + id="midPointStop895" /><a:midPointStop + offset="1" + style="stop-color:#878787" + id="midPointStop896" /></linearGradient><path + a:adobe-blending-mode="multiply" + d="M120.222 0.822 C120.4 1.395 120.496 2.004 120.496 2.636 L120.496 71.264 C120.496 74.637 117.763 77.371 114.389 77.371 L2.634 77.371 C2.003 77.371 1.395 77.275 0.821 77.098 C1.594 79.585 3.913 81.391 6.654 81.391 L118.407 81.391 C121.782 81.391 124.515 78.658 124.515 75.284 L124.515 6.656 C124.517 3.915 122.711 1.594 120.222 0.822 z" + style="opacity:0.5;fill:url(#XMLID_27_)" + id="path897" /><linearGradient + id="XMLID_28_" + gradientUnits="userSpaceOnUse" + x1="62.2686" + y1="8.9253" + x2="62.2686" + y2="98.9295"><stop + offset="0" + style="stop-color:rgb(255,255,255)" + id="stop899" /><stop + offset="1" + style="stop-color:rgb(0,0,0)" + id="stop900" /><a:midPointStop + offset="0" + style="stop-color:#FFFFFF" + id="midPointStop901" /><a:midPointStop + offset="0.5" + style="stop-color:#FFFFFF" + id="midPointStop902" /><a:midPointStop + offset="1" + style="stop-color:#000000" + id="midPointStop903" /></linearGradient><path + a:adobe-blending-mode="screen" + d="M3.655 14.071 C3.655 10.699 6.389 7.964 9.762 7.964 L64.444 7.964 C65.214 5.475 67.506 3.656 70.25 3.656 L121.516 3.656 C122.398 3.656 123.233 3.844 123.991 4.181 C123.041 2.043 120.902 0.548 118.409 0.548 L67.143 0.548 C64.399 0.548 62.108 2.368 61.337 4.857 L6.655 4.857 C3.281 4.857 0.548 7.592 0.548 10.965 L0.548 75.284 C0.548 77.776 2.043 79.916 4.183 80.865 C3.846 80.108 3.656 79.272 3.656 78.391 L3.656 14.071 z" + style="fill:url(#XMLID_28_)" + id="path904" /><linearGradient + id="XMLID_29_" + gradientUnits="userSpaceOnUse" + x1="62.5327" + y1="90.0122" + x2="62.5327" + y2="11.7673"><stop + offset="0" + style="stop-color:rgb(158,158,158)" + id="stop906" /><stop + offset="1" + style="stop-color:rgb(97,97,97)" + id="stop907" /><a:midPointStop + offset="0" + style="stop-color:#9E9E9E" + id="midPointStop908" /><a:midPointStop + offset="0.5" + style="stop-color:#9E9E9E" + id="midPointStop909" /><a:midPointStop + offset="1" + style="stop-color:#616161" + id="midPointStop910" /></linearGradient><path + d="M67.143 0 C64.373 0 61.986 1.732 61.004 4.309 C60.346 4.309 6.655 4.309 6.655 4.309 C2.985 4.309 0 7.294 0 10.965 L0 75.284 C0 78.955 2.985 81.94 6.655 81.94 L118.408 81.94 C122.079 81.94 125.064 78.955 125.064 75.284 L125.064 6.656 C125.064 2.986 122.079 0 118.408 0 zM1.096 75.285 L1.096 10.965 C1.096 7.899 3.59 5.405 6.655 5.405 L61.742 5.405 L61.861 5.019 C62.587 2.673 64.71 1.096 67.143 1.096 L118.409 1.096 C121.476 1.096 123.969 3.591 123.969 6.656 L123.969 75.285 C123.969 78.351 121.477 80.846 118.409 80.846 L6.655 80.846 C3.59 80.845 1.096 78.351 1.096 75.285 z" + style="fill:url(#XMLID_29_)" + id="path911" /></g></g></g></svg> diff --git a/src/gvdirpart/gvdirpart.cpp b/src/gvdirpart/gvdirpart.cpp new file mode 100644 index 0000000..f85f06d --- /dev/null +++ b/src/gvdirpart/gvdirpart.cpp @@ -0,0 +1,288 @@ +/* +Copyright 2004 Jonathan Riddell <[email protected]> + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Steet, Fifth Floor, Boston, MA 02111-1307, USA. + +*/ +#include "gvdirpart.moc" + +#include <qcursor.h> +#include <qfile.h> +#include <qsplitter.h> +#include <qvaluelist.h> + +#include <kdebug.h> +#include <kdeversion.h> +#include <kaction.h> +#include <kicontheme.h> +#include <kiconloader.h> +#include <klocale.h> +#include <kmimetype.h> +#include <kstandarddirs.h> +#include <kparts/browserextension.h> +#include <kparts/genericfactory.h> +#include <kio/job.h> + +#include <gvcore/fileoperation.h> +#include <gvcore/archive.h> +#include <gvcore/cache.h> +#include <gvcore/document.h> +#include <gvcore/fileviewbase.h> +#include <gvcore/fileviewcontroller.h> +#include <gvcore/printdialog.h> +#include <gvcore/imageview.h> +#include <gvcore/slideshow.h> +#include <gvcore/thumbnailloadjob.h> + +#include "config.h" +#include "gvdirpartconfig.h" + +namespace Gwenview { + +// For now let's duplicate +const char CONFIG_CACHE_GROUP[]="cache"; + + +//Factory Code +typedef KParts::GenericFactory<GVDirPart> GVDirFactory; +K_EXPORT_COMPONENT_FACTORY( libgvdirpart /*library name*/, GVDirFactory ) + +GVDirPart::GVDirPart(QWidget* parentWidget, const char* /*widgetName*/, QObject* parent, const char* name, + const QStringList &) : KParts::ReadOnlyPart( parent, name ) { + GVDirFactory::instance()->iconLoader()->addAppDir( "gwenview"); + setInstance( GVDirFactory::instance() ); + KGlobal::locale()->insertCatalogue("gwenview"); + KGlobal::locale()->setActiveCatalogue("gwenview"); + + mBrowserExtension = new GVDirPartBrowserExtension(this); + + mSplitter = new QSplitter(Qt::Horizontal, parentWidget, "gwenview-kpart-splitter"); + mSplitter->setFocusPolicy(QWidget::ClickFocus); + mSplitter->setOpaqueResize(true); + + // Create the widgets + mDocument = new Document(this); + mFileViewController = new FileViewController(mSplitter, actionCollection()); + int width=GVDirPartConfig::fileViewWidth(); + if (width!=-1) { + mFileViewController->resize(width, 10); + } + mImageView = new ImageView(mSplitter, mDocument, actionCollection()); + mSplitter->setResizeMode(mFileViewController, QSplitter::KeepSize); + + mSlideShow = new SlideShow(mDocument); + + setWidget(mSplitter); + + KStdAction::saveAs( mDocument, SLOT(saveAs()), actionCollection(), "saveAs" ); + new KAction(i18n("Rotate &Left"), "rotate_ccw", CTRL + Key_L, this, SLOT(rotateLeft()), actionCollection(), "rotate_left"); + new KAction(i18n("Rotate &Right"), "rotate_cw", CTRL + Key_R, this, SLOT(rotateRight()), actionCollection(), "rotate_right"); + + connect(mFileViewController, SIGNAL(requestContextMenu(const QPoint&, bool)), + mBrowserExtension, SLOT(openFileViewContextMenu(const QPoint&, bool)) ); + + connect(mFileViewController, SIGNAL(urlChanged(const KURL&)), + mDocument, SLOT(setURL(const KURL&)) ); + + connect(mFileViewController, SIGNAL(directoryChanged(const KURL&)), + this, SLOT(directoryChanged(const KURL&)) ); + + connect(mFileViewController, SIGNAL(selectionChanged()), + mBrowserExtension, SLOT(updateActions()) ); + + connect(mImageView, SIGNAL(requestContextMenu(const QPoint&)), + mBrowserExtension, SLOT(openImageViewContextMenu(const QPoint&)) ); + + connect(mSlideShow, SIGNAL(nextURL(const KURL&)), + this, SLOT(slotSlideShowChanged(const KURL&)) ); + + connect(mDocument, SIGNAL(loaded(const KURL&)), + this, SLOT(loaded(const KURL&)) ); + + // For wheel browsing + connect(mImageView, SIGNAL(selectPrevious()), + mFileViewController, SLOT(slotSelectPrevious()) ); + connect(mImageView, SIGNAL(selectNext()), + mFileViewController, SLOT(slotSelectNext()) ); + + mToggleSlideShow = new KToggleAction(i18n("Slide Show..."), "slideshow", 0, this, SLOT(toggleSlideShow()), actionCollection(), "slideshow"); + mToggleSlideShow->setCheckedState( i18n("Stop Slide Show" )); + + setXMLFile( "gvdirpart/gvdirpart.rc" ); + mBrowserExtension->updateActions(); +} + +GVDirPart::~GVDirPart() { + GVDirPartConfig::setFileViewWidth(mFileViewController->width()); + GVDirPartConfig::writeConfig(); + delete mSlideShow; +} + + +void GVDirPart::partActivateEvent(KParts::PartActivateEvent* event) { + if (event->activated()) { + KConfig* config=new KConfig("gwenviewrc"); + Cache::instance()->readConfig(config,CONFIG_CACHE_GROUP); + delete config; + } +} + + +KAboutData* GVDirPart::createAboutData() { + KAboutData* aboutData = new KAboutData( "gvdirpart", I18N_NOOP("GVDirPart"), + "0.1", I18N_NOOP("Image Browser"), + KAboutData::License_GPL, + "(c) 2004, Jonathan Riddell <[email protected]>"); + return aboutData; +} + +bool GVDirPart::openFile() { + //unused because openURL implemented + + //mDocument->setFilename(mFile); + return true; +} + +bool GVDirPart::openURL(const KURL& url) { + if (!url.isValid()) { + return false; + } + + emit started( 0 ); + m_url = url; + m_url.adjustPath(1); + + emit setWindowCaption( m_url.prettyURL() ); + mFileViewController->setDirURL(m_url); + + return true; +} + +void GVDirPart::loaded(const KURL& url) { + QString caption = url.filename(); + if( !mDocument->image().isNull()) + caption += QString(" %1 x %2").arg(mDocument->width()).arg(mDocument->height()); + emit setWindowCaption(caption); + emit completed(); +} + +KURL GVDirPart::pixmapURL() { + return mDocument->url(); +} + +void GVDirPart::toggleSlideShow() { + if (mToggleSlideShow->isChecked()) { + KURL::List list; + KFileItemListIterator it( *mFileViewController->currentFileView()->items() ); + for ( ; it.current(); ++it ) { + KFileItem* item=it.current(); + if (!item->isDir() && !Archive::fileItemIsArchive(item)) { + list.append(item->url()); + } + } + if (list.count()==0) { + mToggleSlideShow->setChecked(false); + return; + } + //FIXME turn on full screen here (anyone know how?) + mSlideShow->start(list); + } else { + //FIXME turn off full screen here + mSlideShow->stop(); + } +} + +void GVDirPart::print() { + KPrinter printer; + if ( !mDocument->filename().isEmpty() ) { + printer.setDocName( m_url.filename() ); + KPrinter::addDialogPage( new PrintDialogPage( mDocument, mImageView, "GV page")); + + if (printer.setup(mImageView, QString::null, true)) { + mDocument->print(&printer); + } + } +} + +void GVDirPart::rotateLeft() { + mDocument->transform(ImageUtils::ROT_270); +} + +void GVDirPart::rotateRight() { + mDocument->transform(ImageUtils::ROT_90); +} + +void GVDirPart::directoryChanged(const KURL& dirURL) { + if( dirURL == m_url ) return; + emit mBrowserExtension->openURLRequest(dirURL); +} + +void GVDirPart::slotSlideShowChanged(const KURL& url) { + mDocument->setURL( url ); + mFileViewController->setFileNameToSelect( url.filename()); +} + + +/***** GVDirPartBrowserExtension *****/ + +GVDirPartBrowserExtension::GVDirPartBrowserExtension(GVDirPart* viewPart, const char* name) + :KParts::BrowserExtension(viewPart, name) { + mGVDirPart = viewPart; + emit enableAction("print", true ); +} + +GVDirPartBrowserExtension::~GVDirPartBrowserExtension() { +} + +void GVDirPartBrowserExtension::updateActions() { + bool somethingSelected = mGVDirPart->fileViewController()->selectionSize() != 0; + emit enableAction("trash", somethingSelected); + emit enableAction("del", somethingSelected); +} + +void GVDirPartBrowserExtension::del() { + FileViewController* fv = mGVDirPart->fileViewController(); + FileOperation::realDelete(fv->selectedURLs(), fv); + +} + +void GVDirPartBrowserExtension::trash() { + FileViewController* fv = mGVDirPart->fileViewController(); + FileOperation::trash(fv->selectedURLs(), fv); +} + + +void GVDirPartBrowserExtension::openFileViewContextMenu(const QPoint& pos, bool onItem) { + if (onItem) { + const KFileItemList* items = mGVDirPart->fileViewController()->currentFileView()->selectedItems(); + emit popupMenu(pos, *items); + } else { + emit popupMenu(pos, mGVDirPart->fileViewController()->dirURL(), 0); + } +} + + +void GVDirPartBrowserExtension::openImageViewContextMenu(const QPoint& pos) { + KURL url=mGVDirPart->url(); + QString mimeType=KMimeType::findByURL(url)->name(); + emit popupMenu(pos, url, mimeType); +} + + +void GVDirPartBrowserExtension::print() { + mGVDirPart->print(); +} + +} // namespace diff --git a/src/gvdirpart/gvdirpart.desktop b/src/gvdirpart/gvdirpart.desktop new file mode 100644 index 0000000..f2632c6 --- /dev/null +++ b/src/gvdirpart/gvdirpart.desktop @@ -0,0 +1,44 @@ +[Desktop Entry] +Type=Service +Name=Image View +Name[bg]=Преглед на изображения +Name[br]=Gwell ar skeudennoù +Name[ca]=Vista d'imatge +Name[cs]=Prohlížeč obrázků +Name[da]=Billedvisning +Name[de]=Bildbetrachter +Name[el]=Προβολή εικόνων +Name[es]=Visor de imágenes +Name[et]=Pildinäitaja +Name[fa]=نمای تصویر +Name[fr]=Aperçu d'images +Name[gl]=Visor de Imaxes +Name[hr]=Pregled slike +Name[hu]=Képnézegető +Name[is]=Myndskoðun +Name[it]=Vista immagini +Name[ja]=画像表示 +Name[ka]=სურათების ნახვა +Name[ms]=Lihat Imej +Name[nds]=Bildansicht +Name[nl]=Afbeeldingweergave +Name[pa]=ਚਿੱਤਰ ਝਲਕ +Name[pt]=Visualizador de Imagens +Name[pt_BR]=Visualizador de Imagens +Name[ru]=Просмотр изображений +Name[sk]=Zobrazenie obrázku +Name[sr]=Приказ слика +Name[sr@Latn]=Prikaz slika +Name[sv]=Bildvisare +Name[ta]=பிம்பக் காட்சி +Name[tg]=Намоишгари тасвир +Name[tr]=Resim Görünümü +Name[uk]=Перегляд зображень +Name[vi]=Xem ảnh +Name[xx]=xxImage Viewxx +Name[zh_CN]=图像查看 +MimeType=inode/directory +ServiceTypes=KParts/ReadOnlyPart +X-KDE-Library=libgvdirpart +InitialPreference=5 +Icon=gvdirpart diff --git a/src/gvdirpart/gvdirpart.h b/src/gvdirpart/gvdirpart.h new file mode 100644 index 0000000..08b5626 --- /dev/null +++ b/src/gvdirpart/gvdirpart.h @@ -0,0 +1,174 @@ +/* +Copyright 2004 Jonathan Riddell <[email protected]> + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Steet, Fifth Floor, Boston, MA 02111-1307, USA. + +*/ +#ifndef __gvdirpart_h__ +#define __gvdirpart_h__ + +#include <kparts/part.h> +#include <kparts/browserextension.h> + +// Forward declarations +class QPoint; +class QSplitter; +class KAboutData; +class KAction; +class KToggleAction; + +namespace Gwenview { + +class ImageView; +class FileViewController; +class Document; +class SlideShow; + +class GVDirPart; + +/** + * The browser extension is an attribute of GVImagePart and provides + * some services to Konqueror. All Konqueror KParts have one. + */ +class GVDirPartBrowserExtension: public KParts::BrowserExtension { + Q_OBJECT + +public: + GVDirPartBrowserExtension(GVDirPart* viewPart, const char* name=0L); + ~GVDirPartBrowserExtension(); + +public slots: + void updateActions(); + + void trash(); + void del(); + + void print(); + +private slots: + void openFileViewContextMenu(const QPoint&, bool onItem); + void openImageViewContextMenu(const QPoint&); + +private: + GVDirPart* mGVDirPart; +}; + +/** + * A Read Only KPart to browse directories and their images using Gwenview + */ +class GVDirPart : public KParts::ReadOnlyPart { + Q_OBJECT +public: + GVDirPart(QWidget*, const char*, QObject*, const char*, const QStringList &); + virtual ~GVDirPart(); + + /** + * Return information about the part + */ + static KAboutData* createAboutData(); + + /** + * Returns the name of the current file in the pixmap + */ + KURL pixmapURL(); + + /** + * Print the image being viewed if there is one + */ + void print(); + + FileViewController* fileViewController() const { return mFileViewController; } + + +protected: + void partActivateEvent(KParts::PartActivateEvent* event); + + /** + * Unused because openURL() is implemented but required to be + * implemented. + */ + virtual bool openFile(); + + /** + * Tell the widgets the URL to browse. Sets the window + * caption and saves URL to m_url (important for history and + * others). + */ + virtual bool openURL(const KURL& url); + +protected slots: + /** + * Turns the slide show on or off + */ + void toggleSlideShow(); + + /** + * Sets Konqueror's caption, statusbar and emits completed(). + * Called by loaded() signal in GVDocument + */ + void loaded(const KURL& url); + + /** + * Rotates the current image 90 degrees counter clockwise + */ + void rotateLeft(); + + /** + * Rotates the current image 90 degrees clockwise + */ + void rotateRight(); + + void directoryChanged(const KURL& dirURL); + + void slotSlideShowChanged( const KURL& ); + +protected: + /** + * The component's widget, contains the files view on the left + * and scroll view on the right. + */ + QSplitter* mSplitter; + + /** + * Scroll widget + */ + ImageView* mImageView; + + /** + * Holds the image + */ + Document* mDocument; + + /** + * Shows the directory's files and folders + */ + + FileViewController* mFileViewController; + + /** + * This inherits from KParts::BrowserExtention and supplies + * some extra functionality to Konqueror. + */ + GVDirPartBrowserExtension* mBrowserExtension; + + /** + * Action turns on slide show + */ + KToggleAction* mToggleSlideShow; + SlideShow* mSlideShow; +}; + +} // namespace +#endif diff --git a/src/gvdirpart/gvdirpart.rc b/src/gvdirpart/gvdirpart.rc new file mode 100644 index 0000000..61717b9 --- /dev/null +++ b/src/gvdirpart/gvdirpart.rc @@ -0,0 +1,46 @@ +<!DOCTYPE kpartgui> +<kpartgui name="GVDirPart" version="5"> +<MenuBar> + <Menu name="file"><Text>&File</Text> + <Action name="saveAs"/> + </Menu> + <Menu name="edit"> + <Action name="rotate_left"/> + <Action name="rotate_right"/> + </Menu> + <Menu name="view"><Text>&View</Text> + <Action name="previous"/> + <Action name="next"/> + <Separator/> + <Menu name="colors"> + <text>&Colors</text> + <Action name="increase_gamma"/> + <Action name="decrease_gamma"/> + <Action name="increase_brightness"/> + <Action name="decrease_brightness"/> + <Action name="increase_contrast"/> + <Action name="decrease_contrast"/> + </Menu> + <Action name="view_zoom_to_fit"/> + <Action name="view_zoom_to_width"/> + <Action name="view_zoom_to_height"/> + <Action name="view_zoom_in"/> + <Action name="view_zoom_out"/> + <Action name="view_actual_size"/> + <Action name="view_zoom_lock"/> + <Separator/> + <Action name="slideshow"/> + </Menu> +</MenuBar> +<ToolBar name="mainToolBar"><text>Main Toolbar</text> + <Separator/> + <Action name="previous"/> + <Action name="next"/> + <Action name="view_zoom_in"/> + <Action name="view_zoom_to"/> + <Action name="view_zoom_out"/> + <Action name="rotate_left"/> + <Action name="rotate_right"/> + <Action name="slideshow"/> +</ToolBar> +</kpartgui> diff --git a/src/gvdirpart/gvdirpartconfig.kcfg b/src/gvdirpart/gvdirpartconfig.kcfg new file mode 100644 index 0000000..5296e51 --- /dev/null +++ b/src/gvdirpart/gvdirpartconfig.kcfg @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE kcfg SYSTEM "http://www.kde.org/standards/kcfg/1.0/kcfg.dtd"> +<kcfg> + <kcfgfile name="gvdirpartrc"/> + <group name="ui"> + <entry name="fileViewWidth" type="Int"> + <default>-1</default> + </entry> + </group> +</kcfg> diff --git a/src/gvdirpart/gvdirpartconfig.kcfgc b/src/gvdirpart/gvdirpartconfig.kcfgc new file mode 100644 index 0000000..e89b1be --- /dev/null +++ b/src/gvdirpart/gvdirpartconfig.kcfgc @@ -0,0 +1,4 @@ +File=gvdirpartconfig.kcfg +ClassName=GVDirPartConfig +Singleton=true +Mutators=true diff --git a/src/gvdirpart/hi16-app-gvdirpart.png b/src/gvdirpart/hi16-app-gvdirpart.png Binary files differnew file mode 100644 index 0000000..175c0df --- /dev/null +++ b/src/gvdirpart/hi16-app-gvdirpart.png diff --git a/src/gvdirpart/hi22-app-gvdirpart.png b/src/gvdirpart/hi22-app-gvdirpart.png Binary files differnew file mode 100644 index 0000000..d590c70 --- /dev/null +++ b/src/gvdirpart/hi22-app-gvdirpart.png diff --git a/src/gvdirpart/hi32-app-gvdirpart.png b/src/gvdirpart/hi32-app-gvdirpart.png Binary files differnew file mode 100644 index 0000000..9f3b269 --- /dev/null +++ b/src/gvdirpart/hi32-app-gvdirpart.png diff --git a/src/gvdirpart/hisc-app-gvdirpart.svg b/src/gvdirpart/hisc-app-gvdirpart.svg new file mode 100644 index 0000000..8846fb5 --- /dev/null +++ b/src/gvdirpart/hisc-app-gvdirpart.svg @@ -0,0 +1,226 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" +"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> +<!-- Created with Inkscape (http://www.inkscape.org/) --> +<svg + width="32.000000pt" + height="32.000000pt" + xmlns="http://www.w3.org/2000/svg" + xmlns:cc="http://web.resource.org/cc/" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:xlink="http://www.w3.org/1999/xlink" + id="svg1" + sodipodi:version="0.32" + inkscape:version="0.40" + sodipodi:docbase="/home/bk/kde/trunk/extragear/graphics/gwenview/gvdirpart" + sodipodi:docname="hisc-app-gvdirpart.svg" + inkscape:export-filename="/home/bk/kde/trunk/extragear/graphics/gwenview/gvdirpart/hi16-app-gvdirpart.png" + inkscape:export-xdpi="36.000000" + inkscape:export-ydpi="36.000000"> + <defs + id="defs3"> + <linearGradient + id="linearGradient1725"> + <stop + style="stop-color:#ef331a;stop-opacity:1.0000000;" + offset="0.0000000" + id="stop1726" /> + <stop + style="stop-color:#f8ffa7;stop-opacity:1.0000000;" + offset="1.0000000" + id="stop1727" /> + </linearGradient> + <linearGradient + id="linearGradient1718"> + <stop + style="stop-color:#1c9e1f;stop-opacity:1.0000000;" + offset="0.0000000" + id="stop1719" /> + <stop + style="stop-color:#ffff3b;stop-opacity:1.0000000;" + offset="1.0000000" + id="stop1720" /> + </linearGradient> + <linearGradient + id="linearGradient1693"> + <stop + style="stop-color:#611aef;stop-opacity:1.0000000;" + offset="0.0000000" + id="stop1694" /> + <stop + style="stop-color:#ffa7ed;stop-opacity:1.0000000;" + offset="1.0000000" + id="stop1695" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient1693" + id="linearGradient1696" + gradientTransform="matrix(0.827170,0.000000,0.000000,1.018056,0.000000,0.789474)" + x1="29.634966" + y1="4.5629220" + x2="29.634966" + y2="22.357569" + gradientUnits="userSpaceOnUse" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient1693" + id="linearGradient1701" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.827170,3.152320e-17,-3.250628e-17,1.018056,6.916667e-16,0.789474)" + x1="29.634966" + y1="4.5629220" + x2="29.634966" + y2="22.357569" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient1693" + id="linearGradient1705" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.827170,-6.893180e-17,1.213881e-16,1.018056,9.424121e-16,0.789474)" + x1="29.634966" + y1="4.5629220" + x2="29.634966" + y2="22.357569" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient1693" + id="linearGradient1709" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.827170,0.000000,0.000000,1.018056,3.497633e-16,0.789474)" + x1="29.634966" + y1="4.5629220" + x2="29.634966" + y2="22.357569" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient1718" + id="linearGradient1717" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.827170,-8.065772e-17,1.254169e-16,1.018056,-4.890799e-16,0.789474)" + x1="29.634966" + y1="4.5629220" + x2="29.634966" + y2="22.357569" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient1725" + id="linearGradient1724" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.827170,0.000000,0.000000,1.018056,-6.641825e-16,0.789474)" + x1="29.634966" + y1="4.5629220" + x2="29.634966" + y2="22.357569" /> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="16.000000" + inkscape:cx="27.304415" + inkscape:cy="15.588455" + inkscape:current-layer="g1698" + showgrid="false" + inkscape:grid-bbox="true" + gridspacingy="1.0000000pt" + gridspacingx="1.0000000pt" + gridoriginy="0.0000000pt" + gridoriginx="0.0000000pt" + inkscape:window-width="1272" + inkscape:window-height="951" + inkscape:window-x="0" + inkscape:window-y="0" /> + <metadata + id="metadata4"> + <rdf:RDF + id="RDF5"> + <cc:Work + rdf:about="" + id="Work6"> + <dc:format + id="format7">image/svg+xml</dc:format> + <dc:type + id="type9" + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <g + id="g1714" + transform="translate(-1.134055e-6,9.842154e-8)"> + <rect + style="fill:#ffffff;fill-opacity:1.0000000;fill-rule:nonzero;stroke:#000000;stroke-width:1.3250000;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-dashoffset:19.842520;stroke-opacity:1.0000000" + id="rect1703" + width="22.424654" + height="29.897104" + x="14.407233" + y="1.9307798" + rx="0.0000000" + ry="12.573674" + transform="matrix(0.654899,-0.106531,0.106531,0.654899,-9.201396,6.501194)" /> + <rect + style="fill:url(#linearGradient1717);fill-opacity:1.0;fill-rule:nonzero;stroke:none;stroke-width:1.3250000;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-dashoffset:19.842520;stroke-opacity:1.0000000;" + id="rect1704" + width="16.250000" + height="20.000000" + x="17.500000" + y="5.0000000" + ry="0.0000000" + transform="matrix(0.654899,-0.106531,0.106531,0.654899,-9.201396,6.501194)" /> + </g> + <g + id="g1721"> + <rect + style="fill:#ffffff;fill-opacity:1.0000000;fill-rule:nonzero;stroke:#000000;stroke-width:1.3250000;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-dashoffset:19.842520;stroke-opacity:1.0000000" + id="rect1707" + width="22.424654" + height="29.897104" + x="14.407233" + y="1.9307798" + rx="0.0000000" + ry="10.240688" + transform="matrix(0.814664,0.000000,0.000000,0.814664,-3.697339,1.466778)" /> + <rect + style="fill:url(#linearGradient1724);fill-opacity:1.0;fill-rule:nonzero;stroke:none;stroke-width:1.3250000;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-dashoffset:19.842520;stroke-opacity:1.0000000;" + id="rect1708" + width="16.250000" + height="20.000000" + x="17.500000" + y="5.0000000" + ry="0.0000000" + transform="matrix(0.814664,0.000000,0.000000,0.814664,-3.697339,1.466778)" /> + </g> + <g + id="g1698" + transform="matrix(0.979870,0.199637,-0.199637,0.979870,3.632494,0.797304)"> + <rect + style="fill:#ffffff;fill-opacity:1.0000000;fill-rule:nonzero;stroke:#000000;stroke-width:1.3250000;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-dashoffset:19.842520;stroke-opacity:1.0000000" + id="rect1064" + width="22.424654" + height="29.897104" + x="14.407233" + y="1.9307798" + rx="0.0000000" + ry="8.3427191" /> + <rect + style="fill:url(#linearGradient1701);fill-opacity:1.0000000;fill-rule:nonzero;stroke:none;stroke-width:1.3250000;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-dashoffset:19.842520;stroke-opacity:1.0000000" + id="rect1687" + width="16.250000" + height="20.000000" + x="17.500000" + y="5.0000000" + ry="0.0000000" /> + </g> + </g> +</svg> diff --git a/src/gvimagepart/Makefile.am b/src/gvimagepart/Makefile.am new file mode 100644 index 0000000..6ed0ff3 --- /dev/null +++ b/src/gvimagepart/Makefile.am @@ -0,0 +1,20 @@ +INCLUDES = -I$(srcdir)/.. $(all_includes) + +# These are not really libraries, but modules dynamically opened. +# So they should be installed in kde_module_dir, which is usually $kde_prefix/lib/kde3 +kde_module_LTLIBRARIES = libgvimagepart.la + +libgvimagepart_la_SOURCES = gvimagepart.cpp +libgvimagepart_la_LIBADD = $(LIB_KPARTS) ../gvcore/libgwenviewcore.la +libgvimagepart_la_LDFLAGS = $(all_libraries) $(KDE_PLUGIN) + +# Automatically generate moc files +METASOURCES = AUTO + +# Install the XML-UI file to its correct location +gvdir = $(kde_datadir)/gvimagepart +gv_DATA = gvimagepart.rc gvimagepartpopup.rc + +# Install the .desktop file into the kde_services directory +kde_services_DATA = gvimagepart.desktop + diff --git a/src/gvimagepart/gvimagepart.cpp b/src/gvimagepart/gvimagepart.cpp new file mode 100644 index 0000000..5750445 --- /dev/null +++ b/src/gvimagepart/gvimagepart.cpp @@ -0,0 +1,450 @@ +/* +Copyright 2004 Jonathan Riddell <[email protected]> + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Steet, Fifth Floor, Boston, MA 02111-1307, USA. + +*/ +#include "gvimagepart.moc" + +#include <qapplication.h> +#include <qcursor.h> +#include <qfile.h> +#include <qpoint.h> + +#include <kaction.h> +#include <kapplication.h> +#include <kconfig.h> +#include <kdebug.h> +#include <kdirlister.h> +#include <kfiledialog.h> +#include <kiconloader.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <kmimetype.h> +#include <kparts/genericfactory.h> + +#include <gvcore/cache.h> +#include <gvcore/document.h> +#include <gvcore/fileoperation.h> +#include <gvcore/printdialog.h> +#include <gvcore/imageview.h> +#include <gvcore/imageloader.h> +#include <gvcore/mimetypeutils.h> + +#include "config.h" + +namespace Gwenview { +// For now let's duplicate +const char CONFIG_CACHE_GROUP[]="cache"; + +#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 bool storeData(QWidget* parent, QFile* file, const QByteArray& data) { + uint sizeWritten = file->writeBlock(data); + if (sizeWritten != data.size()) { + KMessageBox::error( + parent, + i18n("Could not save image to a temporary file")); + return false; + } + return true; +} + + +//Factory Code +typedef KParts::GenericFactory<GVImagePart> GVImageFactory; +K_EXPORT_COMPONENT_FACTORY( libgvimagepart /*library name*/, GVImageFactory ) + +GVImagePart::GVImagePart(QWidget* parentWidget, const char* /*widgetName*/, QObject* parent, + const char* name, const QStringList &) + : KParts::ReadOnlyPart( parent, name ) + , mPrefetch( NULL ) + , mLastDirection( DirectionUnknown ) { + GVImageFactory::instance()->iconLoader()->addAppDir( "gwenview"); + setInstance( GVImageFactory::instance() ); + KGlobal::locale()->insertCatalogue("gwenview"); + KGlobal::locale()->setActiveCatalogue("gwenview"); + + mBrowserExtension = new GVImagePartBrowserExtension(this); + + // Create the widgets + mDocument = new Document(this); + connect( mDocument, SIGNAL( loading()), SLOT( slotLoading())); + connect( mDocument, SIGNAL( loaded(const KURL&)), SLOT( slotLoaded(const KURL&))); + mImageView = new ImageView(parentWidget, mDocument, actionCollection()); + connect( mImageView, SIGNAL(requestContextMenu(const QPoint&)), + this, SLOT(openContextMenu(const QPoint&)) ); + setWidget(mImageView); + + mDirLister = new KDirLister; + mDirLister->setAutoErrorHandlingEnabled( false, 0 ); + mDirLister->setMainWindow(KApplication::kApplication()->mainWidget()); + connect( mDirLister, SIGNAL( clear()), SLOT( dirListerClear())); + connect( mDirLister, SIGNAL( newItems( const KFileItemList& )), + SLOT( dirListerNewItems( const KFileItemList& ))); + connect(mDirLister,SIGNAL(deleteItem(KFileItem*)), + SLOT(dirListerDeleteItem(KFileItem*)) ); + + QStringList mimeTypes=MimeTypeUtils::rasterImageMimeTypes(); + mDirLister->setMimeFilter(mimeTypes); + mPreviousImage=new KAction(i18n("&Previous Image"), + QApplication::reverseLayout() ? "1rightarrow":"1leftarrow", Key_BackSpace, + this,SLOT(slotSelectPrevious()), actionCollection(), "previous"); + mNextImage=new KAction(i18n("&Next Image"), + QApplication::reverseLayout() ? "1leftarrow":"1rightarrow", Key_Space, + this,SLOT(slotSelectNext()), actionCollection(), "next"); + updateNextPrevious(); + + KStdAction::saveAs( this, SLOT(saveAs()), actionCollection(), "saveAs" ); + new KAction(i18n("Rotate &Left"), "rotate_ccw", CTRL + Key_L, this, SLOT(rotateLeft()), actionCollection(), "rotate_left"); + new KAction(i18n("Rotate &Right"), "rotate_cw", CTRL + Key_R, this, SLOT(rotateRight()), actionCollection(), "rotate_right"); + + setXMLFile( "gvimagepart/gvimagepart.rc" ); +} + +GVImagePart::~GVImagePart() { + delete mDirLister; +} + + +void GVImagePart::partActivateEvent(KParts::PartActivateEvent* event) { + if (event->activated()) { + KConfig* config=new KConfig("gwenviewrc"); + Cache::instance()->readConfig(config,CONFIG_CACHE_GROUP); + delete config; + } + KParts::ReadOnlyPart::partActivateEvent( event ); +} + + +void GVImagePart::guiActivateEvent( KParts::GUIActivateEvent* event) { + // Stolen from KHTMLImage + // + // prevent the base implementation from emitting setWindowCaption with + // our url. It destroys our pretty, previously caption. Konq saves/restores + // the caption for us anyway. + if (event->activated()) { + return; + } + KParts::ReadOnlyPart::guiActivateEvent(event); +} + + +KAboutData* GVImagePart::createAboutData() { + KAboutData* aboutData = new KAboutData( "gvimagepart", I18N_NOOP("GVImagePart"), + "0.1", I18N_NOOP("Image Viewer"), + KAboutData::License_GPL, + "(c) 2004, Jonathan Riddell <[email protected]>"); + return aboutData; +} + +bool GVImagePart::openURL(const KURL& url) { + if (!url.isValid()) { + return false; + } + KURL oldURLDir = m_url; + oldURLDir.setFileName( QString::null ); + KURL newURLDir = url; + newURLDir.setFileName( QString::null ); + bool sameDir = oldURLDir == newURLDir; + m_url = url; + emit started( 0 ); + if( mDocument->url() == url ) { // reload button in Konqy - setURL would return immediately + mDocument->reload(); + } else { + mDocument->setURL(url); + } + if( !sameDir ) { + mDirLister->openURL(mDocument->dirURL()); + mLastDirection = DirectionUnknown; + } + return true; +} + +QString GVImagePart::filePath() { + return m_file; +} + +void GVImagePart::slotLoading() { + emit setWindowCaption(mDocument->url().filename() + " - " + i18n("Loading...")); + // Set the location bar URL because we can arrive here if the user click on + // previous/next, which do not use openURLRequest + emit mBrowserExtension->setLocationBarURL(mDocument->url().pathOrURL()); + updateNextPrevious(); +} + +void GVImagePart::slotLoaded(const KURL& url) { + QString caption = url.filename() + QString(" - %1x%2").arg(mDocument->width()).arg(mDocument->height()); + emit setWindowCaption(caption); + emit completed(); + emit setStatusBarText(i18n("Done.")); + prefetchDone(); + mPrefetch = ImageLoader::loader( mLastDirection == DirectionPrevious ? previousURL() : nextURL(), + this, BUSY_PRELOADING ); + connect( mPrefetch, SIGNAL( imageLoaded( bool )), SLOT( prefetchDone())); +} + +void GVImagePart::prefetchDone() { + if( mPrefetch != NULL ) { + mPrefetch->release( this ); + } + mPrefetch = NULL; +} + +void GVImagePart::print() { + KPrinter printer; + + printer.setDocName( m_url.filename() ); + KPrinter::addDialogPage( new PrintDialogPage( mDocument, mImageView, "GV page")); + + if (printer.setup(mImageView, QString::null, true)) { + mDocument->print(&printer); + } +} + +void GVImagePart::rotateLeft() { + mDocument->transform(ImageUtils::ROT_270); +} + +void GVImagePart::rotateRight() { + mDocument->transform(ImageUtils::ROT_90); +} + +void GVImagePart::dirListerClear() { + mImagesInDirectory.clear(); + updateNextPrevious(); +} + +void GVImagePart::dirListerNewItems( const KFileItemList& list ) { + QPtrListIterator<KFileItem> it(list); + for( ; it.current(); ++it ) { + mImagesInDirectory.append( (*it)->name()); + } + qHeapSort( mImagesInDirectory ); + updateNextPrevious(); +} + +void GVImagePart::dirListerDeleteItem( KFileItem* item ) { + mImagesInDirectory.remove( item->name()); + updateNextPrevious(); +} + +void GVImagePart::updateNextPrevious() { + QStringList::ConstIterator current = mImagesInDirectory.find( mDocument->filename()); + if( current == mImagesInDirectory.end()) { + mNextImage->setEnabled( false ); + mPreviousImage->setEnabled( false ); + return; + } + mPreviousImage->setEnabled( current != mImagesInDirectory.begin()); + ++current; + mNextImage->setEnabled( current != mImagesInDirectory.end()); +} + +KURL GVImagePart::nextURL() const { + QStringList::ConstIterator current = mImagesInDirectory.find( mDocument->filename()); + if( current == mImagesInDirectory.end()) { + return KURL(); + } + ++current; + if( current == mImagesInDirectory.end()) { + return KURL(); + } + KURL newURL = mDocument->dirURL(); + newURL.setFileName( *current ); + return newURL; +} + +void GVImagePart::slotSelectNext() { + KURL newURL = nextURL(); + if( newURL.isEmpty()) return; + mLastDirection = DirectionNext; + // Do not use mBrowserExtension->openURLRequest to avoid switching to + // another KPart + openURL(newURL); + emit mBrowserExtension->openURLNotify(); +} + +KURL GVImagePart::previousURL() const { + QStringList::ConstIterator current = mImagesInDirectory.find( mDocument->filename()); + if( current == mImagesInDirectory.end() || current == mImagesInDirectory.begin()) { + return KURL(); + } + --current; + KURL newURL = mDocument->dirURL(); + newURL.setFileName( *current ); + return newURL; +} + +void GVImagePart::slotSelectPrevious() { + KURL newURL = previousURL(); + if( newURL.isEmpty()) return; + mLastDirection = DirectionPrevious; + openURL(newURL); + emit mBrowserExtension->openURLNotify(); +} + + +void GVImagePart::saveAs() { + if (!mDocument->isModified()) { + saveOriginalAs(); + return; + } + + if (mDocument->canBeSaved()) { + mDocument->saveAs(); + return; + } + + KGuiItem saveItem(i18n("&Save Original"), "filesaveas"); + int result = KMessageBox::warningContinueCancel( + widget(), + i18n("Gwenview KPart can't save the modifications you made. Do you want to save the original image?"), + i18n("Warning"), + saveItem); + + if (result == KMessageBox::Cancel) return; + + saveOriginalAs(); +} + + +void GVImagePart::showJobError(KIO::Job* job) { + if (job->error() != 0) { + job->showErrorDialog(widget()); + } +} + + +void GVImagePart::saveOriginalAs() { + KURL srcURL = mDocument->url(); + KURL dstURL = KFileDialog::getSaveURL( + srcURL.fileName(), + QString::null, + widget()); + if (!dstURL.isValid()) return; + + // Try to get data from the cache to avoid downloading the image again. + QByteArray data = Cache::instance()->file(srcURL); + + if (data.size() == 0) { + // We need to read the image again. Let KIO::copy do the work. + KIO::Job* job = KIO::copy(srcURL, dstURL); + job->setWindow(widget()); + connect(job, SIGNAL(result(KIO::Job*)), + this, SLOT(showJobError(KIO::Job*)) ); + return; + } + + if (dstURL.isLocalFile()) { + // Destination is a local file, store it ourself + QString path = dstURL.path(); + QFile file(path); + if (!file.open(IO_WriteOnly)) { + KMessageBox::error( + widget(), + i18n("Could not open '%1' for writing.").arg(path)); + return; + } + storeData(widget(), &file, data); + return; + } + + // We need to send the data to a remote location + new DataUploader(widget(), data, dstURL); +} + + +DataUploader::DataUploader(QWidget* dialogParent, const QByteArray& data, const KURL& dstURL) +: mDialogParent(dialogParent) +{ + mTempFile.setAutoDelete(true); + + // Store it in a temp file + if (! storeData(dialogParent, mTempFile.file(), data) ) return; + + // Now upload it + KURL tmpURL; + tmpURL.setPath(mTempFile.name()); + KIO::Job* job = KIO::copy(tmpURL, dstURL); + job->setWindow(dialogParent); + connect(job, SIGNAL(result(KIO::Job*)), + this, SLOT(slotJobFinished(KIO::Job*)) ); +} + + +void DataUploader::slotJobFinished(KIO::Job* job) { + if (job->error() != 0) { + job->showErrorDialog(mDialogParent); + } + + delete this; +} + + +/** + * Overload KXMLGUIClient so that we can call setXML + */ +class PopupGUIClient : public KXMLGUIClient { +public: + PopupGUIClient( KInstance *inst, const QString &doc ) { + setInstance( inst ); + setXML( doc ); + } +}; + + +void GVImagePart::openContextMenu(const QPoint& pos) { + QString doc = KXMLGUIFactory::readConfigFile( "gvimagepartpopup.rc", true, instance() ); + PopupGUIClient guiClient(instance(), doc); + + KStdAction::saveAs( this, SLOT(saveAs()), guiClient.actionCollection(), "saveAs" ); + + KParts::URLArgs urlArgs; + urlArgs.serviceType = mDocument->mimeType(); + + KParts::BrowserExtension::PopupFlags flags = + KParts::BrowserExtension::ShowNavigationItems + | KParts::BrowserExtension::ShowUp + | KParts::BrowserExtension::ShowReload; + + emit mBrowserExtension->popupMenu(&guiClient, pos, m_url, urlArgs, flags, S_IFREG); +} + + +/***** GVImagePartBrowserExtension *****/ + +GVImagePartBrowserExtension::GVImagePartBrowserExtension(GVImagePart* viewPart, const char* name) + :KParts::BrowserExtension(viewPart, name) { + mGVImagePart = viewPart; + emit enableAction("print", true ); +} + +GVImagePartBrowserExtension::~GVImagePartBrowserExtension() { +} + +void GVImagePartBrowserExtension::print() { + mGVImagePart->print(); +} + +} // namespace diff --git a/src/gvimagepart/gvimagepart.desktop b/src/gvimagepart/gvimagepart.desktop new file mode 100644 index 0000000..97ce129 --- /dev/null +++ b/src/gvimagepart/gvimagepart.desktop @@ -0,0 +1,48 @@ +[Desktop Entry] +Type=Service +Name=Gwenview Image Viewer +Name[bg]=Gwenview - преглед на изображения +Name[br]=Gweler skeudennoù Gwenview +Name[ca]=Visor d'imatges Gwenview +Name[cs]=Prohlížeč obrázků Gwenview +Name[da]=Gwenview billedfremviser +Name[de]=Gwenview Bildbetrachter +Name[el]=Προβολέας εικόνων Gwenview +Name[es]=Visor de imágenes Gwenview +Name[et]=Gwenview pildinäitaja +Name[fa]=مشاهدهگر تصویر Gwenview +Name[fi]=Gwenview-kuvankatseluohjelma +Name[fr]=Aperçu d'images Gwenview +Name[gl]=Visor de Imaxes Gwenview +Name[hi]=ग्वेन-व्यू छवि प्रदर्शक +Name[hr]=Gwenview preglednik slika +Name[hu]=Gwenview képnézegető +Name[is]=Gwenview myndaskoðari +Name[it]=Visualizzatore di immagini Gwenview +Name[ja]=Gwenview 画像ビューア +Name[ka]=Gwenview სურათების დამთვალიერებელი +Name[ms]=Pelihat imej Gwenview +Name[nds]=Bildkieker Gwenview +Name[nl]=Gwenview Afbeeldingenweergave +Name[pa]=Gwenview ਚਿੱਤਰ ਦਰਸ਼ਕ +Name[pl]=Przeglądarka obrazków Gwenview +Name[pt]=Visualizador de Imagens Gwenview +Name[pt_BR]=Visualizador de Imagens Gwenview +Name[ru]=Gwenview +Name[sk]=Prehliadač obrázkov Gwenview +Name[sl]=Pregledovalnik slik Gwenview +Name[sr]=Прегледач слика Gwenview +Name[sr@Latn]=Pregledač slika Gwenview +Name[sv]=Gwenview bildvisare +Name[ta]=Gwenview பிம்பம் பார்வையாளர் +Name[tg]=Намоишгари тасвири Gwenview +Name[tr]=Gwenview Resim Gösterici +Name[uk]=Переглядач зображень - Gwenview +Name[vi]=Bộ xem ảnh Gwenview +Name[xx]=xxGwenview Image Viewerxx +Name[zh_CN]=Gwenview 图像查看器 +MimeType=image/gif;image/jpeg;image/png;image/x-bmp;image/x-eps;image/x-ico;image/x-krl;image/x-portable-bitmap;image/x-portable-pixmap;image/x-xbm;image/x-xpm +ServiceTypes=KParts/ReadOnlyPart +X-KDE-Library=libgvimagepart +InitialPreference=5 +Icon=gwenview diff --git a/src/gvimagepart/gvimagepart.h b/src/gvimagepart/gvimagepart.h new file mode 100644 index 0000000..d77adc7 --- /dev/null +++ b/src/gvimagepart/gvimagepart.h @@ -0,0 +1,181 @@ +/* +Copyright 2004 Jonathan Riddell <[email protected]> + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Steet, Fifth Floor, Boston, MA 02111-1307, USA. + +*/ +#ifndef __gvimagepart_h__ +#define __gvimagepart_h__ + +#include <kparts/part.h> +#include <kparts/browserextension.h> +#include <ktempfile.h> + +// Forward declarations +class QFile; +class QPoint; + +class KAboutData; +class KAction; +class KDirLister; +class KFileItem; + +namespace Gwenview { +class ImageView; +class Document; +class ImageLoader; + +class GVImagePart; + +/** + * The browser extension is an attribute of GVImagePart and provides + * some services to Konqueror. All Konqueror KParts have one. + */ +class GVImagePartBrowserExtension: public KParts::BrowserExtension { + Q_OBJECT + +public: + GVImagePartBrowserExtension(GVImagePart* viewPart, const char* name=0L); + ~GVImagePartBrowserExtension(); + +public slots: + void print(); + +private: + GVImagePart* mGVImagePart; + +}; + +/** + * A Read Only KPart to view images using Gwenview + */ +class GVImagePart : public KParts::ReadOnlyPart { + Q_OBJECT +public: + GVImagePart(QWidget*, const char*, QObject*, const char*, const QStringList &); + virtual ~GVImagePart(); + + /** + * Return information about the part + */ + static KAboutData* createAboutData(); + + /** + * Returns m_file + */ + QString filePath(); + + /** + * Print the image being viewed + */ + void print(); + +public slots: + virtual bool openURL(const KURL& url); + +protected slots: + virtual bool openFile() { return false; } + + /** + * Rotates the current image 90 degrees counter clockwise + */ + void rotateLeft(); + + /** + * Rotates the current image 90 degrees clockwise + */ + void rotateRight(); + +protected: + virtual void partActivateEvent(KParts::PartActivateEvent* event); + virtual void guiActivateEvent( KParts::GUIActivateEvent* event); + +private slots: + + void dirListerClear(); + + void dirListerNewItems( const KFileItemList& ); + + void dirListerDeleteItem(KFileItem*); + + void slotSelectNext(); + void slotSelectPrevious(); + + void prefetchDone(); + + void slotLoading(); + void slotLoaded(const KURL& url); + + void openContextMenu(const QPoint&); + + void saveAs(); + + void showJobError(KIO::Job* job); + +private: + + void updateNextPrevious(); + KURL nextURL() const; + KURL previousURL() const; + void saveOriginalAs(); + + /** + * The component's widget + */ + ImageView* mImageView; + + /** + * Holds the image + */ + Document* mDocument; + + /** + * This inherits from KParts::BrowserExtention and supplies + * some extra functionality to Konqueror. + */ + GVImagePartBrowserExtension* mBrowserExtension; + + // for the next/previous actions + KDirLister* mDirLister; + + KAction* mNextImage; + KAction* mPreviousImage; + // alphabetically sorted filenames of images in the picture's directory + QStringList mImagesInDirectory; + + ImageLoader* mPrefetch; + enum LastDirection { DirectionUnknown, DirectionNext, DirectionPrevious }; + LastDirection mLastDirection; // used for prefetching +}; + + +/** + * This simple helper class uploads data to a remote URL asynchronously + */ +class DataUploader : public QObject { + Q_OBJECT +public: + DataUploader(QWidget* dialogParent, const QByteArray& data, const KURL& destURL); + +private slots: + void slotJobFinished(KIO::Job*); + +private: + KTempFile mTempFile; + QWidget* mDialogParent; +}; + +} // namespace +#endif diff --git a/src/gvimagepart/gvimagepart.rc b/src/gvimagepart/gvimagepart.rc new file mode 100644 index 0000000..af4e9e4 --- /dev/null +++ b/src/gvimagepart/gvimagepart.rc @@ -0,0 +1,40 @@ +<!DOCTYPE kpartgui> +<kpartgui name="GVImagePart" version="6"> +<MenuBar> + <Menu name="file"><Text>&File</Text> + <Action name="saveAs"/> + </Menu> + <Menu name="edit"> + <Action name="rotate_left"/> + <Action name="rotate_right"/> + </Menu> + <Menu name="view"><Text>&View</Text> + <Separator/> + <Menu name="colors"> + <text>&Colors</text> + <Action name="increase_gamma"/> + <Action name="decrease_gamma"/> + <Action name="increase_brightness"/> + <Action name="decrease_brightness"/> + <Action name="increase_contrast"/> + <Action name="decrease_contrast"/> + </Menu> + <Action name="view_zoom_to_fit"/> + <Action name="view_zoom_to_width"/> + <Action name="view_zoom_to_height"/> + <Action name="view_zoom_in"/> + <Action name="view_zoom_out"/> + <Action name="view_actual_size"/> + <Action name="view_zoom_lock"/> + </Menu> +</MenuBar> +<ToolBar name="mainToolBar"><text>Main Toolbar</text> + <Action name="previous"/> + <Action name="next"/> + <Action name="view_zoom_in"/> + <Action name="view_zoom_to"/> + <Action name="view_zoom_out"/> + <Action name="rotate_left"/> + <Action name="rotate_right"/> +</ToolBar> +</kpartgui> diff --git a/src/gvimagepart/gvimagepartpopup.rc b/src/gvimagepart/gvimagepartpopup.rc new file mode 100644 index 0000000..a1b7a6d --- /dev/null +++ b/src/gvimagepart/gvimagepartpopup.rc @@ -0,0 +1,7 @@ +<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd"> +<kpartgui name="gvimagepartpopup" version="1"> + <Menu name="popupmenu"> + <Separator /> + <Action name="saveAs" /> + </Menu> +</kpartgui> diff --git a/src/imageutils/Makefile.am b/src/imageutils/Makefile.am new file mode 100644 index 0000000..33c83b3 --- /dev/null +++ b/src/imageutils/Makefile.am @@ -0,0 +1,36 @@ +# QT_CLEAN_NAMESPACE is needed when building with automake, otherwise +# compilation fails in jpegcontent.cpp +AM_CPPFLAGS = -I$(srcdir) -I$(srcdir)/.. $(LIBEXIF_CFLAGS) $(all_includes) \ + -DQT_CLEAN_NAMESPACE +AM_CCASFLAGS = -I$(srcdir) $(GV_ASM_DEFS) + +KDE_CXXFLAGS = $(USE_EXCEPTIONS) + +noinst_LTLIBRARIES = libgvimageutils.la + +libgvimageutils_la_SOURCES = \ + imageutils.cpp \ + jpegcontent.cpp \ + scale.cpp \ + transupp.c \ + asm_scale.S \ + croppedqimage.cpp + +libgvimageutils_la_LIBADD = $(LIB_KDECORE) $(LIBQT) $(LIBJPEG) $(LIB_EXIV2) + +noinst_HEADERS = \ + orientation.h \ + imageutils.h \ + jpegcontent.h \ + jinclude.h \ + jpegint.h \ + transupp.h \ + jpegerrormanager.h \ + croppedqimage.h + +METASOURCES = AUTO + +check_PROGRAMS = testjpegcontent +testjpegcontent_SOURCES = testjpegcontent.cpp +testjpegcontent_LDADD = $(LIB_KFILE) libgvimageutils.la +testjpegcontent_LDFLAGS = $(all_libraries) diff --git a/src/imageutils/README b/src/imageutils/README new file mode 100644 index 0000000..335d36e --- /dev/null +++ b/src/imageutils/README @@ -0,0 +1,2 @@ +This directory contains image utilities. It contains code from the JPEGLib to +perform lossless transformations and from the ImageMagick library. diff --git a/src/imageutils/asm_scale.S b/src/imageutils/asm_scale.S new file mode 100644 index 0000000..08b43da --- /dev/null +++ b/src/imageutils/asm_scale.S @@ -0,0 +1,810 @@ +#ifdef HAVE_X86_MMX + +#ifdef __EMX__ +/* Due to strange behaviour of as.exe we use this macros */ +/* For all OS/2 coders - please use PGCC to compile this code */ +#define PR_(foo) ___##foo +#define PT_(foo,func) ___##foo,func +#define SIZE(sym) \ + .___end_##sym:; \ + .size ___##sym,.___end_##sym-___##sym; \ + .align 8; +#else +#define PR_(foo) __##foo +#define PT_(foo,func) __##foo,func +#define SIZE(sym) \ + .__end_##sym:; \ + .size __##sym,.__end_##sym-__##sym; \ + .align 8; +#endif + +/*\ +|*| MMX assembly scaling routine for Imlib2 +|*| Written by Willem Monsuwe <[email protected]> +\*/ + +.text + .align 8 +.globl PR_(mimageScale_mmx_AARGBA) +/* .type PT_(mimageScale_mmx_AARGBA,@function) */ + + +/*\ Prototype: __mimageScale_mmx_AARGBA(ImlibScaleInfo *isi, DATA32 *dest, +|*| int dxx, int dyy, int dx, int dy, int dw, int dh, int dow, int sow) +\*/ + +#define isi 8(%ebp) +#define dest 12(%ebp) +#define dxx 16(%ebp) +#define dyy 20(%ebp) +#define dx 24(%ebp) +#define dy 28(%ebp) +#define dw 32(%ebp) +#define dh 36(%ebp) +#define dow 40(%ebp) +#define sow 44(%ebp) + +/*\ Local variables that didn't fit in registers \*/ +#define y -4(%ebp) +#define yp -8(%ebp) +#define yap -12(%ebp) +#define xp -16(%ebp) +#define xap -20(%ebp) +#define Cx -24(%ebp) +#define Mx -28(%ebp) +#define Cy -32(%ebp) +#define My -36(%ebp) +#define sow_4 -40(%ebp) + +/*\ When %edx points to ImlibScaleInfo, these are the members \*/ +#define xpoints (%edx) +#define ypoints 4(%edx) +#define xapoints 8(%edx) +#define yapoints 12(%edx) +#define xup_yup 16(%edx) + +PR_(mimageScale_mmx_AARGBA): + pushl %ebp + movl %esp, %ebp + subl $40, %esp + pushl %ebx + pushl %ecx + pushl %edx + pushl %edi + pushl %esi + movl isi, %edx + + /*\ Check (dw > 0) && (dh > 0) \*/ + cmpl $0, dw + jle .scale_leave + cmpl $0, dh + jle .scale_leave + + /*\ X-based array pointers point to the end; we're looping up to 0 \*/ + /*\ %edi = dest + dow * dy + dx + dw \*/ + movl dow, %eax + imull dy, %eax + addl dx, %eax + addl dw, %eax + movl dest, %edi + leal (%edi, %eax, 4), %edi + /*\ xp = xpoints + dxx + dw \*/ + movl dxx, %ebx + addl dw, %ebx + movl xpoints, %eax + leal (%eax, %ebx, 4), %eax + movl %eax, xp + /*\ xap = xapoints + dxx + dw \*/ + movl xapoints, %eax + leal (%eax, %ebx, 4), %eax + movl %eax, xap + /*\ y = dh \*/ + movl dh, %eax + movl %eax, y + /*\ yp = ypoints + dyy \*/ + movl dyy, %ebx + movl ypoints, %eax + leal (%eax, %ebx, 4), %eax + movl %eax, yp + /*\ yap = yapoints + dyy \*/ + movl yapoints, %eax + leal (%eax, %ebx, 4), %eax + movl %eax, yap + + pxor %mm7, %mm7 + + /*\ Test xup bit \*/ + movl xup_yup, %eax + sarl $1, %eax + jnc .scale_x_down + +.scale_x_up: + /*\ Test yup bit \*/ + sarl $1, %eax + jnc .scale_x_up_y_down + + +/*\ Scaling up both ways \*/ + +.scale_x_up_y_up: + movl sow, %ebx + +.up_up_loop_y: + + /*\ x = -dw \*/ + movl dw, %ecx + negl %ecx + + /*\ %eax = *yap << 4 \*/ + movl yap, %eax + movl (%eax), %eax + sall $4, %eax + jz .up_up_yap_0 + movd %eax, %mm1 + punpcklwd %mm1, %mm1 + punpckldq %mm1, %mm1 + +.up_up_loop1_x: + /*\ %esi = *yp + xp[x] \*/ + movl yp, %eax + movl (%eax), %esi + movl xp, %eax + movl (%eax, %ecx, 4), %eax + leal (%esi, %eax, 4), %esi + + /*\ %eax = xap[x] << 4 \*/ + movl xap, %eax + movl (%eax, %ecx, 4), %eax + sall $4, %eax + jz .up_up_xap_0 + + /*\ %mm0 = xap[x] << 4 \*/ + movd %eax, %mm0 + punpcklwd %mm0, %mm0 + punpckldq %mm0, %mm0 + + /*\ Load and unpack four pixels in parralel + |*| %mm2 = ptr[0], %mm3 = ptr[1] + |*| %mm4 = ptr[sow], %mm5 = ptr[sow + 1] + \*/ + movq (%esi), %mm2 + movq (%esi, %ebx, 4), %mm4 + movq %mm2, %mm3 + movq %mm4, %mm5 + punpcklbw %mm7, %mm2 + punpcklbw %mm7, %mm4 + punpckhbw %mm7, %mm3 + punpckhbw %mm7, %mm5 + + /*\ X interpolation: r = l + (r - l) * xap \*/ + psubw %mm2, %mm3 + psubw %mm4, %mm5 + psllw $4, %mm3 + psllw $4, %mm5 + pmulhw %mm0, %mm3 + pmulhw %mm0, %mm5 + paddw %mm2, %mm3 + paddw %mm4, %mm5 + /*\ Now %mm3 = I(ptr[0], ptr[1]), %mm5 = I(ptr[sow], ptr[sow + 1]) \*/ + jmp .up_up_common +.up_up_xap_0: + /*\ Load and unpack two pixels + |*| %mm3 = ptr[0], %mm5 = ptr[sow] + \*/ + movd (%esi), %mm3 + movd (%esi, %ebx, 4), %mm5 + punpcklbw %mm7, %mm3 + punpcklbw %mm7, %mm5 +.up_up_common: + /*\ Y interpolation: d = u + (d - u) * yap \*/ + psubw %mm3, %mm5 + psllw $4, %mm5 + pmulhw %mm1, %mm5 + paddw %mm3, %mm5 + packuswb %mm5, %mm5 + movd %mm5, (%edi, %ecx, 4) + + /*\ while (++x) \*/ + incl %ecx + jnz .up_up_loop1_x + jmp .up_up_yap_end +.up_up_yap_0: + +.up_up_loop2_x: + /*\ %esi = *yp + xp[x] \*/ + movl yp, %eax + movl (%eax), %esi + movl xp, %eax + movl (%eax, %ecx, 4), %eax + leal (%esi, %eax, 4), %esi + + /*\ %eax = xap[x] << 4 \*/ + movl xap, %eax + movl (%eax, %ecx, 4), %eax + sall $4, %eax + jz .up_up_0 + + /*\ %mm0 = xap[x] << 4 \*/ + movd %eax, %mm0 + punpcklwd %mm0, %mm0 + punpckldq %mm0, %mm0 + + /*\ Load and unpack two pixels in parralel + |*| %mm2 = ptr[0], %mm3 = ptr[1] + \*/ + movq (%esi), %mm2 + movq %mm2, %mm3 + punpcklbw %mm7, %mm2 + punpckhbw %mm7, %mm3 + + /*\ X interpolation: r = l + (r - l) * xap \*/ + psubw %mm2, %mm3 + psllw $4, %mm3 + pmulhw %mm0, %mm3 + paddw %mm2, %mm3 + packuswb %mm3, %mm3 + movd %mm3, (%edi, %ecx, 4) + jmp .up_up_1 +.up_up_0: + /*\ dptr[x] = *sptr \*/ + movl (%esi), %eax + movl %eax, (%edi, %ecx, 4) +.up_up_1: + incl %ecx + jnz .up_up_loop2_x + +.up_up_yap_end: + /*\ dptr += dow \*/ + movl dow, %eax + leal (%edi, %eax, 4), %edi + /*\ yap++; yp++ \*/ + addl $4, yap + addl $4, yp + /*\ while (y--) \*/ + decl y + jnz .up_up_loop_y + + jmp .scale_leave + + +/*\ Scaling down vertically \*/ + +.scale_x_up_y_down: + /*\ sow_4 = sow * 4 \*/ + movl sow, %eax + sall $2, %eax + movl %eax, sow_4 + +.up_down_loop_y: + + /*\ Setup My and Cy \*/ + movl yap, %eax + movzwl (%eax), %ebx + movl %ebx, My + movzwl 2(%eax), %eax + movl %eax, Cy + + /*\ mm4 = Cy \*/ + movd %eax, %mm4 + punpcklwd %mm4, %mm4 + punpckldq %mm4, %mm4 + /*\ mm5 = My \*/ + movd %ebx, %mm5 + punpcklwd %mm5, %mm5 + punpckldq %mm5, %mm5 + + /*\ x = -dw \*/ + movl dw, %ecx + negl %ecx +.up_down_loop_x: + /*\ %esi = *yp + xp[x] \*/ + movl yp, %eax + movl (%eax), %esi + movl xp, %eax + movl (%eax, %ecx, 4), %eax + leal (%esi, %eax, 4), %esi + + movl %esi, %eax + /*\ v = (*p * My) >> 10 \*/ + movd (%eax), %mm0 + punpcklbw %mm7, %mm0 + psllw $6, %mm0 + pmulhw %mm5, %mm0 + + /*\ i = 0x4000 - My \*/ + movl $0x4000, %ebx + subl My, %ebx + jbe 5f + jmp 2f +1: + /*\ p += sow; v += (*p * Cy) >> 10 \*/ + addl sow_4, %eax + movd (%eax), %mm1 + punpcklbw %mm7, %mm1 + psllw $6, %mm1 + pmulhw %mm4, %mm1 + paddw %mm1, %mm0 + + /*\ i -= Cy; while (i > Cy) \*/ + subl Cy, %ebx +2: + cmpl Cy, %ebx + jg 1b + + /*\ mm6 = i \*/ + movd %ebx, %mm6 + punpcklwd %mm6, %mm6 + punpckldq %mm6, %mm6 + + /*\ p += sow; v += (*p * i) >> 10 \*/ + addl sow_4, %eax + movd (%eax), %mm1 + punpcklbw %mm7, %mm1 + psllw $6, %mm1 + pmulhw %mm6, %mm1 + paddw %mm1, %mm0 +5: + /*\ %eax = xap[x] << 5 \*/ + movl xap, %eax + movl (%eax, %ecx, 4), %eax + sall $5, %eax + jz 6f + /*\ mm3 = xap[x] << 5 \*/ + movd %eax, %mm3 + punpcklwd %mm3, %mm3 + punpckldq %mm3, %mm3 + + /*\ p + 1 \*/ + movl %esi, %eax + addl $4, %eax + /*\ vv = (*p * My) >> 10 \*/ + movd (%eax), %mm2 + punpcklbw %mm7, %mm2 + psllw $6, %mm2 + pmulhw %mm5, %mm2 + + /*\ i = 0x4000 - My \*/ + movl $0x4000, %ebx + subl My, %ebx + jbe 5f + jmp 2f +1: + /*\ p += sow; vv += (*p * Cy) >> 10 \*/ + addl sow_4, %eax + movd (%eax), %mm1 + punpcklbw %mm7, %mm1 + psllw $6, %mm1 + pmulhw %mm4, %mm1 + paddw %mm1, %mm2 + + /*\ i -= Cy; while (i > Cy) \*/ + subl Cy, %ebx +2: + cmpl Cy, %ebx + jg 1b + + /*\ p += sow; v += (*p * i) >> 10 \*/ + addl sow_4, %eax + movd (%eax), %mm1 + punpcklbw %mm7, %mm1 + psllw $6, %mm1 + pmulhw %mm6, %mm1 + paddw %mm1, %mm2 +5: + /*\ v = v + (vv - v) * xap \*/ + psubw %mm0, %mm2 + psllw $3, %mm2 + pmulhw %mm3, %mm2 + paddw %mm2, %mm0 +6: + /*\ dest[x] = v >> 4 \*/ + psrlw $4, %mm0 + packuswb %mm0, %mm0 + movd %mm0, (%edi, %ecx, 4) + + /*\ while (++x) \*/ + incl %ecx + jnz .up_down_loop_x + + /*\ dptr += dow \*/ + movl dow, %eax + leal (%edi, %eax, 4), %edi + /*\ yap++; yp++ \*/ + addl $4, yap + addl $4, yp + /*\ while (y--) \*/ + decl y + jnz .up_down_loop_y + + jmp .scale_leave + +.scale_x_down: + /*\ Test yup bit \*/ + sarl $1, %eax + jnc .scale_x_down_y_down + + +/*\ Scaling down horizontally \*/ + +.scale_x_down_y_up: + /*\ sow_4 = sow * 4 \*/ + movl sow, %eax + sall $2, %eax + movl %eax, sow_4 + +.down_up_loop_y: + + /*\ %eax = *yap << 5 \*/ + movl yap, %eax + movl (%eax), %eax + sall $5, %eax + /*\ mm3 = *yap << 5 \*/ + movd %eax, %mm3 + punpcklwd %mm3, %mm3 + punpckldq %mm3, %mm3 + + /*\ x = -dw \*/ + movl dw, %ecx + negl %ecx +.down_up_loop_x: + /*\ %esi = *yp + xp[x] \*/ + movl yp, %eax + movl (%eax), %esi + movl xp, %eax + movl (%eax, %ecx, 4), %eax + leal (%esi, %eax, 4), %esi + + /*\ Setup Mx and Cx \*/ + movl xap, %eax + movzwl (%eax, %ecx, 4), %ebx + movl %ebx, Mx + movzwl 2(%eax, %ecx, 4), %eax + movl %eax, Cx + + /*\ mm4 = Cx \*/ + movd %eax, %mm4 + punpcklwd %mm4, %mm4 + punpckldq %mm4, %mm4 + /*\ mm5 = Mx \*/ + movd %ebx, %mm5 + punpcklwd %mm5, %mm5 + punpckldq %mm5, %mm5 + + movl %esi, %eax + /*\ v = (*p * Mx) >> 10 \*/ + movd (%eax), %mm0 + punpcklbw %mm7, %mm0 + psllw $6, %mm0 + pmulhw %mm5, %mm0 + + /*\ i = 0x4000 - Mx \*/ + movl $0x4000, %ebx + subl Mx, %ebx + jbe 5f + jmp 2f +1: + /*\ p += sow; v += (*p * Cx) >> 10 \*/ + addl $4, %eax + movd (%eax), %mm1 + punpcklbw %mm7, %mm1 + psllw $6, %mm1 + pmulhw %mm4, %mm1 + paddw %mm1, %mm0 + + /*\ i -= Cx; while (i > Cx) \*/ + subl Cx, %ebx +2: + cmpl Cx, %ebx + jg 1b + + /*\ mm6 = i \*/ + movd %ebx, %mm6 + punpcklwd %mm6, %mm6 + punpckldq %mm6, %mm6 + + /*\ p += sow; v += (*p * i) >> 10 \*/ + addl $4, %eax + movd (%eax), %mm1 + punpcklbw %mm7, %mm1 + psllw $6, %mm1 + pmulhw %mm6, %mm1 + paddw %mm1, %mm0 +5: + movd %mm3, %eax + testl %eax, %eax + jz 6f + /*\ p + sow \*/ + movl %esi, %eax + addl sow_4, %eax + /*\ vv = (*p * Mx) >> 10 \*/ + movd (%eax), %mm2 + punpcklbw %mm7, %mm2 + psllw $6, %mm2 + pmulhw %mm5, %mm2 + + /*\ i = 0x4000 - Mx \*/ + movl $0x4000, %ebx + subl Mx, %ebx + jbe 5f + jmp 2f +1: + /*\ p += sow; vv += (*p * Cx) >> 10 \*/ + addl $4, %eax + movd (%eax), %mm1 + punpcklbw %mm7, %mm1 + psllw $6, %mm1 + pmulhw %mm4, %mm1 + paddw %mm1, %mm2 + + /*\ i -= Cx; while (i > Cx) \*/ + subl Cx, %ebx +2: + cmpl Cx, %ebx + jg 1b + + /*\ p += sow; v += (*p * i) >> 10 \*/ + addl $4, %eax + movd (%eax), %mm1 + punpcklbw %mm7, %mm1 + psllw $6, %mm1 + pmulhw %mm6, %mm1 + paddw %mm1, %mm2 +5: + /*\ v = v + (vv - v) * yap \*/ + psubw %mm0, %mm2 + psllw $3, %mm2 + pmulhw %mm3, %mm2 + paddw %mm2, %mm0 +6: + /*\ dest[x] = v >> 4 \*/ + psrlw $4, %mm0 + packuswb %mm0, %mm0 + movd %mm0, (%edi, %ecx, 4) + + /*\ while (++x) \*/ + incl %ecx + jnz .down_up_loop_x + + /*\ dptr += dow \*/ + movl dow, %eax + leal (%edi, %eax, 4), %edi + /*\ yap++; yp++ \*/ + addl $4, yap + addl $4, yp + /*\ while (y--) \*/ + decl y + jnz .down_up_loop_y + + jmp .scale_leave + + +/*\ Scaling down both ways \*/ + +.scale_x_down_y_down: + /*\ sow_4 = sow * 4 \*/ + movl sow, %eax + sall $2, %eax + movl %eax, sow_4 + +.down_down_loop_y: + + /*\ Setup My and Cy \*/ + movl yap, %eax + movzwl (%eax), %ebx + movl %ebx, My + movzwl 2(%eax), %eax + movl %eax, Cy + + /*\ x = -dw \*/ + movl dw, %ecx + negl %ecx +.down_down_loop_x: + /*\ %esi = *yp + xp[x] \*/ + movl yp, %eax + movl (%eax), %esi + movl xp, %eax + movl (%eax, %ecx, 4), %eax + leal (%esi, %eax, 4), %esi + + /*\ Setup Mx and Cx \*/ + movl xap, %eax + movzwl (%eax, %ecx, 4), %ebx + movl %ebx, Mx + movzwl 2(%eax, %ecx, 4), %eax + movl %eax, Cx + + /*\ mm3 = Cx \*/ + movd %eax, %mm3 + punpcklwd %mm3, %mm3 + punpckldq %mm3, %mm3 + /*\ mm5 = Mx \*/ + movd %ebx, %mm5 + punpcklwd %mm5, %mm5 + punpckldq %mm5, %mm5 + + /*\ p = sptr; v = (*p * Mx) >> 9 \*/ + movl %esi, %eax + movd (%eax), %mm0 + punpcklbw %mm7, %mm0 + psllw $7, %mm0 + pmulhw %mm5, %mm0 + + /*\ i = 0x4000 - Mx \*/ + movl $0x4000, %ebx + subl Mx, %ebx + jbe 5f + jmp 2f +1: + /*\ v += (*++p * Cx) >> 9 \*/ + addl $4, %eax + movd (%eax), %mm1 + punpcklbw %mm7, %mm1 + psllw $7, %mm1 + pmulhw %mm3, %mm1 + paddw %mm1, %mm0 + + /*\ i -= Cx; while (i > Cx) \*/ + subl Cx, %ebx +2: + cmpl Cx, %ebx + jg 1b + + /*\ mm6 = i \*/ + movd %ebx, %mm6 + punpcklwd %mm6, %mm6 + punpckldq %mm6, %mm6 + + /*\ v += (*++p * i) >> 9 \*/ + addl $4, %eax + movd (%eax), %mm1 + punpcklbw %mm7, %mm1 + psllw $7, %mm1 + pmulhw %mm6, %mm1 + paddw %mm1, %mm0 +5: + /*\ v *= My \*/ + movd My, %mm4 + punpcklwd %mm4, %mm4 + punpckldq %mm4, %mm4 + psllw $2, %mm0 + pmulhw %mm4, %mm0 + + /*\ j = 0x4000 - My \*/ + movl $0x4000, %edx + subl My, %edx + jbe 6f + jmp 4f +3: + /*\ sptr += sow; p = sptr \*/ + addl sow_4, %esi + movl %esi, %eax + /*\ vx = (*p * Mx) >> 9 \*/ + movd (%eax), %mm1 + punpcklbw %mm7, %mm1 + psllw $7, %mm1 + pmulhw %mm5, %mm1 + + /*\ i = 0x4000 - Mx \*/ + movl $0x4000, %ebx + subl Mx, %ebx + jbe 5f + jmp 2f +1: + /*\ vx += (*++p * Cx) >> 9 \*/ + addl $4, %eax + movd (%eax), %mm2 + punpcklbw %mm7, %mm2 + psllw $7, %mm2 + pmulhw %mm3, %mm2 + paddw %mm2, %mm1 + + /*\ i -= Cx; while (i > Cx) \*/ + subl Cx, %ebx +2: + cmpl Cx, %ebx + jg 1b + + /*\ vx += (*++p * i) >> 9 \*/ + addl $4, %eax + movd (%eax), %mm2 + punpcklbw %mm7, %mm2 + psllw $7, %mm2 + pmulhw %mm6, %mm2 + paddw %mm2, %mm1 +5: + /*\ v += (vx * Cy) >> 14 \*/ + movd Cy, %mm4 + punpcklwd %mm4, %mm4 + punpckldq %mm4, %mm4 + psllw $2, %mm1 + pmulhw %mm4, %mm1 + paddw %mm1, %mm0 + + /*\ j -= Cy; while (j > Cy) \*/ + subl Cy, %edx +4: + cmpl Cy, %edx + jg 3b + + /*\ sptr += sow; p = sptr \*/ + addl sow_4, %esi + movl %esi, %eax + /*\ vx = (*p * Mx) >> 9 \*/ + movd (%eax), %mm1 + punpcklbw %mm7, %mm1 + psllw $7, %mm1 + pmulhw %mm5, %mm1 + + /*\ i = 0x4000 - Mx \*/ + movl $0x4000, %ebx + subl Mx, %ebx + jbe 5f + jmp 2f +1: + /*\ vx += (*++p * Cx) >> 9 \*/ + addl $4, %eax + movd (%eax), %mm2 + punpcklbw %mm7, %mm2 + psllw $7, %mm2 + pmulhw %mm3, %mm2 + paddw %mm2, %mm1 + + /*\ i -= Cx; while (i > Cx) \*/ + subl Cx, %ebx +2: + cmpl Cx, %ebx + jg 1b + + /*\ vx += (*++p * i) >> 9 \*/ + addl $4, %eax + movd (%eax), %mm2 + punpcklbw %mm7, %mm2 + psllw $7, %mm2 + pmulhw %mm6, %mm2 + paddw %mm2, %mm1 +5: + /*\ v += (vx * j) >> 14 \*/ + movd %edx, %mm4 + punpcklwd %mm4, %mm4 + punpckldq %mm4, %mm4 + psllw $2, %mm1 + pmulhw %mm4, %mm1 + paddw %mm1, %mm0 +6: + /*\ dptr[x] = mm0 >> 5 \*/ + psrlw $5, %mm0 + packuswb %mm0, %mm0 + movd %mm0, (%edi, %ecx, 4) + + /*\ while (++x) \*/ + incl %ecx + jnz .down_down_loop_x + + /*\ dptr += dow \*/ + movl dow, %eax + leal (%edi, %eax, 4), %edi + /*\ yap++; yp++ \*/ + addl $4, yap + addl $4, yp + /*\ while (y--) \*/ + decl y + jnz .down_down_loop_y + + jmp .scale_leave + +.scale_leave: + emms + popl %esi + popl %edi + popl %edx + popl %ecx + popl %ebx + movl %ebp, %esp + popl %ebp + ret + +SIZE(mimageScale_mmx_AARGBA) + +#endif + +.section .note.GNU-stack,"",%progbits diff --git a/src/imageutils/croppedqimage.cpp b/src/imageutils/croppedqimage.cpp new file mode 100644 index 0000000..5ed2aab --- /dev/null +++ b/src/imageutils/croppedqimage.cpp @@ -0,0 +1,77 @@ +/* + + Copyright (C) 2005 Lubos Lunak <[email protected]> + + 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 "croppedqimage.h" + +namespace ImageUtils +{ + +// This class is used in ImageView::performPaint(). Just using QImage::copy( QRect ) +// takes a significant time with very large images. So instead of copying the image data +// just create CroppedQImage which fakes a subimage by manipulating its scanline pointers. +// That will of course break if something doesn't use scanlines but accesses the image +// data directly, QImage::copy() being the most notable case. There are two ways +// to handle that: 1) It is possible to manually call normalize() which will make +// CroppedQImage copy the image data and own it, just like proper QImage. 2) CroppedQImage +// has as a data member also QImage holding the original image. This ensures that all +// the original image data are still available for the whole lifetime of CroppedQImage. + +CroppedQImage::CroppedQImage( const QImage& im, const QRect& rect ) + : QImage( rect.size(), im.depth(), im.numColors(), im.bitOrder()) + , orig( im ) + { + if( im.isNull()) + return; + memcpy( colorTable(), im.colorTable(), im.numColors() * sizeof( QRgb )); + setAlphaBuffer( im.hasAlphaBuffer()); + setDotsPerMeterX( im.dotsPerMeterX()); + setDotsPerMeterY( im.dotsPerMeterY()); + //data->offset = im.offset(); + // make scanlines point to right places in the original QImage + for( int i = 0; + i < height(); + ++i ) + jumpTable()[ i ] = im.scanLine( rect.y() + i ) + rect.x() * ( depth() / 8 ); + } + +CroppedQImage& CroppedQImage::operator= ( const QImage& im ) + { + QImage::operator=( im ); + return *this; + } + +void CroppedQImage::normalize() + { + // is it a normal QImage with its own data? + uchar* firstdata = ( uchar* )( jumpTable() + height()); + if( scanLine( 0 ) == firstdata ) + return; + // copy the image data to our own data and make scanlines point properly there + for( int i = 0; + i < height(); + ++i ) + { + uchar* oldline = scanLine( i ); + jumpTable()[ i ] = firstdata + i * bytesPerLine(); + memcpy( scanLine( i ), oldline, bytesPerLine()); + } + } + +} // namespace diff --git a/src/imageutils/croppedqimage.h b/src/imageutils/croppedqimage.h new file mode 100644 index 0000000..4d93840 --- /dev/null +++ b/src/imageutils/croppedqimage.h @@ -0,0 +1,42 @@ +/* + + Copyright (C) 2005 Lubos Lunak <[email protected]> + + 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 CROPPED_QIMAGE_H +#define CROPPED_QIMAGE_H + +#include <qimage.h> + +namespace ImageUtils +{ + +class CroppedQImage + : public QImage + { + public: + CroppedQImage( const QImage& im, const QRect& rect ); + CroppedQImage& operator= ( const QImage& im ); + void normalize(); + private: + QImage orig; + }; + +} // namespace + +#endif diff --git a/src/imageutils/imageutils.cpp b/src/imageutils/imageutils.cpp new file mode 100644 index 0000000..c9d04ca --- /dev/null +++ b/src/imageutils/imageutils.cpp @@ -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 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 <math.h> + +// Qt +#include <qimage.h> +#include <qwmatrix.h> + +// KDE +#include <kdebug.h> +#include <kdeversion.h> +#include <kglobal.h> + +// Local +#include "imageutils/orientation.h" + +namespace ImageUtils { + + +QWMatrix transformMatrix(Orientation orientation) { + QWMatrix matrix; + switch (orientation) { + case NOT_AVAILABLE: + case NORMAL: + break; + + case HFLIP: + matrix.scale(-1,1); + break; + + case ROT_180: + matrix.rotate(180); + break; + + case VFLIP: + matrix.scale(1,-1); + break; + + case TRANSPOSE: + matrix.scale(-1,1); + matrix.rotate(90); + break; + + case ROT_90: + matrix.rotate(90); + break; + + case TRANSVERSE: + matrix.scale(1,-1); + matrix.rotate(90); + break; + + case ROT_270: + matrix.rotate(270); + break; + } + + return matrix; +} + + +QImage transform(const QImage& img, Orientation orientation) { + if (orientation != NOT_AVAILABLE && orientation != NORMAL) { + return img.xForm(transformMatrix(orientation)); + } else { + return img; + } +} + + +inline +int changeBrightness( int value, int brightness ) + { + return KCLAMP( value + brightness * 255 / 100, 0, 255 ); + } + +inline +int changeContrast( int value, int contrast ) + { + return KCLAMP((( value - 127 ) * contrast / 100 ) + 127, 0, 255 ); + } + +inline +int changeGamma( int value, int gamma ) + { + return KCLAMP( int( pow( value / 255.0, 100.0 / gamma ) * 255 ), 0, 255 ); + } + +inline +int changeUsingTable( int value, const int table[] ) + { + return table[ value ]; + } + +/* + Applies either brightness, contrast or gamma conversion on the image. + If the image is not truecolor, the color table is changed. If it is + truecolor, every pixel has to be changed. In order to make it as fast + as possible, alpha value is converted only if necessary. Additionally, + since color components (red/green/blue/alpha) can have only 256 values + but images usually have many pixels, a conversion table is first + created for every color component value, and pixels are converted + using this table. +*/ + +template< int operation( int, int ) > +static +QImage changeImage( const QImage& image, int value ) + { + QImage im = image; + im.detach(); + if( im.numColors() == 0 ) /* truecolor */ + { + if( im.depth() != 32 ) /* just in case */ + im = im.convertDepth( 32 ); + int table[ 256 ]; + for( int i = 0; + i < 256; + ++i ) + table[ i ] = operation( i, value ); + if( im.hasAlphaBuffer()) + { + for( int y = 0; + y < im.height(); + ++y ) + { + QRgb* line = reinterpret_cast< QRgb* >( im.scanLine( y )); + for( int x = 0; + x < im.width(); + ++x ) + line[ x ] = qRgba( changeUsingTable( qRed( line[ x ] ), table ), + changeUsingTable( qGreen( line[ x ] ), table ), + changeUsingTable( qBlue( line[ x ] ), table ), + changeUsingTable( qAlpha( line[ x ] ), table )); + } + } + else + { + for( int y = 0; + y < im.height(); + ++y ) + { + QRgb* line = reinterpret_cast< QRgb* >( im.scanLine( y )); + for( int x = 0; + x < im.width(); + ++x ) + line[ x ] = qRgb( changeUsingTable( qRed( line[ x ] ), table ), + changeUsingTable( qGreen( line[ x ] ), table ), + changeUsingTable( qBlue( line[ x ] ), table )); + } + } + } + else + { + QRgb* colors = im.colorTable(); + for( int i = 0; + i < im.numColors(); + ++i ) + colors[ i ] = qRgb( operation( qRed( colors[ i ] ), value ), + operation( qGreen( colors[ i ] ), value ), + operation( qBlue( colors[ i ] ), value )); + } + return im; + } + + +// brightness is multiplied by 100 in order to avoid floating point numbers +QImage changeBrightness( const QImage& image, int brightness ) + { + if( brightness == 0 ) // no change + return image; + return changeImage< changeBrightness >( image, brightness ); + } + + +// contrast is multiplied by 100 in order to avoid floating point numbers +QImage changeContrast( const QImage& image, int contrast ) + { + if( contrast == 100 ) // no change + return image; + return changeImage< changeContrast >( image, contrast ); + } + +// gamma is multiplied by 100 in order to avoid floating point numbers +QImage changeGamma( const QImage& image, int gamma ) + { + if( gamma == 100 ) // no change + return image; + return changeImage< changeGamma >( image, gamma ); + } + +} // Namespace + diff --git a/src/imageutils/imageutils.h b/src/imageutils/imageutils.h new file mode 100644 index 0000000..62b757c --- /dev/null +++ b/src/imageutils/imageutils.h @@ -0,0 +1,49 @@ +// 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 IMAGEUTILS_H +#define IMAGEUTILS_H + +// Qt +#include <qimage.h> + +// Local +#include "imageutils/orientation.h" + +namespace ImageUtils { + enum SmoothAlgorithm { SMOOTH_NONE, SMOOTH_FAST, SMOOTH_NORMAL, SMOOTH_BEST }; + + QImage scale(const QImage& image, int width, int height, + SmoothAlgorithm alg, QImage::ScaleMode mode = QImage::ScaleFree, double blur = 1.0); + + int extraScalePixels( SmoothAlgorithm alg, double zoom, double blur = 1.0 ); + + QImage transform(const QImage& img, Orientation orientation); + + QImage changeBrightness( const QImage& image, int brightness ); + + QImage changeContrast( const QImage& image, int contrast ); + + QImage changeGamma( const QImage& image, int gamma ); + + QWMatrix transformMatrix(Orientation orientation); +} + +#endif diff --git a/src/imageutils/jinclude.h b/src/imageutils/jinclude.h new file mode 100644 index 0000000..0a4f151 --- /dev/null +++ b/src/imageutils/jinclude.h @@ -0,0 +1,91 @@ +/* + * jinclude.h + * + * Copyright (C) 1991-1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file exists to provide a single place to fix any problems with + * including the wrong system include files. (Common problems are taken + * care of by the standard jconfig symbols, but on really weird systems + * you may have to edit this file.) + * + * NOTE: this file is NOT intended to be included by applications using the + * JPEG library. Most applications need only include jpeglib.h. + */ + + +/* Include auto-config file to find out which system include files we need. */ + +#include "jconfig.h" /* auto configuration options */ +#define JCONFIG_INCLUDED /* so that jpeglib.h doesn't do it again */ + +/* + * We need the NULL macro and size_t typedef. + * On an ANSI-conforming system it is sufficient to include <stddef.h>. + * Otherwise, we get them from <stdlib.h> or <stdio.h>; we may have to + * pull in <sys/types.h> as well. + * Note that the core JPEG library does not require <stdio.h>; + * only the default error handler and data source/destination modules do. + * But we must pull it in because of the references to FILE in jpeglib.h. + * You can remove those references if you want to compile without <stdio.h>. + */ + +#ifdef HAVE_STDDEF_H +#include <stddef.h> +#endif + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif + +#ifdef NEED_SYS_TYPES_H +#include <sys/types.h> +#endif + +#include <stdio.h> + +/* + * We need memory copying and zeroing functions, plus strncpy(). + * ANSI and System V implementations declare these in <string.h>. + * BSD doesn't have the mem() functions, but it does have bcopy()/bzero(). + * Some systems may declare memset and memcpy in <memory.h>. + * + * NOTE: we assume the size parameters to these functions are of type size_t. + * Change the casts in these macros if not! + */ + +#ifdef NEED_BSD_STRINGS + +#include <strings.h> +#define MEMZERO(target,size) bzero((void *)(target), (size_t)(size)) +#define MEMCOPY(dest,src,size) bcopy((const void *)(src), (void *)(dest), (size_t)(size)) + +#else /* not BSD, assume ANSI/SysV string lib */ + +#include <string.h> +#define MEMZERO(target,size) memset((void *)(target), 0, (size_t)(size)) +#define MEMCOPY(dest,src,size) memcpy((void *)(dest), (const void *)(src), (size_t)(size)) + +#endif + +/* + * In ANSI C, and indeed any rational implementation, size_t is also the + * type returned by sizeof(). However, it seems there are some irrational + * implementations out there, in which sizeof() returns an int even though + * size_t is defined as long or unsigned long. To ensure consistent results + * we always use this SIZEOF() macro in place of using sizeof() directly. + */ + +#define SIZEOF(object) ((size_t) sizeof(object)) + +/* + * The modules that use fread() and fwrite() always invoke them through + * these macros. On some systems you may need to twiddle the argument casts. + * CAUTION: argument order is different from underlying functions! + */ + +#define JFREAD(file,buf,sizeofbuf) \ + ((size_t) fread((void *) (buf), (size_t) 1, (size_t) (sizeofbuf), (file))) +#define JFWRITE(file,buf,sizeofbuf) \ + ((size_t) fwrite((const void *) (buf), (size_t) 1, (size_t) (sizeofbuf), (file))) diff --git a/src/imageutils/jpegcontent.cpp b/src/imageutils/jpegcontent.cpp new file mode 100644 index 0000000..b44b14d --- /dev/null +++ b/src/imageutils/jpegcontent.cpp @@ -0,0 +1,666 @@ +// 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. + +*/ +// System +#include <math.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +extern "C" { +#include <jpeglib.h> +#include "transupp.h" +} + +// Qt +#include <qbuffer.h> +#include <qfile.h> +#include <qimage.h> +#include <qmap.h> +#include <qwmatrix.h> + +// KDE +#include <kdebug.h> + +// Exiv2 +#include <exiv2/exif.hpp> +#include <exiv2/image.hpp> + +// Local +#include "imageutils/imageutils.h" +#include "imageutils/jpegcontent.h" +#include "imageutils/jpegerrormanager.h" + +namespace ImageUtils { + +const int INMEM_DST_DELTA=4096; + + +//------------------------------------------ +// +// In-memory data source manager for libjpeg +// +//------------------------------------------ +struct inmem_src_mgr : public jpeg_source_mgr { + QByteArray* mInput; +}; + +void inmem_init_source(j_decompress_ptr cinfo) { + inmem_src_mgr* src=(inmem_src_mgr*)(cinfo->src); + src->next_input_byte=(const JOCTET*)( src->mInput->data() ); + src->bytes_in_buffer=src->mInput->size(); +} + +/** + * If this function is called, it means the JPEG file is broken. We feed the + * decoder with fake EOI has specified in the libjpeg documentation. + */ +int inmem_fill_input_buffer(j_decompress_ptr cinfo) { + static JOCTET fakeEOI[2]={ JOCTET(0xFF), JOCTET(JPEG_EOI)}; + kdWarning() << k_funcinfo << " Image is incomplete" << endl; + cinfo->src->next_input_byte=fakeEOI; + cinfo->src->bytes_in_buffer=2; + return true; +} + +void inmem_skip_input_data(j_decompress_ptr cinfo, long num_bytes) { + if (num_bytes<=0) return; + Q_ASSERT(num_bytes>=long(cinfo->src->bytes_in_buffer)); + cinfo->src->next_input_byte+=num_bytes; + cinfo->src->bytes_in_buffer-=num_bytes; +} + +void inmem_term_source(j_decompress_ptr /*cinfo*/) { +} + + +//----------------------------------------------- +// +// In-memory data destination manager for libjpeg +// +//----------------------------------------------- +struct inmem_dest_mgr : public jpeg_destination_mgr { + QByteArray* mOutput; + + void dump() { + kdDebug() << "dest_mgr:\n"; + kdDebug() << "- next_output_byte: " << next_output_byte << endl; + kdDebug() << "- free_in_buffer: " << free_in_buffer << endl; + kdDebug() << "- output size: " << mOutput->size() << endl; + } +}; + +void inmem_init_destination(j_compress_ptr cinfo) { + inmem_dest_mgr* dest=(inmem_dest_mgr*)(cinfo->dest); + if (dest->mOutput->size()==0) { + bool result=dest->mOutput->resize(INMEM_DST_DELTA); + Q_ASSERT(result); + } + dest->free_in_buffer=dest->mOutput->size(); + dest->next_output_byte=(JOCTET*)(dest->mOutput->data() ); +} + +int inmem_empty_output_buffer(j_compress_ptr cinfo) { + inmem_dest_mgr* dest=(inmem_dest_mgr*)(cinfo->dest); + bool result=dest->mOutput->resize(dest->mOutput->size() + INMEM_DST_DELTA); + Q_ASSERT(result); + dest->next_output_byte=(JOCTET*)( dest->mOutput->data() + dest->mOutput->size() - INMEM_DST_DELTA ); + dest->free_in_buffer=INMEM_DST_DELTA; + + return true; +} + +void inmem_term_destination(j_compress_ptr cinfo) { + inmem_dest_mgr* dest=(inmem_dest_mgr*)(cinfo->dest); + int finalSize=dest->next_output_byte - (JOCTET*)(dest->mOutput->data()); + Q_ASSERT(finalSize>=0); + dest->mOutput->resize(finalSize); +} + + +//--------------------- +// +// JPEGContent::Private +// +//--------------------- +struct JPEGContent::Private { + QByteArray mRawData; + QSize mSize; + QString mComment; + QString mAperture; + QString mExposureTime; + QString mFocalLength; + QString mIso; + + bool mPendingTransformation; + QWMatrix mTransformMatrix; + Exiv2::ExifData mExifData; + + Private() { + mPendingTransformation = false; + } + + void setupInmemSource(j_decompress_ptr cinfo) { + Q_ASSERT(!cinfo->src); + inmem_src_mgr* src = (inmem_src_mgr*) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + sizeof(inmem_src_mgr)); + cinfo->src=(struct jpeg_source_mgr*)(src); + + src->init_source=inmem_init_source; + src->fill_input_buffer=inmem_fill_input_buffer; + src->skip_input_data=inmem_skip_input_data; + src->resync_to_restart=jpeg_resync_to_restart; + src->term_source=inmem_term_source; + + src->mInput=&mRawData; + } + + + void setupInmemDestination(j_compress_ptr cinfo, QByteArray* outputData) { + Q_ASSERT(!cinfo->dest); + inmem_dest_mgr* dest = (inmem_dest_mgr*) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + sizeof(inmem_dest_mgr)); + cinfo->dest=(struct jpeg_destination_mgr*)(dest); + + dest->init_destination=inmem_init_destination; + dest->empty_output_buffer=inmem_empty_output_buffer; + dest->term_destination=inmem_term_destination; + + dest->mOutput=outputData; + } + bool readSize() { + struct jpeg_decompress_struct srcinfo; + jpeg_saved_marker_ptr mark; + + // Init JPEG structs + JPEGErrorManager errorManager; + + // Initialize the JPEG decompression object + srcinfo.err = &errorManager; + jpeg_create_decompress(&srcinfo); + if (setjmp(errorManager.jmp_buffer)) { + kdError() << k_funcinfo << "libjpeg fatal error\n"; + return false; + } + + // Specify data source for decompression + setupInmemSource(&srcinfo); + + // Read the header + jcopy_markers_setup(&srcinfo, JCOPYOPT_ALL); + int result=jpeg_read_header(&srcinfo, true); + if (result!=JPEG_HEADER_OK) { + kdError() << "Could not read jpeg header\n"; + jpeg_destroy_decompress(&srcinfo); + return false; + } + mSize=QSize(srcinfo.image_width, srcinfo.image_height); + + jpeg_destroy_decompress(&srcinfo); + return true; + } +}; + + +//------------ +// +// JPEGContent +// +//------------ +JPEGContent::JPEGContent() { + d=new JPEGContent::Private(); +} + + +JPEGContent::~JPEGContent() { + delete d; +} + + +bool JPEGContent::load(const QString& path) { + QFile file(path); + if (!file.open(IO_ReadOnly)) { + kdError() << "Could not open '" << path << "' for reading\n"; + return false; + } + return loadFromData(file.readAll()); +} + + +bool JPEGContent::loadFromData(const QByteArray& data) { + d->mPendingTransformation = false; + d->mTransformMatrix.reset(); + + d->mRawData = data; + if (d->mRawData.size()==0) { + kdError() << "No data\n"; + return false; + } + + if (!d->readSize()) return false; + + Exiv2::Image::AutoPtr image; + try { + image = Exiv2::ImageFactory::open((unsigned char*)data.data(), data.size()); + image->readMetadata(); + } catch (Exiv2::Error&) { + kdError() << "Could not load image with Exiv2\n"; + return false; + } + + d->mExifData = image->exifData(); + d->mComment = QString::fromUtf8( image->comment().c_str() ); + + d->mAperture=aperture(); + d->mExposureTime=exposureTime(); + d->mIso=iso(); + d->mFocalLength=iso(); + + // Adjust the size according to the orientation + switch (orientation()) { + case TRANSPOSE: + case ROT_90: + case TRANSVERSE: + case ROT_270: + d->mSize.transpose(); + break; + default: + break; + } + + return true; +} + + +Orientation JPEGContent::orientation() const { + Exiv2::ExifKey key("Exif.Image.Orientation"); + Exiv2::ExifData::iterator it = d->mExifData.findKey(key); + if (it == d->mExifData.end()) { + return NOT_AVAILABLE; + } + return Orientation( it->toLong() ); +} + + +int JPEGContent::dotsPerMeterX() const { + return dotsPerMeter("XResolution"); +} + + +int JPEGContent::dotsPerMeterY() const { + return dotsPerMeter("YResolution"); +} + + +int JPEGContent::dotsPerMeter(const QString& keyName) const { + Exiv2::ExifKey keyResUnit("Exif.Image.ResolutionUnit"); + Exiv2::ExifData::iterator it = d->mExifData.findKey(keyResUnit); + if (it == d->mExifData.end()) { + return 0; + } + int res = it->toLong(); + QString keyVal = "Exif.Image." + keyName; + Exiv2::ExifKey keyResolution(keyVal.ascii()); + it = d->mExifData.findKey(keyResolution); + if (it == d->mExifData.end()) { + return 0; + } + // The unit for measuring XResolution and YResolution. The same unit is used for both XResolution and YResolution. + // If the image resolution in unknown, 2 (inches) is designated. + // Default = 2 + // 2 = inches + // 3 = centimeters + // Other = reserved + const float INCHESPERMETER = (100. / 2.54); + Exiv2::Rational r = it->toRational(); + if (r.second == 0) { + // a rational with 0 as second will make hang toLong() conversion + r.second = 1; + } + switch (res) { + case 3: // dots per cm + return int(float(r.first) * 100 / float(r.second)); + default: // dots per inch + return int(float(r.first) * INCHESPERMETER / float(r.second)); + } + + return 0; +} + + +void JPEGContent::resetOrientation() { + Exiv2::ExifKey key("Exif.Image.Orientation"); + Exiv2::ExifData::iterator it = d->mExifData.findKey(key); + if (it == d->mExifData.end()) { + return; + } + + *it = uint16_t(ImageUtils::NORMAL); +} + + +QSize JPEGContent::size() const { + return d->mSize; +} + + +QString JPEGContent::comment() const { + return d->mComment; +} + +QString JPEGContent::getExifInformation(const QString exifkey) const { + QString ret; + + Exiv2::ExifKey key(exifkey.latin1()); + Exiv2::ExifData::iterator it = d->mExifData.findKey(key); + + if (it != d->mExifData.end()) { + std::ostringstream outputString; + outputString << *it; + ret=QString(outputString.str().c_str()); + } + else { + ret="n/a"; + } + return ret; +} + +QString JPEGContent::aperture() const { + d->mAperture=getExifInformation("Exif.Photo.FNumber"); + return d->mAperture; +} + +QString JPEGContent::exposureTime() const { + d->mExposureTime=getExifInformation("Exif.Photo.ExposureTime"); + return d->mExposureTime; +} + +QString JPEGContent::iso() const { + d->mIso=getExifInformation("Exif.Photo.ISOSpeedRatings"); + return d->mIso; +} + +QString JPEGContent::focalLength() const { + d->mFocalLength=getExifInformation("Exif.Photo.FocalLength"); + return d->mFocalLength; +} + +void JPEGContent::setComment(const QString& comment) { + d->mComment = comment; +} + +static QWMatrix createRotMatrix(int angle) { + QWMatrix matrix; + matrix.rotate(angle); + return matrix; +} + + +static QWMatrix createScaleMatrix(int dx, int dy) { + QWMatrix matrix; + matrix.scale(dx, dy); + return matrix; +} + + + +struct OrientationInfo { + OrientationInfo() {} + OrientationInfo(Orientation o, QWMatrix m, JXFORM_CODE j) + : orientation(o), matrix(m), jxform(j) {} + + Orientation orientation; + QWMatrix matrix; + JXFORM_CODE jxform; +}; +typedef QValueList<OrientationInfo> OrientationInfoList; + +static const OrientationInfoList& orientationInfoList() { + static OrientationInfoList list; + if (list.size() == 0) { + QWMatrix rot90 = createRotMatrix(90); + QWMatrix hflip = createScaleMatrix(-1, 1); + QWMatrix vflip = createScaleMatrix(1, -1); + + list + << OrientationInfo(NOT_AVAILABLE, QWMatrix(), JXFORM_NONE) + << OrientationInfo(NORMAL, QWMatrix(), JXFORM_NONE) + << OrientationInfo(HFLIP, hflip, JXFORM_FLIP_H) + << OrientationInfo(ROT_180, createRotMatrix(180), JXFORM_ROT_180) + << OrientationInfo(VFLIP, vflip, JXFORM_FLIP_V) + << OrientationInfo(TRANSPOSE, hflip * rot90, JXFORM_TRANSPOSE) + << OrientationInfo(ROT_90, rot90, JXFORM_ROT_90) + << OrientationInfo(TRANSVERSE, vflip * rot90, JXFORM_TRANSVERSE) + << OrientationInfo(ROT_270, createRotMatrix(270), JXFORM_ROT_270) + ; + } + return list; +} + + +void JPEGContent::transform(Orientation orientation) { + if (orientation != NOT_AVAILABLE && orientation != NORMAL) { + d->mPendingTransformation = true; + OrientationInfoList::ConstIterator it(orientationInfoList().begin()), end(orientationInfoList().end()); + for (; it!=end; ++it) { + if ( (*it).orientation == orientation ) { + d->mTransformMatrix = (*it).matrix * d->mTransformMatrix; + break; + } + } + if (it == end) { + kdWarning() << k_funcinfo << "Could not find matrix for orientation\n"; + } + } +} + + +#if 0 +static void dumpMatrix(const QWMatrix& matrix) { + kdDebug() << "matrix | " << matrix.m11() << ", " << matrix.m12() << " |\n"; + kdDebug() << " | " << matrix.m21() << ", " << matrix.m22() << " |\n"; + kdDebug() << " ( " << matrix.dx() << ", " << matrix.dy() << " )\n"; +} +#endif + + +static bool matricesAreSame(const QWMatrix& m1, const QWMatrix& m2, double tolerance) { + return fabs( m1.m11() - m2.m11() ) < tolerance + && fabs( m1.m12() - m2.m12() ) < tolerance + && fabs( m1.m21() - m2.m21() ) < tolerance + && fabs( m1.m22() - m2.m22() ) < tolerance + && fabs( m1.dx() - m2.dx() ) < tolerance + && fabs( m1.dy() - m2.dy() ) < tolerance; +} + + +static JXFORM_CODE findJxform(const QWMatrix& matrix) { + OrientationInfoList::ConstIterator it(orientationInfoList().begin()), end(orientationInfoList().end()); + for (; it!=end; ++it) { + if ( matricesAreSame( (*it).matrix, matrix, 0.001) ) { + return (*it).jxform; + } + } + kdWarning() << "findJxform: failed\n"; + return JXFORM_NONE; +} + + +void JPEGContent::applyPendingTransformation() { + if (d->mRawData.size()==0) { + kdError() << "No data loaded\n"; + return; + } + + // The following code is inspired by jpegtran.c from the libjpeg + + // Init JPEG structs + struct jpeg_decompress_struct srcinfo; + struct jpeg_compress_struct dstinfo; + jvirt_barray_ptr * src_coef_arrays; + jvirt_barray_ptr * dst_coef_arrays; + + // Initialize the JPEG decompression object + JPEGErrorManager srcErrorManager; + srcinfo.err = &srcErrorManager; + jpeg_create_decompress(&srcinfo); + if (setjmp(srcErrorManager.jmp_buffer)) { + kdError() << k_funcinfo << "libjpeg error in src\n"; + return; + } + + // Initialize the JPEG compression object + JPEGErrorManager dstErrorManager; + dstinfo.err = &dstErrorManager; + jpeg_create_compress(&dstinfo); + if (setjmp(dstErrorManager.jmp_buffer)) { + kdError() << k_funcinfo << "libjpeg error in dst\n"; + return; + } + + // Specify data source for decompression + d->setupInmemSource(&srcinfo); + + // Enable saving of extra markers that we want to copy + jcopy_markers_setup(&srcinfo, JCOPYOPT_ALL); + + (void) jpeg_read_header(&srcinfo, TRUE); + + // Init transformation + jpeg_transform_info transformoption; + transformoption.transform = findJxform(d->mTransformMatrix); + transformoption.force_grayscale = false; + transformoption.trim = false; + jtransform_request_workspace(&srcinfo, &transformoption); + + /* Read source file as DCT coefficients */ + src_coef_arrays = jpeg_read_coefficients(&srcinfo); + + /* Initialize destination compression parameters from source values */ + jpeg_copy_critical_parameters(&srcinfo, &dstinfo); + + /* Adjust destination parameters if required by transform options; + * also find out which set of coefficient arrays will hold the output. + */ + dst_coef_arrays = jtransform_adjust_parameters(&srcinfo, &dstinfo, + src_coef_arrays, + &transformoption); + + /* Specify data destination for compression */ + QByteArray output; + output.resize(d->mRawData.size()); + d->setupInmemDestination(&dstinfo, &output); + + /* Start compressor (note no image data is actually written here) */ + jpeg_write_coefficients(&dstinfo, dst_coef_arrays); + + /* Copy to the output file any extra markers that we want to preserve */ + jcopy_markers_execute(&srcinfo, &dstinfo, JCOPYOPT_ALL); + + /* Execute image transformation, if any */ + jtransform_execute_transformation(&srcinfo, &dstinfo, + src_coef_arrays, + &transformoption); + + /* Finish compression and release memory */ + jpeg_finish_compress(&dstinfo); + jpeg_destroy_compress(&dstinfo); + (void) jpeg_finish_decompress(&srcinfo); + jpeg_destroy_decompress(&srcinfo); + + // Set rawData to our new JPEG + d->mRawData = output; +} + + +QImage JPEGContent::thumbnail() const { + QImage image; + if (!d->mExifData.empty()) { + Exiv2::DataBuf thumbnail = d->mExifData.copyThumbnail(); + image.loadFromData(thumbnail.pData_, thumbnail.size_); + } + return image; +} + + +void JPEGContent::setThumbnail(const QImage& thumbnail) { + if (d->mExifData.empty()) { + return; + } + + QByteArray array; + QBuffer buffer(array); + buffer.open(IO_WriteOnly); + QImageIO iio(&buffer, "JPEG"); + iio.setImage(thumbnail); + if (!iio.write()) { + kdError() << "Could not write thumbnail\n"; + return; + } + + d->mExifData.setJpegThumbnail((unsigned char*)array.data(), array.size()); +} + + +bool JPEGContent::save(const QString& path) { + QFile file(path); + if (!file.open(IO_WriteOnly)) { + kdError() << "Could not open '" << path << "' for writing\n"; + return false; + } + + return save(&file); +} + + +bool JPEGContent::save(QFile* file) { + if (d->mRawData.size()==0) { + kdError() << "No data to store in '" << file->name() << "'\n"; + return false; + } + + if (d->mPendingTransformation) { + applyPendingTransformation(); + d->mPendingTransformation = false; + } + + Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open((unsigned char*)d->mRawData.data(), d->mRawData.size()); + + // Store Exif info + image->setExifData(d->mExifData); + image->setComment(d->mComment.utf8().data()); + image->writeMetadata(); + + // Update mRawData + Exiv2::BasicIo& io = image->io(); + d->mRawData.resize(io.size()); + io.read((unsigned char*)d->mRawData.data(), io.size()); + + QDataStream stream(file); + stream.writeRawBytes(d->mRawData.data(), d->mRawData.size()); + + // Make sure we are up to date + loadFromData(d->mRawData); + return true; +} + + +} // namespace diff --git a/src/imageutils/jpegcontent.h b/src/imageutils/jpegcontent.h new file mode 100644 index 0000000..40af44d --- /dev/null +++ b/src/imageutils/jpegcontent.h @@ -0,0 +1,86 @@ +// 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 JPEGCONTENT_H +#define JPEGCONTENT_H + +// Qt +#include <qcstring.h> + +// Local +#include <imageutils/orientation.h> + +#include "../gvcore/libgwenview_export.h" + +class QImage; +class QString; +class QFile; + +namespace ImageUtils { + + +class LIBGWENVIEW_EXPORT JPEGContent { +public: + JPEGContent(); + ~JPEGContent(); + + Orientation orientation() const; + void resetOrientation(); + + int dotsPerMeterX() const; + int dotsPerMeterY() const; + + QSize size() const; + + QString comment() const; + void setComment(const QString&); + + QString aperture() const; + QString exposureTime() const; + QString iso() const; + QString focalLength() const; + + QString getExifInformation(const QString exifkey) const; + + void transform(Orientation); + + QImage thumbnail() const; + void setThumbnail(const QImage&); + + bool load(const QString& file); + bool loadFromData(const QByteArray& rawData); + bool save(const QString& file); + bool save(QFile*); + +private: + struct Private; + Private *d; + + JPEGContent(const JPEGContent&); + void operator=(const JPEGContent&); + void applyPendingTransformation(); + int dotsPerMeter(const QString& keyName) const; +}; + + +} // namespace + + +#endif /* JPEGCONTENT_H */ diff --git a/src/imageutils/jpegerrormanager.h b/src/imageutils/jpegerrormanager.h new file mode 100644 index 0000000..e4f82bb --- /dev/null +++ b/src/imageutils/jpegerrormanager.h @@ -0,0 +1,61 @@ +// 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 JPEGERRORMANAGER_H +#define JPEGERRORMANAGER_H + +#include <setjmp.h> + +extern "C" { +#define XMD_H +#include <jpeglib.h> +#undef const +} + +namespace ImageUtils { + +/** + * A simple error manager which overrides jpeg_error_mgr.error_exit to avoid + * calls to exit(). It uses setjmp, which I don't like, but I don't fill like + * introducing exceptions to the code base for now. + * + * In order to use it, give an instance of it to jpeg_decompress_struct.err, + * then call setjmp(errorManager.jmp_buffer) + */ +struct JPEGErrorManager : public jpeg_error_mgr { + JPEGErrorManager() : jpeg_error_mgr() { + jpeg_std_error(this); + error_exit=errorExitCallBack; + } + + jmp_buf jmp_buffer; + + static void errorExitCallBack (j_common_ptr cinfo) { + JPEGErrorManager* myerr = static_cast<JPEGErrorManager*>(cinfo->err); + char buffer[JMSG_LENGTH_MAX]; + (*cinfo->err->format_message)(cinfo, buffer); + kdWarning() << k_funcinfo << buffer << endl; + longjmp(myerr->jmp_buffer, 1); + } +}; + +} // namespace + +#endif /* JPEGERRORMANAGER_H */ diff --git a/src/imageutils/jpegint.h b/src/imageutils/jpegint.h new file mode 100644 index 0000000..95b00d4 --- /dev/null +++ b/src/imageutils/jpegint.h @@ -0,0 +1,392 @@ +/* + * jpegint.h + * + * Copyright (C) 1991-1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file provides common declarations for the various JPEG modules. + * These declarations are considered internal to the JPEG library; most + * applications using the library shouldn't need to include this file. + */ + + +/* Declarations for both compression & decompression */ + +typedef enum { /* Operating modes for buffer controllers */ + JBUF_PASS_THRU, /* Plain stripwise operation */ + /* Remaining modes require a full-image buffer to have been created */ + JBUF_SAVE_SOURCE, /* Run source subobject only, save output */ + JBUF_CRANK_DEST, /* Run dest subobject only, using saved data */ + JBUF_SAVE_AND_PASS /* Run both subobjects, save output */ +} J_BUF_MODE; + +/* Values of global_state field (jdapi.c has some dependencies on ordering!) */ +#define CSTATE_START 100 /* after create_compress */ +#define CSTATE_SCANNING 101 /* start_compress done, write_scanlines OK */ +#define CSTATE_RAW_OK 102 /* start_compress done, write_raw_data OK */ +#define CSTATE_WRCOEFS 103 /* jpeg_write_coefficients done */ +#define DSTATE_START 200 /* after create_decompress */ +#define DSTATE_INHEADER 201 /* reading header markers, no SOS yet */ +#define DSTATE_READY 202 /* found SOS, ready for start_decompress */ +#define DSTATE_PRELOAD 203 /* reading multiscan file in start_decompress*/ +#define DSTATE_PRESCAN 204 /* performing dummy pass for 2-pass quant */ +#define DSTATE_SCANNING 205 /* start_decompress done, read_scanlines OK */ +#define DSTATE_RAW_OK 206 /* start_decompress done, read_raw_data OK */ +#define DSTATE_BUFIMAGE 207 /* expecting jpeg_start_output */ +#define DSTATE_BUFPOST 208 /* looking for SOS/EOI in jpeg_finish_output */ +#define DSTATE_RDCOEFS 209 /* reading file in jpeg_read_coefficients */ +#define DSTATE_STOPPING 210 /* looking for EOI in jpeg_finish_decompress */ + + +/* Declarations for compression modules */ + +/* Master control module */ +struct jpeg_comp_master { + JMETHOD(void, prepare_for_pass, (j_compress_ptr cinfo)); + JMETHOD(void, pass_startup, (j_compress_ptr cinfo)); + JMETHOD(void, finish_pass, (j_compress_ptr cinfo)); + + /* State variables made visible to other modules */ + boolean call_pass_startup; /* True if pass_startup must be called */ + boolean is_last_pass; /* True during last pass */ +}; + +/* Main buffer control (downsampled-data buffer) */ +struct jpeg_c_main_controller { + JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(void, process_data, (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JDIMENSION *in_row_ctr, + JDIMENSION in_rows_avail)); +}; + +/* Compression preprocessing (downsampling input buffer control) */ +struct jpeg_c_prep_controller { + JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(void, pre_process_data, (j_compress_ptr cinfo, + JSAMPARRAY input_buf, + JDIMENSION *in_row_ctr, + JDIMENSION in_rows_avail, + JSAMPIMAGE output_buf, + JDIMENSION *out_row_group_ctr, + JDIMENSION out_row_groups_avail)); +}; + +/* Coefficient buffer control */ +struct jpeg_c_coef_controller { + JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(boolean, compress_data, (j_compress_ptr cinfo, + JSAMPIMAGE input_buf)); +}; + +/* Colorspace conversion */ +struct jpeg_color_converter { + JMETHOD(void, start_pass, (j_compress_ptr cinfo)); + JMETHOD(void, color_convert, (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPIMAGE output_buf, + JDIMENSION output_row, int num_rows)); +}; + +/* Downsampling */ +struct jpeg_downsampler { + JMETHOD(void, start_pass, (j_compress_ptr cinfo)); + JMETHOD(void, downsample, (j_compress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION in_row_index, + JSAMPIMAGE output_buf, + JDIMENSION out_row_group_index)); + + boolean need_context_rows; /* TRUE if need rows above & below */ +}; + +/* Forward DCT (also controls coefficient quantization) */ +struct jpeg_forward_dct { + JMETHOD(void, start_pass, (j_compress_ptr cinfo)); + /* perhaps this should be an array??? */ + JMETHOD(void, forward_DCT, (j_compress_ptr cinfo, + jpeg_component_info * compptr, + JSAMPARRAY sample_data, JBLOCKROW coef_blocks, + JDIMENSION start_row, JDIMENSION start_col, + JDIMENSION num_blocks)); +}; + +/* Entropy encoding */ +struct jpeg_entropy_encoder { + JMETHOD(void, start_pass, (j_compress_ptr cinfo, boolean gather_statistics)); + JMETHOD(boolean, encode_mcu, (j_compress_ptr cinfo, JBLOCKROW *MCU_data)); + JMETHOD(void, finish_pass, (j_compress_ptr cinfo)); +}; + +/* Marker writing */ +struct jpeg_marker_writer { + JMETHOD(void, write_file_header, (j_compress_ptr cinfo)); + JMETHOD(void, write_frame_header, (j_compress_ptr cinfo)); + JMETHOD(void, write_scan_header, (j_compress_ptr cinfo)); + JMETHOD(void, write_file_trailer, (j_compress_ptr cinfo)); + JMETHOD(void, write_tables_only, (j_compress_ptr cinfo)); + /* These routines are exported to allow insertion of extra markers */ + /* Probably only COM and APPn markers should be written this way */ + JMETHOD(void, write_marker_header, (j_compress_ptr cinfo, int marker, + unsigned int datalen)); + JMETHOD(void, write_marker_byte, (j_compress_ptr cinfo, int val)); +}; + + +/* Declarations for decompression modules */ + +/* Master control module */ +struct jpeg_decomp_master { + JMETHOD(void, prepare_for_output_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, finish_output_pass, (j_decompress_ptr cinfo)); + + /* State variables made visible to other modules */ + boolean is_dummy_pass; /* True during 1st pass for 2-pass quant */ +}; + +/* Input control module */ +struct jpeg_input_controller { + JMETHOD(int, consume_input, (j_decompress_ptr cinfo)); + JMETHOD(void, reset_input_controller, (j_decompress_ptr cinfo)); + JMETHOD(void, start_input_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, finish_input_pass, (j_decompress_ptr cinfo)); + + /* State variables made visible to other modules */ + boolean has_multiple_scans; /* True if file has multiple scans */ + boolean eoi_reached; /* True when EOI has been consumed */ +}; + +/* Main buffer control (downsampled-data buffer) */ +struct jpeg_d_main_controller { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(void, process_data, (j_decompress_ptr cinfo, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); +}; + +/* Coefficient buffer control */ +struct jpeg_d_coef_controller { + JMETHOD(void, start_input_pass, (j_decompress_ptr cinfo)); + JMETHOD(int, consume_data, (j_decompress_ptr cinfo)); + JMETHOD(void, start_output_pass, (j_decompress_ptr cinfo)); + JMETHOD(int, decompress_data, (j_decompress_ptr cinfo, + JSAMPIMAGE output_buf)); + /* Pointer to array of coefficient virtual arrays, or NULL if none */ + jvirt_barray_ptr *coef_arrays; +}; + +/* Decompression postprocessing (color quantization buffer control) */ +struct jpeg_d_post_controller { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(void, post_process_data, (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, + JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, + JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); +}; + +/* Marker reading & parsing */ +struct jpeg_marker_reader { + JMETHOD(void, reset_marker_reader, (j_decompress_ptr cinfo)); + /* Read markers until SOS or EOI. + * Returns same codes as are defined for jpeg_consume_input: + * JPEG_SUSPENDED, JPEG_REACHED_SOS, or JPEG_REACHED_EOI. + */ + JMETHOD(int, read_markers, (j_decompress_ptr cinfo)); + /* Read a restart marker --- exported for use by entropy decoder only */ + jpeg_marker_parser_method read_restart_marker; + + /* State of marker reader --- nominally internal, but applications + * supplying COM or APPn handlers might like to know the state. + */ + boolean saw_SOI; /* found SOI? */ + boolean saw_SOF; /* found SOF? */ + int next_restart_num; /* next restart number expected (0-7) */ + unsigned int discarded_bytes; /* # of bytes skipped looking for a marker */ +}; + +/* Entropy decoding */ +struct jpeg_entropy_decoder { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + JMETHOD(boolean, decode_mcu, (j_decompress_ptr cinfo, + JBLOCKROW *MCU_data)); + + /* This is here to share code between baseline and progressive decoders; */ + /* other modules probably should not use it */ + boolean insufficient_data; /* set TRUE after emitting warning */ +}; + +/* Inverse DCT (also performs dequantization) */ +typedef JMETHOD(void, inverse_DCT_method_ptr, + (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col)); + +struct jpeg_inverse_dct { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + /* It is useful to allow each component to have a separate IDCT method. */ + inverse_DCT_method_ptr inverse_DCT[MAX_COMPONENTS]; +}; + +/* Upsampling (note that upsampler must also call color converter) */ +struct jpeg_upsampler { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, upsample, (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, + JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, + JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); + + boolean need_context_rows; /* TRUE if need rows above & below */ +}; + +/* Colorspace conversion */ +struct jpeg_color_deconverter { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, color_convert, (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION input_row, + JSAMPARRAY output_buf, int num_rows)); +}; + +/* Color quantization or color precision reduction */ +struct jpeg_color_quantizer { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo, boolean is_pre_scan)); + JMETHOD(void, color_quantize, (j_decompress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPARRAY output_buf, + int num_rows)); + JMETHOD(void, finish_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, new_color_map, (j_decompress_ptr cinfo)); +}; + + +/* Miscellaneous useful macros */ + +#undef MAX +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#undef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) + + +/* We assume that right shift corresponds to signed division by 2 with + * rounding towards minus infinity. This is correct for typical "arithmetic + * shift" instructions that shift in copies of the sign bit. But some + * C compilers implement >> with an unsigned shift. For these machines you + * must define RIGHT_SHIFT_IS_UNSIGNED. + * RIGHT_SHIFT provides a proper signed right shift of an INT32 quantity. + * It is only applied with constant shift counts. SHIFT_TEMPS must be + * included in the variables of any routine using RIGHT_SHIFT. + */ + +#ifdef RIGHT_SHIFT_IS_UNSIGNED +#define SHIFT_TEMPS INT32 shift_temp; +#define RIGHT_SHIFT(x,shft) \ + ((shift_temp = (x)) < 0 ? \ + (shift_temp >> (shft)) | ((~((INT32) 0)) << (32-(shft))) : \ + (shift_temp >> (shft))) +#else +#define SHIFT_TEMPS +#define RIGHT_SHIFT(x,shft) ((x) >> (shft)) +#endif + + +/* Short forms of external names for systems with brain-damaged linkers. */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jinit_compress_master jICompress +#define jinit_c_master_control jICMaster +#define jinit_c_main_controller jICMainC +#define jinit_c_prep_controller jICPrepC +#define jinit_c_coef_controller jICCoefC +#define jinit_color_converter jICColor +#define jinit_downsampler jIDownsampler +#define jinit_forward_dct jIFDCT +#define jinit_huff_encoder jIHEncoder +#define jinit_phuff_encoder jIPHEncoder +#define jinit_marker_writer jIMWriter +#define jinit_master_decompress jIDMaster +#define jinit_d_main_controller jIDMainC +#define jinit_d_coef_controller jIDCoefC +#define jinit_d_post_controller jIDPostC +#define jinit_input_controller jIInCtlr +#define jinit_marker_reader jIMReader +#define jinit_huff_decoder jIHDecoder +#define jinit_phuff_decoder jIPHDecoder +#define jinit_inverse_dct jIIDCT +#define jinit_upsampler jIUpsampler +#define jinit_color_deconverter jIDColor +#define jinit_1pass_quantizer jI1Quant +#define jinit_2pass_quantizer jI2Quant +#define jinit_merged_upsampler jIMUpsampler +#define jinit_memory_mgr jIMemMgr +#define jdiv_round_up jDivRound +#define jround_up jRound +#define jcopy_sample_rows jCopySamples +#define jcopy_block_row jCopyBlocks +#define jzero_far jZeroFar +#define jpeg_zigzag_order jZIGTable +#define jpeg_natural_order jZAGTable +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + + +/* Compression module initialization routines */ +EXTERN(void) jinit_compress_master JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_c_master_control JPP((j_compress_ptr cinfo, + boolean transcode_only)); +EXTERN(void) jinit_c_main_controller JPP((j_compress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_c_prep_controller JPP((j_compress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_c_coef_controller JPP((j_compress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_color_converter JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_downsampler JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_forward_dct JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_huff_encoder JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_phuff_encoder JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_marker_writer JPP((j_compress_ptr cinfo)); +/* Decompression module initialization routines */ +EXTERN(void) jinit_master_decompress JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_d_main_controller JPP((j_decompress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_d_coef_controller JPP((j_decompress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_d_post_controller JPP((j_decompress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_input_controller JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_marker_reader JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_huff_decoder JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_phuff_decoder JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_inverse_dct JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_upsampler JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_color_deconverter JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_1pass_quantizer JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_2pass_quantizer JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_merged_upsampler JPP((j_decompress_ptr cinfo)); +/* Memory manager initialization */ +EXTERN(void) jinit_memory_mgr JPP((j_common_ptr cinfo)); + +/* Utility routines in jutils.c */ +EXTERN(long) jdiv_round_up JPP((long a, long b)); +EXTERN(long) jround_up JPP((long a, long b)); +EXTERN(void) jcopy_sample_rows JPP((JSAMPARRAY input_array, int source_row, + JSAMPARRAY output_array, int dest_row, + int num_rows, JDIMENSION num_cols)); +EXTERN(void) jcopy_block_row JPP((JBLOCKROW input_row, JBLOCKROW output_row, + JDIMENSION num_blocks)); +EXTERN(void) jzero_far JPP((void FAR * target, size_t bytestozero)); +/* Constant tables in jutils.c */ +#if 0 /* This table is not actually needed in v6a */ +extern const int jpeg_zigzag_order[]; /* natural coef order to zigzag order */ +#endif +extern const int jpeg_natural_order[]; /* zigzag coef order to natural order */ + +/* Suppress undefined-structure complaints if necessary. */ + +#ifdef INCOMPLETE_TYPES_BROKEN +#ifndef AM_MEMORY_MANAGER /* only jmemmgr.c defines these */ +struct jvirt_sarray_control { long dummy; }; +struct jvirt_barray_control { long dummy; }; +#endif +#endif /* INCOMPLETE_TYPES_BROKEN */ diff --git a/src/imageutils/orient6.jpg b/src/imageutils/orient6.jpg Binary files differnew file mode 100644 index 0000000..db1002b --- /dev/null +++ b/src/imageutils/orient6.jpg diff --git a/src/imageutils/orientation.h b/src/imageutils/orientation.h new file mode 100644 index 0000000..64f0ad8 --- /dev/null +++ b/src/imageutils/orientation.h @@ -0,0 +1,58 @@ +// 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 ORIENTATION_H +#define ORIENTATION_H + + +namespace ImageUtils { + +/* Explanation extracted from http://sylvana.net/jpegcrop/exif_orientation.html + + For convenience, here is what the letter F would look like if it were tagged +correctly and displayed by a program that ignores the orientation tag (thus +showing the stored image): + + 1 2 3 4 5 6 7 8 + +888888 888888 88 88 8888888888 88 88 8888888888 +88 88 88 88 88 88 88 88 88 88 88 88 +8888 8888 8888 8888 88 8888888888 8888888888 88 +88 88 88 88 +88 88 888888 888888 + +*/ + +enum Orientation { + NOT_AVAILABLE=0, + NORMAL=1, + HFLIP=2, + ROT_180=3, + VFLIP=4, + TRANSPOSE=5, + ROT_90=6, + TRANSVERSE=7, + ROT_270=8 +}; + +} + + +#endif diff --git a/src/imageutils/scale.cpp b/src/imageutils/scale.cpp new file mode 100644 index 0000000..99ed186 --- /dev/null +++ b/src/imageutils/scale.cpp @@ -0,0 +1,1979 @@ +// This file includes code for scaling images, in two versions. +// One ported from ImageMagick (slower, but can achieve better quality), +// and from Imlib2 ported by Mosfet (very fast). + + +// ImageMagick code begin +// ---------------------- + +// This code is ImageMagick's resize code, adapted for QImage, with +// fastfloat class added as an optimization. +// The original license text follows. + +/* +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% % +% % +% RRRR EEEEE SSSSS IIIII ZZZZZ EEEEE % +% R R E SS I ZZ E % +% RRRR EEE SSS I ZZZ EEE % +% R R E SS I ZZ E % +% R R EEEEE SSSSS IIIII ZZZZZ EEEEE % +% % +% ImageMagick Image Resize Methods % +% % +% % +% Software Design % +% John Cristy % +% July 1992 % +% % +% % +% Copyright (C) 2003 ImageMagick Studio, a non-profit organization dedicated % +% to making software imaging solutions freely available. % +% % +% Permission is hereby granted, free of charge, to any person obtaining a % +% copy of this software and associated documentation files ("ImageMagick"), % +% to deal in ImageMagick without restriction, including without limitation % +% the rights to use, copy, modify, merge, publish, distribute, sublicense, % +% and/or sell copies of ImageMagick, and to permit persons to whom the % +% ImageMagick 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 ImageMagick. % +% % +% 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 % +% ImageMagick Studio 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 ImageMagick or the use or other dealings in % +% ImageMagick. % +% % +% Except as contained in this notice, the name of the ImageMagick Studio % +% shall not be used in advertising or otherwise to promote the sale, use or % +% other dealings in ImageMagick without prior written authorization from the % +% ImageMagick Studio. % +% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% +*/ +#include "config.h" + +// System +#ifdef HAVE_ENDIAN_H +#include <endian.h> +#else +#ifdef HAVE_SYS_ENDIAN_H +#include <sys/endian.h> +#endif +#endif + +// Qt +#include <qimage.h> +#include <qcolor.h> + +#include <kdeversion.h> +#include <kcpuinfo.h> + +#include <string.h> +#include <stdlib.h> + +// Local +#include "imageutils.h" + +// everything in namespace +namespace ImageUtils { + + +#define Max QMAX +#define Min QMIN + +// mustn't be less than used precision (i.e. 1/fastfloat::RATIO) +#define MagickEpsilon 0.0002 + +// fastfloat begin +// this class stores floating point numbers as integers, with BITS shift, +// i.e. value XYZ is stored as XYZ * RATIO +struct fastfloat + { + private: + enum { BITS = 12, RATIO = 4096 }; + public: + fastfloat() {} + fastfloat( long v ) : value( v << BITS ) {} + fastfloat( int v ) : value( v << BITS ) {} + fastfloat( double v ) : value( static_cast< long >( v * RATIO + 0.5 )) {} + double toDouble() const { return static_cast< double >( value ) / RATIO; } + long toLong() const { return value >> BITS; } + fastfloat& operator += ( fastfloat r ) { value += r.value; return *this; } + fastfloat& operator -= ( fastfloat r ) { value -= r.value; return *this; } + fastfloat& operator *= ( fastfloat r ) { value = static_cast< long long >( value ) * r.value >> BITS; return *this; } + fastfloat& operator /= ( fastfloat r ) { value = ( static_cast< long long >( value ) << BITS ) / r.value; return *this; } + bool operator< ( fastfloat r ) const { return value < r.value; } + bool operator<= ( fastfloat r ) const { return value <= r.value; } + bool operator> ( fastfloat r ) const { return value > r.value; } + bool operator>= ( fastfloat r ) const { return value >= r.value; } + bool operator== ( fastfloat r ) const { return value == r.value; } + bool operator!= ( fastfloat r ) const { return value != r.value; } + fastfloat operator-() const { return fastfloat( -value, false ); } + private: + fastfloat( long v, bool ) : value( v ) {} // for operator-() + long value; + }; + +inline fastfloat operator+ ( fastfloat l, fastfloat r ) { return fastfloat( l ) += r; } +inline fastfloat operator- ( fastfloat l, fastfloat r ) { return fastfloat( l ) -= r; } +inline fastfloat operator* ( fastfloat l, fastfloat r ) { return fastfloat( l ) *= r; } +inline fastfloat operator/ ( fastfloat l, fastfloat r ) { return fastfloat( l ) /= r; } + +inline bool operator< ( fastfloat l, double r ) { return l < fastfloat( r ); } +inline bool operator<= ( fastfloat l, double r ) { return l <= fastfloat( r ); } +inline bool operator> ( fastfloat l, double r ) { return l > fastfloat( r ); } +inline bool operator>= ( fastfloat l, double r ) { return l >= fastfloat( r ); } +inline bool operator== ( fastfloat l, double r ) { return l == fastfloat( r ); } +inline bool operator!= ( fastfloat l, double r ) { return l != fastfloat( r ); } + +inline bool operator< ( double l, fastfloat r ) { return fastfloat( l ) < r ; } +inline bool operator<= ( double l, fastfloat r ) { return fastfloat( l ) <= r ; } +inline bool operator> ( double l, fastfloat r ) { return fastfloat( l ) > r ; } +inline bool operator>= ( double l, fastfloat r ) { return fastfloat( l ) >= r ; } +inline bool operator== ( double l, fastfloat r ) { return fastfloat( l ) == r ; } +inline bool operator!= ( double l, fastfloat r ) { return fastfloat( l ) != r ; } + +inline double fasttodouble( fastfloat v ) { return v.toDouble(); } +inline long fasttolong( fastfloat v ) { return v.toLong(); } + +#if 1 // change to 0 to turn fastfloat usage off +#else +#define fastfloat double +#define fasttodouble( v ) double( v ) +#define fasttolong( v ) long( v ) +#endif + +//fastfloat end + + +typedef fastfloat (*Filter)(const fastfloat, const fastfloat); + +typedef struct _ContributionInfo +{ + fastfloat + weight; + + long + pixel; +} ContributionInfo; + + +/* +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% % +% % +% % +% R e s i z e I m a g e % +% % +% % +% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% ResizeImage() scales an image to the desired dimensions with one of these +% filters: +% +% Bessel Blackman Box +% Catrom Cubic Gaussian +% Hanning Hermite Lanczos +% Mitchell Point Quandratic +% Sinc Triangle +% +% Most of the filters are FIR (finite impulse response), however, Bessel, +% Gaussian, and Sinc are IIR (infinite impulse response). Bessel and Sinc +% are windowed (brought down to zero) with the Blackman filter. +% +% ResizeImage() was inspired by Paul Heckbert's zoom program. +% +% The format of the ResizeImage method is: +% +% Image *ResizeImage(Image *image,const unsigned long columns, +% const unsigned long rows,const FilterTypes filter,const double blur, +% ExceptionInfo *exception) +% +% A description of each parameter follows: +% +% o image: The image. +% +% o columns: The number of columns in the scaled image. +% +% o rows: The number of rows in the scaled image. +% +% o filter: Image filter to use. +% +% o blur: The blur factor where > 1 is blurry, < 1 is sharp. +% +% o exception: Return any errors or warnings in this structure. +% +% +*/ + +#if 0 +static fastfloat Bessel(const fastfloat x,const fastfloat) +{ + if (x == 0.0) + return(MagickPI/4.0); + return(BesselOrderOne(MagickPI*x)/(2.0*x)); +} + +static fastfloat Sinc(const fastfloat x,const fastfloat) +{ + if (x == 0.0) + return(1.0); + return(sin(MagickPI*x)/(MagickPI*x)); +} + +static fastfloat Blackman(const fastfloat x,const fastfloat) +{ + return(0.42+0.5*cos(MagickPI*x)+0.08*cos(2*MagickPI*x)); +} + +static fastfloat BlackmanBessel(const fastfloat x,const fastfloat) +{ + return(Blackman(x/support,support)*Bessel(x,support)); +} + +static fastfloat BlackmanSinc(const fastfloat x,const fastfloat) +{ + return(Blackman(x/support,support)*Sinc(x,support)); +} +#endif + +static fastfloat Box(const fastfloat x,const fastfloat) +{ + if (x < -0.5) + return(0.0); + if (x < 0.5) + return(1.0); + return(0.0); +} + +#if 0 +static fastfloat Catrom(const fastfloat x,const fastfloat) +{ + if (x < -2.0) + return(0.0); + if (x < -1.0) + return(0.5*(4.0+x*(8.0+x*(5.0+x)))); + if (x < 0.0) + return(0.5*(2.0+x*x*(-5.0-3.0*x))); + if (x < 1.0) + return(0.5*(2.0+x*x*(-5.0+3.0*x))); + if (x < 2.0) + return(0.5*(4.0+x*(-8.0+x*(5.0-x)))); + return(0.0); +} + +static fastfloat Cubic(const fastfloat x,const fastfloat) +{ + if (x < -2.0) + return(0.0); + if (x < -1.0) + return((2.0+x)*(2.0+x)*(2.0+x)/6.0); + if (x < 0.0) + return((4.0+x*x*(-6.0-3.0*x))/6.0); + if (x < 1.0) + return((4.0+x*x*(-6.0+3.0*x))/6.0); + if (x < 2.0) + return((2.0-x)*(2.0-x)*(2.0-x)/6.0); + return(0.0); +} + +static fastfloat Gaussian(const fastfloat x,const fastfloat) +{ + return(exp(-2.0*x*x)*sqrt(2.0/MagickPI)); +} + +static fastfloat Hanning(const fastfloat x,const fastfloat) +{ + return(0.5+0.5*cos(MagickPI*x)); +} + +static fastfloat Hamming(const fastfloat x,const fastfloat) +{ + return(0.54+0.46*cos(MagickPI*x)); +} + +static fastfloat Hermite(const fastfloat x,const fastfloat) +{ + if (x < -1.0) + return(0.0); + if (x < 0.0) + return((2.0*(-x)-3.0)*(-x)*(-x)+1.0); + if (x < 1.0) + return((2.0*x-3.0)*x*x+1.0); + return(0.0); +} + +static fastfloat Lanczos(const fastfloat x,const fastfloat support) +{ + if (x < -3.0) + return(0.0); + if (x < 0.0) + return(Sinc(-x,support)*Sinc(-x/3.0,support)); + if (x < 3.0) + return(Sinc(x,support)*Sinc(x/3.0,support)); + return(0.0); +} + +static fastfloat Mitchell(const fastfloat x,const fastfloat) +{ +#define B (1.0/3.0) +#define C (1.0/3.0) +#define P0 (( 6.0- 2.0*B )/6.0) +#define P2 ((-18.0+12.0*B+ 6.0*C)/6.0) +#define P3 (( 12.0- 9.0*B- 6.0*C)/6.0) +#define Q0 (( 8.0*B+24.0*C)/6.0) +#define Q1 (( -12.0*B-48.0*C)/6.0) +#define Q2 (( 6.0*B+30.0*C)/6.0) +#define Q3 (( - 1.0*B- 6.0*C)/6.0) + + if (x < -2.0) + return(0.0); + if (x < -1.0) + return(Q0-x*(Q1-x*(Q2-x*Q3))); + if (x < 0.0) + return(P0+x*x*(P2-x*P3)); + if (x < 1.0) + return(P0+x*x*(P2+x*P3)); + if (x < 2.0) + return(Q0+x*(Q1+x*(Q2+x*Q3))); + return(0.0); + +#undef B +#undef C +#undef P0 +#undef P2 +#undef P3 +#undef Q0 +#undef Q1 +#undef Q2 +#undef Q3 +} +#endif + +// this is the same like Mitchell, but it has different values +// for B and C, resulting in sharper images +// http://sourceforge.net/mailarchive/forum.php?thread_id=7445822&forum_id=1210 +static fastfloat Bicubic(const fastfloat x,const fastfloat) +{ +#define B (0.0/3.0) +#define C (2.0/3.0) +#define P0 (( 6.0- 2.0*B )/6.0) +#define P2 ((-18.0+12.0*B+ 6.0*C)/6.0) +#define P3 (( 12.0- 9.0*B- 6.0*C)/6.0) +#define Q0 (( 8.0*B+24.0*C)/6.0) +#define Q1 (( -12.0*B-48.0*C)/6.0) +#define Q2 (( 6.0*B+30.0*C)/6.0) +#define Q3 (( - 1.0*B- 6.0*C)/6.0) + + if (x < -2.0) + return(0.0); + if (x < -1.0) + return(Q0-x*(Q1-x*(Q2-x*Q3))); + if (x < 0.0) + return(P0+x*x*(P2-x*P3)); + if (x < 1.0) + return(P0+x*x*(P2+x*P3)); + if (x < 2.0) + return(Q0+x*(Q1+x*(Q2+x*Q3))); + return(0.0); + +#undef B +#undef C +#undef P0 +#undef P2 +#undef P3 +#undef Q0 +#undef Q1 +#undef Q2 +#undef Q3 +} + +#if 0 +static fastfloat Quadratic(const fastfloat x,const fastfloat) +{ + if (x < -1.5) + return(0.0); + if (x < -0.5) + return(0.5*(x+1.5)*(x+1.5)); + if (x < 0.5) + return(0.75-x*x); + if (x < 1.5) + return(0.5*(x-1.5)*(x-1.5)); + return(0.0); +} +#endif + +static fastfloat Triangle(const fastfloat x,const fastfloat) +{ + if (x < -1.0) + return(0.0); + if (x < 0.0) + return(1.0+x); + if (x < 1.0) + return(1.0-x); + return(0.0); +} + +static void HorizontalFilter(const QImage& source,QImage& destination, + const fastfloat x_factor,const fastfloat blur, + ContributionInfo *contribution, Filter filter, fastfloat filtersupport) +{ + fastfloat + center, + density, + scale, + support; + + long + n, + start, + stop, + y; + + register long + i, + x; + + /* + Apply filter to resize horizontally from source to destination. + */ + scale=blur*Max(1.0/x_factor,1.0); + support=scale* filtersupport; + if (support <= 0.5) + { + /* + Reduce to point sampling. + */ + support=0.5+MagickEpsilon; + scale=1.0; + } + scale=1.0/scale; + for (x=0; x < (long) destination.width(); x++) + { + center=(fastfloat) (x+0.5)/x_factor; + start= fasttolong(Max(center-support+0.5,0)); + stop= fasttolong(Min(center+support+0.5,source.width())); + density=0.0; + for (n=0; n < (stop-start); n++) + { + contribution[n].pixel=start+n; + contribution[n].weight= + filter (scale*(start+n-center+0.5), filtersupport ); + density+=contribution[n].weight; + } + if ((density != 0.0) && (density != 1.0)) + { + /* + Normalize. + */ + density=1.0/density; + for (i=0; i < n; i++) + contribution[i].weight*=density; + } +// p=AcquireImagePixels(source,contribution[0].pixel,0,contribution[n-1].pixel- +// contribution[0].pixel+1,source->rows,exception); +// q=SetImagePixels(destination,x,0,1,destination->rows); + for (y=0; y < (long) destination.height(); y++) + { + fastfloat red = 0; + fastfloat green = 0; + fastfloat blue = 0; + fastfloat alpha = 0; + for (i=0; i < n; i++) + { + int px = contribution[i].pixel; + int py = y; + QRgb p = reinterpret_cast< QRgb* >( source.jumpTable()[ py ])[ px ]; + red+=contribution[i].weight*qRed(p); + green+=contribution[i].weight*qGreen(p); + blue+=contribution[i].weight*qBlue(p); + alpha+=contribution[i].weight*qAlpha(p); + } + QRgb pix = qRgba( + fasttolong( red < 0 ? 0 : red > 255 ? 255 : red + 0.5 ), + fasttolong( green < 0 ? 0 : green > 255 ? 255 : green + 0.5 ), + fasttolong( blue < 0 ? 0 : blue > 255 ? 255 : blue + 0.5 ), + fasttolong( alpha < 0 ? 0 : alpha > 255 ? 255 : alpha + 0.5 )); + reinterpret_cast< QRgb* >( destination.jumpTable()[ y ])[ x ] = pix; + } + } +} + +static void VerticalFilter(const QImage& source,QImage& destination, + const fastfloat y_factor,const fastfloat blur, + ContributionInfo *contribution, Filter filter, fastfloat filtersupport ) +{ + fastfloat + center, + density, + scale, + support; + + long + n, + start, + stop, + x; + + register long + i, + y; + + /* + Apply filter to resize vertically from source to destination. + */ + scale=blur*Max(1.0/y_factor,1.0); + support=scale* filtersupport; + if (support <= 0.5) + { + /* + Reduce to point sampling. + */ + support=0.5+MagickEpsilon; + scale=1.0; + } + scale=1.0/scale; + for (y=0; y < (long) destination.height(); y++) + { + center=(fastfloat) (y+0.5)/y_factor; + start= fasttolong(Max(center-support+0.5,0)); + stop= fasttolong(Min(center+support+0.5,source.height())); + density=0.0; + for (n=0; n < (stop-start); n++) + { + contribution[n].pixel=start+n; + contribution[n].weight= + filter (scale*(start+n-center+0.5), filtersupport); + density+=contribution[n].weight; + } + if ((density != 0.0) && (density != 1.0)) + { + /* + Normalize. + */ + density=1.0/density; + for (i=0; i < n; i++) + contribution[i].weight*=density; + } +// p=AcquireImagePixels(source,0,contribution[0].pixel,source->columns, +// contribution[n-1].pixel-contribution[0].pixel+1,exception); +// q=SetImagePixels(destination,0,y,destination->columns,1); + for (x=0; x < (long) destination.width(); x++) + { + fastfloat red = 0; + fastfloat green = 0; + fastfloat blue = 0; + fastfloat alpha = 0; + for (i=0; i < n; i++) + { + int px = x; + int py = contribution[i].pixel; + QRgb p = reinterpret_cast< QRgb* >( source.jumpTable()[ py ])[ px ]; + red+=contribution[i].weight*qRed(p); + green+=contribution[i].weight*qGreen(p); + blue+=contribution[i].weight*qBlue(p); + alpha+=contribution[i].weight*qAlpha(p); + } + QRgb pix = qRgba( + fasttolong( red < 0 ? 0 : red > 255 ? 255 : red + 0.5 ), + fasttolong( green < 0 ? 0 : green > 255 ? 255 : green + 0.5 ), + fasttolong( blue < 0 ? 0 : blue > 255 ? 255 : blue + 0.5 ), + fasttolong( alpha < 0 ? 0 : alpha > 255 ? 255 : alpha + 0.5 )); + reinterpret_cast< QRgb* >( destination.jumpTable()[ y ])[ x ] = pix; + } + } +} + +static QImage ResizeImage(const QImage& image,const int columns, + const int rows, Filter filter, fastfloat filtersupport, double blur) +{ + ContributionInfo + *contribution; + + fastfloat + support, + x_factor, + x_support, + y_factor, + y_support; + + /* + Initialize resize image attributes. + */ + if ((columns == image.width()) && (rows == image.height()) && (blur == 1.0)) + return image.copy(); + QImage resize_image( columns, rows, 32 ); + resize_image.setAlphaBuffer( image.hasAlphaBuffer()); + /* + Allocate filter contribution info. + */ + x_factor=(fastfloat) resize_image.width()/image.width(); + y_factor=(fastfloat) resize_image.height()/image.height(); +// i=(long) LanczosFilter; +// if (image->filter != UndefinedFilter) +// i=(long) image->filter; +// else +// if ((image->storage_class == PseudoClass) || image->matte || +// ((x_factor*y_factor) > 1.0)) +// i=(long) MitchellFilter; + x_support=blur*Max(1.0/x_factor,1.0)*filtersupport; + y_support=blur*Max(1.0/y_factor,1.0)*filtersupport; + support=Max(x_support,y_support); + if (support < filtersupport) + support=filtersupport; + contribution=new ContributionInfo[ fasttolong( 2.0*Max(support,0.5)+3 ) ]; + Q_CHECK_PTR( contribution ); + /* + Resize image. + */ + if (((fastfloat) columns*(image.height()+rows)) > + ((fastfloat) rows*(image.width()+columns))) + { + QImage source_image( columns, image.height(), 32 ); + source_image.setAlphaBuffer( image.hasAlphaBuffer()); + HorizontalFilter(image,source_image,x_factor,blur, + contribution,filter,filtersupport); + VerticalFilter(source_image,resize_image,y_factor, + blur,contribution,filter,filtersupport); + } + else + { + QImage source_image( image.width(), rows, 32 ); + source_image.setAlphaBuffer( image.hasAlphaBuffer()); + VerticalFilter(image,source_image,y_factor,blur, + contribution,filter,filtersupport); + HorizontalFilter(source_image,resize_image,x_factor, + blur,contribution,filter,filtersupport); + } + /* + Free allocated memory. + */ + delete[] contribution; + return(resize_image); +} + + +#undef Max +#undef Min +#undef MagickEpsilon + + +// filters and their matching support values +#if 0 + static const FilterInfo + filters[SincFilter+1] = + { + { Box, 0.0 }, + { Box, 0.0 }, + { Box, 0.5 }, + { Triangle, 1.0 }, + { Hermite, 1.0 }, + { Hanning, 1.0 }, + { Hamming, 1.0 }, + { Blackman, 1.0 }, + { Gaussian, 1.25 }, + { Quadratic, 1.5 }, + { Cubic, 2.0 }, + { Catrom, 2.0 }, + { Mitchell, 2.0 }, + { Lanczos, 3.0 }, + { BlackmanBessel, 3.2383 }, + { BlackmanSinc, 4.0 } + }; +#endif + + +/* +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% % +% % +% % +% S a m p l e I m a g e % +% % +% % +% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% SampleImage() scales an image to the desired dimensions with pixel +% sampling. Unlike other scaling methods, this method does not introduce +% any additional color into the scaled image. +% +% The format of the SampleImage method is: +% +% Image *SampleImage(const Image *image,const unsigned long columns, +% const unsigned long rows,ExceptionInfo *exception) +% +% A description of each parameter follows: +% +% o image: The image. +% +% o columns: The number of columns in the sampled image. +% +% o rows: The number of rows in the sampled image. +% +% o exception: Return any errors or warnings in this structure. +% +% +*/ +QImage SampleImage(const QImage& image,const int columns, + const int rows) +{ + int + *x_offset, + *y_offset; + + long + j, + y; + + uchar + *pixels; + + register const uchar + *p; + + register long + x; + + register uchar + *q; + + /* + Initialize sampled image attributes. + */ + if ((columns == image.width()) && (rows == image.height())) + return image; + // This function is modified to handle any image depth, not only + // 32bit like the ImageMagick original. This avoids the relatively + // expensive conversion. + const int d = image.depth() / 8; + QImage sample_image( columns, rows, image.depth()); + sample_image.setAlphaBuffer( image.hasAlphaBuffer()); + /* + Allocate scan line buffer and column offset buffers. + */ + pixels= new uchar[ image.width() * d ]; + x_offset= new int[ sample_image.width() ]; + y_offset= new int[ sample_image.height() ]; + /* + Initialize pixel offsets. + */ +// In the following several code 0.5 needs to be added, otherwise the image +// would be moved by half a pixel to bottom-right, just like +// with Qt's QImage::scale() + for (x=0; x < (long) sample_image.width(); x++) + { + x_offset[x]=int((x+0.5)*image.width()/sample_image.width()); + } + for (y=0; y < (long) sample_image.height(); y++) + { + y_offset[y]=int((y+0.5)*image.height()/sample_image.height()); + } + /* + Sample each row. + */ + j=(-1); + for (y=0; y < (long) sample_image.height(); y++) + { + q= sample_image.scanLine( y ); + if (j != y_offset[y] ) + { + /* + Read a scan line. + */ + j= y_offset[y]; + p= image.scanLine( j ); + (void) memcpy(pixels,p,image.width()*d); + } + /* + Sample each column. + */ + switch( d ) + { + case 1: // 8bit + for (x=0; x < (long) sample_image.width(); x++) + { + *q++=pixels[ x_offset[x] ]; + } + break; + case 4: // 32bit + for (x=0; x < (long) sample_image.width(); x++) + { + *(QRgb*)q=((QRgb*)pixels)[ x_offset[x] ]; + q += d; + } + break; + default: + for (x=0; x < (long) sample_image.width(); x++) + { + memcpy( q, pixels + x_offset[x] * d, d ); + q += d; + } + break; + } + } + if( d != 4 ) // != 32bit + { + sample_image.setNumColors( image.numColors()); + for( int i = 0; i < image.numColors(); ++i ) + sample_image.setColor( i, image.color( i )); + } + delete[] y_offset; + delete[] x_offset; + delete[] pixels; + return sample_image; +} + + +// ImageMagick code end + + +// Imlib2/Mosfet code begin +// ------------------------ + +// This code is Imlib2 code, additionally modified by Mosfet, and with few small +// modifications for Gwenview. The MMX scaling code also belongs to it. + +// The original license texts follow. + +/** + * This is the normal smoothscale method, based on Imlib2's smoothscale. + * + * Originally I took the algorithm used in NetPBM and Qt and added MMX/3dnow + * optimizations. It ran in about 1/2 the time as Qt. Then I ported Imlib's + * C algorithm and it ran at about the same speed as my MMX optimized one... + * Finally I ported Imlib's MMX version and it ran in less than half the + * time as my MMX algorithm, (taking only a quarter of the time Qt does). + * + * Changes include formatting, namespaces and other C++'ings, removal of old + * #ifdef'ed code, and removal of unneeded border calculation code. + * + * Imlib2 is (C) Carsten Haitzler and various contributors. The MMX code + * is by Willem Monsuwe <[email protected]>. All other modifications are + * (C) Daniel M. Duley. + */ + +/* + Copyright (C) 2004 Daniel M. Duley <[email protected]> + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +/* +Copyright (C) 2000 Carsten Haitzler and various contributors (see AUTHORS) + +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 of the Software and its Copyright notices. In addition publicly +documented acknowledgment must be given that this software has been used if no +source code of this software is made available publicly. This includes +acknowledgments in either Copyright notices, Manuals, Publicity and Marketing +documents or any documentation provided with any product containing this +software. This License does not apply to any software that links to the +libraries provided by this software (statically or dynamically), but only to +the software provided. + +Please see the COPYING.PLAIN for a plain-english explanation of this notice +and it's intent. + +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. +*/ + +namespace MImageScale{ + typedef struct __mimage_scale_info + { + int *xpoints; + unsigned int **ypoints; + int *xapoints, *yapoints; + int xup_yup; + } MImageScaleInfo; + + unsigned int** mimageCalcYPoints(unsigned int *src, int sow, int sh, + int dh); + int* mimageCalcXPoints(int sw, int dw); + int* mimageCalcApoints(int s, int d, int up); + MImageScaleInfo* mimageFreeScaleInfo(MImageScaleInfo *isi); + MImageScaleInfo *mimageCalcScaleInfo(QImage &img, int sw, int sh, + int dw, int dh, char aa, int sow); + void mimageSampleRGBA(MImageScaleInfo *isi, unsigned int *dest, int dxx, + int dyy, int dx, int dy, int dw, int dh, int dow); + void mimageScaleAARGBA(MImageScaleInfo *isi, unsigned int *dest, int dxx, + int dyy, int dx, int dy, int dw, int dh, int dow, + int sow); + void mimageScaleAARGB(MImageScaleInfo *isi, unsigned int *dest, int dxx, + int dyy, int dx, int dy, int dw, int dh, int dow, int + sow); + QImage smoothScale(const QImage& img, int dw, int dh); + typedef long long llong; +} + +#ifdef HAVE_X86_MMX +extern "C" { + void __mimageScale_mmx_AARGBA(MImageScale::MImageScaleInfo *isi, + unsigned int *dest, int dxx, int dyy, + int dx, int dy, int dw, int dh, + int dow, int sow); +} +#endif + +using namespace MImageScale; + +QImage MImageScale::smoothScale(const QImage& image, int dw, int dh) +{ + QImage img = image.depth() < 32 ? image.convertDepth( 32 ) : image; + int w = img.width(); + int h = img.height(); + + int sow = img.bytesPerLine(); + // handle CroppedQImage + if( img.height() > 1 && sow != img.scanLine( 1 ) - img.scanLine( 0 )) + sow = img.scanLine( 1 ) - img.scanLine( 0 ); + sow = sow / ( img.depth() / 8 ); + + MImageScaleInfo *scaleinfo = + mimageCalcScaleInfo(img, w, h, dw, dh, true, sow); + if(!scaleinfo) + return QImage(); + + QImage buffer(dw, dh, 32); + buffer.setAlphaBuffer(img.hasAlphaBuffer()); + +#ifdef HAVE_X86_MMX +//#warning Using MMX Smoothscale + bool haveMMX = KCPUInfo::haveExtension( KCPUInfo::IntelMMX ); + if(haveMMX){ + __mimageScale_mmx_AARGBA(scaleinfo, (unsigned int *)buffer.scanLine(0), + 0, 0, 0, 0, dw, dh, dw, sow); + } + else +#endif + { + if(img.hasAlphaBuffer()) + mimageScaleAARGBA(scaleinfo, (unsigned int *)buffer.scanLine(0), 0, 0, + 0, 0, dw, dh, dw, sow); + else + mimageScaleAARGB(scaleinfo, (unsigned int *)buffer.scanLine(0), 0, 0, + 0, 0, dw, dh, dw, sow); + } + mimageFreeScaleInfo(scaleinfo); + return(buffer); +} + +// +// Code ported from Imlib... +// + +// FIXME: replace with mRed, etc... These work on pointers to pixels, not +// pixel values +#if BYTE_ORDER == BIG_ENDIAN +#define A_VAL(p) ((unsigned char *)(p))[0] +#define R_VAL(p) ((unsigned char *)(p))[1] +#define G_VAL(p) ((unsigned char *)(p))[2] +#define B_VAL(p) ((unsigned char *)(p))[3] +#elif BYTE_ORDER == LITTLE_ENDIAN +#define A_VAL(p) ((unsigned char *)(p))[3] +#define R_VAL(p) ((unsigned char *)(p))[2] +#define G_VAL(p) ((unsigned char *)(p))[1] +#define B_VAL(p) ((unsigned char *)(p))[0] +#else +#error "BYTE_ORDER is not defined" +#endif + +#define INV_XAP (256 - xapoints[x]) +#define XAP (xapoints[x]) +#define INV_YAP (256 - yapoints[dyy + y]) +#define YAP (yapoints[dyy + y]) + +unsigned int** MImageScale::mimageCalcYPoints(unsigned int *src, + int sow, int sh, int dh) +{ + unsigned int **p; + int i, j = 0; + int rv = 0; + llong val, inc; + + if(dh < 0){ + dh = -dh; + rv = 1; + } + p = new unsigned int* [dh+1]; + + val = 0; + inc = (llong(sh) << 16) / dh; + for(i = 0; i < dh; i++){ + p[j++] = src + ((val >> 16) * sow); + val += inc; + } + if(rv){ + for(i = dh / 2; --i >= 0; ){ + unsigned int *tmp = p[i]; + p[i] = p[dh - i - 1]; + p[dh - i - 1] = tmp; + } + } + return(p); +} + +int* MImageScale::mimageCalcXPoints(int sw, int dw) +{ + int *p, i, j = 0; + int rv = 0; + llong val, inc; + + if(dw < 0){ + dw = -dw; + rv = 1; + } + p = new int[dw+1]; + + val = 0; + inc = (llong(sw) << 16) / dw; + for(i = 0; i < dw; i++){ + p[j++] = (val >> 16); + val += inc; + } + + if(rv){ + for(i = dw / 2; --i >= 0; ){ + int tmp = p[i]; + p[i] = p[dw - i - 1]; + p[dw - i - 1] = tmp; + } + } + return(p); +} + +int* MImageScale::mimageCalcApoints(int s, int d, int up) +{ + int *p, i, j = 0, rv = 0; + + if(d < 0){ + rv = 1; + d = -d; + } + p = new int[d]; + + /* scaling up */ + if(up){ + llong val, inc; + + val = 0; + inc = (llong(s) << 16) / d; + for(i = 0; i < d; i++){ + p[j++] = (val >> 8) - ((val >> 8) & 0xffffff00); + if((val >> 16) >= (s - 1)) + p[j - 1] = 0; + val += inc; + } + } + /* scaling down */ + else{ + llong val, inc; + int ap, Cp; + val = 0; + inc = (llong(s) << 16) / d; + Cp = ((llong(d) << 14) / s) + 1; + for(i = 0; i < d; i++){ + ap = ((0x100 - ((val >> 8) & 0xff)) * Cp) >> 8; + p[j] = ap | (Cp << 16); + j++; + val += inc; + } + } + if(rv){ + int tmp; + for(i = d / 2; --i >= 0; ){ + tmp = p[i]; + p[i] = p[d - i - 1]; + p[d - i - 1] = tmp; + } + } + return(p); +} + +MImageScaleInfo* MImageScale::mimageFreeScaleInfo(MImageScaleInfo *isi) +{ + if(isi){ + delete[] isi->xpoints; + delete[] isi->ypoints; + delete[] isi->xapoints; + delete[] isi->yapoints; + delete isi; + } + return(NULL); +} + +MImageScaleInfo* MImageScale::mimageCalcScaleInfo(QImage &img, int sw, int sh, + int dw, int dh, char aa, int sow) +{ + MImageScaleInfo *isi; + int scw, sch; + + scw = dw * img.width() / sw; + sch = dh * img.height() / sh; + + isi = new MImageScaleInfo; + if(!isi) + return(NULL); + memset(isi, 0, sizeof(MImageScaleInfo)); + + isi->xup_yup = (abs(dw) >= sw) + ((abs(dh) >= sh) << 1); + + isi->xpoints = mimageCalcXPoints(img.width(), scw); + if(!isi->xpoints) + return(mimageFreeScaleInfo(isi)); + isi->ypoints = mimageCalcYPoints((unsigned int *)img.scanLine(0), + sow, img.height(), sch ); + if (!isi->ypoints) + return(mimageFreeScaleInfo(isi)); + if(aa){ + isi->xapoints = mimageCalcApoints(img.width(), scw, isi->xup_yup & 1); + if(!isi->xapoints) + return(mimageFreeScaleInfo(isi)); + isi->yapoints = mimageCalcApoints(img.height(), sch, isi->xup_yup & 2); + if(!isi->yapoints) + return(mimageFreeScaleInfo(isi)); + } + return(isi); +} + +/* scale by pixel sampling only */ +void MImageScale::mimageSampleRGBA(MImageScaleInfo *isi, unsigned int *dest, + int dxx, int dyy, int dx, int dy, int dw, + int dh, int dow) +{ + unsigned int *sptr, *dptr; + int x, y, end; + unsigned int **ypoints = isi->ypoints; + int *xpoints = isi->xpoints; + + /* whats the last pixel ont he line so we stop there */ + end = dxx + dw; + /* go through every scanline in the output buffer */ + for(y = 0; y < dh; y++){ + /* get the pointer to the start of the destination scanline */ + dptr = dest + dx + ((y + dy) * dow); + /* calculate the source line we'll scan from */ + sptr = ypoints[dyy + y]; + /* go thru the scanline and copy across */ + for(x = dxx; x < end; x++) + *dptr++ = sptr[xpoints[x]]; + } +} + +/* FIXME: NEED to optimise ScaleAARGBA - currently its "ok" but needs work*/ + +/* scale by area sampling */ +void MImageScale::mimageScaleAARGBA(MImageScaleInfo *isi, unsigned int *dest, + int dxx, int dyy, int dx, int dy, int dw, + int dh, int dow, int sow) +{ + unsigned int *sptr, *dptr; + int x, y, end; + unsigned int **ypoints = isi->ypoints; + int *xpoints = isi->xpoints; + int *xapoints = isi->xapoints; + int *yapoints = isi->yapoints; + + end = dxx + dw; + /* scaling up both ways */ + if(isi->xup_yup == 3){ + /* go through every scanline in the output buffer */ + for(y = 0; y < dh; y++){ + /* calculate the source line we'll scan from */ + dptr = dest + dx + ((y + dy) * dow); + sptr = ypoints[dyy + y]; + if(YAP > 0){ + for(x = dxx; x < end; x++){ + int r, g, b, a; + int rr, gg, bb, aa; + unsigned int *pix; + + if(XAP > 0){ + pix = ypoints[dyy + y] + xpoints[x]; + r = R_VAL(pix) * INV_XAP; + g = G_VAL(pix) * INV_XAP; + b = B_VAL(pix) * INV_XAP; + a = A_VAL(pix) * INV_XAP; + pix++; + r += R_VAL(pix) * XAP; + g += G_VAL(pix) * XAP; + b += B_VAL(pix) * XAP; + a += A_VAL(pix) * XAP; + pix += sow; + rr = R_VAL(pix) * XAP; + gg = G_VAL(pix) * XAP; + bb = B_VAL(pix) * XAP; + aa = A_VAL(pix) * XAP; + pix--; + rr += R_VAL(pix) * INV_XAP; + gg += G_VAL(pix) * INV_XAP; + bb += B_VAL(pix) * INV_XAP; + aa += A_VAL(pix) * INV_XAP; + r = ((rr * YAP) + (r * INV_YAP)) >> 16; + g = ((gg * YAP) + (g * INV_YAP)) >> 16; + b = ((bb * YAP) + (b * INV_YAP)) >> 16; + a = ((aa * YAP) + (a * INV_YAP)) >> 16; + *dptr++ = qRgba(r, g, b, a); + } + else{ + pix = ypoints[dyy + y] + xpoints[x]; + r = R_VAL(pix) * INV_YAP; + g = G_VAL(pix) * INV_YAP; + b = B_VAL(pix) * INV_YAP; + a = A_VAL(pix) * INV_YAP; + pix += sow; + r += R_VAL(pix) * YAP; + g += G_VAL(pix) * YAP; + b += B_VAL(pix) * YAP; + a += A_VAL(pix) * YAP; + r >>= 8; + g >>= 8; + b >>= 8; + a >>= 8; + *dptr++ = qRgba(r, g, b, a); + } + } + } + else{ + for(x = dxx; x < end; x++){ + int r, g, b, a; + unsigned int *pix; + + if(XAP > 0){ + pix = ypoints[dyy + y] + xpoints[x]; + r = R_VAL(pix) * INV_XAP; + g = G_VAL(pix) * INV_XAP; + b = B_VAL(pix) * INV_XAP; + a = A_VAL(pix) * INV_XAP; + pix++; + r += R_VAL(pix) * XAP; + g += G_VAL(pix) * XAP; + b += B_VAL(pix) * XAP; + a += A_VAL(pix) * XAP; + r >>= 8; + g >>= 8; + b >>= 8; + a >>= 8; + *dptr++ = qRgba(r, g, b, a); + } + else + *dptr++ = sptr[xpoints[x] ]; + } + } + } + } + /* if we're scaling down vertically */ + else if(isi->xup_yup == 1){ + /*\ 'Correct' version, with math units prepared for MMXification \*/ + int Cy, j; + unsigned int *pix; + int r, g, b, a, rr, gg, bb, aa; + int yap; + + /* go through every scanline in the output buffer */ + for(y = 0; y < dh; y++){ + Cy = YAP >> 16; + yap = YAP & 0xffff; + + dptr = dest + dx + ((y + dy) * dow); + for(x = dxx; x < end; x++){ + pix = ypoints[dyy + y] + xpoints[x]; + r = (R_VAL(pix) * yap) >> 10; + g = (G_VAL(pix) * yap) >> 10; + b = (B_VAL(pix) * yap) >> 10; + a = (A_VAL(pix) * yap) >> 10; + for(j = (1 << 14) - yap; j > Cy; j -= Cy){ + pix += sow; + r += (R_VAL(pix) * Cy) >> 10; + g += (G_VAL(pix) * Cy) >> 10; + b += (B_VAL(pix) * Cy) >> 10; + a += (A_VAL(pix) * Cy) >> 10; + } + if(j > 0){ + pix += sow; + r += (R_VAL(pix) * j) >> 10; + g += (G_VAL(pix) * j) >> 10; + b += (B_VAL(pix) * j) >> 10; + a += (A_VAL(pix) * j) >> 10; + } + if(XAP > 0){ + pix = ypoints[dyy + y] + xpoints[x] + 1; + rr = (R_VAL(pix) * yap) >> 10; + gg = (G_VAL(pix) * yap) >> 10; + bb = (B_VAL(pix) * yap) >> 10; + aa = (A_VAL(pix) * yap) >> 10; + for(j = (1 << 14) - yap; j > Cy; j -= Cy){ + pix += sow; + rr += (R_VAL(pix) * Cy) >> 10; + gg += (G_VAL(pix) * Cy) >> 10; + bb += (B_VAL(pix) * Cy) >> 10; + aa += (A_VAL(pix) * Cy) >> 10; + } + if(j > 0){ + pix += sow; + rr += (R_VAL(pix) * j) >> 10; + gg += (G_VAL(pix) * j) >> 10; + bb += (B_VAL(pix) * j) >> 10; + aa += (A_VAL(pix) * j) >> 10; + } + r = r * INV_XAP; + g = g * INV_XAP; + b = b * INV_XAP; + a = a * INV_XAP; + r = (r + ((rr * XAP))) >> 12; + g = (g + ((gg * XAP))) >> 12; + b = (b + ((bb * XAP))) >> 12; + a = (a + ((aa * XAP))) >> 12; + } + else{ + r >>= 4; + g >>= 4; + b >>= 4; + a >>= 4; + } + *dptr = qRgba(r, g, b, a); + dptr++; + } + } + } + /* if we're scaling down horizontally */ + else if(isi->xup_yup == 2){ + /*\ 'Correct' version, with math units prepared for MMXification \*/ + int Cx, j; + unsigned int *pix; + int r, g, b, a, rr, gg, bb, aa; + int xap; + + /* go through every scanline in the output buffer */ + for(y = 0; y < dh; y++){ + dptr = dest + dx + ((y + dy) * dow); + for(x = dxx; x < end; x++){ + Cx = XAP >> 16; + xap = XAP & 0xffff; + + pix = ypoints[dyy + y] + xpoints[x]; + r = (R_VAL(pix) * xap) >> 10; + g = (G_VAL(pix) * xap) >> 10; + b = (B_VAL(pix) * xap) >> 10; + a = (A_VAL(pix) * xap) >> 10; + for(j = (1 << 14) - xap; j > Cx; j -= Cx){ + pix++; + r += (R_VAL(pix) * Cx) >> 10; + g += (G_VAL(pix) * Cx) >> 10; + b += (B_VAL(pix) * Cx) >> 10; + a += (A_VAL(pix) * Cx) >> 10; + } + if(j > 0){ + pix++; + r += (R_VAL(pix) * j) >> 10; + g += (G_VAL(pix) * j) >> 10; + b += (B_VAL(pix) * j) >> 10; + a += (A_VAL(pix) * j) >> 10; + } + if(YAP > 0){ + pix = ypoints[dyy + y] + xpoints[x] + sow; + rr = (R_VAL(pix) * xap) >> 10; + gg = (G_VAL(pix) * xap) >> 10; + bb = (B_VAL(pix) * xap) >> 10; + aa = (A_VAL(pix) * xap) >> 10; + for(j = (1 << 14) - xap; j > Cx; j -= Cx){ + pix++; + rr += (R_VAL(pix) * Cx) >> 10; + gg += (G_VAL(pix) * Cx) >> 10; + bb += (B_VAL(pix) * Cx) >> 10; + aa += (A_VAL(pix) * Cx) >> 10; + } + if(j > 0){ + pix++; + rr += (R_VAL(pix) * j) >> 10; + gg += (G_VAL(pix) * j) >> 10; + bb += (B_VAL(pix) * j) >> 10; + aa += (A_VAL(pix) * j) >> 10; + } + r = r * INV_YAP; + g = g * INV_YAP; + b = b * INV_YAP; + a = a * INV_YAP; + r = (r + ((rr * YAP))) >> 12; + g = (g + ((gg * YAP))) >> 12; + b = (b + ((bb * YAP))) >> 12; + a = (a + ((aa * YAP))) >> 12; + } + else{ + r >>= 4; + g >>= 4; + b >>= 4; + a >>= 4; + } + *dptr = qRgba(r, g, b, a); + dptr++; + } + } + } + /* if we're scaling down horizontally & vertically */ + else{ + /*\ 'Correct' version, with math units prepared for MMXification: + |*| The operation 'b = (b * c) >> 16' translates to pmulhw, + |*| so the operation 'b = (b * c) >> d' would translate to + |*| psllw (16 - d), %mmb; pmulh %mmc, %mmb + \*/ + int Cx, Cy, i, j; + unsigned int *pix; + int a, r, g, b, ax, rx, gx, bx; + int xap, yap; + + for(y = 0; y < dh; y++){ + Cy = YAP >> 16; + yap = YAP & 0xffff; + + dptr = dest + dx + ((y + dy) * dow); + for(x = dxx; x < end; x++){ + Cx = XAP >> 16; + xap = XAP & 0xffff; + + sptr = ypoints[dyy + y] + xpoints[x]; + pix = sptr; + sptr += sow; + rx = (R_VAL(pix) * xap) >> 9; + gx = (G_VAL(pix) * xap) >> 9; + bx = (B_VAL(pix) * xap) >> 9; + ax = (A_VAL(pix) * xap) >> 9; + pix++; + for(i = (1 << 14) - xap; i > Cx; i -= Cx){ + rx += (R_VAL(pix) * Cx) >> 9; + gx += (G_VAL(pix) * Cx) >> 9; + bx += (B_VAL(pix) * Cx) >> 9; + ax += (A_VAL(pix) * Cx) >> 9; + pix++; + } + if(i > 0){ + rx += (R_VAL(pix) * i) >> 9; + gx += (G_VAL(pix) * i) >> 9; + bx += (B_VAL(pix) * i) >> 9; + ax += (A_VAL(pix) * i) >> 9; + } + + r = (rx * yap) >> 14; + g = (gx * yap) >> 14; + b = (bx * yap) >> 14; + a = (ax * yap) >> 14; + + for(j = (1 << 14) - yap; j > Cy; j -= Cy){ + pix = sptr; + sptr += sow; + rx = (R_VAL(pix) * xap) >> 9; + gx = (G_VAL(pix) * xap) >> 9; + bx = (B_VAL(pix) * xap) >> 9; + ax = (A_VAL(pix) * xap) >> 9; + pix++; + for(i = (1 << 14) - xap; i > Cx; i -= Cx){ + rx += (R_VAL(pix) * Cx) >> 9; + gx += (G_VAL(pix) * Cx) >> 9; + bx += (B_VAL(pix) * Cx) >> 9; + ax += (A_VAL(pix) * Cx) >> 9; + pix++; + } + if(i > 0){ + rx += (R_VAL(pix) * i) >> 9; + gx += (G_VAL(pix) * i) >> 9; + bx += (B_VAL(pix) * i) >> 9; + ax += (A_VAL(pix) * i) >> 9; + } + + r += (rx * Cy) >> 14; + g += (gx * Cy) >> 14; + b += (bx * Cy) >> 14; + a += (ax * Cy) >> 14; + } + if(j > 0){ + pix = sptr; + sptr += sow; + rx = (R_VAL(pix) * xap) >> 9; + gx = (G_VAL(pix) * xap) >> 9; + bx = (B_VAL(pix) * xap) >> 9; + ax = (A_VAL(pix) * xap) >> 9; + pix++; + for(i = (1 << 14) - xap; i > Cx; i -= Cx){ + rx += (R_VAL(pix) * Cx) >> 9; + gx += (G_VAL(pix) * Cx) >> 9; + bx += (B_VAL(pix) * Cx) >> 9; + ax += (A_VAL(pix) * Cx) >> 9; + pix++; + } + if(i > 0){ + rx += (R_VAL(pix) * i) >> 9; + gx += (G_VAL(pix) * i) >> 9; + bx += (B_VAL(pix) * i) >> 9; + ax += (A_VAL(pix) * i) >> 9; + } + + r += (rx * j) >> 14; + g += (gx * j) >> 14; + b += (bx * j) >> 14; + a += (ax * j) >> 14; + } + + R_VAL(dptr) = r >> 5; + G_VAL(dptr) = g >> 5; + B_VAL(dptr) = b >> 5; + A_VAL(dptr) = a >> 5; + dptr++; + } + } + } +} + +/* scale by area sampling - IGNORE the ALPHA byte*/ +void MImageScale::mimageScaleAARGB(MImageScaleInfo *isi, unsigned int *dest, + int dxx, int dyy, int dx, int dy, int dw, + int dh, int dow, int sow) +{ + unsigned int *sptr, *dptr; + int x, y, end; + unsigned int **ypoints = isi->ypoints; + int *xpoints = isi->xpoints; + int *xapoints = isi->xapoints; + int *yapoints = isi->yapoints; + + end = dxx + dw; + /* scaling up both ways */ + if(isi->xup_yup == 3){ + /* go through every scanline in the output buffer */ + for(y = 0; y < dh; y++){ + /* calculate the source line we'll scan from */ + dptr = dest + dx + ((y + dy) * dow); + sptr = ypoints[dyy + y]; + if(YAP > 0){ + for(x = dxx; x < end; x++){ + int r = 0, g = 0, b = 0; + int rr = 0, gg = 0, bb = 0; + unsigned int *pix; + + if(XAP > 0){ + pix = ypoints[dyy + y] + xpoints[x]; + r = R_VAL(pix) * INV_XAP; + g = G_VAL(pix) * INV_XAP; + b = B_VAL(pix) * INV_XAP; + pix++; + r += R_VAL(pix) * XAP; + g += G_VAL(pix) * XAP; + b += B_VAL(pix) * XAP; + pix += sow; + rr = R_VAL(pix) * XAP; + gg = G_VAL(pix) * XAP; + bb = B_VAL(pix) * XAP; + pix --; + rr += R_VAL(pix) * INV_XAP; + gg += G_VAL(pix) * INV_XAP; + bb += B_VAL(pix) * INV_XAP; + r = ((rr * YAP) + (r * INV_YAP)) >> 16; + g = ((gg * YAP) + (g * INV_YAP)) >> 16; + b = ((bb * YAP) + (b * INV_YAP)) >> 16; + *dptr++ = qRgba(r, g, b, 0xff); + } + else{ + pix = ypoints[dyy + y] + xpoints[x]; + r = R_VAL(pix) * INV_YAP; + g = G_VAL(pix) * INV_YAP; + b = B_VAL(pix) * INV_YAP; + pix += sow; + r += R_VAL(pix) * YAP; + g += G_VAL(pix) * YAP; + b += B_VAL(pix) * YAP; + r >>= 8; + g >>= 8; + b >>= 8; + *dptr++ = qRgba(r, g, b, 0xff); + } + } + } + else{ + for(x = dxx; x < end; x++){ + int r = 0, g = 0, b = 0; + unsigned int *pix; + + if(XAP > 0){ + pix = ypoints[dyy + y] + xpoints[x]; + r = R_VAL(pix) * INV_XAP; + g = G_VAL(pix) * INV_XAP; + b = B_VAL(pix) * INV_XAP; + pix++; + r += R_VAL(pix) * XAP; + g += G_VAL(pix) * XAP; + b += B_VAL(pix) * XAP; + r >>= 8; + g >>= 8; + b >>= 8; + *dptr++ = qRgba(r, g, b, 0xff); + } + else + *dptr++ = sptr[xpoints[x] ]; + } + } + } + } + /* if we're scaling down vertically */ + else if(isi->xup_yup == 1){ + /*\ 'Correct' version, with math units prepared for MMXification \*/ + int Cy, j; + unsigned int *pix; + int r, g, b, rr, gg, bb; + int yap; + + /* go through every scanline in the output buffer */ + for(y = 0; y < dh; y++){ + Cy = YAP >> 16; + yap = YAP & 0xffff; + + dptr = dest + dx + ((y + dy) * dow); + for(x = dxx; x < end; x++){ + pix = ypoints[dyy + y] + xpoints[x]; + r = (R_VAL(pix) * yap) >> 10; + g = (G_VAL(pix) * yap) >> 10; + b = (B_VAL(pix) * yap) >> 10; + pix += sow; + for(j = (1 << 14) - yap; j > Cy; j -= Cy){ + r += (R_VAL(pix) * Cy) >> 10; + g += (G_VAL(pix) * Cy) >> 10; + b += (B_VAL(pix) * Cy) >> 10; + pix += sow; + } + if(j > 0){ + r += (R_VAL(pix) * j) >> 10; + g += (G_VAL(pix) * j) >> 10; + b += (B_VAL(pix) * j) >> 10; + } + if(XAP > 0){ + pix = ypoints[dyy + y] + xpoints[x] + 1; + rr = (R_VAL(pix) * yap) >> 10; + gg = (G_VAL(pix) * yap) >> 10; + bb = (B_VAL(pix) * yap) >> 10; + pix += sow; + for(j = (1 << 14) - yap; j > Cy; j -= Cy){ + rr += (R_VAL(pix) * Cy) >> 10; + gg += (G_VAL(pix) * Cy) >> 10; + bb += (B_VAL(pix) * Cy) >> 10; + pix += sow; + } + if(j > 0){ + rr += (R_VAL(pix) * j) >> 10; + gg += (G_VAL(pix) * j) >> 10; + bb += (B_VAL(pix) * j) >> 10; + } + r = r * INV_XAP; + g = g * INV_XAP; + b = b * INV_XAP; + r = (r + ((rr * XAP))) >> 12; + g = (g + ((gg * XAP))) >> 12; + b = (b + ((bb * XAP))) >> 12; + } + else{ + r >>= 4; + g >>= 4; + b >>= 4; + } + *dptr = qRgba(r, g, b, 0xff); + dptr++; + } + } + } + /* if we're scaling down horizontally */ + else if(isi->xup_yup == 2){ + /*\ 'Correct' version, with math units prepared for MMXification \*/ + int Cx, j; + unsigned int *pix; + int r, g, b, rr, gg, bb; + int xap; + + /* go through every scanline in the output buffer */ + for(y = 0; y < dh; y++){ + dptr = dest + dx + ((y + dy) * dow); + for(x = dxx; x < end; x++){ + Cx = XAP >> 16; + xap = XAP & 0xffff; + + pix = ypoints[dyy + y] + xpoints[x]; + r = (R_VAL(pix) * xap) >> 10; + g = (G_VAL(pix) * xap) >> 10; + b = (B_VAL(pix) * xap) >> 10; + pix++; + for(j = (1 << 14) - xap; j > Cx; j -= Cx){ + r += (R_VAL(pix) * Cx) >> 10; + g += (G_VAL(pix) * Cx) >> 10; + b += (B_VAL(pix) * Cx) >> 10; + pix++; + } + if(j > 0){ + r += (R_VAL(pix) * j) >> 10; + g += (G_VAL(pix) * j) >> 10; + b += (B_VAL(pix) * j) >> 10; + } + if(YAP > 0){ + pix = ypoints[dyy + y] + xpoints[x] + sow; + rr = (R_VAL(pix) * xap) >> 10; + gg = (G_VAL(pix) * xap) >> 10; + bb = (B_VAL(pix) * xap) >> 10; + pix++; + for(j = (1 << 14) - xap; j > Cx; j -= Cx){ + rr += (R_VAL(pix) * Cx) >> 10; + gg += (G_VAL(pix) * Cx) >> 10; + bb += (B_VAL(pix) * Cx) >> 10; + pix++; + } + if(j > 0){ + rr += (R_VAL(pix) * j) >> 10; + gg += (G_VAL(pix) * j) >> 10; + bb += (B_VAL(pix) * j) >> 10; + } + r = r * INV_YAP; + g = g * INV_YAP; + b = b * INV_YAP; + r = (r + ((rr * YAP))) >> 12; + g = (g + ((gg * YAP))) >> 12; + b = (b + ((bb * YAP))) >> 12; + } + else{ + r >>= 4; + g >>= 4; + b >>= 4; + } + *dptr = qRgba(r, g, b, 0xff); + dptr++; + } + } + } + /* fully optimized (i think) - onyl change of algorithm can help */ + /* if we're scaling down horizontally & vertically */ + else{ + /*\ 'Correct' version, with math units prepared for MMXification \*/ + int Cx, Cy, i, j; + unsigned int *pix; + int r, g, b, rx, gx, bx; + int xap, yap; + + for(y = 0; y < dh; y++){ + Cy = YAP >> 16; + yap = YAP & 0xffff; + + dptr = dest + dx + ((y + dy) * dow); + for(x = dxx; x < end; x++){ + Cx = XAP >> 16; + xap = XAP & 0xffff; + + sptr = ypoints[dyy + y] + xpoints[x]; + pix = sptr; + sptr += sow; + rx = (R_VAL(pix) * xap) >> 9; + gx = (G_VAL(pix) * xap) >> 9; + bx = (B_VAL(pix) * xap) >> 9; + pix++; + for(i = (1 << 14) - xap; i > Cx; i -= Cx){ + rx += (R_VAL(pix) * Cx) >> 9; + gx += (G_VAL(pix) * Cx) >> 9; + bx += (B_VAL(pix) * Cx) >> 9; + pix++; + } + if(i > 0){ + rx += (R_VAL(pix) * i) >> 9; + gx += (G_VAL(pix) * i) >> 9; + bx += (B_VAL(pix) * i) >> 9; + } + + r = (rx * yap) >> 14; + g = (gx * yap) >> 14; + b = (bx * yap) >> 14; + + for(j = (1 << 14) - yap; j > Cy; j -= Cy){ + pix = sptr; + sptr += sow; + rx = (R_VAL(pix) * xap) >> 9; + gx = (G_VAL(pix) * xap) >> 9; + bx = (B_VAL(pix) * xap) >> 9; + pix++; + for(i = (1 << 14) - xap; i > Cx; i -= Cx){ + rx += (R_VAL(pix) * Cx) >> 9; + gx += (G_VAL(pix) * Cx) >> 9; + bx += (B_VAL(pix) * Cx) >> 9; + pix++; + } + if(i > 0){ + rx += (R_VAL(pix) * i) >> 9; + gx += (G_VAL(pix) * i) >> 9; + bx += (B_VAL(pix) * i) >> 9; + } + + r += (rx * Cy) >> 14; + g += (gx * Cy) >> 14; + b += (bx * Cy) >> 14; + } + if(j > 0){ + pix = sptr; + sptr += sow; + rx = (R_VAL(pix) * xap) >> 9; + gx = (G_VAL(pix) * xap) >> 9; + bx = (B_VAL(pix) * xap) >> 9; + pix++; + for(i = (1 << 14) - xap; i > Cx; i -= Cx){ + rx += (R_VAL(pix) * Cx) >> 9; + gx += (G_VAL(pix) * Cx) >> 9; + bx += (B_VAL(pix) * Cx) >> 9; + pix++; + } + if(i > 0){ + rx += (R_VAL(pix) * i) >> 9; + gx += (G_VAL(pix) * i) >> 9; + bx += (B_VAL(pix) * i) >> 9; + } + + r += (rx * j) >> 14; + g += (gx * j) >> 14; + b += (bx * j) >> 14; + } + + R_VAL(dptr) = r >> 5; + G_VAL(dptr) = g >> 5; + B_VAL(dptr) = b >> 5; + dptr++; + } + } + } +} + +// Imlib2/Mosfet code end + + +// public functions : +// ------------------ + +// This function returns how many pixels around the zoomed area should be +// included in the image. This is used when doing incremental painting, because +// some smoothing algorithms use surrounding pixels and not including them +// could sometimes make the edges between incremental steps visible. +int extraScalePixels( SmoothAlgorithm alg, double zoom, double blur ) +{ + double filtersupport = 0; + Filter filter = NULL; + switch( alg ) { + case SMOOTH_NONE: + filter = NULL; + filtersupport = 0.0; + break; + case SMOOTH_FAST: + filter = Box; + filtersupport = 0.5; + break; + case SMOOTH_NORMAL: + filter = Triangle; + filtersupport = 1.0; + break; + case SMOOTH_BEST: +// filter = Mitchell; + filter = Bicubic; + filtersupport = 2.0; + break; + } + if( zoom == 1.0 || filtersupport == 0.0 ) return 0; + // Imlib2/Mosfet scale - I have really no idea how many pixels it needs + if( filter == Box && blur == 1.0 ) return int( 3 / zoom + 1 ); +// This is support size for ImageMagick's scaling. + double scale=blur*QMAX(1.0/zoom,1.0); + double support=scale* filtersupport; + if (support <= 0.5) support=0.5+0.000001; + return int( support + 1 ); +} + +QImage scale(const QImage& image, int width, int height, + SmoothAlgorithm alg, QImage::ScaleMode mode, double blur ) +{ + if( image.isNull()) return image.copy(); + + QSize newSize( image.size() ); + newSize.scale( QSize( width, height ), (QSize::ScaleMode)mode ); // ### remove cast in Qt 4.0 + newSize = newSize.expandedTo( QSize( 1, 1 )); // make sure it doesn't become null + + if ( newSize == image.size() ) return image.copy(); + + width = newSize.width(); + height = newSize.height(); + Filter filter = NULL; + fastfloat filtersupport; + + switch( alg ) { + case SMOOTH_NONE: + filter = NULL; + filtersupport = 0.0; + break; + case SMOOTH_FAST: + filter = Box; + filtersupport = 0.5; + break; + case SMOOTH_NORMAL: + default: + filter = Triangle; + filtersupport = 1.0; + break; + case SMOOTH_BEST: +// filter = Mitchell; + filter = Bicubic; + filtersupport = 2.0; + break; + } + + if( filter == Box && blur == 1.0 ) + return MImageScale::smoothScale( image, width, height ); + + if( filter == Box && width > image.width() && height > image.height() && blur == 1.0 ) { + filter = NULL; // Box doesn't really smooth when enlarging + } + + if( filter == NULL ) { + return SampleImage( image, width, height ); // doesn't need 32bit + } + + return ResizeImage( image.convertDepth( 32 ), width, height, filter, filtersupport, blur ); + // unlike Qt's smoothScale() this function introduces new colors to grayscale images ... oh well +} + + +} // namespace diff --git a/src/imageutils/testjpegcontent.cpp b/src/imageutils/testjpegcontent.cpp new file mode 100644 index 0000000..b533373 --- /dev/null +++ b/src/imageutils/testjpegcontent.cpp @@ -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. + +*/ +#include <iostream> + +// Qt +#include <qdir.h> +#include <qfile.h> +#include <qimage.h> +#include <qstring.h> + +// KDE +#include <kapplication.h> +#include <kaboutdata.h> +#include <kcmdlineargs.h> +#include <kdebug.h> +#include <kfilemetainfo.h> + +// Local +#include "imageutils/imageutils.h" +#include "imageutils/jpegcontent.h" + +using namespace std; + +const char* ORIENT6_FILE="orient6.jpg"; +const int ORIENT6_WIDTH=128; // This size is the size *after* orientation +const int ORIENT6_HEIGHT=256; // has been applied +const char* CUT_FILE="cut.jpg"; +const QString ORIENT6_COMMENT="a comment"; +const QString ORIENT1_VFLIP_FILE="test_orient1_vflip.jpg"; +const char* THUMBNAIL_FILE="test_thumbnail.jpg"; +const char* TMP_FILE="tmp.jpg"; + + +class TestEnvironment { +public: + TestEnvironment() { + bool result; + QFile in(ORIENT6_FILE); + result=in.open(IO_ReadOnly); + Q_ASSERT(result); + + QFileInfo info(in); + int size=info.size()/2; + + char* data=new char[size]; + int readSize=in.readBlock(data, size); + Q_ASSERT(size==readSize); + + QFile out(CUT_FILE); + result=out.open(IO_WriteOnly); + Q_ASSERT(result); + + int wroteSize=out.writeBlock(data, size); + Q_ASSERT(size==wroteSize); + delete []data; + } + + ~TestEnvironment() { + QDir::current().remove(CUT_FILE); + } +}; + + +typedef QMap<QString,QString> MetaInfoMap; + +MetaInfoMap getMetaInfo(const QString& path) { + KFileMetaInfo fmi(path); + QStringList list=fmi.supportedKeys(); + QStringList::ConstIterator it=list.begin(); + MetaInfoMap map; + + for ( ; it!=list.end(); ++it) { + KFileMetaInfoItem item=fmi.item(*it); + map[*it]=item.string(); + } + + return map; +} + +void compareMetaInfo(const QString& path1, const QString& path2, const QStringList& ignoredKeys) { + MetaInfoMap mim1=getMetaInfo(path1); + MetaInfoMap mim2=getMetaInfo(path2); + + Q_ASSERT(mim1.keys()==mim2.keys()); + QValueList<QString> keys=mim1.keys(); + QValueList<QString>::ConstIterator it=keys.begin(); + for ( ; it!=keys.end(); ++it) { + QString key=*it; + if (ignoredKeys.contains(key)) continue; + + if (mim1[key]!=mim2[key]) { + kdError() << "Meta info differs. Key:" << key << ", V1:" << mim1[key] << ", V2:" << mim2[key] << endl; + } + } +} + + +void testResetOrientation() { + ImageUtils::JPEGContent content; + bool result; + + // Test resetOrientation without transform + result=content.load(ORIENT6_FILE); + Q_ASSERT(result); + + content.resetOrientation(); + + result=content.save(TMP_FILE); + Q_ASSERT(result); + + result=content.load(TMP_FILE); + Q_ASSERT(result); + Q_ASSERT(content.orientation() == ImageUtils::NORMAL); + + // Test resetOrientation with transform + result=content.load(ORIENT6_FILE); + Q_ASSERT(result); + + content.resetOrientation(); + content.transform(ImageUtils::ROT_90); + + result=content.save(TMP_FILE); + Q_ASSERT(result); + + result=content.load(TMP_FILE); + Q_ASSERT(result); + Q_ASSERT(content.orientation() == ImageUtils::NORMAL); +} + + +/** + * This function tests JPEGContent::transform() by applying a ROT_90 + * transformation, saving, reloading and applying a ROT_270 to undo the ROT_90. + * Saving and reloading are necessary because lossless transformation only + * happens in JPEGContent::save() + */ +void testTransform() { + bool result; + QImage finalImage, expectedImage; + + ImageUtils::JPEGContent content; + result = content.load(ORIENT6_FILE); + Q_ASSERT(result); + + content.transform(ImageUtils::ROT_90); + result = content.save(TMP_FILE); + Q_ASSERT(result); + + result = content.load(TMP_FILE); + Q_ASSERT(result); + content.transform(ImageUtils::ROT_270); + result = content.save(TMP_FILE); + Q_ASSERT(result); + + result = finalImage.load(TMP_FILE); + Q_ASSERT(result); + + result = expectedImage.load(ORIENT6_FILE); + Q_ASSERT(result); + + Q_ASSERT(finalImage == expectedImage); +} + + +void testSetComment() { + QString comment = "test comment"; + ImageUtils::JPEGContent content; + bool result; + result = content.load(ORIENT6_FILE); + Q_ASSERT(result); + + content.setComment(comment); + Q_ASSERT(content.comment() == comment); + result = content.save(TMP_FILE); + Q_ASSERT(result); + + result = content.load(TMP_FILE); + Q_ASSERT(result); + Q_ASSERT(content.comment() == comment); +} + + +int main(int argc, char* argv[]) { + TestEnvironment testEnv; + bool result; + KAboutData aboutData("testjpegcontent", "testjpegcontent", "0"); + KCmdLineArgs::init( argc, argv, &aboutData ); + KApplication kapplication; + + // Reading info + ImageUtils::JPEGContent content; + result=content.load(ORIENT6_FILE); + Q_ASSERT(result); + Q_ASSERT(content.orientation() == 6); + Q_ASSERT(content.comment() == ORIENT6_COMMENT); + Q_ASSERT(content.size() == QSize(ORIENT6_WIDTH, ORIENT6_HEIGHT)); + + // thumbnail() + QImage thumbnail=content.thumbnail(); + result=thumbnail.save(THUMBNAIL_FILE, "JPEG"); + Q_ASSERT(result); + + testResetOrientation(); + testTransform(); + testSetComment(); + + // Test that rotating a file a lot of times does not cause findJxform() to fail + result = content.load(ORIENT6_FILE); + Q_ASSERT(result); + + // 12*4 + 1 is the same as 1, since rotating four times brings you back + for(int loop=0; loop< 12*4 + 1; ++loop) { + content.transform(ImageUtils::ROT_90); + } + result = content.save(TMP_FILE); + Q_ASSERT(result); + + result = content.load(TMP_FILE); + Q_ASSERT(result); + + Q_ASSERT(content.size() == QSize(ORIENT6_HEIGHT, ORIENT6_WIDTH)); + + + // Check the other meta info are still here + QStringList ignoredKeys; + ignoredKeys << "Orientation" << "Comment"; + compareMetaInfo(ORIENT6_FILE, ORIENT1_VFLIP_FILE, ignoredKeys); + + // Test that loading and manipulating a truncated file does not crash + result=content.load(CUT_FILE); + Q_ASSERT(result); + Q_ASSERT(content.orientation() == 6); + Q_ASSERT(content.comment() == ORIENT6_COMMENT); + content.transform(ImageUtils::VFLIP); + kdWarning() << "# Next function should output errors about incomplete image" << endl; + content.save(TMP_FILE); + kdWarning() << "#" << endl; +} diff --git a/src/imageutils/transupp.c b/src/imageutils/transupp.c new file mode 100644 index 0000000..e5ec564 --- /dev/null +++ b/src/imageutils/transupp.c @@ -0,0 +1,928 @@ +/* + * transupp.c + * + * Copyright (C) 1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains image transformation routines and other utility code + * used by the jpegtran sample application. These are NOT part of the core + * JPEG library. But we keep these routines separate from jpegtran.c to + * ease the task of maintaining jpegtran-like programs that have other user + * interfaces. + */ + +/* Although this file really shouldn't have access to the library internals, + * it's helpful to let it call jround_up() and jcopy_block_row(). + */ +#define JPEG_INTERNALS + +#include "jinclude.h" +#include "jpeglib.h" +#include "transupp.h" /* My own external interface */ + + +#if TRANSFORMS_SUPPORTED + +/* + * Lossless image transformation routines. These routines work on DCT + * coefficient arrays and thus do not require any lossy decompression + * or recompression of the image. + * Thanks to Guido Vollbeding for the initial design and code of this feature. + * + * Horizontal flipping is done in-place, using a single top-to-bottom + * pass through the virtual source array. It will thus be much the + * fastest option for images larger than main memory. + * + * The other routines require a set of destination virtual arrays, so they + * need twice as much memory as jpegtran normally does. The destination + * arrays are always written in normal scan order (top to bottom) because + * the virtual array manager expects this. The source arrays will be scanned + * in the corresponding order, which means multiple passes through the source + * arrays for most of the transforms. That could result in much thrashing + * if the image is larger than main memory. + * + * Some notes about the operating environment of the individual transform + * routines: + * 1. Both the source and destination virtual arrays are allocated from the + * source JPEG object, and therefore should be manipulated by calling the + * source's memory manager. + * 2. The destination's component count should be used. It may be smaller + * than the source's when forcing to grayscale. + * 3. Likewise the destination's sampling factors should be used. When + * forcing to grayscale the destination's sampling factors will be all 1, + * and we may as well take that as the effective iMCU size. + * 4. When "trim" is in effect, the destination's dimensions will be the + * trimmed values but the source's will be untrimmed. + * 5. All the routines assume that the source and destination buffers are + * padded out to a full iMCU boundary. This is true, although for the + * source buffer it is an undocumented property of jdcoefct.c. + * Notes 2,3,4 boil down to this: generally we should use the destination's + * dimensions and ignore the source's. + */ + + +LOCAL(void) +do_flip_h (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays) +/* Horizontal flip; done in-place, so no separate dest array is required */ +{ + JDIMENSION MCU_cols, comp_width, blk_x, blk_y; + int ci, k, offset_y; + JBLOCKARRAY buffer; + JCOEFPTR ptr1, ptr2; + JCOEF temp1, temp2; + jpeg_component_info *compptr; + + /* Horizontal mirroring of DCT blocks is accomplished by swapping + * pairs of blocks in-place. Within a DCT block, we perform horizontal + * mirroring by changing the signs of odd-numbered columns. + * Partial iMCUs at the right edge are left untouched. + */ + MCU_cols = dstinfo->image_width / (dstinfo->max_h_samp_factor * DCTSIZE); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_width = MCU_cols * compptr->h_samp_factor; + for (blk_y = 0; blk_y < compptr->height_in_blocks; + blk_y += compptr->v_samp_factor) { + buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + for (blk_x = 0; blk_x * 2 < comp_width; blk_x++) { + ptr1 = buffer[offset_y][blk_x]; + ptr2 = buffer[offset_y][comp_width - blk_x - 1]; + /* this unrolled loop doesn't need to know which row it's on... */ + for (k = 0; k < DCTSIZE2; k += 2) { + temp1 = *ptr1; /* swap even column */ + temp2 = *ptr2; + *ptr1++ = temp2; + *ptr2++ = temp1; + temp1 = *ptr1; /* swap odd column with sign change */ + temp2 = *ptr2; + *ptr1++ = -temp2; + *ptr2++ = -temp1; + } + } + } + } + } +} + + +LOCAL(void) +do_flip_v (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* Vertical flip */ +{ + JDIMENSION MCU_rows, comp_height, dst_blk_x, dst_blk_y; + int ci, i, j, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JBLOCKROW src_row_ptr, dst_row_ptr; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + /* We output into a separate array because we can't touch different + * rows of the source virtual array simultaneously. Otherwise, this + * is a pretty straightforward analog of horizontal flip. + * Within a DCT block, vertical mirroring is done by changing the signs + * of odd-numbered rows. + * Partial iMCUs at the bottom edge are copied verbatim. + */ + MCU_rows = dstinfo->image_height / (dstinfo->max_v_samp_factor * DCTSIZE); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_height = MCU_rows * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + if (dst_blk_y < comp_height) { + /* Row is within the mirrorable area. */ + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + comp_height - dst_blk_y - (JDIMENSION) compptr->v_samp_factor, + (JDIMENSION) compptr->v_samp_factor, FALSE); + } else { + /* Bottom-edge blocks will be copied verbatim. */ + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, FALSE); + } + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + if (dst_blk_y < comp_height) { + /* Row is within the mirrorable area. */ + dst_row_ptr = dst_buffer[offset_y]; + src_row_ptr = src_buffer[compptr->v_samp_factor - offset_y - 1]; + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; + dst_blk_x++) { + dst_ptr = dst_row_ptr[dst_blk_x]; + src_ptr = src_row_ptr[dst_blk_x]; + for (i = 0; i < DCTSIZE; i += 2) { + /* copy even row */ + for (j = 0; j < DCTSIZE; j++) + *dst_ptr++ = *src_ptr++; + /* copy odd row with sign change */ + for (j = 0; j < DCTSIZE; j++) + *dst_ptr++ = - *src_ptr++; + } + } + } else { + /* Just copy row verbatim. */ + jcopy_block_row(src_buffer[offset_y], dst_buffer[offset_y], + compptr->width_in_blocks); + } + } + } + } +} + + +LOCAL(void) +do_transpose (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* Transpose source into destination */ +{ + JDIMENSION dst_blk_x, dst_blk_y; + int ci, i, j, offset_x, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + /* Transposing pixels within a block just requires transposing the + * DCT coefficients. + * Partial iMCUs at the edges require no special treatment; we simply + * process all the available DCT blocks for every component. + */ + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; + dst_blk_x += compptr->h_samp_factor) { + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], dst_blk_x, + (JDIMENSION) compptr->h_samp_factor, FALSE); + for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) { + src_ptr = src_buffer[offset_x][dst_blk_y + offset_y]; + dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x]; + for (i = 0; i < DCTSIZE; i++) + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + } + } + } + } + } +} + + +LOCAL(void) +do_rot_90 (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* 90 degree rotation is equivalent to + * 1. Transposing the image; + * 2. Horizontal mirroring. + * These two steps are merged into a single processing routine. + */ +{ + JDIMENSION MCU_cols, comp_width, dst_blk_x, dst_blk_y; + int ci, i, j, offset_x, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + /* Because of the horizontal mirror step, we can't process partial iMCUs + * at the (output) right edge properly. They just get transposed and + * not mirrored. + */ + MCU_cols = dstinfo->image_width / (dstinfo->max_h_samp_factor * DCTSIZE); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_width = MCU_cols * compptr->h_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; + dst_blk_x += compptr->h_samp_factor) { + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], dst_blk_x, + (JDIMENSION) compptr->h_samp_factor, FALSE); + for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) { + src_ptr = src_buffer[offset_x][dst_blk_y + offset_y]; + if (dst_blk_x < comp_width) { + /* Block is within the mirrorable area. */ + dst_ptr = dst_buffer[offset_y] + [comp_width - dst_blk_x - offset_x - 1]; + for (i = 0; i < DCTSIZE; i++) { + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + i++; + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j]; + } + } else { + /* Edge blocks are transposed but not mirrored. */ + dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x]; + for (i = 0; i < DCTSIZE; i++) + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + } + } + } + } + } + } +} + + +LOCAL(void) +do_rot_270 (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* 270 degree rotation is equivalent to + * 1. Horizontal mirroring; + * 2. Transposing the image. + * These two steps are merged into a single processing routine. + */ +{ + JDIMENSION MCU_rows, comp_height, dst_blk_x, dst_blk_y; + int ci, i, j, offset_x, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + /* Because of the horizontal mirror step, we can't process partial iMCUs + * at the (output) bottom edge properly. They just get transposed and + * not mirrored. + */ + MCU_rows = dstinfo->image_height / (dstinfo->max_v_samp_factor * DCTSIZE); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_height = MCU_rows * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; + dst_blk_x += compptr->h_samp_factor) { + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], dst_blk_x, + (JDIMENSION) compptr->h_samp_factor, FALSE); + for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) { + dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x]; + if (dst_blk_y < comp_height) { + /* Block is within the mirrorable area. */ + src_ptr = src_buffer[offset_x] + [comp_height - dst_blk_y - offset_y - 1]; + for (i = 0; i < DCTSIZE; i++) { + for (j = 0; j < DCTSIZE; j++) { + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + j++; + dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j]; + } + } + } else { + /* Edge blocks are transposed but not mirrored. */ + src_ptr = src_buffer[offset_x][dst_blk_y + offset_y]; + for (i = 0; i < DCTSIZE; i++) + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + } + } + } + } + } + } +} + + +LOCAL(void) +do_rot_180 (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* 180 degree rotation is equivalent to + * 1. Vertical mirroring; + * 2. Horizontal mirroring. + * These two steps are merged into a single processing routine. + */ +{ + JDIMENSION MCU_cols, MCU_rows, comp_width, comp_height, dst_blk_x, dst_blk_y; + int ci, i, j, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JBLOCKROW src_row_ptr, dst_row_ptr; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + MCU_cols = dstinfo->image_width / (dstinfo->max_h_samp_factor * DCTSIZE); + MCU_rows = dstinfo->image_height / (dstinfo->max_v_samp_factor * DCTSIZE); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_width = MCU_cols * compptr->h_samp_factor; + comp_height = MCU_rows * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + if (dst_blk_y < comp_height) { + /* Row is within the vertically mirrorable area. */ + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + comp_height - dst_blk_y - (JDIMENSION) compptr->v_samp_factor, + (JDIMENSION) compptr->v_samp_factor, FALSE); + } else { + /* Bottom-edge rows are only mirrored horizontally. */ + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, FALSE); + } + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + if (dst_blk_y < comp_height) { + /* Row is within the mirrorable area. */ + dst_row_ptr = dst_buffer[offset_y]; + src_row_ptr = src_buffer[compptr->v_samp_factor - offset_y - 1]; + /* Process the blocks that can be mirrored both ways. */ + for (dst_blk_x = 0; dst_blk_x < comp_width; dst_blk_x++) { + dst_ptr = dst_row_ptr[dst_blk_x]; + src_ptr = src_row_ptr[comp_width - dst_blk_x - 1]; + for (i = 0; i < DCTSIZE; i += 2) { + /* For even row, negate every odd column. */ + for (j = 0; j < DCTSIZE; j += 2) { + *dst_ptr++ = *src_ptr++; + *dst_ptr++ = - *src_ptr++; + } + /* For odd row, negate every even column. */ + for (j = 0; j < DCTSIZE; j += 2) { + *dst_ptr++ = - *src_ptr++; + *dst_ptr++ = *src_ptr++; + } + } + } + /* Any remaining right-edge blocks are only mirrored vertically. */ + for (; dst_blk_x < compptr->width_in_blocks; dst_blk_x++) { + dst_ptr = dst_row_ptr[dst_blk_x]; + src_ptr = src_row_ptr[dst_blk_x]; + for (i = 0; i < DCTSIZE; i += 2) { + for (j = 0; j < DCTSIZE; j++) + *dst_ptr++ = *src_ptr++; + for (j = 0; j < DCTSIZE; j++) + *dst_ptr++ = - *src_ptr++; + } + } + } else { + /* Remaining rows are just mirrored horizontally. */ + dst_row_ptr = dst_buffer[offset_y]; + src_row_ptr = src_buffer[offset_y]; + /* Process the blocks that can be mirrored. */ + for (dst_blk_x = 0; dst_blk_x < comp_width; dst_blk_x++) { + dst_ptr = dst_row_ptr[dst_blk_x]; + src_ptr = src_row_ptr[comp_width - dst_blk_x - 1]; + for (i = 0; i < DCTSIZE2; i += 2) { + *dst_ptr++ = *src_ptr++; + *dst_ptr++ = - *src_ptr++; + } + } + /* Any remaining right-edge blocks are only copied. */ + for (; dst_blk_x < compptr->width_in_blocks; dst_blk_x++) { + dst_ptr = dst_row_ptr[dst_blk_x]; + src_ptr = src_row_ptr[dst_blk_x]; + for (i = 0; i < DCTSIZE2; i++) + *dst_ptr++ = *src_ptr++; + } + } + } + } + } +} + + +LOCAL(void) +do_transverse (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* Transverse transpose is equivalent to + * 1. 180 degree rotation; + * 2. Transposition; + * or + * 1. Horizontal mirroring; + * 2. Transposition; + * 3. Horizontal mirroring. + * These steps are merged into a single processing routine. + */ +{ + JDIMENSION MCU_cols, MCU_rows, comp_width, comp_height, dst_blk_x, dst_blk_y; + int ci, i, j, offset_x, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + MCU_cols = dstinfo->image_width / (dstinfo->max_h_samp_factor * DCTSIZE); + MCU_rows = dstinfo->image_height / (dstinfo->max_v_samp_factor * DCTSIZE); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_width = MCU_cols * compptr->h_samp_factor; + comp_height = MCU_rows * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; + dst_blk_x += compptr->h_samp_factor) { + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], dst_blk_x, + (JDIMENSION) compptr->h_samp_factor, FALSE); + for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) { + if (dst_blk_y < comp_height) { + src_ptr = src_buffer[offset_x] + [comp_height - dst_blk_y - offset_y - 1]; + if (dst_blk_x < comp_width) { + /* Block is within the mirrorable area. */ + dst_ptr = dst_buffer[offset_y] + [comp_width - dst_blk_x - offset_x - 1]; + for (i = 0; i < DCTSIZE; i++) { + for (j = 0; j < DCTSIZE; j++) { + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + j++; + dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j]; + } + i++; + for (j = 0; j < DCTSIZE; j++) { + dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j]; + j++; + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + } + } + } else { + /* Right-edge blocks are mirrored in y only */ + dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x]; + for (i = 0; i < DCTSIZE; i++) { + for (j = 0; j < DCTSIZE; j++) { + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + j++; + dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j]; + } + } + } + } else { + src_ptr = src_buffer[offset_x][dst_blk_y + offset_y]; + if (dst_blk_x < comp_width) { + /* Bottom-edge blocks are mirrored in x only */ + dst_ptr = dst_buffer[offset_y] + [comp_width - dst_blk_x - offset_x - 1]; + for (i = 0; i < DCTSIZE; i++) { + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + i++; + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j]; + } + } else { + /* At lower right corner, just transpose, no mirroring */ + dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x]; + for (i = 0; i < DCTSIZE; i++) + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + } + } + } + } + } + } + } +} + + +/* Request any required workspace. + * + * We allocate the workspace virtual arrays from the source decompression + * object, so that all the arrays (both the original data and the workspace) + * will be taken into account while making memory management decisions. + * Hence, this routine must be called after jpeg_read_header (which reads + * the image dimensions) and before jpeg_read_coefficients (which realizes + * the source's virtual arrays). + */ + +GLOBAL(void) +jtransform_request_workspace (j_decompress_ptr srcinfo, + jpeg_transform_info *info) +{ + jvirt_barray_ptr *coef_arrays = NULL; + jpeg_component_info *compptr; + int ci; + + if (info->force_grayscale && + srcinfo->jpeg_color_space == JCS_YCbCr && + srcinfo->num_components == 3) { + /* We'll only process the first component */ + info->num_components = 1; + } else { + /* Process all the components */ + info->num_components = srcinfo->num_components; + } + + switch (info->transform) { + case JXFORM_NONE: + case JXFORM_FLIP_H: + /* Don't need a workspace array */ + break; + case JXFORM_FLIP_V: + case JXFORM_ROT_180: + /* Need workspace arrays having same dimensions as source image. + * Note that we allocate arrays padded out to the next iMCU boundary, + * so that transform routines need not worry about missing edge blocks. + */ + coef_arrays = (jvirt_barray_ptr *) + (*srcinfo->mem->alloc_small) ((j_common_ptr) srcinfo, JPOOL_IMAGE, + SIZEOF(jvirt_barray_ptr) * info->num_components); + for (ci = 0; ci < info->num_components; ci++) { + compptr = srcinfo->comp_info + ci; + coef_arrays[ci] = (*srcinfo->mem->request_virt_barray) + ((j_common_ptr) srcinfo, JPOOL_IMAGE, FALSE, + (JDIMENSION) jround_up((long) compptr->width_in_blocks, + (long) compptr->h_samp_factor), + (JDIMENSION) jround_up((long) compptr->height_in_blocks, + (long) compptr->v_samp_factor), + (JDIMENSION) compptr->v_samp_factor); + } + break; + case JXFORM_TRANSPOSE: + case JXFORM_TRANSVERSE: + case JXFORM_ROT_90: + case JXFORM_ROT_270: + /* Need workspace arrays having transposed dimensions. + * Note that we allocate arrays padded out to the next iMCU boundary, + * so that transform routines need not worry about missing edge blocks. + */ + coef_arrays = (jvirt_barray_ptr *) + (*srcinfo->mem->alloc_small) ((j_common_ptr) srcinfo, JPOOL_IMAGE, + SIZEOF(jvirt_barray_ptr) * info->num_components); + for (ci = 0; ci < info->num_components; ci++) { + compptr = srcinfo->comp_info + ci; + coef_arrays[ci] = (*srcinfo->mem->request_virt_barray) + ((j_common_ptr) srcinfo, JPOOL_IMAGE, FALSE, + (JDIMENSION) jround_up((long) compptr->height_in_blocks, + (long) compptr->v_samp_factor), + (JDIMENSION) jround_up((long) compptr->width_in_blocks, + (long) compptr->h_samp_factor), + (JDIMENSION) compptr->h_samp_factor); + } + break; + } + info->workspace_coef_arrays = coef_arrays; +} + + +/* Transpose destination image parameters */ + +LOCAL(void) +transpose_critical_parameters (j_compress_ptr dstinfo) +{ + int tblno, i, j, ci, itemp; + jpeg_component_info *compptr; + JQUANT_TBL *qtblptr; + JDIMENSION dtemp; + UINT16 qtemp; + + /* Transpose basic image dimensions */ + dtemp = dstinfo->image_width; + dstinfo->image_width = dstinfo->image_height; + dstinfo->image_height = dtemp; + + /* Transpose sampling factors */ + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + itemp = compptr->h_samp_factor; + compptr->h_samp_factor = compptr->v_samp_factor; + compptr->v_samp_factor = itemp; + } + + /* Transpose quantization tables */ + for (tblno = 0; tblno < NUM_QUANT_TBLS; tblno++) { + qtblptr = dstinfo->quant_tbl_ptrs[tblno]; + if (qtblptr != NULL) { + for (i = 0; i < DCTSIZE; i++) { + for (j = 0; j < i; j++) { + qtemp = qtblptr->quantval[i*DCTSIZE+j]; + qtblptr->quantval[i*DCTSIZE+j] = qtblptr->quantval[j*DCTSIZE+i]; + qtblptr->quantval[j*DCTSIZE+i] = qtemp; + } + } + } + } +} + + +/* Trim off any partial iMCUs on the indicated destination edge */ + +LOCAL(void) +trim_right_edge (j_compress_ptr dstinfo) +{ + int ci, max_h_samp_factor; + JDIMENSION MCU_cols; + + /* We have to compute max_h_samp_factor ourselves, + * because it hasn't been set yet in the destination + * (and we don't want to use the source's value). + */ + max_h_samp_factor = 1; + for (ci = 0; ci < dstinfo->num_components; ci++) { + int h_samp_factor = dstinfo->comp_info[ci].h_samp_factor; + max_h_samp_factor = MAX(max_h_samp_factor, h_samp_factor); + } + MCU_cols = dstinfo->image_width / (max_h_samp_factor * DCTSIZE); + if (MCU_cols > 0) /* can't trim to 0 pixels */ + dstinfo->image_width = MCU_cols * (max_h_samp_factor * DCTSIZE); +} + +LOCAL(void) +trim_bottom_edge (j_compress_ptr dstinfo) +{ + int ci, max_v_samp_factor; + JDIMENSION MCU_rows; + + /* We have to compute max_v_samp_factor ourselves, + * because it hasn't been set yet in the destination + * (and we don't want to use the source's value). + */ + max_v_samp_factor = 1; + for (ci = 0; ci < dstinfo->num_components; ci++) { + int v_samp_factor = dstinfo->comp_info[ci].v_samp_factor; + max_v_samp_factor = MAX(max_v_samp_factor, v_samp_factor); + } + MCU_rows = dstinfo->image_height / (max_v_samp_factor * DCTSIZE); + if (MCU_rows > 0) /* can't trim to 0 pixels */ + dstinfo->image_height = MCU_rows * (max_v_samp_factor * DCTSIZE); +} + + +/* Adjust output image parameters as needed. + * + * This must be called after jpeg_copy_critical_parameters() + * and before jpeg_write_coefficients(). + * + * The return value is the set of virtual coefficient arrays to be written + * (either the ones allocated by jtransform_request_workspace, or the + * original source data arrays). The caller will need to pass this value + * to jpeg_write_coefficients(). + */ + +GLOBAL(jvirt_barray_ptr *) +jtransform_adjust_parameters (j_decompress_ptr srcinfo, + j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jpeg_transform_info *info) +{ + /* If force-to-grayscale is requested, adjust destination parameters */ + if (info->force_grayscale) { + /* We use jpeg_set_colorspace to make sure subsidiary settings get fixed + * properly. Among other things, the target h_samp_factor & v_samp_factor + * will get set to 1, which typically won't match the source. + * In fact we do this even if the source is already grayscale; that + * provides an easy way of coercing a grayscale JPEG with funny sampling + * factors to the customary 1,1. (Some decoders fail on other factors.) + */ + if ((dstinfo->jpeg_color_space == JCS_YCbCr && + dstinfo->num_components == 3) || + (dstinfo->jpeg_color_space == JCS_GRAYSCALE && + dstinfo->num_components == 1)) { + /* We have to preserve the source's quantization table number. */ + int sv_quant_tbl_no = dstinfo->comp_info[0].quant_tbl_no; + jpeg_set_colorspace(dstinfo, JCS_GRAYSCALE); + dstinfo->comp_info[0].quant_tbl_no = sv_quant_tbl_no; + } else { + /* Sorry, can't do it */ + ERREXIT(dstinfo, JERR_CONVERSION_NOTIMPL); + } + } + + /* Correct the destination's image dimensions etc if necessary */ + switch (info->transform) { + case JXFORM_NONE: + /* Nothing to do */ + break; + case JXFORM_FLIP_H: + if (info->trim) + trim_right_edge(dstinfo); + break; + case JXFORM_FLIP_V: + if (info->trim) + trim_bottom_edge(dstinfo); + break; + case JXFORM_TRANSPOSE: + transpose_critical_parameters(dstinfo); + /* transpose does NOT have to trim anything */ + break; + case JXFORM_TRANSVERSE: + transpose_critical_parameters(dstinfo); + if (info->trim) { + trim_right_edge(dstinfo); + trim_bottom_edge(dstinfo); + } + break; + case JXFORM_ROT_90: + transpose_critical_parameters(dstinfo); + if (info->trim) + trim_right_edge(dstinfo); + break; + case JXFORM_ROT_180: + if (info->trim) { + trim_right_edge(dstinfo); + trim_bottom_edge(dstinfo); + } + break; + case JXFORM_ROT_270: + transpose_critical_parameters(dstinfo); + if (info->trim) + trim_bottom_edge(dstinfo); + break; + } + + /* Return the appropriate output data set */ + if (info->workspace_coef_arrays != NULL) + return info->workspace_coef_arrays; + return src_coef_arrays; +} + + +/* Execute the actual transformation, if any. + * + * This must be called *after* jpeg_write_coefficients, because it depends + * on jpeg_write_coefficients to have computed subsidiary values such as + * the per-component width and height fields in the destination object. + * + * Note that some transformations will modify the source data arrays! + */ + +GLOBAL(void) +jtransform_execute_transformation (j_decompress_ptr srcinfo, + j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jpeg_transform_info *info) +{ + jvirt_barray_ptr *dst_coef_arrays = info->workspace_coef_arrays; + + switch (info->transform) { + case JXFORM_NONE: + break; + case JXFORM_FLIP_H: + do_flip_h(srcinfo, dstinfo, src_coef_arrays); + break; + case JXFORM_FLIP_V: + do_flip_v(srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays); + break; + case JXFORM_TRANSPOSE: + do_transpose(srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays); + break; + case JXFORM_TRANSVERSE: + do_transverse(srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays); + break; + case JXFORM_ROT_90: + do_rot_90(srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays); + break; + case JXFORM_ROT_180: + do_rot_180(srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays); + break; + case JXFORM_ROT_270: + do_rot_270(srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays); + break; + } +} + +#endif /* TRANSFORMS_SUPPORTED */ + + +/* Setup decompression object to save desired markers in memory. + * This must be called before jpeg_read_header() to have the desired effect. + */ + +GLOBAL(void) +jcopy_markers_setup (j_decompress_ptr srcinfo, JCOPY_OPTION option) +{ +#ifdef SAVE_MARKERS_SUPPORTED + int m; + + /* Save comments except under NONE option */ + if (option != JCOPYOPT_NONE) { + jpeg_save_markers(srcinfo, JPEG_COM, 0xFFFF); + } + /* Save all types of APPn markers iff ALL option */ + if (option == JCOPYOPT_ALL) { + for (m = 0; m < 16; m++) + jpeg_save_markers(srcinfo, JPEG_APP0 + m, 0xFFFF); + } +#endif /* SAVE_MARKERS_SUPPORTED */ +} + +/* Copy markers saved in the given source object to the destination object. + * This should be called just after jpeg_start_compress() or + * jpeg_write_coefficients(). + * Note that those routines will have written the SOI, and also the + * JFIF APP0 or Adobe APP14 markers if selected. + */ + +GLOBAL(void) +jcopy_markers_execute (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JCOPY_OPTION option) +{ + jpeg_saved_marker_ptr marker; + + /* In the current implementation, we don't actually need to examine the + * option flag here; we just copy everything that got saved. + * But to avoid confusion, we do not output JFIF and Adobe APP14 markers + * if the encoder library already wrote one. + */ + for (marker = srcinfo->marker_list; marker != NULL; marker = marker->next) { + if (dstinfo->write_JFIF_header && + marker->marker == JPEG_APP0 && + marker->data_length >= 5 && + GETJOCTET(marker->data[0]) == 0x4A && + GETJOCTET(marker->data[1]) == 0x46 && + GETJOCTET(marker->data[2]) == 0x49 && + GETJOCTET(marker->data[3]) == 0x46 && + GETJOCTET(marker->data[4]) == 0) + continue; /* reject duplicate JFIF */ + if (dstinfo->write_Adobe_marker && + marker->marker == JPEG_APP0+14 && + marker->data_length >= 5 && + GETJOCTET(marker->data[0]) == 0x41 && + GETJOCTET(marker->data[1]) == 0x64 && + GETJOCTET(marker->data[2]) == 0x6F && + GETJOCTET(marker->data[3]) == 0x62 && + GETJOCTET(marker->data[4]) == 0x65) + continue; /* reject duplicate Adobe */ +#ifdef NEED_FAR_POINTERS + /* We could use jpeg_write_marker if the data weren't FAR... */ + { + unsigned int i; + jpeg_write_m_header(dstinfo, marker->marker, marker->data_length); + for (i = 0; i < marker->data_length; i++) + jpeg_write_m_byte(dstinfo, marker->data[i]); + } +#else + jpeg_write_marker(dstinfo, marker->marker, + marker->data, marker->data_length); +#endif + } +} diff --git a/src/imageutils/transupp.h b/src/imageutils/transupp.h new file mode 100644 index 0000000..c4292b5 --- /dev/null +++ b/src/imageutils/transupp.h @@ -0,0 +1,141 @@ +/* + * transupp.h + * + * Copyright (C) 1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains declarations for image transformation routines and + * other utility code used by the jpegtran sample application. These are + * NOT part of the core JPEG library. But we keep these routines separate + * from jpegtran.c to ease the task of maintaining jpegtran-like programs + * that have other user interfaces. + * + * NOTE: all the routines declared here have very specific requirements + * about when they are to be executed during the reading and writing of the + * source and destination files. See the comments in transupp.c, or see + * jpegtran.c for an example of correct usage. + */ + +#ifndef TRANSUPP_H +#define TRANSUPP_H + +/* If you happen not to want the image transform support, disable it here */ +#ifndef TRANSFORMS_SUPPORTED +#define TRANSFORMS_SUPPORTED 1 /* 0 disables transform code */ +#endif + +/* Short forms of external names for systems with brain-damaged linkers. */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jtransform_request_workspace jTrRequest +#define jtransform_adjust_parameters jTrAdjust +#define jtransform_execute_transformation jTrExec +#define jcopy_markers_setup jCMrkSetup +#define jcopy_markers_execute jCMrkExec +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + + +/* + * Codes for supported types of image transformations. + */ + +typedef enum { + JXFORM_NONE, /* no transformation */ + JXFORM_FLIP_H, /* horizontal flip */ + JXFORM_FLIP_V, /* vertical flip */ + JXFORM_TRANSPOSE, /* transpose across UL-to-LR axis */ + JXFORM_TRANSVERSE, /* transpose across UR-to-LL axis */ + JXFORM_ROT_90, /* 90-degree clockwise rotation */ + JXFORM_ROT_180, /* 180-degree rotation */ + JXFORM_ROT_270 /* 270-degree clockwise (or 90 ccw) */ +} JXFORM_CODE; + +/* + * Although rotating and flipping data expressed as DCT coefficients is not + * hard, there is an asymmetry in the JPEG format specification for images + * whose dimensions aren't multiples of the iMCU size. The right and bottom + * image edges are padded out to the next iMCU boundary with junk data; but + * no padding is possible at the top and left edges. If we were to flip + * the whole image including the pad data, then pad garbage would become + * visible at the top and/or left, and real pixels would disappear into the + * pad margins --- perhaps permanently, since encoders & decoders may not + * bother to preserve DCT blocks that appear to be completely outside the + * nominal image area. So, we have to exclude any partial iMCUs from the + * basic transformation. + * + * Transpose is the only transformation that can handle partial iMCUs at the + * right and bottom edges completely cleanly. flip_h can flip partial iMCUs + * at the bottom, but leaves any partial iMCUs at the right edge untouched. + * Similarly flip_v leaves any partial iMCUs at the bottom edge untouched. + * The other transforms are defined as combinations of these basic transforms + * and process edge blocks in a way that preserves the equivalence. + * + * The "trim" option causes untransformable partial iMCUs to be dropped; + * this is not strictly lossless, but it usually gives the best-looking + * result for odd-size images. Note that when this option is active, + * the expected mathematical equivalences between the transforms may not hold. + * (For example, -rot 270 -trim trims only the bottom edge, but -rot 90 -trim + * followed by -rot 180 -trim trims both edges.) + * + * We also offer a "force to grayscale" option, which simply discards the + * chrominance channels of a YCbCr image. This is lossless in the sense that + * the luminance channel is preserved exactly. It's not the same kind of + * thing as the rotate/flip transformations, but it's convenient to handle it + * as part of this package, mainly because the transformation routines have to + * be aware of the option to know how many components to work on. + */ + +typedef struct { + /* Options: set by caller */ + JXFORM_CODE transform; /* image transform operator */ + boolean trim; /* if TRUE, trim partial MCUs as needed */ + boolean force_grayscale; /* if TRUE, convert color image to grayscale */ + + /* Internal workspace: caller should not touch these */ + int num_components; /* # of components in workspace */ + jvirt_barray_ptr * workspace_coef_arrays; /* workspace for transformations */ +} jpeg_transform_info; + + +#if TRANSFORMS_SUPPORTED + +/* Request any required workspace */ +EXTERN(void) jtransform_request_workspace + JPP((j_decompress_ptr srcinfo, jpeg_transform_info *info)); +/* Adjust output image parameters */ +EXTERN(jvirt_barray_ptr *) jtransform_adjust_parameters + JPP((j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jpeg_transform_info *info)); +/* Execute the actual transformation, if any */ +EXTERN(void) jtransform_execute_transformation + JPP((j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jpeg_transform_info *info)); + +#endif /* TRANSFORMS_SUPPORTED */ + + +/* + * Support for copying optional markers from source to destination file. + */ + +typedef enum { + JCOPYOPT_NONE, /* copy no optional markers */ + JCOPYOPT_COMMENTS, /* copy only comment (COM) markers */ + JCOPYOPT_ALL /* copy all optional markers */ +} JCOPY_OPTION; + +#define JCOPYOPT_DEFAULT JCOPYOPT_COMMENTS /* recommended default */ + +/* Setup decompression object to save desired markers in memory */ +EXTERN(void) jcopy_markers_setup + JPP((j_decompress_ptr srcinfo, JCOPY_OPTION option)); +/* Copy markers saved in the given source object to the destination object */ +EXTERN(void) jcopy_markers_execute + JPP((j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JCOPY_OPTION option)); + +#endif + diff --git a/src/pics/Makefile.am b/src/pics/Makefile.am new file mode 100644 index 0000000..e52ab4c --- /dev/null +++ b/src/pics/Makefile.am @@ -0,0 +1 @@ +SUBDIRS=action app cursor thumbnail diff --git a/src/pics/action/Makefile.am b/src/pics/action/Makefile.am new file mode 100644 index 0000000..fe4963d --- /dev/null +++ b/src/pics/action/Makefile.am @@ -0,0 +1,2 @@ +icondir = $(kde_datadir)/gwenview/icons +icon_ICON = AUTO diff --git a/src/pics/action/hi16-action-dnd1.png b/src/pics/action/hi16-action-dnd1.png Binary files differnew file mode 100644 index 0000000..60f9594 --- /dev/null +++ b/src/pics/action/hi16-action-dnd1.png diff --git a/src/pics/action/hi16-action-dnd2.png b/src/pics/action/hi16-action-dnd2.png Binary files differnew file mode 100644 index 0000000..fc70f77 --- /dev/null +++ b/src/pics/action/hi16-action-dnd2.png diff --git a/src/pics/action/hi16-action-dnd3.png b/src/pics/action/hi16-action-dnd3.png Binary files differnew file mode 100644 index 0000000..942ad5f --- /dev/null +++ b/src/pics/action/hi16-action-dnd3.png diff --git a/src/pics/action/hi16-action-dnd4.png b/src/pics/action/hi16-action-dnd4.png Binary files differnew file mode 100644 index 0000000..fc70f77 --- /dev/null +++ b/src/pics/action/hi16-action-dnd4.png diff --git a/src/pics/action/hi16-action-dnd5.png b/src/pics/action/hi16-action-dnd5.png Binary files differnew file mode 100644 index 0000000..da740a7 --- /dev/null +++ b/src/pics/action/hi16-action-dnd5.png diff --git a/src/pics/action/hi16-action-dnd6.png b/src/pics/action/hi16-action-dnd6.png Binary files differnew file mode 100644 index 0000000..c7e6605 --- /dev/null +++ b/src/pics/action/hi16-action-dnd6.png diff --git a/src/pics/action/hi16-action-dnd7.png b/src/pics/action/hi16-action-dnd7.png Binary files differnew file mode 100644 index 0000000..4911557 --- /dev/null +++ b/src/pics/action/hi16-action-dnd7.png diff --git a/src/pics/action/hi16-action-dnd8.png b/src/pics/action/hi16-action-dnd8.png Binary files differnew file mode 100644 index 0000000..77c6371 --- /dev/null +++ b/src/pics/action/hi16-action-dnd8.png diff --git a/src/pics/action/hi16-action-flip.png b/src/pics/action/hi16-action-flip.png Binary files differnew file mode 100644 index 0000000..3481a3c --- /dev/null +++ b/src/pics/action/hi16-action-flip.png diff --git a/src/pics/action/hi16-action-mirror.png b/src/pics/action/hi16-action-mirror.png Binary files differnew file mode 100644 index 0000000..7942f3f --- /dev/null +++ b/src/pics/action/hi16-action-mirror.png diff --git a/src/pics/action/hi16-action-rotate_left.png b/src/pics/action/hi16-action-rotate_left.png Binary files differnew file mode 100644 index 0000000..0c138a9 --- /dev/null +++ b/src/pics/action/hi16-action-rotate_left.png diff --git a/src/pics/action/hi16-action-rotate_right.png b/src/pics/action/hi16-action-rotate_right.png Binary files differnew file mode 100644 index 0000000..7a9119d --- /dev/null +++ b/src/pics/action/hi16-action-rotate_right.png diff --git a/src/pics/action/hi16-action-slideshow_pause.png b/src/pics/action/hi16-action-slideshow_pause.png Binary files differnew file mode 100644 index 0000000..fb08349 --- /dev/null +++ b/src/pics/action/hi16-action-slideshow_pause.png diff --git a/src/pics/action/hi16-action-slideshow_play.png b/src/pics/action/hi16-action-slideshow_play.png Binary files differnew file mode 100644 index 0000000..30c4612 --- /dev/null +++ b/src/pics/action/hi16-action-slideshow_play.png diff --git a/src/pics/action/hi22-action-flip.png b/src/pics/action/hi22-action-flip.png Binary files differnew file mode 100644 index 0000000..b5ddd6d --- /dev/null +++ b/src/pics/action/hi22-action-flip.png diff --git a/src/pics/action/hi22-action-mirror.png b/src/pics/action/hi22-action-mirror.png Binary files differnew file mode 100644 index 0000000..01e9e72 --- /dev/null +++ b/src/pics/action/hi22-action-mirror.png diff --git a/src/pics/action/hi22-action-rotate_left.png b/src/pics/action/hi22-action-rotate_left.png Binary files differnew file mode 100644 index 0000000..a3356f2 --- /dev/null +++ b/src/pics/action/hi22-action-rotate_left.png diff --git a/src/pics/action/hi22-action-rotate_right.png b/src/pics/action/hi22-action-rotate_right.png Binary files differnew file mode 100644 index 0000000..be32aab --- /dev/null +++ b/src/pics/action/hi22-action-rotate_right.png diff --git a/src/pics/action/hi22-action-slideshow_pause.png b/src/pics/action/hi22-action-slideshow_pause.png Binary files differnew file mode 100644 index 0000000..18e02ee --- /dev/null +++ b/src/pics/action/hi22-action-slideshow_pause.png diff --git a/src/pics/action/hi22-action-slideshow_play.png b/src/pics/action/hi22-action-slideshow_play.png Binary files differnew file mode 100644 index 0000000..834e364 --- /dev/null +++ b/src/pics/action/hi22-action-slideshow_play.png diff --git a/src/pics/action/hi32-action-flip.png b/src/pics/action/hi32-action-flip.png Binary files differnew file mode 100644 index 0000000..e0dd902 --- /dev/null +++ b/src/pics/action/hi32-action-flip.png diff --git a/src/pics/action/hi32-action-mirror.png b/src/pics/action/hi32-action-mirror.png Binary files differnew file mode 100644 index 0000000..a74d98d --- /dev/null +++ b/src/pics/action/hi32-action-mirror.png diff --git a/src/pics/action/hi32-action-rotate_left.png b/src/pics/action/hi32-action-rotate_left.png Binary files differnew file mode 100644 index 0000000..2c34e81 --- /dev/null +++ b/src/pics/action/hi32-action-rotate_left.png diff --git a/src/pics/action/hi32-action-rotate_right.png b/src/pics/action/hi32-action-rotate_right.png Binary files differnew file mode 100644 index 0000000..86a8085 --- /dev/null +++ b/src/pics/action/hi32-action-rotate_right.png diff --git a/src/pics/action/hi32-action-slideshow_pause.png b/src/pics/action/hi32-action-slideshow_pause.png Binary files differnew file mode 100644 index 0000000..b2d8bb7 --- /dev/null +++ b/src/pics/action/hi32-action-slideshow_pause.png diff --git a/src/pics/action/hi32-action-slideshow_play.png b/src/pics/action/hi32-action-slideshow_play.png Binary files differnew file mode 100644 index 0000000..eb24b8d --- /dev/null +++ b/src/pics/action/hi32-action-slideshow_play.png diff --git a/src/pics/action/hi48-action-flip.png b/src/pics/action/hi48-action-flip.png Binary files differnew file mode 100644 index 0000000..48b5592 --- /dev/null +++ b/src/pics/action/hi48-action-flip.png diff --git a/src/pics/action/hi48-action-mirror.png b/src/pics/action/hi48-action-mirror.png Binary files differnew file mode 100644 index 0000000..c7f92e4 --- /dev/null +++ b/src/pics/action/hi48-action-mirror.png diff --git a/src/pics/action/hi48-action-rotate_left.png b/src/pics/action/hi48-action-rotate_left.png Binary files differnew file mode 100644 index 0000000..6dfcae6 --- /dev/null +++ b/src/pics/action/hi48-action-rotate_left.png diff --git a/src/pics/action/hi48-action-rotate_right.png b/src/pics/action/hi48-action-rotate_right.png Binary files differnew file mode 100644 index 0000000..55def95 --- /dev/null +++ b/src/pics/action/hi48-action-rotate_right.png diff --git a/src/pics/action/hi48-action-slideshow_pause.png b/src/pics/action/hi48-action-slideshow_pause.png Binary files differnew file mode 100644 index 0000000..c7b1f93 --- /dev/null +++ b/src/pics/action/hi48-action-slideshow_pause.png diff --git a/src/pics/action/hi48-action-slideshow_play.png b/src/pics/action/hi48-action-slideshow_play.png Binary files differnew file mode 100644 index 0000000..d0d52b3 --- /dev/null +++ b/src/pics/action/hi48-action-slideshow_play.png diff --git a/src/pics/action/hisc-action-slideshow_pause.svgz b/src/pics/action/hisc-action-slideshow_pause.svgz Binary files differnew file mode 100644 index 0000000..2282ee4 --- /dev/null +++ b/src/pics/action/hisc-action-slideshow_pause.svgz diff --git a/src/pics/action/hisc-action-slideshow_play.svgz b/src/pics/action/hisc-action-slideshow_play.svgz Binary files differnew file mode 100644 index 0000000..d1c861e --- /dev/null +++ b/src/pics/action/hisc-action-slideshow_play.svgz diff --git a/src/pics/action/imageops.svg b/src/pics/action/imageops.svg new file mode 100644 index 0000000..191ef16 --- /dev/null +++ b/src/pics/action/imageops.svg @@ -0,0 +1,189 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" +"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> +<!-- Created with Sodipodi ("http://www.sodipodi.com/") --> +<svg + id="svg548" + sodipodi:version="0.33" + width="900pt" + height="700pt" + sodipodi:docbase="/home/bk/kde/kdeextragear-1/gwenview/pics/action/" + sodipodi:docname="/home/bk/kde/kdeextragear-1/gwenview/pics/action/imageops.svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:xlink="http://www.w3.org/1999/xlink"> + <defs + id="defs550"> + <linearGradient + id="linearGradient558"> + <stop + style="stop-color:#ffffff;stop-opacity:1;" + offset="0.000000" + id="stop559" /> + <stop + style="stop-color:#ffffff;stop-opacity:0.352941;" + offset="1.000000" + id="stop560" /> + </linearGradient> + <linearGradient + id="linearGradient553"> + <stop + style="stop-color:#007a32;stop-opacity:1;" + offset="0.000000" + id="stop554" /> + <stop + style="stop-color:#35ea37;stop-opacity:1;" + offset="1.000000" + id="stop555" /> + </linearGradient> + <linearGradient + xlink:href="#linearGradient553" + id="linearGradient556" + x1="3.15965986" + y1="0.09498235" + x2="3.15967345" + y2="0.67751479" + gradientUnits="objectBoundingBox" + spreadMethod="pad" + gradientTransform="matrix(-1.378171e-16,0.796770,1.255067,2.448342e-16,3.517360e-8,2.585750e-9)" /> + <linearGradient + xlink:href="#linearGradient558" + id="linearGradient561" + x1="0.94531113" + y1="0.44628099" + x2="0.40624991" + y2="0.44628099" + gradientUnits="objectBoundingBox" + spreadMethod="pad" /> + <linearGradient + xlink:href="#linearGradient553" + id="linearGradient569" + x1="0.12921008" + y1="0.09498235" + x2="0.12922382" + y2="0.67751479" + gradientUnits="objectBoundingBox" + spreadMethod="pad" + gradientTransform="matrix(1.378171e-16,-0.796770,1.255067,2.448342e-16,3.517360e-8,-2.088561e-7)" /> + <linearGradient + xlink:href="#linearGradient553" + id="linearGradient632" + x1="-0.32952857" + y1="-0.67768455" + x2="-0.32951140" + y2="0.04958969" + gradientUnits="objectBoundingBox" + gradientTransform="matrix(-6.123032e-17,0.597790,-1.672828,-6.123032e-17,5.277323e-8,-3.412015e-8)" + spreadMethod="pad" /> + <linearGradient + xlink:href="#linearGradient558" + id="linearGradient634" + x1="0.45161262" + y1="0.02343214" + x2="0.45161262" + y2="0.73437494" + gradientUnits="objectBoundingBox" + spreadMethod="pad" /> + <linearGradient + xlink:href="#linearGradient553" + id="linearGradient660" + x1="-2.71572614" + y1="-0.87878966" + x2="-2.71570897" + y2="-0.15151539" + gradientUnits="objectBoundingBox" + gradientTransform="scale(1.000000,-1.000000)" + spreadMethod="pad" /> + <linearGradient + xlink:href="#linearGradient558" + id="linearGradient661" + x1="0.03636313" + y1="0.52158850" + x2="0.95454532" + y2="0.52491683" + gradientUnits="objectBoundingBox" + spreadMethod="pad" /> + </defs> + <sodipodi:namedview + id="base" /> + <rect + style="font-size:12;fill:#808080;fill-opacity:0;fill-rule:evenodd;stroke-width:1pt;" + id="rect562" + width="233.750015" + height="233.750015" + x="-440.798187" + y="2.522268" + transform="matrix(-1.836909e-16,-1.000000,1.000000,-1.836909e-16,0.000000,0.000000)" /> + <rect + style="font-size:12;fill:#808080;fill-opacity:0;fill-rule:evenodd;stroke-width:1pt;" + id="rect566" + width="233.750015" + height="233.750015" + x="-440.798187" + y="285.022299" + transform="matrix(-1.836909e-16,-1.000000,1.000000,-1.836909e-16,0.000000,0.000000)" /> + <rect + style="font-size:12;fill:#808080;fill-opacity:0;fill-rule:evenodd;stroke-width:1pt;" + id="rect662" + width="233.750015" + height="233.750015" + x="-440.798126" + y="552.522268" + transform="matrix(-1.836909e-16,-1.000000,1.000000,-1.836909e-16,0.000000,0.000000)" /> + <rect + style="font-size:12;fill:#808080;fill-opacity:0;fill-rule:evenodd;stroke-width:1pt;" + id="rect663" + width="233.750015" + height="233.750015" + x="-440.798126" + y="802.522268" + transform="matrix(-1.836909e-16,-1.000000,1.000000,-1.836909e-16,0.000000,0.000000)" /> + <path + style="font-size:12;fill:url(#linearGradient660);fill-rule:evenodd;stroke:#008f0c;stroke-width:7.5;stroke-linecap:round;stroke-linejoin:round;" + d="M 153.325 592.19 L 152.772 656.955 L 196.259 656.675 L 132.978 752.149 L 69.9135 657.089 L 114.614 656.741 L 114.647 592.056 L 71.0204 592.056 L 132.978 496.443 L 197.366 592.47 L 153.325 592.19 z " + id="path595" + sodipodi:nodetypes="ccccccccccc" + transform="matrix(1.000000,0.000000,0.000000,0.833794,797.5000,-194.9881)" /> + <path + style="font-size:12;fill:url(#linearGradient634);fill-rule:evenodd;stroke-width:1pt;" + d="M 132.897 510.744 L 85.1775 585.017 L 121.401 585.017 L 121.401 662.413 L 83.899 663.342 L 119.544 717.213 L 106.454 671.991 L 131.757 671.991 L 131.757 577.252 L 105.615 576.81 L 133.012 519.911 L 145.953 531.735 L 132.897 510.744 z " + id="path633" + sodipodi:nodetypes="ccccccccccccc" + transform="matrix(1.000000,0.000000,0.000000,0.833794,797.5000,-194.9881)" /> + <path + style="font-size:12;fill:url(#linearGradient569);fill-rule:evenodd;stroke:#008f0c;stroke-width:7.5;stroke-linecap:round;stroke-linejoin:round;" + d="M 483.891 586.643 C 506.637 491.188 472.662 431.723 373.641 430.563 L 373.641 386.936 L 278.581 452.215 L 374.055 513.282 L 373.775 469.241 C 448.371 472.02 424.94 536.393 412.542 586.376 L 483.891 586.643 z " + id="path567" + sodipodi:nodetypes="cccccccc" + transform="matrix(1.836909e-16,-1.000000,-1.000000,-1.836909e-16,887.6635,710.1699)" /> + <path + style="font-size:12;fill:url(#linearGradient561);fill-rule:evenodd;stroke-width:10;stroke-linecap:round;stroke-linejoin:round;" + d="M 512.202 599.885 C 531.35 492.363 488.088 434.561 370.574 437.208 L 370.574 391.909 L 293.799 444.377 L 360.545 413.304 L 360.295 449.155 C 505.054 436.162 506.114 543.551 485.509 599.579 L 512.202 599.885 z " + id="path568" + sodipodi:nodetypes="cccccccc" + transform="matrix(1.469528e-16,-0.800000,-0.800000,-1.469528e-16,798.5403,641.2339)" /> + <path + style="font-size:12;fill:url(#linearGradient556);fill-rule:evenodd;stroke:#008f0c;stroke-width:7.5;stroke-linecap:round;stroke-linejoin:round;" + d="M 483.891 586.643 C 506.637 491.188 472.662 431.723 373.641 430.563 L 373.641 386.936 L 278.581 452.215 L 374.055 513.282 L 373.775 469.241 C 448.371 472.02 424.94 536.393 412.542 586.376 L 483.891 586.643 z " + id="path552" + sodipodi:nodetypes="cccccccc" + transform="matrix(-1.836909e-16,-1.000000,1.000000,-1.836909e-16,-368.4156,710.1699)" /> + <path + style="font-size:12;fill:url(#linearGradient561);fill-rule:evenodd;stroke-width:10;stroke-linecap:round;stroke-linejoin:round;" + d="M 512.202 599.885 C 531.35 492.363 488.088 434.561 370.574 437.208 L 370.574 391.909 L 293.799 444.377 L 360.545 413.304 L 360.295 449.155 C 505.054 436.162 506.114 543.551 485.509 599.579 L 512.202 599.885 z " + id="path557" + sodipodi:nodetypes="cccccccc" + transform="matrix(-1.469528e-16,-0.800000,0.800000,-1.469528e-16,-277.3949,641.2339)" /> + <path + style="font-size:12;fill:url(#linearGradient632);fill-rule:evenodd;stroke:#008f0c;stroke-width:7.5;stroke-linecap:round;stroke-linejoin:round;" + d="M 153.325 592.19 L 152.772 656.955 L 196.259 656.675 L 132.978 752.149 L 69.9135 657.089 L 114.614 656.741 L 114.647 592.056 L 71.0204 592.056 L 132.978 496.443 L 197.366 592.47 L 153.325 592.19 z " + id="path650" + sodipodi:nodetypes="ccccccccccc" + transform="matrix(-6.123032e-17,1.000000,0.833794,5.105347e-17,149.3555,191.9064)" /> + <path + style="font-size:12;fill:url(#linearGradient661);fill-rule:evenodd;stroke-width:1pt;" + d="M 132.897 510.744 L 85.1775 585.017 L 121.401 585.017 L 121.401 662.413 L 83.899 663.342 L 119.544 717.213 L 106.454 671.991 L 131.757 671.991 L 131.757 577.252 L 105.615 576.81 L 133.012 519.911 L 145.953 531.735 L 132.897 510.744 z " + id="path651" + sodipodi:nodetypes="ccccccccccccc" + transform="matrix(-6.123032e-17,1.000000,0.833794,5.105347e-17,149.3555,191.9064)" /> +</svg> diff --git a/src/pics/action/imageops.svgz b/src/pics/action/imageops.svgz Binary files differnew file mode 100644 index 0000000..b82dbe4 --- /dev/null +++ b/src/pics/action/imageops.svgz diff --git a/src/pics/app/Makefile.am b/src/pics/app/Makefile.am new file mode 100644 index 0000000..a4b97f0 --- /dev/null +++ b/src/pics/app/Makefile.am @@ -0,0 +1 @@ +KDE_ICON=AUTO diff --git a/src/pics/app/hi128-app-gwenview.png b/src/pics/app/hi128-app-gwenview.png Binary files differnew file mode 100644 index 0000000..277769a --- /dev/null +++ b/src/pics/app/hi128-app-gwenview.png diff --git a/src/pics/app/hi16-app-gwenview.png b/src/pics/app/hi16-app-gwenview.png Binary files differnew file mode 100644 index 0000000..d93a242 --- /dev/null +++ b/src/pics/app/hi16-app-gwenview.png diff --git a/src/pics/app/hi22-app-gwenview.png b/src/pics/app/hi22-app-gwenview.png Binary files differnew file mode 100644 index 0000000..1f98c35 --- /dev/null +++ b/src/pics/app/hi22-app-gwenview.png diff --git a/src/pics/app/hi32-app-gwenview.png b/src/pics/app/hi32-app-gwenview.png Binary files differnew file mode 100644 index 0000000..5421fd8 --- /dev/null +++ b/src/pics/app/hi32-app-gwenview.png diff --git a/src/pics/app/hi48-app-gwenview.png b/src/pics/app/hi48-app-gwenview.png Binary files differnew file mode 100644 index 0000000..5db2a9a --- /dev/null +++ b/src/pics/app/hi48-app-gwenview.png diff --git a/src/pics/app/hi64-app-gwenview.png b/src/pics/app/hi64-app-gwenview.png Binary files differnew file mode 100644 index 0000000..c427605 --- /dev/null +++ b/src/pics/app/hi64-app-gwenview.png diff --git a/src/pics/app/hisc-app-gwenview.svgz b/src/pics/app/hisc-app-gwenview.svgz Binary files differnew file mode 100644 index 0000000..70ad5a1 --- /dev/null +++ b/src/pics/app/hisc-app-gwenview.svgz diff --git a/src/pics/cursor/Makefile.am b/src/pics/cursor/Makefile.am new file mode 100644 index 0000000..3143a4e --- /dev/null +++ b/src/pics/cursor/Makefile.am @@ -0,0 +1,2 @@ +cursorsdir=$(kde_datadir)/gwenview/cursors +cursors_DATA=zoom.png diff --git a/src/pics/cursor/zoom.png b/src/pics/cursor/zoom.png Binary files differnew file mode 100644 index 0000000..2250544 --- /dev/null +++ b/src/pics/cursor/zoom.png diff --git a/src/pics/thumbnail/Makefile.am b/src/pics/thumbnail/Makefile.am new file mode 100644 index 0000000..767c22b --- /dev/null +++ b/src/pics/thumbnail/Makefile.am @@ -0,0 +1,2 @@ +thumbnaildir=$(kde_datadir)/gwenview/thumbnail +thumbnail_DATA=wait.png diff --git a/src/pics/thumbnail/wait.png b/src/pics/thumbnail/wait.png Binary files differnew file mode 100644 index 0000000..62008cd --- /dev/null +++ b/src/pics/thumbnail/wait.png diff --git a/src/spec/gwenview-RH.spec b/src/spec/gwenview-RH.spec new file mode 100644 index 0000000..1b664db --- /dev/null +++ b/src/spec/gwenview-RH.spec @@ -0,0 +1,86 @@ +%define desktop_vendor rockers + +Name: gwenview +Summary: Gwenview is an image viewer for KDE. +Version: 0.16.2 +Release: 0 +License: GPL +Group: Amusements/Graphics +Source: http://prdownloads.sourceforge.net/gwenview/%{name}-%{version}.tar.bz2 +BuildRoot: %{_tmppath}/%{name}-root +Url: http://gwenview.sourceforge.net/home/ +Requires: libpng, kdebase >= 3.0 +BuildRequires: libpng-devel, kdelibs-devel, arts-devel, libjpeg-devel +BuildRequires: XFree86-devel, zlib-devel, qt-devel >= 3.0.2 + +%description +Gwenview is an image viewer for KDE. + +It features a folder tree window and a file list window to +provide easy navigation among your thousand images. + +Image loading is done by the Qt library, giving you access +to all image formats your Qt installation supports, but +Gwenview can also browse GIMP files (*.xcf) thanks to the +included QXCFI component developed by Lignum Computing. + +Gwenview correctly displays images with alpha channel, +using the traditionnal checker board as a background to +reveal transparency. + +%prep +rm -rf %{buildroot} + +%setup -q + +%build +%configure --with-xinerama +make %{_smp_mflags} + +%install +%makeinstall + +desktop-file-install --vendor %{desktop_vendor} --delete-original \ + --dir %{buildroot}%{_datadir}/applications \ + --add-category X-Red-Hat-Extra \ + --add-category Application \ + --add-category Graphics \ + %{buildroot}%{_datadir}/applnk/Graphics/%{name}.desktop + +rm -rf %{buildroot}%{_datadir}/applnk + +mv %{buildroot}%{_datadir}/hicolor/16x16/actions/ \ +%{buildroot}%{_datadir}/icons/hicolor/16x16 + +mkdir %{buildroot}%{_datadir}/icons/hicolor/22x22 +mv %{buildroot}%{_datadir}/hicolor/22x22/actions/ \ +%{buildroot}%{_datadir}/icons/hicolor/22x22 + +mv %{buildroot}%{_datadir}/hicolor/32x32/actions/ \ +%{buildroot}%{_datadir}/icons/hicolor/32x32 + +rm -rf %{buildroot}%{_datadir}/hicolor + +%clean +rm -rf %{buildroot} + +%post + +%postun + +%files +%defattr(-,root,root,-) +%doc AUTHORS COPYING README INSTALL CREDITS TODO NEWS +%{_bindir}/%{name}* +%{_mandir}/man1/%{name}* +%{_datadir}/apps/konqueror/servicemenus/konqgwenview.desktop +%{_datadir}/applications/%{desktop_vendor}-%{name}.desktop +%{_datadir}/icons/*/*/*/* +%{_datadir}/locale/*/LC_MESSAGES/%{name}* + +%changelog +* Fri Feb 14 2003 Robert Rockers <brockers at dps.state.ok.us> 0.16.2 +- Recompiled for version 0.16.2 + +* Fri Feb 7 2003 Robert Rockers <brockers at dps.state.ok.us> 0.16.1 +- Initial RedHat RPM release. diff --git a/src/spec/gwenview-SuSE.spec b/src/spec/gwenview-SuSE.spec new file mode 100644 index 0000000..2d90254 --- /dev/null +++ b/src/spec/gwenview-SuSE.spec @@ -0,0 +1,45 @@ +Summary:Simple image viewer for KDE +Name: gwenview +Version: 0.15.1 +Release: 1 +Copyright: GPL +Group: Application/Multimedia +Source0: %{name}-%{version}.tar.bz2 +URL: http://gwenview.sourceforge.net + +Packager: Dario Abatianni <[email protected]> + +BuildRoot: /tmp/build/%{name}-%{version} + +%description +Gwenview is an image viewer for KDE 3.x. + +It features a folder tree window and a file list window to provide easy +navigation in your file hierarchy. Image loading is done by the Qt library, +so it supports all image formats your Qt installation supports. + +%prep +rm -rf $RPM_BUILD_ROOT + +%setup + +%build +./configure --prefix=$RPM_BUILD_ROOT/opt/kde3 --enable-final +make + +%install +make install + +%files +%defattr(-,root,root,0755) +/opt/kde3/bin/gwenview +/opt/kde3/share/apps/gwenview/icons/*/*/actions/*.png +/opt/kde3/share/icons/*/*/apps/gwenview.png +/opt/kde3/share/applnk/Graphics/gwenview.desktop +/opt/kde3/share/apps/konqueror/servicemenus/konqgwenview.desktop +/opt/kde3/share/locale/*/LC_MESSAGES/gwenview.mo +/opt/kde3/man/man1/gwenview.1 +%doc NEWS README TODO ChangeLog COPYING CREDITS + +%clean +rm -rf $RPM_BUILD_ROOT diff --git a/src/spec/gwenview-mdk.spec b/src/spec/gwenview-mdk.spec new file mode 100644 index 0000000..a78f9c2 --- /dev/null +++ b/src/spec/gwenview-mdk.spec @@ -0,0 +1,466 @@ +%define gvname gwenview +%define version 1.2.92 +## Distribution Specific Release Tag +%{?!mkrel: %define mkrel(c:) %{-c:0.%{-c*}.}%{!?_with_unstable:%(perl -e '$_="%{1}";m/(.\*)(\\d+)$/;$rel=${2}-1;re;print "$1$rel";').%{?subrel:%subrel}%{!?subrel:1}.%{?distversion:%distversion}%{?!distversion:%(echo $[%{mdkversion}/10])}}%{?_with_unstable:%{1}}%{?distsuffix:%distsuffix}%{?!distsuffix:mdk}} +%define release %mkrel 1 + +%define gvcorename libgwenviewcore +%define gvdirpartname gvdirpart +%define gvimagepartname gvimagepart + +%define name %gvname + +%define major 1 +%define libname %mklibname %{gvname} %major +%define libnamedev %mklibname %{gvname} %major -d + +# building kipi version +%define build_kipi 1 +%{?_with_nokipi: %global build_kipi 0} +%if %build_kipi +%define kipiopt --enable-kipi +%define kipireq libkipi-devel +%endif + + +Summary: Fast and easy to use image viewer for KDE +Name: %name +Version: %version +Release: %release +License: GPL +Group: Graphics +Source0: http://prdownloads.sourceforge.net/gwenview/%{gvname}-%{version}.tar.bz2 + +URL: http://gwenview.sourceforge.net +BuildRoot: %_tmppath/%{name}-%{version} +# added automake1.7 requirement to patch Makefile(s).am +%if %build_kipi +BuildRequires: kdelibs-devel automake1.7 X11-devel %{kipireq} +%else +BuildRequires: kdelibs-devel automake1.7 X11-devel +%endif + + +%description +Gwenview is a fast and easy to use image viewer/browser for KDE. +All common image formats are supported, such as PNG(including transparency), +JPEG(including EXIF tags and lossless transformations), GIF, XCF (Gimp +image format), BMP, XPM and others. Standard features include slideshow, +fullscreen view, image thumbnails, drag'n'drop, image zoom, full network +transparency using the KIO framework, including basic file operations and +browsing in compressed archives, non-blocking GUI with adjustable views. +Gwenview also provides image and directory KParts components for use e.g. in +Konqueror. Additional features, such as image renaming, comparing, +converting, and batch processing, HTML gallery and others are provided by the +KIPI image framework. + +%package -n %libname +Summary: Libraries for gwenview image viewer +Group: System/Libraries +Requires: %{name} = %{version} + +%description -n %libname +Gwenview is a fast and easy to use image viewer/browser for KDE. +%{libname} contains the libraries needed to use %{gvname} + +%package -n %libnamedev +Summary: Devel files (gwenview image viewer) +Group: Development/Other +Requires: %libname = %{version} +Provides: lib%{gvname}-devel = %{version}-%{release} +Provides: %{gvname}-devel = %{version}-%{release} + +%description -n %libnamedev +Gwenview is a fast and easy to use image viewer/browser for KDE. +%{libnamedev} contains the libraries and header files needed to +develop programs which make use of %{libname}. + +%prep +rm -rf $RPM_BUILD_ROOT + +%setup -q -n %{gvname}-%{version} + +%build +make -f admin/Makefile.common cvs + +export QTDIR=%_prefix/%_lib/qt3 +export KDEDIR=%_prefix + +export LD_LIBRARY_PATH=$QTDIR/%_lib:$KDEDIR/%_lib:$LD_LIBRARY_PATH +export PATH=$QTDIR/bin:$KDEDIR/bin:$PATH + +# Search for qt/kde libraries in the right directories (avoid patch) +# NOTE: please don't regenerate configure scripts below +perl -pi -e "s@/lib(\"|\b[^/])@/%_lib\1@g if /(kde|qt)_(libdirs|libraries)=/" configure + +./configure --disable-rpath \ +%if %build_kipi + %kipiopt \ +%else + \ +%endif + --prefix=%_prefix \ + --libdir=%_libdir \ + --mandir=%_mandir \ + --datadir=%_datadir + + +%make + +%install +rm -rf $RPM_BUILD_ROOT +mkdir -p %buildroot/%_datadir/applnk/Multimedia/Graphics +%makeinstall + +install -d %buildroot/%_menudir/ +kdedesktop2mdkmenu.pl %{gvname} "Multimedia/Graphics" %buildroot/%_datadir/applications/kde/%{gvname}.desktop %buildroot/%_menudir/%{gvname} + +#icons for rpmlint +mkdir -p %buildroot/{%_liconsdir,%_miconsdir,%_iconsdir} +ln -s %_datadir/icons/hicolor/64x64/apps/%{gvname}.png %buildroot/%_liconsdir +ln -s %_datadir/icons/hicolor/32x32/apps/%{gvname}.png %buildroot/%_iconsdir +ln -s %_datadir/icons/hicolor/16x16/apps/%{gvname}.png %buildroot/%_miconsdir + +%find_lang %{gvname} + +%post +%update_menus + +%postun +%clean_menus + +%post -n %libname -p /sbin/ldconfig + +%postun -n %libname -p /sbin/ldconfig + +%files -f %{gvname}.lang +%defattr(-,root,root,0755) +%doc NEWS AUTHORS README TODO ChangeLog COPYING INSTALL +%_bindir/%{gvname} + +%dir %_datadir/apps/%{gvdirpartname}/ +%_datadir/apps/%{gvdirpartname}/gvdirpart.rc + +%dir %_datadir/apps/%{gvimagepartname}/ +%_datadir/apps/%{gvimagepartname}/gvimagepart.rc +%_datadir/services/%{gvdirpartname}.desktop +%_datadir/services/%{gvimagepartname}.desktop +%_menudir/* +%_datadir/apps/konqueror/servicemenus/* +%_datadir/apps/kconf_update/%{gvname}* +%dir %_datadir/apps/%{gvname}/ +%_datadir/apps/%{gvname}/* +%_datadir/icons/crystalsvg/16x16/apps/* +%_datadir/icons/crystalsvg/22x22/apps/* +%_datadir/icons/hicolor/* +%_liconsdir/%{gvname}.png +%_iconsdir/%{gvname}.png +%_miconsdir/%{gvname}.png +%_datadir/applications/kde/%{gvname}.desktop +%_datadir/config.kcfg/gvconfig.kcfg +%dir %_datadir/doc/HTML/ +%_datadir/doc/HTML/* +%_mandir/man1/%{gvname}.1.bz2 + +%_libdir/kde3/* +%_libdir/libkdeinit_%{gvname}.so +%_libdir/libkdeinit_%{gvname}.la + +%files -n %libname +%defattr(-,root,root,0755) +%_libdir/*.so.* + +%files -n %libnamedev +%defattr(-,root,root,0755) +%_includedir/libgwenview_export.h +%_libdir/%{gvcorename}.so +%_libdir/%{gvcorename}.la + +%clean +rm -rf $RPM_BUILD_ROOT + +%changelog +* Sun Aug 21 2005 Angelo Naselli <[email protected]> 1.2.92-1mdk +- New release 1.2.92 + +* Wed Jul 20 2005 Angelo Naselli <[email protected]> 1.2.91-1mdk +- New release 1.2.91 +- patched for fvisibility problem (aligned to svn) + +* Mon May 09 2005 Laurent MONTEL <[email protected]> 1.2.0-4 +- Real fix build on x86_64 + +* Sun May 08 2005 Angelo Naselli <[email protected]> 1.2.0-3mdk +- fix for x86_64 arch + +* Sun May 08 2005 Angelo Naselli <[email protected]> 1.2.0-2mdk +- Rebuild + +* Sun Apr 03 2005 Angelo Naselli <[email protected]> 1.2.0-1mdk +- really built new version + +* Sun Mar 20 2005 Angelo Naselli <[email protected]> 1.2.0-0.pre4.2mdk +- really built new version + +* Sun Mar 20 2005 Angelo Naselli <[email protected]> 1.2.0-0.pre4.1mdk +- new version + +* Sat Mar 19 2005 Angelo Naselli <[email protected]> 1.2.0-0.pre3.2mdk +- fix bug #14731 + +* Sun Feb 27 2005 Angelo Naselli <[email protected]> 1.2.0-0.pre3.1mdk +- new version + +* Sun Feb 13 2005 Angelo Naselli <[email protected]> 1.2.0-0.pre2.2mdk +- define mkrel macro if not exist + +* Sun Feb 13 2005 Angelo Naselli <[email protected]> 1.2.0-0.pre2.1mdk +- new version + +* Sat Jan 29 2005 Angelo Naselli <[email protected]> 1.2.0-0.pre1.2mdk +- added patch to make it compile for mdk official 10.1 +- added patch to fix zoom (from cvs) +- added patch to add missing files (from cvs) + +* Mon Jan 24 2005 Angelo Naselli <[email protected]> 1.2.0-0.pre1.1mdk +- new version + +* Wed Jan 19 2005 Angelo Naselli <[email protected]> 1.1.8-0.4mdk +- fix bug 13100 + +* Sun Jan 16 2005 Angelo Naselli <[email protected]> 1.1.8-0.3mdk +- better handling of symlink + +* Thu Jan 13 2005 Angelo Naselli <[email protected]> 1.1.8-0.2mdk +- fix double click into kpart + +* Sun Jan 09 2005 Angelo Naselli <[email protected]> 1.1.8-0.1mdk +- 1.1.8 +- fix Requires section to be compliant to the library policy + +* Thu Dec 30 2004 Angelo Naselli <[email protected]> 1.1.7-0.5mdk +- added 1.1.7b patch (solved some build problems) + +* Wed Dec 29 2004 Angelo Naselli <[email protected]> 1.1.7-0.4mdk +- description restyling + +* Sun Dec 26 2004 Angelo Naselli <[email protected]> 1.1.7-0.3mdk +- fix Require and Provide section + +* Sun Dec 26 2004 Angelo Naselli <[email protected]> 1.1.7-0.2mdk +- removed hack management +- added distro-specific release tag management + use option "--with official" to build mdk official package + +* Mon Dec 20 2004 Laurent MONTEL <[email protected]> 1.1.7-0.1mdk +- 1.1.7 + +* Fri Dec 10 2004 Laurent MONTEL <[email protected]> 1.1.6-0.2mdk +- Fix spec file + +* Sun Oct 24 2004 Angelo Naselli <[email protected]> 1.1.6-0.1mdk +- new version + * New features: + o The application now has two modes: browse and view. Browse mode shows + all views: folder, file and image. View mode only shows the image. + Gwenview starts in browse mode except if an image URL is given as + an argument. You can switch between modes using the toolbar button, + or with the "View/Browse mode" menu item or with the Ctrl+Return shortcut. + o JPEGTran code has been integrated into Gwenview, there's no need to install + it separately anymore. + * Fixes: + o Update the EXIF thumbnail when rotating a JPEG file. + o In the folder view, folders now open with a single click (By Daniel Thaler). + o Reworked coordinate conversions in order to avoid subtle paint errors. + o Remember computed optimal repaint sizes in the config file, + so they are available immediately after next start. + o Remember shown URL after session restore. +* Sat Oct 16 2004 Angelo Naselli <[email protected]> 1.1.5-0.3mdk +- rebuilt for new liblipi + fixing +* Sat Oct 09 2004 Angelo Naselli <[email protected]> 1.1.5-0.2mdk +- applied Lubos Lunak's patch to avoid printing crash using Konqueror +* Mon Sep 20 2004 Angelo Naselli <[email protected]> 1.1.5-0.1mdk +- new version + * New features: + o The thumbnail progress bar and stop buttons are now embedded in the thumbnail view. + o The location bar now shows the file names instead of the folders. + o The thumbnails toolbar buttons have been moved to a specialized file view toolbar. + o It's now possible to assign key shortcuts to KIPI plugins. + o New manpage by Christopher Martin. + * Fixes: + o Do not display the folder name as an image in the status bar. + o Make sure the folder KPart starts in the right folder. + o Unbreak the saving of key shortcuts. + o Remote urls are correctly bookmarked. + o Do not try to overwrite the trash when trashing only one file. + +* Sun Aug 29 2004 Angelo Naselli <[email protected]> 1.1.4-0.2mdk +- patch for russian language + +* Tue Aug 24 2004 Angelo Naselli <[email protected]> 1.1.4-0.1mdk +- changed spec file to manage -with-hack option to build gwenview with + hack suffix (default is without hack) +- from Aur�lien G�teau: +- New features: + - In the thumbnail view, It's now possible to sort images in reverse order. + - Use EXIF-stored thumbnail if available. + - Option to disable saving of generated thumbnails to cache. + - In fullscreen mode, it's now possible to display the image comment or size + in addition to the file path. + - The fullscreen On-Screen-Display is more readable now. + - The background color of the image view can be configured. + - When printing, it's now possible to enlarge images so that they fill the + page. +- Fixes: + - In the folder view, pressing Enter now opens the selected folder. + - Use icon list for the configuration dialog. + - Avoid data loss if the JPEG images are saved while being rotated by + JPEGTran. + - The back button in Konqueror now works correctly with gvimagepart. + - The default layout is more user-friendly. + - Non-trivial URLs (e.g. http query URL) are correctly handled. + - You can now drop images on the image view. + +* Sat Jun 12 2004 Angelo Naselli <[email protected]> 1.1.3-0.1mdk +- new release: my wedding present :-) + from Aur�lien G�teau: + Gwenview codenamed "Hurry up, I'm getting married tomorrow" + * New features: + o You can now define custom branches in the dir view (By Craig Drummond) + o An image cache has been added to speedup image loading. + o Gwenview now uses freedesktop.org thumbnail spec to store thumbnails. + o A new option to automatically empty thumbnail cache on exit (By Angelo Naselli). + o The image size is now displayed below file names in thumbnail view. + * Fixes: + o Don't crash when switching to fullscreen while generating thumbnails and coming back (By Lubos Lunak) + o Faster thumbnail generation (By Lubos Lunak) + o Faster image painting by dynamically determining suitable paint size (By Lubos Lunak) + o Use the "Standard Background" color as the background for thumbnails and folders (By Craig Drummond). + o Make sure the current image is reloaded if it has been modified outside Gwenview. + +* Tue Jun 1 2004 Angelo Naselli <[email protected]> 1.1.2-0.3mdk +- hack suffix on kpart lib + +* Thu May 13 2004 Lenny Cartier <[email protected]> 1.1.2-0.2mdk +- merge with changes from Angelo Naselli <[email protected]> + +* Wed May 12 2004 Lenny Cartier <[email protected]> 1.1.2-0.1mdk +- 1.1.2 +- from Angelo Naselli <[email protected]> : +- mdk version of the development release (1.1.1) named gwenview_hack + from Aur�lien G�teau: + - New features: + - Added KPart support, this installs in Konqueror a new file view mode and let you view + images in an embedded Gwenview (By Jonathan Riddell). + - Asynchronous JPEG loading, based on Khtml loader. + - Really asynchronous PNG loading (By Lubos Lunak). + - Mouse wheel will now scroll the image by default. Holding Ctrl will scroll horizontally. + An option has been added to the setting dialog to toggle between scroll and browse + (By Jeroen Peters). + - When holding shift over the image, right click will zoom out (By Jeroen Peters). + - Image painting is now progressive (By Lubos Lunak). + - Fixes: + - The rotate and mirror functions can now work on multiple selection. + - Make it possible to load another image or quit even if you can't save your changes. + - Gwenview won't spawn multiple instances of jpegtran anymore. + +* Sun Feb 01 2004 Angelo Naselli <[email protected]> 1.1.0-0.1mdk +- mdk version of the first development release (1.1.0) + from Aur�lien G�teau: + - New features: + - New settings in print dialog to specify how the image must be printed. + - Big thumbnails are really BIG now. + - First implementation of asynchronous image loading. Only for PNG right now. + - Fixes: + - The move and copy dialogs now use a tree view. + - In the thumbnail view, create thumbnails for the visible images first + +* Sun Jan 11 2004 Angelo Naselli <[email protected]> 1.0.1-0.1mdk +- built mdk version of Gwenview 1.0.0 release +- fix icons for rpmlint + from Aur�lien G�teau: + - New features: + - Double-clicking an image in the file view will open it in fullscreen. + - Fixes: + - Gave contributors the credit they deserve in the about box. + - Updated to libexif 0.5.12 and applied patch from libexif CVS. + - When going to the parent folder, make sure the folder we were in before is selected. + - If there's no image in the current folder, select the first visible file. + - When holding down Shift to zoom, keep the same area of the image under the cursor. + - Nicer drag cursor. + - Hopefully fixed every cases where the image was not centered in the view. + +* Mon Dec 08 2003 Angelo Naselli <[email protected]> 1.0.0-0.1mdk +- built mdk version of Gwenview 1.0.0 release + from Aur�lien G�teau: + - New features: + - Show a wait icon for not-generated-yet thumbnails (inspired from Nautilus + thumbnail view). + - Show a broken icon for broken images. + - Fixes: + - If auto-zoom is on, make sure the zoom is updated after rotating an image. + - Fixed crash when loading XCF images if Gwenview was compiled with gcc 3.3.1. + - Before running an external tool, change working directory to current folder. + - When switching images in fullscreen, don't show the cursor. + - Use standard KDE icons for zoom actions. + - New icons for slideshow and image operations. + - New magnifier cursor. + +* Sun Nov 16 2003 Angelo Naselli <[email protected]> 1.0.0-0.pre4.1mdk +- built mdk version + from Aur�lien G�teau: + - New features + - Added a new option to hide the busy pointer when loading an + image in fullscreen. + - Added a popup menu to select the sorting mode. + Usefull in thumbnail view. + - Fixes: + - Use a KDE dialog for the configuration dialog. + - Removed the image view mouse behavior configuration + options. The behavior is much simpler now: left button + to drag image, + - middle button to toggle auto-zoom and mouse-wheel + to browse images. Ifrom Aur�lien G�teauef you want to zoom hold Shift + and use either the mouse-wheel or the left button. + +* Wed Nov 05 2003 Marcel Pol <[email protected]> 1.0.0-0.pre3.2mdk +- redo changelog +- rm -rf $RPM_BUILD_ROOT in %%install instead of %%prep + +* Tue Nov 04 2003 Angelo Naselli <[email protected]> 1.0.0-0.pre3.1mdk +- built mdk version + - New features from Aur�lien G�teau: + - Added a "don't ask me again" check box to the save prompt dialog. + - Added a reload button. + - Added a "Go" button to the location toolbar. + - Fixes: + - Really fixed saving of external tools. + - Make sure the folder view is updated when a folder is renamed. + - The mouse-wheel behaviors are not messed anymore by dialogs or by + showing the popup menu. + +* Mon Nov 03 2003 Marcel Pol <[email protected]> 1.0.0-0.pre2.2mdk +- buildrequires +- quiet setup + +* Wed Oct 22 2003 Angelo Naselli <[email protected]> 1.0.0-0.pre2.1mdk +- added some changes on spec file imported from + Lenny Cartier <[email protected]> 1.0.0-0.pre1.1mdk + +* Mon Oct 20 2003 Lenny Cartier <[email protected]> 1.0.0-0.pre1.1mdk +- from Angelo Naselli <[email protected]> : + - built mdk version + +* Mon Oct 12 2003 Angelo Naselli <[email protected]> 1.0.0pre2-1mdk +- fixed some bugs on spec file +- built mdk version + +* Mon Oct 6 2003 Angelo Naselli <[email protected]> 1.0.0pre1-2mdk +- Added print button patch + +* Mon Sep 29 2003 Angelo Naselli <[email protected]> 1.0.0pre1-1mdk +- built mdk version + +* Fri Aug 08 2003 Angelo N. <[email protected]> 0.17.1a-1mdk +- built mdk version + diff --git a/src/tools/Makefile.am b/src/tools/Makefile.am new file mode 100644 index 0000000..b689d90 --- /dev/null +++ b/src/tools/Makefile.am @@ -0,0 +1,3 @@ +toolsdir=$(kde_datadir)/gwenview/tools +tools_DATA=gimp.desktop wallpaper.desktop tiledwallpaper.desktop \ + konqueror.desktop kolourpaint.desktop diff --git a/src/tools/gimp.desktop b/src/tools/gimp.desktop new file mode 100644 index 0000000..8882956 --- /dev/null +++ b/src/tools/gimp.desktop @@ -0,0 +1,37 @@ +[Desktop Entry] +ServiceTypes=image/* +Exec=gimp-remote %F +Icon=gimp +Name=The GIMP +Name[br]=Ar GIMP +Name[bs]=GIMP +Name[ca]=El GIMP +Name[da]=GIMP +Name[de]=GIMP +Name[el]=Το GIMP +Name[et]=GIMP +Name[fa]=GIMP +Name[fi]=GIMP +Name[fr]=GIMP +Name[gl]=O Gimp +Name[hi]=द गिम्प +Name[hr]=GIMP +Name[hu]=A GIMP +Name[it]=GIMP +Name[ms]=GIMP +Name[nds]=GIMP +Name[pa]=ਜੈਮਪ +Name[pl]=GIMP +Name[pt]=O GIMP +Name[pt_BR]=O GIMP +Name[ru]=GIMP +Name[sk]=GIMP +Name[sl]=GIMP +Name[sr]=GIMP +Name[sr@Latn]=GIMP +Name[sv]=GIMP +Name[tg]=ГИМП +Name[tr]=GIMP +Name[uk]=GIMP +Name[vi]=GIMP +Name[xx]=xxThe GIMPxx diff --git a/src/tools/kolourpaint.desktop b/src/tools/kolourpaint.desktop new file mode 100644 index 0000000..2cce8ee --- /dev/null +++ b/src/tools/kolourpaint.desktop @@ -0,0 +1,7 @@ +[Desktop Entry] +ServiceTypes=image/* +Name=KolourPaint +Name[pa]=ਕੇ-ਰੰਗ-ਪੇਂਟ +Name[xx]=xxKolourPaintxx +Icon=kolourpaint +Exec=kolourpaint %u diff --git a/src/tools/konqueror.desktop b/src/tools/konqueror.desktop new file mode 100644 index 0000000..b307f9a --- /dev/null +++ b/src/tools/konqueror.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +ServiceTypes=* +Exec=konqueror %u +Icon=konqueror +Name=Konqueror +Name[hi]=कॉन्क्वेरर +Name[pa]=ਕੋਨਕਿਊਰੋਰ +Name[ta]=கான்குவரர் +Name[tg]=Конкуерор +Name[xx]=xxKonquerorxx diff --git a/src/tools/tiledwallpaper.desktop b/src/tools/tiledwallpaper.desktop new file mode 100644 index 0000000..3c91c47 --- /dev/null +++ b/src/tools/tiledwallpaper.desktop @@ -0,0 +1,43 @@ +[Desktop Entry] +ServiceTypes=image/* +Exec=dcop kdesktop KBackgroundIface setWallpaper %u 2 +Icon=background +Name=Set as Tiled Wallpaper +Name[bg]=Използване като тапет със застилане +Name[ca]=Estableix com a paper pintat (mosaic) +Name[cs]=Použít jako dlaždice na plochu +Name[da]=Sæt som fliselagt tapet +Name[de]=Als Hintergrundbild verwenden (gekachelt) +Name[el]=Ορισμός σαν Ταπετσαρία σε παράθεση +Name[es]=Establecer como fondo de pantalla embaldosado +Name[et]=Sea paanidena taustapildiks +Name[fa]=تنظیم به عنوان کاغذدیواری کاشیشده +Name[fi]=Aseta taustakuvaksi monistettuna +Name[fr]=Établir comme papier peint en mosaïque +Name[gl]=Colocar como Fondo de Escritório en Mosaico +Name[hi]=टाइल्ड वालपेपर के रूप में सेट करें +Name[hr]=Popločeno na radnu površinu +Name[hu]=Beállítás háttérképnek (mozaikszerűen) +Name[is]=Setja sem flísalagt veggfóður +Name[it]=Imposta come immagine di sfondo ripetuta +Name[ja]=タイルされた壁紙として設定 +Name[ka]=მოზაიკურ ფონად დაყენება +Name[ms]=Tetapn sebagai Kertas Dinding Jubin +Name[nds]=As kachelt Achtergrundbild fastleggen +Name[nl]=Als getegelde achtergrondafbeelding instellen +Name[pa]=ਤਣਿਆ ਵਾਲਪੇਪਰ ਬਣਾਓ +Name[pl]=Ustaw jako kafelkowaną tapetę +Name[pt]=Colocar como Fundo de Ecrã em Mosaico +Name[pt_BR]=Configurar como papel de parede ladrilhado +Name[ru]=Расположить черепицей на рабочем столе +Name[sk]=Nastaviť ako pozadie (dlaždice) +Name[sr]=Постави као поплочану позадину +Name[sr@Latn]=Postavi kao popločanu pozadinu +Name[sv]=Ange som skrivbordsunderlägg sida vid sida +Name[ta]=முழுவதும் வால்பேப்பராக அமை +Name[tg]=Сурати мизи кориро бо мавзӯъи сафолӣ танзим кунед +Name[tr]=Döşenmiş Duvar Kağıdı Yap +Name[uk]=Встановити як шпалери (плиткою) +Name[vi]=Đặt là ảnh nền màn hình đã lát đều +Name[xx]=xxSet as Tiled Wallpaperxx +Name[zh_CN]=设为平铺墙纸 diff --git a/src/tools/wallpaper.desktop b/src/tools/wallpaper.desktop new file mode 100644 index 0000000..5287a5b --- /dev/null +++ b/src/tools/wallpaper.desktop @@ -0,0 +1,45 @@ +[Desktop Entry] +ServiceTypes=image/* +Exec=dcop kdesktop KBackgroundIface setWallpaper %u 6 +Icon=background +Name=Set as Wallpaper +Name[ar]=عيين كورق جدار +Name[bg]=Използване като тапет +Name[ca]=Estableix com a paper pintat +Name[cs]=Použít jako tapetu +Name[cy]=Gosod fel Papur Wal +Name[da]=Sæt som tapet +Name[de]=Als Hintergrundbild verwenden +Name[el]=Ορισμός σαν Ταπετσαρία +Name[es]=Establecer como fondo +Name[et]=Sea taustapildiks +Name[fa]=تنظیم به عنوان کاغذدیواری +Name[fi]=Aseta taustakuvaksi +Name[fr]=Établir comme papier peint +Name[gl]=Colocar como Fondo de Escritório +Name[hi]=वालपेपर के रूप में सेट करें +Name[hr]=Postavi na radnu površinu +Name[hu]=Beállítás háttérképnek +Name[is]=Setja sem veggfóður +Name[it]=Imposta come immagine di sfondo +Name[ja]=壁紙として設定 +Name[ka]=ფონად მითითება +Name[ms]=Tetapn sebagai Kertas Dinding +Name[nds]=As Achtergrundbild fastleggen +Name[nl]=Als achtergrondafbeelding instellen +Name[pa]=ਵਾਲਪੇਪਰ ਸੈੱਟ ਕਰੋ +Name[pl]=Ustaw jako tapetę +Name[pt]=Colocar como Fundo de Ecrã +Name[pt_BR]=Configurar como papel de parede +Name[ru]=Сделать рисунком рабочего стола +Name[sk]=Nastaviť ako pozadie +Name[sr]=Постави као позадину +Name[sr@Latn]=Postavi kao pozadinu +Name[sv]=Ange som skrivbordsunderlägg +Name[ta]=வால்பேப்பராக அமைக்கவும் +Name[tg]=Танзими сурати мизи корӣ +Name[tr]=Duvar Kağıdı Yap +Name[uk]=Встановити як шпалери +Name[vi]=Đặt là ảnh nền màn hình +Name[xx]=xxSet as Wallpaperxx +Name[zh_CN]=设为墙纸 diff --git a/src/tsthread/Makefile.am b/src/tsthread/Makefile.am new file mode 100644 index 0000000..cd0ed32 --- /dev/null +++ b/src/tsthread/Makefile.am @@ -0,0 +1,12 @@ +INCLUDES = -I$(srcdir) -I$(srcdir)/.. $(all_includes) + +noinst_LTLIBRARIES = libtsthread.la + +libtsthread_la_SOURCES = \ + tsthread.cpp \ + tswaitcondition.cpp + +noinst_HEADERS = \ + tsthread.h + +METASOURCES = AUTO diff --git a/src/tsthread/tsthread.cpp b/src/tsthread/tsthread.cpp new file mode 100644 index 0000000..16b4538 --- /dev/null +++ b/src/tsthread/tsthread.cpp @@ -0,0 +1,194 @@ +/**************************************************************************** + + Copyright (C) 2004 Lubos Lunak <[email protected]> + +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 OR COPYRIGHT HOLDERS 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. + +****************************************************************************/ + +#include "tsthread.h" + +#include <qapplication.h> +#include <qmetaobject.h> +#include <kdebug.h> +#include <qguardedptr.h> + +#include <assert.h> + +#ifdef TS_QTHREADSTORAGE +QThreadStorage< TSThread** >* TSThread::current_thread; +#else +TSCurrentThread* TSThread::current_thread; +#endif +TSThread* TSThread::main_thread = NULL; + +class TSMainThread + : public TSThread + { + protected: + virtual void run() { assert( false ); } + }; + +TSThread::Helper::Helper( TSThread* parent ) + : thread( parent ) + { + assert( parent ); + } + +void TSThread::Helper::run() + { + thread->executeThread(); + } + + +TSThread::TSThread() + : thread( this ) + , cancelling( false ) + , emit_pending( false ) + , cancel_mutex( NULL ) + , cancel_cond( NULL ) + , deleted_flag( NULL ) + { + } + +TSThread::~TSThread() + { + if( deleted_flag != NULL ) + *deleted_flag = true; + } + +void TSThread::start() + { + if( current_thread == NULL ) + initCurrentThread(); + thread.start(); + } + +void TSThread::cancel() + { + QMutexLocker lock( &mutex ); + cancelling = true; + if( cancel_mutex != NULL ) + { + QMutexLocker lock( cancel_mutex ); + cancel_cond->wakeAll(); + } + } + +void TSThread::wait( unsigned long time ) + { + thread.wait( time ); + } + +bool TSThread::finished() const + { + return thread.finished(); + } + +bool TSThread::running() const + { + return thread.running(); + } + +void TSThread::initCurrentThread() + { +#ifdef TS_QTHREADSTORAGE + current_thread = new QThreadStorage< TSThread** >(); + typedef TSThread* TSThreadPtr; + // set pointer for main thread (this must be main thread) + current_thread->setLocalData( new TSThreadPtr( NULL )); // TODO NULL ? +#else + current_thread = new TSCurrentThread(); + main_thread = new TSMainThread; + // set pointer for main thread (this must be main thread) + current_thread->setLocalData( main_thread ); +#endif + } + +void TSThread::executeThread() + { +#ifdef TS_QTHREADSTORAGE + // store dynamically allocated pointer, so that + // QThreadStorage deletes the pointer and not TSThread + typedef TSThread* TSThreadPtr; + current_thread->setLocalData( new TSThreadPtr( this )); +#else + current_thread->setLocalData( this ); +#endif + run(); + postSignal( this, NULL ); // = terminated() + } + +void TSThread::postSignal( QObject* obj, const char* signal ) + { + assert( currentThread() == this ); + qApp->postEvent( this, new SignalEvent( signal, obj, NULL )); + } + +void TSThread::emitSignalInternal( QObject* obj, const char* signal, QUObject* o ) + { + assert( currentThread() == this ); + QMutexLocker locker( &signal_mutex ); + emit_pending = true; + qApp->postEvent( this, new SignalEvent( signal, obj, o )); + while( emit_pending ) + signal_cond.wait( &signal_mutex ); + } + +void TSThread::emitCancellableSignalInternal( QObject* obj, const char* signal, QUObject* o ) + { + assert( currentThread() == this ); + // can't use this->mutex, because its locking will be triggered by TSWaitCondition + QMutexLocker locker( &signal_mutex ); + emit_pending = true; + qApp->postEvent( this, new SignalEvent( signal, obj, o )); + while( emit_pending && !testCancel()) + signal_cond.cancellableWait( &signal_mutex ); + emit_pending = false; // in case of cancel + } + +void TSThread::customEvent( QCustomEvent* ev ) + { + SignalEvent* e = static_cast< SignalEvent* >( ev ); + if( e->signal.isEmpty()) // = terminated() + { // threadExecute() finishes before the actual thread terminates + if( !finished()) + wait(); + emit terminated(); + return; + } + bool deleted = false; + deleted_flag = &deleted; // this is like QGuardedPtr for self, but faster + int signal_id = e->object->metaObject()->findSignal( normalizeSignalSlot( e->signal ).data() + 1, true ); + if( signal_id >= 0 ) + e->object->qt_emit( signal_id, e->args ); + else + kdWarning() << "Cannot emit signal \"" << e->signal << "\"." << endl; + if( deleted ) // some slot deleted 'this' + return; + deleted_flag = NULL; + QMutexLocker locker( &signal_mutex ); + if( emit_pending ) + { + emit_pending = false; + signal_cond.wakeOne(); + } + } + +#include "tsthread.moc" diff --git a/src/tsthread/tsthread.h b/src/tsthread/tsthread.h new file mode 100644 index 0000000..5b98890 --- /dev/null +++ b/src/tsthread/tsthread.h @@ -0,0 +1,387 @@ +/**************************************************************************** + + Copyright (C) 2004 Lubos Lunak <[email protected]> + +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 OR COPYRIGHT HOLDERS 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. + +****************************************************************************/ + +#ifndef TSTHREAD_H +#define TSTHREAD_H + +#include <qobject.h> +#include <qthread.h> +#include <qwaitcondition.h> +#include <qdeepcopy.h> +#include <qmutex.h> +#include <private/qucomextra_p.h> + +#ifdef TS_QTHREADSTORAGE +#include <qthreadstorage.h> +#else +#include <pthread.h> +#endif + +#include "tswaitcondition.h" + +// how difficult ... +template< typename T > +T TSDeepCopy( const T& t ) +{ + return QDeepCopy< T >( t ); +} + +class TSCurrentThread; + +/** + Thread class, internally based on QThread, which intentionally doesn't have + dangerous crap like QThread::terminate() and intentionally has useful features + like emitting signals to the main thread, currentThread() or support + for cancelling. + */ +class TSThread + : public QObject + { + Q_OBJECT + public: + TSThread(); + virtual ~TSThread(); + /** + * Starts the thread. + * @see QThread::start() + */ + void start(); + /** + * Waits for the thread to finish. + * @see QThread::wait() + */ + void wait( unsigned long time = ULONG_MAX ); + /** + * Returns true if the thread has finished. + * @see QThread::finished() + */ + bool finished() const; + /** + * Returns true if the thread is running. + * @see QThread::running() + */ + bool running() const; + /** + * Sends the thread a request to terminate. + * The thread must check for cancellation using testCancel() + * and finish execution (return from run()). + * @see testCancel() + */ + void cancel(); + // TODO suspend + resume? + /** + * Returns true if a request to terminate is pending. + * @see cancel() + */ + bool testCancel() const; + /** + * Returns pointer to the current thread, i.e. thread from which + * this function is called. + */ + static TSThread* currentThread(); + /** + * Returns a pointer to the main thread. Mostly useful for currentThread(). + */ + static TSThread* mainThread(); + /** + * Emits the specified signal in the main thread. This function returns + * only after all slots connected to the signal have been executed (i.e. + * it works like normal signal). The signal can have one pointer argument, + * which can be used for communication in either direction. QObject::sender() + * in slots is valid. + * Example: + * \code + * emitSignal( this, SIGNAL( result( int* )), &result_data ); + * \endcode + * @see postSignal + * @see emitCancellableSignal + */ + void emitSignal( QObject* obj, const char* signal ); + template< typename T1 > + void emitSignal( QObject* obj, const char* signal, const T1& p1 ); + template< typename T1, typename T2 > + void emitSignal( QObject* obj, const char* signal, const T1& p1, const T2& p2 ); + /** + * This function works like emitSignal(), but additionally acts as a cancellation + * point, i.e. calling cancel() on the thread causes premature return. + * @see emitSignal + * @see postSignal + */ + void emitCancellableSignal( QObject* obj, const char* signal ); + template< typename T1 > + void emitCancellableSignal( QObject* obj, const char* signal, const T1& p1 ); + template< typename T1, typename T2 > + void emitCancellableSignal( QObject* obj, const char* signal, const T1& p1, const T2& p2 ); + /** + * Posts (i.e. it is not executed immediatelly like normal signals) + * a signal to be emitted in the main thread. The signal cannot + * have any parameters, use your TSThread derived class instance + * data members instead. QObject::sender() in slots is valid, unless + * the thread instance is destroyed before the signal is processed. + * @see emitSignal + */ + void postSignal( QObject* obj, const char* signal ); // is emitted _always_ in main thread + protected: + /** + * The code to be executed in the started thread. + * @see QThread::run() + */ + virtual void run() = 0; + signals: + /** + * Emitted after the thread is terminated. + */ + void terminated(); // is emitted _always_ in main thread + protected: + /** + * @internal + */ + void customEvent( QCustomEvent* e ); + private: + class SignalEvent + : public QCustomEvent + { + public: + SignalEvent( const char* sig, QObject* obj, QUObject* o ) + : QCustomEvent( QEvent::User ), signal( sig ), object( obj ), args( o ) + { + } + const QCString signal; + QObject* object; + QUObject* args; + }; + class Helper + : public QThread + { + public: + Helper( TSThread* parent ); + protected: + virtual void run(); + private: + TSThread* thread; + }; + void executeThread(); + static void initCurrentThread(); + bool setCancelData( QMutex*m, QWaitCondition* c ); + void setSignalData( QUObject* o, int i ); + void setSignalData( QUObject* o, const QImage& i ); + void setSignalData( QUObject* o, const QString& s ); + void setSignalData( QUObject* o, bool b ); + void setSignalData( QUObject* o, const QColor& c ); + void setSignalData( QUObject* o, const char* s ); + void setSignalData( QUObject* o, const QSize& ); + void emitSignalInternal( QObject* obj, const char* signal, QUObject* o ); + void emitCancellableSignalInternal( QObject* obj, const char* signal, QUObject* o ); + friend class Helper; + friend class TSWaitCondition; + Helper thread; + bool cancelling; + bool emit_pending; + mutable QMutex mutex; + QMutex signal_mutex; + TSWaitCondition signal_cond; + QMutex* cancel_mutex; + QWaitCondition* cancel_cond; + bool* deleted_flag; +#ifdef TS_QTHREADSTORAGE + static QThreadStorage< TSThread** >* current_thread; +#else + static TSCurrentThread* current_thread; +#endif + static TSThread* main_thread; + private: + TSThread( const TSThread& ); + TSThread& operator=( const TSThread& ); + }; + +#ifndef TS_QTHREADSTORAGE +/** + * @internal + */ +class TSCurrentThread + { + public: + TSCurrentThread(); + ~TSCurrentThread(); + TSThread* localData() const; + void setLocalData( TSThread* t ); + private: + pthread_key_t key; + }; + + +inline TSCurrentThread::TSCurrentThread() + { + pthread_key_create( &key, NULL ); + } + +inline TSCurrentThread::~TSCurrentThread() + { + pthread_key_delete( key ); + } + +inline void TSCurrentThread::setLocalData( TSThread* t ) + { + pthread_setspecific( key, t ); + } + +inline TSThread* TSCurrentThread::localData() const + { + return static_cast< TSThread* >( pthread_getspecific( key )); + } +#endif + +inline +bool TSThread::testCancel() const + { + QMutexLocker lock( &mutex ); + return cancelling; + } + +#include <kdebug.h> +inline +bool TSThread::setCancelData( QMutex* m, QWaitCondition* c ) + { + QMutexLocker lock( &mutex ); + if( cancelling && m != NULL ) + return false; + cancel_mutex = m; + cancel_cond = c; + return true; + } + +inline +TSThread* TSThread::currentThread() + { + if( current_thread == NULL ) + initCurrentThread(); +#ifdef TS_QTHREADSTORAGE + return *current_thread->localData(); +#else + return current_thread->localData(); +#endif + } + +inline +TSThread* TSThread::mainThread() + { + return main_thread; + } + +inline +void TSThread::setSignalData( QUObject* o, int i ) + { + static_QUType_int.set( o, i ); + } + +inline +void TSThread::setSignalData( QUObject* o, const QImage& i ) + { + static_QUType_varptr.set( o, &i ); + } + +inline +void TSThread::setSignalData( QUObject* o, const QString& s ) + { + static_QUType_QString.set( o, s ); + } + +inline +void TSThread::setSignalData( QUObject* o, bool b ) + { + static_QUType_bool.set( o, b ); + } + +inline +void TSThread::setSignalData( QUObject* o, const QColor& c ) + { + static_QUType_varptr.set( o, &c ); + } + +inline +void TSThread::setSignalData( QUObject* o, const char* s ) + { + static_QUType_charstar.set( o, s ); + } + +inline +void TSThread::setSignalData( QUObject* o, const QSize& s ) + { + static_QUType_varptr.set( o, &s ); + } + +inline +void TSThread::emitSignal( QObject* obj, const char* signal ) + { + QUObject o[ 1 ]; + emitSignalInternal( obj, signal, o ); + } + +template< typename T1 > +inline +void TSThread::emitSignal( QObject* obj, const char* signal, const T1& p1 ) + { + QUObject o[ 2 ]; + setSignalData( o + 1, p1 ); + emitSignalInternal( obj, signal, o ); + } + +template< typename T1, typename T2 > +inline +void TSThread::emitSignal( QObject* obj, const char* signal, const T1& p1, const T2& p2 ) + { + QUObject o[ 3 ]; + setSignalData( o + 1, p1 ); + setSignalData( o + 2, p2 ); + emitSignalInternal( obj, signal, o ); + } + +inline +void TSThread::emitCancellableSignal( QObject* obj, const char* signal ) + { + QUObject o[ 1 ]; + emitCancellableSignalInternal( obj, signal, o ); + } + +template< typename T1 > +inline +void TSThread::emitCancellableSignal( QObject* obj, const char* signal, const T1& p1 ) + { + QUObject o[ 2 ]; + setSignalData( o + 1, p1 ); + emitCancellableSignalInternal( obj, signal, o ); + } + +template< typename T1, typename T2 > +inline +void TSThread::emitCancellableSignal( QObject* obj, const char* signal, const T1& p1, const T2& p2 ) + { + QUObject o[ 3 ]; + setSignalData( o + 1, p1 ); + setSignalData( o + 2, p2 ); + emitCancellableSignalInternal( obj, signal, o ); + } + + +#endif diff --git a/src/tsthread/tswaitcondition.cpp b/src/tsthread/tswaitcondition.cpp new file mode 100644 index 0000000..e11e989 --- /dev/null +++ b/src/tsthread/tswaitcondition.cpp @@ -0,0 +1,60 @@ +/**************************************************************************** + + Copyright (C) 2004 Lubos Lunak <[email protected]> + +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 OR COPYRIGHT HOLDERS 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. + +****************************************************************************/ + +#include "tswaitcondition.h" + +#include "tsthread.h" + +bool TSWaitCondition::wait( QMutex* m, unsigned long time ) + { + return cond.wait( m, time ); // TODO? + } + +bool TSWaitCondition::cancellableWait( QMutex* m, unsigned long time ) + { + mutex.lock(); + if( !TSThread::currentThread()->setCancelData( &mutex, &cond )) + { + mutex.unlock(); + return false; + } + m->unlock(); + bool ret = cond.wait( &mutex, time ); + TSThread::currentThread()->setCancelData( NULL, NULL ); + mutex.unlock(); + m->lock(); + return ret; + } + +void TSWaitCondition::wakeOne() + { + QMutexLocker locker( &mutex ); + cond.wakeOne(); + } + +void TSWaitCondition::wakeAll() + { + QMutexLocker locker( &mutex ); + cond.wakeAll(); + } diff --git a/src/tsthread/tswaitcondition.h b/src/tsthread/tswaitcondition.h new file mode 100644 index 0000000..09f9cab --- /dev/null +++ b/src/tsthread/tswaitcondition.h @@ -0,0 +1,72 @@ +/**************************************************************************** + + Copyright (C) 2004 Lubos Lunak <[email protected]> + +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 OR COPYRIGHT HOLDERS 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. + +****************************************************************************/ + +#ifndef TSWAITCONDITION_H +#define TSWAITCONDITION_H + +#include <qmutex.h> +#include <qwaitcondition.h> + +class TSWaitCondition + { + public: + TSWaitCondition(); + ~TSWaitCondition(); + bool wait( QMutex* mutex, unsigned long time = ULONG_MAX ); + bool wait( QMutex& mutex, unsigned long time = ULONG_MAX ); + bool cancellableWait( QMutex* mutex, unsigned long time = ULONG_MAX ); + bool cancellableWait( QMutex& mutex, unsigned long time = ULONG_MAX ); + void wakeOne(); + void wakeAll(); + private: + QMutex mutex; + QWaitCondition cond; + private: + TSWaitCondition( const TSWaitCondition& ); + TSWaitCondition& operator=( const TSWaitCondition& ); + }; + +inline +TSWaitCondition::TSWaitCondition() + { + } + +inline +TSWaitCondition::~TSWaitCondition() + { + } + +inline +bool TSWaitCondition::wait( QMutex& mutex, unsigned long time ) + { + return wait( &mutex, time ); + } + +inline +bool TSWaitCondition::cancellableWait( QMutex& mutex, unsigned long time ) + { + return cancellableWait( &mutex, time ); + } + +#endif diff --git a/src/updates/Makefile.am b/src/updates/Makefile.am new file mode 100644 index 0000000..c246428 --- /dev/null +++ b/src/updates/Makefile.am @@ -0,0 +1,9 @@ +update_DATA = \ + gwenview_thumbnail_size.upd \ + gwenview_1.4_osdformat.upd + +update_SCRIPTS = \ + gwenview_thumbnail_size.sh \ + gwenview_1.4_osdformat.sh + +updatedir = $(kde_datadir)/kconf_update diff --git a/src/updates/gwenview_1.4_osdformat.sh b/src/updates/gwenview_1.4_osdformat.sh new file mode 100755 index 0000000..6a28550 --- /dev/null +++ b/src/updates/gwenview_1.4_osdformat.sh @@ -0,0 +1,43 @@ +#!/bin/sh + +# Escape backslashes. Shell sucks. +sed s'/\\/BACKSLASH/g' | ( + +osdMode= +while read line ; do + if echo $line | grep '^osd mode' >/dev/null 2>/dev/null ; then + osdMode=$(echo $line | sed 's/^.*=//') + continue + fi + if echo $line | grep '^free output format' >/dev/null 2>/dev/null ; then + freeOutputFormat=$(echo $line | sed -e 's/^.*=//') + continue + fi + echo $line +done + +case $osdMode in + 0) + osdFormat='' + ;; + 1) + osdFormat='%p' + ;; + 2) + osdFormat='%c' + ;; + 3) + osdFormat='%p\\n%c' + ;; + 4) + osdFormat="$freeOutputFormat" + ;; + *) + osdFormat='' + ;; +esac + +echo osdFormat=$osdFormat + +) | sed 's/BACKSLASH/\\/g' +# Escape backslashes. Shell sucks. diff --git a/src/updates/gwenview_1.4_osdformat.upd b/src/updates/gwenview_1.4_osdformat.upd new file mode 100644 index 0000000..52f341d --- /dev/null +++ b/src/updates/gwenview_1.4_osdformat.upd @@ -0,0 +1,4 @@ +Id=osdformat +File=gwenviewrc +Group=pixmap widget +Script=gwenview_1.4_osdformat.sh,sh diff --git a/src/updates/gwenview_1.4_osdformat_test.sh b/src/updates/gwenview_1.4_osdformat_test.sh new file mode 100755 index 0000000..ed555d7 --- /dev/null +++ b/src/updates/gwenview_1.4_osdformat_test.sh @@ -0,0 +1,39 @@ +#!/bin/sh +check() { + cat > src.txt <<EOF +$1 +EOF + cat > tmp.txt <<EOF +$2 +EOF + sort tmp.txt > wanted.txt + ./gwenview_1.4_osdformat.sh < src.txt | sort > dst.txt + if diff -uwB --brief wanted.txt dst.txt ; then + echo ok + else + echo " +-------------- +# From: +$1 +# To: +$2 +# Got: +" + cat dst.txt + echo "# Wanted:" + cat wanted.txt + echo " +-------------- +" + fi +} + +check "osd mode=0" "osdFormat=" +check "osd mode=1" "osdFormat=%p" +check "osd mode=2" "osdFormat=%c" +check "osd mode=3" "osdFormat=%p\\n%c" +check "osd mode=3 +something else" "osdFormat=%p\\n%c +something else" +check "osd mode=4 +free output format=zog\\nzog" "osdFormat=zog\\nzog" diff --git a/src/updates/gwenview_thumbnail_size.sh b/src/updates/gwenview_thumbnail_size.sh new file mode 100644 index 0000000..5e2872b --- /dev/null +++ b/src/updates/gwenview_thumbnail_size.sh @@ -0,0 +1,12 @@ +#! /bin/sh +while read line; do + if echo "$line" | grep '^thumbnail size=small' >/dev/null 2>/dev/null; then + echo "thumbnail size=48" + elif echo "$line" | grep '^thumbnail size=med' >/dev/null 2>/dev/null; then + echo "thumbnail size=96" + elif echo "$line" | grep '^thumbnail size=large' >/dev/null 2>/dev/null; then + echo "thumbnail size=128" + else + echo "$line" + fi +done diff --git a/src/updates/gwenview_thumbnail_size.upd b/src/updates/gwenview_thumbnail_size.upd new file mode 100644 index 0000000..dea652b --- /dev/null +++ b/src/updates/gwenview_thumbnail_size.upd @@ -0,0 +1,5 @@ +Id=gwenview_thumbnail_size +File=gwenviewrc +Group=file widget +Options=overwrite +Script=gwenview_thumbnail_size.sh,sh |