diff options
Diffstat (limited to 'src')
96 files changed, 36749 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..8d9e15f --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,239 @@ +if ENABLE_JPEG +jpeg_LIB = $(top_builddir)/jpegutils/libeom-jpegutils.la +endif + +toolbar_LIB = $(top_builddir)/cut-n-paste/toolbar-editor/libtoolbareditor.la + +screensaver_LIB = $(top_builddir)/cut-n-paste/totem-screensaver/libtotemscrsaver.la + +noinst_LTLIBRARIES = libeom.la + +bin_PROGRAMS = eom + +headerdir = $(prefix)/include/eom-@EOM_API_VERSION@/eom +header_DATA = $(INST_H_FILES) + +MARSHAL_OUTPUT = \ + eom-marshal.h \ + eom-marshal.c + +NOINST_H_FILES = \ + eom-session.h \ + eom-util.h \ + eom-pixbuf-util.h \ + eom-preferences-dialog.h \ + eom-config-keys.h \ + eom-image-jpeg.h \ + eom-image-private.h \ + eom-uri-converter.h \ + eom-metadata-reader.h \ + eom-metadata-reader-jpg.h \ + eom-metadata-reader-png.h \ + eom-save-as-dialog-helper.h \ + eom-print-image-setup.h \ + eom-print-preview.h \ + eom-print.h \ + eom-module.h \ + eom-plugin-manager.h \ + eom-plugin-engine.h \ + uta.h \ + eom-close-confirmation-dialog.h \ + zoom.h + +if ENABLE_PYTHON +NOINST_H_FILES += \ + eom-python-module.h \ + eom-python-plugin.h +endif + +INST_H_FILES = \ + eom-application.h \ + eom-debug.h \ + eom-window.h \ + eom-sidebar.h \ + eom-dialog.h \ + eom-properties-dialog.h \ + eom-error-message-area.h \ + eom-file-chooser.h \ + eom-statusbar.h \ + eom-thumb-nav.h \ + eom-transform.h \ + eom-image.h \ + eom-enums.h \ + eom-image-save-info.h \ + eom-scroll-view.h \ + eom-thumb-view.h \ + eom-list-store.h \ + eom-thumbnail.h \ + eom-job-queue.h \ + eom-jobs.h \ + eom-plugin.h + +libeom_la_SOURCES = \ + eom-application.c \ + eom-session.c \ + eom-debug.c \ + eom-util.c \ + eom-pixbuf-util.c \ + eom-window.c \ + eom-sidebar.c \ + eom-dialog.c \ + eom-preferences-dialog.c \ + eom-properties-dialog.c \ + eom-error-message-area.c \ + eom-file-chooser.c \ + eom-statusbar.c \ + eom-thumb-nav.c \ + eom-transform.c \ + eom-image.c \ + eom-image-jpeg.c \ + eom-image-save-info.c \ + eom-scroll-view.c \ + eom-thumb-view.c \ + eom-list-store.c \ + eom-thumbnail.c \ + eom-job-queue.c \ + eom-jobs.c \ + eom-uri-converter.c \ + eom-metadata-reader.c \ + eom-metadata-reader-jpg.c \ + eom-metadata-reader-png.c \ + eom-save-as-dialog-helper.c \ + eom-print-image-setup.c \ + eom-print-preview.c \ + eom-print.c \ + eom-module.c \ + eom-close-confirmation-dialog.c \ + eom-plugin.c \ + eom-plugin-manager.c \ + eom-plugin-engine.c \ + uta.c \ + zoom.c \ + $(BUILT_SOURCES) \ + $(NOINST_H_FILES) \ + $(INST_H_FILES) + +if HAVE_EXIF +INST_H_FILES += \ + eom-exif-util.h \ + eom-exif-details.h +libeom_la_SOURCES += \ + eom-exif-util.c \ + eom-exif-details.c +endif + +if ENABLE_PYTHON +libeom_la_SOURCES += \ + eom-python-module.c \ + eom-python-module.h \ + eom-python-plugin.c \ + eom-python-plugin.h +endif + +if HAVE_EXEMPI +# We need to make sure eom-exif-details.h +# is only listed once in INST_H_FILES +# or the build will break with automake-1.11 +if !HAVE_EXIF +INST_H_FILES += \ + eom-exif-details.h +endif !HAVE_EXIF +libeom_la_SOURCES += \ + eom-exif-details.c +endif HAVE_EXEMPI + +libeom_la_CFLAGS = \ + -I$(top_srcdir)/jpegutils \ + -I$(top_srcdir)/cut-n-paste/toolbar-editor \ + -I$(top_srcdir)/cut-n-paste/totem-screensaver \ + $(EOM_CFLAGS) \ + $(WARN_CFLAGS) \ + -DG_LOG_DOMAIN=\"EOM\" \ + -DEOM_PREFIX=\""${prefix}"\" \ + -DEOM_DATA_DIR=\""$(pkgdatadir)"\" \ + -DEOM_LOCALE_DIR=\""$(datadir)/locale"\" \ + -DEOM_PIXMAPS_DIR=\""$(datadir)/pixmaps/eom"\" \ + -DEOM_PLUGIN_DIR=\""$(libdir)/eom/plugins"\" + +libeom_la_LIBADD = \ + $(EOM_LIBS) + +if HAVE_LCMS +libeom_la_CFLAGS += \ + $(X11_CFLAGS) + +libeom_la_LIBADD += \ + $(X11_LIBS) +endif + +if ENABLE_PYTHON +libeom_la_CFLAGS += \ + $(NO_STRICT_ALIASING_CFLAGS) \ + $(PYGTK_CFLAGS) \ + $(PYTHON_CFLAGS) \ + $(AM_CFLAGS) + +libeom_la_LIBADD += \ + $(top_builddir)/bindings/python/eom.la +endif + +libeom_la_LDFLAGS = -export-dynamic -no-undefined -export-symbols-regex "^[[^_]].*" + +eom_SOURCES = main.c + +eom_CFLAGS = \ + -I$(top_srcdir)/cut-n-paste/toolbar-editor \ + -I$(top_srcdir)/cut-n-paste/totem-screensaver \ + $(EOM_CFLAGS) \ + -DEOM_DATA_DIR=\""$(pkgdatadir)"\" \ + -DEOM_LOCALE_DIR=\""$(datadir)/locale"\" + +eom_LDADD = \ + libeom.la \ + $(EOM_LIBS) \ + $(LIBJPEG) \ + $(toolbar_LIB) \ + $(screensaver_LIB) \ + $(jpeg_LIB) + +eom_LDFLAGS = -export-dynamic -no-undefined -export-symbols-regex "^[[^_]].*" + +BUILT_SOURCES = \ + eom-enum-types.c \ + eom-enum-types.h \ + $(MARSHAL_OUTPUT) + +eom-enum-types.h: eom-enum-types.h.template $(INST_H_FILES) $(GLIB_MKENUMS) + $(AM_V_GEN)(cd $(srcdir) && $(GLIB_MKENUMS) --template eom-enum-types.h.template $(INST_H_FILES)) > $@ + +eom-enum-types.c: eom-enum-types.c.template $(INST_H_FILES) $(GLIB_MKENUMS) + $(AM_V_GEN)(cd $(srcdir) && $(GLIB_MKENUMS) --template eom-enum-types.c.template $(INST_H_FILES)) > $@ + +eom-marshal.h: eom-marshal.list $(GLIB_GENMARSHAL) + $(AM_V_GEN)$(GLIB_GENMARSHAL) $< --header --internal --prefix=eom_marshal > $@ + +eom-marshal.c: eom-marshal.list $(GLIB_GENMARSHAL) + $(AM_V_GEN)$(GLIB_GENMARSHAL) $< --body --header --prefix=eom_marshal > $@ + +EXTRA_DIST = \ + eom-enum-types.h.template \ + eom-enum-types.c.template \ + eom-marshal.list + +if HAVE_DBUS + +BUILT_SOURCES += eom-application-service.h + +EXTRA_DIST += eom-application-service.xml + +eom-application-service.h: eom-application-service.xml + $(AM_V_GEN)dbus-binding-tool --prefix=eom_application --mode=glib-server --output=eom-application-service.h $< + +endif + +CLEANFILES = $(BUILT_SOURCES) + +dist-hook: + cd $(distdir); rm -f $(BUILT_SOURCES) + +-include $(top_srcdir)/git.mk diff --git a/src/Makefile.in b/src/Makefile.in new file mode 100644 index 0000000..deacf79 --- /dev/null +++ b/src/Makefile.in @@ -0,0 +1,1326 @@ +# Makefile.in generated by automake 1.11.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, +# Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + + +VPATH = @srcdir@ +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +bin_PROGRAMS = eom$(EXEEXT) +@ENABLE_PYTHON_TRUE@am__append_1 = \ +@ENABLE_PYTHON_TRUE@ eom-python-module.h \ +@ENABLE_PYTHON_TRUE@ eom-python-plugin.h + +@HAVE_EXIF_TRUE@am__append_2 = \ +@HAVE_EXIF_TRUE@ eom-exif-util.h \ +@HAVE_EXIF_TRUE@ eom-exif-details.h + +@HAVE_EXIF_TRUE@am__append_3 = \ +@HAVE_EXIF_TRUE@ eom-exif-util.c \ +@HAVE_EXIF_TRUE@ eom-exif-details.c + +@ENABLE_PYTHON_TRUE@am__append_4 = \ +@ENABLE_PYTHON_TRUE@ eom-python-module.c \ +@ENABLE_PYTHON_TRUE@ eom-python-module.h \ +@ENABLE_PYTHON_TRUE@ eom-python-plugin.c \ +@ENABLE_PYTHON_TRUE@ eom-python-plugin.h + + +# We need to make sure eom-exif-details.h +# is only listed once in INST_H_FILES +# or the build will break with automake-1.11 +@HAVE_EXEMPI_TRUE@@HAVE_EXIF_FALSE@am__append_5 = \ +@HAVE_EXEMPI_TRUE@@HAVE_EXIF_FALSE@ eom-exif-details.h + +@HAVE_EXEMPI_TRUE@am__append_6 = \ +@HAVE_EXEMPI_TRUE@ eom-exif-details.c + +@HAVE_LCMS_TRUE@am__append_7 = \ +@HAVE_LCMS_TRUE@ $(X11_CFLAGS) + +@HAVE_LCMS_TRUE@am__append_8 = \ +@HAVE_LCMS_TRUE@ $(X11_LIBS) + +@ENABLE_PYTHON_TRUE@am__append_9 = \ +@ENABLE_PYTHON_TRUE@ $(NO_STRICT_ALIASING_CFLAGS) \ +@ENABLE_PYTHON_TRUE@ $(PYGTK_CFLAGS) \ +@ENABLE_PYTHON_TRUE@ $(PYTHON_CFLAGS) \ +@ENABLE_PYTHON_TRUE@ $(AM_CFLAGS) + +@ENABLE_PYTHON_TRUE@am__append_10 = \ +@ENABLE_PYTHON_TRUE@ $(top_builddir)/bindings/python/eom.la + +@HAVE_DBUS_TRUE@am__append_11 = eom-application-service.h +@HAVE_DBUS_TRUE@am__append_12 = eom-application-service.xml +subdir = src +DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(SHELL) $(top_srcdir)/mkinstalldirs +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +LTLIBRARIES = $(noinst_LTLIBRARIES) +am__DEPENDENCIES_1 = +@HAVE_LCMS_TRUE@am__DEPENDENCIES_2 = $(am__DEPENDENCIES_1) +libeom_la_DEPENDENCIES = $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_2) \ + $(am__append_10) +am__libeom_la_SOURCES_DIST = eom-application.c eom-session.c \ + eom-debug.c eom-util.c eom-pixbuf-util.c eom-window.c \ + eom-sidebar.c eom-dialog.c eom-preferences-dialog.c \ + eom-properties-dialog.c eom-error-message-area.c \ + eom-file-chooser.c eom-statusbar.c eom-thumb-nav.c \ + eom-transform.c eom-image.c eom-image-jpeg.c \ + eom-image-save-info.c eom-scroll-view.c eom-thumb-view.c \ + eom-list-store.c eom-thumbnail.c eom-job-queue.c eom-jobs.c \ + eom-uri-converter.c eom-metadata-reader.c \ + eom-metadata-reader-jpg.c eom-metadata-reader-png.c \ + eom-save-as-dialog-helper.c eom-print-image-setup.c \ + eom-print-preview.c eom-print.c eom-module.c \ + eom-close-confirmation-dialog.c eom-plugin.c \ + eom-plugin-manager.c eom-plugin-engine.c uta.c zoom.c \ + eom-enum-types.c eom-enum-types.h eom-marshal.h eom-marshal.c \ + eom-application-service.h eom-session.h eom-util.h \ + eom-pixbuf-util.h eom-preferences-dialog.h eom-config-keys.h \ + eom-image-jpeg.h eom-image-private.h eom-uri-converter.h \ + eom-metadata-reader.h eom-metadata-reader-jpg.h \ + eom-metadata-reader-png.h eom-save-as-dialog-helper.h \ + eom-print-image-setup.h eom-print-preview.h eom-print.h \ + eom-module.h eom-plugin-manager.h eom-plugin-engine.h uta.h \ + eom-close-confirmation-dialog.h zoom.h eom-python-module.h \ + eom-python-plugin.h eom-application.h eom-debug.h eom-window.h \ + eom-sidebar.h eom-dialog.h eom-properties-dialog.h \ + eom-error-message-area.h eom-file-chooser.h eom-statusbar.h \ + eom-thumb-nav.h eom-transform.h eom-image.h eom-enums.h \ + eom-image-save-info.h eom-scroll-view.h eom-thumb-view.h \ + eom-list-store.h eom-thumbnail.h eom-job-queue.h eom-jobs.h \ + eom-plugin.h eom-exif-util.h eom-exif-details.h \ + eom-exif-util.c eom-exif-details.c eom-python-module.c \ + eom-python-plugin.c +am__objects_1 = libeom_la-eom-marshal.lo +am__objects_2 = +am__objects_3 = libeom_la-eom-enum-types.lo $(am__objects_1) \ + $(am__objects_2) +am__objects_4 = $(am__objects_2) +am__objects_5 = $(am__objects_2) $(am__objects_2) +@HAVE_EXIF_TRUE@am__objects_6 = libeom_la-eom-exif-util.lo \ +@HAVE_EXIF_TRUE@ libeom_la-eom-exif-details.lo +@ENABLE_PYTHON_TRUE@am__objects_7 = libeom_la-eom-python-module.lo \ +@ENABLE_PYTHON_TRUE@ libeom_la-eom-python-plugin.lo +@HAVE_EXEMPI_TRUE@am__objects_8 = libeom_la-eom-exif-details.lo +am_libeom_la_OBJECTS = libeom_la-eom-application.lo \ + libeom_la-eom-session.lo libeom_la-eom-debug.lo \ + libeom_la-eom-util.lo libeom_la-eom-pixbuf-util.lo \ + libeom_la-eom-window.lo libeom_la-eom-sidebar.lo \ + libeom_la-eom-dialog.lo libeom_la-eom-preferences-dialog.lo \ + libeom_la-eom-properties-dialog.lo \ + libeom_la-eom-error-message-area.lo \ + libeom_la-eom-file-chooser.lo libeom_la-eom-statusbar.lo \ + libeom_la-eom-thumb-nav.lo libeom_la-eom-transform.lo \ + libeom_la-eom-image.lo libeom_la-eom-image-jpeg.lo \ + libeom_la-eom-image-save-info.lo libeom_la-eom-scroll-view.lo \ + libeom_la-eom-thumb-view.lo libeom_la-eom-list-store.lo \ + libeom_la-eom-thumbnail.lo libeom_la-eom-job-queue.lo \ + libeom_la-eom-jobs.lo libeom_la-eom-uri-converter.lo \ + libeom_la-eom-metadata-reader.lo \ + libeom_la-eom-metadata-reader-jpg.lo \ + libeom_la-eom-metadata-reader-png.lo \ + libeom_la-eom-save-as-dialog-helper.lo \ + libeom_la-eom-print-image-setup.lo \ + libeom_la-eom-print-preview.lo libeom_la-eom-print.lo \ + libeom_la-eom-module.lo \ + libeom_la-eom-close-confirmation-dialog.lo \ + libeom_la-eom-plugin.lo libeom_la-eom-plugin-manager.lo \ + libeom_la-eom-plugin-engine.lo libeom_la-uta.lo \ + libeom_la-zoom.lo $(am__objects_3) $(am__objects_4) \ + $(am__objects_5) $(am__objects_6) $(am__objects_7) \ + $(am__objects_8) +libeom_la_OBJECTS = $(am_libeom_la_OBJECTS) +AM_V_lt = $(am__v_lt_$(V)) +am__v_lt_ = $(am__v_lt_$(AM_DEFAULT_VERBOSITY)) +am__v_lt_0 = --silent +libeom_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(libeom_la_CFLAGS) \ + $(CFLAGS) $(libeom_la_LDFLAGS) $(LDFLAGS) -o $@ +am__installdirs = "$(DESTDIR)$(bindir)" "$(DESTDIR)$(headerdir)" +PROGRAMS = $(bin_PROGRAMS) +am_eom_OBJECTS = eom-main.$(OBJEXT) +eom_OBJECTS = $(am_eom_OBJECTS) +eom_DEPENDENCIES = libeom.la $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(toolbar_LIB) $(screensaver_LIB) \ + $(jpeg_LIB) +eom_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(eom_CFLAGS) $(CFLAGS) \ + $(eom_LDFLAGS) $(LDFLAGS) -o $@ +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_$(V)) +am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY)) +am__v_CC_0 = @echo " CC " $@; +AM_V_at = $(am__v_at_$(V)) +am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY)) +am__v_at_0 = @ +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_$(V)) +am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY)) +am__v_CCLD_0 = @echo " CCLD " $@; +AM_V_GEN = $(am__v_GEN_$(V)) +am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY)) +am__v_GEN_0 = @echo " GEN " $@; +SOURCES = $(libeom_la_SOURCES) $(eom_SOURCES) +DIST_SOURCES = $(am__libeom_la_SOURCES_DIST) $(eom_SOURCES) +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +DATA = $(header_DATA) +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ +ALL_LINGUAS = @ALL_LINGUAS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CATALOGS = @CATALOGS@ +CATOBJEXT = @CATOBJEXT@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DATADIRNAME = @DATADIRNAME@ +DBUS_BINDING_TOOL = @DBUS_BINDING_TOOL@ +DBUS_CFLAGS = @DBUS_CFLAGS@ +DBUS_LIBS = @DBUS_LIBS@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DISABLE_DEPRECATED = @DISABLE_DEPRECATED@ +DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@ +DLLTOOL = @DLLTOOL@ +DOC_USER_FORMATS = @DOC_USER_FORMATS@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EOM_API_VERSION = @EOM_API_VERSION@ +EOM_CFLAGS = @EOM_CFLAGS@ +EOM_DOC_EXIF_START = @EOM_DOC_EXIF_START@ +EOM_DOC_EXIF_STOP = @EOM_DOC_EXIF_STOP@ +EOM_LIBS = @EOM_LIBS@ +EOM_MAJOR_VERSION = @EOM_MAJOR_VERSION@ +EOM_MICRO_VERSION = @EOM_MICRO_VERSION@ +EOM_MINOR_VERSION = @EOM_MINOR_VERSION@ +EXEEXT = @EXEEXT@ +EXEMPI_CFLAGS = @EXEMPI_CFLAGS@ +EXEMPI_LIBS = @EXEMPI_LIBS@ +EXIF_CFLAGS = @EXIF_CFLAGS@ +EXIF_LIBS = @EXIF_LIBS@ +FGREP = @FGREP@ +GETTEXT_PACKAGE = @GETTEXT_PACKAGE@ +GLIB_GENMARSHAL = @GLIB_GENMARSHAL@ +GLIB_MKENUMS = @GLIB_MKENUMS@ +GMOFILES = @GMOFILES@ +GMSGFMT = @GMSGFMT@ +GREP = @GREP@ +GTKDOC_CHECK = @GTKDOC_CHECK@ +GTKDOC_DEPS_CFLAGS = @GTKDOC_DEPS_CFLAGS@ +GTKDOC_DEPS_LIBS = @GTKDOC_DEPS_LIBS@ +GTKDOC_MKPDF = @GTKDOC_MKPDF@ +GTKDOC_REBASE = @GTKDOC_REBASE@ +HELP_DIR = @HELP_DIR@ +HTML_DIR = @HTML_DIR@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +INSTOBJEXT = @INSTOBJEXT@ +INTLLIBS = @INTLLIBS@ +INTLTOOL_EXTRACT = @INTLTOOL_EXTRACT@ +INTLTOOL_MERGE = @INTLTOOL_MERGE@ +INTLTOOL_PERL = @INTLTOOL_PERL@ +INTLTOOL_UPDATE = @INTLTOOL_UPDATE@ +LCMS_CFLAGS = @LCMS_CFLAGS@ +LCMS_LIBS = @LCMS_LIBS@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBJPEG = @LIBJPEG@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIBXML2_CFLAGS = @LIBXML2_CFLAGS@ +LIBXML2_LIBS = @LIBXML2_LIBS@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBOBJS = @LTLIBOBJS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MATECONFTOOL = @MATECONFTOOL@ +MATECONF_SCHEMA_CONFIG_SOURCE = @MATECONF_SCHEMA_CONFIG_SOURCE@ +MATECONF_SCHEMA_FILE_DIR = @MATECONF_SCHEMA_FILE_DIR@ +MKDIR_P = @MKDIR_P@ +MKINSTALLDIRS = @MKINSTALLDIRS@ +MSGFMT = @MSGFMT@ +MSGFMT_OPTS = @MSGFMT_OPTS@ +MSGMERGE = @MSGMERGE@ +NM = @NM@ +NMEDIT = @NMEDIT@ +NO_STRICT_ALIASING_CFLAGS = @NO_STRICT_ALIASING_CFLAGS@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OMF_DIR = @OMF_DIR@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +POFILES = @POFILES@ +POSUB = @POSUB@ +PO_IN_DATADIR_FALSE = @PO_IN_DATADIR_FALSE@ +PO_IN_DATADIR_TRUE = @PO_IN_DATADIR_TRUE@ +PYGOBJECT_CODEGEN = @PYGOBJECT_CODEGEN@ +PYGOBJECT_DEFSDIR = @PYGOBJECT_DEFSDIR@ +PYGOBJECT_H2DEF = @PYGOBJECT_H2DEF@ +PYGTK_CFLAGS = @PYGTK_CFLAGS@ +PYGTK_DEFSDIR = @PYGTK_DEFSDIR@ +PYGTK_LIBS = @PYGTK_LIBS@ +PYTHON = @PYTHON@ +PYTHON_CFLAGS = @PYTHON_CFLAGS@ +PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@ +PYTHON_EXTRA_LIBS = @PYTHON_EXTRA_LIBS@ +PYTHON_LIBS = @PYTHON_LIBS@ +PYTHON_LIB_LOC = @PYTHON_LIB_LOC@ +PYTHON_PLATFORM = @PYTHON_PLATFORM@ +PYTHON_PREFIX = @PYTHON_PREFIX@ +PYTHON_VERSION = @PYTHON_VERSION@ +RANLIB = @RANLIB@ +RSVG_CFLAGS = @RSVG_CFLAGS@ +RSVG_LIBS = @RSVG_LIBS@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +USE_NLS = @USE_NLS@ +VERSION = @VERSION@ +WARN_CFLAGS = @WARN_CFLAGS@ +X11_CFLAGS = @X11_CFLAGS@ +X11_LIBS = @X11_LIBS@ +XGETTEXT = @XGETTEXT@ +XMKMF = @XMKMF@ +X_CFLAGS = @X_CFLAGS@ +X_EXTRA_LIBS = @X_EXTRA_LIBS@ +X_LIBS = @X_LIBS@ +X_PRE_LIBS = @X_PRE_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +pkgpyexecdir = @pkgpyexecdir@ +pkgpythondir = @pkgpythondir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +pyexecdir = @pyexecdir@ +pythondir = @pythondir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +@ENABLE_JPEG_TRUE@jpeg_LIB = $(top_builddir)/jpegutils/libeom-jpegutils.la +toolbar_LIB = $(top_builddir)/cut-n-paste/toolbar-editor/libtoolbareditor.la +screensaver_LIB = $(top_builddir)/cut-n-paste/totem-screensaver/libtotemscrsaver.la +noinst_LTLIBRARIES = libeom.la +headerdir = $(prefix)/include/eom-@EOM_API_VERSION@/eom +header_DATA = $(INST_H_FILES) +MARSHAL_OUTPUT = \ + eom-marshal.h \ + eom-marshal.c + +NOINST_H_FILES = eom-session.h eom-util.h eom-pixbuf-util.h \ + eom-preferences-dialog.h eom-config-keys.h eom-image-jpeg.h \ + eom-image-private.h eom-uri-converter.h eom-metadata-reader.h \ + eom-metadata-reader-jpg.h eom-metadata-reader-png.h \ + eom-save-as-dialog-helper.h eom-print-image-setup.h \ + eom-print-preview.h eom-print.h eom-module.h \ + eom-plugin-manager.h eom-plugin-engine.h uta.h \ + eom-close-confirmation-dialog.h zoom.h $(am__append_1) +INST_H_FILES = eom-application.h eom-debug.h eom-window.h \ + eom-sidebar.h eom-dialog.h eom-properties-dialog.h \ + eom-error-message-area.h eom-file-chooser.h eom-statusbar.h \ + eom-thumb-nav.h eom-transform.h eom-image.h eom-enums.h \ + eom-image-save-info.h eom-scroll-view.h eom-thumb-view.h \ + eom-list-store.h eom-thumbnail.h eom-job-queue.h eom-jobs.h \ + eom-plugin.h $(am__append_2) $(am__append_5) +libeom_la_SOURCES = eom-application.c eom-session.c eom-debug.c \ + eom-util.c eom-pixbuf-util.c eom-window.c eom-sidebar.c \ + eom-dialog.c eom-preferences-dialog.c eom-properties-dialog.c \ + eom-error-message-area.c eom-file-chooser.c eom-statusbar.c \ + eom-thumb-nav.c eom-transform.c eom-image.c eom-image-jpeg.c \ + eom-image-save-info.c eom-scroll-view.c eom-thumb-view.c \ + eom-list-store.c eom-thumbnail.c eom-job-queue.c eom-jobs.c \ + eom-uri-converter.c eom-metadata-reader.c \ + eom-metadata-reader-jpg.c eom-metadata-reader-png.c \ + eom-save-as-dialog-helper.c eom-print-image-setup.c \ + eom-print-preview.c eom-print.c eom-module.c \ + eom-close-confirmation-dialog.c eom-plugin.c \ + eom-plugin-manager.c eom-plugin-engine.c uta.c zoom.c \ + $(BUILT_SOURCES) $(NOINST_H_FILES) $(INST_H_FILES) \ + $(am__append_3) $(am__append_4) $(am__append_6) +libeom_la_CFLAGS = -I$(top_srcdir)/jpegutils \ + -I$(top_srcdir)/cut-n-paste/toolbar-editor \ + -I$(top_srcdir)/cut-n-paste/totem-screensaver $(EOM_CFLAGS) \ + $(WARN_CFLAGS) -DG_LOG_DOMAIN=\"EOM\" \ + -DEOM_PREFIX=\""${prefix}"\" \ + -DEOM_DATA_DIR=\""$(pkgdatadir)"\" \ + -DEOM_LOCALE_DIR=\""$(datadir)/locale"\" \ + -DEOM_PIXMAPS_DIR=\""$(datadir)/pixmaps/eom"\" \ + -DEOM_PLUGIN_DIR=\""$(libdir)/eom/plugins"\" $(am__append_7) \ + $(am__append_9) +libeom_la_LIBADD = $(EOM_LIBS) $(am__append_8) $(am__append_10) +libeom_la_LDFLAGS = -export-dynamic -no-undefined -export-symbols-regex "^[[^_]].*" +eom_SOURCES = main.c +eom_CFLAGS = \ + -I$(top_srcdir)/cut-n-paste/toolbar-editor \ + -I$(top_srcdir)/cut-n-paste/totem-screensaver \ + $(EOM_CFLAGS) \ + -DEOM_DATA_DIR=\""$(pkgdatadir)"\" \ + -DEOM_LOCALE_DIR=\""$(datadir)/locale"\" + +eom_LDADD = \ + libeom.la \ + $(EOM_LIBS) \ + $(LIBJPEG) \ + $(toolbar_LIB) \ + $(screensaver_LIB) \ + $(jpeg_LIB) + +eom_LDFLAGS = -export-dynamic -no-undefined -export-symbols-regex "^[[^_]].*" +BUILT_SOURCES = eom-enum-types.c eom-enum-types.h $(MARSHAL_OUTPUT) \ + $(am__append_11) +EXTRA_DIST = eom-enum-types.h.template eom-enum-types.c.template \ + eom-marshal.list $(am__append_12) +CLEANFILES = $(BUILT_SOURCES) +all: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +clean-noinstLTLIBRARIES: + -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES) + @list='$(noinst_LTLIBRARIES)'; for p in $$list; do \ + dir="`echo $$p | sed -e 's|/[^/]*$$||'`"; \ + test "$$dir" != "$$p" || dir=.; \ + echo "rm -f \"$${dir}/so_locations\""; \ + rm -f "$${dir}/so_locations"; \ + done +libeom.la: $(libeom_la_OBJECTS) $(libeom_la_DEPENDENCIES) + $(AM_V_CCLD)$(libeom_la_LINK) $(libeom_la_OBJECTS) $(libeom_la_LIBADD) $(LIBS) +install-binPROGRAMS: $(bin_PROGRAMS) + @$(NORMAL_INSTALL) + test -z "$(bindir)" || $(MKDIR_P) "$(DESTDIR)$(bindir)" + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p || test -f $$p1; \ + then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-binPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(bindir)" && rm -f $$files + +clean-binPROGRAMS: + @list='$(bin_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +eom$(EXEEXT): $(eom_OBJECTS) $(eom_DEPENDENCIES) + @rm -f eom$(EXEEXT) + $(AM_V_CCLD)$(eom_LINK) $(eom_OBJECTS) $(eom_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/eom-main.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libeom_la-eom-application.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libeom_la-eom-close-confirmation-dialog.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libeom_la-eom-debug.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libeom_la-eom-dialog.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libeom_la-eom-enum-types.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libeom_la-eom-error-message-area.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libeom_la-eom-exif-details.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libeom_la-eom-exif-util.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libeom_la-eom-file-chooser.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libeom_la-eom-image-jpeg.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libeom_la-eom-image-save-info.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libeom_la-eom-image.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libeom_la-eom-job-queue.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libeom_la-eom-jobs.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libeom_la-eom-list-store.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libeom_la-eom-marshal.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libeom_la-eom-metadata-reader-jpg.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libeom_la-eom-metadata-reader-png.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libeom_la-eom-metadata-reader.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libeom_la-eom-module.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libeom_la-eom-pixbuf-util.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libeom_la-eom-plugin-engine.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libeom_la-eom-plugin-manager.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libeom_la-eom-plugin.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libeom_la-eom-preferences-dialog.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libeom_la-eom-print-image-setup.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libeom_la-eom-print-preview.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libeom_la-eom-print.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libeom_la-eom-properties-dialog.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libeom_la-eom-python-module.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libeom_la-eom-python-plugin.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libeom_la-eom-save-as-dialog-helper.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libeom_la-eom-scroll-view.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libeom_la-eom-session.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libeom_la-eom-sidebar.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libeom_la-eom-statusbar.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libeom_la-eom-thumb-nav.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libeom_la-eom-thumb-view.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libeom_la-eom-thumbnail.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libeom_la-eom-transform.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libeom_la-eom-uri-converter.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libeom_la-eom-util.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libeom_la-eom-window.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libeom_la-uta.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libeom_la-zoom.Plo@am__quote@ + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LTCOMPILE) -c -o $@ $< + +libeom_la-eom-application.lo: eom-application.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -MT libeom_la-eom-application.lo -MD -MP -MF $(DEPDIR)/libeom_la-eom-application.Tpo -c -o libeom_la-eom-application.lo `test -f 'eom-application.c' || echo '$(srcdir)/'`eom-application.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeom_la-eom-application.Tpo $(DEPDIR)/libeom_la-eom-application.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='eom-application.c' object='libeom_la-eom-application.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -c -o libeom_la-eom-application.lo `test -f 'eom-application.c' || echo '$(srcdir)/'`eom-application.c + +libeom_la-eom-session.lo: eom-session.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -MT libeom_la-eom-session.lo -MD -MP -MF $(DEPDIR)/libeom_la-eom-session.Tpo -c -o libeom_la-eom-session.lo `test -f 'eom-session.c' || echo '$(srcdir)/'`eom-session.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeom_la-eom-session.Tpo $(DEPDIR)/libeom_la-eom-session.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='eom-session.c' object='libeom_la-eom-session.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -c -o libeom_la-eom-session.lo `test -f 'eom-session.c' || echo '$(srcdir)/'`eom-session.c + +libeom_la-eom-debug.lo: eom-debug.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -MT libeom_la-eom-debug.lo -MD -MP -MF $(DEPDIR)/libeom_la-eom-debug.Tpo -c -o libeom_la-eom-debug.lo `test -f 'eom-debug.c' || echo '$(srcdir)/'`eom-debug.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeom_la-eom-debug.Tpo $(DEPDIR)/libeom_la-eom-debug.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='eom-debug.c' object='libeom_la-eom-debug.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -c -o libeom_la-eom-debug.lo `test -f 'eom-debug.c' || echo '$(srcdir)/'`eom-debug.c + +libeom_la-eom-util.lo: eom-util.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -MT libeom_la-eom-util.lo -MD -MP -MF $(DEPDIR)/libeom_la-eom-util.Tpo -c -o libeom_la-eom-util.lo `test -f 'eom-util.c' || echo '$(srcdir)/'`eom-util.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeom_la-eom-util.Tpo $(DEPDIR)/libeom_la-eom-util.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='eom-util.c' object='libeom_la-eom-util.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -c -o libeom_la-eom-util.lo `test -f 'eom-util.c' || echo '$(srcdir)/'`eom-util.c + +libeom_la-eom-pixbuf-util.lo: eom-pixbuf-util.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -MT libeom_la-eom-pixbuf-util.lo -MD -MP -MF $(DEPDIR)/libeom_la-eom-pixbuf-util.Tpo -c -o libeom_la-eom-pixbuf-util.lo `test -f 'eom-pixbuf-util.c' || echo '$(srcdir)/'`eom-pixbuf-util.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeom_la-eom-pixbuf-util.Tpo $(DEPDIR)/libeom_la-eom-pixbuf-util.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='eom-pixbuf-util.c' object='libeom_la-eom-pixbuf-util.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -c -o libeom_la-eom-pixbuf-util.lo `test -f 'eom-pixbuf-util.c' || echo '$(srcdir)/'`eom-pixbuf-util.c + +libeom_la-eom-window.lo: eom-window.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -MT libeom_la-eom-window.lo -MD -MP -MF $(DEPDIR)/libeom_la-eom-window.Tpo -c -o libeom_la-eom-window.lo `test -f 'eom-window.c' || echo '$(srcdir)/'`eom-window.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeom_la-eom-window.Tpo $(DEPDIR)/libeom_la-eom-window.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='eom-window.c' object='libeom_la-eom-window.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -c -o libeom_la-eom-window.lo `test -f 'eom-window.c' || echo '$(srcdir)/'`eom-window.c + +libeom_la-eom-sidebar.lo: eom-sidebar.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -MT libeom_la-eom-sidebar.lo -MD -MP -MF $(DEPDIR)/libeom_la-eom-sidebar.Tpo -c -o libeom_la-eom-sidebar.lo `test -f 'eom-sidebar.c' || echo '$(srcdir)/'`eom-sidebar.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeom_la-eom-sidebar.Tpo $(DEPDIR)/libeom_la-eom-sidebar.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='eom-sidebar.c' object='libeom_la-eom-sidebar.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -c -o libeom_la-eom-sidebar.lo `test -f 'eom-sidebar.c' || echo '$(srcdir)/'`eom-sidebar.c + +libeom_la-eom-dialog.lo: eom-dialog.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -MT libeom_la-eom-dialog.lo -MD -MP -MF $(DEPDIR)/libeom_la-eom-dialog.Tpo -c -o libeom_la-eom-dialog.lo `test -f 'eom-dialog.c' || echo '$(srcdir)/'`eom-dialog.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeom_la-eom-dialog.Tpo $(DEPDIR)/libeom_la-eom-dialog.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='eom-dialog.c' object='libeom_la-eom-dialog.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -c -o libeom_la-eom-dialog.lo `test -f 'eom-dialog.c' || echo '$(srcdir)/'`eom-dialog.c + +libeom_la-eom-preferences-dialog.lo: eom-preferences-dialog.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -MT libeom_la-eom-preferences-dialog.lo -MD -MP -MF $(DEPDIR)/libeom_la-eom-preferences-dialog.Tpo -c -o libeom_la-eom-preferences-dialog.lo `test -f 'eom-preferences-dialog.c' || echo '$(srcdir)/'`eom-preferences-dialog.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeom_la-eom-preferences-dialog.Tpo $(DEPDIR)/libeom_la-eom-preferences-dialog.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='eom-preferences-dialog.c' object='libeom_la-eom-preferences-dialog.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -c -o libeom_la-eom-preferences-dialog.lo `test -f 'eom-preferences-dialog.c' || echo '$(srcdir)/'`eom-preferences-dialog.c + +libeom_la-eom-properties-dialog.lo: eom-properties-dialog.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -MT libeom_la-eom-properties-dialog.lo -MD -MP -MF $(DEPDIR)/libeom_la-eom-properties-dialog.Tpo -c -o libeom_la-eom-properties-dialog.lo `test -f 'eom-properties-dialog.c' || echo '$(srcdir)/'`eom-properties-dialog.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeom_la-eom-properties-dialog.Tpo $(DEPDIR)/libeom_la-eom-properties-dialog.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='eom-properties-dialog.c' object='libeom_la-eom-properties-dialog.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -c -o libeom_la-eom-properties-dialog.lo `test -f 'eom-properties-dialog.c' || echo '$(srcdir)/'`eom-properties-dialog.c + +libeom_la-eom-error-message-area.lo: eom-error-message-area.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -MT libeom_la-eom-error-message-area.lo -MD -MP -MF $(DEPDIR)/libeom_la-eom-error-message-area.Tpo -c -o libeom_la-eom-error-message-area.lo `test -f 'eom-error-message-area.c' || echo '$(srcdir)/'`eom-error-message-area.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeom_la-eom-error-message-area.Tpo $(DEPDIR)/libeom_la-eom-error-message-area.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='eom-error-message-area.c' object='libeom_la-eom-error-message-area.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -c -o libeom_la-eom-error-message-area.lo `test -f 'eom-error-message-area.c' || echo '$(srcdir)/'`eom-error-message-area.c + +libeom_la-eom-file-chooser.lo: eom-file-chooser.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -MT libeom_la-eom-file-chooser.lo -MD -MP -MF $(DEPDIR)/libeom_la-eom-file-chooser.Tpo -c -o libeom_la-eom-file-chooser.lo `test -f 'eom-file-chooser.c' || echo '$(srcdir)/'`eom-file-chooser.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeom_la-eom-file-chooser.Tpo $(DEPDIR)/libeom_la-eom-file-chooser.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='eom-file-chooser.c' object='libeom_la-eom-file-chooser.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -c -o libeom_la-eom-file-chooser.lo `test -f 'eom-file-chooser.c' || echo '$(srcdir)/'`eom-file-chooser.c + +libeom_la-eom-statusbar.lo: eom-statusbar.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -MT libeom_la-eom-statusbar.lo -MD -MP -MF $(DEPDIR)/libeom_la-eom-statusbar.Tpo -c -o libeom_la-eom-statusbar.lo `test -f 'eom-statusbar.c' || echo '$(srcdir)/'`eom-statusbar.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeom_la-eom-statusbar.Tpo $(DEPDIR)/libeom_la-eom-statusbar.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='eom-statusbar.c' object='libeom_la-eom-statusbar.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -c -o libeom_la-eom-statusbar.lo `test -f 'eom-statusbar.c' || echo '$(srcdir)/'`eom-statusbar.c + +libeom_la-eom-thumb-nav.lo: eom-thumb-nav.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -MT libeom_la-eom-thumb-nav.lo -MD -MP -MF $(DEPDIR)/libeom_la-eom-thumb-nav.Tpo -c -o libeom_la-eom-thumb-nav.lo `test -f 'eom-thumb-nav.c' || echo '$(srcdir)/'`eom-thumb-nav.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeom_la-eom-thumb-nav.Tpo $(DEPDIR)/libeom_la-eom-thumb-nav.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='eom-thumb-nav.c' object='libeom_la-eom-thumb-nav.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -c -o libeom_la-eom-thumb-nav.lo `test -f 'eom-thumb-nav.c' || echo '$(srcdir)/'`eom-thumb-nav.c + +libeom_la-eom-transform.lo: eom-transform.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -MT libeom_la-eom-transform.lo -MD -MP -MF $(DEPDIR)/libeom_la-eom-transform.Tpo -c -o libeom_la-eom-transform.lo `test -f 'eom-transform.c' || echo '$(srcdir)/'`eom-transform.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeom_la-eom-transform.Tpo $(DEPDIR)/libeom_la-eom-transform.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='eom-transform.c' object='libeom_la-eom-transform.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -c -o libeom_la-eom-transform.lo `test -f 'eom-transform.c' || echo '$(srcdir)/'`eom-transform.c + +libeom_la-eom-image.lo: eom-image.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -MT libeom_la-eom-image.lo -MD -MP -MF $(DEPDIR)/libeom_la-eom-image.Tpo -c -o libeom_la-eom-image.lo `test -f 'eom-image.c' || echo '$(srcdir)/'`eom-image.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeom_la-eom-image.Tpo $(DEPDIR)/libeom_la-eom-image.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='eom-image.c' object='libeom_la-eom-image.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -c -o libeom_la-eom-image.lo `test -f 'eom-image.c' || echo '$(srcdir)/'`eom-image.c + +libeom_la-eom-image-jpeg.lo: eom-image-jpeg.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -MT libeom_la-eom-image-jpeg.lo -MD -MP -MF $(DEPDIR)/libeom_la-eom-image-jpeg.Tpo -c -o libeom_la-eom-image-jpeg.lo `test -f 'eom-image-jpeg.c' || echo '$(srcdir)/'`eom-image-jpeg.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeom_la-eom-image-jpeg.Tpo $(DEPDIR)/libeom_la-eom-image-jpeg.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='eom-image-jpeg.c' object='libeom_la-eom-image-jpeg.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -c -o libeom_la-eom-image-jpeg.lo `test -f 'eom-image-jpeg.c' || echo '$(srcdir)/'`eom-image-jpeg.c + +libeom_la-eom-image-save-info.lo: eom-image-save-info.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -MT libeom_la-eom-image-save-info.lo -MD -MP -MF $(DEPDIR)/libeom_la-eom-image-save-info.Tpo -c -o libeom_la-eom-image-save-info.lo `test -f 'eom-image-save-info.c' || echo '$(srcdir)/'`eom-image-save-info.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeom_la-eom-image-save-info.Tpo $(DEPDIR)/libeom_la-eom-image-save-info.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='eom-image-save-info.c' object='libeom_la-eom-image-save-info.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -c -o libeom_la-eom-image-save-info.lo `test -f 'eom-image-save-info.c' || echo '$(srcdir)/'`eom-image-save-info.c + +libeom_la-eom-scroll-view.lo: eom-scroll-view.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -MT libeom_la-eom-scroll-view.lo -MD -MP -MF $(DEPDIR)/libeom_la-eom-scroll-view.Tpo -c -o libeom_la-eom-scroll-view.lo `test -f 'eom-scroll-view.c' || echo '$(srcdir)/'`eom-scroll-view.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeom_la-eom-scroll-view.Tpo $(DEPDIR)/libeom_la-eom-scroll-view.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='eom-scroll-view.c' object='libeom_la-eom-scroll-view.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -c -o libeom_la-eom-scroll-view.lo `test -f 'eom-scroll-view.c' || echo '$(srcdir)/'`eom-scroll-view.c + +libeom_la-eom-thumb-view.lo: eom-thumb-view.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -MT libeom_la-eom-thumb-view.lo -MD -MP -MF $(DEPDIR)/libeom_la-eom-thumb-view.Tpo -c -o libeom_la-eom-thumb-view.lo `test -f 'eom-thumb-view.c' || echo '$(srcdir)/'`eom-thumb-view.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeom_la-eom-thumb-view.Tpo $(DEPDIR)/libeom_la-eom-thumb-view.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='eom-thumb-view.c' object='libeom_la-eom-thumb-view.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -c -o libeom_la-eom-thumb-view.lo `test -f 'eom-thumb-view.c' || echo '$(srcdir)/'`eom-thumb-view.c + +libeom_la-eom-list-store.lo: eom-list-store.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -MT libeom_la-eom-list-store.lo -MD -MP -MF $(DEPDIR)/libeom_la-eom-list-store.Tpo -c -o libeom_la-eom-list-store.lo `test -f 'eom-list-store.c' || echo '$(srcdir)/'`eom-list-store.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeom_la-eom-list-store.Tpo $(DEPDIR)/libeom_la-eom-list-store.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='eom-list-store.c' object='libeom_la-eom-list-store.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -c -o libeom_la-eom-list-store.lo `test -f 'eom-list-store.c' || echo '$(srcdir)/'`eom-list-store.c + +libeom_la-eom-thumbnail.lo: eom-thumbnail.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -MT libeom_la-eom-thumbnail.lo -MD -MP -MF $(DEPDIR)/libeom_la-eom-thumbnail.Tpo -c -o libeom_la-eom-thumbnail.lo `test -f 'eom-thumbnail.c' || echo '$(srcdir)/'`eom-thumbnail.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeom_la-eom-thumbnail.Tpo $(DEPDIR)/libeom_la-eom-thumbnail.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='eom-thumbnail.c' object='libeom_la-eom-thumbnail.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -c -o libeom_la-eom-thumbnail.lo `test -f 'eom-thumbnail.c' || echo '$(srcdir)/'`eom-thumbnail.c + +libeom_la-eom-job-queue.lo: eom-job-queue.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -MT libeom_la-eom-job-queue.lo -MD -MP -MF $(DEPDIR)/libeom_la-eom-job-queue.Tpo -c -o libeom_la-eom-job-queue.lo `test -f 'eom-job-queue.c' || echo '$(srcdir)/'`eom-job-queue.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeom_la-eom-job-queue.Tpo $(DEPDIR)/libeom_la-eom-job-queue.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='eom-job-queue.c' object='libeom_la-eom-job-queue.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -c -o libeom_la-eom-job-queue.lo `test -f 'eom-job-queue.c' || echo '$(srcdir)/'`eom-job-queue.c + +libeom_la-eom-jobs.lo: eom-jobs.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -MT libeom_la-eom-jobs.lo -MD -MP -MF $(DEPDIR)/libeom_la-eom-jobs.Tpo -c -o libeom_la-eom-jobs.lo `test -f 'eom-jobs.c' || echo '$(srcdir)/'`eom-jobs.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeom_la-eom-jobs.Tpo $(DEPDIR)/libeom_la-eom-jobs.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='eom-jobs.c' object='libeom_la-eom-jobs.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -c -o libeom_la-eom-jobs.lo `test -f 'eom-jobs.c' || echo '$(srcdir)/'`eom-jobs.c + +libeom_la-eom-uri-converter.lo: eom-uri-converter.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -MT libeom_la-eom-uri-converter.lo -MD -MP -MF $(DEPDIR)/libeom_la-eom-uri-converter.Tpo -c -o libeom_la-eom-uri-converter.lo `test -f 'eom-uri-converter.c' || echo '$(srcdir)/'`eom-uri-converter.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeom_la-eom-uri-converter.Tpo $(DEPDIR)/libeom_la-eom-uri-converter.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='eom-uri-converter.c' object='libeom_la-eom-uri-converter.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -c -o libeom_la-eom-uri-converter.lo `test -f 'eom-uri-converter.c' || echo '$(srcdir)/'`eom-uri-converter.c + +libeom_la-eom-metadata-reader.lo: eom-metadata-reader.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -MT libeom_la-eom-metadata-reader.lo -MD -MP -MF $(DEPDIR)/libeom_la-eom-metadata-reader.Tpo -c -o libeom_la-eom-metadata-reader.lo `test -f 'eom-metadata-reader.c' || echo '$(srcdir)/'`eom-metadata-reader.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeom_la-eom-metadata-reader.Tpo $(DEPDIR)/libeom_la-eom-metadata-reader.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='eom-metadata-reader.c' object='libeom_la-eom-metadata-reader.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -c -o libeom_la-eom-metadata-reader.lo `test -f 'eom-metadata-reader.c' || echo '$(srcdir)/'`eom-metadata-reader.c + +libeom_la-eom-metadata-reader-jpg.lo: eom-metadata-reader-jpg.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -MT libeom_la-eom-metadata-reader-jpg.lo -MD -MP -MF $(DEPDIR)/libeom_la-eom-metadata-reader-jpg.Tpo -c -o libeom_la-eom-metadata-reader-jpg.lo `test -f 'eom-metadata-reader-jpg.c' || echo '$(srcdir)/'`eom-metadata-reader-jpg.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeom_la-eom-metadata-reader-jpg.Tpo $(DEPDIR)/libeom_la-eom-metadata-reader-jpg.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='eom-metadata-reader-jpg.c' object='libeom_la-eom-metadata-reader-jpg.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -c -o libeom_la-eom-metadata-reader-jpg.lo `test -f 'eom-metadata-reader-jpg.c' || echo '$(srcdir)/'`eom-metadata-reader-jpg.c + +libeom_la-eom-metadata-reader-png.lo: eom-metadata-reader-png.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -MT libeom_la-eom-metadata-reader-png.lo -MD -MP -MF $(DEPDIR)/libeom_la-eom-metadata-reader-png.Tpo -c -o libeom_la-eom-metadata-reader-png.lo `test -f 'eom-metadata-reader-png.c' || echo '$(srcdir)/'`eom-metadata-reader-png.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeom_la-eom-metadata-reader-png.Tpo $(DEPDIR)/libeom_la-eom-metadata-reader-png.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='eom-metadata-reader-png.c' object='libeom_la-eom-metadata-reader-png.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -c -o libeom_la-eom-metadata-reader-png.lo `test -f 'eom-metadata-reader-png.c' || echo '$(srcdir)/'`eom-metadata-reader-png.c + +libeom_la-eom-save-as-dialog-helper.lo: eom-save-as-dialog-helper.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -MT libeom_la-eom-save-as-dialog-helper.lo -MD -MP -MF $(DEPDIR)/libeom_la-eom-save-as-dialog-helper.Tpo -c -o libeom_la-eom-save-as-dialog-helper.lo `test -f 'eom-save-as-dialog-helper.c' || echo '$(srcdir)/'`eom-save-as-dialog-helper.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeom_la-eom-save-as-dialog-helper.Tpo $(DEPDIR)/libeom_la-eom-save-as-dialog-helper.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='eom-save-as-dialog-helper.c' object='libeom_la-eom-save-as-dialog-helper.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -c -o libeom_la-eom-save-as-dialog-helper.lo `test -f 'eom-save-as-dialog-helper.c' || echo '$(srcdir)/'`eom-save-as-dialog-helper.c + +libeom_la-eom-print-image-setup.lo: eom-print-image-setup.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -MT libeom_la-eom-print-image-setup.lo -MD -MP -MF $(DEPDIR)/libeom_la-eom-print-image-setup.Tpo -c -o libeom_la-eom-print-image-setup.lo `test -f 'eom-print-image-setup.c' || echo '$(srcdir)/'`eom-print-image-setup.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeom_la-eom-print-image-setup.Tpo $(DEPDIR)/libeom_la-eom-print-image-setup.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='eom-print-image-setup.c' object='libeom_la-eom-print-image-setup.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -c -o libeom_la-eom-print-image-setup.lo `test -f 'eom-print-image-setup.c' || echo '$(srcdir)/'`eom-print-image-setup.c + +libeom_la-eom-print-preview.lo: eom-print-preview.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -MT libeom_la-eom-print-preview.lo -MD -MP -MF $(DEPDIR)/libeom_la-eom-print-preview.Tpo -c -o libeom_la-eom-print-preview.lo `test -f 'eom-print-preview.c' || echo '$(srcdir)/'`eom-print-preview.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeom_la-eom-print-preview.Tpo $(DEPDIR)/libeom_la-eom-print-preview.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='eom-print-preview.c' object='libeom_la-eom-print-preview.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -c -o libeom_la-eom-print-preview.lo `test -f 'eom-print-preview.c' || echo '$(srcdir)/'`eom-print-preview.c + +libeom_la-eom-print.lo: eom-print.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -MT libeom_la-eom-print.lo -MD -MP -MF $(DEPDIR)/libeom_la-eom-print.Tpo -c -o libeom_la-eom-print.lo `test -f 'eom-print.c' || echo '$(srcdir)/'`eom-print.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeom_la-eom-print.Tpo $(DEPDIR)/libeom_la-eom-print.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='eom-print.c' object='libeom_la-eom-print.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -c -o libeom_la-eom-print.lo `test -f 'eom-print.c' || echo '$(srcdir)/'`eom-print.c + +libeom_la-eom-module.lo: eom-module.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -MT libeom_la-eom-module.lo -MD -MP -MF $(DEPDIR)/libeom_la-eom-module.Tpo -c -o libeom_la-eom-module.lo `test -f 'eom-module.c' || echo '$(srcdir)/'`eom-module.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeom_la-eom-module.Tpo $(DEPDIR)/libeom_la-eom-module.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='eom-module.c' object='libeom_la-eom-module.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -c -o libeom_la-eom-module.lo `test -f 'eom-module.c' || echo '$(srcdir)/'`eom-module.c + +libeom_la-eom-close-confirmation-dialog.lo: eom-close-confirmation-dialog.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -MT libeom_la-eom-close-confirmation-dialog.lo -MD -MP -MF $(DEPDIR)/libeom_la-eom-close-confirmation-dialog.Tpo -c -o libeom_la-eom-close-confirmation-dialog.lo `test -f 'eom-close-confirmation-dialog.c' || echo '$(srcdir)/'`eom-close-confirmation-dialog.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeom_la-eom-close-confirmation-dialog.Tpo $(DEPDIR)/libeom_la-eom-close-confirmation-dialog.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='eom-close-confirmation-dialog.c' object='libeom_la-eom-close-confirmation-dialog.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -c -o libeom_la-eom-close-confirmation-dialog.lo `test -f 'eom-close-confirmation-dialog.c' || echo '$(srcdir)/'`eom-close-confirmation-dialog.c + +libeom_la-eom-plugin.lo: eom-plugin.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -MT libeom_la-eom-plugin.lo -MD -MP -MF $(DEPDIR)/libeom_la-eom-plugin.Tpo -c -o libeom_la-eom-plugin.lo `test -f 'eom-plugin.c' || echo '$(srcdir)/'`eom-plugin.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeom_la-eom-plugin.Tpo $(DEPDIR)/libeom_la-eom-plugin.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='eom-plugin.c' object='libeom_la-eom-plugin.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -c -o libeom_la-eom-plugin.lo `test -f 'eom-plugin.c' || echo '$(srcdir)/'`eom-plugin.c + +libeom_la-eom-plugin-manager.lo: eom-plugin-manager.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -MT libeom_la-eom-plugin-manager.lo -MD -MP -MF $(DEPDIR)/libeom_la-eom-plugin-manager.Tpo -c -o libeom_la-eom-plugin-manager.lo `test -f 'eom-plugin-manager.c' || echo '$(srcdir)/'`eom-plugin-manager.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeom_la-eom-plugin-manager.Tpo $(DEPDIR)/libeom_la-eom-plugin-manager.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='eom-plugin-manager.c' object='libeom_la-eom-plugin-manager.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -c -o libeom_la-eom-plugin-manager.lo `test -f 'eom-plugin-manager.c' || echo '$(srcdir)/'`eom-plugin-manager.c + +libeom_la-eom-plugin-engine.lo: eom-plugin-engine.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -MT libeom_la-eom-plugin-engine.lo -MD -MP -MF $(DEPDIR)/libeom_la-eom-plugin-engine.Tpo -c -o libeom_la-eom-plugin-engine.lo `test -f 'eom-plugin-engine.c' || echo '$(srcdir)/'`eom-plugin-engine.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeom_la-eom-plugin-engine.Tpo $(DEPDIR)/libeom_la-eom-plugin-engine.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='eom-plugin-engine.c' object='libeom_la-eom-plugin-engine.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -c -o libeom_la-eom-plugin-engine.lo `test -f 'eom-plugin-engine.c' || echo '$(srcdir)/'`eom-plugin-engine.c + +libeom_la-uta.lo: uta.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -MT libeom_la-uta.lo -MD -MP -MF $(DEPDIR)/libeom_la-uta.Tpo -c -o libeom_la-uta.lo `test -f 'uta.c' || echo '$(srcdir)/'`uta.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeom_la-uta.Tpo $(DEPDIR)/libeom_la-uta.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='uta.c' object='libeom_la-uta.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -c -o libeom_la-uta.lo `test -f 'uta.c' || echo '$(srcdir)/'`uta.c + +libeom_la-zoom.lo: zoom.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -MT libeom_la-zoom.lo -MD -MP -MF $(DEPDIR)/libeom_la-zoom.Tpo -c -o libeom_la-zoom.lo `test -f 'zoom.c' || echo '$(srcdir)/'`zoom.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeom_la-zoom.Tpo $(DEPDIR)/libeom_la-zoom.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='zoom.c' object='libeom_la-zoom.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -c -o libeom_la-zoom.lo `test -f 'zoom.c' || echo '$(srcdir)/'`zoom.c + +libeom_la-eom-enum-types.lo: eom-enum-types.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -MT libeom_la-eom-enum-types.lo -MD -MP -MF $(DEPDIR)/libeom_la-eom-enum-types.Tpo -c -o libeom_la-eom-enum-types.lo `test -f 'eom-enum-types.c' || echo '$(srcdir)/'`eom-enum-types.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeom_la-eom-enum-types.Tpo $(DEPDIR)/libeom_la-eom-enum-types.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='eom-enum-types.c' object='libeom_la-eom-enum-types.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -c -o libeom_la-eom-enum-types.lo `test -f 'eom-enum-types.c' || echo '$(srcdir)/'`eom-enum-types.c + +libeom_la-eom-marshal.lo: eom-marshal.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -MT libeom_la-eom-marshal.lo -MD -MP -MF $(DEPDIR)/libeom_la-eom-marshal.Tpo -c -o libeom_la-eom-marshal.lo `test -f 'eom-marshal.c' || echo '$(srcdir)/'`eom-marshal.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeom_la-eom-marshal.Tpo $(DEPDIR)/libeom_la-eom-marshal.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='eom-marshal.c' object='libeom_la-eom-marshal.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -c -o libeom_la-eom-marshal.lo `test -f 'eom-marshal.c' || echo '$(srcdir)/'`eom-marshal.c + +libeom_la-eom-exif-util.lo: eom-exif-util.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -MT libeom_la-eom-exif-util.lo -MD -MP -MF $(DEPDIR)/libeom_la-eom-exif-util.Tpo -c -o libeom_la-eom-exif-util.lo `test -f 'eom-exif-util.c' || echo '$(srcdir)/'`eom-exif-util.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeom_la-eom-exif-util.Tpo $(DEPDIR)/libeom_la-eom-exif-util.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='eom-exif-util.c' object='libeom_la-eom-exif-util.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -c -o libeom_la-eom-exif-util.lo `test -f 'eom-exif-util.c' || echo '$(srcdir)/'`eom-exif-util.c + +libeom_la-eom-exif-details.lo: eom-exif-details.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -MT libeom_la-eom-exif-details.lo -MD -MP -MF $(DEPDIR)/libeom_la-eom-exif-details.Tpo -c -o libeom_la-eom-exif-details.lo `test -f 'eom-exif-details.c' || echo '$(srcdir)/'`eom-exif-details.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeom_la-eom-exif-details.Tpo $(DEPDIR)/libeom_la-eom-exif-details.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='eom-exif-details.c' object='libeom_la-eom-exif-details.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -c -o libeom_la-eom-exif-details.lo `test -f 'eom-exif-details.c' || echo '$(srcdir)/'`eom-exif-details.c + +libeom_la-eom-python-module.lo: eom-python-module.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -MT libeom_la-eom-python-module.lo -MD -MP -MF $(DEPDIR)/libeom_la-eom-python-module.Tpo -c -o libeom_la-eom-python-module.lo `test -f 'eom-python-module.c' || echo '$(srcdir)/'`eom-python-module.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeom_la-eom-python-module.Tpo $(DEPDIR)/libeom_la-eom-python-module.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='eom-python-module.c' object='libeom_la-eom-python-module.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -c -o libeom_la-eom-python-module.lo `test -f 'eom-python-module.c' || echo '$(srcdir)/'`eom-python-module.c + +libeom_la-eom-python-plugin.lo: eom-python-plugin.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -MT libeom_la-eom-python-plugin.lo -MD -MP -MF $(DEPDIR)/libeom_la-eom-python-plugin.Tpo -c -o libeom_la-eom-python-plugin.lo `test -f 'eom-python-plugin.c' || echo '$(srcdir)/'`eom-python-plugin.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeom_la-eom-python-plugin.Tpo $(DEPDIR)/libeom_la-eom-python-plugin.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='eom-python-plugin.c' object='libeom_la-eom-python-plugin.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libeom_la_CFLAGS) $(CFLAGS) -c -o libeom_la-eom-python-plugin.lo `test -f 'eom-python-plugin.c' || echo '$(srcdir)/'`eom-python-plugin.c + +eom-main.o: main.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(eom_CFLAGS) $(CFLAGS) -MT eom-main.o -MD -MP -MF $(DEPDIR)/eom-main.Tpo -c -o eom-main.o `test -f 'main.c' || echo '$(srcdir)/'`main.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/eom-main.Tpo $(DEPDIR)/eom-main.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='main.c' object='eom-main.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(eom_CFLAGS) $(CFLAGS) -c -o eom-main.o `test -f 'main.c' || echo '$(srcdir)/'`main.c + +eom-main.obj: main.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(eom_CFLAGS) $(CFLAGS) -MT eom-main.obj -MD -MP -MF $(DEPDIR)/eom-main.Tpo -c -o eom-main.obj `if test -f 'main.c'; then $(CYGPATH_W) 'main.c'; else $(CYGPATH_W) '$(srcdir)/main.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/eom-main.Tpo $(DEPDIR)/eom-main.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='main.c' object='eom-main.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(eom_CFLAGS) $(CFLAGS) -c -o eom-main.obj `if test -f 'main.c'; then $(CYGPATH_W) 'main.c'; else $(CYGPATH_W) '$(srcdir)/main.c'; fi` + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs +install-headerDATA: $(header_DATA) + @$(NORMAL_INSTALL) + test -z "$(headerdir)" || $(MKDIR_P) "$(DESTDIR)$(headerdir)" + @list='$(header_DATA)'; test -n "$(headerdir)" || list=; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(headerdir)'"; \ + $(INSTALL_DATA) $$files "$(DESTDIR)$(headerdir)" || exit $$?; \ + done + +uninstall-headerDATA: + @$(NORMAL_UNINSTALL) + @list='$(header_DATA)'; test -n "$(headerdir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + test -n "$$files" || exit 0; \ + echo " ( cd '$(DESTDIR)$(headerdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(headerdir)" && rm -f $$files + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + mkid -fID $$unique +tags: TAGS + +TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + set x; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: CTAGS +CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done + $(MAKE) $(AM_MAKEFLAGS) \ + top_distdir="$(top_distdir)" distdir="$(distdir)" \ + dist-hook +check-am: all-am +check: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) check-am +all-am: Makefile $(LTLIBRARIES) $(PROGRAMS) $(DATA) +installdirs: + for dir in "$(DESTDIR)$(bindir)" "$(DESTDIR)$(headerdir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + `test -z '$(STRIP)' || \ + echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +mostlyclean-generic: + +clean-generic: + -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES) + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." + -test -z "$(BUILT_SOURCES)" || rm -f $(BUILT_SOURCES) +clean: clean-am + +clean-am: clean-binPROGRAMS clean-generic clean-libtool \ + clean-noinstLTLIBRARIES mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-headerDATA + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: install-binPROGRAMS + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-binPROGRAMS uninstall-headerDATA + +.MAKE: all check install install-am install-strip + +.PHONY: CTAGS GTAGS all all-am check check-am clean clean-binPROGRAMS \ + clean-generic clean-libtool clean-noinstLTLIBRARIES ctags \ + dist-hook distclean distclean-compile distclean-generic \ + distclean-libtool distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-binPROGRAMS \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-headerDATA install-html \ + install-html-am install-info install-info-am install-man \ + install-pdf install-pdf-am install-ps install-ps-am \ + install-strip installcheck installcheck-am installdirs \ + maintainer-clean maintainer-clean-generic mostlyclean \ + mostlyclean-compile mostlyclean-generic mostlyclean-libtool \ + pdf pdf-am ps ps-am tags uninstall uninstall-am \ + uninstall-binPROGRAMS uninstall-headerDATA + + +eom-enum-types.h: eom-enum-types.h.template $(INST_H_FILES) $(GLIB_MKENUMS) + $(AM_V_GEN)(cd $(srcdir) && $(GLIB_MKENUMS) --template eom-enum-types.h.template $(INST_H_FILES)) > $@ + +eom-enum-types.c: eom-enum-types.c.template $(INST_H_FILES) $(GLIB_MKENUMS) + $(AM_V_GEN)(cd $(srcdir) && $(GLIB_MKENUMS) --template eom-enum-types.c.template $(INST_H_FILES)) > $@ + +eom-marshal.h: eom-marshal.list $(GLIB_GENMARSHAL) + $(AM_V_GEN)$(GLIB_GENMARSHAL) $< --header --internal --prefix=eom_marshal > $@ + +eom-marshal.c: eom-marshal.list $(GLIB_GENMARSHAL) + $(AM_V_GEN)$(GLIB_GENMARSHAL) $< --body --header --prefix=eom_marshal > $@ + +@[email protected]: eom-application-service.xml +@HAVE_DBUS_TRUE@ $(AM_V_GEN)dbus-binding-tool --prefix=eom_application --mode=glib-server --output=eom-application-service.h $< + +dist-hook: + cd $(distdir); rm -f $(BUILT_SOURCES) + +-include $(top_srcdir)/git.mk + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/eom-application-service.xml b/src/eom-application-service.xml new file mode 100644 index 0000000..6089812 --- /dev/null +++ b/src/eom-application-service.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8" ?> + +<node name="/org/mate/eom/Eom"> + + <interface name="org.mate.eom.Application"> + <annotation name="org.freedesktop.DBus.GLib.CSymbol" value="eom_application"/> + + <method name="OpenWindow"> + <annotation name="org.freedesktop.DBus.GLib.CSymbol" value="eom_application_open_window"/> + <arg type="u" name="timestamp" direction="in"/> + <arg type="y" name="flags" direction="in"/> + </method> + + <method name="OpenUris"> + <annotation name="org.freedesktop.DBus.Glib.CSymbol" value="eom_application_open_uris"/> + <arg type="as" name="uris" direction="in"/> + <arg type="u" name="timestamp" direction="in"/> + <arg type="y" name="flags" direction="in"/> + </method> + + </interface> + +</node> diff --git a/src/eom-application.c b/src/eom-application.c new file mode 100644 index 0000000..2da184d --- /dev/null +++ b/src/eom-application.c @@ -0,0 +1,566 @@ +/* Eye Of Mate - Application Facade + * + * Copyright (C) 2006 The Free Software Foundation + * + * Author: Lucas Rocha <[email protected]> + * + * Based on evince code (shell/ev-application.h) by: + * - Martin Kretzschmar <[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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "eom-image.h" +#include "eom-session.h" +#include "eom-window.h" +#include "eom-application.h" +#include "eom-util.h" + +#ifdef HAVE_DBUS +#include "totem-scrsaver.h" +#endif + +#include <string.h> +#include <glib.h> +#include <glib/gi18n.h> +#include <glib-object.h> +#include <gtk/gtk.h> + +#ifdef HAVE_DBUS +#include "eom-application-service.h" +#include <dbus/dbus-glib-bindings.h> + +#define APPLICATION_SERVICE_NAME "org.mate.eom.ApplicationService" +#endif + +static void eom_application_load_accelerators (void); +static void eom_application_save_accelerators (void); + +#define EOM_APPLICATION_GET_PRIVATE(object) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((object), EOM_TYPE_APPLICATION, EomApplicationPrivate)) + +G_DEFINE_TYPE (EomApplication, eom_application, G_TYPE_OBJECT); + +#ifdef HAVE_DBUS + +/** + * eom_application_register_service: + * @application: An #EomApplication. + * + * Registers #EomApplication<!-- -->'s DBus service, to allow + * remote calls. If the DBus service is already registered, + * or there is any other connection error, returns %FALSE. + * + * Returns: %TRUE if the service was registered succesfully. %FALSE + * otherwise. + **/ +gboolean +eom_application_register_service (EomApplication *application) +{ + static DBusGConnection *connection = NULL; + DBusGProxy *driver_proxy; + GError *err = NULL; + guint request_name_result; + + if (connection) { + g_warning ("Service already registered."); + return FALSE; + } + + connection = dbus_g_bus_get (DBUS_BUS_STARTER, &err); + + if (connection == NULL) { + g_warning ("Service registration failed."); + g_error_free (err); + + return FALSE; + } + + driver_proxy = dbus_g_proxy_new_for_name (connection, + DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS); + + if (!org_freedesktop_DBus_request_name (driver_proxy, + APPLICATION_SERVICE_NAME, + DBUS_NAME_FLAG_DO_NOT_QUEUE, + &request_name_result, &err)) { + g_warning ("Service registration failed."); + g_clear_error (&err); + } + + g_object_unref (driver_proxy); + + if (request_name_result == DBUS_REQUEST_NAME_REPLY_EXISTS) { + return FALSE; + } + + dbus_g_object_type_install_info (EOM_TYPE_APPLICATION, + &dbus_glib_eom_application_object_info); + + dbus_g_connection_register_g_object (connection, + "/org/mate/eom/Eom", + G_OBJECT (application)); + + application->scr_saver = totem_scrsaver_new (); + g_object_set (application->scr_saver, + "reason", _("Running in fullscreen mode"), + NULL); + + return TRUE; +} +#endif /* ENABLE_DBUS */ + +static void +eom_application_class_init (EomApplicationClass *eom_application_class) +{ +} + +static void +eom_application_init (EomApplication *eom_application) +{ + const gchar *dot_dir = eom_util_dot_dir (); + + eom_session_init (eom_application); + + eom_application->toolbars_model = egg_toolbars_model_new (); + + egg_toolbars_model_load_names (eom_application->toolbars_model, + EOM_DATA_DIR "/eom-toolbar.xml"); + + if (G_LIKELY (dot_dir != NULL)) + eom_application->toolbars_file = g_build_filename + (dot_dir, "eom_toolbar.xml", NULL); + + if (!dot_dir || !egg_toolbars_model_load_toolbars (eom_application->toolbars_model, + eom_application->toolbars_file)) { + + egg_toolbars_model_load_toolbars (eom_application->toolbars_model, + EOM_DATA_DIR "/eom-toolbar.xml"); + } + + egg_toolbars_model_set_flags (eom_application->toolbars_model, 0, + EGG_TB_MODEL_NOT_REMOVABLE); + + eom_application_load_accelerators (); +} + +/** + * eom_application_get_instance: + * + * Returns a singleton instance of #EomApplication currently running. + * If not running yet, it will create one. + * + * Returns: a running #EomApplication. + **/ +EomApplication * +eom_application_get_instance (void) +{ + static EomApplication *instance; + + if (!instance) { + instance = EOM_APPLICATION (g_object_new (EOM_TYPE_APPLICATION, NULL)); + } + + return instance; +} + +static EomWindow * +eom_application_get_empty_window (EomApplication *application) +{ + EomWindow *empty_window = NULL; + GList *windows; + GList *l; + + g_return_val_if_fail (EOM_IS_APPLICATION (application), NULL); + + windows = eom_application_get_windows (application); + + for (l = windows; l != NULL; l = l->next) { + EomWindow *window = EOM_WINDOW (l->data); + + if (eom_window_is_empty (window)) { + empty_window = window; + break; + } + } + + g_list_free (windows); + + return empty_window; +} + +/** + * eom_application_open_window: + * @application: An #EomApplication. + * @timestamp: The timestamp of the user interaction which triggered this call + * (see gtk_window_present_with_time()). + * @flags: A set of #EomStartupFlags influencing a new windows' state. + * @error: Return location for a #GError, or NULL to ignore errors. + * + * Opens and presents an empty #EomWindow to the user. If there is + * an empty window already open, this will be used. Otherwise, a + * new one will be instantiated. + * + * Returns: %FALSE if @application is invalid, %TRUE otherwise + **/ +gboolean +eom_application_open_window (EomApplication *application, + guint32 timestamp, + EomStartupFlags flags, + GError **error) +{ + GtkWidget *new_window = NULL; + + new_window = GTK_WIDGET (eom_application_get_empty_window (application)); + + if (new_window == NULL) { + new_window = eom_window_new (flags); + } + + g_return_val_if_fail (EOM_IS_APPLICATION (application), FALSE); + + gtk_window_present_with_time (GTK_WINDOW (new_window), + timestamp); + + return TRUE; +} + +static EomWindow * +eom_application_get_file_window (EomApplication *application, GFile *file) +{ + EomWindow *file_window = NULL; + GList *windows; + GList *l; + + g_return_val_if_fail (file != NULL, NULL); + g_return_val_if_fail (EOM_IS_APPLICATION (application), NULL); + + windows = gtk_window_list_toplevels (); + + for (l = windows; l != NULL; l = l->next) { + if (EOM_IS_WINDOW (l->data)) { + EomWindow *window = EOM_WINDOW (l->data); + + if (!eom_window_is_empty (window)) { + EomImage *image = eom_window_get_image (window); + GFile *window_file; + + window_file = eom_image_get_file (image); + if (g_file_equal (window_file, file)) { + file_window = window; + break; + } + } + } + } + + g_list_free (windows); + + return file_window; +} + +static void +eom_application_show_window (EomWindow *window, gpointer user_data) +{ + gtk_window_present_with_time (GTK_WINDOW (window), + GPOINTER_TO_UINT (user_data)); +} + +/** + * eom_application_open_file_list: + * @application: An #EomApplication. + * @file_list: A list of #GFile<!-- -->s. + * @timestamp: The timestamp of the user interaction which triggered this call + * (see gtk_window_present_with_time()). + * @flags: A set of #EomStartupFlags influencing a new windows' state. + * @error: Return location for a #GError, or NULL to ignore errors. + * + * Opens a list of files in a #EomWindow. If an #EomWindow displaying the first + * image in the list is already open, this will be used. Otherwise, an empty + * #EomWindow is used, either already existing or newly created. + * + * Returns: Currently always %TRUE. + **/ +gboolean +eom_application_open_file_list (EomApplication *application, + GSList *file_list, + guint timestamp, + EomStartupFlags flags, + GError **error) +{ + EomWindow *new_window = NULL; + + if (file_list != NULL) + new_window = eom_application_get_file_window (application, + (GFile *) file_list->data); + + if (new_window != NULL) { + gtk_window_present_with_time (GTK_WINDOW (new_window), + timestamp); + return TRUE; + } + + new_window = eom_application_get_empty_window (application); + + if (new_window == NULL) { + new_window = EOM_WINDOW (eom_window_new (flags)); + } + + g_signal_connect (new_window, + "prepared", + G_CALLBACK (eom_application_show_window), + GUINT_TO_POINTER (timestamp)); + + eom_window_open_file_list (new_window, file_list); + + return TRUE; +} + +/** + * eom_application_open_uri_list: + * @application: An #EomApplication. + * @uri_list: A list of URIs. + * @timestamp: The timestamp of the user interaction which triggered this call + * (see gtk_window_present_with_time()). + * @flags: A set of #EomStartupFlags influencing a new windows' state. + * @error: Return location for a #GError, or NULL to ignore errors. + * + * Opens a list of images, from a list of URIs. See + * eom_application_open_file_list() for details. + * + * Returns: Currently always %TRUE. + **/ +gboolean +eom_application_open_uri_list (EomApplication *application, + GSList *uri_list, + guint timestamp, + EomStartupFlags flags, + GError **error) +{ + GSList *file_list = NULL; + + g_return_val_if_fail (EOM_IS_APPLICATION (application), FALSE); + + file_list = eom_util_string_list_to_file_list (uri_list); + + return eom_application_open_file_list (application, + file_list, + timestamp, + flags, + error); +} + +#ifdef HAVE_DBUS +/** + * eom_application_open_uris: + * @application: an #EomApplication + * @uris: A #GList of URI strings. + * @timestamp: The timestamp of the user interaction which triggered this call + * (see gtk_window_present_with_time()). + * @flags: A set of #EomStartupFlags influencing a new windows' state. + * @error: Return location for a #GError, or NULL to ignore errors. + * + * Opens a list of images, from a list of URI strings. See + * eom_application_open_file_list() for details. + * + * Returns: Currently always %TRUE. + **/ +gboolean +eom_application_open_uris (EomApplication *application, + gchar **uris, + guint timestamp, + EomStartupFlags flags, + GError **error) +{ + GSList *file_list = NULL; + + file_list = eom_util_strings_to_file_list (uris); + + return eom_application_open_file_list (application, file_list, timestamp, + flags, error); +} +#endif + +/** + * eom_application_shutdown: + * @application: An #EomApplication. + * + * Takes care of shutting down the Eye of MATE, and quits. + **/ +void +eom_application_shutdown (EomApplication *application) +{ + g_return_if_fail (EOM_IS_APPLICATION (application)); + + if (application->toolbars_model) { + g_object_unref (application->toolbars_model); + application->toolbars_model = NULL; + + g_free (application->toolbars_file); + application->toolbars_file = NULL; + } + + eom_application_save_accelerators (); + + g_object_unref (application); + + gtk_main_quit (); +} + +/** + * eom_application_get_windows: + * @application: An #EomApplication. + * + * Gets the list of existing #EomApplication<!-- -->s. The windows + * in this list are not individually referenced, you need to keep + * your own references if you want to perform actions that may destroy + * them. + * + * Returns: A new list of #EomWindow<!-- -->s. + **/ +GList * +eom_application_get_windows (EomApplication *application) +{ + GList *l, *toplevels; + GList *windows = NULL; + + g_return_val_if_fail (EOM_IS_APPLICATION (application), NULL); + + toplevels = gtk_window_list_toplevels (); + + for (l = toplevels; l != NULL; l = l->next) { + if (EOM_IS_WINDOW (l->data)) { + windows = g_list_append (windows, l->data); + } + } + + g_list_free (toplevels); + + return windows; +} + +/** + * eom_application_get_toolbars_model: + * @application: An #EomApplication. + * + * Retrieves the #EggToolbarsModel for the toolbar in #EomApplication. + * + * Returns: An #EggToolbarsModel. + **/ +EggToolbarsModel * +eom_application_get_toolbars_model (EomApplication *application) +{ + g_return_val_if_fail (EOM_IS_APPLICATION (application), NULL); + + return application->toolbars_model; +} + +/** + * eom_application_save_toolbars_model: + * @application: An #EomApplication. + * + * Causes the saving of the model of the toolbar in #EomApplication to a file. + **/ +void +eom_application_save_toolbars_model (EomApplication *application) +{ + if (G_LIKELY(application->toolbars_file != NULL)) + egg_toolbars_model_save_toolbars (application->toolbars_model, + application->toolbars_file, + "1.0"); +} + +/** + * eom_application_reset_toolbars_model: + * @app: an #EomApplication + * + * Restores the toolbars model to the defaults. + **/ +void +eom_application_reset_toolbars_model (EomApplication *app) +{ + g_return_if_fail (EOM_IS_APPLICATION (app)); + + g_object_unref (app->toolbars_model); + + app->toolbars_model = egg_toolbars_model_new (); + + egg_toolbars_model_load_names (app->toolbars_model, + EOM_DATA_DIR "/eom-toolbar.xml"); + egg_toolbars_model_load_toolbars (app->toolbars_model, + EOM_DATA_DIR "/eom-toolbar.xml"); + egg_toolbars_model_set_flags (app->toolbars_model, 0, + EGG_TB_MODEL_NOT_REMOVABLE); +} + +#ifdef HAVE_DBUS +/** + * eom_application_screensaver_enable: + * @application: an #EomApplication. + * + * Enables the screensaver. Usually necessary after a call to + * eom_application_screensaver_disable(). + **/ +void +eom_application_screensaver_enable (EomApplication *application) +{ + if (application->scr_saver) + totem_scrsaver_enable (application->scr_saver); +} + +/** + * eom_application_screensaver_disable: + * @application: an #EomApplication. + * + * Disables the screensaver. Useful when the application is in fullscreen or + * similar mode. + **/ +void +eom_application_screensaver_disable (EomApplication *application) +{ + if (application->scr_saver) + totem_scrsaver_disable (application->scr_saver); +} +#endif + +static void +eom_application_load_accelerators (void) +{ + gchar *accelfile = g_build_filename (g_get_home_dir (), + ".mate2", + "accels", + "eom", NULL); + + /* gtk_accel_map_load does nothing if the file does not exist */ + gtk_accel_map_load (accelfile); + g_free (accelfile); +} + +static void +eom_application_save_accelerators (void) +{ + gchar *accelfile = g_build_filename (g_get_home_dir (), + ".mate2", + "accels", + "eom", NULL); + + gtk_accel_map_save (accelfile); + g_free (accelfile); +} diff --git a/src/eom-application.h b/src/eom-application.h new file mode 100644 index 0000000..8f4485a --- /dev/null +++ b/src/eom-application.h @@ -0,0 +1,118 @@ +/* Eye Of Mate - Application Facade + * + * Copyright (C) 2006 The Free Software Foundation + * + * Author: Lucas Rocha <[email protected]> + * + * Based on evince code (shell/ev-application.h) by: + * - Martin Kretzschmar <[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 __EOM_APPLICATION_H__ +#define __EOM_APPLICATION_H__ + +#include "eom-window.h" +#include "egg-toolbars-model.h" + +#ifdef HAVE_DBUS +#include "totem-scrsaver.h" +#endif + +#include <glib.h> +#include <glib-object.h> + +G_BEGIN_DECLS + +typedef struct _EomApplication EomApplication; +typedef struct _EomApplicationClass EomApplicationClass; +typedef struct _EomApplicationPrivate EomApplicationPrivate; + +#define EOM_TYPE_APPLICATION (eom_application_get_type ()) +#define EOM_APPLICATION(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), EOM_TYPE_APPLICATION, EomApplication)) +#define EOM_APPLICATION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), EOM_TYPE_APPLICATION, EomApplicationClass)) +#define EOM_IS_APPLICATION(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), EOM_TYPE_APPLICATION)) +#define EOM_IS_APPLICATION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), EOM_TYPE_APPLICATION)) +#define EOM_APPLICATION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), EOM_TYPE_APPLICATION, EomApplicationClass)) + +#define EOM_APP (eom_application_get_instance ()) + +struct _EomApplication { + GObject base_instance; + + EggToolbarsModel *toolbars_model; + gchar *toolbars_file; +#ifdef HAVE_DBUS + TotemScrsaver *scr_saver; +#endif +}; + +struct _EomApplicationClass { + GObjectClass parent_class; +}; + +GType eom_application_get_type (void) G_GNUC_CONST; + +EomApplication *eom_application_get_instance (void); + +#ifdef HAVE_DBUS +gboolean eom_application_register_service (EomApplication *application); +#endif + +void eom_application_shutdown (EomApplication *application); + +gboolean eom_application_open_window (EomApplication *application, + guint timestamp, + EomStartupFlags flags, + GError **error); + +gboolean eom_application_open_uri_list (EomApplication *application, + GSList *uri_list, + guint timestamp, + EomStartupFlags flags, + GError **error); + +gboolean eom_application_open_file_list (EomApplication *application, + GSList *file_list, + guint timestamp, + EomStartupFlags flags, + GError **error); + +#ifdef HAVE_DBUS +gboolean eom_application_open_uris (EomApplication *application, + gchar **uris, + guint timestamp, + EomStartupFlags flags, + GError **error); +#endif + +GList *eom_application_get_windows (EomApplication *application); + +EggToolbarsModel *eom_application_get_toolbars_model (EomApplication *application); + +void eom_application_save_toolbars_model (EomApplication *application); + +void eom_application_reset_toolbars_model (EomApplication *app); + +#ifdef HAVE_DBUS +void eom_application_screensaver_enable (EomApplication *application); + +void eom_application_screensaver_disable (EomApplication *application); +#endif + +G_END_DECLS + +#endif /* __EOM_APPLICATION_H__ */ diff --git a/src/eom-close-confirmation-dialog.c b/src/eom-close-confirmation-dialog.c new file mode 100644 index 0000000..0798c5e --- /dev/null +++ b/src/eom-close-confirmation-dialog.c @@ -0,0 +1,698 @@ +/* + * eom-close-confirmation-dialog.c + * This file is part of eom + * + * Author: Marcus Carlson <[email protected]> + * + * Based on gedit code (gedit/gedit-close-confirmation.c) by gedit Team + * + * Copyright (C) 2004-2009 MATE Foundation + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <glib/gi18n.h> + +#include "eom-close-confirmation-dialog.h" +#include <eom-window.h> + +/* Properties */ +enum +{ + PROP_0, + PROP_UNSAVED_IMAGES +}; + +/* Mode */ +enum +{ + SINGLE_IMG_MODE, + MULTIPLE_IMGS_MODE +}; + +/* Columns */ +enum +{ + SAVE_COLUMN, + IMAGE_COLUMN, + NAME_COLUMN, + IMG_COLUMN, /* a handy pointer to the image */ + N_COLUMNS +}; + +struct _EomCloseConfirmationDialogPrivate +{ + GList *unsaved_images; + + GList *selected_images; + + GtkTreeModel *list_store; + GtkCellRenderer *toggle_renderer; +}; + + +#define EOM_CLOSE_CONFIRMATION_DIALOG_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), \ + EOM_TYPE_CLOSE_CONFIRMATION_DIALOG, \ + EomCloseConfirmationDialogPrivate)) + +#define GET_MODE(priv) (((priv->unsaved_images != NULL) && \ + (priv->unsaved_images->next == NULL)) ? \ + SINGLE_IMG_MODE : MULTIPLE_IMGS_MODE) + +#define IMAGE_COLUMN_HEIGHT 40 + +G_DEFINE_TYPE(EomCloseConfirmationDialog, eom_close_confirmation_dialog, GTK_TYPE_DIALOG) + +static void set_unsaved_image (EomCloseConfirmationDialog *dlg, + const GList *list); + +static GList *get_selected_imgs (GtkTreeModel *store); + +static GdkPixbuf * +eom_close_confirmation_dialog_get_icon (const gchar *icon_name) +{ + GError *error = NULL; + GtkIconTheme *icon_theme; + GdkPixbuf *pixbuf; + + icon_theme = gtk_icon_theme_get_default (); + + pixbuf = gtk_icon_theme_load_icon (icon_theme, + icon_name, + IMAGE_COLUMN_HEIGHT, + 0, + &error); + + if (!pixbuf) { + g_warning ("Couldn't load icon: %s", error->message); + g_error_free (error); + } + + return pixbuf; +} + +static GdkPixbuf* +get_nothumb_pixbuf (void) +{ + static GOnce nothumb_once = G_ONCE_INIT; + g_once (¬humb_once, (GThreadFunc) eom_close_confirmation_dialog_get_icon, "image-x-generic"); + return GDK_PIXBUF (g_object_ref (nothumb_once.retval)); +} + +/* Since we connect in the costructor we are sure this handler will be called + * before the user ones + */ +static void +response_cb (EomCloseConfirmationDialog *dlg, + gint response_id, + gpointer data) +{ + EomCloseConfirmationDialogPrivate *priv; + + g_return_if_fail (EOM_IS_CLOSE_CONFIRMATION_DIALOG (dlg)); + + priv = dlg->priv; + + if (priv->selected_images != NULL) + g_list_free (priv->selected_images); + + if (response_id == GTK_RESPONSE_YES) + { + if (GET_MODE (priv) == SINGLE_IMG_MODE) + { + priv->selected_images = + g_list_copy (priv->unsaved_images); + } + else + { + g_return_if_fail (priv->list_store); + + priv->selected_images = + get_selected_imgs (priv->list_store); + } + } + else + priv->selected_images = NULL; +} + +static void +add_buttons (EomCloseConfirmationDialog *dlg) +{ + gtk_dialog_add_button (GTK_DIALOG (dlg), + _("Close _without Saving"), + GTK_RESPONSE_NO); + + gtk_dialog_add_button (GTK_DIALOG (dlg), + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL); + + gtk_dialog_add_button (GTK_DIALOG (dlg), + GTK_STOCK_SAVE, + GTK_RESPONSE_YES); + + gtk_dialog_set_default_response (GTK_DIALOG (dlg), + GTK_RESPONSE_YES); +} + +static void +eom_close_confirmation_dialog_init (EomCloseConfirmationDialog *dlg) +{ + AtkObject *atk_obj; + + dlg->priv = EOM_CLOSE_CONFIRMATION_DIALOG_GET_PRIVATE (dlg); + + gtk_container_set_border_width (GTK_CONTAINER (dlg), 5); + gtk_box_set_spacing (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dlg))), 14); + gtk_window_set_resizable (GTK_WINDOW (dlg), FALSE); + gtk_dialog_set_has_separator (GTK_DIALOG (dlg), FALSE); + gtk_window_set_skip_taskbar_hint (GTK_WINDOW (dlg), TRUE); + + gtk_window_set_title (GTK_WINDOW (dlg), ""); + + gtk_window_set_modal (GTK_WINDOW (dlg), TRUE); + gtk_window_set_destroy_with_parent (GTK_WINDOW (dlg), TRUE); + + atk_obj = gtk_widget_get_accessible (GTK_WIDGET (dlg)); + atk_object_set_role (atk_obj, ATK_ROLE_ALERT); + atk_object_set_name (atk_obj, _("Question")); + + g_signal_connect (dlg, + "response", + G_CALLBACK (response_cb), + NULL); +} + +static void +eom_close_confirmation_dialog_finalize (GObject *object) +{ + EomCloseConfirmationDialogPrivate *priv; + + priv = EOM_CLOSE_CONFIRMATION_DIALOG (object)->priv; + + if (priv->unsaved_images != NULL) + g_list_free (priv->unsaved_images); + + if (priv->selected_images != NULL) + g_list_free (priv->selected_images); + + /* Call the parent's destructor */ + G_OBJECT_CLASS (eom_close_confirmation_dialog_parent_class)->finalize (object); +} + +static void +eom_close_confirmation_dialog_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + EomCloseConfirmationDialog *dlg; + + dlg = EOM_CLOSE_CONFIRMATION_DIALOG (object); + + switch (prop_id) + { + case PROP_UNSAVED_IMAGES: + set_unsaved_image (dlg, g_value_get_pointer (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +eom_close_confirmation_dialog_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + EomCloseConfirmationDialogPrivate *priv; + + priv = EOM_CLOSE_CONFIRMATION_DIALOG (object)->priv; + + switch( prop_id ) + { + case PROP_UNSAVED_IMAGES: + g_value_set_pointer (value, priv->unsaved_images); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +eom_close_confirmation_dialog_class_init (EomCloseConfirmationDialogClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->set_property = eom_close_confirmation_dialog_set_property; + gobject_class->get_property = eom_close_confirmation_dialog_get_property; + gobject_class->finalize = eom_close_confirmation_dialog_finalize; + + g_type_class_add_private (klass, sizeof (EomCloseConfirmationDialogPrivate)); + + g_object_class_install_property (gobject_class, + PROP_UNSAVED_IMAGES, + g_param_spec_pointer ("unsaved_images", + "Unsaved Images", + "List of Unsaved Images", + (G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY))); +} + +static GList * +get_selected_imgs (GtkTreeModel *store) +{ + GList *list; + gboolean valid; + GtkTreeIter iter; + + list = NULL; + valid = gtk_tree_model_get_iter_first (store, &iter); + + while (valid) + { + gboolean to_save; + EomImage *img; + + gtk_tree_model_get (store, &iter, + SAVE_COLUMN, &to_save, + IMG_COLUMN, &img, + -1); + if (to_save) + list = g_list_prepend (list, img); + + valid = gtk_tree_model_iter_next (store, &iter); + } + + list = g_list_reverse (list); + + return list; +} + +GList * +eom_close_confirmation_dialog_get_selected_images (EomCloseConfirmationDialog *dlg) +{ + g_return_val_if_fail (EOM_IS_CLOSE_CONFIRMATION_DIALOG (dlg), NULL); + + return g_list_copy (dlg->priv->selected_images); +} + +GtkWidget * +eom_close_confirmation_dialog_new (GtkWindow *parent, + GList *unsaved_images) +{ + GtkWidget *dlg; + GtkWindowGroup *wg; + + g_return_val_if_fail (unsaved_images != NULL, NULL); + + dlg = GTK_WIDGET (g_object_new (EOM_TYPE_CLOSE_CONFIRMATION_DIALOG, + "unsaved_images", unsaved_images, + NULL)); + g_return_val_if_fail (dlg != NULL, NULL); + + if (parent != NULL) + { + wg = gtk_window_get_group (parent); + + /* gtk_window_get_group returns a default group when the given + * window doesn't have a group. Explicitly add the window to + * the group here to make sure it's actually in the returned + * group. It makes no difference if it is already. */ + gtk_window_group_add_window (wg, parent); + gtk_window_group_add_window (wg, GTK_WINDOW (dlg)); + + gtk_window_set_transient_for (GTK_WINDOW (dlg), parent); + } + + return dlg; +} + +GtkWidget * +eom_close_confirmation_dialog_new_single (GtkWindow *parent, + EomImage *image) +{ + GtkWidget *dlg; + GList *unsaved_images; + g_return_val_if_fail (image != NULL, NULL); + + unsaved_images = g_list_prepend (NULL, image); + + dlg = eom_close_confirmation_dialog_new (parent, + unsaved_images); + + g_list_free (unsaved_images); + + return dlg; +} + +static gchar * +get_text_secondary_label (EomImage *image) +{ + gchar *secondary_msg; + + secondary_msg = g_strdup (_("If you don't save, your changes will be lost.")); + + return secondary_msg; +} + +static void +build_single_img_dialog (EomCloseConfirmationDialog *dlg) +{ + GtkWidget *hbox; + GtkWidget *vbox; + GtkWidget *primary_label; + GtkWidget *secondary_label; + GtkWidget *image; + EomImage *img; + const gchar *image_name; + gchar *str; + gchar *markup_str; + + g_return_if_fail (dlg->priv->unsaved_images->data != NULL); + img = EOM_IMAGE (dlg->priv->unsaved_images->data); + + /* Image */ + image = gtk_image_new_from_stock (GTK_STOCK_DIALOG_WARNING, + GTK_ICON_SIZE_DIALOG); + gtk_misc_set_alignment (GTK_MISC (image), 0.5, 0.0); + + /* Primary label */ + primary_label = gtk_label_new (NULL); + gtk_label_set_line_wrap (GTK_LABEL (primary_label), TRUE); + gtk_label_set_use_markup (GTK_LABEL (primary_label), TRUE); + gtk_misc_set_alignment (GTK_MISC (primary_label), 0.0, 0.5); + gtk_label_set_selectable (GTK_LABEL (primary_label), TRUE); + + image_name = eom_image_get_caption (img); + + str = g_markup_printf_escaped (_("Save changes to image \"%s\" before closing?"), + image_name); + markup_str = g_strconcat ("<span weight=\"bold\" size=\"larger\">", str, "</span>", NULL); + g_free (str); + + gtk_label_set_markup (GTK_LABEL (primary_label), markup_str); + g_free (markup_str); + + /* Secondary label */ + str = get_text_secondary_label (img); + + secondary_label = gtk_label_new (str); + g_free (str); + gtk_label_set_line_wrap (GTK_LABEL (secondary_label), TRUE); + gtk_misc_set_alignment (GTK_MISC (secondary_label), 0.0, 0.5); + gtk_label_set_selectable (GTK_LABEL (secondary_label), TRUE); + + hbox = gtk_hbox_new (FALSE, 12); + gtk_container_set_border_width (GTK_CONTAINER (hbox), 5); + + gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0); + + vbox = gtk_vbox_new (FALSE, 12); + + gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, FALSE, 0); + + gtk_box_pack_start (GTK_BOX (vbox), primary_label, FALSE, FALSE, 0); + + gtk_box_pack_start (GTK_BOX (vbox), secondary_label, FALSE, FALSE, 0); + + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dlg))), + hbox, + FALSE, + FALSE, + 0); + + add_buttons (dlg); + + gtk_widget_show_all (hbox); +} + +static void +populate_model (GtkTreeModel *store, GList *imgs) +{ + GtkTreeIter iter; + + while (imgs != NULL) + { + EomImage *img; + const gchar *name; + GdkPixbuf *buf = NULL; + GdkPixbuf *buf_scaled = NULL; + int width; + double ratio; + + img = EOM_IMAGE (imgs->data); + + name = eom_image_get_caption (img); + buf = eom_image_get_thumbnail (img); + + if (buf) { + ratio = IMAGE_COLUMN_HEIGHT / (double) gdk_pixbuf_get_height (buf); + width = (int) (gdk_pixbuf_get_width (buf) * ratio); + buf_scaled = gdk_pixbuf_scale_simple (buf, width, IMAGE_COLUMN_HEIGHT, GDK_INTERP_BILINEAR); + } else + buf_scaled = get_nothumb_pixbuf (); + + gtk_list_store_append (GTK_LIST_STORE (store), &iter); + gtk_list_store_set (GTK_LIST_STORE (store), &iter, + SAVE_COLUMN, TRUE, + IMAGE_COLUMN, buf_scaled, + NAME_COLUMN, name, + IMG_COLUMN, img, + -1); + + imgs = g_list_next (imgs); + g_object_unref (buf_scaled); + } +} + +static void +save_toggled (GtkCellRendererToggle *renderer, gchar *path_str, GtkTreeModel *store) +{ + GtkTreePath *path = gtk_tree_path_new_from_string (path_str); + GtkTreeIter iter; + gboolean active; + + gtk_tree_model_get_iter (store, &iter, path); + gtk_tree_model_get (store, &iter, SAVE_COLUMN, &active, -1); + + active ^= 1; + + gtk_list_store_set (GTK_LIST_STORE (store), &iter, + SAVE_COLUMN, active, -1); + + gtk_tree_path_free (path); +} + +static GtkWidget * +create_treeview (EomCloseConfirmationDialogPrivate *priv) +{ + GtkListStore *store; + GtkWidget *treeview; + GtkCellRenderer *renderer; + GtkTreeViewColumn *column; + + treeview = gtk_tree_view_new (); + gtk_widget_set_size_request (treeview, 260, 120); + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (treeview), FALSE); + gtk_tree_view_set_enable_search (GTK_TREE_VIEW (treeview), FALSE); + + /* Create and populate the model */ + store = gtk_list_store_new (N_COLUMNS, G_TYPE_BOOLEAN, GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_POINTER); + populate_model (GTK_TREE_MODEL (store), priv->unsaved_images); + + /* Set model to the treeview */ + gtk_tree_view_set_model (GTK_TREE_VIEW (treeview), GTK_TREE_MODEL (store)); + g_object_unref (store); + + priv->list_store = GTK_TREE_MODEL (store); + + /* Add columns */ + priv->toggle_renderer = renderer = gtk_cell_renderer_toggle_new (); + g_signal_connect (renderer, "toggled", + G_CALLBACK (save_toggled), store); + + column = gtk_tree_view_column_new_with_attributes ("Save?", + renderer, + "active", + SAVE_COLUMN, + NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column); + + renderer = gtk_cell_renderer_pixbuf_new (); + + column = gtk_tree_view_column_new_with_attributes ("Image", + renderer, + "pixbuf", + IMAGE_COLUMN, + NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column); + + + + renderer = gtk_cell_renderer_text_new (); + column = gtk_tree_view_column_new_with_attributes ("Name", + renderer, + "text", + NAME_COLUMN, + NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column); + + return treeview; +} + +static void +build_multiple_imgs_dialog (EomCloseConfirmationDialog *dlg) +{ + EomCloseConfirmationDialogPrivate *priv; + GtkWidget *hbox; + GtkWidget *image; + GtkWidget *vbox; + GtkWidget *primary_label; + GtkWidget *vbox2; + GtkWidget *select_label; + GtkWidget *scrolledwindow; + GtkWidget *treeview; + GtkWidget *secondary_label; + gchar *str; + gchar *markup_str; + + priv = dlg->priv; + + hbox = gtk_hbox_new (FALSE, 12); + gtk_container_set_border_width (GTK_CONTAINER (hbox), 5); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dlg))), + hbox, TRUE, TRUE, 0); + + /* Image */ + image = gtk_image_new_from_stock (GTK_STOCK_DIALOG_WARNING, + GTK_ICON_SIZE_DIALOG); + gtk_misc_set_alignment (GTK_MISC (image), 0.5, 0.0); + gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0); + + vbox = gtk_vbox_new (FALSE, 12); + gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0); + + /* Primary label */ + primary_label = gtk_label_new (NULL); + gtk_label_set_line_wrap (GTK_LABEL (primary_label), TRUE); + gtk_label_set_use_markup (GTK_LABEL (primary_label), TRUE); + gtk_misc_set_alignment (GTK_MISC (primary_label), 0.0, 0.5); + gtk_label_set_selectable (GTK_LABEL (primary_label), TRUE); + + str = g_strdup_printf ( + ngettext ("There is %d image with unsaved changes. " + "Save changes before closing?", + "There are %d images with unsaved changes. " + "Save changes before closing?", + g_list_length (priv->unsaved_images)), + g_list_length (priv->unsaved_images)); + + markup_str = g_strconcat ("<span weight=\"bold\" size=\"larger\">", str, "</span>", NULL); + g_free (str); + + gtk_label_set_markup (GTK_LABEL (primary_label), markup_str); + g_free (markup_str); + gtk_box_pack_start (GTK_BOX (vbox), primary_label, FALSE, FALSE, 0); + + vbox2 = gtk_vbox_new (FALSE, 8); + gtk_box_pack_start (GTK_BOX (vbox), vbox2, FALSE, FALSE, 0); + + select_label = gtk_label_new_with_mnemonic (_("S_elect the images you want to save:")); + + gtk_box_pack_start (GTK_BOX (vbox2), select_label, FALSE, FALSE, 0); + gtk_label_set_line_wrap (GTK_LABEL (select_label), TRUE); + gtk_misc_set_alignment (GTK_MISC (select_label), 0.0, 0.5); + + scrolledwindow = gtk_scrolled_window_new (NULL, NULL); + gtk_box_pack_start (GTK_BOX (vbox2), scrolledwindow, TRUE, TRUE, 0); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwindow), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolledwindow), + GTK_SHADOW_IN); + + treeview = create_treeview (priv); + gtk_container_add (GTK_CONTAINER (scrolledwindow), treeview); + + /* Secondary label */ + secondary_label = gtk_label_new (_("If you don't save, " + "all your changes will be lost.")); + + gtk_box_pack_start (GTK_BOX (vbox2), secondary_label, FALSE, FALSE, 0); + gtk_label_set_line_wrap (GTK_LABEL (secondary_label), TRUE); + gtk_misc_set_alignment (GTK_MISC (secondary_label), 0, 0.5); + gtk_label_set_selectable (GTK_LABEL (secondary_label), TRUE); + + gtk_label_set_mnemonic_widget (GTK_LABEL (select_label), treeview); + + add_buttons (dlg); + + gtk_widget_show_all (hbox); +} + +static void +set_unsaved_image (EomCloseConfirmationDialog *dlg, + const GList *list) +{ + EomCloseConfirmationDialogPrivate *priv; + + g_return_if_fail (list != NULL); + + priv = dlg->priv; + g_return_if_fail (priv->unsaved_images == NULL); + + priv->unsaved_images = g_list_copy ((GList *)list); + + if (GET_MODE (priv) == SINGLE_IMG_MODE) + { + build_single_img_dialog (dlg); + } + else + { + build_multiple_imgs_dialog (dlg); + } +} + +const GList * +eom_close_confirmation_dialog_get_unsaved_images (EomCloseConfirmationDialog *dlg) +{ + g_return_val_if_fail (EOM_IS_CLOSE_CONFIRMATION_DIALOG (dlg), NULL); + + return dlg->priv->unsaved_images; +} + +void +eom_close_confirmation_dialog_set_sensitive (EomCloseConfirmationDialog *dlg, + gboolean value) +{ + GtkWidget *action_area; + + g_return_if_fail (EOM_IS_CLOSE_CONFIRMATION_DIALOG (dlg)); + + action_area = gtk_dialog_get_action_area (GTK_DIALOG (dlg)); + gtk_widget_set_sensitive (action_area, value); + + if (dlg->priv->toggle_renderer) + gtk_cell_renderer_toggle_set_activatable (GTK_CELL_RENDERER_TOGGLE (dlg->priv->toggle_renderer), value); +} diff --git a/src/eom-close-confirmation-dialog.h b/src/eom-close-confirmation-dialog.h new file mode 100644 index 0000000..3c5f44e --- /dev/null +++ b/src/eom-close-confirmation-dialog.h @@ -0,0 +1,79 @@ +/* + * eom-close-confirmation-dialog.h + * This file is part of eom + * + * Author: Marcus Carlson <[email protected]> + * + * Based on gedit code (gedit/gedit-close-confirmation.h) by gedit Team + * + * Copyright (C) 2004-2009 MATE Foundation + * + * 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 __EOM_CLOSE_CONFIRMATION_DIALOG_H__ +#define __EOM_CLOSE_CONFIRMATION_DIALOG_H__ + +#include <glib.h> +#include <gtk/gtk.h> + +#include <eom-image.h> + +#define EOM_TYPE_CLOSE_CONFIRMATION_DIALOG (eom_close_confirmation_dialog_get_type ()) +#define EOM_CLOSE_CONFIRMATION_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EOM_TYPE_CLOSE_CONFIRMATION_DIALOG, EomCloseConfirmationDialog)) +#define EOM_CLOSE_CONFIRMATION_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EOM_TYPE_CLOSE_CONFIRMATION_DIALOG, EomCloseConfirmationDialogClass)) +#define EOM_IS_CLOSE_CONFIRMATION_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EOM_TYPE_CLOSE_CONFIRMATION_DIALOG)) +#define EOM_IS_CLOSE_CONFIRMATION_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EOM_TYPE_CLOSE_CONFIRMATION_DIALOG)) +#define EOM_CLOSE_CONFIRMATION_DIALOG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj),EOM_TYPE_CLOSE_CONFIRMATION_DIALOG, EomCloseConfirmationDialogClass)) + +typedef struct _EomCloseConfirmationDialog EomCloseConfirmationDialog; +typedef struct _EomCloseConfirmationDialogClass EomCloseConfirmationDialogClass; +typedef struct _EomCloseConfirmationDialogPrivate EomCloseConfirmationDialogPrivate; + +struct _EomCloseConfirmationDialog +{ + GtkDialog parent; + + /*< private > */ + EomCloseConfirmationDialogPrivate *priv; +}; + +struct _EomCloseConfirmationDialogClass +{ + GtkDialogClass parent_class; +}; + +G_GNUC_INTERNAL +GType eom_close_confirmation_dialog_get_type (void) G_GNUC_CONST; + +G_GNUC_INTERNAL +GtkWidget *eom_close_confirmation_dialog_new (GtkWindow *parent, + GList *unsaved_documents); +G_GNUC_INTERNAL +GtkWidget *eom_close_confirmation_dialog_new_single (GtkWindow *parent, + EomImage *image); + +G_GNUC_INTERNAL +const GList *eom_close_confirmation_dialog_get_unsaved_images (EomCloseConfirmationDialog *dlg); + +G_GNUC_INTERNAL +GList *eom_close_confirmation_dialog_get_selected_images (EomCloseConfirmationDialog *dlg); + +G_GNUC_INTERNAL +void eom_close_confirmation_dialog_set_sensitive (EomCloseConfirmationDialog *dlg, gboolean value); + +#endif /* __EOM_CLOSE_CONFIRMATION_DIALOG_H__ */ + diff --git a/src/eom-config-keys.h b/src/eom-config-keys.h new file mode 100644 index 0000000..7d7074c --- /dev/null +++ b/src/eom-config-keys.h @@ -0,0 +1,63 @@ +/* Eye Of Mate - MateConf Keys Macros + * + * Copyright (C) 2000-2006 The Free Software Foundation + * + * Author: Lucas Rocha <[email protected]> + * + * Based on code by: + * - Federico Mena-Quintero <[email protected]> + * - Jens Finke <[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 __EOM_CONFIG_KEYS_H__ +#define __EOM_CONFIG_KEYS_H__ + +#define EOM_CONF_DIR "/apps/eom" + +#define EOM_CONF_DESKTOP_WALLPAPER "/desktop/mate/background/picture_filename" +#define EOM_CONF_DESKTOP_CAN_SAVE "/desktop/mate/lockdown/disable_save_to_disk" +#define EOM_CONF_DESKTOP_CAN_PRINT "/desktop/mate/lockdown/disable_printing" +#define EOM_CONF_DESKTOP_CAN_SETUP_PAGE "/desktop/mate/lockdown/disable_print_setup" + +#define EOM_CONF_VIEW_BACKGROUND_COLOR "/apps/eom/view/background-color" +#define EOM_CONF_VIEW_INTERPOLATE "/apps/eom/view/interpolate" +#define EOM_CONF_VIEW_EXTRAPOLATE "/apps/eom/view/extrapolate" +#define EOM_CONF_VIEW_SCROLL_WHEEL_ZOOM "/apps/eom/view/scroll_wheel_zoom" +#define EOM_CONF_VIEW_ZOOM_MULTIPLIER "/apps/eom/view/zoom_multiplier" +#define EOM_CONF_VIEW_AUTOROTATE "/apps/eom/view/autorotate" +#define EOM_CONF_VIEW_TRANSPARENCY "/apps/eom/view/transparency" +#define EOM_CONF_VIEW_TRANS_COLOR "/apps/eom/view/trans_color" +#define EOM_CONF_VIEW_USE_BG_COLOR "/apps/eom/view/use-background-color" + +#define EOM_CONF_FULLSCREEN_LOOP "/apps/eom/full_screen/loop" +#define EOM_CONF_FULLSCREEN_UPSCALE "/apps/eom/full_screen/upscale" +#define EOM_CONF_FULLSCREEN_SECONDS "/apps/eom/full_screen/seconds" + +#define EOM_CONF_UI_TOOLBAR "/apps/eom/ui/toolbar" +#define EOM_CONF_UI_STATUSBAR "/apps/eom/ui/statusbar" +#define EOM_CONF_UI_IMAGE_COLLECTION "/apps/eom/ui/image_collection" +#define EOM_CONF_UI_IMAGE_COLLECTION_POSITION "/apps/eom/ui/image_collection_position" +#define EOM_CONF_UI_IMAGE_COLLECTION_RESIZABLE "/apps/eom/ui/image_collection_resizable" +#define EOM_CONF_UI_SIDEBAR "/apps/eom/ui/sidebar" +#define EOM_CONF_UI_SCROLL_BUTTONS "/apps/eom/ui/scroll_buttons" +#define EOM_CONF_UI_DISABLE_TRASH_CONFIRMATION "/apps/eom/ui/disable_trash_confirmation" +#define EOM_CONF_UI_FILECHOOSER_XDG_FALLBACK "/apps/eom/ui/filechooser_xdg_fallback" +#define EOM_CONF_UI_PROPSDIALOG_NETBOOK_MODE "/apps/eom/ui/propsdialog_netbook_mode" + +#define EOM_CONF_PLUGINS_ACTIVE_PLUGINS "/apps/eom/plugins/active_plugins" + +#endif /* __EOM_CONFIG_KEYS_H__ */ diff --git a/src/eom-debug.c b/src/eom-debug.c new file mode 100644 index 0000000..6d373af --- /dev/null +++ b/src/eom-debug.c @@ -0,0 +1,160 @@ +/* Eye Of Mate - Debugging + * + * Copyright (C) 2007 The Free Software Foundation + * + * Author: Lucas Rocha <[email protected]> + * + * Based on gedit code (gedit/gedit-debug.h) by: + * - Alex Roberts <[email protected]> + * - Evan Lawrence <[email protected]> + * - Chema Celorio <[email protected]> + * - Paolo Maggi <[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. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> + +#include "eom-debug.h" + +#define ENABLE_PROFILING + +#ifdef ENABLE_PROFILING +static GTimer *timer = NULL; +static gdouble last = 0.0; +#endif + +static EomDebugSection debug = EOM_NO_DEBUG; + +void +eom_debug_init (void) +{ + if (g_getenv ("EOM_DEBUG") != NULL) + { + /* Enable all debugging */ + debug = ~EOM_NO_DEBUG; + goto out; + } + + if (g_getenv ("EOM_DEBUG_WINDOW") != NULL) + debug = debug | EOM_DEBUG_WINDOW; + + if (g_getenv ("EOM_DEBUG_VIEW") != NULL) + debug = debug | EOM_DEBUG_VIEW; + + if (g_getenv ("EOM_DEBUG_JOBS") != NULL) + debug = debug | EOM_DEBUG_JOBS; + + if (g_getenv ("EOM_DEBUG_THUMBNAIL") != NULL) + debug = debug | EOM_DEBUG_THUMBNAIL; + + if (g_getenv ("EOM_DEBUG_IMAGE_DATA") != NULL) + debug = debug | EOM_DEBUG_IMAGE_DATA; + + if (g_getenv ("EOM_DEBUG_IMAGE_LOAD") != NULL) + debug = debug | EOM_DEBUG_IMAGE_LOAD; + + if (g_getenv ("EOM_DEBUG_IMAGE_SAVE") != NULL) + debug = debug | EOM_DEBUG_IMAGE_SAVE; + + if (g_getenv ("EOM_DEBUG_LIST_STORE") != NULL) + debug = debug | EOM_DEBUG_LIST_STORE; + + if (g_getenv ("EOM_DEBUG_PREFERENCES") != NULL) + debug = debug | EOM_DEBUG_PREFERENCES; + + if (g_getenv ("EOM_DEBUG_PRINTING") != NULL) + debug = debug | EOM_DEBUG_PRINTING; + + if (g_getenv ("EOM_DEBUG_LCMS") != NULL) + debug = debug | EOM_DEBUG_LCMS; + + if (g_getenv ("EOM_DEBUG_PLUGINS") != NULL) + debug = debug | EOM_DEBUG_PLUGINS; + +out: + +#ifdef ENABLE_PROFILING + if (debug != EOM_NO_DEBUG) + timer = g_timer_new (); +#endif + return; +} + +void +eom_debug_message (EomDebugSection section, + const gchar *file, + gint line, + const gchar *function, + const gchar *format, ...) +{ + if (G_UNLIKELY (debug & section)) + { +#ifdef ENABLE_PROFILING + gdouble seconds; +#endif + + va_list args; + gchar *msg; + + g_return_if_fail (format != NULL); + + va_start (args, format); + msg = g_strdup_vprintf (format, args); + va_end (args); + +#ifdef ENABLE_PROFILING + g_return_if_fail (timer != NULL); + + seconds = g_timer_elapsed (timer, NULL); + g_print ("[%f (%f)] %s:%d (%s) %s\n", + seconds, seconds - last, file, line, function, msg); + last = seconds; +#else + g_print ("%s:%d (%s) %s\n", file, line, function, msg); +#endif + + fflush (stdout); + + g_free (msg); + } +} + +void eom_debug (EomDebugSection section, + const gchar *file, + gint line, + const gchar *function) +{ + if (G_UNLIKELY (debug & section)) + { +#ifdef ENABLE_PROFILING + gdouble seconds; + + g_return_if_fail (timer != NULL); + + seconds = g_timer_elapsed (timer, NULL); + g_print ("[%f (%f)] %s:%d (%s)\n", + seconds, seconds - last, file, line, function); + last = seconds; +#else + g_print ("%s:%d (%s)\n", file, line, function); +#endif + fflush (stdout); + } +} diff --git a/src/eom-debug.h b/src/eom-debug.h new file mode 100644 index 0000000..bde758c --- /dev/null +++ b/src/eom-debug.h @@ -0,0 +1,75 @@ +/* Eye Of Mate - Debugging + * + * Copyright (C) 2007 The Free Software Foundation + * + * Author: Lucas Rocha <[email protected]> + * + * Based on gedit code (gedit/gedit-debug.h) by: + * - Alex Roberts <[email protected]> + * - Evan Lawrence <[email protected]> + * - Chema Celorio <[email protected]> + * - Paolo Maggi <[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 __EOM_DEBUG_H__ +#define __EOM_DEBUG_H__ + +#include <glib.h> + +typedef enum { + EOM_NO_DEBUG = 0, + EOM_DEBUG_WINDOW = 1 << 0, + EOM_DEBUG_VIEW = 1 << 1, + EOM_DEBUG_JOBS = 1 << 2, + EOM_DEBUG_THUMBNAIL = 1 << 3, + EOM_DEBUG_IMAGE_DATA = 1 << 4, + EOM_DEBUG_IMAGE_LOAD = 1 << 5, + EOM_DEBUG_IMAGE_SAVE = 1 << 6, + EOM_DEBUG_LIST_STORE = 1 << 7, + EOM_DEBUG_PREFERENCES = 1 << 8, + EOM_DEBUG_PRINTING = 1 << 9, + EOM_DEBUG_LCMS = 1 << 10, + EOM_DEBUG_PLUGINS = 1 << 11 +} EomDebugSection; + +#define DEBUG_WINDOW EOM_DEBUG_WINDOW, __FILE__, __LINE__, G_STRFUNC +#define DEBUG_VIEW EOM_DEBUG_VIEW, __FILE__, __LINE__, G_STRFUNC +#define DEBUG_JOBS EOM_DEBUG_JOBS, __FILE__, __LINE__, G_STRFUNC +#define DEBUG_THUMBNAIL EOM_DEBUG_THUMBNAIL, __FILE__, __LINE__, G_STRFUNC +#define DEBUG_IMAGE_DATA EOM_DEBUG_IMAGE_DATA, __FILE__, __LINE__, G_STRFUNC +#define DEBUG_IMAGE_LOAD EOM_DEBUG_IMAGE_LOAD, __FILE__, __LINE__, G_STRFUNC +#define DEBUG_IMAGE_SAVE EOM_DEBUG_IMAGE_SAVE, __FILE__, __LINE__, G_STRFUNC +#define DEBUG_LIST_STORE EOM_DEBUG_LIST_STORE, __FILE__, __LINE__, G_STRFUNC +#define DEBUG_PREFERENCES EOM_DEBUG_PREFERENCES, __FILE__, __LINE__, G_STRFUNC +#define DEBUG_PRINTING EOM_DEBUG_PRINTING, __FILE__, __LINE__, G_STRFUNC +#define DEBUG_LCMS EOM_DEBUG_LCMS, __FILE__, __LINE__, G_STRFUNC +#define DEBUG_PLUGINS EOM_DEBUG_PLUGINS, __FILE__, __LINE__, G_STRFUNC + +void eom_debug_init (void); + +void eom_debug (EomDebugSection section, + const gchar *file, + gint line, + const gchar *function); + +void eom_debug_message (EomDebugSection section, + const gchar *file, + gint line, + const gchar *function, + const gchar *format, ...) G_GNUC_PRINTF(5, 6); + +#endif /* __EOM_DEBUG_H__ */ diff --git a/src/eom-dialog.c b/src/eom-dialog.c new file mode 100644 index 0000000..fac74a9 --- /dev/null +++ b/src/eom-dialog.c @@ -0,0 +1,237 @@ +/* Eye Of Mate - Image Dialog + * + * Copyright (C) 2006 The Free Software Foundation + * + * Author: Lucas Rocha <[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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "eom-dialog.h" + +#include <glib.h> +#include <glib-object.h> +#include <gtk/gtk.h> + +#define EOM_DIALOG_GET_PRIVATE(object) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((object), EOM_TYPE_DIALOG, EomDialogPrivate)) + +G_DEFINE_TYPE (EomDialog, eom_dialog, G_TYPE_OBJECT); + +enum { + PROP_0, + PROP_PARENT_WINDOW, +}; + +struct _EomDialogPrivate { + GtkWidget *dlg; + GtkBuilder *xml; + GtkWindow *parent; +}; + +static void +eom_dialog_construct_impl (EomDialog *dialog, + const gchar *glade_file, + const gchar *dlg_node) +{ + EomDialogPrivate *priv; + gchar *filename; + + g_return_if_fail (dialog != NULL); + g_return_if_fail (EOM_IS_DIALOG (dialog)); + + priv = dialog->priv; + + filename = g_build_filename (EOM_DATA_DIR, glade_file, NULL); + + priv->xml = gtk_builder_new (); + gtk_builder_set_translation_domain (priv->xml, GETTEXT_PACKAGE); + g_assert (gtk_builder_add_from_file (priv->xml, filename, NULL)); + + g_free (filename); + + priv->dlg = GTK_WIDGET (gtk_builder_get_object (priv->xml, dlg_node)); + + if (priv->parent != NULL) { + gtk_window_set_transient_for (GTK_WINDOW (priv->dlg), + priv->parent); + } +} + +static void +eom_dialog_show_impl (EomDialog *dialog) +{ + g_return_if_fail (dialog != NULL); + g_return_if_fail (EOM_IS_DIALOG (dialog)); + + gtk_window_present (GTK_WINDOW (dialog->priv->dlg)); +} + +static void +eom_dialog_hide_impl (EomDialog *dialog) +{ + g_return_if_fail (dialog != NULL); + g_return_if_fail (EOM_IS_DIALOG (dialog)); + + gtk_widget_hide (dialog->priv->dlg); +} + +static void +eom_dialog_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + EomDialog *dialog = EOM_DIALOG (object); + + switch (prop_id) { + case PROP_PARENT_WINDOW: + dialog->priv->parent = g_value_get_object (value); + break; + } +} + +static void +eom_dialog_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + EomDialog *dialog = EOM_DIALOG (object); + + switch (prop_id) { + case PROP_PARENT_WINDOW: + g_value_set_object (value, dialog->priv->parent); + break; + } +} + +static void +eom_dialog_dispose (GObject *object) +{ + EomDialog *dialog; + EomDialogPrivate *priv; + + g_return_if_fail (object != NULL); + g_return_if_fail (EOM_IS_DIALOG (object)); + + dialog = EOM_DIALOG (object); + priv = dialog->priv; + + if (priv->dlg) { + gtk_widget_destroy (priv->dlg); + priv->dlg = NULL; + } + + if (priv->xml) { + g_object_unref (priv->xml); + priv->xml = NULL; + } + + G_OBJECT_CLASS (eom_dialog_parent_class)->dispose (object); +} + +static void +eom_dialog_class_init (EomDialogClass *class) +{ + GObjectClass *g_object_class = (GObjectClass *) class; + + g_object_class->dispose = eom_dialog_dispose; + g_object_class->set_property = eom_dialog_set_property; + g_object_class->get_property = eom_dialog_get_property; + + class->construct = eom_dialog_construct_impl; + class->show = eom_dialog_show_impl; + class->hide = eom_dialog_hide_impl; + + g_object_class_install_property (g_object_class, + PROP_PARENT_WINDOW, + g_param_spec_object ("parent-window", + "Parent window", + "Parent window", + GTK_TYPE_WINDOW, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | + G_PARAM_STATIC_BLURB)); + + g_type_class_add_private (g_object_class, sizeof (EomDialogPrivate)); +} + +static void +eom_dialog_init (EomDialog *dialog) +{ + dialog->priv = EOM_DIALOG_GET_PRIVATE (dialog); + + dialog->priv->dlg = NULL; + dialog->priv->xml = NULL; + dialog->priv->parent = NULL; +} + +void +eom_dialog_construct (EomDialog *dialog, + const gchar *glade_file, + const gchar *dlg_node) +{ + EomDialogClass *klass = EOM_DIALOG_GET_CLASS (dialog); + klass->construct (dialog, glade_file, dlg_node); +} + +void +eom_dialog_show (EomDialog *dialog) +{ + EomDialogClass *klass = EOM_DIALOG_GET_CLASS (dialog); + klass->show (dialog); +} + +void +eom_dialog_hide (EomDialog *dialog) +{ + EomDialogClass *klass = EOM_DIALOG_GET_CLASS (dialog); + klass->hide (dialog); +} + +void +eom_dialog_get_controls (EomDialog *dialog, + const gchar *property_id, + ...) +{ + EomDialogPrivate *priv; + GtkWidget **wptr; + va_list varargs; + + g_return_if_fail (dialog != NULL); + g_return_if_fail (EOM_IS_DIALOG (dialog)); + + priv = dialog->priv; + + va_start (varargs, property_id); + + while (property_id != NULL) + { + wptr = va_arg (varargs, GtkWidget **); + *wptr = GTK_WIDGET (gtk_builder_get_object (priv->xml, + property_id)); + + property_id = va_arg (varargs, const gchar *); + } + + va_end (varargs); +} diff --git a/src/eom-dialog.h b/src/eom-dialog.h new file mode 100644 index 0000000..3022dee --- /dev/null +++ b/src/eom-dialog.h @@ -0,0 +1,76 @@ +/* Eye Of Mate - EOM Dialog + * + * Copyright (C) 2006 The Free Software Foundation + * + * Author: Lucas Rocha <[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 __EOM_DIALOG_H__ +#define __EOM_DIALOG_H__ + +#include <glib.h> +#include <glib-object.h> +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +typedef struct _EomDialog EomDialog; +typedef struct _EomDialogClass EomDialogClass; +typedef struct _EomDialogPrivate EomDialogPrivate; + +#define EOM_TYPE_DIALOG (eom_dialog_get_type ()) +#define EOM_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), EOM_TYPE_DIALOG, EomDialog)) +#define EOM_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), EOM_TYPE_DIALOG, EomDialogClass)) +#define EOM_IS_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), EOM_TYPE_DIALOG)) +#define EOM_IS_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), EOM_TYPE_DIALOG)) +#define EOM_DIALOG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), EOM_TYPE_DIALOG, EomDialogClass)) + +struct _EomDialog { + GObject object; + + EomDialogPrivate *priv; +}; + +struct _EomDialogClass { + GObjectClass parent_class; + + void (* construct) (EomDialog *dialog, + const gchar *glade_file, + const gchar *dlg_node); + + void (* show) (EomDialog *dialog); + + void (* hide) (EomDialog *dialog); +}; + +GType eom_dialog_get_type (void) G_GNUC_CONST; + +void eom_dialog_construct (EomDialog *dialog, + const gchar *glade_file, + const gchar *dlg_node); + +void eom_dialog_show (EomDialog *dialog); + +void eom_dialog_hide (EomDialog *dialog); + +void eom_dialog_get_controls (EomDialog *dialog, + const gchar *property_id, + ...); + +G_END_DECLS + +#endif /* __EOM_DIALOG_H__ */ diff --git a/src/eom-enum-types.c.template b/src/eom-enum-types.c.template new file mode 100644 index 0000000..a7212e0 --- /dev/null +++ b/src/eom-enum-types.c.template @@ -0,0 +1,39 @@ +/*** BEGIN file-header ***/ +#include "eom-enum-types.h" + +/*** END file-header ***/ + +/*** BEGIN file-production ***/ +/* enumerations from "@filename@" */ +#include "@filename@" + +/*** END file-production ***/ + +/*** BEGIN value-header ***/ +GType +@enum_name@_get_type (void) +{ + static GType the_type = 0; + + if (the_type == 0) + { + static const G@Type@Value values[] = { +/*** END value-header ***/ + +/*** BEGIN value-production ***/ + { @VALUENAME@, + "@VALUENAME@", + "@valuenick@" }, +/*** END value-production ***/ + +/*** BEGIN value-tail ***/ + { 0, NULL, NULL } + }; + the_type = g_@type@_register_static ( + g_intern_static_string ("@EnumName@"), + values); + } + return the_type; +} + +/*** END value-tail ***/ diff --git a/src/eom-enum-types.h.template b/src/eom-enum-types.h.template new file mode 100644 index 0000000..e70c3b0 --- /dev/null +++ b/src/eom-enum-types.h.template @@ -0,0 +1,27 @@ +/*** BEGIN file-header ***/ +#ifndef __EOM_ENUM_TYPES_H__ +#define __EOM_ENUM_TYPES_H__ + +#include <glib-object.h> + +G_BEGIN_DECLS + +/*** END file-header ***/ + +/*** BEGIN file-production ***/ +/* Enumerations from "@filename@" */ + +/*** END file-production ***/ + +/*** BEGIN enumeration-production ***/ +#define EOM_TYPE_@ENUMSHORT@ (@enum_name@_get_type()) +G_GNUC_INTERNAL GType @enum_name@_get_type (void) G_GNUC_CONST; + +/*** END enumeration-production ***/ + +/*** BEGIN file-tail ***/ +G_END_DECLS + +#endif /* __EOM_ENUM_TYPES_H__ */ +/*** END file-tail ***/ + diff --git a/src/eom-enums.h b/src/eom-enums.h new file mode 100644 index 0000000..b39e978 --- /dev/null +++ b/src/eom-enums.h @@ -0,0 +1,37 @@ +/* Eye of MATE - General enumerations. + * + * Copyright (C) 2007-2008 The Free Software Foundation + * + * Author: Lucas Rocha <[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 __EOM_ENUMS__ +#define __EOM_ENUMS__ + +typedef enum { + EOM_IMAGE_DATA_IMAGE = 1 << 0, + EOM_IMAGE_DATA_DIMENSION = 1 << 1, + EOM_IMAGE_DATA_EXIF = 1 << 2, + EOM_IMAGE_DATA_XMP = 1 << 3 +} EomImageData; + +#define EOM_IMAGE_DATA_ALL (EOM_IMAGE_DATA_IMAGE | \ + EOM_IMAGE_DATA_DIMENSION | \ + EOM_IMAGE_DATA_EXIF | \ + EOM_IMAGE_DATA_XMP) + +#endif diff --git a/src/eom-error-message-area.c b/src/eom-error-message-area.c new file mode 100644 index 0000000..f0a2baf --- /dev/null +++ b/src/eom-error-message-area.c @@ -0,0 +1,192 @@ +/* Eye Of Mate - Erro Message Area + * + * Copyright (C) 2007 The Free Software Foundation + * + * Author: Lucas Rocha <[email protected]> + * + * Based on gedit code (gedit/gedit-message-area.h) by: + * - Paolo Maggi <[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. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "eom-error-message-area.h" +#include "eom-image.h" + +#include <glib.h> +#include <glib/gi18n.h> +#include <glib-object.h> +#include <gtk/gtk.h> + +static void +set_message_area_text_and_icon (GtkInfoBar *message_area, + const gchar *icon_stock_id, + const gchar *primary_text, + const gchar *secondary_text) +{ + GtkWidget *hbox_content; + GtkWidget *image; + GtkWidget *vbox; + gchar *primary_markup; + gchar *secondary_markup; + GtkWidget *primary_label; + GtkWidget *secondary_label; + + hbox_content = gtk_hbox_new (FALSE, 8); + gtk_widget_show (hbox_content); + + image = gtk_image_new_from_stock (icon_stock_id, GTK_ICON_SIZE_DIALOG); + gtk_widget_show (image); + gtk_box_pack_start (GTK_BOX (hbox_content), image, FALSE, FALSE, 0); + gtk_misc_set_alignment (GTK_MISC (image), 0.5, 0); + + vbox = gtk_vbox_new (FALSE, 6); + gtk_widget_show (vbox); + gtk_box_pack_start (GTK_BOX (hbox_content), vbox, TRUE, TRUE, 0); + + primary_markup = g_strdup_printf ("<b>%s</b>", primary_text); + primary_label = gtk_label_new (primary_markup); + g_free (primary_markup); + + gtk_widget_show (primary_label); + + gtk_box_pack_start (GTK_BOX (vbox), primary_label, TRUE, TRUE, 0); + gtk_label_set_use_markup (GTK_LABEL (primary_label), TRUE); + gtk_label_set_line_wrap (GTK_LABEL (primary_label), FALSE); + gtk_misc_set_alignment (GTK_MISC (primary_label), 0, 0.5); + + gtk_widget_set_can_focus (primary_label, TRUE); + + gtk_label_set_selectable (GTK_LABEL (primary_label), TRUE); + + if (secondary_text != NULL) { + secondary_markup = g_strdup_printf ("<small>%s</small>", + secondary_text); + secondary_label = gtk_label_new (secondary_markup); + g_free (secondary_markup); + + gtk_widget_show (secondary_label); + + gtk_box_pack_start (GTK_BOX (vbox), secondary_label, TRUE, TRUE, 0); + + gtk_widget_set_can_focus (secondary_label, TRUE); + + gtk_label_set_use_markup (GTK_LABEL (secondary_label), TRUE); + gtk_label_set_line_wrap (GTK_LABEL (secondary_label), TRUE); + gtk_label_set_selectable (GTK_LABEL (secondary_label), TRUE); + gtk_misc_set_alignment (GTK_MISC (secondary_label), 0, 0.5); + } + + gtk_box_pack_start (GTK_BOX (gtk_info_bar_get_content_area (GTK_INFO_BAR (message_area))), hbox_content, TRUE, TRUE, 0); +} + +static GtkWidget * +create_error_message_area (const gchar *primary_text, + const gchar *secondary_text, + gboolean recoverable) +{ + GtkWidget *message_area; + + if (recoverable) + message_area = gtk_info_bar_new_with_buttons (_("_Retry"), + GTK_RESPONSE_OK, + NULL); + else + message_area = gtk_info_bar_new (); + + gtk_info_bar_set_message_type (GTK_INFO_BAR (message_area), + GTK_MESSAGE_ERROR); + + set_message_area_text_and_icon (GTK_INFO_BAR (message_area), + GTK_STOCK_DIALOG_ERROR, + primary_text, + secondary_text); + + return message_area; +} + +GtkWidget * +eom_image_load_error_message_area_new (const gchar *caption, + const GError *error) +{ + GtkWidget *message_area; + gchar *error_message = NULL; + gchar *message_details = NULL; + gchar *pango_escaped_caption = NULL; + + g_return_val_if_fail (caption != NULL, NULL); + g_return_val_if_fail (error != NULL, NULL); + + /* Escape the caption string with respect to pango markup. + This is necessary because otherwise characters like "&" will + be interpreted as the beginning of a pango entity inside + the message area GtkLabel. */ + pango_escaped_caption = g_markup_escape_text (caption, -1); + error_message = g_strdup_printf (_("Could not load image '%s'."), + pango_escaped_caption); + + message_details = g_strdup (error->message); + + message_area = create_error_message_area (error_message, + message_details, + TRUE); + + g_free (pango_escaped_caption); + g_free (error_message); + g_free (message_details); + + return message_area; +} + +GtkWidget * +eom_no_images_error_message_area_new (GFile *file) +{ + GtkWidget *message_area; + gchar *error_message = NULL; + + if (file != NULL) { + gchar *uri_str, *unescaped_str, *pango_escaped_str; + + uri_str = g_file_get_uri (file); + /* Unescape URI with respect to rules defined in RFC 3986. */ + unescaped_str = g_uri_unescape_string (uri_str, NULL); + + /* Escape the URI string with respect to pango markup. + This is necessary because the URI string can contain + for example "&" which will otherwise be interpreted + as a pango markup entity when inserted into a GtkLabel. */ + pango_escaped_str = g_markup_escape_text (unescaped_str, -1); + error_message = g_strdup_printf (_("No images found in '%s'."), + pango_escaped_str); + + g_free (pango_escaped_str); + g_free (uri_str); + g_free (unescaped_str); + } else { + error_message = g_strdup (_("The given locations contain no images.")); + } + + message_area = create_error_message_area (error_message, + NULL, + FALSE); + + g_free (error_message); + + return message_area; +} diff --git a/src/eom-error-message-area.h b/src/eom-error-message-area.h new file mode 100644 index 0000000..8b285ba --- /dev/null +++ b/src/eom-error-message-area.h @@ -0,0 +1,37 @@ +/* Eye Of Mate - Erro Message Area + * + * Copyright (C) 2007 The Free Software Foundation + * + * Author: Lucas Rocha <[email protected]> + * + * Based on gedit code (gedit/gedit-message-area.h) by: + * - Paolo Maggi <[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 __EOM_ERROR_MESSAGE_AREA__ +#define __EOM_ERROR_MESSAGE_AREA__ + +#include <glib.h> +#include <gtk/gtk.h> +#include <gio/gio.h> + +GtkWidget *eom_image_load_error_message_area_new (const gchar *caption, + const GError *error); + +GtkWidget *eom_no_images_error_message_area_new (GFile *file); + +#endif /* __EOM_ERROR_MESSAGE_AREA__ */ diff --git a/src/eom-exif-details.c b/src/eom-exif-details.c new file mode 100644 index 0000000..2385e6a --- /dev/null +++ b/src/eom-exif-details.c @@ -0,0 +1,572 @@ +/* Eye Of Mate - EOM Image Exif Details + * + * Copyright (C) 2006 The Free Software Foundation + * + * Author: Lucas Rocha <[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. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "eom-exif-details.h" +#include "eom-util.h" + +#if HAVE_EXIF +#include <libexif/exif-entry.h> +#include <libexif/exif-utils.h> +#endif +#if HAVE_EXEMPI +#include <exempi/xmp.h> +#include <exempi/xmpconsts.h> +#endif + +#include <glib/gi18n.h> +#include <gtk/gtk.h> + +#include <string.h> + +#define EOM_EXIF_DETAILS_GET_PRIVATE(object) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((object), EOM_TYPE_EXIF_DETAILS, EomExifDetailsPrivate)) + +G_DEFINE_TYPE (EomExifDetails, eom_exif_details, GTK_TYPE_TREE_VIEW) + +typedef enum { + EXIF_CATEGORY_CAMERA, + EXIF_CATEGORY_IMAGE_DATA, + EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS, + EXIF_CATEGORY_MAKER_NOTE, + EXIF_CATEGORY_OTHER, +#ifdef HAVE_EXEMPI + XMP_CATEGORY_EXIF, + XMP_CATEGORY_IPTC, + XMP_CATEGORY_RIGHTS, + XMP_CATEGORY_OTHER +#endif +} ExifCategory; + +typedef struct { + char *label; + char *path; +} ExifCategoryInfo; + +static ExifCategoryInfo exif_categories[] = { + { N_("Camera"), "0" }, + { N_("Image Data"), "1" }, + { N_("Image Taking Conditions"), "2" }, + { N_("Maker Note"), "3" }, + { N_("Other"), "4" }, +#ifdef HAVE_EXEMPI + { N_("XMP Exif"), "5" }, + { N_("XMP IPTC"), "6" }, + { N_("XMP Rights Management"), "7" }, + { N_("XMP Other"), "8" }, +#endif + { NULL, NULL } +}; + +typedef struct { + int id; + ExifCategory category; +} ExifTagCategory; + +#ifdef HAVE_EXIF +static ExifTagCategory exif_tag_category_map[] = { + { EXIF_TAG_INTEROPERABILITY_INDEX, EXIF_CATEGORY_CAMERA}, + { EXIF_TAG_INTEROPERABILITY_VERSION, EXIF_CATEGORY_CAMERA}, + { EXIF_TAG_IMAGE_WIDTH, EXIF_CATEGORY_IMAGE_DATA}, + { EXIF_TAG_IMAGE_LENGTH , EXIF_CATEGORY_IMAGE_DATA}, + { EXIF_TAG_BITS_PER_SAMPLE ,EXIF_CATEGORY_CAMERA }, + { EXIF_TAG_COMPRESSION , EXIF_CATEGORY_IMAGE_DATA}, + { EXIF_TAG_PHOTOMETRIC_INTERPRETATION , EXIF_CATEGORY_OTHER}, + { EXIF_TAG_FILL_ORDER , EXIF_CATEGORY_OTHER}, + { EXIF_TAG_DOCUMENT_NAME , EXIF_CATEGORY_IMAGE_DATA}, + { EXIF_TAG_IMAGE_DESCRIPTION , EXIF_CATEGORY_IMAGE_DATA}, + { EXIF_TAG_MAKE , EXIF_CATEGORY_CAMERA}, + { EXIF_TAG_MODEL , EXIF_CATEGORY_CAMERA}, + { EXIF_TAG_STRIP_OFFSETS , EXIF_CATEGORY_IMAGE_DATA}, + { EXIF_TAG_ORIENTATION , EXIF_CATEGORY_IMAGE_DATA}, + { EXIF_TAG_SAMPLES_PER_PIXEL , EXIF_CATEGORY_CAMERA}, + { EXIF_TAG_ROWS_PER_STRIP , EXIF_CATEGORY_CAMERA}, + { EXIF_TAG_STRIP_BYTE_COUNTS , EXIF_CATEGORY_CAMERA}, + { EXIF_TAG_X_RESOLUTION , EXIF_CATEGORY_CAMERA}, + { EXIF_TAG_Y_RESOLUTION , EXIF_CATEGORY_CAMERA}, + { EXIF_TAG_PLANAR_CONFIGURATION , EXIF_CATEGORY_OTHER}, + { EXIF_TAG_RESOLUTION_UNIT , EXIF_CATEGORY_CAMERA}, + { EXIF_TAG_TRANSFER_FUNCTION , EXIF_CATEGORY_OTHER}, + { EXIF_TAG_SOFTWARE , EXIF_CATEGORY_CAMERA}, + { EXIF_TAG_DATE_TIME , EXIF_CATEGORY_IMAGE_DATA}, + { EXIF_TAG_ARTIST , EXIF_CATEGORY_IMAGE_DATA}, + { EXIF_TAG_WHITE_POINT , EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS}, + { EXIF_TAG_PRIMARY_CHROMATICITIES, EXIF_CATEGORY_OTHER}, + { EXIF_TAG_TRANSFER_RANGE , EXIF_CATEGORY_OTHER}, + { EXIF_TAG_JPEG_PROC , EXIF_CATEGORY_OTHER}, + { EXIF_TAG_JPEG_INTERCHANGE_FORMAT, EXIF_CATEGORY_OTHER}, + { EXIF_TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, }, + { EXIF_TAG_YCBCR_COEFFICIENTS , EXIF_CATEGORY_IMAGE_DATA}, + { EXIF_TAG_YCBCR_SUB_SAMPLING , EXIF_CATEGORY_IMAGE_DATA}, + { EXIF_TAG_YCBCR_POSITIONING , EXIF_CATEGORY_IMAGE_DATA}, + { EXIF_TAG_REFERENCE_BLACK_WHITE, EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS}, + { EXIF_TAG_RELATED_IMAGE_FILE_FORMAT,EXIF_CATEGORY_OTHER}, + { EXIF_TAG_RELATED_IMAGE_WIDTH , EXIF_CATEGORY_OTHER}, + { EXIF_TAG_RELATED_IMAGE_LENGTH , EXIF_CATEGORY_OTHER}, + { EXIF_TAG_CFA_REPEAT_PATTERN_DIM, EXIF_CATEGORY_OTHER}, + { EXIF_TAG_CFA_PATTERN , EXIF_CATEGORY_OTHER}, + { EXIF_TAG_BATTERY_LEVEL , EXIF_CATEGORY_OTHER}, + { EXIF_TAG_COPYRIGHT , EXIF_CATEGORY_IMAGE_DATA}, + { EXIF_TAG_EXPOSURE_TIME , EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS}, + { EXIF_TAG_FNUMBER , EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS}, + { EXIF_TAG_IPTC_NAA , EXIF_CATEGORY_OTHER}, + { EXIF_TAG_EXIF_IFD_POINTER , EXIF_CATEGORY_OTHER}, + { EXIF_TAG_INTER_COLOR_PROFILE , EXIF_CATEGORY_OTHER}, + { EXIF_TAG_EXPOSURE_PROGRAM , EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS}, + { EXIF_TAG_SPECTRAL_SENSITIVITY , EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS}, + { EXIF_TAG_GPS_INFO_IFD_POINTER , EXIF_CATEGORY_OTHER}, + { EXIF_TAG_ISO_SPEED_RATINGS , EXIF_CATEGORY_IMAGE_DATA}, + { EXIF_TAG_OECF , EXIF_CATEGORY_OTHER}, + { EXIF_TAG_EXIF_VERSION , EXIF_CATEGORY_CAMERA}, + { EXIF_TAG_DATE_TIME_ORIGINAL , EXIF_CATEGORY_IMAGE_DATA}, + { EXIF_TAG_DATE_TIME_DIGITIZED , EXIF_CATEGORY_IMAGE_DATA}, + { EXIF_TAG_COMPONENTS_CONFIGURATION , EXIF_CATEGORY_CAMERA}, + { EXIF_TAG_COMPRESSED_BITS_PER_PIXEL, EXIF_CATEGORY_CAMERA}, + { EXIF_TAG_SHUTTER_SPEED_VALUE , EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS}, + { EXIF_TAG_APERTURE_VALUE , EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS}, + { EXIF_TAG_BRIGHTNESS_VALUE , EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS}, + { EXIF_TAG_EXPOSURE_BIAS_VALUE , EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS}, + { EXIF_TAG_MAX_APERTURE_VALUE , EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS}, + { EXIF_TAG_SUBJECT_DISTANCE , EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS}, + { EXIF_TAG_METERING_MODE , EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS}, + { EXIF_TAG_LIGHT_SOURCE , EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS}, + { EXIF_TAG_FLASH , EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS}, + { EXIF_TAG_FOCAL_LENGTH , EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS}, + { EXIF_TAG_SUBJECT_AREA , EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS}, + { EXIF_TAG_MAKER_NOTE , EXIF_CATEGORY_OTHER}, + { EXIF_TAG_USER_COMMENT , EXIF_CATEGORY_IMAGE_DATA}, + { EXIF_TAG_SUBSEC_TIME , EXIF_CATEGORY_OTHER}, + { EXIF_TAG_SUB_SEC_TIME_ORIGINAL, EXIF_CATEGORY_OTHER}, + { EXIF_TAG_SUB_SEC_TIME_DIGITIZED, EXIF_CATEGORY_OTHER}, + { EXIF_TAG_FLASH_PIX_VERSION , EXIF_CATEGORY_OTHER}, + { EXIF_TAG_COLOR_SPACE , EXIF_CATEGORY_OTHER}, + { EXIF_TAG_PIXEL_X_DIMENSION , EXIF_CATEGORY_IMAGE_DATA}, + { EXIF_TAG_PIXEL_Y_DIMENSION , EXIF_CATEGORY_IMAGE_DATA}, + { EXIF_TAG_RELATED_SOUND_FILE , EXIF_CATEGORY_OTHER}, + { EXIF_TAG_INTEROPERABILITY_IFD_POINTER, EXIF_CATEGORY_OTHER}, + { EXIF_TAG_FLASH_ENERGY ,EXIF_CATEGORY_OTHER }, + { EXIF_TAG_SPATIAL_FREQUENCY_RESPONSE, EXIF_CATEGORY_OTHER}, + { EXIF_TAG_FOCAL_PLANE_X_RESOLUTION, EXIF_CATEGORY_CAMERA}, + { EXIF_TAG_FOCAL_PLANE_Y_RESOLUTION, EXIF_CATEGORY_CAMERA}, + { EXIF_TAG_FOCAL_PLANE_RESOLUTION_UNIT, EXIF_CATEGORY_CAMERA}, + { EXIF_TAG_SUBJECT_LOCATION, EXIF_CATEGORY_OTHER}, + { EXIF_TAG_EXPOSURE_INDEX, EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS}, + { EXIF_TAG_SENSING_METHOD, EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS}, + { EXIF_TAG_FILE_SOURCE , EXIF_CATEGORY_OTHER}, + { EXIF_TAG_SCENE_TYPE, EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS}, + { EXIF_TAG_NEW_CFA_PATTERN, EXIF_CATEGORY_OTHER}, + { EXIF_TAG_CUSTOM_RENDERED, EXIF_CATEGORY_OTHER}, + { EXIF_TAG_EXPOSURE_MODE, EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS}, + { EXIF_TAG_WHITE_BALANCE, EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS}, + { EXIF_TAG_DIGITAL_ZOOM_RATIO, EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS}, + { EXIF_TAG_FOCAL_LENGTH_IN_35MM_FILM, EXIF_CATEGORY_CAMERA}, + { EXIF_TAG_SCENE_CAPTURE_TYPE , EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS}, + { EXIF_TAG_GAIN_CONTROL , EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS}, + { EXIF_TAG_CONTRAST , EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS}, + { EXIF_TAG_SATURATION , EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS}, + { EXIF_TAG_SHARPNESS , EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS}, + { EXIF_TAG_DEVICE_SETTING_DESCRIPTION, EXIF_CATEGORY_CAMERA}, + { EXIF_TAG_SUBJECT_DISTANCE_RANGE, EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS}, + { EXIF_TAG_IMAGE_UNIQUE_ID , EXIF_CATEGORY_IMAGE_DATA}, + { -1, -1 } +}; +#endif + +#define MODEL_COLUMN_ATTRIBUTE 0 +#define MODEL_COLUMN_VALUE 1 + +struct _EomExifDetailsPrivate { + GtkTreeModel *model; + + GHashTable *id_path_hash; + GHashTable *id_path_hash_mnote; +}; + +static char* set_row_data (GtkTreeStore *store, char *path, char *parent, const char *attribute, const char *value); + +static void eom_exif_details_reset (EomExifDetails *exif_details); + +static void +eom_exif_details_dispose (GObject *object) +{ + EomExifDetailsPrivate *priv; + + priv = EOM_EXIF_DETAILS (object)->priv; + + if (priv->model) { + g_object_unref (priv->model); + priv->model = NULL; + } + + if (priv->id_path_hash) { + g_hash_table_destroy (priv->id_path_hash); + priv->id_path_hash = NULL; + } + + if (priv->id_path_hash_mnote) { + g_hash_table_destroy (priv->id_path_hash_mnote); + priv->id_path_hash_mnote = NULL; + } + G_OBJECT_CLASS (eom_exif_details_parent_class)->dispose (object); +} + +static void +eom_exif_details_init (EomExifDetails *exif_details) +{ + EomExifDetailsPrivate *priv; + GtkTreeViewColumn *column; + GtkCellRenderer *cell; + + exif_details->priv = EOM_EXIF_DETAILS_GET_PRIVATE (exif_details); + + priv = exif_details->priv; + + priv->model = GTK_TREE_MODEL (gtk_tree_store_new (2, G_TYPE_STRING, G_TYPE_STRING)); + priv->id_path_hash = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free); + priv->id_path_hash_mnote = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free); + + /* Tag name column */ + cell = gtk_cell_renderer_text_new (); + column = gtk_tree_view_column_new_with_attributes (_("Tag"), cell, + "text", MODEL_COLUMN_ATTRIBUTE, + NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (exif_details), column); + + /* Value column */ + cell = gtk_cell_renderer_text_new (); + column = gtk_tree_view_column_new_with_attributes (_("Value"), cell, + "text", MODEL_COLUMN_VALUE, + NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (exif_details), column); + + gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (exif_details), TRUE); + + eom_exif_details_reset (exif_details); + + gtk_tree_view_set_model (GTK_TREE_VIEW (exif_details), + GTK_TREE_MODEL (priv->model)); +} + +static void +eom_exif_details_class_init (EomExifDetailsClass *klass) +{ + GObjectClass *object_class = (GObjectClass*) klass; + + object_class->dispose = eom_exif_details_dispose; + + g_type_class_add_private (object_class, sizeof (EomExifDetailsPrivate)); +} + +#ifdef HAVE_EXIF +static ExifCategory +get_exif_category (ExifEntry *entry) +{ + ExifCategory cat = EXIF_CATEGORY_OTHER; + int i; + + for (i = 0; exif_tag_category_map [i].id != -1; i++) { + if (exif_tag_category_map[i].id == (int) entry->tag) { + cat = exif_tag_category_map[i].category; + break; + } + } + + return cat; +} +#endif + +static char* +set_row_data (GtkTreeStore *store, char *path, char *parent, const char *attribute, const char *value) +{ + GtkTreeIter iter; + gchar *utf_attribute = NULL; + gchar *utf_value = NULL; + gboolean iter_valid = FALSE; + + if (!attribute) return NULL; + + if (path != NULL) { + iter_valid = gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (store), &iter, path); + } + + if (!iter_valid) { + GtkTreePath *tree_path; + GtkTreeIter parent_iter; + gboolean parent_valid = FALSE; + + if (parent != NULL) { + parent_valid = gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (store), + &parent_iter, + parent); + } + + gtk_tree_store_append (store, &iter, parent_valid ? &parent_iter : NULL); + + if (path == NULL) { + tree_path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter); + + if (tree_path != NULL) { + path = gtk_tree_path_to_string (tree_path); + gtk_tree_path_free (tree_path); + } + } + } + + utf_attribute = eom_util_make_valid_utf8 (attribute); + + gtk_tree_store_set (store, &iter, MODEL_COLUMN_ATTRIBUTE, utf_attribute, -1); + g_free (utf_attribute); + + if (value != NULL) { + utf_value = eom_util_make_valid_utf8 (value); + gtk_tree_store_set (store, &iter, MODEL_COLUMN_VALUE, utf_value, -1); + g_free (utf_value); + } + + return path; +} + +#ifdef HAVE_EXIF +static void +exif_entry_cb (ExifEntry *entry, gpointer data) +{ + GtkTreeStore *store; + EomExifDetails *view; + EomExifDetailsPrivate *priv; + ExifCategory cat; + char *path; + char b[1024]; + + view = EOM_EXIF_DETAILS (data); + priv = view->priv; + + store = GTK_TREE_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (view))); + + path = g_hash_table_lookup (priv->id_path_hash, GINT_TO_POINTER (entry->tag)); + + if (path != NULL) { + set_row_data (store, + path, + NULL, + exif_tag_get_name (entry->tag), + exif_entry_get_value (entry, b, sizeof(b))); + } else { + + ExifMnoteData *mnote = (entry->tag == EXIF_TAG_MAKER_NOTE ? + exif_data_get_mnote_data (entry->parent->parent) : NULL); + + if (mnote) { + // Supported MakerNote Found + unsigned int i, c = exif_mnote_data_count (mnote); + + for (i = 0; i < c; i++) { + path = g_hash_table_lookup (priv->id_path_hash_mnote, GINT_TO_POINTER (i)); + if (path != NULL) { + set_row_data (store, path, NULL, + exif_mnote_data_get_title (mnote, i), + exif_mnote_data_get_value (mnote, i, b, sizeof(b))); + } else { + path = set_row_data (store, + NULL, + exif_categories[EXIF_CATEGORY_MAKER_NOTE].path, + exif_mnote_data_get_title (mnote, i), + exif_mnote_data_get_value (mnote, i, b, sizeof(b))); + g_hash_table_insert (priv->id_path_hash_mnote, GINT_TO_POINTER (i), path); + } + } + } else { + cat = get_exif_category (entry); + + path = set_row_data (store, + NULL, + exif_categories[cat].path, + exif_tag_get_name (entry->tag), + exif_entry_get_value (entry, b, + sizeof(b))); + + g_hash_table_insert (priv->id_path_hash, + GINT_TO_POINTER (entry->tag), + path); + } + } +} +#endif + +#ifdef HAVE_EXIF +static void +exif_content_cb (ExifContent *content, gpointer data) +{ + exif_content_foreach_entry (content, exif_entry_cb, data); +} +#endif + +GtkWidget * +eom_exif_details_new (void) +{ + GObject *object; + + object = g_object_new (EOM_TYPE_EXIF_DETAILS, NULL); + + return GTK_WIDGET (object); +} + +static void +eom_exif_details_reset (EomExifDetails *exif_details) +{ + int i; + EomExifDetailsPrivate *priv = exif_details->priv; + + gtk_tree_store_clear (GTK_TREE_STORE (priv->model)); + + g_hash_table_remove_all (priv->id_path_hash); + g_hash_table_remove_all (priv->id_path_hash_mnote); + + for (i = 0; exif_categories [i].label != NULL; i++) { + char *translated_string; + + translated_string = gettext (exif_categories[i].label); + + set_row_data (GTK_TREE_STORE (priv->model), + exif_categories[i].path, + NULL, + translated_string, + NULL); + } +} + +#ifdef HAVE_EXIF +void +eom_exif_details_update (EomExifDetails *exif_details, ExifData *data) +{ + EomExifDetailsPrivate *priv; + + g_return_if_fail (EOM_IS_EXIF_DETAILS (exif_details)); + + priv = exif_details->priv; + + eom_exif_details_reset (exif_details); + if (data) { + exif_data_foreach_content (data, exif_content_cb, exif_details); + } +} +#endif /* HAVE_EXIF */ + +#ifdef HAVE_EXEMPI +typedef struct { + const char *id; + ExifCategory category; +} XmpNsCategory; + +static XmpNsCategory xmp_ns_category_map[] = { + { NS_EXIF, XMP_CATEGORY_EXIF}, + { NS_TIFF, XMP_CATEGORY_EXIF}, + { NS_XAP, XMP_CATEGORY_EXIF}, + { NS_XAP_RIGHTS, XMP_CATEGORY_RIGHTS}, + { NS_EXIF_AUX, XMP_CATEGORY_EXIF}, + { NS_DC, XMP_CATEGORY_IPTC}, + { NS_IPTC4XMP, XMP_CATEGORY_IPTC}, + { NS_CC, XMP_CATEGORY_RIGHTS}, + { NULL, -1} +}; + +static ExifCategory +get_xmp_category (XmpStringPtr schema) +{ + ExifCategory cat = XMP_CATEGORY_OTHER; + const char *s = xmp_string_cstr(schema); + int i; + + for (i = 0; xmp_ns_category_map[i].id != NULL; i++) { + if (strcmp (xmp_ns_category_map[i].id, s) == 0) { + cat = xmp_ns_category_map[i].category; + break; + } + } + + return cat; +} + +static void +xmp_entry_insert (EomExifDetails *view, XmpStringPtr xmp_schema, + XmpStringPtr xmp_path, XmpStringPtr xmp_prop) +{ + GtkTreeStore *store; + EomExifDetailsPrivate *priv; + ExifCategory cat; + char *path; + gchar *key; + + priv = view->priv; + + key = g_strconcat (xmp_string_cstr (xmp_schema), ":", + xmp_string_cstr (xmp_path), NULL); + + store = GTK_TREE_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (view))); + + path = g_hash_table_lookup (priv->id_path_hash, key); + + if (path != NULL) { + set_row_data (store, path, NULL, + xmp_string_cstr (xmp_path), + xmp_string_cstr (xmp_prop)); + + g_free(key); + } + else { + cat = get_xmp_category (xmp_schema); + + path = set_row_data (store, NULL, exif_categories[cat].path, + xmp_string_cstr(xmp_path), + xmp_string_cstr(xmp_prop)); + + g_hash_table_insert (priv->id_path_hash, key, path); + } +} + +void +eom_exif_details_xmp_update (EomExifDetails *view, XmpPtr data) +{ + EomExifDetailsPrivate *priv; + + g_return_if_fail (EOM_IS_EXIF_DETAILS (view)); + + priv = view->priv; + + if (data) { + XmpIteratorPtr iter = xmp_iterator_new(data, NULL, NULL, XMP_ITER_JUSTLEAFNODES); + XmpStringPtr the_schema = xmp_string_new (); + XmpStringPtr the_path = xmp_string_new (); + XmpStringPtr the_prop = xmp_string_new (); + + while (xmp_iterator_next (iter, the_schema, the_path, the_prop, NULL)) { + xmp_entry_insert (view, the_schema, the_path, the_prop); + } + + xmp_string_free (the_prop); + xmp_string_free (the_path); + xmp_string_free (the_schema); + xmp_iterator_free (iter); + } +} +#endif diff --git a/src/eom-exif-details.h b/src/eom-exif-details.h new file mode 100644 index 0000000..5964133 --- /dev/null +++ b/src/eom-exif-details.h @@ -0,0 +1,72 @@ +/* Eye Of Mate - EOM Image Exif Details + * + * Copyright (C) 2006 The Free Software Foundation + * + * Author: Lucas Rocha <[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 __EOM_EXIF_DETAILS__ +#define __EOM_EXIF_DETAILS__ + +#include <glib-object.h> +#include <gtk/gtk.h> +#if HAVE_EXIF +#include <libexif/exif-data.h> +#endif +#if HAVE_EXEMPI +#include <exempi/xmp.h> +#endif + +G_BEGIN_DECLS + +typedef struct _EomExifDetails EomExifDetails; +typedef struct _EomExifDetailsClass EomExifDetailsClass; +typedef struct _EomExifDetailsPrivate EomExifDetailsPrivate; + +#define EOM_TYPE_EXIF_DETAILS (eom_exif_details_get_type ()) +#define EOM_EXIF_DETAILS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EOM_TYPE_EXIF_DETAILS, EomExifDetails)) +#define EOM_EXIF_DETAILS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), EOM_TYPE_EXIF_DETAILS, EomExifDetailsClass)) +#define EOM_IS_EXIF_DETAILS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EOM_TYPE_EXIF_DETAILS)) +#define EOM_IS_EXIF_DETAILS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EOM_TYPE_EXIF_DETAILS)) +#define EOM_EXIF_DETAILS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EOM_TYPE_EXIF_DETAILS, EomExifDetailsClass)) + +struct _EomExifDetails { + GtkTreeView parent; + + EomExifDetailsPrivate *priv; +}; + +struct _EomExifDetailsClass { + GtkTreeViewClass parent_class; +}; + +GType eom_exif_details_get_type (void) G_GNUC_CONST; + +GtkWidget *eom_exif_details_new (void); + +#if HAVE_EXIF +void eom_exif_details_update (EomExifDetails *view, + ExifData *data); +#endif +#if HAVE_EXEMPI +void eom_exif_details_xmp_update (EomExifDetails *view, + XmpPtr xmp_data); +#endif + +G_END_DECLS + +#endif /* __EOM_EXIF_DETAILS__ */ diff --git a/src/eom-exif-util.c b/src/eom-exif-util.c new file mode 100644 index 0000000..93d54e0 --- /dev/null +++ b/src/eom-exif-util.c @@ -0,0 +1,214 @@ +/* Eye Of Mate - EXIF Utilities + * + * Copyright (C) 2006-2007 The Free Software Foundation + * + * Author: Lucas Rocha <[email protected]> + * Author: Claudio Saavedra <[email protected]> + * Author: Felix Riemann <[email protected]> + * + * Based on code by: + * - Jens Finke <[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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_STRPTIME +#define _XOPEN_SOURCE +#endif +#include <time.h> + +#include "eom-exif-util.h" + +#include <string.h> +#include <glib/gi18n.h> + +#define DATE_BUF_SIZE 200 + +/* gboolean <-> gpointer conversion macros taken from gedit */ +#ifndef GBOOLEAN_TO_POINTER +#define GBOOLEAN_TO_POINTER(i) (GINT_TO_POINTER ((i) ? 2 : 1)) +#endif +#ifndef GPOINTER_TO_BOOLEAN +#define GPOINTER_TO_BOOLEAN(i) ((gboolean) ((GPOINTER_TO_INT (i) == 2) ? TRUE : FALSE)) +#endif + +static gpointer +_check_strptime_updates_wday (gpointer data) +{ + struct tm tm; + + memset (&tm, '\0', sizeof (tm)); + strptime ("2008:12:24 20:30:45", "%Y:%m:%d %T", &tm); + /* Check if tm.tm_wday is set to Wednesday (3) now */ + return GBOOLEAN_TO_POINTER (tm.tm_wday == 3); +} + +/** + * _calculate_wday_yday: + * @tm: A struct tm that should be updated. + * + * Ensure tm_wday and tm_yday are set correctly in a struct tm. + * The other date (dmy) values must have been set already. + **/ +static void +_calculate_wday_yday (struct tm *tm) +{ + GDate *exif_date; + struct tm tmp_tm; + + exif_date = g_date_new_dmy (tm->tm_mday, + tm->tm_mon+1, + tm->tm_year+1900); + + g_return_if_fail (exif_date != NULL && g_date_valid (exif_date)); + + // Use this to get GLib <-> libc corrected values + g_date_to_struct_tm (exif_date, &tmp_tm); + g_date_free (exif_date); + + tm->tm_wday = tmp_tm.tm_wday; + tm->tm_yday = tmp_tm.tm_yday; +} + +#ifdef HAVE_STRPTIME +static gchar * +eom_exif_util_format_date_with_strptime (const gchar *date) +{ + static GOnce strptime_updates_wday = G_ONCE_INIT; + gchar *new_date = NULL; + gchar tmp_date[DATE_BUF_SIZE]; + gchar *p; + gsize dlen; + struct tm tm; + + memset (&tm, '\0', sizeof (tm)); + p = strptime (date, "%Y:%m:%d %T", &tm); + + if (p == date + strlen (date)) { + g_once (&strptime_updates_wday, + _check_strptime_updates_wday, + NULL); + + // Ensure tm.tm_wday and tm.tm_yday are set + if (!GPOINTER_TO_BOOLEAN (strptime_updates_wday.retval)) + _calculate_wday_yday (&tm); + + /* A strftime-formatted string, to display the date the image was taken. */ + dlen = strftime (tmp_date, DATE_BUF_SIZE * sizeof(gchar), _("%a, %d %B %Y %X"), &tm); + new_date = g_strndup (tmp_date, dlen); + } + + return new_date; +} +#else +static gchar * +eom_exif_util_format_date_by_hand (const gchar *date) +{ + int year, month, day, hour, minutes, seconds; + int result; + gchar *new_date = NULL; + + result = sscanf (date, "%d:%d:%d %d:%d:%d", + &year, &month, &day, &hour, &minutes, &seconds); + + if (result < 3 || !g_date_valid_dmy (day, month, year)) { + return NULL; + } else { + gchar tmp_date[DATE_BUF_SIZE]; + gsize dlen; + time_t secs; + struct tm tm; + + memset (&tm, '\0', sizeof (tm)); + tm.tm_mday = day; + tm.tm_mon = month-1; + tm.tm_year = year-1900; + // Calculate tm.tm_wday + _calculate_wday_yday (&tm); + + if (result < 5) { + /* A strftime-formatted string, to display the date the image was taken, for the case we don't have the time. */ + dlen = strftime (tmp_date, DATE_BUF_SIZE * sizeof(gchar), _("%a, %d %B %Y"), &tm); + } else { + tm.tm_sec = result < 6 ? 0 : seconds; + tm.tm_min = minutes; + tm.tm_hour = hour; + /* A strftime-formatted string, to display the date the image was taken. */ + dlen = strftime (tmp_date, DATE_BUF_SIZE * sizeof(gchar), _("%a, %d %B %Y %X"), &tm); + } + + if (dlen == 0) + return NULL; + else + new_date = g_strndup (tmp_date, dlen); + } + return new_date; +} +#endif /* HAVE_STRPTIME */ + +/** + * eom_exif_util_format_date: + * @date: a date string following Exif specifications + * + * Takes a date string formatted after Exif specifications and generates a + * more readable, possibly localized, string out of it. + * + * Returns: a newly allocated date string formatted according to the + * current locale. + */ +gchar * +eom_exif_util_format_date (const gchar *date) +{ + gchar *new_date; +#ifdef HAVE_STRPTIME + new_date = eom_exif_util_format_date_with_strptime (date); +#else + new_date = eom_exif_util_format_date_by_hand (date); +#endif /* HAVE_STRPTIME */ + return new_date; +} + +/** + * eom_exif_util_get_value: + * @exif_data: pointer to an <structname>ExifData</structname> struct + * @tag_id: the requested tag's id. See <filename>exif-tag.h</filename> + * from the libexif package for possible values (e.g. %EXIF_TAG_EXPOSURE_MODE). + * @buffer: a pre-allocated output buffer + * @buf_size: size of @buffer + * + * Convenience function to extract a string representation of an Exif tag + * directly from an <structname>ExifData</structname> struct. The string is + * written into @buffer as far as @buf_size permits. + * + * Returns: a pointer to @buffer. + */ +const gchar * +eom_exif_util_get_value (ExifData *exif_data, gint tag_id, gchar *buffer, guint buf_size) +{ + ExifEntry *exif_entry; + const gchar *exif_value; + + exif_entry = exif_data_get_entry (exif_data, tag_id); + + buffer[0] = 0; + + exif_value = exif_entry_get_value (exif_entry, buffer, buf_size); + + return exif_value; +} diff --git a/src/eom-exif-util.h b/src/eom-exif-util.h new file mode 100644 index 0000000..9463524 --- /dev/null +++ b/src/eom-exif-util.h @@ -0,0 +1,41 @@ +/* Eye Of Mate - EXIF Utilities + * + * Copyright (C) 2006-2007 The Free Software Foundation + * + * Author: Lucas Rocha <[email protected]> + * Author: Claudio Saavedra <[email protected]> + * Author: Felix Riemann <[email protected]> + * + * Based on code by: + * - Jens Finke <[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 __EOM_EXIF_UTIL_H__ +#define __EOM_EXIF_UTIL_H__ + +#include <glib.h> +#include <libexif/exif-data.h> + +G_BEGIN_DECLS + +gchar* eom_exif_util_format_date (const gchar *date); + +const gchar *eom_exif_util_get_value (ExifData *exif_data, gint tag_id, gchar *buffer, guint buf_size); + +G_END_DECLS + +#endif /* __EOM_EXIF_UTIL_H__ */ diff --git a/src/eom-file-chooser.c b/src/eom-file-chooser.c new file mode 100644 index 0000000..83a6fe2 --- /dev/null +++ b/src/eom-file-chooser.c @@ -0,0 +1,497 @@ +/* + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "eom-file-chooser.h" +#include "eom-config-keys.h" +#include "eom-pixbuf-util.h" + +#include <stdlib.h> + +#include <glib.h> +#include <glib/gi18n.h> +#include <gio/gio.h> +#include <gtk/gtk.h> +#include <mateconf/mateconf-client.h> + +/* We must define MATE_DESKTOP_USE_UNSTABLE_API to be able + to use MateDesktopThumbnail */ +#ifndef MATE_DESKTOP_USE_UNSTABLE_API +#define MATE_DESKTOP_USE_UNSTABLE_API +#endif +#include <libmateui/mate-desktop-thumbnail.h> + +static char *last_dir[] = { NULL, NULL, NULL, NULL }; + +#define FILE_FORMAT_KEY "file-format" + +#define EOM_FILE_CHOOSER_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), \ + EOM_TYPE_FILE_CHOOSER, \ + EomFileChooserPrivate)) + +struct _EomFileChooserPrivate +{ + MateDesktopThumbnailFactory *thumb_factory; + + GtkWidget *image; + GtkWidget *size_label; + GtkWidget *dim_label; + GtkWidget *creator_label; +}; + +G_DEFINE_TYPE(EomFileChooser, eom_file_chooser, GTK_TYPE_FILE_CHOOSER_DIALOG) + +static void +eom_file_chooser_finalize (GObject *object) +{ + EomFileChooserPrivate *priv; + + priv = EOM_FILE_CHOOSER (object)->priv; + + if (priv->thumb_factory != NULL) + g_object_unref (priv->thumb_factory); + + (* G_OBJECT_CLASS (eom_file_chooser_parent_class)->finalize) (object); +} + +static void +eom_file_chooser_class_init (EomFileChooserClass *klass) +{ + GObjectClass *object_class = (GObjectClass *) klass; + + object_class->finalize = eom_file_chooser_finalize; + + g_type_class_add_private (object_class, sizeof (EomFileChooserPrivate)); +} + +static void +eom_file_chooser_init (EomFileChooser *chooser) +{ + chooser->priv = EOM_FILE_CHOOSER_GET_PRIVATE (chooser); +} + +static void +response_cb (GtkDialog *dlg, gint id, gpointer data) +{ + char *dir; + GtkFileChooserAction action; + + if (id == GTK_RESPONSE_OK) { + dir = gtk_file_chooser_get_current_folder (GTK_FILE_CHOOSER (dlg)); + action = gtk_file_chooser_get_action (GTK_FILE_CHOOSER (dlg)); + + if (last_dir [action] != NULL) + g_free (last_dir [action]); + + last_dir [action] = dir; + } +} + +static void +save_response_cb (GtkDialog *dlg, gint id, gpointer data) +{ + GFile *file; + GdkPixbufFormat *format; + + if (id != GTK_RESPONSE_OK) + return; + + file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dlg)); + format = eom_pixbuf_get_format (file); + g_object_unref (file); + + if (!format || !gdk_pixbuf_format_is_writable (format)) { + GtkWidget *msg_dialog; + + msg_dialog = gtk_message_dialog_new ( + GTK_WINDOW (dlg), + GTK_DIALOG_MODAL, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_OK, + _("File format is unknown or unsupported")); + + gtk_message_dialog_format_secondary_text ( + GTK_MESSAGE_DIALOG (msg_dialog), + "%s\n%s", + _("Eye of MATE could not determine a supported writable file format based on the filename."), + _("Please try a different file extension like .png or .jpg.")); + + gtk_dialog_run (GTK_DIALOG (msg_dialog)); + gtk_widget_destroy (msg_dialog); + + g_signal_stop_emission_by_name (dlg, "response"); + } else { + response_cb (dlg, id, data); + } +} + +static void +eom_file_chooser_add_filter (EomFileChooser *chooser) +{ + GSList *it; + GSList *formats; + GtkFileFilter *all_file_filter; + GtkFileFilter *all_img_filter; + GtkFileFilter *filter; + GSList *filters = NULL; + gchar **mime_types, **pattern, *tmp; + int i; + GtkFileChooserAction action; + + action = gtk_file_chooser_get_action (GTK_FILE_CHOOSER (chooser)); + + if (action != GTK_FILE_CHOOSER_ACTION_SAVE && action != GTK_FILE_CHOOSER_ACTION_OPEN) { + return; + } + + /* All Files Filter */ + all_file_filter = gtk_file_filter_new (); + gtk_file_filter_set_name (all_file_filter, _("All Files")); + gtk_file_filter_add_pattern (all_file_filter, "*"); + + /* All Image Filter */ + all_img_filter = gtk_file_filter_new (); + gtk_file_filter_set_name (all_img_filter, _("All Images")); + + if (action == GTK_FILE_CHOOSER_ACTION_SAVE) { + formats = eom_pixbuf_get_savable_formats (); + } + else { + formats = gdk_pixbuf_get_formats (); + } + + /* Image filters */ + for (it = formats; it != NULL; it = it->next) { + char *filter_name; + char *description, *extension; + GdkPixbufFormat *format; + filter = gtk_file_filter_new (); + + format = (GdkPixbufFormat*) it->data; + description = gdk_pixbuf_format_get_description (format); + extension = gdk_pixbuf_format_get_name (format); + + /* Filter name: First description then file extension, eg. "The PNG-Format (*.png)".*/ + filter_name = g_strdup_printf (_("%s (*.%s)"), description, extension); + g_free (description); + g_free (extension); + + gtk_file_filter_set_name (filter, filter_name); + g_free (filter_name); + + mime_types = gdk_pixbuf_format_get_mime_types ((GdkPixbufFormat *) it->data); + for (i = 0; mime_types[i] != NULL; i++) { + gtk_file_filter_add_mime_type (filter, mime_types[i]); + gtk_file_filter_add_mime_type (all_img_filter, mime_types[i]); + } + g_strfreev (mime_types); + + pattern = gdk_pixbuf_format_get_extensions ((GdkPixbufFormat *) it->data); + for (i = 0; pattern[i] != NULL; i++) { + tmp = g_strconcat ("*.", pattern[i], NULL); + gtk_file_filter_add_pattern (filter, tmp); + gtk_file_filter_add_pattern (all_img_filter, tmp); + g_free (tmp); + } + g_strfreev (pattern); + + /* attach GdkPixbufFormat to filter, see also + * eom_file_chooser_get_format. */ + g_object_set_data (G_OBJECT (filter), + FILE_FORMAT_KEY, + format); + + filters = g_slist_prepend (filters, filter); + } + g_slist_free (formats); + + /* Add filter to filechooser */ + gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), all_file_filter); + gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), all_img_filter); + gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (chooser), all_img_filter); + + for (it = filters; it != NULL; it = it->next) { + gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), GTK_FILE_FILTER (it->data)); + } + g_slist_free (filters); +} + +static void +set_preview_label (GtkWidget *label, const char *str) +{ + if (str == NULL) { + gtk_widget_hide (GTK_WIDGET (label)); + } + else { + gtk_label_set_text (GTK_LABEL (label), str); + gtk_widget_show (GTK_WIDGET (label)); + } +} + +/* Sets the pixbuf as preview thumbnail and tries to read and display + * further information according to the thumbnail spec. + */ +static void +set_preview_pixbuf (EomFileChooser *chooser, GdkPixbuf *pixbuf, goffset size) +{ + EomFileChooserPrivate *priv; + int bytes; + int pixels; + const char *bytes_str; + const char *width; + const char *height; + const char *creator = NULL; + char *size_str = NULL; + char *dim_str = NULL; + + g_return_if_fail (EOM_IS_FILE_CHOOSER (chooser)); + + priv = chooser->priv; + + gtk_image_set_from_pixbuf (GTK_IMAGE (priv->image), pixbuf); + + if (pixbuf != NULL) { + /* try to read file size */ + bytes_str = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::Size"); + if (bytes_str != NULL) { + bytes = atoi (bytes_str); + size_str = g_format_size_for_display (bytes); + } + else { + size_str = g_format_size_for_display (size); + } + + /* try to read image dimensions */ + width = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::Image::Width"); + height = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::Image::Height"); + + if ((width != NULL) && (height != NULL)) { + pixels = atoi (height); + /* Pixel size of image: width x height in pixel */ + dim_str = g_strdup_printf ("%s x %s %s", width, height, ngettext ("pixel", "pixels", pixels)); + } + +#if 0 + /* Not sure, if this is really useful, therefore its commented out for now. */ + + /* try to read creator of the thumbnail */ + creator = gdk_pixbuf_get_option (pixbuf, "tEXt::Software"); + + /* stupid workaround to display nicer string if the + * thumbnail is created through the mate libraries. + */ + if (g_ascii_strcasecmp (creator, "Mate::ThumbnailFactory") == 0) { + creator = "MATE Libs"; + } +#endif + } + + set_preview_label (priv->size_label, size_str); + set_preview_label (priv->dim_label, dim_str); + set_preview_label (priv->creator_label, creator); + + if (size_str != NULL) { + g_free (size_str); + } + + if (dim_str != NULL) { + g_free (dim_str); + } +} + +static void +update_preview_cb (GtkFileChooser *file_chooser, gpointer data) +{ + EomFileChooserPrivate *priv; + char *uri; + char *thumb_path = NULL; + GFile *file; + GFileInfo *file_info; + GdkPixbuf *pixbuf = NULL; + gboolean have_preview = FALSE; + + priv = EOM_FILE_CHOOSER (file_chooser)->priv; + + uri = gtk_file_chooser_get_preview_uri (file_chooser); + if (uri == NULL) { + gtk_file_chooser_set_preview_widget_active (file_chooser, FALSE); + return; + } + + file = g_file_new_for_uri (uri); + file_info = g_file_query_info (file, + G_FILE_ATTRIBUTE_TIME_MODIFIED "," + G_FILE_ATTRIBUTE_STANDARD_SIZE, + 0, NULL, NULL); + g_object_unref (file); + + if ((file_info != NULL) && (priv->thumb_factory != NULL)) { + guint64 mtime; + + mtime = g_file_info_get_attribute_uint64 (file_info, + G_FILE_ATTRIBUTE_TIME_MODIFIED); + thumb_path = mate_desktop_thumbnail_factory_lookup (priv->thumb_factory, uri, mtime); + if (thumb_path == NULL) { + /* read files smaller than 100kb directly */ + if (g_file_info_get_size (file_info) <= 100000) { + /* FIXME: we should then output also the image dimensions */ + thumb_path = gtk_file_chooser_get_preview_filename (file_chooser); + } + } + + if (thumb_path != NULL && g_file_test (thumb_path, G_FILE_TEST_EXISTS)) { + /* try to load and display preview thumbnail */ + pixbuf = gdk_pixbuf_new_from_file (thumb_path, NULL); + + have_preview = (pixbuf != NULL); + + set_preview_pixbuf (EOM_FILE_CHOOSER (file_chooser), pixbuf, + g_file_info_get_size (file_info)); + + if (pixbuf != NULL) { + g_object_unref (pixbuf); + } + } + } + + if (thumb_path != NULL) { + g_free (thumb_path); + } + + g_free (uri); + g_object_unref (file_info); + + gtk_file_chooser_set_preview_widget_active (file_chooser, have_preview); +} + +static void +eom_file_chooser_add_preview (GtkWidget *widget) +{ + EomFileChooserPrivate *priv; + GtkWidget *vbox; + + priv = EOM_FILE_CHOOSER (widget)->priv; + + vbox = gtk_vbox_new (FALSE, 6); + gtk_container_set_border_width (GTK_CONTAINER (vbox), 12); + + priv->image = gtk_image_new (); + /* 128x128 is maximum size of thumbnails */ + gtk_widget_set_size_request (priv->image, 128,128); + + priv->dim_label = gtk_label_new (NULL); + priv->size_label = gtk_label_new (NULL); + priv->creator_label = gtk_label_new (NULL); + + gtk_box_pack_start (GTK_BOX (vbox), priv->image, FALSE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (vbox), priv->dim_label, FALSE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (vbox), priv->size_label, FALSE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (vbox), priv->creator_label, FALSE, TRUE, 0); + + gtk_widget_show_all (vbox); + + gtk_file_chooser_set_preview_widget (GTK_FILE_CHOOSER (widget), vbox); + gtk_file_chooser_set_preview_widget_active (GTK_FILE_CHOOSER (widget), FALSE); + + priv->thumb_factory = mate_desktop_thumbnail_factory_new (MATE_DESKTOP_THUMBNAIL_SIZE_NORMAL); + + g_signal_connect (widget, "update-preview", + G_CALLBACK (update_preview_cb), NULL); +} + +GtkWidget * +eom_file_chooser_new (GtkFileChooserAction action) +{ + GtkWidget *chooser; + gchar *title = NULL; + + chooser = g_object_new (EOM_TYPE_FILE_CHOOSER, + "action", action, + "select-multiple", (action == GTK_FILE_CHOOSER_ACTION_OPEN), + "local-only", FALSE, + NULL); + + switch (action) { + case GTK_FILE_CHOOSER_ACTION_OPEN: + gtk_dialog_add_buttons (GTK_DIALOG (chooser), + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OPEN, GTK_RESPONSE_OK, + NULL); + title = _("Open Image"); + break; + + case GTK_FILE_CHOOSER_ACTION_SAVE: + gtk_dialog_add_buttons (GTK_DIALOG (chooser), + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_SAVE, GTK_RESPONSE_OK, + NULL); + title = _("Save Image"); + break; + + case GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER: + gtk_dialog_add_buttons (GTK_DIALOG (chooser), + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OPEN, GTK_RESPONSE_OK, + NULL); + title = _("Open Folder"); + break; + + default: + g_assert_not_reached (); + } + + if (action != GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) { + eom_file_chooser_add_filter (EOM_FILE_CHOOSER (chooser)); + eom_file_chooser_add_preview (chooser); + } + + if (last_dir[action] != NULL) { + gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (chooser), last_dir [action]); + } + + g_signal_connect (chooser, "response", + G_CALLBACK ((action == GTK_FILE_CHOOSER_ACTION_SAVE) ? + save_response_cb : response_cb), + NULL); + + gtk_window_set_title (GTK_WINDOW (chooser), title); + gtk_dialog_set_default_response (GTK_DIALOG (chooser), GTK_RESPONSE_OK); + + gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (chooser), TRUE); + + return chooser; +} + +GdkPixbufFormat * +eom_file_chooser_get_format (EomFileChooser *chooser) +{ + GtkFileFilter *filter; + GdkPixbufFormat* format; + + g_return_val_if_fail (EOM_IS_FILE_CHOOSER (chooser), NULL); + + filter = gtk_file_chooser_get_filter (GTK_FILE_CHOOSER (chooser)); + if (filter == NULL) + return NULL; + + format = g_object_get_data (G_OBJECT (filter), FILE_FORMAT_KEY); + + return format; +} diff --git a/src/eom-file-chooser.h b/src/eom-file-chooser.h new file mode 100644 index 0000000..dc31fcb --- /dev/null +++ b/src/eom-file-chooser.h @@ -0,0 +1,60 @@ +/* + * 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 _EOM_FILE_CHOOSER_H_ +#define _EOM_FILE_CHOOSER_H_ + +#include <gtk/gtk.h> +#include <gdk-pixbuf/gdk-pixbuf.h> + +G_BEGIN_DECLS + +#define EOM_TYPE_FILE_CHOOSER (eom_file_chooser_get_type ()) +#define EOM_FILE_CHOOSER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), EOM_TYPE_FILE_CHOOSER, EomFileChooser)) +#define EOM_FILE_CHOOSER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), EOM_TYPE_FILE_CHOOSER, EomFileChooserClass)) + +#define EOM_IS_FILE_CHOOSER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), EOM_TYPE_FILE_CHOOSER)) +#define EOM_IS_FILE_CHOOSER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), EOM_TYPE_FILE_CHOOSER)) +#define EOM_FILE_CHOOSER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), EOM_TYPE_FILE_CHOOSER, EomFileChooserClass)) + +typedef struct _EomFileChooser EomFileChooser; +typedef struct _EomFileChooserClass EomFileChooserClass; +typedef struct _EomFileChooserPrivate EomFileChooserPrivate; + +struct _EomFileChooser +{ + GtkFileChooserDialog parent; + + EomFileChooserPrivate *priv; +}; + +struct _EomFileChooserClass +{ + GtkFileChooserDialogClass parent_class; +}; + + +GType eom_file_chooser_get_type (void) G_GNUC_CONST; + +GtkWidget *eom_file_chooser_new (GtkFileChooserAction action); + +GdkPixbufFormat *eom_file_chooser_get_format (EomFileChooser *chooser); + + +G_END_DECLS + +#endif /* _EOM_FILE_CHOOSER_H_ */ diff --git a/src/eom-image-jpeg.c b/src/eom-image-jpeg.c new file mode 100644 index 0000000..ef7a2cc --- /dev/null +++ b/src/eom-image-jpeg.c @@ -0,0 +1,518 @@ +/* This code is based on the jpeg saving code from gdk-pixbuf. Full copyright + * notice is given in the following: + */ +/* GdkPixbuf library - JPEG image loader + * + * Copyright (C) 1999 Michael Zucchi + * Copyright (C) 1999 The Free Software Foundation + * + * Progressive loading code Copyright (C) 1999 Red Hat, Inc. + * + * Authors: Michael Zucchi <[email protected]> + * Federico Mena-Quintero <[email protected]> + * Michael Fulbright <[email protected]> + * + * This library 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 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. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "eom-image-jpeg.h" +#include "eom-image-private.h" + +#if HAVE_JPEG + +#include <sys/types.h> +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <setjmp.h> +#include <jpeglib.h> +#include <jerror.h> +#include "transupp.h" +#include <glib.h> +#include <gdk-pixbuf/gdk-pixbuf.h> +#include <glib/gi18n.h> +#if HAVE_EXIF +#include <libexif/exif-data.h> +#endif + +#ifdef G_OS_WIN32 +#define sigjmp_buf jmp_buf +#define sigsetjmp(env, savesigs) setjmp (env) +#define siglongjmp longjmp +#endif + +typedef enum { + EOM_SAVE_NONE, + EOM_SAVE_JPEG_AS_JPEG, + EOM_SAVE_ANY_AS_JPEG +} EomJpegSaveMethod; + +/* error handler data */ +struct error_handler_data { + struct jpeg_error_mgr pub; + sigjmp_buf setjmp_buffer; + GError **error; + char *filename; +}; + + +static void +fatal_error_handler (j_common_ptr cinfo) +{ + struct error_handler_data *errmgr; + char buffer[JMSG_LENGTH_MAX]; + + errmgr = (struct error_handler_data *) cinfo->err; + + /* Create the message */ + (* cinfo->err->format_message) (cinfo, buffer); + + /* broken check for *error == NULL for robustness against + * crappy JPEG library + */ + if (errmgr->error && *errmgr->error == NULL) { + g_set_error (errmgr->error, + 0, + 0, + "Error interpreting JPEG image file: %s\n\n%s", + g_path_get_basename (errmgr->filename), + buffer); + } + + siglongjmp (errmgr->setjmp_buffer, 1); + + g_assert_not_reached (); +} + + +static void +output_message_handler (j_common_ptr cinfo) +{ + /* This method keeps libjpeg from dumping crap to stderr */ + /* do nothing */ +} + +static void +init_transform_info (EomImage *image, jpeg_transform_info *info) +{ + EomImagePrivate *priv; + EomTransform *composition = NULL; + EomTransformType transformation; + JXFORM_CODE trans_code = JXFORM_NONE; + + g_return_if_fail (EOM_IS_IMAGE (image)); + + priv = image->priv; + + if (priv->trans != NULL && priv->trans_autorotate != NULL) { + composition = eom_transform_compose (priv->trans, + priv->trans_autorotate); + } else if (priv->trans != NULL) { + composition = g_object_ref (priv->trans); + } else if (priv->trans_autorotate != NULL) { + composition = g_object_ref (priv->trans_autorotate); + } + + if (composition != NULL) { + transformation = eom_transform_get_transform_type (composition); + + switch (transformation) { + case EOM_TRANSFORM_ROT_90: + trans_code = JXFORM_ROT_90; + break; + case EOM_TRANSFORM_ROT_270: + trans_code = JXFORM_ROT_270; + break; + case EOM_TRANSFORM_ROT_180: + trans_code = JXFORM_ROT_180; + break; + case EOM_TRANSFORM_FLIP_HORIZONTAL: + trans_code = JXFORM_FLIP_H; + break; + case EOM_TRANSFORM_FLIP_VERTICAL: + trans_code = JXFORM_FLIP_V; + break; + default: + trans_code = JXFORM_NONE; + break; + } + } + + info->transform = trans_code; + info->trim = FALSE; +#if JPEG_LIB_VERSION >= 80 + info->crop = FALSE; +#endif + info->force_grayscale = FALSE; + + g_object_unref (composition); +} + +static gboolean +_save_jpeg_as_jpeg (EomImage *image, const char *file, EomImageSaveInfo *source, + EomImageSaveInfo *target, GError **error) +{ + struct jpeg_decompress_struct srcinfo; + struct jpeg_compress_struct dstinfo; + struct error_handler_data jsrcerr, jdsterr; + jpeg_transform_info transformoption; + jvirt_barray_ptr *src_coef_arrays; + jvirt_barray_ptr *dst_coef_arrays; + FILE *output_file; + FILE *input_file; + EomImagePrivate *priv; + gchar *infile_uri; + + g_return_val_if_fail (EOM_IS_IMAGE (image), FALSE); + g_return_val_if_fail (EOM_IMAGE (image)->priv->file != NULL, FALSE); + + priv = image->priv; + + init_transform_info (image, &transformoption); + + /* Initialize the JPEG decompression object with default error + * handling. */ + jsrcerr.filename = g_file_get_path (priv->file); + srcinfo.err = jpeg_std_error (&(jsrcerr.pub)); + jsrcerr.pub.error_exit = fatal_error_handler; + jsrcerr.pub.output_message = output_message_handler; + jsrcerr.error = error; + + jpeg_create_decompress (&srcinfo); + + /* Initialize the JPEG compression object with default error + * handling. */ + jdsterr.filename = (char *) file; + dstinfo.err = jpeg_std_error (&(jdsterr.pub)); + jdsterr.pub.error_exit = fatal_error_handler; + jdsterr.pub.output_message = output_message_handler; + jdsterr.error = error; + + jpeg_create_compress (&dstinfo); + + dstinfo.err->trace_level = 0; + dstinfo.arith_code = FALSE; + dstinfo.optimize_coding = FALSE; + + jsrcerr.pub.trace_level = jdsterr.pub.trace_level; + srcinfo.mem->max_memory_to_use = dstinfo.mem->max_memory_to_use; + + /* Open the output file. */ + /* FIXME: Make this a GIO aware input manager */ + infile_uri = g_file_get_path (priv->file); + input_file = fopen (infile_uri, "rb"); + if (input_file == NULL) { + g_warning ("Input file not openable: %s\n", infile_uri); + g_free (jsrcerr.filename); + g_free (infile_uri); + return FALSE; + } + g_free (infile_uri); + + output_file = fopen (file, "wb"); + if (output_file == NULL) { + g_warning ("Output file not openable: %s\n", file); + fclose (input_file); + g_free (jsrcerr.filename); + return FALSE; + } + + if (sigsetjmp (jsrcerr.setjmp_buffer, 1)) { + fclose (output_file); + fclose (input_file); + jpeg_destroy_compress (&dstinfo); + jpeg_destroy_decompress (&srcinfo); + g_free (jsrcerr.filename); + return FALSE; + } + + if (sigsetjmp (jdsterr.setjmp_buffer, 1)) { + fclose (output_file); + fclose (input_file); + jpeg_destroy_compress (&dstinfo); + jpeg_destroy_decompress (&srcinfo); + g_free (jsrcerr.filename); + return FALSE; + } + + /* Specify data source for decompression */ + jpeg_stdio_src (&srcinfo, input_file); + + /* Enable saving of extra markers that we want to copy */ + jcopy_markers_setup (&srcinfo, JCOPYOPT_DEFAULT); + + /* Read file header */ + (void) jpeg_read_header (&srcinfo, TRUE); + + /* Any space needed by a transform option must be requested before + * jpeg_read_coefficients so that memory allocation will be done right. + */ + 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 */ + jpeg_stdio_dest (&dstinfo, output_file); + + /* Start compressor (note no image data is actually written here) */ + jpeg_write_coefficients (&dstinfo, dst_coef_arrays); + + /* handle EXIF/IPTC data explicitly */ +#if HAVE_EXIF + /* exif_chunk and exif are mutally exclusvie, this is what we assure here */ + g_assert (priv->exif_chunk == NULL); + if (priv->exif != NULL) + { + unsigned char *exif_buf; + unsigned int exif_buf_len; + + exif_data_save_data (priv->exif, &exif_buf, &exif_buf_len); + jpeg_write_marker (&dstinfo, JPEG_APP0+1, exif_buf, exif_buf_len); + g_free (exif_buf); + } +#else + if (priv->exif_chunk != NULL) { + jpeg_write_marker (&dstinfo, JPEG_APP0+1, priv->exif_chunk, priv->exif_chunk_len); + } +#endif + /* FIXME: Consider IPTC data too */ + + /* Copy to the output file any extra markers that we want to + * preserve */ + jcopy_markers_execute (&srcinfo, &dstinfo, JCOPYOPT_DEFAULT); + + /* 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); + g_free (jsrcerr.filename); + + /* Close files */ + fclose (input_file); + fclose (output_file); + + return TRUE; +} + +static gboolean +_save_any_as_jpeg (EomImage *image, const char *file, EomImageSaveInfo *source, + EomImageSaveInfo *target, GError **error) +{ + EomImagePrivate *priv; + GdkPixbuf *pixbuf; + struct jpeg_compress_struct cinfo; + guchar *buf = NULL; + guchar *ptr; + guchar *pixels = NULL; + JSAMPROW *jbuf; + int y = 0; + volatile int quality = 75; /* default; must be between 0 and 100 */ + int i, j; + int w, h = 0; + int rowstride = 0; + FILE *outfile; + struct error_handler_data jerr; + + g_return_val_if_fail (EOM_IS_IMAGE (image), FALSE); + g_return_val_if_fail (EOM_IMAGE (image)->priv->image != NULL, FALSE); + + priv = image->priv; + pixbuf = priv->image; + + outfile = fopen (file, "wb"); + if (outfile == NULL) { + g_set_error (error, /* FIXME: Better error message */ + GDK_PIXBUF_ERROR, + GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY, + _("Couldn't create temporary file for saving: %s"), + file); + return FALSE; + } + + rowstride = gdk_pixbuf_get_rowstride (pixbuf); + w = gdk_pixbuf_get_width (pixbuf); + h = gdk_pixbuf_get_height (pixbuf); + + /* no image data? abort */ + pixels = gdk_pixbuf_get_pixels (pixbuf); + g_return_val_if_fail (pixels != NULL, FALSE); + + /* allocate a small buffer to convert image data */ + buf = g_try_malloc (w * 3 * sizeof (guchar)); + if (!buf) { + g_set_error (error, + GDK_PIXBUF_ERROR, + GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY, + _("Couldn't allocate memory for loading JPEG file")); + fclose (outfile); + return FALSE; + } + + /* set up error handling */ + jerr.filename = (char *) file; + cinfo.err = jpeg_std_error (&(jerr.pub)); + jerr.pub.error_exit = fatal_error_handler; + jerr.pub.output_message = output_message_handler; + jerr.error = error; + + /* setup compress params */ + jpeg_create_compress (&cinfo); + jpeg_stdio_dest (&cinfo, outfile); + cinfo.image_width = w; + cinfo.image_height = h; + cinfo.input_components = 3; + cinfo.in_color_space = JCS_RGB; + + /* error exit routine */ + if (sigsetjmp (jerr.setjmp_buffer, 1)) { + g_free (buf); + fclose (outfile); + jpeg_destroy_compress (&cinfo); + return FALSE; + } + + /* set desired jpeg quality if available */ + if (target != NULL && target->jpeg_quality >= 0.0) { + quality = (int) MIN (target->jpeg_quality, 1.0) * 100; + } + + /* set up jepg compression parameters */ + jpeg_set_defaults (&cinfo); + jpeg_set_quality (&cinfo, quality, TRUE); + jpeg_start_compress (&cinfo, TRUE); + + /* write EXIF/IPTC data explicitly */ +#if HAVE_EXIF + /* exif_chunk and exif are mutally exclusvie, this is what we assure here */ + g_assert (priv->exif_chunk == NULL); + if (priv->exif != NULL) + { + unsigned char *exif_buf; + unsigned int exif_buf_len; + + exif_data_save_data (priv->exif, &exif_buf, &exif_buf_len); + jpeg_write_marker (&cinfo, 0xe1, exif_buf, exif_buf_len); + g_free (exif_buf); + } +#else + if (priv->exif_chunk != NULL) { + jpeg_write_marker (&cinfo, JPEG_APP0+1, priv->exif_chunk, priv->exif_chunk_len); + } +#endif + /* FIXME: Consider IPTC data too */ + + /* get the start pointer */ + ptr = pixels; + /* go one scanline at a time... and save */ + i = 0; + while (cinfo.next_scanline < cinfo.image_height) { + /* convert scanline from ARGB to RGB packed */ + for (j = 0; j < w; j++) + memcpy (&(buf[j*3]), &(ptr[i*rowstride + j*(rowstride/w)]), 3); + + /* write scanline */ + jbuf = (JSAMPROW *)(&buf); + jpeg_write_scanlines (&cinfo, jbuf, 1); + i++; + y++; + + } + + /* finish off */ + jpeg_finish_compress (&cinfo); + jpeg_destroy_compress(&cinfo); + g_free (buf); + + fclose (outfile); + + return TRUE; +} + +gboolean +eom_image_jpeg_save_file (EomImage *image, const char *file, + EomImageSaveInfo *source, EomImageSaveInfo *target, + GError **error) +{ + EomJpegSaveMethod method = EOM_SAVE_NONE; + gboolean source_is_jpeg = FALSE; + gboolean target_is_jpeg = FALSE; + gboolean result; + + g_return_val_if_fail (source != NULL, FALSE); + + source_is_jpeg = !g_ascii_strcasecmp (source->format, EOM_FILE_FORMAT_JPEG); + + /* determine which method should be used for saving */ + if (target == NULL) { + if (source_is_jpeg) { + method = EOM_SAVE_JPEG_AS_JPEG; + } + } + else { + target_is_jpeg = !g_ascii_strcasecmp (target->format, EOM_FILE_FORMAT_JPEG); + + if (source_is_jpeg && target_is_jpeg) { + if (target->jpeg_quality < 0.0) { + method = EOM_SAVE_JPEG_AS_JPEG; + } + else { + /* reencoding is required, cause quality is set */ + method = EOM_SAVE_ANY_AS_JPEG; + } + } + else if (!source_is_jpeg && target_is_jpeg) { + method = EOM_SAVE_ANY_AS_JPEG; + } + } + + switch (method) { + case EOM_SAVE_JPEG_AS_JPEG: + result = _save_jpeg_as_jpeg (image, file, source, target, error); + break; + case EOM_SAVE_ANY_AS_JPEG: + result = _save_any_as_jpeg (image, file, source, target, error); + break; + default: + result = FALSE; + } + + return result; +} +#endif diff --git a/src/eom-image-jpeg.h b/src/eom-image-jpeg.h new file mode 100644 index 0000000..4e43d0d --- /dev/null +++ b/src/eom-image-jpeg.h @@ -0,0 +1,22 @@ +#ifndef _EOM_IMAGE_JPEG_H_ +#define _EOM_IMAGE_JPEG_H_ + +#if HAVE_JPEG + +#include <glib.h> +#include "eom-image.h" +#include "eom-image-save-info.h" + +/* Saves a source jpeg file in an arbitrary format (as specified by + * target). The target pointer may be NULL, in which case the output + * file is saved as jpeg too. This method tries to be as smart as + * possible. It will save the image as lossless as possible (if the + * target is a jpeg image too). + */ +G_GNUC_INTERNAL +gboolean eom_image_jpeg_save_file (EomImage *image, const char *file, + EomImageSaveInfo *source, EomImageSaveInfo *target, + GError **error); +#endif + +#endif /* _EOM_IMAGE_JPEG_H_ */ diff --git a/src/eom-image-private.h b/src/eom-image-private.h new file mode 100644 index 0000000..794d4ad --- /dev/null +++ b/src/eom-image-private.h @@ -0,0 +1,96 @@ +/* Eye Of Mate - Image Private Data + * + * Copyright (C) 2007 The Free Software Foundation + * + * Author: Lucas Rocha <[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 __EOM_IMAGE_PRIVATE_H__ +#define __EOM_IMAGE_PRIVATE_H__ + +#include "eom-image.h" +#ifdef HAVE_RSVG +#include <librsvg/rsvg.h> +#endif + +G_BEGIN_DECLS + +struct _EomImagePrivate { + GFile *file; + + EomImageStatus status; + EomImageStatus prev_status; + gboolean is_monitored; + EomImageMetadataStatus metadata_status; + + GdkPixbuf *image; + GdkPixbufAnimation *anim; + GdkPixbufAnimationIter *anim_iter; + gboolean is_playing; + GdkPixbuf *thumbnail; +#ifdef HAVE_RSVG + RsvgHandle *svg; +#endif + + gint width; + gint height; + + goffset bytes; + gchar *file_type; + gboolean threadsafe_format; + + /* Holds EXIF raw data */ + guint exif_chunk_len; + guchar *exif_chunk; + + /* Holds IPTC raw data */ + guchar *iptc_chunk; + guint iptc_chunk_len; + + gboolean modified; + +#ifdef HAVE_EXIF + gboolean autorotate; + gint orientation; + ExifData *exif; +#endif +#ifdef HAVE_EXEMPI + XmpPtr xmp; +#endif + +#ifdef HAVE_LCMS + cmsHPROFILE profile; +#endif + + gchar *caption; + + gchar *collate_key; + + GMutex *status_mutex; + + gboolean cancel_loading; + guint data_ref_count; + + GSList *undo_stack; + + EomTransform *trans; + EomTransform *trans_autorotate; +}; + +G_END_DECLS + +#endif /* __EOM_IMAGE_PRIVATE_H__ */ diff --git a/src/eom-image-save-info.c b/src/eom-image-save-info.c new file mode 100644 index 0000000..d368a61 --- /dev/null +++ b/src/eom-image-save-info.c @@ -0,0 +1,150 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> +#include "eom-image-save-info.h" +#include "eom-image-private.h" +#include "eom-pixbuf-util.h" +#include "eom-image.h" + +G_DEFINE_TYPE (EomImageSaveInfo, eom_image_save_info, G_TYPE_OBJECT) + +static void +eom_image_save_info_dispose (GObject *object) +{ + EomImageSaveInfo *info = EOM_IMAGE_SAVE_INFO (object); + + if (info->file != NULL) { + g_object_unref (info->file); + info->file = NULL; + } + + if (info->format != NULL) { + g_free (info->format); + info->format = NULL; + } + + G_OBJECT_CLASS (eom_image_save_info_parent_class)->dispose (object); +} + +static void +eom_image_save_info_init (EomImageSaveInfo *obj) +{ + +} + +static void +eom_image_save_info_class_init (EomImageSaveInfoClass *klass) +{ + GObjectClass *object_class = (GObjectClass*) klass; + + object_class->dispose = eom_image_save_info_dispose; +} + +/* is_local_uri: + * + * Checks if the URI points to a local file system. This tests simply + * if the URI scheme is 'file'. This function is used to ensure that + * we can write to the path-part of the URI with non-VFS aware + * filesystem calls. + */ +static gboolean +is_local_file (GFile *file) +{ + char *scheme; + gboolean ret; + + g_return_val_if_fail (file != NULL, FALSE); + + scheme = g_file_get_uri_scheme (file); + + ret = (g_ascii_strcasecmp (scheme, "file") == 0); + g_free (scheme); + return ret; +} + +static char* +get_save_file_type_by_file (GFile *file) +{ + GdkPixbufFormat *format; + char *type = NULL; + + format = eom_pixbuf_get_format (file); + if (format != NULL) { + type = gdk_pixbuf_format_get_name (format); + } + + return type; +} + +EomImageSaveInfo* +eom_image_save_info_from_image (gpointer data) +{ + EomImageSaveInfo *info = NULL; + EomImage *image; + + image = EOM_IMAGE (data); + + g_return_val_if_fail (EOM_IS_IMAGE (image), NULL); + + info = g_object_new (EOM_TYPE_IMAGE_SAVE_INFO, NULL); + + info->file = eom_image_get_file (image); + info->format = g_strdup (image->priv->file_type); + info->exists = g_file_query_exists (info->file, NULL); + info->local = is_local_file (info->file); + info->has_metadata = eom_image_has_data (image, EOM_IMAGE_DATA_EXIF); + info->modified = eom_image_is_modified (image); + info->overwrite = FALSE; + + info->jpeg_quality = -1.0; + + return info; +} + +EomImageSaveInfo* +eom_image_save_info_from_uri (const char *txt_uri, GdkPixbufFormat *format) +{ + GFile *file; + EomImageSaveInfo *info; + + g_return_val_if_fail (txt_uri != NULL, NULL); + + file = g_file_new_for_uri (txt_uri); + + info = eom_image_save_info_from_file (file, format); + + g_object_unref (file); + + return info; +} + +EomImageSaveInfo* +eom_image_save_info_from_file (GFile *file, GdkPixbufFormat *format) +{ + EomImageSaveInfo *info; + + g_return_val_if_fail (file != NULL, NULL); + + info = g_object_new (EOM_TYPE_IMAGE_SAVE_INFO, NULL); + + info->file = g_object_ref (file); + if (format == NULL) { + info->format = get_save_file_type_by_file (info->file); + } + else { + info->format = gdk_pixbuf_format_get_name (format); + } + info->exists = g_file_query_exists (file, NULL); + info->local = is_local_file (file); + info->has_metadata = FALSE; + info->modified = FALSE; + info->overwrite = FALSE; + + info->jpeg_quality = -1.0; + + g_assert (info->format != NULL); + + return info; +} diff --git a/src/eom-image-save-info.h b/src/eom-image-save-info.h new file mode 100644 index 0000000..8a3c36f --- /dev/null +++ b/src/eom-image-save-info.h @@ -0,0 +1,54 @@ +#ifndef _EOM_IMAGE_SAVE_INFO_H_ +#define _EOM_IMAGE_SAVE_INFO_H_ + +#include <glib-object.h> +#include <gio/gio.h> +#include <gdk-pixbuf/gdk-pixbuf.h> + +G_BEGIN_DECLS + +struct EomImage; + +#define EOM_TYPE_IMAGE_SAVE_INFO (eom_image_save_info_get_type ()) +#define EOM_IMAGE_SAVE_INFO(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), EOM_TYPE_IMAGE_SAVE_INFO, EomImageSaveInfo)) +#define EOM_IMAGE_SAVE_INFO_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), EOM_TYPE_IMAGE_SAVE_INFO, EomImageSaveInfoClass)) +#define EOM_IS_IMAGE_SAVE_INFO(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), EOM_TYPE_IMAGE_SAVE_INFO)) +#define EOM_IS_IMAGE_SAVE_INFO_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), EOM_TYPE_IMAGE_SAVE_INFO)) +#define EOM_IMAGE_SAVE_INFO_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), EOM_TYPE_IMAGE_SAVE_INFO, EomImageSaveInfoClass)) + +typedef struct _EomImageSaveInfo EomImageSaveInfo; +typedef struct _EomImageSaveInfoClass EomImageSaveInfoClass; + +struct _EomImageSaveInfo { + GObject parent; + + GFile *file; + char *format; + gboolean exists; + gboolean local; + gboolean has_metadata; + gboolean modified; + gboolean overwrite; + + float jpeg_quality; /* valid range: [0.0 ... 1.0] */ +}; + +struct _EomImageSaveInfoClass { + GObjectClass parent_klass; +}; + +#define EOM_FILE_FORMAT_JPEG "jpeg" + +GType eom_image_save_info_get_type (void) G_GNUC_CONST; + +EomImageSaveInfo *eom_image_save_info_from_image (gpointer data); + +EomImageSaveInfo *eom_image_save_info_from_uri (const char *uri, + GdkPixbufFormat *format); + +EomImageSaveInfo *eom_image_save_info_from_file (GFile *file, + GdkPixbufFormat *format); + +G_END_DECLS + +#endif /* _EOM_IMAGE_SAVE_INFO_H_ */ diff --git a/src/eom-image.c b/src/eom-image.c new file mode 100644 index 0000000..e57e7cb --- /dev/null +++ b/src/eom-image.c @@ -0,0 +1,2237 @@ +/* Eye Of Mate - Image + * + * Copyright (C) 2006 The Free Software Foundation + * + * Author: Lucas Rocha <[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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#define GDK_PIXBUF_ENABLE_BACKEND + +#include "eom-image.h" +#include "eom-image-private.h" +#include "eom-debug.h" + +#ifdef HAVE_JPEG +#include "eom-image-jpeg.h" +#endif + +#include "eom-marshal.h" +#include "eom-pixbuf-util.h" +#include "eom-metadata-reader.h" +#include "eom-image-save-info.h" +#include "eom-transform.h" +#include "eom-util.h" +#include "eom-jobs.h" +#include "eom-thumbnail.h" + +#include <unistd.h> +#include <string.h> + +#include <glib.h> +#include <glib-object.h> +#include <glib/gi18n.h> +#include <gtk/gtk.h> +#include <gdk-pixbuf/gdk-pixbuf.h> + +#ifdef HAVE_EXIF +#include <libexif/exif-data.h> +#include <libexif/exif-utils.h> +#include <libexif/exif-loader.h> +#endif + +#ifdef HAVE_LCMS +#include <lcms.h> +#ifndef EXIF_TAG_GAMMA +#define EXIF_TAG_GAMMA 0xa500 +#endif +#endif + +#define EOM_IMAGE_GET_PRIVATE(object) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((object), EOM_TYPE_IMAGE, EomImagePrivate)) + +G_DEFINE_TYPE (EomImage, eom_image, G_TYPE_OBJECT) + +enum { + SIGNAL_CHANGED, + SIGNAL_SIZE_PREPARED, + SIGNAL_THUMBNAIL_CHANGED, + SIGNAL_SAVE_PROGRESS, + SIGNAL_NEXT_FRAME, + SIGNAL_FILE_CHANGED, + SIGNAL_LAST +}; + +static gint signals[SIGNAL_LAST]; + +static GList *supported_mime_types = NULL; + +#define EOM_IMAGE_READ_BUFFER_SIZE 65535 + +static void +eom_image_free_mem_private (EomImage *image) +{ + EomImagePrivate *priv; + + priv = image->priv; + + if (priv->status == EOM_IMAGE_STATUS_LOADING) { + eom_image_cancel_load (image); + } else { + if (priv->anim_iter != NULL) { + g_object_unref (priv->anim_iter); + priv->anim_iter = NULL; + } + + if (priv->anim != NULL) { + g_object_unref (priv->anim); + priv->anim = NULL; + } + + priv->is_playing = FALSE; + + if (priv->image != NULL) { + g_object_unref (priv->image); + priv->image = NULL; + } + +#ifdef HAVE_RSVG + if (priv->svg != NULL) { + g_object_unref (priv->svg); + priv->svg = NULL; + } +#endif + +#ifdef HAVE_EXIF + if (priv->exif != NULL) { + exif_data_unref (priv->exif); + priv->exif = NULL; + } +#endif + + if (priv->exif_chunk != NULL) { + g_free (priv->exif_chunk); + priv->exif_chunk = NULL; + } + + priv->exif_chunk_len = 0; + +#ifdef HAVE_EXEMPI + if (priv->xmp != NULL) { + xmp_free (priv->xmp); + priv->xmp = NULL; + } +#endif + +#ifdef HAVE_LCMS + if (priv->profile != NULL) { + cmsCloseProfile (priv->profile); + priv->profile = NULL; + } +#endif + + priv->status = EOM_IMAGE_STATUS_UNKNOWN; + } +} + +static void +eom_image_dispose (GObject *object) +{ + EomImagePrivate *priv; + + priv = EOM_IMAGE (object)->priv; + + eom_image_free_mem_private (EOM_IMAGE (object)); + + if (priv->file) { + g_object_unref (priv->file); + priv->file = NULL; + } + + if (priv->caption) { + g_free (priv->caption); + priv->caption = NULL; + } + + if (priv->collate_key) { + g_free (priv->collate_key); + priv->collate_key = NULL; + } + + if (priv->file_type) { + g_free (priv->file_type); + priv->file_type = NULL; + } + + if (priv->status_mutex) { + g_mutex_free (priv->status_mutex); + priv->status_mutex = NULL; + } + + if (priv->trans) { + g_object_unref (priv->trans); + priv->trans = NULL; + } + + if (priv->trans_autorotate) { + g_object_unref (priv->trans_autorotate); + priv->trans_autorotate = NULL; + } + + if (priv->undo_stack) { + g_slist_foreach (priv->undo_stack, (GFunc) g_object_unref, NULL); + g_slist_free (priv->undo_stack); + priv->undo_stack = NULL; + } + + G_OBJECT_CLASS (eom_image_parent_class)->dispose (object); +} + +static void +eom_image_class_init (EomImageClass *klass) +{ + GObjectClass *object_class = (GObjectClass*) klass; + + object_class->dispose = eom_image_dispose; + + signals[SIGNAL_SIZE_PREPARED] = + g_signal_new ("size-prepared", + EOM_TYPE_IMAGE, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EomImageClass, size_prepared), + NULL, NULL, + eom_marshal_VOID__INT_INT, + G_TYPE_NONE, 2, + G_TYPE_INT, + G_TYPE_INT); + + signals[SIGNAL_CHANGED] = + g_signal_new ("changed", + EOM_TYPE_IMAGE, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EomImageClass, changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[SIGNAL_THUMBNAIL_CHANGED] = + g_signal_new ("thumbnail-changed", + EOM_TYPE_IMAGE, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EomImageClass, thumbnail_changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[SIGNAL_SAVE_PROGRESS] = + g_signal_new ("save-progress", + EOM_TYPE_IMAGE, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EomImageClass, save_progress), + NULL, NULL, + g_cclosure_marshal_VOID__FLOAT, + G_TYPE_NONE, 1, + G_TYPE_FLOAT); + /** + * EomImage::next-frame: + * @img: the object which received the signal. + * @delay: number of milliseconds the current frame will be displayed. + * + * The ::next-frame signal will be emitted each time an animated image + * advances to the next frame. + */ + signals[SIGNAL_NEXT_FRAME] = + g_signal_new ("next-frame", + EOM_TYPE_IMAGE, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EomImageClass, next_frame), + NULL, NULL, + g_cclosure_marshal_VOID__INT, + G_TYPE_NONE, 1, + G_TYPE_INT); + + signals[SIGNAL_FILE_CHANGED] = g_signal_new ("file-changed", + EOM_TYPE_IMAGE, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EomImageClass, file_changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + g_type_class_add_private (object_class, sizeof (EomImagePrivate)); +} + +static void +eom_image_init (EomImage *img) +{ + img->priv = EOM_IMAGE_GET_PRIVATE (img); + + img->priv->file = NULL; + img->priv->image = NULL; + img->priv->anim = NULL; + img->priv->anim_iter = NULL; + img->priv->is_playing = FALSE; + img->priv->thumbnail = NULL; + img->priv->width = -1; + img->priv->height = -1; + img->priv->modified = FALSE; + img->priv->status_mutex = g_mutex_new (); + img->priv->status = EOM_IMAGE_STATUS_UNKNOWN; + img->priv->metadata_status = EOM_IMAGE_METADATA_NOT_READ; + img->priv->is_monitored = FALSE; + img->priv->undo_stack = NULL; + img->priv->trans = NULL; + img->priv->trans_autorotate = NULL; + img->priv->data_ref_count = 0; +#ifdef HAVE_EXIF + img->priv->orientation = 0; + img->priv->autorotate = FALSE; + img->priv->exif = NULL; +#endif +#ifdef HAVE_EXEMPI + img->priv->xmp = NULL; +#endif +#ifdef HAVE_LCMS + img->priv->profile = NULL; +#endif +#ifdef HAVE_RSVG + img->priv->svg = NULL; +#endif +} + +EomImage * +eom_image_new (const char *txt_uri) +{ + EomImage *img; + + img = EOM_IMAGE (g_object_new (EOM_TYPE_IMAGE, NULL)); + + img->priv->file = g_file_new_for_uri (txt_uri); + + return img; +} + +EomImage * +eom_image_new_file (GFile *file) +{ + EomImage *img; + + img = EOM_IMAGE (g_object_new (EOM_TYPE_IMAGE, NULL)); + + img->priv->file = g_object_ref (file); + + return img; +} + +GQuark +eom_image_error_quark (void) +{ + static GQuark q = 0; + + if (q == 0) { + q = g_quark_from_static_string ("eom-image-error-quark"); + } + + return q; +} + +static void +eom_image_update_exif_data (EomImage *image) +{ +#ifdef HAVE_EXIF + EomImagePrivate *priv; + ExifEntry *entry; + ExifByteOrder bo; + + eom_debug (DEBUG_IMAGE_DATA); + + g_return_if_fail (EOM_IS_IMAGE (image)); + + priv = image->priv; + + if (priv->exif == NULL) return; + + bo = exif_data_get_byte_order (priv->exif); + + /* Update image width */ + entry = exif_data_get_entry (priv->exif, EXIF_TAG_PIXEL_X_DIMENSION); + if (entry != NULL && (priv->width >= 0)) { + if (entry->format == EXIF_FORMAT_LONG) + exif_set_long (entry->data, bo, priv->width); + else if (entry->format == EXIF_FORMAT_SHORT) + exif_set_short (entry->data, bo, priv->width); + else + g_warning ("Exif entry has unsupported size"); + } + + /* Update image height */ + entry = exif_data_get_entry (priv->exif, EXIF_TAG_PIXEL_Y_DIMENSION); + if (entry != NULL && (priv->height >= 0)) { + if (entry->format == EXIF_FORMAT_LONG) + exif_set_long (entry->data, bo, priv->height); + else if (entry->format == EXIF_FORMAT_SHORT) + exif_set_short (entry->data, bo, priv->height); + else + g_warning ("Exif entry has unsupported size"); + } + + /* Update image orientation */ + entry = exif_data_get_entry (priv->exif, EXIF_TAG_ORIENTATION); + if (entry != NULL) { + if (entry->format == EXIF_FORMAT_LONG) + exif_set_long (entry->data, bo, 1); + else if (entry->format == EXIF_FORMAT_SHORT) + exif_set_short (entry->data, bo, 1); + else + g_warning ("Exif entry has unsupported size"); + + priv->orientation = 1; + } +#endif +} + +static void +eom_image_real_transform (EomImage *img, + EomTransform *trans, + gboolean is_undo, + EomJob *job) +{ + EomImagePrivate *priv; + GdkPixbuf *transformed; + gboolean modified = FALSE; + + g_return_if_fail (EOM_IS_IMAGE (img)); + g_return_if_fail (EOM_IS_TRANSFORM (trans)); + + priv = img->priv; + + if (priv->image != NULL) { + transformed = eom_transform_apply (trans, priv->image, job); + + g_object_unref (priv->image); + priv->image = transformed; + + priv->width = gdk_pixbuf_get_width (transformed); + priv->height = gdk_pixbuf_get_height (transformed); + + modified = TRUE; + } + + if (priv->thumbnail != NULL) { + transformed = eom_transform_apply (trans, priv->thumbnail, NULL); + + g_object_unref (priv->thumbnail); + priv->thumbnail = transformed; + + modified = TRUE; + } + + if (modified) { + priv->modified = TRUE; + eom_image_update_exif_data (img); + } + + if (priv->trans == NULL) { + g_object_ref (trans); + priv->trans = trans; + } else { + EomTransform *composition; + + composition = eom_transform_compose (priv->trans, trans); + + g_object_unref (priv->trans); + + priv->trans = composition; + } + + if (!is_undo) { + g_object_ref (trans); + priv->undo_stack = g_slist_prepend (priv->undo_stack, trans); + } +} + +static gboolean +do_emit_size_prepared_signal (EomImage *img) +{ + g_signal_emit (img, signals[SIGNAL_SIZE_PREPARED], 0, + img->priv->width, img->priv->height); + return FALSE; +} + +static void +eom_image_emit_size_prepared (EomImage *img) +{ + gdk_threads_add_idle_full (G_PRIORITY_DEFAULT_IDLE, + (GSourceFunc) do_emit_size_prepared_signal, + g_object_ref (img), g_object_unref); +} + +static gboolean +check_loader_threadsafety (GdkPixbufLoader *loader, gboolean *result) +{ + GdkPixbufFormat *format; + gboolean ret_val = FALSE; + + format = gdk_pixbuf_loader_get_format (loader); + if (format) { + ret_val = TRUE; + if (result) + /* FIXME: We should not be accessing this struct internals + * directly. Keep track of bug #469209 to fix that. */ + *result = format->flags & GDK_PIXBUF_FORMAT_THREADSAFE; + } + + return ret_val; +} + +static void +eom_image_pre_size_prepared (GdkPixbufLoader *loader, + gint width, + gint height, + gpointer data) +{ + EomImage *img; + + eom_debug (DEBUG_IMAGE_LOAD); + + g_return_if_fail (EOM_IS_IMAGE (data)); + + img = EOM_IMAGE (data); + check_loader_threadsafety (loader, &img->priv->threadsafe_format); +} + +static void +eom_image_size_prepared (GdkPixbufLoader *loader, + gint width, + gint height, + gpointer data) +{ + EomImage *img; + + eom_debug (DEBUG_IMAGE_LOAD); + + g_return_if_fail (EOM_IS_IMAGE (data)); + + img = EOM_IMAGE (data); + + g_mutex_lock (img->priv->status_mutex); + + img->priv->width = width; + img->priv->height = height; + + g_mutex_unlock (img->priv->status_mutex); + +#ifdef HAVE_EXIF + if (img->priv->threadsafe_format && (!img->priv->autorotate || img->priv->exif)) +#else + if (img->priv->threadsafe_format) +#endif + eom_image_emit_size_prepared (img); +} + +static EomMetadataReader* +check_for_metadata_img_format (EomImage *img, guchar *buffer, guint bytes_read) +{ + EomMetadataReader *md_reader = NULL; + + eom_debug_message (DEBUG_IMAGE_DATA, "Check image format for jpeg: %x%x - length: %i", + buffer[0], buffer[1], bytes_read); + + if (bytes_read >= 2) { + /* SOI (start of image) marker for JPEGs is 0xFFD8 */ + if ((buffer[0] == 0xFF) && (buffer[1] == 0xD8)) { + md_reader = eom_metadata_reader_new (EOM_METADATA_JPEG); + } + if (bytes_read >= 8 && + memcmp (buffer, "\x89PNG\x0D\x0A\x1a\x0A", 8) == 0) { + md_reader = eom_metadata_reader_new (EOM_METADATA_PNG); + } + } + + return md_reader; +} + +static gboolean +eom_image_needs_transformation (EomImage *img) +{ + g_return_val_if_fail (EOM_IS_IMAGE (img), FALSE); + + return (img->priv->trans != NULL || img->priv->trans_autorotate != NULL); +} + +static gboolean +eom_image_apply_transformations (EomImage *img, GError **error) +{ + GdkPixbuf *transformed = NULL; + EomTransform *composition = NULL; + EomImagePrivate *priv; + + g_return_val_if_fail (EOM_IS_IMAGE (img), FALSE); + + priv = img->priv; + + if (priv->trans == NULL && priv->trans_autorotate == NULL) { + return TRUE; + } + + if (priv->image == NULL) { + g_set_error (error, + EOM_IMAGE_ERROR, + EOM_IMAGE_ERROR_NOT_LOADED, + _("Transformation on unloaded image.")); + + return FALSE; + } + + if (priv->trans != NULL && priv->trans_autorotate != NULL) { + composition = eom_transform_compose (priv->trans, + priv->trans_autorotate); + } else if (priv->trans != NULL) { + composition = g_object_ref (priv->trans); + } else if (priv->trans_autorotate != NULL) { + composition = g_object_ref (priv->trans_autorotate); + } + + if (composition != NULL) { + transformed = eom_transform_apply (composition, priv->image, NULL); + } + + g_object_unref (priv->image); + priv->image = transformed; + + if (transformed != NULL) { + priv->width = gdk_pixbuf_get_width (priv->image); + priv->height = gdk_pixbuf_get_height (priv->image); + } else { + g_set_error (error, + EOM_IMAGE_ERROR, + EOM_IMAGE_ERROR_GENERIC, + _("Transformation failed.")); + } + + g_object_unref (composition); + + return (transformed != NULL); +} + +static void +eom_image_get_file_info (EomImage *img, + goffset *bytes, + gchar **mime_type, + GError **error) +{ + GFileInfo *file_info; + + file_info = g_file_query_info (img->priv->file, + G_FILE_ATTRIBUTE_STANDARD_SIZE "," + G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, + 0, NULL, error); + + if (file_info == NULL) { + if (bytes) + *bytes = 0; + + if (mime_type) + *mime_type = NULL; + + g_set_error (error, + EOM_IMAGE_ERROR, + EOM_IMAGE_ERROR_VFS, + "Error in getting image file info"); + } else { + if (bytes) + *bytes = g_file_info_get_size (file_info); + + if (mime_type) + *mime_type = g_strdup (g_file_info_get_content_type (file_info)); + g_object_unref (file_info); + } +} + +#ifdef HAVE_LCMS +void +eom_image_apply_display_profile (EomImage *img, cmsHPROFILE screen) +{ + EomImagePrivate *priv; + cmsHTRANSFORM transform; + gint row, width, rows, stride; + guchar *p; + + g_return_if_fail (img != NULL); + + priv = img->priv; + + if (screen == NULL || priv->profile == NULL) return; + + /* TODO: support other colorspaces than RGB */ + if (cmsGetColorSpace (priv->profile) != icSigRgbData || + cmsGetColorSpace (screen) != icSigRgbData) { + eom_debug_message (DEBUG_LCMS, "One or both ICC profiles not in RGB colorspace; not correcting"); + return; + } + + /* TODO: find the right way to colorcorrect RGBA images */ + if (gdk_pixbuf_get_has_alpha (priv->image)) { + eom_debug_message (DEBUG_LCMS, "Colorcorrecting RGBA images is unsupported."); + return; + } + + transform = cmsCreateTransform (priv->profile, + TYPE_RGB_8, + screen, + TYPE_RGB_8, + INTENT_PERCEPTUAL, + 0); + + if (G_LIKELY (transform != NULL)) { + rows = gdk_pixbuf_get_height (priv->image); + width = gdk_pixbuf_get_width (priv->image); + stride = gdk_pixbuf_get_rowstride (priv->image); + p = gdk_pixbuf_get_pixels (priv->image); + + for (row = 0; row < rows; ++row) { + cmsDoTransform (transform, p, p, width); + p += stride; + } + cmsDeleteTransform (transform); + } +} + +static void +eom_image_set_icc_data (EomImage *img, EomMetadataReader *md_reader) +{ + EomImagePrivate *priv = img->priv; + + priv->profile = eom_metadata_reader_get_icc_profile (md_reader); + + +} +#endif + +#ifdef HAVE_EXIF +static void +eom_image_set_orientation (EomImage *img) +{ + EomImagePrivate *priv; + ExifData* exif; + + g_return_if_fail (EOM_IS_IMAGE (img)); + + priv = img->priv; + + exif = (ExifData*) eom_image_get_exif_info (img); + + if (exif != NULL) { + ExifByteOrder o = exif_data_get_byte_order (exif); + + ExifEntry *entry = exif_data_get_entry (exif, + EXIF_TAG_ORIENTATION); + + if (entry && entry->data != NULL) { + priv->orientation = exif_get_short (entry->data, o); + } + } + + /* exif_data_unref handles NULL values like g_free */ + exif_data_unref (exif); + + if (priv->orientation > 4 && + priv->orientation < 9) { + gint tmp; + + tmp = priv->width; + priv->width = priv->height; + priv->height = tmp; + } +} + +static void +eom_image_real_autorotate (EomImage *img) +{ + static const EomTransformType lookup[8] = {EOM_TRANSFORM_NONE, + EOM_TRANSFORM_FLIP_HORIZONTAL, + EOM_TRANSFORM_ROT_180, + EOM_TRANSFORM_FLIP_VERTICAL, + EOM_TRANSFORM_TRANSPOSE, + EOM_TRANSFORM_ROT_90, + EOM_TRANSFORM_TRANSVERSE, + EOM_TRANSFORM_ROT_270}; + EomImagePrivate *priv; + EomTransformType type; + + g_return_if_fail (EOM_IS_IMAGE (img)); + + priv = img->priv; + + type = (priv->orientation >= 1 && priv->orientation <= 8 ? + lookup[priv->orientation - 1] : EOM_TRANSFORM_NONE); + + if (type != EOM_TRANSFORM_NONE) { + img->priv->trans_autorotate = eom_transform_new (type); + } + + /* Disable auto orientation for next loads */ + priv->autorotate = FALSE; +} + +void +eom_image_autorotate (EomImage *img) +{ + g_return_if_fail (EOM_IS_IMAGE (img)); + + /* Schedule auto orientation */ + img->priv->autorotate = TRUE; +} +#endif + +#ifdef HAVE_EXEMPI +static void +eom_image_set_xmp_data (EomImage *img, EomMetadataReader *md_reader) +{ + EomImagePrivate *priv; + + g_return_if_fail (EOM_IS_IMAGE (img)); + + priv = img->priv; + + if (priv->xmp) { + xmp_free (priv->xmp); + } + priv->xmp = eom_metadata_reader_get_xmp_data (md_reader); +} +#endif + +static void +eom_image_set_exif_data (EomImage *img, EomMetadataReader *md_reader) +{ + EomImagePrivate *priv; + + g_return_if_fail (EOM_IS_IMAGE (img)); + + priv = img->priv; + +#ifdef HAVE_EXIF + g_mutex_lock (priv->status_mutex); + if (priv->exif) { + exif_data_unref (priv->exif); + } + priv->exif = eom_metadata_reader_get_exif_data (md_reader); + g_mutex_unlock (priv->status_mutex); + + priv->exif_chunk = NULL; + priv->exif_chunk_len = 0; + + /* EXIF data is already available, set the image orientation */ + if (priv->autorotate) { + eom_image_set_orientation (img); + + /* Emit size prepared signal if we have the size */ + if (priv->width > 0 && + priv->height > 0) { + eom_image_emit_size_prepared (img); + } + } +#else + if (priv->exif_chunk) { + g_free (priv->exif_chunk); + } + eom_metadata_reader_get_exif_chunk (md_reader, + &priv->exif_chunk, + &priv->exif_chunk_len); +#endif +} + +/* + * Attempts to get the image dimensions from the thumbnail. + * Returns FALSE if this information is not found. + **/ +static gboolean +eom_image_get_dimension_from_thumbnail (EomImage *image, + gint *width, + gint *height) +{ + if (image->priv->thumbnail == NULL) + return FALSE; + + *width = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (image->priv->thumbnail), + EOM_THUMBNAIL_ORIGINAL_WIDTH)); + *height = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (image->priv->thumbnail), + EOM_THUMBNAIL_ORIGINAL_HEIGHT)); + + return (*width || *height); +} + +static gboolean +eom_image_real_load (EomImage *img, + guint data2read, + EomJob *job, + GError **error) +{ + EomImagePrivate *priv; + GFileInputStream *input_stream; + EomMetadataReader *md_reader = NULL; + GdkPixbufFormat *format; + gchar *mime_type; + GdkPixbufLoader *loader = NULL; + guchar *buffer; + goffset bytes_read, bytes_read_total = 0; + gboolean failed = FALSE; + gboolean first_run = TRUE; + gboolean set_metadata = TRUE; + gboolean read_image_data = (data2read & EOM_IMAGE_DATA_IMAGE); + gboolean read_only_dimension = (data2read & EOM_IMAGE_DATA_DIMENSION) && + ((data2read ^ EOM_IMAGE_DATA_DIMENSION) == 0); + + + priv = img->priv; + + g_assert (!read_image_data || priv->image == NULL); + + if (read_image_data && priv->file_type != NULL) { + g_free (priv->file_type); + priv->file_type = NULL; + } + + priv->threadsafe_format = FALSE; + + eom_image_get_file_info (img, &priv->bytes, &mime_type, error); + + if (error && *error) { + g_free (mime_type); + return FALSE; + } + + if (read_only_dimension) { + gint width, height; + gboolean done; + + done = eom_image_get_dimension_from_thumbnail (img, + &width, + &height); + + if (done) { + priv->width = width; + priv->height = height; + + g_free (mime_type); + return TRUE; + } + } + + input_stream = g_file_read (priv->file, NULL, error); + + if (input_stream == NULL) { + g_free (mime_type); + + if (error != NULL) { + g_clear_error (error); + g_set_error (error, + EOM_IMAGE_ERROR, + EOM_IMAGE_ERROR_VFS, + "Failed to open input stream for file"); + } + return FALSE; + } + + buffer = g_new0 (guchar, EOM_IMAGE_READ_BUFFER_SIZE); + + if (read_image_data || read_only_dimension) { + gboolean checked_threadsafety = FALSE; + +#ifdef HAVE_RSVG + if (priv->svg != NULL) { + g_object_unref (priv->svg); + priv->svg = NULL; + } + + if (!strcmp (mime_type, "image/svg+xml")) { + gchar *file_path; + /* Keep the object for rendering */ + priv->svg = rsvg_handle_new (); + file_path = g_file_get_path (priv->file); + rsvg_handle_set_base_uri (priv->svg, file_path); + g_free (file_path); + } +#endif + loader = gdk_pixbuf_loader_new_with_mime_type (mime_type, error); + + if (error && *error) { + g_error_free (*error); + *error = NULL; + + loader = gdk_pixbuf_loader_new (); + } else { + /* The mimetype-based loader should know the + * format here already. */ + checked_threadsafety = check_loader_threadsafety (loader, &priv->threadsafe_format); + } + + /* This is used to detect non-threadsafe loaders and disable + * any possible asyncronous task that could bring deadlocks + * to image loading process. */ + if (!checked_threadsafety) + g_signal_connect (loader, + "size-prepared", + G_CALLBACK (eom_image_pre_size_prepared), + img); + + g_signal_connect_object (G_OBJECT (loader), + "size-prepared", + G_CALLBACK (eom_image_size_prepared), + img, + 0); + } + g_free (mime_type); + + while (!priv->cancel_loading) { + /* FIXME: make this async */ + bytes_read = g_input_stream_read (G_INPUT_STREAM (input_stream), + buffer, + EOM_IMAGE_READ_BUFFER_SIZE, + NULL, error); + + if (bytes_read == 0) { + /* End of the file */ + break; + } else if (bytes_read == -1) { + failed = TRUE; + + g_set_error (error, + EOM_IMAGE_ERROR, + EOM_IMAGE_ERROR_VFS, + "Failed to read from input stream"); + + break; + } + + if ((read_image_data || read_only_dimension)) { + if (!gdk_pixbuf_loader_write (loader, buffer, bytes_read, error)) { + failed = TRUE; + break; + } +#ifdef HAVE_RSVG + if (eom_image_is_svg (img) && + !rsvg_handle_write (priv->svg, buffer, bytes_read, error)) { + failed = TRUE; + break; + } +#endif + } + + bytes_read_total += bytes_read; + + if (job != NULL) { + float progress = (float) bytes_read_total / (float) priv->bytes; + eom_job_set_progress (job, progress); + } + + if (first_run) { + md_reader = check_for_metadata_img_format (img, buffer, bytes_read); + + if (md_reader == NULL) { + if (data2read == EOM_IMAGE_DATA_EXIF) { + g_set_error (error, + EOM_IMAGE_ERROR, + EOM_IMAGE_ERROR_GENERIC, + _("EXIF not supported for this file format.")); + break; + } + + if (priv->threadsafe_format) + eom_image_emit_size_prepared (img); + + priv->metadata_status = EOM_IMAGE_METADATA_NOT_AVAILABLE; + } + + first_run = FALSE; + } + + if (md_reader != NULL) { + eom_metadata_reader_consume (md_reader, buffer, bytes_read); + + if (eom_metadata_reader_finished (md_reader)) { + if (set_metadata) { + eom_image_set_exif_data (img, md_reader); + +#ifdef HAVE_LCMS + eom_image_set_icc_data (img, md_reader); +#endif + +#ifdef HAVE_EXEMPI + eom_image_set_xmp_data (img, md_reader); +#endif + set_metadata = FALSE; + priv->metadata_status = EOM_IMAGE_METADATA_READY; + } + + if (data2read == EOM_IMAGE_DATA_EXIF) + break; + } + } + + if (read_only_dimension && + eom_image_has_data (img, EOM_IMAGE_DATA_DIMENSION)) { + break; + } + } + + if (read_image_data || read_only_dimension) { + if (failed) { + gdk_pixbuf_loader_close (loader, NULL); + } else if (!gdk_pixbuf_loader_close (loader, error)) { + if (gdk_pixbuf_loader_get_pixbuf (loader) != NULL) { + /* Clear error in order to support partial + * images as well. */ + g_clear_error (error); + } + } +#ifdef HAVE_RSVG + if (eom_image_is_svg (img)) + rsvg_handle_close (priv->svg, error); +#endif + } + + g_free (buffer); + + g_object_unref (G_OBJECT (input_stream)); + + failed = (failed || + priv->cancel_loading || + bytes_read_total == 0 || + (error && *error != NULL)); + + if (failed) { + if (priv->cancel_loading) { + priv->cancel_loading = FALSE; + priv->status = EOM_IMAGE_STATUS_UNKNOWN; + } else { + priv->status = EOM_IMAGE_STATUS_FAILED; + } + } else if (read_image_data) { + if (priv->image != NULL) { + g_object_unref (priv->image); + } + + priv->anim = gdk_pixbuf_loader_get_animation (loader); + + if (gdk_pixbuf_animation_is_static_image (priv->anim)) { + priv->image = gdk_pixbuf_animation_get_static_image (priv->anim); + priv->anim = NULL; + } else { + priv->anim_iter = gdk_pixbuf_animation_get_iter (priv->anim,NULL); + priv->image = gdk_pixbuf_animation_iter_get_pixbuf (priv->anim_iter); + } + + if (G_LIKELY (priv->image != NULL)) { + g_object_ref (priv->image); + + priv->width = gdk_pixbuf_get_width (priv->image); + priv->height = gdk_pixbuf_get_height (priv->image); + + format = gdk_pixbuf_loader_get_format (loader); + + if (format != NULL) { + priv->file_type = gdk_pixbuf_format_get_name (format); + } + + /* If it's non-threadsafe loader, then trigger window + * showing in the end of the process. */ + if (!priv->threadsafe_format) + eom_image_emit_size_prepared (img); + } else { + /* Some loaders don't report errors correctly. + * Error will be set below. */ + failed = TRUE; + priv->status = EOM_IMAGE_STATUS_FAILED; + } + } + + if (loader != NULL) { + g_object_unref (loader); + } + + if (md_reader != NULL) { + g_object_unref (md_reader); + md_reader = NULL; + } + + /* Catch-all in case of poor-error reporting */ + if (failed && error && *error == NULL) { + g_set_error (error, + EOM_IMAGE_ERROR, + EOM_IMAGE_ERROR_GENERIC, + _("Image loading failed.")); + } + + return !failed; +} + +gboolean +eom_image_has_data (EomImage *img, EomImageData req_data) +{ + EomImagePrivate *priv; + gboolean has_data = TRUE; + + g_return_val_if_fail (EOM_IS_IMAGE (img), FALSE); + + priv = img->priv; + + if ((req_data & EOM_IMAGE_DATA_IMAGE) > 0) { + req_data = (req_data & !EOM_IMAGE_DATA_IMAGE); + has_data = has_data && (priv->image != NULL); + } + + if ((req_data & EOM_IMAGE_DATA_DIMENSION) > 0 ) { + req_data = (req_data & !EOM_IMAGE_DATA_DIMENSION); + has_data = has_data && (priv->width >= 0) && (priv->height >= 0); + } + + if ((req_data & EOM_IMAGE_DATA_EXIF) > 0) { + req_data = (req_data & !EOM_IMAGE_DATA_EXIF); +#ifdef HAVE_EXIF + has_data = has_data && (priv->exif != NULL); +#else + has_data = has_data && (priv->exif_chunk != NULL); +#endif + } + + if ((req_data & EOM_IMAGE_DATA_XMP) > 0) { + req_data = (req_data & !EOM_IMAGE_DATA_XMP); +#ifdef HAVE_EXEMPI + has_data = has_data && (priv->xmp != NULL); +#endif + } + + if (req_data != 0) { + g_warning ("Asking for unknown data, remaining: %i\n", req_data); + has_data = FALSE; + } + + return has_data; +} + +gboolean +eom_image_load (EomImage *img, EomImageData data2read, EomJob *job, GError **error) +{ + EomImagePrivate *priv; + gboolean success = FALSE; + + eom_debug (DEBUG_IMAGE_LOAD); + + g_return_val_if_fail (EOM_IS_IMAGE (img), FALSE); + + priv = EOM_IMAGE (img)->priv; + + if (data2read == 0) { + return TRUE; + } + + if (eom_image_has_data (img, data2read)) { + return TRUE; + } + + priv->status = EOM_IMAGE_STATUS_LOADING; + + success = eom_image_real_load (img, data2read, job, error); + +#ifdef HAVE_EXIF + /* Check that the metadata was loaded at least once before + * trying to autorotate. */ + if (priv->autorotate && + priv->metadata_status == EOM_IMAGE_METADATA_READY) { + eom_image_real_autorotate (img); + } +#endif + + if (success && eom_image_needs_transformation (img)) { + success = eom_image_apply_transformations (img, error); + } + + if (success) { + priv->status = EOM_IMAGE_STATUS_LOADED; + } else { + priv->status = EOM_IMAGE_STATUS_FAILED; + } + + return success; +} + +void +eom_image_set_thumbnail (EomImage *img, GdkPixbuf *thumbnail) +{ + EomImagePrivate *priv; + + g_return_if_fail (EOM_IS_IMAGE (img)); + g_return_if_fail (GDK_IS_PIXBUF (thumbnail) || thumbnail == NULL); + + priv = img->priv; + + if (priv->thumbnail != NULL) { + g_object_unref (priv->thumbnail); + priv->thumbnail = NULL; + } + + if (thumbnail != NULL && priv->trans != NULL) { + priv->thumbnail = eom_transform_apply (priv->trans, thumbnail, NULL); + } else { + priv->thumbnail = thumbnail; + + if (thumbnail != NULL) { + g_object_ref (priv->thumbnail); + } + } + + if (priv->thumbnail != NULL) { + g_signal_emit (img, signals[SIGNAL_THUMBNAIL_CHANGED], 0); + } +} + +GdkPixbuf * +eom_image_get_pixbuf (EomImage *img) +{ + GdkPixbuf *image = NULL; + + g_return_val_if_fail (EOM_IS_IMAGE (img), NULL); + + g_mutex_lock (img->priv->status_mutex); + image = img->priv->image; + g_mutex_unlock (img->priv->status_mutex); + + if (image != NULL) { + g_object_ref (image); + } + + return image; +} + +#ifdef HAVE_LCMS +cmsHPROFILE +eom_image_get_profile (EomImage *img) +{ + g_return_val_if_fail (EOM_IS_IMAGE (img), NULL); + + return img->priv->profile; +} +#endif + +GdkPixbuf * +eom_image_get_thumbnail (EomImage *img) +{ + g_return_val_if_fail (EOM_IS_IMAGE (img), NULL); + + if (img->priv->thumbnail != NULL) { + g_object_ref (img->priv->thumbnail); + + return img->priv->thumbnail; + } + + return NULL; +} + +void +eom_image_get_size (EomImage *img, int *width, int *height) +{ + EomImagePrivate *priv; + + g_return_if_fail (EOM_IS_IMAGE (img)); + + priv = img->priv; + + *width = priv->width; + *height = priv->height; +} + +void +eom_image_transform (EomImage *img, EomTransform *trans, EomJob *job) +{ + eom_image_real_transform (img, trans, FALSE, job); +} + +void +eom_image_undo (EomImage *img) +{ + EomImagePrivate *priv; + EomTransform *trans; + EomTransform *inverse; + + g_return_if_fail (EOM_IS_IMAGE (img)); + + priv = img->priv; + + if (priv->undo_stack != NULL) { + trans = EOM_TRANSFORM (priv->undo_stack->data); + + inverse = eom_transform_reverse (trans); + + eom_image_real_transform (img, inverse, TRUE, NULL); + + priv->undo_stack = g_slist_delete_link (priv->undo_stack, priv->undo_stack); + + g_object_unref (trans); + g_object_unref (inverse); + + if (eom_transform_is_identity (priv->trans)) { + g_object_unref (priv->trans); + priv->trans = NULL; + } + } + + priv->modified = (priv->undo_stack != NULL); +} + +static GFile * +tmp_file_get (void) +{ + GFile *tmp_file; + char *tmp_file_path; + gint fd; + + tmp_file_path = g_build_filename (g_get_tmp_dir (), "eom-save-XXXXXX", NULL); + fd = g_mkstemp (tmp_file_path); + if (fd == -1) { + g_free (tmp_file_path); + return NULL; + } + else { + tmp_file = g_file_new_for_path (tmp_file_path); + g_free (tmp_file_path); + return tmp_file; + } +} + +static void +transfer_progress_cb (goffset cur_bytes, + goffset total_bytes, + gpointer user_data) +{ + EomImage *image = EOM_IMAGE (user_data); + + if (cur_bytes > 0) { + g_signal_emit (G_OBJECT(image), + signals[SIGNAL_SAVE_PROGRESS], + 0, + (gfloat) cur_bytes / (gfloat) total_bytes); + } +} + +static gboolean +tmp_file_move_to_uri (EomImage *image, + GFile *tmpfile, + GFile *file, + gboolean overwrite, + GError **error) +{ + gboolean result; + GError *ioerror = NULL; + + result = g_file_move (tmpfile, + file, + (overwrite ? G_FILE_COPY_OVERWRITE : 0) | + G_FILE_COPY_ALL_METADATA, + NULL, + (GFileProgressCallback) transfer_progress_cb, + image, + &ioerror); + + if (result == FALSE) { + if (g_error_matches (ioerror, G_IO_ERROR, + G_IO_ERROR_EXISTS)) { + g_set_error (error, EOM_IMAGE_ERROR, + EOM_IMAGE_ERROR_FILE_EXISTS, + "File exists"); + } else { + g_set_error (error, EOM_IMAGE_ERROR, + EOM_IMAGE_ERROR_VFS, + "VFS error moving the temp file"); + } + g_clear_error (&ioerror); + } + + return result; +} + +static gboolean +tmp_file_delete (GFile *tmpfile) +{ + gboolean result; + GError *err = NULL; + + if (tmpfile == NULL) return FALSE; + + result = g_file_delete (tmpfile, NULL, &err); + if (result == FALSE) { + char *tmpfile_path; + if (err != NULL) { + if (err->code == G_IO_ERROR_NOT_FOUND) { + g_error_free (err); + return TRUE; + } + g_error_free (err); + } + tmpfile_path = g_file_get_path (tmpfile); + g_warning ("Couldn't delete temporary file: %s", tmpfile_path); + g_free (tmpfile_path); + } + + return result; +} + +static void +eom_image_reset_modifications (EomImage *image) +{ + EomImagePrivate *priv; + + g_return_if_fail (EOM_IS_IMAGE (image)); + + priv = image->priv; + + g_slist_foreach (priv->undo_stack, (GFunc) g_object_unref, NULL); + g_slist_free (priv->undo_stack); + priv->undo_stack = NULL; + + if (priv->trans != NULL) { + g_object_unref (priv->trans); + priv->trans = NULL; + } + + if (priv->trans_autorotate != NULL) { + g_object_unref (priv->trans_autorotate); + priv->trans_autorotate = NULL; + } + + priv->modified = FALSE; +} + +static void +eom_image_link_with_target (EomImage *image, EomImageSaveInfo *target) +{ + EomImagePrivate *priv; + + g_return_if_fail (EOM_IS_IMAGE (image)); + g_return_if_fail (EOM_IS_IMAGE_SAVE_INFO (target)); + + priv = image->priv; + + /* update file location */ + if (priv->file != NULL) { + g_object_unref (priv->file); + } + priv->file = g_object_ref (target->file); + + /* Clear caption and caption key, these will be + * updated on next eom_image_get_caption call. + */ + if (priv->caption != NULL) { + g_free (priv->caption); + priv->caption = NULL; + } + if (priv->collate_key != NULL) { + g_free (priv->collate_key); + priv->collate_key = NULL; + } + + /* update file format */ + if (priv->file_type != NULL) { + g_free (priv->file_type); + } + priv->file_type = g_strdup (target->format); +} + +gboolean +eom_image_save_by_info (EomImage *img, EomImageSaveInfo *source, GError **error) +{ + EomImagePrivate *priv; + EomImageStatus prev_status; + gboolean success = FALSE; + GFile *tmp_file; + char *tmp_file_path; + + g_return_val_if_fail (EOM_IS_IMAGE (img), FALSE); + g_return_val_if_fail (EOM_IS_IMAGE_SAVE_INFO (source), FALSE); + + priv = img->priv; + + prev_status = priv->status; + + /* Image is now being saved */ + priv->status = EOM_IMAGE_STATUS_SAVING; + + /* see if we need any saving at all */ + if (source->exists && !source->modified) { + return TRUE; + } + + /* fail if there is no image to save */ + if (priv->image == NULL) { + g_set_error (error, EOM_IMAGE_ERROR, + EOM_IMAGE_ERROR_NOT_LOADED, + _("No image loaded.")); + return FALSE; + } + + /* generate temporary file */ + tmp_file = tmp_file_get (); + + if (tmp_file == NULL) { + g_set_error (error, EOM_IMAGE_ERROR, + EOM_IMAGE_ERROR_TMP_FILE_FAILED, + _("Temporary file creation failed.")); + return FALSE; + } + + tmp_file_path = g_file_get_path (tmp_file); + +#ifdef HAVE_JPEG + /* determine kind of saving */ + if ((g_ascii_strcasecmp (source->format, EOM_FILE_FORMAT_JPEG) == 0) && + source->exists && source->modified) + { + success = eom_image_jpeg_save_file (img, tmp_file_path, source, NULL, error); + } +#endif + + if (!success && (*error == NULL)) { + success = gdk_pixbuf_save (priv->image, tmp_file_path, source->format, error, NULL); + } + + if (success) { + /* try to move result file to target uri */ + success = tmp_file_move_to_uri (img, tmp_file, priv->file, TRUE /*overwrite*/, error); + } + + if (success) { + eom_image_reset_modifications (img); + } + + tmp_file_delete (tmp_file); + + g_free (tmp_file_path); + g_object_unref (tmp_file); + + priv->status = prev_status; + + return success; +} + +static gboolean +eom_image_copy_file (EomImage *image, EomImageSaveInfo *source, EomImageSaveInfo *target, GError **error) +{ + gboolean result; + GError *ioerror = NULL; + + g_return_val_if_fail (EOM_IS_IMAGE_SAVE_INFO (source), FALSE); + g_return_val_if_fail (EOM_IS_IMAGE_SAVE_INFO (target), FALSE); + + result = g_file_copy (source->file, + target->file, + (target->overwrite ? G_FILE_COPY_OVERWRITE : 0) | + G_FILE_COPY_ALL_METADATA, + NULL, + EOM_IS_IMAGE (image) ? transfer_progress_cb :NULL, + image, + &ioerror); + + if (result == FALSE) { + if (ioerror->code == G_IO_ERROR_EXISTS) { + g_set_error (error, EOM_IMAGE_ERROR, + EOM_IMAGE_ERROR_FILE_EXISTS, + "%s", ioerror->message); + } else { + g_set_error (error, EOM_IMAGE_ERROR, + EOM_IMAGE_ERROR_VFS, + "%s", ioerror->message); + } + g_error_free (ioerror); + } + + return result; +} + +gboolean +eom_image_save_as_by_info (EomImage *img, EomImageSaveInfo *source, EomImageSaveInfo *target, GError **error) +{ + EomImagePrivate *priv; + gboolean success = FALSE; + char *tmp_file_path; + GFile *tmp_file; + gboolean direct_copy = FALSE; + + g_return_val_if_fail (EOM_IS_IMAGE (img), FALSE); + g_return_val_if_fail (EOM_IS_IMAGE_SAVE_INFO (source), FALSE); + g_return_val_if_fail (EOM_IS_IMAGE_SAVE_INFO (target), FALSE); + + priv = img->priv; + + /* fail if there is no image to save */ + if (priv->image == NULL) { + g_set_error (error, + EOM_IMAGE_ERROR, + EOM_IMAGE_ERROR_NOT_LOADED, + _("No image loaded.")); + + return FALSE; + } + + /* generate temporary file name */ + tmp_file = tmp_file_get (); + + if (tmp_file == NULL) { + g_set_error (error, + EOM_IMAGE_ERROR, + EOM_IMAGE_ERROR_TMP_FILE_FAILED, + _("Temporary file creation failed.")); + + return FALSE; + } + tmp_file_path = g_file_get_path (tmp_file); + + /* determine kind of saving */ + if (g_ascii_strcasecmp (source->format, target->format) == 0 && !source->modified) { + success = eom_image_copy_file (img, source, target, error); + direct_copy = success; + } + +#ifdef HAVE_JPEG + else if ((g_ascii_strcasecmp (source->format, EOM_FILE_FORMAT_JPEG) == 0 && source->exists) || + (g_ascii_strcasecmp (target->format, EOM_FILE_FORMAT_JPEG) == 0)) + { + success = eom_image_jpeg_save_file (img, tmp_file_path, source, target, error); + } +#endif + + if (!success && (*error == NULL)) { + success = gdk_pixbuf_save (priv->image, tmp_file_path, target->format, error, NULL); + } + + if (success && !direct_copy) { /* not required if we alredy copied the file directly */ + /* try to move result file to target uri */ + success = tmp_file_move_to_uri (img, tmp_file, target->file, target->overwrite, error); + } + + if (success) { + /* update image information to new uri */ + eom_image_reset_modifications (img); + eom_image_link_with_target (img, target); + } + + tmp_file_delete (tmp_file); + g_object_unref (tmp_file); + g_free (tmp_file_path); + + priv->status = EOM_IMAGE_STATUS_UNKNOWN; + + return success; +} + + +/* + * This function is extracted from + * File: caja/libcaja-private/caja-file.c + * Revision: 1.309 + * Author: Darin Adler <[email protected]> + */ +static gboolean +have_broken_filenames (void) +{ + static gboolean initialized = FALSE; + static gboolean broken; + + if (initialized) { + return broken; + } + + broken = g_getenv ("G_BROKEN_FILENAMES") != NULL; + + initialized = TRUE; + + return broken; +} + +/* + * This function is inspired by + * caja/libcaja-private/caja-file.c:caja_file_get_display_name_nocopy + * Revision: 1.309 + * Author: Darin Adler <[email protected]> + */ +const gchar* +eom_image_get_caption (EomImage *img) +{ + EomImagePrivate *priv; + char *name; + char *utf8_name; + char *scheme; + gboolean validated = FALSE; + gboolean broken_filenames; + + g_return_val_if_fail (EOM_IS_IMAGE (img), NULL); + + priv = img->priv; + + if (priv->file == NULL) return NULL; + + if (priv->caption != NULL) + /* Use cached caption string */ + return priv->caption; + + name = g_file_get_basename (priv->file); + scheme = g_file_get_uri_scheme (priv->file); + + if (name != NULL && g_ascii_strcasecmp (scheme, "file") == 0) { + /* Support the G_BROKEN_FILENAMES feature of + * glib by using g_filename_to_utf8 to convert + * local filenames to UTF-8. Also do the same + * thing with any local filename that does not + * validate as good UTF-8. + */ + broken_filenames = have_broken_filenames (); + + if (broken_filenames || !g_utf8_validate (name, -1, NULL)) { + utf8_name = g_locale_to_utf8 (name, -1, NULL, NULL, NULL); + if (utf8_name != NULL) { + g_free (name); + name = utf8_name; + /* Guaranteed to be correct utf8 here */ + validated = TRUE; + } + } else if (!broken_filenames) { + /* name was valid, no need to re-validate */ + validated = TRUE; + } + } + + if (!validated && !g_utf8_validate (name, -1, NULL)) { + if (name == NULL) { + name = g_strdup ("[Invalid Unicode]"); + } else { + utf8_name = eom_util_make_valid_utf8 (name); + g_free (name); + name = utf8_name; + } + } + + priv->caption = name; + + if (priv->caption == NULL) { + char *short_str; + + short_str = g_file_get_basename (priv->file); + if (g_utf8_validate (short_str, -1, NULL)) { + priv->caption = g_strdup (short_str); + } else { + priv->caption = g_filename_to_utf8 (short_str, -1, NULL, NULL, NULL); + } + g_free (short_str); + } + g_free (scheme); + + return priv->caption; +} + +const gchar* +eom_image_get_collate_key (EomImage *img) +{ + EomImagePrivate *priv; + + g_return_val_if_fail (EOM_IS_IMAGE (img), NULL); + + priv = img->priv; + + if (priv->collate_key == NULL) { + const char *caption; + + caption = eom_image_get_caption (img); + + priv->collate_key = g_utf8_collate_key_for_filename (caption, -1); + } + + return priv->collate_key; +} + +void +eom_image_cancel_load (EomImage *img) +{ + EomImagePrivate *priv; + + g_return_if_fail (EOM_IS_IMAGE (img)); + + priv = img->priv; + + g_mutex_lock (priv->status_mutex); + + if (priv->status == EOM_IMAGE_STATUS_LOADING) { + priv->cancel_loading = TRUE; + } + + g_mutex_unlock (priv->status_mutex); +} + +gpointer +eom_image_get_exif_info (EomImage *img) +{ + EomImagePrivate *priv; + gpointer data = NULL; + + g_return_val_if_fail (EOM_IS_IMAGE (img), NULL); + + priv = img->priv; + +#ifdef HAVE_EXIF + g_mutex_lock (priv->status_mutex); + + exif_data_ref (priv->exif); + data = priv->exif; + + g_mutex_unlock (priv->status_mutex); +#endif + + return data; +} + + +gpointer +eom_image_get_xmp_info (EomImage *img) +{ + EomImagePrivate *priv; + gpointer data = NULL; + + g_return_val_if_fail (EOM_IS_IMAGE (img), NULL); + + priv = img->priv; + +#ifdef HAVE_EXEMPI + g_mutex_lock (priv->status_mutex); + data = (gpointer) xmp_copy (priv->xmp); + g_mutex_unlock (priv->status_mutex); +#endif + + return data; +} + + +GFile * +eom_image_get_file (EomImage *img) +{ + g_return_val_if_fail (EOM_IS_IMAGE (img), NULL); + + return g_object_ref (img->priv->file); +} + +gboolean +eom_image_is_modified (EomImage *img) +{ + g_return_val_if_fail (EOM_IS_IMAGE (img), FALSE); + + return img->priv->modified; +} + +goffset +eom_image_get_bytes (EomImage *img) +{ + g_return_val_if_fail (EOM_IS_IMAGE (img), 0); + + return img->priv->bytes; +} + +void +eom_image_modified (EomImage *img) +{ + g_return_if_fail (EOM_IS_IMAGE (img)); + + g_signal_emit (G_OBJECT (img), signals[SIGNAL_CHANGED], 0); +} + +gchar* +eom_image_get_uri_for_display (EomImage *img) +{ + EomImagePrivate *priv; + gchar *uri_str = NULL; + gchar *str = NULL; + + g_return_val_if_fail (EOM_IS_IMAGE (img), NULL); + + priv = img->priv; + + if (priv->file != NULL) { + uri_str = g_file_get_uri (priv->file); + + if (uri_str != NULL) { + str = g_uri_unescape_string (uri_str, NULL); + g_free (uri_str); + } + } + + return str; +} + +EomImageStatus +eom_image_get_status (EomImage *img) +{ + g_return_val_if_fail (EOM_IS_IMAGE (img), EOM_IMAGE_STATUS_UNKNOWN); + + return img->priv->status; +} + +/** + * eom_image_get_metadata_status: + * @img: a #EomImage + * + * Returns the current status of the image metadata, that is, + * whether the metadata has not been read yet, is ready, or not available at all. + * + * Returns: one of #EomImageMetadataStatus + **/ +EomImageMetadataStatus +eom_image_get_metadata_status (EomImage *img) +{ + g_return_val_if_fail (EOM_IS_IMAGE (img), EOM_IMAGE_METADATA_NOT_AVAILABLE); + + return img->priv->metadata_status; +} + +void +eom_image_data_ref (EomImage *img) +{ + g_return_if_fail (EOM_IS_IMAGE (img)); + + g_object_ref (G_OBJECT (img)); + img->priv->data_ref_count++; + + g_assert (img->priv->data_ref_count <= G_OBJECT (img)->ref_count); +} + +void +eom_image_data_unref (EomImage *img) +{ + g_return_if_fail (EOM_IS_IMAGE (img)); + + if (img->priv->data_ref_count > 0) { + img->priv->data_ref_count--; + } else { + g_warning ("More image data unrefs than refs."); + } + + if (img->priv->data_ref_count == 0) { + eom_image_free_mem_private (img); + } + + g_object_unref (G_OBJECT (img)); + + g_assert (img->priv->data_ref_count <= G_OBJECT (img)->ref_count); +} + +static gint +compare_quarks (gconstpointer a, gconstpointer b) +{ + GQuark quark; + + quark = g_quark_from_string ((const gchar *) a); + + return quark - GPOINTER_TO_INT (b); +} + +GList * +eom_image_get_supported_mime_types (void) +{ + GSList *format_list, *it; + gchar **mime_types; + int i; + + if (!supported_mime_types) { + format_list = gdk_pixbuf_get_formats (); + + for (it = format_list; it != NULL; it = it->next) { + mime_types = + gdk_pixbuf_format_get_mime_types ((GdkPixbufFormat *) it->data); + + for (i = 0; mime_types[i] != NULL; i++) { + supported_mime_types = + g_list_prepend (supported_mime_types, + g_strdup (mime_types[i])); + } + + g_strfreev (mime_types); + } + + supported_mime_types = g_list_sort (supported_mime_types, + (GCompareFunc) compare_quarks); + + g_slist_free (format_list); + } + + return supported_mime_types; +} + +gboolean +eom_image_is_supported_mime_type (const char *mime_type) +{ + GList *supported_mime_types, *result; + GQuark quark; + + if (mime_type == NULL) { + return FALSE; + } + + supported_mime_types = eom_image_get_supported_mime_types (); + + quark = g_quark_from_string (mime_type); + + result = g_list_find_custom (supported_mime_types, + GINT_TO_POINTER (quark), + (GCompareFunc) compare_quarks); + + return (result != NULL); +} + +static gboolean +eom_image_iter_advance (EomImage *img) +{ + EomImagePrivate *priv; + gboolean new_frame; + + g_return_val_if_fail (EOM_IS_IMAGE (img), FALSE); + g_return_val_if_fail (GDK_IS_PIXBUF_ANIMATION_ITER (img->priv->anim_iter), FALSE); + + priv = img->priv; + + if ((new_frame = gdk_pixbuf_animation_iter_advance (img->priv->anim_iter, NULL)) == TRUE) + { + g_mutex_lock (priv->status_mutex); + g_object_unref (priv->image); + priv->image = gdk_pixbuf_animation_iter_get_pixbuf (priv->anim_iter); + g_object_ref (priv->image); + /* keep the transformation over time */ + if (EOM_IS_TRANSFORM (priv->trans)) { + GdkPixbuf* transformed = eom_transform_apply (priv->trans, priv->image, NULL); + g_object_unref (priv->image); + priv->image = transformed; + priv->width = gdk_pixbuf_get_width (transformed); + priv->height = gdk_pixbuf_get_height (transformed); + } + g_mutex_unlock (priv->status_mutex); + /* Emit next frame signal so we can update the display */ + g_signal_emit (img, signals[SIGNAL_NEXT_FRAME], 0, + gdk_pixbuf_animation_iter_get_delay_time (priv->anim_iter)); + } + + return new_frame; +} + +/** + * eom_image_is_animation: + * @img: a #EomImage + * + * Checks whether a given image is animated. + * + * Returns: #TRUE if it is an animated image, #FALSE otherwise. + * + **/ +gboolean +eom_image_is_animation (EomImage *img) +{ + g_return_val_if_fail (EOM_IS_IMAGE (img), FALSE); + return img->priv->anim != NULL; +} + +static gboolean +private_timeout (gpointer data) +{ + EomImage *img = EOM_IMAGE (data); + EomImagePrivate *priv = img->priv; + + if (eom_image_is_animation (img) && + !g_source_is_destroyed (g_main_current_source ()) && + priv->is_playing) { + while (eom_image_iter_advance (img) != TRUE) {}; /* cpu-sucking ? */ + g_timeout_add (gdk_pixbuf_animation_iter_get_delay_time (priv->anim_iter), private_timeout, img); + return FALSE; + } + priv->is_playing = FALSE; + return FALSE; /* stop playing */ +} + +/** + * eom_image_start_animation: + * @img: a #EomImage + * + * Starts playing an animated image. + * + * Returns: %TRUE on success, %FALSE if @img is already playing or isn't an animated image. + **/ +gboolean +eom_image_start_animation (EomImage *img) +{ + EomImagePrivate *priv; + + g_return_val_if_fail (EOM_IS_IMAGE (img), FALSE); + priv = img->priv; + + if (!eom_image_is_animation (img) || priv->is_playing) + return FALSE; + + g_mutex_lock (priv->status_mutex); + g_object_ref (priv->anim_iter); + priv->is_playing = TRUE; + g_mutex_unlock (priv->status_mutex); + + g_timeout_add (gdk_pixbuf_animation_iter_get_delay_time (priv->anim_iter), private_timeout, img); + + return TRUE; +} + +#ifdef HAVE_RSVG +gboolean +eom_image_is_svg (EomImage *img) +{ + g_return_val_if_fail (EOM_IS_IMAGE (img), FALSE); + + return (img->priv->svg != NULL); +} + +RsvgHandle * +eom_image_get_svg (EomImage *img) +{ + g_return_val_if_fail (EOM_IS_IMAGE (img), NULL); + + return img->priv->svg; +} + +EomTransform * +eom_image_get_transform (EomImage *img) +{ + g_return_val_if_fail (EOM_IS_IMAGE (img), NULL); + + return img->priv->trans; +} + +#endif + +/** + * eom_image_file_changed: + * @img: a #EomImage + * + * Emits EomImage::file-changed signal + **/ +void +eom_image_file_changed (EomImage *img) +{ + g_return_if_fail (EOM_IS_IMAGE (img)); + + g_signal_emit (img, signals[SIGNAL_FILE_CHANGED], 0); +} diff --git a/src/eom-image.h b/src/eom-image.h new file mode 100644 index 0000000..791eb8a --- /dev/null +++ b/src/eom-image.h @@ -0,0 +1,218 @@ +/* Eye Of Mate - Image + * + * Copyright (C) 2007 The Free Software Foundation + * + * Author: Lucas Rocha <[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 __EOM_IMAGE_H__ +#define __EOM_IMAGE_H__ + +#include "eom-jobs.h" +#include "eom-window.h" +#include "eom-transform.h" +#include "eom-image-save-info.h" +#include "eom-enums.h" + +#include <glib.h> +#include <glib-object.h> +#include <gdk-pixbuf/gdk-pixbuf.h> + +#ifdef HAVE_EXIF +#include <libexif/exif-data.h> +#endif + +#ifdef HAVE_LCMS +#include <lcms.h> +#endif + +#ifdef HAVE_EXEMPI +#include <exempi/xmp.h> +#endif + +#ifdef HAVE_RSVG +#include <librsvg/rsvg.h> +#endif + +G_BEGIN_DECLS + +#ifndef __EOM_IMAGE_DECLR__ +#define __EOM_IMAGE_DECLR__ +typedef struct _EomImage EomImage; +#endif +typedef struct _EomImageClass EomImageClass; +typedef struct _EomImagePrivate EomImagePrivate; + +#define EOM_TYPE_IMAGE (eom_image_get_type ()) +#define EOM_IMAGE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), EOM_TYPE_IMAGE, EomImage)) +#define EOM_IMAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), EOM_TYPE_IMAGE, EomImageClass)) +#define EOM_IS_IMAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), EOM_TYPE_IMAGE)) +#define EOM_IS_IMAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), EOM_TYPE_IMAGE)) +#define EOM_IMAGE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), EOM_TYPE_IMAGE, EomImageClass)) + +typedef enum { + EOM_IMAGE_ERROR_SAVE_NOT_LOCAL, + EOM_IMAGE_ERROR_NOT_LOADED, + EOM_IMAGE_ERROR_VFS, + EOM_IMAGE_ERROR_FILE_EXISTS, + EOM_IMAGE_ERROR_TMP_FILE_FAILED, + EOM_IMAGE_ERROR_GENERIC, + EOM_IMAGE_ERROR_UNKNOWN +} EomImageError; + +#define EOM_IMAGE_ERROR eom_image_error_quark () + +typedef enum { + EOM_IMAGE_STATUS_UNKNOWN, + EOM_IMAGE_STATUS_LOADING, + EOM_IMAGE_STATUS_LOADED, + EOM_IMAGE_STATUS_SAVING, + EOM_IMAGE_STATUS_FAILED +} EomImageStatus; + +typedef enum { + EOM_IMAGE_METADATA_NOT_READ, + EOM_IMAGE_METADATA_NOT_AVAILABLE, + EOM_IMAGE_METADATA_READY +} EomImageMetadataStatus; + +struct _EomImage { + GObject parent; + + EomImagePrivate *priv; +}; + +struct _EomImageClass { + GObjectClass parent_class; + + void (* changed) (EomImage *img); + + void (* size_prepared) (EomImage *img, + int width, + int height); + + void (* thumbnail_changed) (EomImage *img); + + void (* save_progress) (EomImage *img, + gfloat progress); + + void (* next_frame) (EomImage *img, + gint delay); + + void (* file_changed) (EomImage *img); +}; + +GType eom_image_get_type (void) G_GNUC_CONST; + +GQuark eom_image_error_quark (void); + +EomImage *eom_image_new (const char *txt_uri); + +EomImage *eom_image_new_file (GFile *file); + +gboolean eom_image_load (EomImage *img, + EomImageData data2read, + EomJob *job, + GError **error); + +void eom_image_cancel_load (EomImage *img); + +gboolean eom_image_has_data (EomImage *img, + EomImageData data); + +void eom_image_data_ref (EomImage *img); + +void eom_image_data_unref (EomImage *img); + +void eom_image_set_thumbnail (EomImage *img, + GdkPixbuf *pixbuf); + +gboolean eom_image_save_as_by_info (EomImage *img, + EomImageSaveInfo *source, + EomImageSaveInfo *target, + GError **error); + +gboolean eom_image_save_by_info (EomImage *img, + EomImageSaveInfo *source, + GError **error); + +GdkPixbuf* eom_image_get_pixbuf (EomImage *img); + +GdkPixbuf* eom_image_get_thumbnail (EomImage *img); + +void eom_image_get_size (EomImage *img, + gint *width, + gint *height); + +goffset eom_image_get_bytes (EomImage *img); + +gboolean eom_image_is_modified (EomImage *img); + +void eom_image_modified (EomImage *img); + +const gchar* eom_image_get_caption (EomImage *img); + +const gchar *eom_image_get_collate_key (EomImage *img); + +gpointer eom_image_get_exif_info (EomImage *img); + +gpointer eom_image_get_xmp_info (EomImage *img); + +GFile* eom_image_get_file (EomImage *img); + +gchar* eom_image_get_uri_for_display (EomImage *img); + +EomImageStatus eom_image_get_status (EomImage *img); + +EomImageMetadataStatus eom_image_get_metadata_status (EomImage *img); + +void eom_image_transform (EomImage *img, + EomTransform *trans, + EomJob *job); + +#ifdef HAVE_EXIF +void eom_image_autorotate (EomImage *img); +#endif + +#ifdef HAVE_LCMS +cmsHPROFILE eom_image_get_profile (EomImage *img); + +void eom_image_apply_display_profile (EomImage *img, + cmsHPROFILE display_profile); +#endif + +void eom_image_undo (EomImage *img); + +GList *eom_image_get_supported_mime_types (void); + +gboolean eom_image_is_supported_mime_type (const char *mime_type); + +gboolean eom_image_is_animation (EomImage *img); + +gboolean eom_image_start_animation (EomImage *img); + +#ifdef HAVE_RSVG +gboolean eom_image_is_svg (EomImage *img); +RsvgHandle *eom_image_get_svg (EomImage *img); +EomTransform *eom_image_get_transform (EomImage *img); +#endif + +void eom_image_file_changed (EomImage *img); + +G_END_DECLS + +#endif /* __EOM_IMAGE_H__ */ diff --git a/src/eom-job-queue.c b/src/eom-job-queue.c new file mode 100644 index 0000000..93ed903 --- /dev/null +++ b/src/eom-job-queue.c @@ -0,0 +1,238 @@ +/* Eye Of Mate - Jobs Queue + * + * Copyright (C) 2006 The Free Software Foundation + * + * Author: Lucas Rocha <[email protected]> + * + * Based on evince code (shell/ev-job-queue.c) by: + * - Martin Kretzschmar <[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 "eom-jobs.h" +#include "eom-job-queue.h" + +static GCond *render_cond = NULL; +static GMutex *eom_queue_mutex = NULL; + +static GQueue *thumbnail_queue = NULL; +static GQueue *load_queue = NULL; +static GQueue *model_queue = NULL; +static GQueue *transform_queue = NULL; +static GQueue *save_queue = NULL; +static GQueue *copy_queue = NULL; + +static gboolean +remove_job_from_queue (GQueue *queue, EomJob *job) +{ + GList *list; + + list = g_queue_find (queue, job); + + if (list) { + g_object_unref (G_OBJECT (job)); + g_queue_delete_link (queue, list); + + return TRUE; + } + + return FALSE; +} + +static void +add_job_to_queue_locked (GQueue *queue, EomJob *job) +{ + g_object_ref (job); + g_queue_push_tail (queue, job); + g_cond_broadcast (render_cond); +} + +static gboolean +notify_finished (GObject *job) +{ + eom_job_finished (EOM_JOB (job)); + + return FALSE; +} + +static void +handle_job (EomJob *job) +{ + g_object_ref (G_OBJECT (job)); + + // Do the EOM_JOB cast for safety + eom_job_run (EOM_JOB (job)); + + g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, + (GSourceFunc) notify_finished, + job, + g_object_unref); +} + +static gboolean +no_jobs_available_unlocked (void) +{ + return g_queue_is_empty (load_queue) && + g_queue_is_empty (transform_queue) && + g_queue_is_empty (thumbnail_queue) && + g_queue_is_empty (model_queue) && + g_queue_is_empty (save_queue) && + g_queue_is_empty (copy_queue); +} + +static EomJob * +search_for_jobs_unlocked (void) +{ + EomJob *job; + + job = (EomJob *) g_queue_pop_head (load_queue); + if (job) + return job; + + job = (EomJob *) g_queue_pop_head (transform_queue); + if (job) + return job; + + job = (EomJob *) g_queue_pop_head (thumbnail_queue); + if (job) + return job; + + job = (EomJob *) g_queue_pop_head (model_queue); + if (job) + return job; + + job = (EomJob *) g_queue_pop_head (save_queue); + if (job) + return job; + + job = (EomJob *) g_queue_pop_head (copy_queue); + if (job) + return job; + + return NULL; +} + +static gpointer +eom_render_thread (gpointer data) +{ + while (TRUE) { + EomJob *job; + + g_mutex_lock (eom_queue_mutex); + + if (no_jobs_available_unlocked ()) { + g_cond_wait (render_cond, eom_queue_mutex); + } + + job = search_for_jobs_unlocked (); + + g_mutex_unlock (eom_queue_mutex); + + /* Now that we have our job, we handle it */ + if (job) { + handle_job (job); + g_object_unref (G_OBJECT (job)); + } + } + return NULL; + +} + +void +eom_job_queue_init (void) +{ + if (!g_thread_supported ()) g_thread_init (NULL); + + render_cond = g_cond_new (); + eom_queue_mutex = g_mutex_new (); + + thumbnail_queue = g_queue_new (); + load_queue = g_queue_new (); + model_queue = g_queue_new (); + transform_queue = g_queue_new (); + save_queue = g_queue_new (); + copy_queue = g_queue_new (); + + g_thread_create (eom_render_thread, NULL, FALSE, NULL); +} + +static GQueue * +find_queue (EomJob *job) +{ + if (EOM_IS_JOB_THUMBNAIL (job)) { + return thumbnail_queue; + } else if (EOM_IS_JOB_LOAD (job)) { + return load_queue; + } else if (EOM_IS_JOB_MODEL (job)) { + return model_queue; + } else if (EOM_IS_JOB_TRANSFORM (job)) { + return transform_queue; + } else if (EOM_IS_JOB_SAVE (job)) { + return save_queue; + } else if (EOM_IS_JOB_COPY (job)) { + return copy_queue; + } + + g_assert_not_reached (); + + return NULL; +} + +void +eom_job_queue_add_job (EomJob *job) +{ + GQueue *queue; + + g_return_if_fail (EOM_IS_JOB (job)); + + queue = find_queue (job); + + g_mutex_lock (eom_queue_mutex); + + add_job_to_queue_locked (queue, job); + + g_mutex_unlock (eom_queue_mutex); +} + +gboolean +eom_job_queue_remove_job (EomJob *job) +{ + gboolean retval = FALSE; + + g_return_val_if_fail (EOM_IS_JOB (job), FALSE); + + g_mutex_lock (eom_queue_mutex); + + if (EOM_IS_JOB_THUMBNAIL (job)) { + retval = remove_job_from_queue (thumbnail_queue, job); + } else if (EOM_IS_JOB_LOAD (job)) { + retval = remove_job_from_queue (load_queue, job); + } else if (EOM_IS_JOB_MODEL (job)) { + retval = remove_job_from_queue (model_queue, job); + } else if (EOM_IS_JOB_TRANSFORM (job)) { + retval = remove_job_from_queue (transform_queue, job); + } else if (EOM_IS_JOB_SAVE (job)) { + retval = remove_job_from_queue (save_queue, job); + } else if (EOM_IS_JOB_COPY (job)) { + retval = remove_job_from_queue (copy_queue, job); + } else { + g_assert_not_reached (); + } + + g_mutex_unlock (eom_queue_mutex); + + return retval; +} diff --git a/src/eom-job-queue.h b/src/eom-job-queue.h new file mode 100644 index 0000000..6a71e7b --- /dev/null +++ b/src/eom-job-queue.h @@ -0,0 +1,42 @@ +/* Eye Of Mate - Jobs Queue + * + * Copyright (C) 2006 The Free Software Foundation + * + * Author: Lucas Rocha <[email protected]> + * + * Based on evince code (shell/ev-job-queue.h) by: + * - Martin Kretzschmar <[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 __EOM_JOB_QUEUE_H__ +#define __EOM_JOB_QUEUE_H__ + +#include "eom-jobs.h" + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +void eom_job_queue_init (void); + +void eom_job_queue_add_job (EomJob *job); + +gboolean eom_job_queue_remove_job (EomJob *job); + +G_END_DECLS + +#endif /* __EOM_JOB_QUEUE_H__ */ diff --git a/src/eom-jobs.c b/src/eom-jobs.c new file mode 100644 index 0000000..c3bac3a --- /dev/null +++ b/src/eom-jobs.c @@ -0,0 +1,885 @@ +/* Eye Of Mate - Jobs + * + * Copyright (C) 2006 The Free Software Foundation + * + * Author: Lucas Rocha <[email protected]> + * + * Based on evince code (shell/ev-jobs.c) by: + * - Martin Kretzschmar <[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 "eom-uri-converter.h" +#include "eom-jobs.h" +#include "eom-job-queue.h" +#include "eom-image.h" +#include "eom-transform.h" +#include "eom-list-store.h" +#include "eom-thumbnail.h" +#include "eom-pixbuf-util.h" + +#include <gdk-pixbuf/gdk-pixbuf.h> + +#define EOM_JOB_GET_PRIVATE(object) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((object), EOM_TYPE_JOB, EomJobPrivate)) + +G_DEFINE_TYPE (EomJob, eom_job, G_TYPE_OBJECT); +G_DEFINE_TYPE (EomJobThumbnail, eom_job_thumbnail, EOM_TYPE_JOB); +G_DEFINE_TYPE (EomJobLoad, eom_job_load, EOM_TYPE_JOB); +G_DEFINE_TYPE (EomJobModel, eom_job_model, EOM_TYPE_JOB); +G_DEFINE_TYPE (EomJobTransform, eom_job_transform, EOM_TYPE_JOB); +G_DEFINE_TYPE (EomJobSave, eom_job_save, EOM_TYPE_JOB); +G_DEFINE_TYPE (EomJobSaveAs, eom_job_save_as, EOM_TYPE_JOB_SAVE); +G_DEFINE_TYPE (EomJobCopy, eom_job_copy, EOM_TYPE_JOB); + +enum +{ + SIGNAL_FINISHED, + SIGNAL_PROGRESS, + SIGNAL_LAST_SIGNAL +}; + +static guint job_signals[SIGNAL_LAST_SIGNAL]; + +static void eom_job_copy_run (EomJob *ejob); +static void eom_job_load_run (EomJob *ejob); +static void eom_job_model_run (EomJob *ejob); +static void eom_job_save_run (EomJob *job); +static void eom_job_save_as_run (EomJob *job); +static void eom_job_thumbnail_run (EomJob *ejob); +static void eom_job_transform_run (EomJob *ejob); + +static void eom_job_init (EomJob *job) +{ + job->mutex = g_mutex_new(); + job->progress = 0.0; +} + +static void +eom_job_dispose (GObject *object) +{ + EomJob *job; + + job = EOM_JOB (object); + + if (job->error) { + g_error_free (job->error); + job->error = NULL; + } + + if (job->mutex) { + g_mutex_free (job->mutex); + job->mutex = NULL; + } + + (* G_OBJECT_CLASS (eom_job_parent_class)->dispose) (object); +} + +static void +eom_job_run_default (EomJob *job) +{ + g_critical ("Class \"%s\" does not implement the required run action", + G_OBJECT_CLASS_NAME (G_OBJECT_GET_CLASS (job))); +} + +static void +eom_job_class_init (EomJobClass *class) +{ + GObjectClass *oclass; + + oclass = G_OBJECT_CLASS (class); + + oclass->dispose = eom_job_dispose; + + class->run = eom_job_run_default; + + job_signals [SIGNAL_FINISHED] = + g_signal_new ("finished", + EOM_TYPE_JOB, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EomJobClass, finished), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + job_signals [SIGNAL_PROGRESS] = + g_signal_new ("progress", + EOM_TYPE_JOB, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EomJobClass, progress), + NULL, NULL, + g_cclosure_marshal_VOID__FLOAT, + G_TYPE_NONE, 1, + G_TYPE_FLOAT); +} + +void +eom_job_finished (EomJob *job) +{ + g_return_if_fail (EOM_IS_JOB (job)); + + g_signal_emit (job, job_signals[SIGNAL_FINISHED], 0); +} + +/** + * eom_job_run: + * @job: the job to execute. + * + * Executes the job passed as @job. Usually there is no need to call this + * on your own. Jobs should be executed by using the EomJobQueue. + **/ +void +eom_job_run (EomJob *job) +{ + EomJobClass *class; + + g_return_if_fail (EOM_IS_JOB (job)); + + class = EOM_JOB_GET_CLASS (job); + if (class->run) + class->run (job); + else + eom_job_run_default (job); +} +static gboolean +notify_progress (gpointer data) +{ + EomJob *job = EOM_JOB (data); + + g_signal_emit (job, job_signals[SIGNAL_PROGRESS], 0, job->progress); + + return FALSE; +} + +void +eom_job_set_progress (EomJob *job, float progress) +{ + g_return_if_fail (EOM_IS_JOB (job)); + g_return_if_fail (progress >= 0.0 && progress <= 1.0); + + g_mutex_lock (job->mutex); + job->progress = progress; + g_mutex_unlock (job->mutex); + + g_idle_add (notify_progress, job); +} + +static void eom_job_thumbnail_init (EomJobThumbnail *job) { /* Do Nothing */ } + +static void +eom_job_thumbnail_dispose (GObject *object) +{ + EomJobThumbnail *job; + + job = EOM_JOB_THUMBNAIL (object); + + if (job->image) { + g_object_unref (job->image); + job->image = NULL; + } + + if (job->thumbnail) { + g_object_unref (job->thumbnail); + job->thumbnail = NULL; + } + + (* G_OBJECT_CLASS (eom_job_thumbnail_parent_class)->dispose) (object); +} + +static void +eom_job_thumbnail_class_init (EomJobThumbnailClass *class) +{ + GObjectClass *oclass; + + oclass = G_OBJECT_CLASS (class); + + oclass->dispose = eom_job_thumbnail_dispose; + + EOM_JOB_CLASS (class)->run = eom_job_thumbnail_run; +} + +EomJob * +eom_job_thumbnail_new (EomImage *image) +{ + EomJobThumbnail *job; + + job = g_object_new (EOM_TYPE_JOB_THUMBNAIL, NULL); + + if (image) { + job->image = g_object_ref (image); + } + + return EOM_JOB (job); +} + +static void +eom_job_thumbnail_run (EomJob *ejob) +{ + gchar *orig_width, *orig_height; + gint width, height; + GdkPixbuf *pixbuf; + EomJobThumbnail *job; + + g_return_if_fail (EOM_IS_JOB_THUMBNAIL (ejob)); + + job = EOM_JOB_THUMBNAIL (ejob); + + if (ejob->error) { + g_error_free (ejob->error); + ejob->error = NULL; + } + + job->thumbnail = eom_thumbnail_load (job->image, + &ejob->error); + + if (!job->thumbnail) { + ejob->finished = TRUE; + return; + } + + orig_width = g_strdup (gdk_pixbuf_get_option (job->thumbnail, "tEXt::Thumb::Image::Width")); + orig_height = g_strdup (gdk_pixbuf_get_option (job->thumbnail, "tEXt::Thumb::Image::Height")); + + pixbuf = eom_thumbnail_fit_to_size (job->thumbnail, EOM_LIST_STORE_THUMB_SIZE); + g_object_unref (job->thumbnail); + job->thumbnail = eom_thumbnail_add_frame (pixbuf); + g_object_unref (pixbuf); + + if (orig_width) { + sscanf (orig_width, "%i", &width); + g_object_set_data (G_OBJECT (job->thumbnail), + EOM_THUMBNAIL_ORIGINAL_WIDTH, + GINT_TO_POINTER (width)); + g_free (orig_width); + } + if (orig_height) { + sscanf (orig_height, "%i", &height); + g_object_set_data (G_OBJECT (job->thumbnail), + EOM_THUMBNAIL_ORIGINAL_HEIGHT, + GINT_TO_POINTER (height)); + g_free (orig_height); + } + + if (ejob->error) { + g_warning ("%s", ejob->error->message); + } + + ejob->finished = TRUE; +} + +static void eom_job_load_init (EomJobLoad *job) { /* Do Nothing */ } + +static void +eom_job_load_dispose (GObject *object) +{ + EomJobLoad *job; + + job = EOM_JOB_LOAD (object); + + if (job->image) { + g_object_unref (job->image); + job->image = NULL; + } + + (* G_OBJECT_CLASS (eom_job_load_parent_class)->dispose) (object); +} + +static void +eom_job_load_class_init (EomJobLoadClass *class) +{ + GObjectClass *oclass; + + oclass = G_OBJECT_CLASS (class); + + oclass->dispose = eom_job_load_dispose; + EOM_JOB_CLASS (class)->run = eom_job_load_run; +} + +EomJob * +eom_job_load_new (EomImage *image, EomImageData data) +{ + EomJobLoad *job; + + job = g_object_new (EOM_TYPE_JOB_LOAD, NULL); + + if (image) { + job->image = g_object_ref (image); + } + job->data = data; + + return EOM_JOB (job); +} + +static void +eom_job_load_run (EomJob *job) +{ + g_return_if_fail (EOM_IS_JOB_LOAD (job)); + + if (job->error) { + g_error_free (job->error); + job->error = NULL; + } + + eom_image_load (EOM_IMAGE (EOM_JOB_LOAD (job)->image), + EOM_JOB_LOAD (job)->data, + job, + &job->error); + + job->finished = TRUE; +} + +static void eom_job_model_init (EomJobModel *job) { /* Do Nothing */ } + +static void +eom_job_model_dispose (GObject *object) +{ + EomJobModel *job; + + job = EOM_JOB_MODEL (object); + + (* G_OBJECT_CLASS (eom_job_model_parent_class)->dispose) (object); +} + +static void +eom_job_model_class_init (EomJobModelClass *class) +{ + GObjectClass *oclass; + + oclass = G_OBJECT_CLASS (class); + + oclass->dispose = eom_job_model_dispose; + EOM_JOB_CLASS (class)->run = eom_job_model_run; +} + +EomJob * +eom_job_model_new (GSList *file_list) +{ + EomJobModel *job; + + job = g_object_new (EOM_TYPE_JOB_MODEL, NULL); + + job->file_list = file_list; + + return EOM_JOB (job); +} + +static void +filter_files (GSList *files, GList **file_list, GList **error_list) +{ + GSList *it; + GFileInfo *file_info; + + for (it = files; it != NULL; it = it->next) { + GFile *file; + GFileType type = G_FILE_TYPE_UNKNOWN; + + file = (GFile *) it->data; + + if (file != NULL) { + file_info = g_file_query_info (file, + G_FILE_ATTRIBUTE_STANDARD_TYPE","G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, + 0, NULL, NULL); + if (file_info == NULL) { + type = G_FILE_TYPE_UNKNOWN; + } else { + type = g_file_info_get_file_type (file_info); + + /* Workaround for gvfs backends that + don't set the GFileType. */ + if (G_UNLIKELY (type == G_FILE_TYPE_UNKNOWN)) { + const gchar *ctype; + + ctype = g_file_info_get_content_type (file_info); + + /* If the content type is supported + adjust the file_type */ + if (eom_image_is_supported_mime_type (ctype)) + type = G_FILE_TYPE_REGULAR; + } + + g_object_unref (file_info); + } + } + + switch (type) { + case G_FILE_TYPE_REGULAR: + case G_FILE_TYPE_DIRECTORY: + *file_list = g_list_prepend (*file_list, g_object_ref (file)); + break; + default: + *error_list = g_list_prepend (*error_list, + g_file_get_uri (file)); + break; + } + + g_object_unref (file); + } + + *file_list = g_list_reverse (*file_list); + *error_list = g_list_reverse (*error_list); +} + +static void +eom_job_model_run (EomJob *ejob) +{ + GList *filtered_list = NULL; + GList *error_list = NULL; + EomJobModel *job; + + g_return_if_fail (EOM_IS_JOB_MODEL (ejob)); + + job = EOM_JOB_MODEL (ejob); + + filter_files (job->file_list, &filtered_list, &error_list); + + job->store = EOM_LIST_STORE (eom_list_store_new ()); + + eom_list_store_add_files (job->store, filtered_list); + + g_list_foreach (filtered_list, (GFunc) g_object_unref, NULL); + g_list_free (filtered_list); + + g_list_foreach (error_list, (GFunc) g_free, NULL); + g_list_free (error_list); + + ejob->finished = TRUE; +} + +static void eom_job_transform_init (EomJobTransform *job) { /* Do Nothing */ } + +static void +eom_job_transform_dispose (GObject *object) +{ + EomJobTransform *job; + + job = EOM_JOB_TRANSFORM (object); + + if (job->trans) { + g_object_unref (job->trans); + job->trans = NULL; + } + + g_list_foreach (job->images, (GFunc) g_object_unref, NULL); + g_list_free (job->images); + + (* G_OBJECT_CLASS (eom_job_transform_parent_class)->dispose) (object); +} + +static void +eom_job_transform_class_init (EomJobTransformClass *class) +{ + GObjectClass *oclass; + + oclass = G_OBJECT_CLASS (class); + + oclass->dispose = eom_job_transform_dispose; + + EOM_JOB_CLASS (class)->run = eom_job_transform_run; +} + +EomJob * +eom_job_transform_new (GList *images, EomTransform *trans) +{ + EomJobTransform *job; + + job = g_object_new (EOM_TYPE_JOB_TRANSFORM, NULL); + + if (trans) { + job->trans = g_object_ref (trans); + } else { + job->trans = NULL; + } + + job->images = images; + + return EOM_JOB (job); +} + +static gboolean +eom_job_transform_image_modified (gpointer data) +{ + g_return_val_if_fail (EOM_IS_IMAGE (data), FALSE); + + eom_image_modified (EOM_IMAGE (data)); + g_object_unref (G_OBJECT (data)); + + return FALSE; +} + +void +eom_job_transform_run (EomJob *ejob) +{ + EomJobTransform *job; + GList *it; + + g_return_if_fail (EOM_IS_JOB_TRANSFORM (ejob)); + + job = EOM_JOB_TRANSFORM (ejob); + + if (ejob->error) { + g_error_free (ejob->error); + ejob->error = NULL; + } + + for (it = job->images; it != NULL; it = it->next) { + EomImage *image = EOM_IMAGE (it->data); + + if (job->trans == NULL) { + eom_image_undo (image); + } else { + eom_image_transform (image, job->trans, ejob); + } + + if (eom_image_is_modified (image) || job->trans == NULL) { + g_object_ref (image); + g_idle_add (eom_job_transform_image_modified, image); + } + } + + ejob->finished = TRUE; +} + +static void eom_job_save_init (EomJobSave *job) { /* do nothing */ } + +static void +eom_job_save_dispose (GObject *object) +{ + EomJobSave *job; + + job = EOM_JOB_SAVE (object); + + if (job->images) { + g_list_foreach (job->images, (GFunc) g_object_unref, NULL); + g_list_free (job->images); + job->images = NULL; + } + + (* G_OBJECT_CLASS (eom_job_save_parent_class)->dispose) (object); +} + +static void +eom_job_save_class_init (EomJobSaveClass *class) +{ + G_OBJECT_CLASS (class)->dispose = eom_job_save_dispose; + EOM_JOB_CLASS (class)->run = eom_job_save_run; +} + +EomJob * +eom_job_save_new (GList *images) +{ + EomJobSave *job; + + job = g_object_new (EOM_TYPE_JOB_SAVE, NULL); + + job->images = images; + job->current_image = NULL; + + return EOM_JOB (job); +} + +static void +save_progress_handler (EomImage *image, gfloat progress, gpointer data) +{ + EomJobSave *job = EOM_JOB_SAVE (data); + guint n_images = g_list_length (job->images); + gfloat job_progress; + + job_progress = (job->current_pos / (gfloat) n_images) + (progress / n_images); + + eom_job_set_progress (EOM_JOB (job), job_progress); +} + +static void +eom_job_save_run (EomJob *ejob) +{ + EomJobSave *job; + GList *it; + + g_return_if_fail (EOM_IS_JOB_SAVE (ejob)); + + job = EOM_JOB_SAVE (ejob); + + job->current_pos = 0; + + for (it = job->images; it != NULL; it = it->next, job->current_pos++) { + EomImage *image = EOM_IMAGE (it->data); + EomImageSaveInfo *save_info = NULL; + gulong handler_id = 0; + gboolean success = FALSE; + + job->current_image = image; + + /* Make sure the image doesn't go away while saving */ + eom_image_data_ref (image); + + if (!eom_image_has_data (image, EOM_IMAGE_DATA_ALL)) { + eom_image_load (image, + EOM_IMAGE_DATA_ALL, + NULL, + &ejob->error); + } + + handler_id = g_signal_connect (G_OBJECT (image), + "save-progress", + G_CALLBACK (save_progress_handler), + job); + + save_info = eom_image_save_info_from_image (image); + + success = eom_image_save_by_info (image, + save_info, + &ejob->error); + + if (save_info) + g_object_unref (save_info); + + if (handler_id != 0) + g_signal_handler_disconnect (G_OBJECT (image), handler_id); + + eom_image_data_unref (image); + + if (!success) break; + } + + ejob->finished = TRUE; +} + +static void eom_job_save_as_init (EomJobSaveAs *job) { /* do nothing */ } + +static void eom_job_save_as_dispose (GObject *object) +{ + EomJobSaveAs *job = EOM_JOB_SAVE_AS (object); + + if (job->converter != NULL) { + g_object_unref (job->converter); + job->converter = NULL; + } + + if (job->file != NULL) { + g_object_unref (job->file); + job->file = NULL; + } + + (* G_OBJECT_CLASS (eom_job_save_as_parent_class)->dispose) (object); +} + +static void +eom_job_save_as_class_init (EomJobSaveAsClass *class) +{ + G_OBJECT_CLASS (class)->dispose = eom_job_save_as_dispose; + EOM_JOB_CLASS (class)->run = eom_job_save_as_run; +} + +EomJob * +eom_job_save_as_new (GList *images, EomURIConverter *converter, GFile *file) +{ + EomJobSaveAs *job; + + g_assert (converter != NULL || g_list_length (images) == 1); + + job = g_object_new (EOM_TYPE_JOB_SAVE_AS, NULL); + + EOM_JOB_SAVE(job)->images = images; + + job->converter = converter ? g_object_ref (converter) : NULL; + job->file = file ? g_object_ref (file) : NULL; + + return EOM_JOB (job); +} + +static void +eom_job_save_as_run (EomJob *ejob) +{ + EomJobSave *job; + EomJobSaveAs *saveas_job; + GList *it; + guint n_images; + + g_return_if_fail (EOM_IS_JOB_SAVE_AS (ejob)); + + job = EOM_JOB_SAVE (ejob); + + n_images = g_list_length (job->images); + + saveas_job = EOM_JOB_SAVE_AS (job); + + job->current_pos = 0; + + for (it = job->images; it != NULL; it = it->next, job->current_pos++) { + GdkPixbufFormat *format; + EomImageSaveInfo *src_info, *dest_info; + EomImage *image = EOM_IMAGE (it->data); + gboolean success = FALSE; + gulong handler_id = 0; + + job->current_image = image; + + eom_image_data_ref (image); + + if (!eom_image_has_data (image, EOM_IMAGE_DATA_ALL)) { + eom_image_load (image, + EOM_IMAGE_DATA_ALL, + NULL, + &ejob->error); + } + + g_assert (ejob->error == NULL); + + handler_id = g_signal_connect (G_OBJECT (image), + "save-progress", + G_CALLBACK (save_progress_handler), + job); + + src_info = eom_image_save_info_from_image (image); + + if (n_images == 1) { + g_assert (saveas_job->file != NULL); + + format = eom_pixbuf_get_format (saveas_job->file); + + dest_info = eom_image_save_info_from_file (saveas_job->file, + format); + + /* SaveAsDialog has already secured permission to overwrite */ + if (dest_info->exists) { + dest_info->overwrite = TRUE; + } + } else { + GFile *dest_file; + gboolean result; + + result = eom_uri_converter_do (saveas_job->converter, + image, + &dest_file, + &format, + NULL); + + g_assert (result); + + dest_info = eom_image_save_info_from_file (dest_file, + format); + } + + success = eom_image_save_as_by_info (image, + src_info, + dest_info, + &ejob->error); + + if (src_info) + g_object_unref (src_info); + + if (dest_info) + g_object_unref (dest_info); + + if (handler_id != 0) + g_signal_handler_disconnect (G_OBJECT (image), handler_id); + + eom_image_data_unref (image); + + if (!success) + break; + } + + ejob->finished = TRUE; +} + +static void eom_job_copy_init (EomJobCopy *job) { /* do nothing */}; + +static void +eom_job_copy_dispose (GObject *object) +{ + EomJobCopy *job = EOM_JOB_COPY (object); + + if (job->dest) { + g_free (job->dest); + job->dest = NULL; + } + + (* G_OBJECT_CLASS (eom_job_copy_parent_class)->dispose) (object); +} + +static void +eom_job_copy_class_init (EomJobCopyClass *class) +{ + G_OBJECT_CLASS (class)->dispose = eom_job_copy_dispose; + EOM_JOB_CLASS (class)->run = eom_job_copy_run; +} + +EomJob * +eom_job_copy_new (GList *images, const gchar *dest) +{ + EomJobCopy *job; + + g_assert (images != NULL && dest != NULL); + + job = g_object_new (EOM_TYPE_JOB_COPY, NULL); + + job->images = images; + job->dest = g_strdup (dest); + + return EOM_JOB (job); +} + +static void +eom_job_copy_progress_callback (goffset current_num_bytes, + goffset total_num_bytes, + gpointer user_data) +{ + gfloat job_progress; + guint n_images; + EomJobCopy *job; + + job = EOM_JOB_COPY (user_data); + n_images = g_list_length (job->images); + + job_progress = ((current_num_bytes / (gfloat) total_num_bytes) + job->current_pos)/n_images; + + eom_job_set_progress (EOM_JOB (job), job_progress); +} + +void +eom_job_copy_run (EomJob *ejob) +{ + EomJobCopy *job; + GList *it; + guint n_images; + GFile *src, *dest; + gchar *filename, *dest_filename; + + g_return_if_fail (EOM_IS_JOB_COPY (ejob)); + + job = EOM_JOB_COPY (ejob); + + n_images = g_list_length (job->images); + + job->current_pos = 0; + + for (it = job->images; it != NULL; it = g_list_next (it), job->current_pos++) { + src = (GFile *) it->data; + filename = g_file_get_basename (src); + dest_filename = g_build_filename (job->dest, filename, NULL); + dest = g_file_new_for_path (dest_filename); + + g_file_copy (src, dest, + G_FILE_COPY_OVERWRITE, NULL, + eom_job_copy_progress_callback, job, + &ejob->error); + g_free (filename); + g_free (dest_filename); + } + + ejob->finished = TRUE; +} diff --git a/src/eom-jobs.h b/src/eom-jobs.h new file mode 100644 index 0000000..7fd4965 --- /dev/null +++ b/src/eom-jobs.h @@ -0,0 +1,275 @@ +/* Eye Of Mate - Jobs + * + * Copyright (C) 2006 The Free Software Foundation + * + * Author: Lucas Rocha <[email protected]> + * + * Based on evince code (shell/ev-jobs.h) by: + * - Martin Kretzschmar <[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 __EOM_JOBS_H__ +#define __EOM_JOBS_H__ + +#include "eom-list-store.h" +#include "eom-transform.h" +#include "eom-enums.h" + +#include <glib.h> +#include <gio/gio.h> +#include <gtk/gtk.h> +#include <gdk-pixbuf/gdk-pixbuf.h> + +G_BEGIN_DECLS + +#ifndef __EOM_IMAGE_DECLR__ +#define __EOM_IMAGE_DECLR__ + typedef struct _EomImage EomImage; +#endif + +#ifndef __EOM_URI_CONVERTER_DECLR__ +#define __EOM_URI_CONVERTER_DECLR__ +typedef struct _EomURIConverter EomURIConverter; +#endif + +#ifndef __EOM_JOB_DECLR__ +#define __EOM_JOB_DECLR__ +typedef struct _EomJob EomJob; +#endif +typedef struct _EomJobClass EomJobClass; + +typedef struct _EomJobThumbnail EomJobThumbnail; +typedef struct _EomJobThumbnailClass EomJobThumbnailClass; + +typedef struct _EomJobLoad EomJobLoad; +typedef struct _EomJobLoadClass EomJobLoadClass; + +typedef struct _EomJobModel EomJobModel; +typedef struct _EomJobModelClass EomJobModelClass; + +typedef struct _EomJobTransform EomJobTransform; +typedef struct _EomJobTransformClass EomJobTransformClass; + +typedef struct _EomJobSave EomJobSave; +typedef struct _EomJobSaveClass EomJobSaveClass; + +typedef struct _EomJobSaveAs EomJobSaveAs; +typedef struct _EomJobSaveAsClass EomJobSaveAsClass; + +typedef struct _EomJobCopy EomJobCopy; +typedef struct _EomJobCopyClass EomJobCopyClass; + +#define EOM_TYPE_JOB (eom_job_get_type()) +#define EOM_JOB(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), EOM_TYPE_JOB, EomJob)) +#define EOM_JOB_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), EOM_TYPE_JOB, EomJobClass)) +#define EOM_IS_JOB(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), EOM_TYPE_JOB)) +#define EOM_JOB_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EOM_TYPE_JOB, EomJobClass)) + +#define EOM_TYPE_JOB_THUMBNAIL (eom_job_thumbnail_get_type()) +#define EOM_JOB_THUMBNAIL(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), EOM_TYPE_JOB_THUMBNAIL, EomJobThumbnail)) +#define EOM_JOB_THUMBNAIL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), EOM_TYPE_JOB_THUMBNAIL, EomJobThumbnailClass)) +#define EOM_IS_JOB_THUMBNAIL(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), EOM_TYPE_JOB_THUMBNAIL)) + +#define EOM_TYPE_JOB_LOAD (eom_job_load_get_type()) +#define EOM_JOB_LOAD(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), EOM_TYPE_JOB_LOAD, EomJobLoad)) +#define EOM_JOB_LOAD_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), EOM_TYPE_JOB_LOAD, EomJobLoadClass)) +#define EOM_IS_JOB_LOAD(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), EOM_TYPE_JOB_LOAD)) + +#define EOM_TYPE_JOB_MODEL (eom_job_model_get_type()) +#define EOM_JOB_MODEL(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), EOM_TYPE_JOB_MODEL, EomJobModel)) +#define EOM_JOB_MODEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), EOM_TYPE_JOB_MODEL, EomJobModelClass)) +#define EOM_IS_JOB_MODEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), EOM_TYPE_JOB_MODEL)) + +#define EOM_TYPE_JOB_TRANSFORM (eom_job_transform_get_type()) +#define EOM_JOB_TRANSFORM(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), EOM_TYPE_JOB_TRANSFORM, EomJobTransform)) +#define EOM_JOB_TRANSFORM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), EOM_TYPE_JOB_TRANSFORM, EomJobTransformClass)) +#define EOM_IS_JOB_TRANSFORM(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), EOM_TYPE_JOB_TRANSFORM)) + +#define EOM_TYPE_JOB_SAVE (eom_job_save_get_type()) +#define EOM_JOB_SAVE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), EOM_TYPE_JOB_SAVE, EomJobSave)) +#define EOM_JOB_SAVE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), EOM_TYPE_JOB_SAVE, EomJobSaveClass)) +#define EOM_IS_JOB_SAVE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), EOM_TYPE_JOB_SAVE)) +#define EOM_JOB_SAVE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EOM_TYPE_JOB_SAVE, EomJobSaveClass)) + +#define EOM_TYPE_JOB_SAVE_AS (eom_job_save_as_get_type()) +#define EOM_JOB_SAVE_AS(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), EOM_TYPE_JOB_SAVE_AS, EomJobSaveAs)) +#define EOM_JOB_SAVE_AS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), EOM_TYPE_JOB_SAVE_AS, EomJobSaveAsClass)) +#define EOM_IS_JOB_SAVE_AS(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), EOM_TYPE_JOB_SAVE_AS)) + +#define EOM_TYPE_JOB_COPY (eom_job_copy_get_type()) +#define EOM_JOB_COPY(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), EOM_TYPE_JOB_COPY, EomJobCopy)) +#define EOM_JOB_COPY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), EOM_TYPE_JOB_COPY, EomJobCopyClass)) +#define EOM_IS_JOB_COPY(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), EOM_TYPE_JOB_COPY)) + + +struct _EomJob +{ + GObject parent; + + GError *error; + GMutex *mutex; + float progress; + gboolean finished; +}; + +struct _EomJobClass +{ + GObjectClass parent_class; + + void (* finished) (EomJob *job); + void (* progress) (EomJob *job, float progress); + void (*run) (EomJob *job); +}; + +struct _EomJobThumbnail +{ + EomJob parent; + EomImage *image; + GdkPixbuf *thumbnail; +}; + +struct _EomJobThumbnailClass +{ + EomJobClass parent_class; +}; + +struct _EomJobLoad +{ + EomJob parent; + EomImage *image; + EomImageData data; +}; + +struct _EomJobLoadClass +{ + EomJobClass parent_class; +}; + +struct _EomJobModel +{ + EomJob parent; + EomListStore *store; + GSList *file_list; +}; + +struct _EomJobModelClass +{ + EomJobClass parent_class; +}; + +struct _EomJobTransform +{ + EomJob parent; + GList *images; + EomTransform *trans; +}; + +struct _EomJobTransformClass +{ + EomJobClass parent_class; +}; + +typedef enum { + EOM_SAVE_RESPONSE_NONE, + EOM_SAVE_RESPONSE_RETRY, + EOM_SAVE_RESPONSE_SKIP, + EOM_SAVE_RESPONSE_OVERWRITE, + EOM_SAVE_RESPONSE_CANCEL, + EOM_SAVE_RESPONSE_LAST +} EomJobSaveResponse; + +struct _EomJobSave +{ + EomJob parent; + GList *images; + guint current_pos; + EomImage *current_image; +}; + +struct _EomJobSaveClass +{ + EomJobClass parent_class; +}; + +struct _EomJobSaveAs +{ + EomJobSave parent; + EomURIConverter *converter; + GFile *file; +}; + +struct _EomJobSaveAsClass +{ + EomJobSaveClass parent; +}; + +struct _EomJobCopy +{ + EomJob parent; + GList *images; + guint current_pos; + gchar *dest; +}; + +struct _EomJobCopyClass +{ + EomJobClass parent_class; +}; + +/* base job class */ +GType eom_job_get_type (void) G_GNUC_CONST; +void eom_job_finished (EomJob *job); +void eom_job_run (EomJob *job); +void eom_job_set_progress (EomJob *job, + float progress); + +/* EomJobThumbnail */ +GType eom_job_thumbnail_get_type (void) G_GNUC_CONST; +EomJob *eom_job_thumbnail_new (EomImage *image); + +/* EomJobLoad */ +GType eom_job_load_get_type (void) G_GNUC_CONST; +EomJob *eom_job_load_new (EomImage *image, + EomImageData data); + +/* EomJobModel */ +GType eom_job_model_get_type (void) G_GNUC_CONST; +EomJob *eom_job_model_new (GSList *file_list); + +/* EomJobTransform */ +GType eom_job_transform_get_type (void) G_GNUC_CONST; +EomJob *eom_job_transform_new (GList *images, + EomTransform *trans); + +/* EomJobSave */ +GType eom_job_save_get_type (void) G_GNUC_CONST; +EomJob *eom_job_save_new (GList *images); + +/* EomJobSaveAs */ +GType eom_job_save_as_get_type (void) G_GNUC_CONST; +EomJob *eom_job_save_as_new (GList *images, + EomURIConverter *converter, + GFile *file); + +/*EomJobCopy */ +GType eom_job_copy_get_type (void) G_GNUC_CONST; +EomJob *eom_job_copy_new (GList *images, + const gchar *dest); + +G_END_DECLS + +#endif /* __EOM_JOBS_H__ */ diff --git a/src/eom-list-store.c b/src/eom-list-store.c new file mode 100644 index 0000000..562019e --- /dev/null +++ b/src/eom-list-store.c @@ -0,0 +1,931 @@ +/* Eye Of Mate - Image Store + * + * Copyright (C) 2006-2008 The Free Software Foundation + * + * Author: Claudio Saavedra <[email protected]> + * + * Based on code by: Jens Finke <[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 "eom-list-store.h" +#include "eom-thumbnail.h" +#include "eom-image.h" +#include "eom-job-queue.h" +#include "eom-jobs.h" + +#include <string.h> + +#define EOM_LIST_STORE_GET_PRIVATE(object) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((object), EOM_TYPE_LIST_STORE, EomListStorePrivate)) + +G_DEFINE_TYPE (EomListStore, eom_list_store, GTK_TYPE_LIST_STORE); + +struct _EomListStorePrivate { + GList *monitors; /* Monitors for the directories */ + gint initial_image; /* The image that should be selected firstly by the view. */ + GdkPixbuf *busy_image; /* Loading image icon */ + GdkPixbuf *missing_image; /* Missing image icon */ + GMutex *mutex; /* Mutex for saving the jobs in the model */ +}; + +static void +eom_list_store_finalize (GObject *object) +{ + EomListStore *store = EOM_LIST_STORE (object); + + if (store->priv != NULL) { + g_free (store->priv); + store->priv = NULL; + } + + G_OBJECT_CLASS (eom_list_store_parent_class)->finalize (object); +} + +static void +foreach_monitors_free (gpointer data, gpointer user_data) +{ + g_file_monitor_cancel (G_FILE_MONITOR (data)); +} + +static void +eom_list_store_dispose (GObject *object) +{ + EomListStore *store = EOM_LIST_STORE (object); + + g_list_foreach (store->priv->monitors, + foreach_monitors_free, NULL); + + g_list_free (store->priv->monitors); + + store->priv->monitors = NULL; + + if(store->priv->busy_image != NULL) { + g_object_unref (store->priv->busy_image); + store->priv->busy_image = NULL; + } + + if(store->priv->missing_image != NULL) { + g_object_unref (store->priv->missing_image); + store->priv->missing_image = NULL; + } + + g_mutex_free (store->priv->mutex); + + G_OBJECT_CLASS (eom_list_store_parent_class)->dispose (object); +} + +static void +eom_list_store_class_init (EomListStoreClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = eom_list_store_finalize; + object_class->dispose = eom_list_store_dispose; + + g_type_class_add_private (object_class, sizeof (EomListStorePrivate)); +} + +/* + Sorting functions +*/ + +static gint +eom_list_store_compare_func (GtkTreeModel *model, + GtkTreeIter *a, + GtkTreeIter *b, + gpointer user_data) +{ + gint r_value; + + EomImage *image_a, *image_b; + + gtk_tree_model_get (model, a, + EOM_LIST_STORE_EOM_IMAGE, &image_a, + -1); + + gtk_tree_model_get (model, b, + EOM_LIST_STORE_EOM_IMAGE, &image_b, + -1); + + r_value = strcmp (eom_image_get_collate_key (image_a), + eom_image_get_collate_key (image_b)); + + g_object_unref (G_OBJECT (image_a)); + g_object_unref (G_OBJECT (image_b)); + + return r_value; +} + +static GdkPixbuf * +eom_list_store_get_icon (const gchar *icon_name) +{ + GError *error = NULL; + GtkIconTheme *icon_theme; + GdkPixbuf *pixbuf; + + icon_theme = gtk_icon_theme_get_default (); + + pixbuf = gtk_icon_theme_load_icon (icon_theme, + icon_name, + EOM_LIST_STORE_THUMB_SIZE, + 0, + &error); + + if (!pixbuf) { + g_warning ("Couldn't load icon: %s", error->message); + g_error_free (error); + } + + return pixbuf; +} + +static void +eom_list_store_init (EomListStore *self) +{ + GType types[EOM_LIST_STORE_NUM_COLUMNS]; + + types[EOM_LIST_STORE_THUMBNAIL] = GDK_TYPE_PIXBUF; + types[EOM_LIST_STORE_EOM_IMAGE] = G_TYPE_OBJECT; + types[EOM_LIST_STORE_THUMB_SET] = G_TYPE_BOOLEAN; + types[EOM_LIST_STORE_EOM_JOB] = G_TYPE_POINTER; + + gtk_list_store_set_column_types (GTK_LIST_STORE (self), + EOM_LIST_STORE_NUM_COLUMNS, types); + + self->priv = EOM_LIST_STORE_GET_PRIVATE (self); + + self->priv->monitors = NULL; + self->priv->initial_image = -1; + + self->priv->busy_image = eom_list_store_get_icon ("image-loading"); + self->priv->missing_image = eom_list_store_get_icon ("image-missing"); + + self->priv->mutex = g_mutex_new (); + + gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (self), + eom_list_store_compare_func, + NULL, NULL); + + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (self), + GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID, + GTK_SORT_ASCENDING); +} + +/** + * eom_list_store_new: + * + * Creates a new and empty #EomListStore. + * + * Returns: a newly created #EomListStore. + **/ +GtkListStore* +eom_list_store_new (void) +{ + return g_object_new (EOM_TYPE_LIST_STORE, NULL); +} + +/* + Searchs for a file in the store. If found and @iter_found is not NULL, + then sets @iter_found to a #GtkTreeIter pointing to the file. + */ +static gboolean +is_file_in_list_store (EomListStore *store, + const gchar *info_uri, + GtkTreeIter *iter_found) +{ + gboolean found = FALSE; + EomImage *image; + GFile *file; + gchar *str; + GtkTreeIter iter; + + if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter)) { + return FALSE; + } + + do { + gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, + EOM_LIST_STORE_EOM_IMAGE, &image, + -1); + if (!image) + continue; + + file = eom_image_get_file (image); + str = g_file_get_uri (file); + + found = (strcmp (str, info_uri) == 0)? TRUE : FALSE; + + g_object_unref (file); + g_free (str); + g_object_unref (G_OBJECT (image)); + + } while (!found && + gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter)); + + if (found && iter_found != NULL) { + *iter_found = iter; + } + + return found; +} + +static gboolean +is_file_in_list_store_file (EomListStore *store, + GFile *file, + GtkTreeIter *iter_found) +{ + gchar *uri_str; + gboolean result; + + uri_str = g_file_get_uri (file); + + result = is_file_in_list_store (store, uri_str, iter_found); + + g_free (uri_str); + + return result; +} + +static void +eom_job_thumbnail_cb (EomJobThumbnail *job, gpointer data) +{ + EomListStore *store; + GtkTreeIter iter; + EomImage *image; + GdkPixbuf *thumbnail; + GFile *file; + + g_return_if_fail (EOM_IS_LIST_STORE (data)); + + store = EOM_LIST_STORE (data); + + file = eom_image_get_file (job->image); + + if (is_file_in_list_store_file (store, file, &iter)) { + gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, + EOM_LIST_STORE_EOM_IMAGE, &image, + -1); + + if (job->thumbnail) { + eom_image_set_thumbnail (image, job->thumbnail); + + /* Getting the thumbnail, in case it needed + * transformations */ + thumbnail = eom_image_get_thumbnail (image); + } else { + thumbnail = g_object_ref (store->priv->missing_image); + } + + gtk_list_store_set (GTK_LIST_STORE (store), &iter, + EOM_LIST_STORE_THUMBNAIL, thumbnail, + EOM_LIST_STORE_THUMB_SET, TRUE, + EOM_LIST_STORE_EOM_JOB, NULL, + -1); + + g_object_unref (thumbnail); + } + + g_object_unref (file); +} + +static void +on_image_changed (EomImage *image, EomListStore *store) +{ + GtkTreePath *path; + GtkTreeIter iter; + gint pos; + + pos = eom_list_store_get_pos_by_image (store, image); + path = gtk_tree_path_new_from_indices (pos, -1); + + gtk_tree_model_get_iter (GTK_TREE_MODEL (store), &iter, path); + eom_list_store_thumbnail_refresh (store, &iter); + gtk_tree_path_free (path); +} + +/** + * eom_list_store_remove: + * @store: An #EomListStore. + * @iter: A #GtkTreeIter. + * + * Removes the image pointed by @iter from @store. + **/ +static void +eom_list_store_remove (EomListStore *store, GtkTreeIter *iter) +{ + EomImage *image; + + gtk_tree_model_get (GTK_TREE_MODEL (store), iter, + EOM_LIST_STORE_EOM_IMAGE, &image, + -1); + + g_signal_handlers_disconnect_by_func (image, on_image_changed, store); + g_object_unref (image); + + gtk_list_store_remove (GTK_LIST_STORE (store), iter); +} + +/** + * eom_list_store_append_image: + * @store: An #EomListStore. + * @image: An #EomImage. + * + * Adds an #EomImage to @store. The thumbnail of the image is not + * loaded and will only be loaded if the thumbnail is made visible + * or eom_list_store_set_thumbnail() is called. + * + **/ +void +eom_list_store_append_image (EomListStore *store, EomImage *image) +{ + GtkTreeIter iter; + + g_signal_connect (image, "changed", + G_CALLBACK (on_image_changed), + store); + + gtk_list_store_append (GTK_LIST_STORE (store), &iter); + gtk_list_store_set (GTK_LIST_STORE (store), &iter, + EOM_LIST_STORE_EOM_IMAGE, image, + EOM_LIST_STORE_THUMBNAIL, store->priv->busy_image, + EOM_LIST_STORE_THUMB_SET, FALSE, + -1); +} + +static void +eom_list_store_append_image_from_file (EomListStore *store, + GFile *file) +{ + EomImage *image; + + g_return_if_fail (EOM_IS_LIST_STORE (store)); + + image = eom_image_new_file (file); + + eom_list_store_append_image (store, image); +} + +static void +file_monitor_changed_cb (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event, + EomListStore *store) +{ + const char *mimetype; + GFileInfo *file_info; + GtkTreeIter iter; + EomImage *image; + + switch (event) { + case G_FILE_MONITOR_EVENT_CHANGED: + file_info = g_file_query_info (file, + G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, + 0, NULL, NULL); + if (file_info == NULL) { + break; + } + mimetype = g_file_info_get_content_type (file_info); + + if (is_file_in_list_store_file (store, file, &iter)) { + if (eom_image_is_supported_mime_type (mimetype)) { + gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, + EOM_LIST_STORE_EOM_IMAGE, &image, + -1); + eom_image_file_changed (image); + g_object_unref (image); + eom_list_store_thumbnail_refresh (store, &iter); + } else { + eom_list_store_remove (store, &iter); + } + } else { + if (eom_image_is_supported_mime_type (mimetype)) { + eom_list_store_append_image_from_file (store, file); + } + } + g_object_unref (file_info); + break; + case G_FILE_MONITOR_EVENT_DELETED: + if (is_file_in_list_store_file (store, file, &iter)) { + EomImage *image; + + gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, + EOM_LIST_STORE_EOM_IMAGE, &image, + -1); + + eom_list_store_remove (store, &iter); + } + break; + case G_FILE_MONITOR_EVENT_CREATED: + if (!is_file_in_list_store_file (store, file, NULL)) { + file_info = g_file_query_info (file, + G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, + 0, NULL, NULL); + if (file_info == NULL) { + break; + } + mimetype = g_file_info_get_content_type (file_info); + + if (eom_image_is_supported_mime_type (mimetype)) { + eom_list_store_append_image_from_file (store, file); + } + g_object_unref (file_info); + } + break; + case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED: + file_info = g_file_query_info (file, + G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, + 0, NULL, NULL); + if (file_info == NULL) { + break; + } + mimetype = g_file_info_get_content_type (file_info); + if (is_file_in_list_store_file (store, file, &iter) && + eom_image_is_supported_mime_type (mimetype)) { + eom_list_store_thumbnail_refresh (store, &iter); + } + g_object_unref (file_info); + break; + case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT: + case G_FILE_MONITOR_EVENT_PRE_UNMOUNT: + case G_FILE_MONITOR_EVENT_UNMOUNTED: + break; + } +} + +/* + * Called for each file in a directory. Checks if the file is some + * sort of image. If so, it creates an image object and adds it to the + * list. + */ +static void +directory_visit (GFile *directory, + GFileInfo *children_info, + EomListStore *store) +{ + GFile *child; + gboolean load_uri = FALSE; + const char *mime_type, *name; + + mime_type = g_file_info_get_content_type (children_info); + name = g_file_info_get_name (children_info); + + if (!g_str_has_prefix (name, ".")) { + if (eom_image_is_supported_mime_type (mime_type)) { + load_uri = TRUE; + } + } + + if (load_uri) { + child = g_file_get_child (directory, name); + eom_list_store_append_image_from_file (store, child); + } +} + +static void +eom_list_store_append_directory (EomListStore *store, + GFile *file, + GFileType file_type) +{ + GFileMonitor *file_monitor; + GFileEnumerator *file_enumerator; + GFileInfo *file_info; + + g_return_if_fail (file_type == G_FILE_TYPE_DIRECTORY); + + file_monitor = g_file_monitor_directory (file, + 0, NULL, NULL); + + if (file_monitor != NULL) { + g_signal_connect (file_monitor, "changed", + G_CALLBACK (file_monitor_changed_cb), store); + + /* prepend seems more efficient to me, we don't need this list + to be sorted */ + store->priv->monitors = g_list_prepend (store->priv->monitors, file_monitor); + } + + file_enumerator = g_file_enumerate_children (file, + G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE "," + G_FILE_ATTRIBUTE_STANDARD_NAME, + 0, NULL, NULL); + file_info = g_file_enumerator_next_file (file_enumerator, NULL, NULL); + + while (file_info != NULL) + { + directory_visit (file, file_info, store); + g_object_unref (file_info); + file_info = g_file_enumerator_next_file (file_enumerator, NULL, NULL); + } + g_object_unref (file_enumerator); +} + +/** + * eom_list_store_add_files: + * @store: An #EomListStore. + * @file_list: A %NULL-terminated list of #GFile's. + * + * Adds a list of #GFile's to @store. The given list + * must be %NULL-terminated. + * + * If any of the #GFile's in @file_list is a directory, all the images + * in that directory will be added to @store. If the list of files contains + * only one file and this is a regular file, then all the images in the same + * directory will be added as well to @store. + * + **/ +void +eom_list_store_add_files (EomListStore *store, GList *file_list) +{ + GList *it; + GFileInfo *file_info; + GFileType file_type; + GFile *initial_file = NULL; + GtkTreeIter iter; + + if (file_list == NULL) { + return; + } + + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store), + GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, + GTK_SORT_ASCENDING); + + for (it = file_list; it != NULL; it = it->next) { + GFile *file = (GFile *) it->data; + + file_info = g_file_query_info (file, + G_FILE_ATTRIBUTE_STANDARD_TYPE"," + G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, + 0, NULL, NULL); + if (file_info == NULL) { + continue; + } + file_type = g_file_info_get_file_type (file_info); + + /* Workaround for gvfs backends that don't set the GFileType. */ + if (G_UNLIKELY (file_type == G_FILE_TYPE_UNKNOWN)) { + const gchar *ctype; + + ctype = g_file_info_get_content_type (file_info); + + /* If the content type is supported adjust file_type */ + if (eom_image_is_supported_mime_type (ctype)) + file_type = G_FILE_TYPE_REGULAR; + } + + g_object_unref (file_info); + + if (file_type == G_FILE_TYPE_DIRECTORY) { + eom_list_store_append_directory (store, file, file_type); + } else if (file_type == G_FILE_TYPE_REGULAR && + g_list_length (file_list) == 1) { + + initial_file = g_file_dup (file); + + file = g_file_get_parent (file); + file_info = g_file_query_info (file, + G_FILE_ATTRIBUTE_STANDARD_TYPE, + 0, NULL, NULL); + + /* If we can't get a file_info, + file_type will stay as G_FILE_TYPE_REGULAR */ + if (file_info != NULL) { + file_type = g_file_info_get_file_type (file_info); + g_object_unref (file_info); + } + + if (file_type == G_FILE_TYPE_DIRECTORY) { + eom_list_store_append_directory (store, file, file_type); + + if (!is_file_in_list_store_file (store, + initial_file, + &iter)) { + eom_list_store_append_image_from_file (store, initial_file); + } + } else { + eom_list_store_append_image_from_file (store, initial_file); + } + g_object_unref (file); + } else if (file_type == G_FILE_TYPE_REGULAR && + g_list_length (file_list) > 1) { + eom_list_store_append_image_from_file (store, file); + } + } + + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store), + GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID, + GTK_SORT_ASCENDING); + + if (initial_file && + is_file_in_list_store_file (store, initial_file, &iter)) { + store->priv->initial_image = eom_list_store_get_pos_by_iter (store, &iter); + g_object_unref (initial_file); + } else { + store->priv->initial_image = 0; + } +} + +/** + * eom_list_store_remove_image: + * @store: An #EomListStore. + * @image: An #EomImage. + * + * Removes @image from @store. + **/ +void +eom_list_store_remove_image (EomListStore *store, EomImage *image) +{ + GtkTreeIter iter; + GFile *file; + + g_return_if_fail (EOM_IS_LIST_STORE (store)); + g_return_if_fail (EOM_IS_IMAGE (image)); + + file = eom_image_get_file (image); + + if (is_file_in_list_store_file (store, file, &iter)) { + eom_list_store_remove (store, &iter); + } + g_object_unref (file); +} + +/** + * eom_list_store_new_from_glist: + * @list: a %NULL-terminated list of #EomImage's. + * + * Creates a new #EomListStore from a list of #EomImage's. + * The given list must be %NULL-terminated. + * + * Returns: a new #EomListStore. + **/ +GtkListStore * +eom_list_store_new_from_glist (GList *list) +{ + GList *it; + + GtkListStore *store = eom_list_store_new (); + + for (it = list; it != NULL; it = it->next) { + eom_list_store_append_image (EOM_LIST_STORE (store), + EOM_IMAGE (it->data)); + } + + return store; +} + +/** + * eom_list_store_get_pos_by_image: + * @store: An #EomListStore. + * @image: An #EomImage. + * + * Gets the position where @image is stored in @store. If @image + * is not stored in @store, -1 is returned. + * + * Returns: the position of @image in @store or -1 if not found. + **/ +gint +eom_list_store_get_pos_by_image (EomListStore *store, EomImage *image) +{ + GtkTreeIter iter; + gint pos = -1; + GFile *file; + + g_return_val_if_fail (EOM_IS_LIST_STORE (store), -1); + g_return_val_if_fail (EOM_IS_IMAGE (image), -1); + + file = eom_image_get_file (image); + + if (is_file_in_list_store_file (store, file, &iter)) { + pos = eom_list_store_get_pos_by_iter (store, &iter); + } + + g_object_unref (file); + return pos; +} + +/** + * eom_list_store_get_image_by_pos: + * @store: An #EomListStore. + * @pos: the position of the required #EomImage. + * + * Gets the #EomImage in the position @pos of @store. If there is + * no image at position @pos, %NULL is returned. + * + * Returns: the #EomImage in position @pos or %NULL. + * + **/ +EomImage * +eom_list_store_get_image_by_pos (EomListStore *store, gint pos) +{ + EomImage *image = NULL; + GtkTreeIter iter; + + g_return_val_if_fail (EOM_IS_LIST_STORE (store), NULL); + + if (gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (store), &iter, NULL, pos)) { + gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, + EOM_LIST_STORE_EOM_IMAGE, &image, + -1); + } + + return image; +} + +/** + * eom_list_store_get_pos_by_iter: + * @store: An #EomListStore. + * @iter: A #GtkTreeIter pointing to an image in @store. + * + * Gets the position of the image pointed by @iter. + * + * Returns: The position of the image pointed by @iter. + **/ +gint +eom_list_store_get_pos_by_iter (EomListStore *store, + GtkTreeIter *iter) +{ + gint *indices; + GtkTreePath *path; + gint pos; + + path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), iter); + indices = gtk_tree_path_get_indices (path); + pos = indices [0]; + gtk_tree_path_free (path); + + return pos; +} + +/** + * eom_list_store_length: + * @store: An #EomListStore. + * + * Returns the number of images in the store. + * + * Returns: The number of images in @store. + **/ +gint +eom_list_store_length (EomListStore *store) +{ + g_return_val_if_fail (EOM_IS_LIST_STORE (store), -1); + + return gtk_tree_model_iter_n_children (GTK_TREE_MODEL (store), NULL); +} + +/** + * eom_list_store_get_initial_pos: + * @store: An #EomListStore. + * + * Gets the position of the #EomImage that should be loaded first. + * If not set, it returns -1. + * + * Returns: the position of the image to be loaded first or -1. + * + **/ +gint +eom_list_store_get_initial_pos (EomListStore *store) +{ + g_return_val_if_fail (EOM_IS_LIST_STORE (store), -1); + + return store->priv->initial_image; +} + +static void +eom_list_store_remove_thumbnail_job (EomListStore *store, + GtkTreeIter *iter) +{ + EomJob *job; + + gtk_tree_model_get (GTK_TREE_MODEL (store), iter, + EOM_LIST_STORE_EOM_JOB, &job, + -1); + + if (job != NULL) { + g_mutex_lock (store->priv->mutex); + eom_job_queue_remove_job (job); + gtk_list_store_set (GTK_LIST_STORE (store), iter, + EOM_LIST_STORE_EOM_JOB, NULL, + -1); + g_mutex_unlock (store->priv->mutex); + } + + +} + +static void +eom_list_store_add_thumbnail_job (EomListStore *store, GtkTreeIter *iter) +{ + EomImage *image; + EomJob *job; + + gtk_tree_model_get (GTK_TREE_MODEL (store), iter, + EOM_LIST_STORE_EOM_IMAGE, &image, + EOM_LIST_STORE_EOM_JOB, &job, + -1); + + if (job != NULL) { + g_object_unref (image); + return; + } + + job = eom_job_thumbnail_new (image); + + g_signal_connect (job, + "finished", + G_CALLBACK (eom_job_thumbnail_cb), + store); + + g_mutex_lock (store->priv->mutex); + gtk_list_store_set (GTK_LIST_STORE (store), iter, + EOM_LIST_STORE_EOM_JOB, job, + -1); + eom_job_queue_add_job (job); + g_mutex_unlock (store->priv->mutex); + g_object_unref (job); + g_object_unref (image); +} + +/** + * eom_list_store_thumbnail_set: + * @store: An #EomListStore. + * @iter: A #GtkTreeIter pointing to an image in @store. + * + * Sets the thumbnail for the image pointed by @iter. + * + **/ +void +eom_list_store_thumbnail_set (EomListStore *store, + GtkTreeIter *iter) +{ + gboolean thumb_set = FALSE; + + gtk_tree_model_get (GTK_TREE_MODEL (store), iter, + EOM_LIST_STORE_THUMB_SET, &thumb_set, + -1); + + if (thumb_set) { + return; + } + + eom_list_store_add_thumbnail_job (store, iter); +} + +/** + * eom_list_store_thumbnail_unset: + * @store: An #EomListStore. + * @iter: A #GtkTreeIter pointing to an image in @store. + * + * Unsets the thumbnail for the image pointed by @iter, changing + * it to a "busy" icon. + * + **/ +void +eom_list_store_thumbnail_unset (EomListStore *store, + GtkTreeIter *iter) +{ + EomImage *image; + + eom_list_store_remove_thumbnail_job (store, iter); + + gtk_tree_model_get (GTK_TREE_MODEL (store), iter, + EOM_LIST_STORE_EOM_IMAGE, &image, + -1); + eom_image_set_thumbnail (image, NULL); + g_object_unref (image); + + gtk_list_store_set (GTK_LIST_STORE (store), iter, + EOM_LIST_STORE_THUMBNAIL, store->priv->busy_image, + EOM_LIST_STORE_THUMB_SET, FALSE, + -1); +} + +/** + * eom_list_store_thumbnail_refresh: + * @store: An #EomListStore. + * @iter: A #GtkTreeIter pointing to an image in @store. + * + * Refreshes the thumbnail for the image pointed by @iter. + * + **/ +void +eom_list_store_thumbnail_refresh (EomListStore *store, + GtkTreeIter *iter) +{ + eom_list_store_remove_thumbnail_job (store, iter); + eom_list_store_add_thumbnail_job (store, iter); +} diff --git a/src/eom-list-store.h b/src/eom-list-store.h new file mode 100644 index 0000000..f603e53 --- /dev/null +++ b/src/eom-list-store.h @@ -0,0 +1,113 @@ +/* Eye Of Mate - Image Store + * + * Copyright (C) 2006-2007 The Free Software Foundation + * + * Author: Claudio Saavedra <[email protected]> + * + * Based on code by: Jens Finke <[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 EOM_LIST_STORE_H +#define EOM_LIST_STORE_H + +#include <gtk/gtk.h> +#include <glib-object.h> +#include <gio/gio.h> + +G_BEGIN_DECLS + +#ifndef __EOM_IMAGE_DECLR__ +#define __EOM_IMAGE_DECLR__ + typedef struct _EomImage EomImage; +#endif + +typedef struct _EomListStore EomListStore; +typedef struct _EomListStoreClass EomListStoreClass; +typedef struct _EomListStorePrivate EomListStorePrivate; + +#define EOM_TYPE_LIST_STORE eom_list_store_get_type() +#define EOM_LIST_STORE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EOM_TYPE_LIST_STORE, EomListStore)) +#define EOM_LIST_STORE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EOM_TYPE_LIST_STORE, EomListStoreClass)) +#define EOM_IS_LIST_STORE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EOM_TYPE_LIST_STORE)) +#define EOM_IS_LIST_STORE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EOM_TYPE_LIST_STORE)) +#define EOM_LIST_STORE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EOM_TYPE_LIST_STORE, EomListStoreClass)) + +#define EOM_LIST_STORE_THUMB_SIZE 90 + +typedef enum { + EOM_LIST_STORE_THUMBNAIL = 0, + EOM_LIST_STORE_THUMB_SET, + EOM_LIST_STORE_EOM_IMAGE, + EOM_LIST_STORE_EOM_JOB, + EOM_LIST_STORE_NUM_COLUMNS +} EomListStoreColumn; + +struct _EomListStore { + GtkListStore parent; + EomListStorePrivate *priv; +}; + +struct _EomListStoreClass { + GtkListStoreClass parent_class; + + /* Padding for future expansion */ + void (* _eom_reserved1) (void); + void (* _eom_reserved2) (void); + void (* _eom_reserved3) (void); + void (* _eom_reserved4) (void); +}; + +GType eom_list_store_get_type (void) G_GNUC_CONST; + +GtkListStore *eom_list_store_new (void); + +GtkListStore *eom_list_store_new_from_glist (GList *list); + +void eom_list_store_append_image (EomListStore *store, + EomImage *image); + +void eom_list_store_add_files (EomListStore *store, + GList *file_list); + +void eom_list_store_remove_image (EomListStore *store, + EomImage *image); + +gint eom_list_store_get_pos_by_image (EomListStore *store, + EomImage *image); + +EomImage *eom_list_store_get_image_by_pos (EomListStore *store, + gint pos); + +gint eom_list_store_get_pos_by_iter (EomListStore *store, + GtkTreeIter *iter); + +gint eom_list_store_length (EomListStore *store); + +gint eom_list_store_get_initial_pos (EomListStore *store); + +void eom_list_store_thumbnail_set (EomListStore *store, + GtkTreeIter *iter); + +void eom_list_store_thumbnail_unset (EomListStore *store, + GtkTreeIter *iter); + +void eom_list_store_thumbnail_refresh (EomListStore *store, + GtkTreeIter *iter); + +G_END_DECLS + +#endif diff --git a/src/eom-marshal.list b/src/eom-marshal.list new file mode 100644 index 0000000..1a117a6 --- /dev/null +++ b/src/eom-marshal.list @@ -0,0 +1,2 @@ +VOID:INT,INT +VOID:DOUBLE diff --git a/src/eom-metadata-reader-jpg.c b/src/eom-metadata-reader-jpg.c new file mode 100644 index 0000000..788d2d3 --- /dev/null +++ b/src/eom-metadata-reader-jpg.c @@ -0,0 +1,673 @@ +/* Eye Of MATE -- JPEG Metadata Reader + * + * Copyright (C) 2008 The Free Software Foundation + * + * Author: Felix Riemann <[email protected]> + * + * Based on the original EomMetadataReader code. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> + +#include "eom-metadata-reader.h" +#include "eom-metadata-reader-jpg.h" +#include "eom-debug.h" + +typedef enum { + EMR_READ = 0, + EMR_READ_SIZE_HIGH_BYTE, + EMR_READ_SIZE_LOW_BYTE, + EMR_READ_MARKER, + EMR_SKIP_BYTES, + EMR_READ_APP1, + EMR_READ_EXIF, + EMR_READ_XMP, + EMR_READ_ICC, + EMR_READ_IPTC, + EMR_FINISHED +} EomMetadataReaderState; + +typedef enum { + EJA_EXIF = 0, + EJA_XMP, + EJA_OTHER +} EomJpegApp1Type; + + +#define EOM_JPEG_MARKER_START 0xFF +#define EOM_JPEG_MARKER_SOI 0xD8 +#define EOM_JPEG_MARKER_APP1 0xE1 +#define EOM_JPEG_MARKER_APP2 0xE2 +#define EOM_JPEG_MARKER_APP14 0xED + +#define IS_FINISHED(priv) (priv->state == EMR_READ && \ + priv->exif_chunk != NULL && \ + priv->icc_chunk != NULL && \ + priv->iptc_chunk != NULL && \ + priv->xmp_chunk != NULL) + +struct _EomMetadataReaderJpgPrivate { + EomMetadataReaderState state; + + /* data fields */ + guint exif_len; + gpointer exif_chunk; + + gpointer iptc_chunk; + guint iptc_len; + + guint icc_len; + gpointer icc_chunk; + + gpointer xmp_chunk; + guint xmp_len; + + /* management fields */ + int size; + int last_marker; + int bytes_read; +}; + +#define EOM_METADATA_READER_JPG_GET_PRIVATE(object) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((object), EOM_TYPE_METADATA_READER_JPG, EomMetadataReaderJpgPrivate)) + +static void +eom_metadata_reader_jpg_init_emr_iface (gpointer g_iface, gpointer iface_data); + + +G_DEFINE_TYPE_WITH_CODE (EomMetadataReaderJpg, eom_metadata_reader_jpg, + G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (EOM_TYPE_METADATA_READER, + eom_metadata_reader_jpg_init_emr_iface)) + + +static void +eom_metadata_reader_jpg_dispose (GObject *object) +{ + EomMetadataReaderJpg *emr = EOM_METADATA_READER_JPG (object); + + if (emr->priv->exif_chunk != NULL) { + g_free (emr->priv->exif_chunk); + emr->priv->exif_chunk = NULL; + } + + if (emr->priv->iptc_chunk != NULL) { + g_free (emr->priv->iptc_chunk); + emr->priv->iptc_chunk = NULL; + } + + if (emr->priv->xmp_chunk != NULL) { + g_free (emr->priv->xmp_chunk); + emr->priv->xmp_chunk = NULL; + } + + if (emr->priv->icc_chunk != NULL) { + g_free (emr->priv->icc_chunk); + emr->priv->icc_chunk = NULL; + } + + G_OBJECT_CLASS (eom_metadata_reader_jpg_parent_class)->dispose (object); +} + +static void +eom_metadata_reader_jpg_init (EomMetadataReaderJpg *obj) +{ + EomMetadataReaderJpgPrivate *priv; + + priv = obj->priv = EOM_METADATA_READER_JPG_GET_PRIVATE (obj); + priv->exif_chunk = NULL; + priv->exif_len = 0; + priv->iptc_chunk = NULL; + priv->iptc_len = 0; + priv->icc_chunk = NULL; + priv->icc_len = 0; +} + +static void +eom_metadata_reader_jpg_class_init (EomMetadataReaderJpgClass *klass) +{ + GObjectClass *object_class = (GObjectClass*) klass; + + object_class->dispose = eom_metadata_reader_jpg_dispose; + + g_type_class_add_private (klass, sizeof (EomMetadataReaderJpgPrivate)); +} + +static gboolean +eom_metadata_reader_jpg_finished (EomMetadataReaderJpg *emr) +{ + g_return_val_if_fail (EOM_IS_METADATA_READER_JPG (emr), TRUE); + + return (emr->priv->state == EMR_FINISHED); +} + + +static EomJpegApp1Type +eom_metadata_identify_app1 (gchar *buf, guint len) +{ + if (len < 5) { + return EJA_OTHER; + } + + if (len < 29) { + return (strncmp ("Exif", buf, 5) == 0 ? EJA_EXIF : EJA_OTHER); + } + + if (strncmp ("Exif", buf, 5) == 0) { + return EJA_EXIF; + } else if (strncmp ("http://ns.adobe.com/xap/1.0/", buf, 29) == 0) { + return EJA_XMP; + } + + return EJA_OTHER; +} + +static void +eom_metadata_reader_get_next_block (EomMetadataReaderJpgPrivate* priv, + guchar *chunk, + int* i, + const guchar *buf, + int len, + EomMetadataReaderState state) +{ + if (*i + priv->size < len) { + /* read data in one block */ + memcpy ((guchar*) (chunk) + priv->bytes_read, &buf[*i], priv->size); + priv->state = EMR_READ; + *i = *i + priv->size - 1; /* the for-loop consumes the other byte */ + } else { + int chunk_len = len - *i; + memcpy ((guchar*) (chunk) + priv->bytes_read, &buf[*i], chunk_len); + priv->bytes_read += chunk_len; /* bytes already read */ + priv->size = (*i + priv->size) - len; /* remaining data to read */ + *i = len - 1; + priv->state = state; + } +} + +static void +eom_metadata_reader_jpg_consume (EomMetadataReaderJpg *emr, const guchar *buf, guint len) +{ + EomMetadataReaderJpgPrivate *priv; + EomJpegApp1Type app1_type; + int i; + EomMetadataReaderState next_state = EMR_READ; + guchar *chunk = NULL; + + g_return_if_fail (EOM_IS_METADATA_READER_JPG (emr)); + + priv = emr->priv; + + if (priv->state == EMR_FINISHED) return; + + for (i = 0; (i < len) && (priv->state != EMR_FINISHED); i++) { + + switch (priv->state) { + case EMR_READ: + if (buf[i] == EOM_JPEG_MARKER_START) { + priv->state = EMR_READ_MARKER; + } + else { + priv->state = EMR_FINISHED; + } + break; + + case EMR_READ_MARKER: + if ((buf [i] & 0xF0) == 0xE0 || buf[i] == 0xFE) { + /* we are reading some sort of APPxx or COM marker */ + /* these are always followed by 2 bytes of size information */ + priv->last_marker = buf [i]; + priv->size = 0; + priv->state = EMR_READ_SIZE_HIGH_BYTE; + + eom_debug_message (DEBUG_IMAGE_DATA, "APPx or COM Marker Found: %x", priv->last_marker); + } + else { + /* otherwise simply consume the byte */ + priv->state = EMR_READ; + } + break; + + case EMR_READ_SIZE_HIGH_BYTE: + priv->size = (buf [i] & 0xff) << 8; + priv->state = EMR_READ_SIZE_LOW_BYTE; + break; + + case EMR_READ_SIZE_LOW_BYTE: + priv->size |= (buf [i] & 0xff); + + if (priv->size > 2) /* ignore the two size-bytes */ + priv->size -= 2; + + if (priv->size == 0) { + priv->state = EMR_READ; + } else if (priv->last_marker == EOM_JPEG_MARKER_APP1 && + ((priv->exif_chunk == NULL) || (priv->xmp_chunk == NULL))) + { + priv->state = EMR_READ_APP1; + } else if (priv->last_marker == EOM_JPEG_MARKER_APP2 && + priv->icc_chunk == NULL && priv->size > 14) + { + /* Chunk has 14 bytes identification data */ + priv->state = EMR_READ_ICC; + } else if (priv->last_marker == EOM_JPEG_MARKER_APP14 && + priv->iptc_chunk == NULL) + { + priv->state = EMR_READ_IPTC; + } else { + priv->state = EMR_SKIP_BYTES; + } + + priv->last_marker = 0; + break; + + case EMR_SKIP_BYTES: + eom_debug_message (DEBUG_IMAGE_DATA, "Skip bytes: %i", priv->size); + + if (i + priv->size < len) { + i = i + priv->size - 1; /* the for-loop consumes the other byte */ + priv->size = 0; + } + else { + priv->size = (i + priv->size) - len; + i = len - 1; + } + if (priv->size == 0) { /* don't need to skip any more bytes */ + priv->state = EMR_READ; + } + break; + + case EMR_READ_APP1: + eom_debug_message (DEBUG_IMAGE_DATA, "Read APP1 data, Length: %i", priv->size); + + app1_type = eom_metadata_identify_app1 ((gchar*) &buf[i], priv->size); + + switch (app1_type) { + case EJA_EXIF: + if (priv->exif_chunk == NULL) { + priv->exif_chunk = g_new0 (guchar, priv->size); + priv->exif_len = priv->size; + priv->bytes_read = 0; + chunk = priv->exif_chunk; + next_state = EMR_READ_EXIF; + } else { + chunk = NULL; + priv->state = EMR_SKIP_BYTES; + } + break; + case EJA_XMP: + if (priv->xmp_chunk == NULL) { + priv->xmp_chunk = g_new0 (guchar, priv->size); + priv->xmp_len = priv->size; + priv->bytes_read = 0; + chunk = priv->xmp_chunk; + next_state = EMR_READ_XMP; + } else { + chunk = NULL; + priv->state = EMR_SKIP_BYTES; + } + break; + case EJA_OTHER: + default: + /* skip unknown data */ + chunk = NULL; + priv->state = EMR_SKIP_BYTES; + break; + } + + if (chunk) { + eom_metadata_reader_get_next_block (priv, chunk, + &i, buf, + len, + next_state); + } + + if (IS_FINISHED(priv)) + priv->state = EMR_FINISHED; + break; + + case EMR_READ_EXIF: + eom_debug_message (DEBUG_IMAGE_DATA, "Read continuation of EXIF data, length: %i", priv->size); + { + eom_metadata_reader_get_next_block (priv, priv->exif_chunk, + &i, buf, len, EMR_READ_EXIF); + } + if (IS_FINISHED(priv)) + priv->state = EMR_FINISHED; + break; + + case EMR_READ_XMP: + eom_debug_message (DEBUG_IMAGE_DATA, "Read continuation of XMP data, length: %i", priv->size); + { + eom_metadata_reader_get_next_block (priv, priv->xmp_chunk, + &i, buf, len, EMR_READ_XMP); + } + if (IS_FINISHED (priv)) + priv->state = EMR_FINISHED; + break; + + case EMR_READ_ICC: + eom_debug_message (DEBUG_IMAGE_DATA, + "Read continuation of ICC data, " + "length: %i", priv->size); + + if (priv->icc_chunk == NULL) { + priv->icc_chunk = g_new0 (guchar, priv->size); + priv->icc_len = priv->size; + priv->bytes_read = 0; + } + + eom_metadata_reader_get_next_block (priv, + priv->icc_chunk, + &i, buf, len, + EMR_READ_ICC); + + /* Test that the chunk actually contains ICC data. */ + if (priv->state == EMR_READ && priv->icc_chunk) { + const char* icc_chunk = priv->icc_chunk; + gboolean valid = TRUE; + + /* Chunk should begin with the + * ICC_PROFILE\0 identifier */ + valid &= strncmp (icc_chunk, + "ICC_PROFILE\0",12) == 0; + /* Make sure this is the first and only + * ICC chunk in the file as we don't + * support merging chunks yet. */ + valid &= *(guint16*)(icc_chunk+12) == 0x101; + + if (!valid) { + /* This no ICC data. Throw it away. */ + eom_debug_message (DEBUG_IMAGE_DATA, + "Supposed ICC chunk didn't validate. " + "Ignoring."); + g_free (priv->icc_chunk); + priv->icc_chunk = NULL; + priv->icc_len = 0; + } + } + + if (IS_FINISHED(priv)) + priv->state = EMR_FINISHED; + break; + + case EMR_READ_IPTC: + eom_debug_message (DEBUG_IMAGE_DATA, + "Read continuation of IPTC data, " + "length: %i", priv->size); + + if (priv->iptc_chunk == NULL) { + priv->iptc_chunk = g_new0 (guchar, priv->size); + priv->iptc_len = priv->size; + priv->bytes_read = 0; + } + + eom_metadata_reader_get_next_block (priv, + priv->iptc_chunk, + &i, buf, len, + EMR_READ_IPTC); + + if (IS_FINISHED(priv)) + priv->state = EMR_FINISHED; + break; + + default: + g_assert_not_reached (); + } + } +} + +/* Returns the raw exif data. NOTE: The caller of this function becomes + * the new owner of this piece of memory and is responsible for freeing it! + */ +static void +eom_metadata_reader_jpg_get_exif_chunk (EomMetadataReaderJpg *emr, guchar **data, guint *len) +{ + EomMetadataReaderJpgPrivate *priv; + + g_return_if_fail (EOM_IS_METADATA_READER (emr)); + priv = emr->priv; + + *data = (guchar*) priv->exif_chunk; + *len = priv->exif_len; + + priv->exif_chunk = NULL; + priv->exif_len = 0; +} + +#ifdef HAVE_EXIF +static gpointer +eom_metadata_reader_jpg_get_exif_data (EomMetadataReaderJpg *emr) +{ + EomMetadataReaderJpgPrivate *priv; + ExifData *data = NULL; + + g_return_val_if_fail (EOM_IS_METADATA_READER (emr), NULL); + priv = emr->priv; + + if (priv->exif_chunk != NULL) { + data = exif_data_new_from_data (priv->exif_chunk, priv->exif_len); + } + + return data; +} +#endif + + +#ifdef HAVE_EXEMPI + +/* skip the signature */ +#define EOM_XMP_OFFSET (29) + +static gpointer +eom_metadata_reader_jpg_get_xmp_data (EomMetadataReaderJpg *emr ) +{ + EomMetadataReaderJpgPrivate *priv; + XmpPtr xmp = NULL; + + g_return_val_if_fail (EOM_IS_METADATA_READER (emr), NULL); + + priv = emr->priv; + + if (priv->xmp_chunk != NULL) { + xmp = xmp_new (priv->xmp_chunk+EOM_XMP_OFFSET, + priv->xmp_len-EOM_XMP_OFFSET); + } + + return (gpointer)xmp; +} +#endif + +/* + * FIXME: very broken, assumes the profile fits in a single chunk. Change to + * parse the sections and construct a single memory chunk, or maybe even parse + * the profile. + */ +#ifdef HAVE_LCMS +static gpointer +eom_metadata_reader_jpg_get_icc_profile (EomMetadataReaderJpg *emr) +{ + EomMetadataReaderJpgPrivate *priv; + cmsHPROFILE profile = NULL; + + g_return_val_if_fail (EOM_IS_METADATA_READER (emr), NULL); + + priv = emr->priv; + + if (priv->icc_chunk) { + cmsErrorAction (LCMS_ERROR_SHOW); + + profile = cmsOpenProfileFromMem(priv->icc_chunk + 14, priv->icc_len - 14); + + if (profile) { + eom_debug_message (DEBUG_LCMS, "JPEG has ICC profile"); + } else { + eom_debug_message (DEBUG_LCMS, "JPEG has invalid ICC profile"); + } + } + +#ifdef HAVE_EXIF + if (!profile && priv->exif_chunk != NULL) { + ExifEntry *entry; + ExifByteOrder o; + gint color_space; + ExifData *exif = eom_metadata_reader_jpg_get_exif_data (emr); + + if (!exif) return NULL; + + o = exif_data_get_byte_order (exif); + + entry = exif_data_get_entry (exif, EXIF_TAG_COLOR_SPACE); + + if (entry == NULL) { + exif_data_unref (exif); + return NULL; + } + + color_space = exif_get_short (entry->data, o); + + switch (color_space) { + case 1: + eom_debug_message (DEBUG_LCMS, "JPEG is sRGB"); + + profile = cmsCreate_sRGBProfile (); + + break; + case 2: + eom_debug_message (DEBUG_LCMS, "JPEG is Adobe RGB (Disabled)"); + + /* TODO: create Adobe RGB profile */ + //profile = cmsCreate_Adobe1998Profile (); + + break; + case 0xFFFF: + { + cmsCIExyY whitepoint; + cmsCIExyYTRIPLE primaries; + LPGAMMATABLE gamma[3]; + double gammaValue; + ExifRational r; + + const int offset = exif_format_get_size (EXIF_FORMAT_RATIONAL); + + entry = exif_data_get_entry (exif, EXIF_TAG_WHITE_POINT); + + if (entry && entry->components == 2) { + r = exif_get_rational (entry->data, o); + whitepoint.x = (double) r.numerator / r.denominator; + + r = exif_get_rational (entry->data + offset, o); + whitepoint.y = (double) r.numerator / r.denominator; + whitepoint.Y = 1.0; + } else { + eom_debug_message (DEBUG_LCMS, "No whitepoint found"); + break; + } + + entry = exif_data_get_entry (exif, EXIF_TAG_PRIMARY_CHROMATICITIES); + + if (entry && entry->components == 6) { + r = exif_get_rational (entry->data + 0 * offset, o); + primaries.Red.x = (double) r.numerator / r.denominator; + + r = exif_get_rational (entry->data + 1 * offset, o); + primaries.Red.y = (double) r.numerator / r.denominator; + + r = exif_get_rational (entry->data + 2 * offset, o); + primaries.Green.x = (double) r.numerator / r.denominator; + + r = exif_get_rational (entry->data + 3 * offset, o); + primaries.Green.y = (double) r.numerator / r.denominator; + + r = exif_get_rational (entry->data + 4 * offset, o); + primaries.Blue.x = (double) r.numerator / r.denominator; + + r = exif_get_rational (entry->data + 5 * offset, o); + primaries.Blue.y = (double) r.numerator / r.denominator; + + primaries.Red.Y = primaries.Green.Y = primaries.Blue.Y = 1.0; + } else { + eom_debug_message (DEBUG_LCMS, "No primary chromaticities found"); + break; + } + + entry = exif_data_get_entry (exif, EXIF_TAG_GAMMA); + + if (entry) { + r = exif_get_rational (entry->data, o); + gammaValue = (double) r.numerator / r.denominator; + } else { + eom_debug_message (DEBUG_LCMS, "No gamma found"); + gammaValue = 2.2; + } + + gamma[0] = gamma[1] = gamma[2] = cmsBuildGamma (256, gammaValue); + + profile = cmsCreateRGBProfile (&whitepoint, &primaries, gamma); + + cmsFreeGamma(gamma[0]); + + eom_debug_message (DEBUG_LCMS, "JPEG is calibrated"); + + break; + } + } + + exif_data_unref (exif); + } +#endif + return profile; +} +#endif + +static void +eom_metadata_reader_jpg_init_emr_iface (gpointer g_iface, gpointer iface_data) +{ + EomMetadataReaderInterface *iface; + + iface = (EomMetadataReaderInterface*)g_iface; + + iface->consume = + (void (*) (EomMetadataReader *self, const guchar *buf, guint len)) + eom_metadata_reader_jpg_consume; + iface->finished = + (gboolean (*) (EomMetadataReader *self)) + eom_metadata_reader_jpg_finished; + iface->get_raw_exif = + (void (*) (EomMetadataReader *self, guchar **data, guint *len)) + eom_metadata_reader_jpg_get_exif_chunk; +#ifdef HAVE_EXIF + iface->get_exif_data = + (gpointer (*) (EomMetadataReader *self)) + eom_metadata_reader_jpg_get_exif_data; +#endif +#ifdef HAVE_LCMS + iface->get_icc_profile = + (gpointer (*) (EomMetadataReader *self)) + eom_metadata_reader_jpg_get_icc_profile; +#endif +#ifdef HAVE_EXEMPI + iface->get_xmp_ptr = + (gpointer (*) (EomMetadataReader *self)) + eom_metadata_reader_jpg_get_xmp_data; +#endif +} + diff --git a/src/eom-metadata-reader-jpg.h b/src/eom-metadata-reader-jpg.h new file mode 100644 index 0000000..722a720 --- /dev/null +++ b/src/eom-metadata-reader-jpg.h @@ -0,0 +1,55 @@ +/* Eye Of MATE -- JPEG Metadata Reader + * + * Copyright (C) 2008 The Free Software Foundation + * + * Author: Felix Riemann <[email protected]> + * + * Based on the original EomMetadataReader code. + * + * 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 _EOM_METADATA_READER_JPG_H_ +#define _EOM_METADATA_READER_JPG_H_ + +G_BEGIN_DECLS + +#define EOM_TYPE_METADATA_READER_JPG (eom_metadata_reader_jpg_get_type ()) +#define EOM_METADATA_READER_JPG(o) (G_TYPE_CHECK_INSTANCE_CAST ((o),EOM_TYPE_METADATA_READER_JPG, EomMetadataReaderJpg)) +#define EOM_METADATA_READER_JPG_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), EOM_TYPE_METADATA_READER_JPG, EomMetadataReaderJpgClass)) +#define EOM_IS_METADATA_READER_JPG(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), EOM_TYPE_METADATA_READER_JPG)) +#define EOM_IS_METADATA_READER_JPG_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), EOM_TYPE_METADATA_READER_JPG)) +#define EOM_METADATA_READER_JPG_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), EOM_TYPE_METADATA_READER_JPG, EomMetadataReaderJpgClass)) + +typedef struct _EomMetadataReaderJpg EomMetadataReaderJpg; +typedef struct _EomMetadataReaderJpgClass EomMetadataReaderJpgClass; +typedef struct _EomMetadataReaderJpgPrivate EomMetadataReaderJpgPrivate; + +struct _EomMetadataReaderJpg { + GObject parent; + + EomMetadataReaderJpgPrivate *priv; +}; + +struct _EomMetadataReaderJpgClass { + GObjectClass parent_klass; +}; + +G_GNUC_INTERNAL +GType eom_metadata_reader_jpg_get_type (void) G_GNUC_CONST; + +G_END_DECLS + +#endif /* _EOM_METADATA_READER_JPG_H_ */ diff --git a/src/eom-metadata-reader-png.c b/src/eom-metadata-reader-png.c new file mode 100644 index 0000000..2092f62 --- /dev/null +++ b/src/eom-metadata-reader-png.c @@ -0,0 +1,648 @@ +/* Eye Of MATE -- PNG Metadata Reader + * + * Copyright (C) 2008 The Free Software Foundation + * + * Author: Felix Riemann <[email protected]> + * + * Based on the old EomMetadataReader code. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> +#include <zlib.h> + +#include "eom-metadata-reader.h" +#include "eom-metadata-reader-png.h" +#include "eom-debug.h" + +typedef enum { + EMR_READ_MAGIC, + EMR_READ_SIZE_HIGH_HIGH_BYTE, + EMR_READ_SIZE_HIGH_LOW_BYTE, + EMR_READ_SIZE_LOW_HIGH_BYTE, + EMR_READ_SIZE_LOW_LOW_BYTE, + EMR_READ_CHUNK_NAME, + EMR_SKIP_BYTES, + EMR_CHECK_CRC, + EMR_SKIP_CRC, + EMR_READ_XMP_ITXT, + EMR_READ_ICCP, + EMR_READ_SRGB, + EMR_READ_CHRM, + EMR_READ_GAMA, + EMR_FINISHED +} EomMetadataReaderPngState; + +#if 0 +#define IS_FINISHED(priv) (priv->icc_chunk != NULL && \ + priv->xmp_chunk != NULL) +#endif + +struct _EomMetadataReaderPngPrivate { + EomMetadataReaderPngState state; + + /* data fields */ + guint32 icc_len; + gpointer icc_chunk; + + gpointer xmp_chunk; + guint32 xmp_len; + + guint32 sRGB_len; + gpointer sRGB_chunk; + + gpointer cHRM_chunk; + guint32 cHRM_len; + + guint32 gAMA_len; + gpointer gAMA_chunk; + + /* management fields */ + gsize size; + gsize bytes_read; + guint sub_step; + guchar chunk_name[4]; + gpointer *crc_chunk; + guint32 *crc_len; + guint32 target_crc; + gboolean hasIHDR; +}; + +#define EOM_METADATA_READER_PNG_GET_PRIVATE(object) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((object), EOM_TYPE_METADATA_READER_PNG, EomMetadataReaderPngPrivate)) + +static void +eom_metadata_reader_png_init_emr_iface (gpointer g_iface, gpointer iface_data); + +G_DEFINE_TYPE_WITH_CODE (EomMetadataReaderPng, eom_metadata_reader_png, + G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (EOM_TYPE_METADATA_READER, + eom_metadata_reader_png_init_emr_iface)) + +static void +eom_metadata_reader_png_dispose (GObject *object) +{ + EomMetadataReaderPng *emr = EOM_METADATA_READER_PNG (object); + EomMetadataReaderPngPrivate *priv = emr->priv; + + g_free (priv->xmp_chunk); + priv->xmp_chunk = NULL; + + g_free (priv->icc_chunk); + priv->icc_chunk = NULL; + + g_free (priv->sRGB_chunk); + priv->sRGB_chunk = NULL; + + g_free (priv->cHRM_chunk); + priv->cHRM_chunk = NULL; + + g_free (priv->gAMA_chunk); + priv->gAMA_chunk = NULL; + + G_OBJECT_CLASS (eom_metadata_reader_png_parent_class)->dispose (object); +} + +static void +eom_metadata_reader_png_init (EomMetadataReaderPng *obj) +{ + EomMetadataReaderPngPrivate *priv; + + priv = obj->priv = EOM_METADATA_READER_PNG_GET_PRIVATE (obj); + priv->icc_chunk = NULL; + priv->icc_len = 0; + priv->xmp_chunk = NULL; + priv->xmp_len = 0; + priv->sRGB_chunk = NULL; + priv->sRGB_len = 0; + priv->cHRM_chunk = NULL; + priv->cHRM_len = 0; + priv->gAMA_chunk = NULL; + priv->gAMA_len = 0; + + priv->sub_step = 0; + priv->state = EMR_READ_MAGIC; + priv->hasIHDR = FALSE; +} + +static void +eom_metadata_reader_png_class_init (EomMetadataReaderPngClass *klass) +{ + GObjectClass *object_class = (GObjectClass*) klass; + + object_class->dispose = eom_metadata_reader_png_dispose; + + g_type_class_add_private (klass, sizeof (EomMetadataReaderPngPrivate)); +} + +static gboolean +eom_metadata_reader_png_finished (EomMetadataReaderPng *emr) +{ + g_return_val_if_fail (EOM_IS_METADATA_READER_PNG (emr), TRUE); + + return (emr->priv->state == EMR_FINISHED); +} + + +static void +eom_metadata_reader_png_get_next_block (EomMetadataReaderPngPrivate* priv, + guchar *chunk, + int* i, + const guchar *buf, + int len, + EomMetadataReaderPngState state) +{ + if (*i + priv->size < len) { + /* read data in one block */ + memcpy ((guchar*) (chunk) + priv->bytes_read, &buf[*i], priv->size); + priv->state = EMR_CHECK_CRC; + *i = *i + priv->size - 1; /* the for-loop consumes the other byte */ + priv->size = 0; + } else { + int chunk_len = len - *i; + memcpy ((guchar*) (chunk) + priv->bytes_read, &buf[*i], chunk_len); + priv->bytes_read += chunk_len; /* bytes already read */ + priv->size = (*i + priv->size) - len; /* remaining data to read */ + *i = len - 1; + priv->state = state; + } +} + +static void +eom_metadata_reader_png_consume (EomMetadataReaderPng *emr, const guchar *buf, guint len) +{ + EomMetadataReaderPngPrivate *priv; + int i; + guint32 chunk_crc; + static const gchar PNGMAGIC[8] = "\x89PNG\x0D\x0A\x1a\x0A"; + + g_return_if_fail (EOM_IS_METADATA_READER_PNG (emr)); + + priv = emr->priv; + + if (priv->state == EMR_FINISHED) return; + + for (i = 0; (i < len) && (priv->state != EMR_FINISHED); i++) { + + switch (priv->state) { + case EMR_READ_MAGIC: + /* Check PNG magic string */ + if (priv->sub_step < 8 && + (gchar)buf[i] == PNGMAGIC[priv->sub_step]) { + if (priv->sub_step == 7) + priv->state = EMR_READ_SIZE_HIGH_HIGH_BYTE; + priv->sub_step++; + } else { + priv->state = EMR_FINISHED; + } + break; + case EMR_READ_SIZE_HIGH_HIGH_BYTE: + /* Read the high byte of the size's high word */ + priv->size |= (buf[i] & 0xFF) << 24; + priv->state = EMR_READ_SIZE_HIGH_LOW_BYTE; + break; + case EMR_READ_SIZE_HIGH_LOW_BYTE: + /* Read the low byte of the size's high word */ + priv->size |= (buf[i] & 0xFF) << 16; + priv->state = EMR_READ_SIZE_LOW_HIGH_BYTE; + break; + case EMR_READ_SIZE_LOW_HIGH_BYTE: + /* Read the high byte of the size's low word */ + priv->size |= (buf [i] & 0xff) << 8; + priv->state = EMR_READ_SIZE_LOW_LOW_BYTE; + break; + case EMR_READ_SIZE_LOW_LOW_BYTE: + /* Read the high byte of the size's low word */ + priv->size |= (buf [i] & 0xff); + /* The maximum chunk length is 2^31-1 */ + if (G_LIKELY (priv->size <= (guint32) 0x7fffffff)) { + priv->state = EMR_READ_CHUNK_NAME; + /* Make sure sub_step is 0 before next step */ + priv->sub_step = 0; + } else { + priv->state = EMR_FINISHED; + eom_debug_message (DEBUG_IMAGE_DATA, + "chunk size larger than " + "2^31-1; stopping parser"); + } + + break; + case EMR_READ_CHUNK_NAME: + /* Read the 4-byte chunk name */ + if (priv->sub_step > 3) + g_assert_not_reached (); + + priv->chunk_name[priv->sub_step] = buf[i]; + + if (priv->sub_step++ != 3) + break; + + if (G_UNLIKELY (!priv->hasIHDR)) { + /* IHDR should be the first chunk in a PNG */ + if (priv->size == 13 + && memcmp (priv->chunk_name, "IHDR", 4) == 0){ + priv->hasIHDR = TRUE; + } else { + /* Stop parsing if it is not */ + priv->state = EMR_FINISHED; + } + } + + /* Try to identify the chunk by its name. + * Already do some sanity checks where possible */ + if (memcmp (priv->chunk_name, "iTXt", 4) == 0 && + priv->size > (22 + 54) && priv->xmp_chunk == NULL) { + priv->state = EMR_READ_XMP_ITXT; + } else if (memcmp (priv->chunk_name, "iCCP", 4) == 0 && + priv->icc_chunk == NULL) { + priv->state = EMR_READ_ICCP; + } else if (memcmp (priv->chunk_name, "sRGB", 4) == 0 && + priv->sRGB_chunk == NULL && priv->size == 1) { + priv->state = EMR_READ_SRGB; + } else if (memcmp (priv->chunk_name, "cHRM", 4) == 0 && + priv->cHRM_chunk == NULL && priv->size == 32) { + priv->state = EMR_READ_CHRM; + } else if (memcmp (priv->chunk_name, "gAMA", 4) == 0 && + priv->gAMA_chunk == NULL && priv->size == 4) { + priv->state = EMR_READ_GAMA; + } else if (memcmp (priv->chunk_name, "IEND", 4) == 0) { + priv->state = EMR_FINISHED; + } else { + /* Skip chunk + 4-byte CRC32 value */ + priv->size += 4; + priv->state = EMR_SKIP_BYTES; + } + priv->sub_step = 0; + break; + case EMR_SKIP_CRC: + /* Skip the 4-byte CRC32 value following every chunk */ + priv->size = 4; + case EMR_SKIP_BYTES: + /* Skip chunk and start reading the size of the next one */ + eom_debug_message (DEBUG_IMAGE_DATA, + "Skip bytes: %" G_GSIZE_FORMAT, + priv->size); + + if (i + priv->size < len) { + i = i + priv->size - 1; /* the for-loop consumes the other byte */ + priv->size = 0; + priv->state = EMR_READ_SIZE_HIGH_HIGH_BYTE; + } + else { + priv->size = (i + priv->size) - len; + i = len - 1; + } + break; + case EMR_CHECK_CRC: + /* Read the chunks CRC32 value from the file,... */ + if (priv->sub_step == 0) + priv->target_crc = 0; + + priv->target_crc |= buf[i] << ((3 - priv->sub_step) * 8); + + if (priv->sub_step++ != 3) + break; + + /* ...generate the chunks CRC32,... */ + chunk_crc = crc32 (crc32 (0L, Z_NULL, 0), priv->chunk_name, 4); + chunk_crc = crc32 (chunk_crc, *priv->crc_chunk, *priv->crc_len); + + eom_debug_message (DEBUG_IMAGE_DATA, "Checking CRC: Chunk: 0x%X - Target: 0x%X", chunk_crc, priv->target_crc); + + /* ...and check if they match. If they don't throw + * the chunk away and stop parsing. */ + if (priv->target_crc == chunk_crc) { + priv->state = EMR_READ_SIZE_HIGH_HIGH_BYTE; + } else { + g_free (*priv->crc_chunk); + *priv->crc_chunk = NULL; + *priv->crc_len = 0; + /* Stop parsing for security reasons */ + priv->state = EMR_FINISHED; + } + priv->sub_step = 0; + break; + case EMR_READ_XMP_ITXT: + /* Extract an iTXt chunk possibly containing + * an XMP packet */ + eom_debug_message (DEBUG_IMAGE_DATA, + "Read XMP Chunk - size: %" + G_GSIZE_FORMAT, priv->size); + + if (priv->xmp_chunk == NULL) { + priv->xmp_chunk = g_new0 (guchar, priv->size); + priv->xmp_len = priv->size; + priv->crc_len = &priv->xmp_len; + priv->bytes_read = 0; + priv->crc_chunk = &priv->xmp_chunk; + } + eom_metadata_reader_png_get_next_block (priv, + priv->xmp_chunk, + &i, buf, len, + EMR_READ_XMP_ITXT); + + if (priv->state == EMR_CHECK_CRC) { + /* Check if it is actually an XMP chunk. + * Throw it away if not. + * The check has 4 extra \0's to check + * if the chunk is configured correctly. */ + if (memcmp (priv->xmp_chunk, "XML:com.adobe.xmp\0\0\0\0\0", 22) != 0) { + priv->state = EMR_SKIP_CRC; + g_free (priv->xmp_chunk); + priv->xmp_chunk = NULL; + priv->xmp_len = 0; + } + } + break; + case EMR_READ_ICCP: + /* Extract an iCCP chunk containing a + * deflated ICC profile. */ + eom_debug_message (DEBUG_IMAGE_DATA, + "Read ICC Chunk - size: %" + G_GSIZE_FORMAT, priv->size); + + if (priv->icc_chunk == NULL) { + priv->icc_chunk = g_new0 (guchar, priv->size); + priv->icc_len = priv->size; + priv->crc_len = &priv->icc_len; + priv->bytes_read = 0; + priv->crc_chunk = &priv->icc_chunk; + } + + eom_metadata_reader_png_get_next_block (priv, + priv->icc_chunk, + &i, buf, len, + EMR_READ_ICCP); + break; + case EMR_READ_SRGB: + /* Extract the sRGB chunk. Marks the image data as + * being in sRGB colorspace. */ + eom_debug_message (DEBUG_IMAGE_DATA, + "Read sRGB Chunk - value: %u", *(buf+i)); + + if (priv->sRGB_chunk == NULL) { + priv->sRGB_chunk = g_new0 (guchar, priv->size); + priv->sRGB_len = priv->size; + priv->crc_len = &priv->sRGB_len; + priv->bytes_read = 0; + priv->crc_chunk = &priv->sRGB_chunk; + } + + eom_metadata_reader_png_get_next_block (priv, + priv->sRGB_chunk, + &i, buf, len, + EMR_READ_SRGB); + break; + case EMR_READ_CHRM: + /* Extract the cHRM chunk. Contains the coordinates of + * the image's whitepoint and primary chromacities. */ + eom_debug_message (DEBUG_IMAGE_DATA, + "Read cHRM Chunk - size: %" + G_GSIZE_FORMAT, priv->size); + + if (priv->cHRM_chunk == NULL) { + priv->cHRM_chunk = g_new0 (guchar, priv->size); + priv->cHRM_len = priv->size; + priv->crc_len = &priv->cHRM_len; + priv->bytes_read = 0; + priv->crc_chunk = &priv->cHRM_chunk; + } + + eom_metadata_reader_png_get_next_block (priv, + priv->cHRM_chunk, + &i, buf, len, + EMR_READ_ICCP); + break; + case EMR_READ_GAMA: + /* Extract the gAMA chunk containing the + * image's gamma value */ + eom_debug_message (DEBUG_IMAGE_DATA, + "Read gAMA-Chunk - size: %" + G_GSIZE_FORMAT, priv->size); + + if (priv->gAMA_chunk == NULL) { + priv->gAMA_chunk = g_new0 (guchar, priv->size); + priv->gAMA_len = priv->size; + priv->crc_len = &priv->gAMA_len; + priv->bytes_read = 0; + priv->crc_chunk = &priv->gAMA_chunk; + } + + eom_metadata_reader_png_get_next_block (priv, + priv->gAMA_chunk, + &i, buf, len, + EMR_READ_ICCP); + break; + default: + g_assert_not_reached (); + } + } +} + +#ifdef HAVE_EXEMPI + +/* skip the chunk ID */ +#define EOM_XMP_OFFSET (22) + +static gpointer +eom_metadata_reader_png_get_xmp_data (EomMetadataReaderPng *emr ) +{ + EomMetadataReaderPngPrivate *priv; + XmpPtr xmp = NULL; + + g_return_val_if_fail (EOM_IS_METADATA_READER_PNG (emr), NULL); + + priv = emr->priv; + + if (priv->xmp_chunk != NULL) { + xmp = xmp_new (priv->xmp_chunk+EOM_XMP_OFFSET, + priv->xmp_len-EOM_XMP_OFFSET); + } + + return (gpointer) xmp; +} +#endif + +#ifdef HAVE_LCMS + +#define EXTRACT_DOUBLE_UINT_BLOCK_OFFSET(chunk,offset,divider) \ + (double)(GUINT32_FROM_BE(*((guint32*)((chunk)+((offset)*4))))/(double)(divider)) + +/* This is the amount of memory the inflate output buffer gets increased by + * while decompressing the ICC profile */ +#define EOM_ICC_INFLATE_BUFFER_STEP 1024 + +/* I haven't seen ICC profiles larger than 1MB yet. + * A maximum output buffer of 5MB should be enough. */ +#define EOM_ICC_INFLATE_BUFFER_LIMIT (1024*1024*5) + +static gpointer +eom_metadata_reader_png_get_icc_profile (EomMetadataReaderPng *emr) +{ + EomMetadataReaderPngPrivate *priv; + cmsHPROFILE profile = NULL; + + g_return_val_if_fail (EOM_IS_METADATA_READER_PNG (emr), NULL); + + priv = emr->priv; + + if (priv->icc_chunk) { + gpointer outbuf; + gsize offset = 0; + z_stream zstr; + int z_ret; + + /* Use default allocation functions */ + zstr.zalloc = Z_NULL; + zstr.zfree = Z_NULL; + zstr.opaque = Z_NULL; + + /* Skip the name of the ICC profile */ + while (*((guchar*)priv->icc_chunk+offset) != '\0') + offset++; + /* Ensure the compression method (deflate) */ + if (*((guchar*)priv->icc_chunk+(++offset)) != '\0') + return NULL; + ++offset; //offset now points to the start of the deflated data + + /* Prepare the zlib data structure for decompression */ + zstr.next_in = priv->icc_chunk + offset; + zstr.avail_in = priv->icc_len - offset; + if (inflateInit (&zstr) != Z_OK) { + return NULL; + } + + /* Prepare output buffer and make zlib aware of it */ + outbuf = g_malloc (EOM_ICC_INFLATE_BUFFER_STEP); + zstr.next_out = outbuf; + zstr.avail_out = EOM_ICC_INFLATE_BUFFER_STEP; + + do { + if (zstr.avail_out == 0) { + /* The output buffer was not large enough to + * hold all the decompressed data. Increase its + * size and continue decompression. */ + gsize new_size = zstr.total_out + EOM_ICC_INFLATE_BUFFER_STEP; + + if (G_UNLIKELY (new_size > EOM_ICC_INFLATE_BUFFER_LIMIT)) { + /* Enforce a memory limit for the output + * buffer to avoid possible OOM cases */ + inflateEnd (&zstr); + g_free (outbuf); + eom_debug_message (DEBUG_IMAGE_DATA, "ICC profile is too large. Ignoring."); + return NULL; + } + outbuf = g_realloc(outbuf, new_size); + zstr.avail_out = EOM_ICC_INFLATE_BUFFER_STEP; + zstr.next_out = outbuf + zstr.total_out; + } + z_ret = inflate (&zstr, Z_SYNC_FLUSH); + } while (z_ret == Z_OK); + + if (G_UNLIKELY (z_ret != Z_STREAM_END)) { + eom_debug_message (DEBUG_IMAGE_DATA, "Error while inflating ICC profile: %s (%d)", zstr.msg, z_ret); + inflateEnd (&zstr); + g_free (outbuf); + return NULL; + } + + cmsErrorAction (LCMS_ERROR_SHOW); + + profile = cmsOpenProfileFromMem(outbuf, zstr.total_out); + inflateEnd (&zstr); + g_free (outbuf); + + eom_debug_message (DEBUG_LCMS, "PNG has %s ICC profile", profile ? "valid" : "invalid"); + } + + if (!profile && priv->sRGB_chunk) { + eom_debug_message (DEBUG_LCMS, "PNG is sRGB"); + /* If the file has an sRGB chunk the image data is in the sRGB + * colorspace. lcms has a built-in sRGB profile. */ + + profile = cmsCreate_sRGBProfile (); + } + + if (!profile && priv->cHRM_chunk) { + cmsCIExyY whitepoint; + cmsCIExyYTRIPLE primaries; + LPGAMMATABLE gamma[3]; + double gammaValue = 2.2; // 2.2 should be a sane default gamma + + /* This uglyness extracts the chromacity and whitepoint values + * from a PNG's cHRM chunk. These can be accurate up to the + * 5th decimal point. + * They are saved as integer values multiplied by 100000. */ + + eom_debug_message (DEBUG_LCMS, "Trying to calculate color profile"); + + whitepoint.x = EXTRACT_DOUBLE_UINT_BLOCK_OFFSET (priv->cHRM_chunk, 0, 100000); + whitepoint.y = EXTRACT_DOUBLE_UINT_BLOCK_OFFSET (priv->cHRM_chunk, 1, 100000); + + primaries.Red.x = EXTRACT_DOUBLE_UINT_BLOCK_OFFSET (priv->cHRM_chunk, 2, 100000); + primaries.Red.y = EXTRACT_DOUBLE_UINT_BLOCK_OFFSET (priv->cHRM_chunk, 3, 100000); + primaries.Green.x = EXTRACT_DOUBLE_UINT_BLOCK_OFFSET (priv->cHRM_chunk, 4, 100000); + primaries.Green.y = EXTRACT_DOUBLE_UINT_BLOCK_OFFSET (priv->cHRM_chunk, 5, 100000); + primaries.Blue.x = EXTRACT_DOUBLE_UINT_BLOCK_OFFSET (priv->cHRM_chunk, 6, 100000); + primaries.Blue.y = EXTRACT_DOUBLE_UINT_BLOCK_OFFSET (priv->cHRM_chunk, 7, 100000); + + primaries.Red.Y = primaries.Green.Y = primaries.Blue.Y = 1.0; + + /* If the gAMA_chunk is present use its value which is saved + * the same way as the whitepoint. Use 2.2 as default value if + * the chunk is not present. */ + if (priv->gAMA_chunk) + gammaValue = (double) 1.0/EXTRACT_DOUBLE_UINT_BLOCK_OFFSET (priv->gAMA_chunk, 0, 100000); + + gamma[0] = gamma[1] = gamma[2] = cmsBuildGamma (256, gammaValue); + + profile = cmsCreateRGBProfile (&whitepoint, &primaries, gamma); + + cmsFreeGamma(gamma[0]); + } + + return profile; +} +#endif + +static void +eom_metadata_reader_png_init_emr_iface (gpointer g_iface, gpointer iface_data) +{ + EomMetadataReaderInterface *iface; + + iface = (EomMetadataReaderInterface*) g_iface; + + iface->consume = + (void (*) (EomMetadataReader *self, const guchar *buf, guint len)) + eom_metadata_reader_png_consume; + iface->finished = + (gboolean (*) (EomMetadataReader *self)) + eom_metadata_reader_png_finished; +#ifdef HAVE_LCMS + iface->get_icc_profile = + (cmsHPROFILE (*) (EomMetadataReader *self)) + eom_metadata_reader_png_get_icc_profile; +#endif +#ifdef HAVE_EXEMPI + iface->get_xmp_ptr = + (gpointer (*) (EomMetadataReader *self)) + eom_metadata_reader_png_get_xmp_data; +#endif +} diff --git a/src/eom-metadata-reader-png.h b/src/eom-metadata-reader-png.h new file mode 100644 index 0000000..7dde41f --- /dev/null +++ b/src/eom-metadata-reader-png.h @@ -0,0 +1,55 @@ +/* Eye Of MATE -- PNG Metadata Reader + * + * Copyright (C) 2008 The Free Software Foundation + * + * Author: Felix Riemann <[email protected]> + * + * Based on the old EomMetadataReader code. + * + * 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 _EOM_METADATA_READER_PNG_H_ +#define _EOM_METADATA_READER_PNG_H_ + +G_BEGIN_DECLS + +#define EOM_TYPE_METADATA_READER_PNG (eom_metadata_reader_png_get_type ()) +#define EOM_METADATA_READER_PNG(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), EOM_TYPE_METADATA_READER_PNG, EomMetadataReaderPng)) +#define EOM_METADATA_READER_PNG_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), EOM_TYPE_METADATA_READER_PNG, EomMetadataReaderPngClass)) +#define EOM_IS_METADATA_READER_PNG(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), EOM_TYPE_METADATA_READER_PNG)) +#define EOM_IS_METADATA_READER_PNG_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), EOM_TYPE_METADATA_READER_PNG)) +#define EOM_METADATA_READER_PNG_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), EOM_TYPE_METADATA_READER_PNG, EomMetadataReaderPngClass)) + +typedef struct _EomMetadataReaderPng EomMetadataReaderPng; +typedef struct _EomMetadataReaderPngClass EomMetadataReaderPngClass; +typedef struct _EomMetadataReaderPngPrivate EomMetadataReaderPngPrivate; + +struct _EomMetadataReaderPng { + GObject parent; + + EomMetadataReaderPngPrivate *priv; +}; + +struct _EomMetadataReaderPngClass { + GObjectClass parent_klass; +}; + +G_GNUC_INTERNAL +GType eom_metadata_reader_png_get_type (void) G_GNUC_CONST; + +G_END_DECLS + +#endif /* _EOM_METADATA_READER_PNG_H_ */ diff --git a/src/eom-metadata-reader.c b/src/eom-metadata-reader.c new file mode 100644 index 0000000..82955e8 --- /dev/null +++ b/src/eom-metadata-reader.c @@ -0,0 +1,150 @@ +/* Eye Of MATE -- Metadata Reader Interface + * + * Copyright (C) 2008 The Free Software Foundation + * + * Author: Felix Riemann <[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. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "eom-metadata-reader.h" +#include "eom-metadata-reader-jpg.h" +#include "eom-metadata-reader-png.h" +#include "eom-debug.h" + + +GType +eom_metadata_reader_get_type (void) +{ + static GType reader_type = 0; + + if (G_UNLIKELY (reader_type == 0)) { + reader_type = g_type_register_static_simple (G_TYPE_INTERFACE, + "EomMetadataReader", + sizeof (EomMetadataReaderInterface), + NULL, 0, NULL, 0); + } + + return reader_type; +} + +EomMetadataReader* +eom_metadata_reader_new (EomMetadataFileType type) +{ + EomMetadataReader *emr; + + switch (type) { + case EOM_METADATA_JPEG: + emr = EOM_METADATA_READER (g_object_new (EOM_TYPE_METADATA_READER_JPG, NULL)); + break; + case EOM_METADATA_PNG: + emr = EOM_METADATA_READER (g_object_new (EOM_TYPE_METADATA_READER_PNG, NULL)); + break; + default: + emr = NULL; + break; + } + + return emr; +} + +gboolean +eom_metadata_reader_finished (EomMetadataReader *emr) +{ + g_return_val_if_fail (EOM_IS_METADATA_READER (emr), TRUE); + + return EOM_METADATA_READER_GET_INTERFACE (emr)->finished (emr); +} + + +void +eom_metadata_reader_consume (EomMetadataReader *emr, const guchar *buf, guint len) +{ + EOM_METADATA_READER_GET_INTERFACE (emr)->consume (emr, buf, len); +} + +/* Returns the raw exif data. NOTE: The caller of this function becomes + * the new owner of this piece of memory and is responsible for freeing it! + */ +void +eom_metadata_reader_get_exif_chunk (EomMetadataReader *emr, guchar **data, guint *len) +{ + EomMetadataReaderInterface *iface; + + g_return_if_fail (data != NULL && len != NULL); + iface = EOM_METADATA_READER_GET_INTERFACE (emr); + + if (iface->get_raw_exif) { + iface->get_raw_exif (emr, data, len); + } else { + g_return_if_fail (data != NULL && len != NULL); + + *data = NULL; + *len = 0; + } + +} + +#ifdef HAVE_EXIF +ExifData* +eom_metadata_reader_get_exif_data (EomMetadataReader *emr) +{ + gpointer exif_data = NULL; + EomMetadataReaderInterface *iface; + + iface = EOM_METADATA_READER_GET_INTERFACE (emr); + + if (iface->get_exif_data) + exif_data = iface->get_exif_data (emr); + + return exif_data; +} +#endif + +#ifdef HAVE_EXEMPI +XmpPtr +eom_metadata_reader_get_xmp_data (EomMetadataReader *emr ) +{ + gpointer xmp_data = NULL; + EomMetadataReaderInterface *iface; + + iface = EOM_METADATA_READER_GET_INTERFACE (emr); + + if (iface->get_xmp_ptr) + xmp_data = iface->get_xmp_ptr (emr); + + return xmp_data; +} +#endif + +#ifdef HAVE_LCMS +cmsHPROFILE +eom_metadata_reader_get_icc_profile (EomMetadataReader *emr) +{ + EomMetadataReaderInterface *iface; + gpointer profile = NULL; + + iface = EOM_METADATA_READER_GET_INTERFACE (emr); + + if (iface->get_icc_profile) + profile = iface->get_icc_profile (emr); + + return profile; +} +#endif diff --git a/src/eom-metadata-reader.h b/src/eom-metadata-reader.h new file mode 100644 index 0000000..ad8803d --- /dev/null +++ b/src/eom-metadata-reader.h @@ -0,0 +1,112 @@ +/* Eye Of MATE -- Metadata Reader Interface + * + * Copyright (C) 2008 The Free Software Foundation + * + * Author: Felix Riemann <[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 _EOM_METADATA_READER_H_ +#define _EOM_METADATA_READER_H_ + +#include <glib-object.h> +#if HAVE_EXIF +#include <libexif/exif-data.h> +#endif +#if HAVE_EXEMPI +#include <exempi/xmp.h> +#endif +#if HAVE_LCMS +#include <lcms.h> +#endif + +G_BEGIN_DECLS + +#define EOM_TYPE_METADATA_READER (eom_metadata_reader_get_type ()) +#define EOM_METADATA_READER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), EOM_TYPE_METADATA_READER, EomMetadataReader)) +#define EOM_IS_METADATA_READER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), EOM_TYPE_METADATA_READER)) +#define EOM_METADATA_READER_GET_INTERFACE(o) (G_TYPE_INSTANCE_GET_INTERFACE ((o), EOM_TYPE_METADATA_READER, EomMetadataReaderInterface)) + +typedef struct _EomMetadataReader EomMetadataReader; +typedef struct _EomMetadataReaderInterface EomMetadataReaderInterface; + +struct _EomMetadataReaderInterface { + GTypeInterface parent; + + void (*consume) (EomMetadataReader *self, + const guchar *buf, + guint len); + + gboolean (*finished) (EomMetadataReader *self); + + void (*get_raw_exif) (EomMetadataReader *self, + guchar **data, + guint *len); + + gpointer (*get_exif_data) (EomMetadataReader *self); + + gpointer (*get_icc_profile) (EomMetadataReader *self); + + gpointer (*get_xmp_ptr) (EomMetadataReader *self); +}; + +typedef enum { + EOM_METADATA_JPEG, + EOM_METADATA_PNG +} EomMetadataFileType; + +G_GNUC_INTERNAL +GType eom_metadata_reader_get_type (void) G_GNUC_CONST; + +G_GNUC_INTERNAL +EomMetadataReader* eom_metadata_reader_new (EomMetadataFileType type); + +G_GNUC_INTERNAL +void eom_metadata_reader_consume (EomMetadataReader *emr, + const guchar *buf, + guint len); + +G_GNUC_INTERNAL +gboolean eom_metadata_reader_finished (EomMetadataReader *emr); + +G_GNUC_INTERNAL +void eom_metadata_reader_get_exif_chunk (EomMetadataReader *emr, + guchar **data, + guint *len); + +#ifdef HAVE_EXIF +G_GNUC_INTERNAL +ExifData* eom_metadata_reader_get_exif_data (EomMetadataReader *emr); +#endif + +#ifdef HAVE_EXEMPI +G_GNUC_INTERNAL +XmpPtr eom_metadata_reader_get_xmp_data (EomMetadataReader *emr); +#endif + +#if 0 +gpointer eom_metadata_reader_get_iptc_chunk (EomMetadataReader *emr); +IptcData* eom_metadata_reader_get_iptc_data (EomMetadataReader *emr); +#endif + +#ifdef HAVE_LCMS +G_GNUC_INTERNAL +cmsHPROFILE eom_metadata_reader_get_icc_profile (EomMetadataReader *emr); +#endif + +G_END_DECLS + +#endif /* _EOM_METADATA_READER_H_ */ diff --git a/src/eom-module.c b/src/eom-module.c new file mode 100644 index 0000000..79fac47 --- /dev/null +++ b/src/eom-module.c @@ -0,0 +1,167 @@ +/* Eye Of Mate - EOM Module + * + * Copyright (C) 2007 The Free Software Foundation + * + * Author: Lucas Rocha <[email protected]> + * + * Based on gedit code (gedit/gedit-module.c) by: + * - Paolo Maggi <[email protected]> + * - Marco Pesenti Gritti <[email protected]> + * - Christian Persch <[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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "eom-module.h" +#include "eom-debug.h" + +#include <gmodule.h> + +#define EOM_MODULE_GET_PRIVATE(object) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((object), EOM_TYPE_MODULE, EomModulePrivate)) + +G_DEFINE_TYPE (EomModule, eom_module, G_TYPE_TYPE_MODULE); + +typedef GType (*EomModuleRegisterFunc) (GTypeModule *); + +static gboolean +eom_module_load (GTypeModule *gmodule) +{ + EomModule *module = EOM_MODULE (gmodule); + EomModuleRegisterFunc register_func; + + eom_debug_message (DEBUG_PLUGINS, "Loading %s", module->path); + + module->library = g_module_open (module->path, 0); + + if (module->library == NULL) { + g_warning ("%s", g_module_error()); + + return FALSE; + } + + /* Extract symbols from the lib */ + if (!g_module_symbol (module->library, + "register_eom_plugin", + (void *) ®ister_func)) { + g_warning ("%s", g_module_error()); + g_module_close (module->library); + + return FALSE; + } + + /* Symbol can still be NULL even though g_module_symbol + * returned TRUE */ + if (register_func == NULL) { + g_warning ("Symbol 'register_eom_plugin' should not be NULL"); + g_module_close (module->library); + + return FALSE; + } + + module->type = register_func (gmodule); + + if (module->type == 0) { + g_warning ("Invalid eom plugin contained by module %s", module->path); + return FALSE; + } + + return TRUE; +} + +static void +eom_module_unload (GTypeModule *gmodule) +{ + EomModule *module = EOM_MODULE (gmodule); + + eom_debug_message (DEBUG_PLUGINS, "Unloading %s", module->path); + + g_module_close (module->library); + + module->library = NULL; + module->type = 0; +} + +const gchar * +eom_module_get_path (EomModule *module) +{ + g_return_val_if_fail (EOM_IS_MODULE (module), NULL); + + return module->path; +} + +GObject * +eom_module_new_object (EomModule *module) +{ + eom_debug_message (DEBUG_PLUGINS, "Creating object of type %s", g_type_name (module->type)); + + if (module->type == 0) { + return NULL; + } + + return g_object_new (module->type, NULL); +} + +static void +eom_module_init (EomModule *module) +{ + eom_debug_message (DEBUG_PLUGINS, "EomModule %p initialising", module); +} + +static void +eom_module_finalize (GObject *object) +{ + EomModule *module = EOM_MODULE (object); + + eom_debug_message (DEBUG_PLUGINS, "EomModule %p finalising", module); + + g_free (module->path); + + G_OBJECT_CLASS (eom_module_parent_class)->finalize (object); +} + +static void +eom_module_class_init (EomModuleClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (class); + GTypeModuleClass *module_class = G_TYPE_MODULE_CLASS (class); + + object_class->finalize = eom_module_finalize; + + module_class->load = eom_module_load; + module_class->unload = eom_module_unload; +} + +EomModule * +eom_module_new (const gchar *path) +{ + EomModule *module; + + if (path == NULL || path[0] == '\0') { + return NULL; + } + + module = g_object_new (EOM_TYPE_MODULE, NULL); + + g_type_module_set_name (G_TYPE_MODULE (module), path); + + module->path = g_strdup (path); + + return module; +} diff --git a/src/eom-module.h b/src/eom-module.h new file mode 100644 index 0000000..241ba28 --- /dev/null +++ b/src/eom-module.h @@ -0,0 +1,72 @@ +/* Eye Of Mate - Main Window + * + * Copyright (C) 2007 The Free Software Foundation + * + * Author: Lucas Rocha <[email protected]> + * + * Based on gedit code (gedit/gedit-module.c) by: + * - Paolo Maggi <[email protected]> + * - Marco Pesenti Gritti <[email protected]> + * - Christian Persch <[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 EOM_MODULE_H +#define EOM_MODULE_H + +#include <glib-object.h> +#include <gmodule.h> + +G_BEGIN_DECLS + +typedef struct _EomModule EomModule; +typedef struct _EomModuleClass EomModuleClass; +typedef struct _EomModulePrivate EomModulePrivate; + +#define EOM_TYPE_MODULE (eom_module_get_type ()) +#define EOM_MODULE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EOM_TYPE_MODULE, EomModule)) +#define EOM_MODULE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EOM_TYPE_MODULE, EomModuleClass)) +#define EOM_IS_MODULE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EOM_TYPE_MODULE)) +#define EOM_IS_MODULE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), EOM_TYPE_MODULE)) +#define EOM_MODULE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), EOM_TYPE_MODULE, EomModuleClass)) + +struct _EomModule { + GTypeModule parent_instance; + + GModule *library; + gchar *path; + GType type; +}; + +struct _EomModuleClass { + GTypeModuleClass parent_class; +}; + +G_GNUC_INTERNAL +GType eom_module_get_type (void) G_GNUC_CONST; + +G_GNUC_INTERNAL +EomModule *eom_module_new (const gchar *path); + +G_GNUC_INTERNAL +const gchar *eom_module_get_path (EomModule *module); + +G_GNUC_INTERNAL +GObject *eom_module_new_object (EomModule *module); + +G_END_DECLS + +#endif diff --git a/src/eom-pixbuf-util.c b/src/eom-pixbuf-util.c new file mode 100644 index 0000000..837bb42 --- /dev/null +++ b/src/eom-pixbuf-util.c @@ -0,0 +1,136 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> +#include "eom-pixbuf-util.h" + +GSList* +eom_pixbuf_get_savable_formats (void) +{ + GSList *list; + GSList *write_list = NULL; + GSList *it; + + list = gdk_pixbuf_get_formats (); + + for (it = list; it != NULL; it = it->next) { + GdkPixbufFormat *format; + + format = (GdkPixbufFormat*) it->data; + if (gdk_pixbuf_format_is_writable (format)) { + write_list = g_slist_prepend (write_list, format); + } + } + + g_slist_free (list); + write_list = g_slist_reverse (write_list); + + return write_list; +} + +GdkPixbufFormat* +eom_pixbuf_get_format_by_suffix (const char *suffix) +{ + GSList *list; + GSList *it; + GdkPixbufFormat *result = NULL; + + g_return_val_if_fail (suffix != NULL, NULL); + + list = gdk_pixbuf_get_formats (); + + for (it = list; (it != NULL) && (result == NULL); it = it->next) { + GdkPixbufFormat *format; + gchar **extensions; + int i; + + format = (GdkPixbufFormat*) it->data; + + extensions = gdk_pixbuf_format_get_extensions (format); + for (i = 0; extensions[i] != NULL; i++) { + /* g_print ("check extension: %s against %s\n", extensions[i], suffix); */ + if (g_ascii_strcasecmp (suffix, extensions[i]) == 0) { + result = format; + break; + } + } + + g_strfreev (extensions); + } + + g_slist_free (list); + + return result; +} + +char* +eom_pixbuf_get_common_suffix (GdkPixbufFormat *format) +{ + char **extensions; + int i; + char *result = NULL; + + if (format == NULL) return NULL; + + extensions = gdk_pixbuf_format_get_extensions (format); + if (extensions[0] == NULL) return NULL; + + /* try to find 3-char suffix first, use the last occurence */ + for (i = 0; extensions [i] != NULL; i++) { + if (strlen (extensions[i]) <= 3) { + g_free (result); + result = g_ascii_strdown (extensions[i], -1); + } + } + + /* otherwise take the first one */ + if (result == NULL) { + result = g_ascii_strdown (extensions[0], -1); + } + + g_strfreev (extensions); + + return result; +} + +static char* +get_suffix_from_basename (const char *basename) +{ + char *suffix; + char *suffix_start; + guint len; + + /* FIXME: does this work for all locales? */ + suffix_start = g_utf8_strrchr (basename, -1, '.'); + + if (suffix_start == NULL) + return NULL; + + len = strlen (suffix_start) - 1; + suffix = g_strndup (suffix_start+1, len); + + return suffix; + +} + +GdkPixbufFormat * +eom_pixbuf_get_format (GFile *file) +{ + GdkPixbufFormat *format; + char *path, *basename, *suffix; + g_return_val_if_fail (file != NULL, NULL); + + path = g_file_get_path (file); + basename = g_path_get_basename (path); + suffix = get_suffix_from_basename (basename); + + format = eom_pixbuf_get_format_by_suffix (suffix); + + g_free (path); + g_free (basename); + g_free (suffix); + + return format; +} + diff --git a/src/eom-pixbuf-util.h b/src/eom-pixbuf-util.h new file mode 100644 index 0000000..e3d6268 --- /dev/null +++ b/src/eom-pixbuf-util.h @@ -0,0 +1,20 @@ +#ifndef _EOM_PIXBUF_UTIL_H_ +#define _EOM_PIXBUF_UTIL_H_ + +#include <gdk-pixbuf/gdk-pixbuf.h> +#include <gio/gio.h> + +G_GNUC_INTERNAL +GSList* eom_pixbuf_get_savable_formats (void); + +G_GNUC_INTERNAL +GdkPixbufFormat* eom_pixbuf_get_format_by_suffix (const char *suffix); + +G_GNUC_INTERNAL +GdkPixbufFormat* eom_pixbuf_get_format (GFile *file); + +G_GNUC_INTERNAL +char* eom_pixbuf_get_common_suffix (GdkPixbufFormat *format); + +#endif /* _EOM_PIXBUF_UTIL_H_ */ + diff --git a/src/eom-plugin-engine.c b/src/eom-plugin-engine.c new file mode 100644 index 0000000..09662b5 --- /dev/null +++ b/src/eom-plugin-engine.c @@ -0,0 +1,943 @@ +/* Eye Of Mate - EOM Plugin Manager + * + * Copyright (C) 2007 The Free Software Foundation + * + * Author: Lucas Rocha <[email protected]> + * + * Based on gedit code (gedit/gedit-module.c) by: + * - Paolo Maggi <[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. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> + +#include "eom-plugin-engine.h" +#include "eom-plugin.h" +#include "eom-module.h" +#include "eom-debug.h" +#include "eom-application.h" +#include "eom-config-keys.h" +#include "eom-util.h" + +#include <glib/gi18n.h> +#include <glib.h> +#include <mateconf/mateconf-client.h> + +#ifdef ENABLE_PYTHON +#include "eom-python-module.h" +#endif + +#define USER_EOM_PLUGINS_LOCATION "plugins/" + +#define EOM_PLUGINS_ENGINE_BASE_KEY "/apps/eom/plugins" + +#define PLUGIN_EXT ".eom-plugin" + +typedef enum { + EOM_PLUGIN_LOADER_C, + EOM_PLUGIN_LOADER_PY, +} EomPluginLoader; + +struct _EomPluginInfo +{ + gchar *file; + + gchar *location; + EomPluginLoader loader; + GTypeModule *module; + + gchar *name; + gchar *desc; + gchar *icon_name; + gchar **authors; + gchar *copyright; + gchar *website; + + EomPlugin *plugin; + + gint active : 1; + + /* A plugin is unavailable if it is not possible to activate it + due to an error loading the plugin module (e.g. for Python plugins + when the interpreter has not been correctly initializated) */ + gint available : 1; +}; + +static void eom_plugin_engine_active_plugins_changed (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data); + +static GList *eom_plugins_list = NULL; + +static MateConfClient *eom_plugin_engine_mateconf_client = NULL; + +static GSList *active_plugins = NULL; + +static void +eom_plugin_info_free (EomPluginInfo *info) +{ + if (info->plugin != NULL) { + eom_debug_message (DEBUG_PLUGINS, "Unref plugin %s", info->name); + + g_object_unref (info->plugin); + } + + g_free (info->file); + g_free (info->location); + g_free (info->name); + g_free (info->desc); + g_free (info->icon_name); + g_free (info->website); + g_free (info->copyright); + g_strfreev (info->authors); + + g_free (info); +} + +static EomPluginInfo * +eom_plugin_engine_load (const gchar *file) +{ + EomPluginInfo *info; + GKeyFile *plugin_file = NULL; + gchar *str; + + g_return_val_if_fail (file != NULL, NULL); + + eom_debug_message (DEBUG_PLUGINS, "Loading plugin: %s", file); + + info = g_new0 (EomPluginInfo, 1); + info->file = g_strdup (file); + + plugin_file = g_key_file_new (); + + if (!g_key_file_load_from_file (plugin_file, file, G_KEY_FILE_NONE, NULL)) { + g_warning ("Bad plugin file: %s", file); + + goto error; + } + + if (!g_key_file_has_key (plugin_file, + "Eom Plugin", + "IAge", + NULL)) { + eom_debug_message (DEBUG_PLUGINS, + "IAge key does not exist in file: %s", file); + + goto error; + } + + /* Check IAge=2 */ + if (g_key_file_get_integer (plugin_file, + "Eom Plugin", + "IAge", + NULL) != 2) { + eom_debug_message (DEBUG_PLUGINS, + "Wrong IAge in file: %s", file); + + goto error; + } + + /* Get Location */ + str = g_key_file_get_string (plugin_file, + "Eom Plugin", + "Module", + NULL); + + if ((str != NULL) && (*str != '\0')) { + info->location = str; + } else { + g_warning ("Could not find 'Module' in %s", file); + + goto error; + } + + /* Get the loader for this plugin */ + str = g_key_file_get_string (plugin_file, + "Eom Plugin", + "Loader", + NULL); + + if (str && strcmp(str, "python") == 0) { + info->loader = EOM_PLUGIN_LOADER_PY; + +#ifndef ENABLE_PYTHON + g_warning ("Cannot load Python plugin '%s' since eom was not " + "compiled with Python support.", file); + + goto error; +#endif + + } else { + info->loader = EOM_PLUGIN_LOADER_C; + } + + g_free (str); + + /* Get Name */ + str = g_key_file_get_locale_string (plugin_file, + "Eom Plugin", + "Name", + NULL, NULL); + if (str) { + info->name = str; + } else { + g_warning ("Could not find 'Name' in %s", file); + + goto error; + } + + /* Get Description */ + str = g_key_file_get_locale_string (plugin_file, + "Eom Plugin", + "Description", + NULL, NULL); + if (str) { + info->desc = str; + } else { + eom_debug_message (DEBUG_PLUGINS, "Could not find 'Description' in %s", file); + } + + /* Get Icon */ + str = g_key_file_get_locale_string (plugin_file, + "Eom Plugin", + "Icon", + NULL, NULL); + if (str) { + info->icon_name = str; + } else { + eom_debug_message (DEBUG_PLUGINS, "Could not find 'Icon' in %s, " + "using 'eom-plugin'", file); + } + + /* Get Authors */ + info->authors = g_key_file_get_string_list (plugin_file, + "Eom Plugin", + "Authors", + NULL, + NULL); + + if (info->authors == NULL) + eom_debug_message (DEBUG_PLUGINS, "Could not find 'Authors' in %s", file); + + + /* Get Copyright */ + str = g_key_file_get_string (plugin_file, + "Eom Plugin", + "Copyright", + NULL); + if (str) { + info->copyright = str; + } else { + eom_debug_message (DEBUG_PLUGINS, "Could not find 'Copyright' in %s", file); + } + + /* Get Website */ + str = g_key_file_get_string (plugin_file, + "Eom Plugin", + "Website", + NULL); + if (str) { + info->website = str; + } else { + eom_debug_message (DEBUG_PLUGINS, "Could not find 'Website' in %s", file); + } + + g_key_file_free (plugin_file); + + /* If we know nothing about the availability of the plugin, + set it as available */ + info->available = TRUE; + + return info; + +error: + g_free (info->file); + g_free (info->location); + g_free (info->name); + g_free (info); + g_key_file_free (plugin_file); + + return NULL; +} + +static gint +compare_plugin_info (EomPluginInfo *info1, + EomPluginInfo *info2) +{ + return strcmp (info1->location, info2->location); +} + +static void +eom_plugin_engine_load_dir (const gchar *dir) +{ + GError *error = NULL; + GDir *d; + const gchar *dirent; + + if (!g_file_test (dir, G_FILE_TEST_IS_DIR)) { + return; + } + + g_return_if_fail (eom_plugin_engine_mateconf_client != NULL); + + eom_debug_message (DEBUG_PLUGINS, "DIR: %s", dir); + + d = g_dir_open (dir, 0, &error); + + if (!d) { + g_warning ("%s", error->message); + g_error_free (error); + + return; + } + + while ((dirent = g_dir_read_name (d))) { + if (g_str_has_suffix (dirent, PLUGIN_EXT)) { + gchar *plugin_file; + EomPluginInfo *info; + + plugin_file = g_build_filename (dir, dirent, NULL); + info = eom_plugin_engine_load (plugin_file); + g_free (plugin_file); + + if (info == NULL) + continue; + + /* If a plugin with this name has already been loaded + * drop this one (user plugins override system plugins) */ + if (g_list_find_custom (eom_plugins_list, + info, + (GCompareFunc)compare_plugin_info) != NULL) { + g_warning ("Two or more plugins named '%s'. " + "Only the first will be considered.\n", + info->location); + + eom_plugin_info_free (info); + + continue; + } + + /* Actually, the plugin will be activated when reactivate_all + * will be called for the first time. */ + info->active = (g_slist_find_custom (active_plugins, + info->location, + (GCompareFunc)strcmp) != NULL); + + eom_plugins_list = g_list_prepend (eom_plugins_list, info); + + eom_debug_message (DEBUG_PLUGINS, "Plugin %s loaded", info->name); + } + } + + eom_plugins_list = g_list_reverse (eom_plugins_list); + + g_dir_close (d); +} + +static void +eom_plugin_engine_load_all (void) +{ + gchar *pdir; + + pdir = g_build_filename (eom_util_dot_dir (), + USER_EOM_PLUGINS_LOCATION, NULL); + + /* Load user's plugins */ + eom_plugin_engine_load_dir (pdir); + + g_free (pdir); + + /* Load system plugins */ + eom_plugin_engine_load_dir (EOM_PLUGIN_DIR "/"); +} + +gboolean +eom_plugin_engine_init (void) +{ + eom_debug (DEBUG_PLUGINS); + + g_return_val_if_fail (eom_plugins_list == NULL, FALSE); + + if (!g_module_supported ()) { + g_warning ("eom is not able to initialize the plugins engine."); + + return FALSE; + } + + eom_plugin_engine_mateconf_client = mateconf_client_get_default (); + + g_return_val_if_fail (eom_plugin_engine_mateconf_client != NULL, FALSE); + + mateconf_client_add_dir (eom_plugin_engine_mateconf_client, + EOM_PLUGINS_ENGINE_BASE_KEY, + MATECONF_CLIENT_PRELOAD_ONELEVEL, + NULL); + + mateconf_client_notify_add (eom_plugin_engine_mateconf_client, + EOM_CONF_PLUGINS_ACTIVE_PLUGINS, + eom_plugin_engine_active_plugins_changed, + NULL, NULL, NULL); + + active_plugins = mateconf_client_get_list (eom_plugin_engine_mateconf_client, + EOM_CONF_PLUGINS_ACTIVE_PLUGINS, + MATECONF_VALUE_STRING, + NULL); + + eom_plugin_engine_load_all (); + + return TRUE; +} + +void +eom_plugin_engine_garbage_collect (void) +{ +#ifdef ENABLE_PYTHON + eom_python_garbage_collect (); +#endif +} + +void +eom_plugin_engine_shutdown (void) +{ + GList *pl; + + eom_debug (DEBUG_PLUGINS); + +#ifdef ENABLE_PYTHON + /* Note: that this may cause finalization of objects (typically + * the EomWindow) by running the garbage collector. Since some + * of the plugin may have installed callbacks upon object + * finalization (typically they need to free the WindowData) + * it must run before we get rid of the plugins. + */ + eom_python_shutdown (); +#endif + + g_return_if_fail (eom_plugin_engine_mateconf_client != NULL); + + for (pl = eom_plugins_list; pl; pl = pl->next) { + EomPluginInfo *info = (EomPluginInfo*) pl->data; + + eom_plugin_info_free (info); + } + + g_slist_foreach (active_plugins, (GFunc)g_free, NULL); + g_slist_free (active_plugins); + + active_plugins = NULL; + + g_list_free (eom_plugins_list); + eom_plugins_list = NULL; + + g_object_unref (eom_plugin_engine_mateconf_client); + eom_plugin_engine_mateconf_client = NULL; +} + +const GList * +eom_plugin_engine_get_plugins_list (void) +{ + eom_debug (DEBUG_PLUGINS); + + return eom_plugins_list; +} + +static gboolean +load_plugin_module (EomPluginInfo *info) +{ + gchar *path; + gchar *dirname; + + eom_debug (DEBUG_PLUGINS); + + g_return_val_if_fail (info != NULL, FALSE); + g_return_val_if_fail (info->file != NULL, FALSE); + g_return_val_if_fail (info->location != NULL, FALSE); + g_return_val_if_fail (info->plugin == NULL, FALSE); + g_return_val_if_fail (info->available, FALSE); + + switch (info->loader) { + case EOM_PLUGIN_LOADER_C: + dirname = g_path_get_dirname (info->file); + g_return_val_if_fail (dirname != NULL, FALSE); + + path = g_module_build_path (dirname, info->location); + + g_free (dirname); + + g_return_val_if_fail (path != NULL, FALSE); + + info->module = G_TYPE_MODULE (eom_module_new (path)); + + g_free (path); + + break; + +#ifdef ENABLE_PYTHON + case EOM_PLUGIN_LOADER_PY: + { + gchar *dir; + + if (!eom_python_init ()) { + /* Mark plugin as unavailable and fails */ + info->available = FALSE; + + g_warning ("Cannot load Python plugin '%s' since eom " + "was not able to initialize the Python interpreter.", + info->name); + + return FALSE; + } + + dir = g_path_get_dirname (info->file); + + g_return_val_if_fail ((info->location != NULL) && + (info->location[0] != '\0'), + FALSE); + + info->module = G_TYPE_MODULE ( + eom_python_module_new (dir, info->location)); + + g_free (dir); + + break; + } +#endif + default: + g_return_val_if_reached (FALSE); + } + + if (!g_type_module_use (info->module)) { + switch (info->loader) { + case EOM_PLUGIN_LOADER_C: + g_warning ("Cannot load plugin '%s' since file '%s' cannot be read.", + info->name, + eom_module_get_path (EOM_MODULE (info->module))); + break; + + case EOM_PLUGIN_LOADER_PY: + g_warning ("Cannot load Python plugin '%s' since file '%s' cannot be read.", + info->name, + info->location); + break; + + default: + g_return_val_if_reached (FALSE); + } + + g_object_unref (G_OBJECT (info->module)); + + info->module = NULL; + + /* Mark plugin as unavailable and fails */ + info->available = FALSE; + + return FALSE; + } + + switch (info->loader) { + case EOM_PLUGIN_LOADER_C: + info->plugin = + EOM_PLUGIN (eom_module_new_object (EOM_MODULE (info->module))); + break; + +#ifdef ENABLE_PYTHON + case EOM_PLUGIN_LOADER_PY: + info->plugin = + EOM_PLUGIN (eom_python_module_new_object (EOM_PYTHON_MODULE (info->module))); + break; +#endif + + default: + g_return_val_if_reached (FALSE); + } + + g_type_module_unuse (info->module); + + eom_debug_message (DEBUG_PLUGINS, "End"); + + return TRUE; +} + +static gboolean +eom_plugin_engine_activate_plugin_real (EomPluginInfo *info) +{ + gboolean res = TRUE; + + /* Plugin is not available, don't try to activate/load it */ + if (!info->available) { + return FALSE; + } + + if (info->plugin == NULL) + res = load_plugin_module (info); + + if (res) { + const GList *wins = eom_application_get_windows (EOM_APP); + + while (wins != NULL) { + eom_plugin_activate (info->plugin, + EOM_WINDOW (wins->data)); + + wins = g_list_next (wins); + } + } else { + g_warning ("Error activating plugin '%s'", info->name); + } + + return res; +} + +gboolean +eom_plugin_engine_activate_plugin (EomPluginInfo *info) +{ + eom_debug (DEBUG_PLUGINS); + + g_return_val_if_fail (info != NULL, FALSE); + + if (!info->available) + return FALSE; + + if (info->active) + return TRUE; + + if (eom_plugin_engine_activate_plugin_real (info)) { + gboolean res; + GSList *list; + + /* Update plugin state */ + info->active = TRUE; + + list = active_plugins; + + while (list != NULL) { + if (strcmp (info->location, (gchar *)list->data) == 0) { + g_warning ("Plugin '%s' is already active.", info->name); + + return TRUE; + } + + list = g_slist_next (list); + } + + active_plugins = g_slist_insert_sorted (active_plugins, + g_strdup (info->location), + (GCompareFunc)strcmp); + + res = mateconf_client_set_list (eom_plugin_engine_mateconf_client, + EOM_CONF_PLUGINS_ACTIVE_PLUGINS, + MATECONF_VALUE_STRING, + active_plugins, + NULL); + + if (!res) + g_warning ("Error saving the list of active plugins."); + + return TRUE; + } + + return FALSE; +} + +static void +eom_plugin_engine_deactivate_plugin_real (EomPluginInfo *info) +{ + const GList *wins = eom_application_get_windows (EOM_APP); + + while (wins != NULL) { + eom_plugin_deactivate (info->plugin, + EOM_WINDOW (wins->data)); + + wins = g_list_next (wins); + } +} + +gboolean +eom_plugin_engine_deactivate_plugin (EomPluginInfo *info) +{ + gboolean res; + GSList *list; + + eom_debug (DEBUG_PLUGINS); + + g_return_val_if_fail (info != NULL, FALSE); + + if (!info->active || !info->available) + return TRUE; + + eom_plugin_engine_deactivate_plugin_real (info); + + /* Update plugin state */ + info->active = FALSE; + + list = active_plugins; + res = (list == NULL); + + while (list != NULL) { + if (strcmp (info->location, (gchar *)list->data) == 0) { + g_free (list->data); + active_plugins = g_slist_delete_link (active_plugins, list); + list = NULL; + res = TRUE; + } else { + list = g_slist_next (list); + } + } + + if (!res) { + g_warning ("Plugin '%s' is already deactivated.", info->name); + + return TRUE; + } + + res = mateconf_client_set_list (eom_plugin_engine_mateconf_client, + EOM_CONF_PLUGINS_ACTIVE_PLUGINS, + MATECONF_VALUE_STRING, + active_plugins, + NULL); + + if (!res) + g_warning ("Error saving the list of active plugins."); + + return TRUE; +} + +gboolean +eom_plugin_engine_plugin_is_active (EomPluginInfo *info) +{ + g_return_val_if_fail (info != NULL, FALSE); + + return (info->available && info->active); +} + +gboolean +eom_plugin_engine_plugin_is_available (EomPluginInfo *info) +{ + g_return_val_if_fail (info != NULL, FALSE); + + return (info->available != FALSE); +} + +static void +reactivate_all (EomWindow *window) +{ + GList *pl; + + eom_debug (DEBUG_PLUGINS); + + for (pl = eom_plugins_list; pl; pl = pl->next) { + gboolean res = TRUE; + + EomPluginInfo *info = (EomPluginInfo*)pl->data; + + /* If plugin is not available, don't try to activate/load it */ + if (info->available && info->active) { + if (info->plugin == NULL) + res = load_plugin_module (info); + + if (res) + eom_plugin_activate (info->plugin, + window); + } + } + + eom_debug_message (DEBUG_PLUGINS, "End"); +} + +void +eom_plugin_engine_update_plugins_ui (EomWindow *window, + gboolean new_window) +{ + GList *pl; + + eom_debug (DEBUG_PLUGINS); + + g_return_if_fail (EOM_IS_WINDOW (window)); + + if (new_window) + reactivate_all (window); + + /* Updated ui of all the plugins that implement update_ui */ + for (pl = eom_plugins_list; pl; pl = pl->next) { + EomPluginInfo *info = (EomPluginInfo*)pl->data; + + if (!info->available || !info->active) + continue; + + eom_debug_message (DEBUG_PLUGINS, "Updating UI of %s", info->name); + + eom_plugin_update_ui (info->plugin, window); + } +} + +gboolean +eom_plugin_engine_plugin_is_configurable (EomPluginInfo *info) { + eom_debug (DEBUG_PLUGINS); + + g_return_val_if_fail (info != NULL, FALSE); + + if ((info->plugin == NULL) || !info->active || !info->available) + return FALSE; + + return eom_plugin_is_configurable (info->plugin); +} + +void +eom_plugin_engine_configure_plugin (EomPluginInfo *info, + GtkWindow *parent) +{ + GtkWidget *conf_dlg; + + GtkWindowGroup *wg; + + eom_debug (DEBUG_PLUGINS); + + g_return_if_fail (info != NULL); + + conf_dlg = eom_plugin_create_configure_dialog (info->plugin); + + g_return_if_fail (conf_dlg != NULL); + + gtk_window_set_transient_for (GTK_WINDOW (conf_dlg), + parent); + + // Will return a default group if no group is set + wg = gtk_window_get_group (parent); + + // For now assign a dedicated window group if it is + // the default one until we know if this is really needed + if (wg == gtk_window_get_group (NULL)) { + wg = gtk_window_group_new (); + gtk_window_group_add_window (wg, parent); + } + + gtk_window_group_add_window (wg, + GTK_WINDOW (conf_dlg)); + + gtk_window_set_modal (GTK_WINDOW (conf_dlg), TRUE); + + gtk_widget_show (conf_dlg); +} + +static void +eom_plugin_engine_active_plugins_changed (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data) +{ + GList *pl; + gboolean to_activate; + + eom_debug (DEBUG_PLUGINS); + + g_return_if_fail (entry->key != NULL); + g_return_if_fail (entry->value != NULL); + + if (!((entry->value->type == MATECONF_VALUE_LIST) && + (mateconf_value_get_list_type (entry->value) == MATECONF_VALUE_STRING))) { + g_warning ("The mateconf key '%s' may be corrupted.", EOM_CONF_PLUGINS_ACTIVE_PLUGINS); + return; + } + + active_plugins = mateconf_client_get_list (eom_plugin_engine_mateconf_client, + EOM_CONF_PLUGINS_ACTIVE_PLUGINS, + MATECONF_VALUE_STRING, + NULL); + + for (pl = eom_plugins_list; pl; pl = pl->next) { + EomPluginInfo *info = (EomPluginInfo*)pl->data; + + if (!info->available) + continue; + + to_activate = (g_slist_find_custom (active_plugins, + info->location, + (GCompareFunc)strcmp) != NULL); + + if (!info->active && to_activate) { + /* Activate plugin */ + if (eom_plugin_engine_activate_plugin_real (info)) + /* Update plugin state */ + info->active = TRUE; + } else { + if (info->active && !to_activate) { + eom_plugin_engine_deactivate_plugin_real (info); + + /* Update plugin state */ + info->active = FALSE; + } + } + } +} + +const gchar * +eom_plugin_engine_get_plugin_name (EomPluginInfo *info) +{ + g_return_val_if_fail (info != NULL, NULL); + + return info->name; +} + +const gchar * +eom_plugin_engine_get_plugin_description (EomPluginInfo *info) +{ + g_return_val_if_fail (info != NULL, NULL); + + return info->desc; +} + +const gchar * +eom_plugin_engine_get_plugin_icon_name (EomPluginInfo *info) +{ + g_return_val_if_fail (info != NULL, NULL); + + /* Use the eom-plugin icon as a default if the plugin does not + have its own */ + if (info->icon_name != NULL && + gtk_icon_theme_has_icon (gtk_icon_theme_get_default (), + info->icon_name)) + return info->icon_name; + else + return "eom-plugin"; +} + +const gchar ** +eom_plugin_engine_get_plugin_authors (EomPluginInfo *info) +{ + g_return_val_if_fail (info != NULL, (const gchar **)NULL); + + return (const gchar **) info->authors; +} + +const gchar * +eom_plugin_engine_get_plugin_website (EomPluginInfo *info) +{ + g_return_val_if_fail (info != NULL, NULL); + + return info->website; +} + +const gchar * +eom_plugin_engine_get_plugin_copyright (EomPluginInfo *info) +{ + g_return_val_if_fail (info != NULL, NULL); + + return info->copyright; +} diff --git a/src/eom-plugin-engine.h b/src/eom-plugin-engine.h new file mode 100644 index 0000000..bd2ce7f --- /dev/null +++ b/src/eom-plugin-engine.h @@ -0,0 +1,89 @@ +/* Eye Of Mate - EOM Plugin Engine + * + * Copyright (C) 2007 The Free Software Foundation + * + * Author: Lucas Rocha <[email protected]> + * + * Based on gedit code (gedit/gedit-plugins-engine.h) by: + * - Paolo Maggi <[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 __EOM_PLUGIN_ENGINE_H__ +#define __EOM_PLUGIN_ENGINE_H__ + +#include "eom-window.h" + +#include <glib.h> + +typedef struct _EomPluginInfo EomPluginInfo; + +G_GNUC_INTERNAL +gboolean eom_plugin_engine_init (void); + +G_GNUC_INTERNAL +void eom_plugin_engine_shutdown (void); + +G_GNUC_INTERNAL +void eom_plugin_engine_garbage_collect (void); + +G_GNUC_INTERNAL +const GList *eom_plugin_engine_get_plugins_list (void); + +G_GNUC_INTERNAL +gboolean eom_plugin_engine_activate_plugin (EomPluginInfo *info); + +G_GNUC_INTERNAL +gboolean eom_plugin_engine_deactivate_plugin (EomPluginInfo *info); + +G_GNUC_INTERNAL +gboolean eom_plugin_engine_plugin_is_active (EomPluginInfo *info); + +G_GNUC_INTERNAL +gboolean eom_plugin_engine_plugin_is_available (EomPluginInfo *info); + +G_GNUC_INTERNAL +gboolean eom_plugin_engine_plugin_is_configurable + (EomPluginInfo *info); + +G_GNUC_INTERNAL +void eom_plugin_engine_configure_plugin (EomPluginInfo *info, + GtkWindow *parent); + +G_GNUC_INTERNAL +void eom_plugin_engine_update_plugins_ui (EomWindow *window, + gboolean new_window); + +G_GNUC_INTERNAL +const gchar *eom_plugin_engine_get_plugin_name (EomPluginInfo *info); + +G_GNUC_INTERNAL +const gchar *eom_plugin_engine_get_plugin_description + (EomPluginInfo *info); + +G_GNUC_INTERNAL +const gchar *eom_plugin_engine_get_plugin_icon_name (EomPluginInfo *info); + +G_GNUC_INTERNAL +const gchar **eom_plugin_engine_get_plugin_authors (EomPluginInfo *info); + +G_GNUC_INTERNAL +const gchar *eom_plugin_engine_get_plugin_website (EomPluginInfo *info); + +G_GNUC_INTERNAL +const gchar *eom_plugin_engine_get_plugin_copyright (EomPluginInfo *info); + +#endif /* __EOM_PLUGIN_ENGINE_H__ */ diff --git a/src/eom-plugin-manager.c b/src/eom-plugin-manager.c new file mode 100644 index 0000000..623827f --- /dev/null +++ b/src/eom-plugin-manager.c @@ -0,0 +1,917 @@ +/* Eye Of Mate - EOM Plugin Manager + * + * Copyright (C) 2007 The Free Software Foundation + * + * Author: Lucas Rocha <[email protected]> + * + * Based on gedit code (gedit/gedit-module.c) by: + * - Paolo Maggi <[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. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> + +#include "eom-plugin-manager.h" +#include "eom-plugin-engine.h" +#include "eom-util.h" +#include "eom-plugin.h" +#include "eom-debug.h" + +#include <glib/gi18n.h> + +enum { + ACTIVE_COLUMN, + AVAILABLE_COLUMN, + INFO_COLUMN, + N_COLUMNS +}; + +#define EOM_PLUGIN_MANAGER_GET_PRIVATE(object) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((object), EOM_TYPE_PLUGIN_MANAGER, EomPluginManagerPrivate)) + +G_DEFINE_TYPE (EomPluginManager, eom_plugin_manager, GTK_TYPE_VBOX) + +#define PLUGIN_MANAGER_NAME_TITLE _("Plugin") +#define PLUGIN_MANAGER_ACTIVE_TITLE _("Enabled") + +struct _EomPluginManagerPrivate { + GtkWidget *tree; + + GtkWidget *about_button; + GtkWidget *configure_button; + + const GList *plugins; + + GtkWidget *about; + + GtkWidget *popup_menu; +}; + +static EomPluginInfo *plugin_manager_get_selected_plugin (EomPluginManager *pm); +static void plugin_manager_toggle_active (GtkTreeIter *iter, GtkTreeModel *model); +static void eom_plugin_manager_finalize (GObject *object); + +static void +eom_plugin_manager_class_init (EomPluginManagerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = eom_plugin_manager_finalize; + + g_type_class_add_private (object_class, sizeof (EomPluginManagerPrivate)); +} + +static void +about_button_cb (GtkWidget *button, + EomPluginManager *pm) +{ + EomPluginInfo *info; + + eom_debug (DEBUG_PLUGINS); + + info = plugin_manager_get_selected_plugin (pm); + + g_return_if_fail (info != NULL); + + /* If there is another about dialog already open destroy it */ + if (pm->priv->about) + gtk_widget_destroy (pm->priv->about); + + pm->priv->about = g_object_new (GTK_TYPE_ABOUT_DIALOG, + "program-name" , eom_plugin_engine_get_plugin_name (info), + "copyright", eom_plugin_engine_get_plugin_copyright (info), + "authors", eom_plugin_engine_get_plugin_authors (info), + "comments", eom_plugin_engine_get_plugin_description (info), + "website", eom_plugin_engine_get_plugin_website (info), + "logo-icon-name", eom_plugin_engine_get_plugin_icon_name (info), + NULL); + + gtk_window_set_destroy_with_parent (GTK_WINDOW (pm->priv->about), + TRUE); + + g_signal_connect (pm->priv->about, + "response", + G_CALLBACK (gtk_widget_destroy), + NULL); + + g_signal_connect (pm->priv->about, + "destroy", + G_CALLBACK (gtk_widget_destroyed), + &pm->priv->about); + + gtk_window_set_transient_for (GTK_WINDOW (pm->priv->about), + GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET(pm)))); + + gtk_widget_show (pm->priv->about); +} + +static void +configure_button_cb (GtkWidget *button, + EomPluginManager *pm) +{ + EomPluginInfo *info; + GtkWindow *toplevel; + + eom_debug (DEBUG_PLUGINS); + + info = plugin_manager_get_selected_plugin (pm); + + g_return_if_fail (info != NULL); + + eom_debug_message (DEBUG_PLUGINS, "Configuring: %s\n", + eom_plugin_engine_get_plugin_name (info)); + + toplevel = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET(pm))); + + eom_plugin_engine_configure_plugin (info, toplevel); + + eom_debug_message (DEBUG_PLUGINS, "Done"); +} + +static void +plugin_manager_view_info_cell_cb (GtkTreeViewColumn *tree_column, + GtkCellRenderer *cell, + GtkTreeModel *tree_model, + GtkTreeIter *iter, + gpointer data) +{ + EomPluginInfo *info; + gchar *text; + + g_return_if_fail (tree_model != NULL); + g_return_if_fail (tree_column != NULL); + + gtk_tree_model_get (tree_model, iter, INFO_COLUMN, &info, -1); + + if (info == NULL) + return; + + text = g_markup_printf_escaped ("<b>%s</b>\n%s", + eom_plugin_engine_get_plugin_name (info), + eom_plugin_engine_get_plugin_description (info)); + + g_object_set (G_OBJECT (cell), + "markup", text, + "sensitive", eom_plugin_engine_plugin_is_available (info), + NULL); + + g_free (text); +} + +static void +plugin_manager_view_icon_cell_cb (GtkTreeViewColumn *tree_column, + GtkCellRenderer *cell, + GtkTreeModel *tree_model, + GtkTreeIter *iter, + gpointer data) +{ + EomPluginInfo *info; + + g_return_if_fail (tree_model != NULL); + g_return_if_fail (tree_column != NULL); + + gtk_tree_model_get (tree_model, iter, INFO_COLUMN, &info, -1); + + if (info == NULL) + return; + + g_object_set (G_OBJECT (cell), + "icon-name", eom_plugin_engine_get_plugin_icon_name (info), + "sensitive", eom_plugin_engine_plugin_is_available (info), + NULL); +} + + +static void +active_toggled_cb (GtkCellRendererToggle *cell, + gchar *path_str, + EomPluginManager *pm) +{ + GtkTreeIter iter; + GtkTreePath *path; + GtkTreeModel *model; + + eom_debug (DEBUG_PLUGINS); + + path = gtk_tree_path_new_from_string (path_str); + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (pm->priv->tree)); + + g_return_if_fail (model != NULL); + + gtk_tree_model_get_iter (model, &iter, path); + + if (&iter != NULL) + plugin_manager_toggle_active (&iter, model); + + gtk_tree_path_free (path); +} + +static void +cursor_changed_cb (GtkTreeView *view, + gpointer data) +{ + EomPluginManager *pm = data; + EomPluginInfo *info; + + eom_debug (DEBUG_PLUGINS); + + info = plugin_manager_get_selected_plugin (pm); + + gtk_widget_set_sensitive (GTK_WIDGET (pm->priv->about_button), + info != NULL); + + gtk_widget_set_sensitive (GTK_WIDGET (pm->priv->configure_button), + (info != NULL) && + eom_plugin_engine_plugin_is_configurable (info)); +} + +static void +row_activated_cb (GtkTreeView *tree_view, + GtkTreePath *path, + GtkTreeViewColumn *column, + gpointer data) +{ + EomPluginManager *pm = data; + GtkTreeIter iter; + GtkTreeModel *model; + + eom_debug (DEBUG_PLUGINS); + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (pm->priv->tree)); + + g_return_if_fail (model != NULL); + + gtk_tree_model_get_iter (model, &iter, path); + + g_return_if_fail (&iter != NULL); + + plugin_manager_toggle_active (&iter, model); +} + +static void +plugin_manager_populate_lists (EomPluginManager *pm) +{ + const GList *plugins; + GtkListStore *model; + GtkTreeIter iter; + + eom_debug (DEBUG_PLUGINS); + + plugins = pm->priv->plugins; + + model = GTK_LIST_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (pm->priv->tree))); + + while (plugins) { + EomPluginInfo *info; + info = (EomPluginInfo *)plugins->data; + + gtk_list_store_append (model, &iter); + gtk_list_store_set (model, &iter, + ACTIVE_COLUMN, eom_plugin_engine_plugin_is_active (info), + AVAILABLE_COLUMN, eom_plugin_engine_plugin_is_available (info), + INFO_COLUMN, info, + -1); + + plugins = plugins->next; + } + + if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (model), &iter)) { + GtkTreeSelection *selection; + EomPluginInfo* info; + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (pm->priv->tree)); + + g_return_if_fail (selection != NULL); + + gtk_tree_selection_select_iter (selection, &iter); + + gtk_tree_model_get (GTK_TREE_MODEL (model), &iter, + INFO_COLUMN, &info, -1); + + gtk_widget_set_sensitive (GTK_WIDGET (pm->priv->configure_button), + eom_plugin_engine_plugin_is_configurable (info)); + } +} + +static gboolean +plugin_manager_set_active (GtkTreeIter *iter, + GtkTreeModel *model, + gboolean active) +{ + EomPluginInfo *info; + gboolean res = TRUE; + + eom_debug (DEBUG_PLUGINS); + + gtk_tree_model_get (model, iter, INFO_COLUMN, &info, -1); + + g_return_val_if_fail (info != NULL, FALSE); + + if (active) { + /* Activate the plugin */ + if (!eom_plugin_engine_activate_plugin (info)) { + eom_debug_message (DEBUG_PLUGINS, "Could not activate %s.\n", + eom_plugin_engine_get_plugin_name (info)); + + res = FALSE; + } + } else { + /* Deactivate the plugin */ + if (!eom_plugin_engine_deactivate_plugin (info)) { + eom_debug_message (DEBUG_PLUGINS, "Could not deactivate %s.\n", + eom_plugin_engine_get_plugin_name (info)); + + res = FALSE; + } + } + + /* Set new value */ + gtk_list_store_set (GTK_LIST_STORE (model), + iter, + ACTIVE_COLUMN, eom_plugin_engine_plugin_is_active (info), + AVAILABLE_COLUMN, eom_plugin_engine_plugin_is_available (info), + -1); + + return res; +} + +static void +plugin_manager_toggle_active (GtkTreeIter *iter, + GtkTreeModel *model) +{ + gboolean active; + + eom_debug (DEBUG_PLUGINS); + + gtk_tree_model_get (model, iter, ACTIVE_COLUMN, &active, -1); + + active ^= 1; + + plugin_manager_set_active (iter, model, active); +} + +static EomPluginInfo * +plugin_manager_get_selected_plugin (EomPluginManager *pm) +{ + EomPluginInfo *info = NULL; + GtkTreeModel *model; + GtkTreeIter iter; + GtkTreeSelection *selection; + + eom_debug (DEBUG_PLUGINS); + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (pm->priv->tree)); + + g_return_val_if_fail (model != NULL, NULL); + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (pm->priv->tree)); + + g_return_val_if_fail (selection != NULL, NULL); + + if (gtk_tree_selection_get_selected (selection, NULL, &iter)) { + gtk_tree_model_get (model, &iter, INFO_COLUMN, &info, -1); + } + + return info; +} + +static void +plugin_manager_set_active_all (EomPluginManager *pm, + gboolean active) +{ + GtkTreeModel *model; + GtkTreeIter iter; + + eom_debug (DEBUG_PLUGINS); + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (pm->priv->tree)); + + g_return_if_fail (model != NULL); + + gtk_tree_model_get_iter_first (model, &iter); + + do { + plugin_manager_set_active (&iter, model, active); + } while (gtk_tree_model_iter_next (model, &iter)); +} + +/* Callback used as the interactive search comparison function */ +static gboolean +name_search_cb (GtkTreeModel *model, + gint column, + const gchar *key, + GtkTreeIter *iter, + gpointer data) +{ + EomPluginInfo *info; + gchar *normalized_string; + gchar *normalized_key; + gchar *case_normalized_string; + gchar *case_normalized_key; + gint key_len; + gboolean retval; + + gtk_tree_model_get (model, iter, INFO_COLUMN, &info, -1); + + if (!info) + return FALSE; + + normalized_string = g_utf8_normalize (eom_plugin_engine_get_plugin_name (info), -1, G_NORMALIZE_ALL); + normalized_key = g_utf8_normalize (key, -1, G_NORMALIZE_ALL); + case_normalized_string = g_utf8_casefold (normalized_string, -1); + case_normalized_key = g_utf8_casefold (normalized_key, -1); + + key_len = strlen (case_normalized_key); + + /* Oddly enough, this callback must return whether to stop the search + * because we found a match, not whether we actually matched. */ + retval = (strncmp (case_normalized_key, case_normalized_string, key_len) != 0); + + g_free (normalized_key); + g_free (normalized_string); + g_free (case_normalized_key); + g_free (case_normalized_string); + + return retval; +} + +static void +enable_plugin_menu_cb (GtkMenu *menu, + EomPluginManager *pm) +{ + GtkTreeModel *model; + GtkTreeIter iter; + GtkTreeSelection *selection; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (pm->priv->tree)); + + g_return_if_fail (model != NULL); + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (pm->priv->tree)); + + g_return_if_fail (selection != NULL); + + if (gtk_tree_selection_get_selected (selection, NULL, &iter)) + plugin_manager_toggle_active (&iter, model); +} + +static void +enable_all_menu_cb (GtkMenu *menu, + EomPluginManager *pm) +{ + plugin_manager_set_active_all (pm, TRUE); +} + +static void +disable_all_menu_cb (GtkMenu *menu, + EomPluginManager *pm) +{ + plugin_manager_set_active_all (pm, FALSE); +} + +static GtkWidget * +create_tree_popup_menu (EomPluginManager *pm) +{ + GtkWidget *menu; + GtkWidget *item; + GtkWidget *image; + EomPluginInfo *info; + + info = plugin_manager_get_selected_plugin (pm); + + if (info == NULL) + return NULL; + + menu = gtk_menu_new (); + + item = gtk_image_menu_item_new_with_mnemonic (_("_About")); + image = gtk_image_new_from_stock (GTK_STOCK_ABOUT, + GTK_ICON_SIZE_MENU); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image); + g_signal_connect (item, "activate", + G_CALLBACK (about_button_cb), pm); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + + item = gtk_image_menu_item_new_with_mnemonic (_("C_onfigure")); + image = gtk_image_new_from_stock (GTK_STOCK_PREFERENCES, + GTK_ICON_SIZE_MENU); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image); + g_signal_connect (item, "activate", + G_CALLBACK (configure_button_cb), pm); + gtk_widget_set_sensitive (item, + eom_plugin_engine_plugin_is_configurable (info)); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + + item = gtk_check_menu_item_new_with_mnemonic (_("A_ctivate")); + gtk_widget_set_sensitive (item, + eom_plugin_engine_plugin_is_available (info)); + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), + eom_plugin_engine_plugin_is_active (info)); + g_signal_connect (item, "toggled", + G_CALLBACK (enable_plugin_menu_cb), pm); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + + item = gtk_separator_menu_item_new (); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + + item = gtk_menu_item_new_with_mnemonic (_("Ac_tivate All")); + g_signal_connect (item, "activate", + G_CALLBACK (enable_all_menu_cb), pm); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + + item = gtk_menu_item_new_with_mnemonic (_("_Deactivate All")); + g_signal_connect (item, "activate", + G_CALLBACK (disable_all_menu_cb), pm); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + + gtk_widget_show_all (menu); + + return menu; +} + +static void +tree_popup_menu_detach (EomPluginManager *pm, + GtkMenu *menu) +{ + pm->priv->popup_menu = NULL; +} + +static void +menu_position_under_widget (GtkMenu *menu, + gint *x, + gint *y, + gboolean *push_in, + gpointer user_data) +{ + GtkWidget *w = GTK_WIDGET (user_data); + GtkRequisition requisition; + GtkAllocation allocation; + + gdk_window_get_origin (gtk_widget_get_window (w), x, y); + gtk_widget_size_request (GTK_WIDGET (menu), &requisition); + gtk_widget_get_allocation (w, &allocation); + + if (gtk_widget_get_direction (w) == GTK_TEXT_DIR_RTL) { + *x += allocation.x + allocation.width - requisition.width; + } else { + *x += allocation.x; + } + + *y += allocation.y + allocation.height; + + *push_in = TRUE; +} + +static void +menu_position_under_tree_view (GtkMenu *menu, + gint *x, + gint *y, + gboolean *push_in, + gpointer user_data) +{ + GtkTreeView *tree = GTK_TREE_VIEW (user_data); + GtkTreeModel *model; + GtkTreeSelection *selection; + GtkTreeIter iter; + + model = gtk_tree_view_get_model (tree); + + g_return_if_fail (model != NULL); + + selection = gtk_tree_view_get_selection (tree); + + g_return_if_fail (selection != NULL); + + if (gtk_tree_selection_get_selected (selection, NULL, &iter)) { + GtkTreePath *path; + GdkRectangle rect; + + gdk_window_get_origin (gtk_widget_get_window(GTK_WIDGET (tree)), + x, y); + + path = gtk_tree_model_get_path (model, &iter); + + gtk_tree_view_get_cell_area (tree, + path, + gtk_tree_view_get_column (tree, 0), /* FIXME 0 for RTL ? */ + &rect); + gtk_tree_path_free (path); + + *x += rect.x; + *y += rect.y + rect.height; + + if (gtk_widget_get_direction (GTK_WIDGET (tree)) == GTK_TEXT_DIR_RTL) { + GtkRequisition requisition; + + gtk_widget_size_request (GTK_WIDGET (menu), &requisition); + + *x += rect.width - requisition.width; + } + } else { + /* No selection -> regular "under widget" positioning */ + menu_position_under_widget (menu, + x, y, push_in, + tree); + } +} +static void +show_tree_popup_menu (GtkTreeView *tree, + EomPluginManager *pm, + GdkEventButton *event) +{ + if (pm->priv->popup_menu) + gtk_widget_destroy (pm->priv->popup_menu); + + pm->priv->popup_menu = create_tree_popup_menu (pm); + + if (pm->priv->popup_menu == NULL) + return; + + gtk_menu_attach_to_widget (GTK_MENU (pm->priv->popup_menu), + GTK_WIDGET (pm), + (GtkMenuDetachFunc) tree_popup_menu_detach); + + if (event != NULL) { + gtk_menu_popup (GTK_MENU (pm->priv->popup_menu), NULL, NULL, + NULL, NULL, + event->button, event->time); + } else { + gtk_menu_popup (GTK_MENU (pm->priv->popup_menu), NULL, NULL, + menu_position_under_tree_view, tree, + 0, gtk_get_current_event_time ()); + + gtk_menu_shell_select_first (GTK_MENU_SHELL (pm->priv->popup_menu), + FALSE); + } +} + +static gboolean +button_press_event_cb (GtkWidget *tree, + GdkEventButton *event, + EomPluginManager *pm) +{ + /* We want the treeview selection to be updated before showing the menu. + * This code is evil, thanks to Federico Mena Quintero's black magic. + * See: http://mail.gnome.org/archives/gtk-devel-list/2006-February/msg00168.html + * FIXME: Let's remove it asap. + */ + static gboolean in_press = FALSE; + gboolean handled; + + if (in_press) + return FALSE; /* we re-entered */ + + if (GDK_BUTTON_PRESS != event->type || 3 != event->button) + return FALSE; /* let the normal handler run */ + + in_press = TRUE; + handled = gtk_widget_event (tree, (GdkEvent *) event); + in_press = FALSE; + + if (!handled) + return FALSE; + + /* The selection is fully updated by now */ + show_tree_popup_menu (GTK_TREE_VIEW (tree), pm, event); + + return TRUE; +} + +static gboolean +popup_menu_cb (GtkTreeView *tree, + EomPluginManager *pm) +{ + show_tree_popup_menu (tree, pm, NULL); + + return TRUE; +} + +static gint +model_name_sort_func (GtkTreeModel *model, + GtkTreeIter *iter1, + GtkTreeIter *iter2, + gpointer user_data) +{ + EomPluginInfo *info1, *info2; + + gtk_tree_model_get (model, iter1, INFO_COLUMN, &info1, -1); + gtk_tree_model_get (model, iter2, INFO_COLUMN, &info2, -1); + + return g_utf8_collate (eom_plugin_engine_get_plugin_name (info1), + eom_plugin_engine_get_plugin_name (info2)); +} + +static void +plugin_manager_construct_tree (EomPluginManager *pm) +{ + GtkTreeViewColumn *column; + GtkCellRenderer *cell; + GtkListStore *model; + + eom_debug (DEBUG_PLUGINS); + + model = gtk_list_store_new (N_COLUMNS, + G_TYPE_BOOLEAN, + G_TYPE_BOOLEAN, + G_TYPE_POINTER); + + gtk_tree_view_set_model (GTK_TREE_VIEW (pm->priv->tree), + GTK_TREE_MODEL (model)); + + g_object_unref (model); + + gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (pm->priv->tree), TRUE); + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (pm->priv->tree), FALSE); + + /* First column */ + cell = gtk_cell_renderer_toggle_new (); + g_object_set (cell, "xpad", 6, NULL); + g_signal_connect (cell, + "toggled", + G_CALLBACK (active_toggled_cb), + pm); + column = gtk_tree_view_column_new_with_attributes (PLUGIN_MANAGER_ACTIVE_TITLE, + cell, + "active", ACTIVE_COLUMN, + "activatable", AVAILABLE_COLUMN, + "sensitive", AVAILABLE_COLUMN, + NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (pm->priv->tree), column); + + /* Second column */ + column = gtk_tree_view_column_new (); + gtk_tree_view_column_set_title (column, PLUGIN_MANAGER_NAME_TITLE); + gtk_tree_view_column_set_resizable (column, TRUE); + + cell = gtk_cell_renderer_pixbuf_new (); + gtk_tree_view_column_pack_start (column, cell, FALSE); + g_object_set (cell, "stock-size", GTK_ICON_SIZE_SMALL_TOOLBAR, NULL); + gtk_tree_view_column_set_cell_data_func (column, cell, + plugin_manager_view_icon_cell_cb, + pm, NULL); + + cell = gtk_cell_renderer_text_new (); + gtk_tree_view_column_pack_start (column, cell, TRUE); + g_object_set (cell, "ellipsize", PANGO_ELLIPSIZE_END, NULL); + gtk_tree_view_column_set_cell_data_func (column, cell, + plugin_manager_view_info_cell_cb, + pm, NULL); + + gtk_tree_view_column_set_spacing (column, 6); + gtk_tree_view_append_column (GTK_TREE_VIEW (pm->priv->tree), column); + + /* Sort on the plugin names */ + gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (model), + model_name_sort_func, + NULL, + NULL); + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (model), + GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID, + GTK_SORT_ASCENDING); + + /* Enable search for our non-string column */ + gtk_tree_view_set_search_column (GTK_TREE_VIEW (pm->priv->tree), + INFO_COLUMN); + gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (pm->priv->tree), + name_search_cb, + NULL, + NULL); + + g_signal_connect (pm->priv->tree, + "cursor_changed", + G_CALLBACK (cursor_changed_cb), + pm); + + g_signal_connect (pm->priv->tree, + "row_activated", + G_CALLBACK (row_activated_cb), + pm); + + g_signal_connect (pm->priv->tree, + "button-press-event", + G_CALLBACK (button_press_event_cb), + pm); + + g_signal_connect (pm->priv->tree, + "popup-menu", + G_CALLBACK (popup_menu_cb), + pm); + + gtk_widget_show (pm->priv->tree); +} + +static void +eom_plugin_manager_init (EomPluginManager *pm) +{ + GtkWidget *label; + GtkWidget *viewport; + GtkWidget *hbuttonbox; + + eom_debug (DEBUG_PLUGINS); + + pm->priv = EOM_PLUGIN_MANAGER_GET_PRIVATE (pm); + + gtk_box_set_spacing (GTK_BOX (pm), 6); + + label = gtk_label_new_with_mnemonic (_("Active _Plugins:")); + + gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_LEFT); + + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + + gtk_box_pack_start (GTK_BOX (pm), label, FALSE, TRUE, 0); + + viewport = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (viewport), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (viewport), + GTK_SHADOW_IN); + + gtk_box_pack_start (GTK_BOX (pm), viewport, TRUE, TRUE, 0); + + pm->priv->tree = gtk_tree_view_new (); + gtk_container_add (GTK_CONTAINER (viewport), pm->priv->tree); + + gtk_label_set_mnemonic_widget (GTK_LABEL (label), pm->priv->tree); + + hbuttonbox = gtk_hbutton_box_new (); + + gtk_box_pack_start (GTK_BOX (pm), hbuttonbox, FALSE, FALSE, 0); + + gtk_button_box_set_layout (GTK_BUTTON_BOX (hbuttonbox), GTK_BUTTONBOX_END); + gtk_box_set_spacing (GTK_BOX (hbuttonbox), 8); + + pm->priv->about_button = gtk_button_new_with_mnemonic (_("_About Plugin")); + gtk_button_set_image (GTK_BUTTON (pm->priv->about_button), + gtk_image_new_from_stock (GTK_STOCK_ABOUT, + GTK_ICON_SIZE_BUTTON)); + + gtk_container_add (GTK_CONTAINER (hbuttonbox), pm->priv->about_button); + + pm->priv->configure_button = gtk_button_new_with_mnemonic (_("C_onfigure Plugin")); + gtk_button_set_image (GTK_BUTTON (pm->priv->configure_button), + gtk_image_new_from_stock (GTK_STOCK_PREFERENCES, + GTK_ICON_SIZE_BUTTON)); + + gtk_container_add (GTK_CONTAINER (hbuttonbox), pm->priv->configure_button); + + gtk_widget_set_size_request (GTK_WIDGET (viewport), 270, 100); + + g_signal_connect (pm->priv->about_button, + "clicked", + G_CALLBACK (about_button_cb), + pm); + + g_signal_connect (pm->priv->configure_button, + "clicked", + G_CALLBACK (configure_button_cb), + pm); + + plugin_manager_construct_tree (pm); + + /* Get the list of available plugins (or installed) */ + pm->priv->plugins = eom_plugin_engine_get_plugins_list (); + + if (pm->priv->plugins != NULL) { + plugin_manager_populate_lists (pm); + } else { + gtk_widget_set_sensitive (pm->priv->about_button, FALSE); + gtk_widget_set_sensitive (pm->priv->configure_button, FALSE); + } +} + +static void +eom_plugin_manager_finalize (GObject *object) +{ + EomPluginManager *pm = EOM_PLUGIN_MANAGER (object); + + if (pm->priv->popup_menu) + gtk_widget_destroy (pm->priv->popup_menu); + + G_OBJECT_CLASS (eom_plugin_manager_parent_class)->finalize (object); +} + +GtkWidget * +eom_plugin_manager_new (void) +{ + return g_object_new (EOM_TYPE_PLUGIN_MANAGER, NULL); +} diff --git a/src/eom-plugin-manager.h b/src/eom-plugin-manager.h new file mode 100644 index 0000000..e35c817 --- /dev/null +++ b/src/eom-plugin-manager.h @@ -0,0 +1,61 @@ +/* Eye Of Mate - EOM Plugin Manager + * + * Copyright (C) 2007 The Free Software Foundation + * + * Author: Lucas Rocha <[email protected]> + * + * Based on gedit code (gedit/gedit-module.c) by: + * - Paolo Maggi <[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 __EOM_PLUGIN_MANAGER_H__ +#define __EOM_PLUGIN_MANAGER_H__ + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +typedef struct _EomPluginManager EomPluginManager; +typedef struct _EomPluginManagerClass EomPluginManagerClass; +typedef struct _EomPluginManagerPrivate EomPluginManagerPrivate; + +#define EOM_TYPE_PLUGIN_MANAGER (eom_plugin_manager_get_type()) +#define EOM_PLUGIN_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), EOM_TYPE_PLUGIN_MANAGER, EomPluginManager)) +#define EOM_PLUGIN_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), EOM_TYPE_PLUGIN_MANAGER, EomPluginManagerClass)) +#define EOM_IS_PLUGIN_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), EOM_TYPE_PLUGIN_MANAGER)) +#define EOM_IS_PLUGIN_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EOM_TYPE_PLUGIN_MANAGER)) +#define EOM_PLUGIN_MANAGER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), EOM_TYPE_PLUGIN_MANAGER, EomPluginManagerClass)) + +struct _EomPluginManager { + GtkVBox vbox; + + EomPluginManagerPrivate *priv; +}; + +struct _EomPluginManagerClass { + GtkVBoxClass parent_class; +}; + +G_GNUC_INTERNAL +GType eom_plugin_manager_get_type (void) G_GNUC_CONST; + +G_GNUC_INTERNAL +GtkWidget *eom_plugin_manager_new (void); + +G_END_DECLS + +#endif /* __EOM_PLUGIN_MANAGER_H__ */ diff --git a/src/eom-plugin.c b/src/eom-plugin.c new file mode 100644 index 0000000..7210128 --- /dev/null +++ b/src/eom-plugin.c @@ -0,0 +1,108 @@ +/* Eye Of Mate - EOM Plugin + * + * Copyright (C) 2007 The Free Software Foundation + * + * Author: Lucas Rocha <[email protected]> + * + * Based on gedit code (gedit/gedit-module.c) by: + * - Paolo Maggi <[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. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "eom-plugin.h" + +G_DEFINE_TYPE (EomPlugin, eom_plugin, G_TYPE_OBJECT) + +static void +dummy (EomPlugin *plugin, EomWindow *window) +{ +} + +static GtkWidget * +create_configure_dialog (EomPlugin *plugin) +{ + return NULL; +} + +static gboolean +is_configurable (EomPlugin *plugin) +{ + return (EOM_PLUGIN_GET_CLASS (plugin)->create_configure_dialog != + create_configure_dialog); +} + +static void +eom_plugin_class_init (EomPluginClass *klass) +{ + klass->activate = dummy; + klass->deactivate = dummy; + klass->update_ui = dummy; + + klass->create_configure_dialog = create_configure_dialog; + klass->is_configurable = is_configurable; +} + +static void +eom_plugin_init (EomPlugin *plugin) +{ +} + +void +eom_plugin_activate (EomPlugin *plugin, EomWindow *window) +{ + g_return_if_fail (EOM_IS_PLUGIN (plugin)); + g_return_if_fail (EOM_IS_WINDOW (window)); + + EOM_PLUGIN_GET_CLASS (plugin)->activate (plugin, window); +} + +void +eom_plugin_deactivate (EomPlugin *plugin, EomWindow *window) +{ + g_return_if_fail (EOM_IS_PLUGIN (plugin)); + g_return_if_fail (EOM_IS_WINDOW (window)); + + EOM_PLUGIN_GET_CLASS (plugin)->deactivate (plugin, window); +} + +void +eom_plugin_update_ui (EomPlugin *plugin, EomWindow *window) +{ + g_return_if_fail (EOM_IS_PLUGIN (plugin)); + g_return_if_fail (EOM_IS_WINDOW (window)); + + EOM_PLUGIN_GET_CLASS (plugin)->update_ui (plugin, window); +} + +gboolean +eom_plugin_is_configurable (EomPlugin *plugin) +{ + g_return_val_if_fail (EOM_IS_PLUGIN (plugin), FALSE); + + return EOM_PLUGIN_GET_CLASS (plugin)->is_configurable (plugin); +} + +GtkWidget * +eom_plugin_create_configure_dialog (EomPlugin *plugin) +{ + g_return_val_if_fail (EOM_IS_PLUGIN (plugin), NULL); + + return EOM_PLUGIN_GET_CLASS (plugin)->create_configure_dialog (plugin); +} diff --git a/src/eom-plugin.h b/src/eom-plugin.h new file mode 100644 index 0000000..83151e9 --- /dev/null +++ b/src/eom-plugin.h @@ -0,0 +1,222 @@ +/* Eye Of Mate - EOM Plugin + * + * Copyright (C) 2007 The Free Software Foundation + * + * Author: Lucas Rocha <[email protected]> + * + * Based on gedit code (gedit/gedit-module.c) by: + * - Paolo Maggi <[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 __EOM_PLUGIN_H__ +#define __EOM_PLUGIN_H__ + +#include "eom-window.h" +#include "eom-debug.h" + +#include <glib-object.h> + +G_BEGIN_DECLS + +typedef struct _EomPlugin EomPlugin; +typedef struct _EomPluginClass EomPluginClass; +typedef struct _EomPluginPrivate EomPluginPrivate; + +#define EOM_TYPE_PLUGIN (eom_plugin_get_type()) +#define EOM_PLUGIN(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), EOM_TYPE_PLUGIN, EomPlugin)) +#define EOM_PLUGIN_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), EOM_TYPE_PLUGIN, EomPlugin const)) +#define EOM_PLUGIN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), EOM_TYPE_PLUGIN, EomPluginClass)) +#define EOM_IS_PLUGIN(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), EOM_TYPE_PLUGIN)) +#define EOM_IS_PLUGIN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EOM_TYPE_PLUGIN)) +#define EOM_PLUGIN_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), EOM_TYPE_PLUGIN, EomPluginClass)) + +struct _EomPlugin { + GObject parent; +}; + +struct _EomPluginClass { + GObjectClass parent_class; + + void (*activate) (EomPlugin *plugin, + EomWindow *window); + + void (*deactivate) (EomPlugin *plugin, + EomWindow *window); + + void (*update_ui) (EomPlugin *plugin, + EomWindow *window); + + GtkWidget *(*create_configure_dialog) + (EomPlugin *plugin); + + /* Plugins should not override this, it's handled automatically + * by the EomPluginClass */ + gboolean (*is_configurable) + (EomPlugin *plugin); + + /* Padding for future expansion */ + void (*_eom_reserved1) (void); + void (*_eom_reserved2) (void); + void (*_eom_reserved3) (void); + void (*_eom_reserved4) (void); +}; + +GType eom_plugin_get_type (void) G_GNUC_CONST; + +void eom_plugin_activate (EomPlugin *plugin, + EomWindow *window); + +void eom_plugin_deactivate (EomPlugin *plugin, + EomWindow *window); + +void eom_plugin_update_ui (EomPlugin *plugin, + EomWindow *window); + +gboolean eom_plugin_is_configurable (EomPlugin *plugin); + +GtkWidget *eom_plugin_create_configure_dialog + (EomPlugin *plugin); + +/* + * Utility macro used to register plugins + * + * use: EOM_PLUGIN_REGISTER_TYPE_WITH_CODE(PluginName, plugin_name, CODE) + */ +#define EOM_PLUGIN_REGISTER_TYPE_WITH_CODE(PluginName, plugin_name, CODE) \ + \ +static GType plugin_name##_type = 0; \ + \ +GType \ +plugin_name##_get_type (void) \ +{ \ + return plugin_name##_type; \ +} \ + \ +static void plugin_name##_init (PluginName *self); \ +static void plugin_name##_class_init (PluginName##Class *klass); \ +static gpointer plugin_name##_parent_class = NULL; \ +static void plugin_name##_class_intern_init (gpointer klass) \ +{ \ + plugin_name##_parent_class = g_type_class_peek_parent (klass); \ + plugin_name##_class_init ((PluginName##Class *) klass); \ +} \ + \ +G_MODULE_EXPORT GType \ +register_eom_plugin (GTypeModule *module) \ +{ \ + static const GTypeInfo our_info = \ + { \ + sizeof (PluginName##Class), \ + NULL, /* base_init */ \ + NULL, /* base_finalize */ \ + (GClassInitFunc) plugin_name##_class_intern_init, \ + NULL, \ + NULL, /* class_data */ \ + sizeof (PluginName), \ + 0, /* n_preallocs */ \ + (GInstanceInitFunc) plugin_name##_init \ + }; \ + \ + eom_debug_message (DEBUG_PLUGINS, "Registering " #PluginName); \ + \ + /* Initialise the i18n stuff */ \ + bindtextdomain (GETTEXT_PACKAGE, EOM_LOCALEDIR); \ + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); \ + \ + plugin_name##_type = g_type_module_register_type (module, \ + EOM_TYPE_PLUGIN, \ + #PluginName, \ + &our_info, \ + 0); \ + \ + CODE \ + \ + return plugin_name##_type; \ +} + +/* + * Utility macro used to register plugins + * + * use: EOM_PLUGIN_REGISTER_TYPE(PluginName, plugin_name) + */ +#define EOM_PLUGIN_REGISTER_TYPE(PluginName, plugin_name) \ + EOM_PLUGIN_REGISTER_TYPE_WITH_CODE(PluginName, plugin_name, ;) + +/* + * Utility macro used to register gobject types in plugins with additional code + * + * use: EOM_PLUGIN_DEFINE_TYPE_WITH_CODE(ObjectName, object_name, PARENT_TYPE, CODE) + */ +#define EOM_PLUGIN_DEFINE_TYPE_WITH_CODE(ObjectName, object_name, PARENT_TYPE, CODE) \ + \ +static GType g_define_type_id = 0; \ + \ +GType \ +object_name##_get_type (void) \ +{ \ + return g_define_type_id; \ +} \ + \ +static void object_name##_init (ObjectName *self); \ +static void object_name##_class_init (ObjectName##Class *klass); \ +static gpointer object_name##_parent_class = NULL; \ +static void object_name##_class_intern_init (gpointer klass) \ +{ \ + object_name##_parent_class = g_type_class_peek_parent (klass); \ + object_name##_class_init ((ObjectName##Class *) klass); \ +} \ + \ +GType \ +object_name##_register_type (GTypeModule *module) \ +{ \ + static const GTypeInfo our_info = \ + { \ + sizeof (ObjectName##Class), \ + NULL, /* base_init */ \ + NULL, /* base_finalize */ \ + (GClassInitFunc) object_name##_class_intern_init, \ + NULL, \ + NULL, /* class_data */ \ + sizeof (ObjectName), \ + 0, /* n_preallocs */ \ + (GInstanceInitFunc) object_name##_init \ + }; \ + \ + eom_debug_message (DEBUG_PLUGINS, "Registering " #ObjectName); \ + \ + g_define_type_id = g_type_module_register_type (module, \ + PARENT_TYPE, \ + #ObjectName, \ + &our_info, \ + 0); \ + \ + CODE \ + \ + return g_define_type_id; \ +} + +/* + * Utility macro used to register gobject types in plugins + * + * use: EOM_PLUGIN_DEFINE_TYPE(ObjectName, object_name, PARENT_TYPE) + */ +#define EOM_PLUGIN_DEFINE_TYPE(ObjectName, object_name, PARENT_TYPE) \ + EOM_PLUGIN_DEFINE_TYPE_WITH_CODE(ObjectName, object_name, PARENT_TYPE, ;) + +G_END_DECLS + +#endif /* __EOM_PLUGIN_H__ */ diff --git a/src/eom-preferences-dialog.c b/src/eom-preferences-dialog.c new file mode 100644 index 0000000..52a9772 --- /dev/null +++ b/src/eom-preferences-dialog.c @@ -0,0 +1,489 @@ +/* Eye Of Mate - EOM Preferences Dialog + * + * Copyright (C) 2006 The Free Software Foundation + * + * Author: Lucas Rocha <[email protected]> + * + * Based on code by: + * - Jens Finke <[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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "eom-preferences-dialog.h" +#include "eom-plugin-manager.h" +#include "eom-util.h" +#include "eom-config-keys.h" + +#include <glib.h> +#include <glib/gi18n.h> +#include <glib-object.h> +#include <gtk/gtk.h> +#include <mateconf/mateconf-client.h> + +#define EOM_PREFERENCES_DIALOG_GET_PRIVATE(object) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((object), EOM_TYPE_PREFERENCES_DIALOG, EomPreferencesDialogPrivate)) + +G_DEFINE_TYPE (EomPreferencesDialog, eom_preferences_dialog, EOM_TYPE_DIALOG); + +enum { + PROP_0, + PROP_MATECONF_CLIENT, +}; + +#define MATECONF_OBJECT_KEY "MATECONF_KEY" +#define MATECONF_OBJECT_VALUE "MATECONF_VALUE" +#define TOGGLE_INVERT_VALUE "TOGGLE_INVERT_VALUE" + +struct _EomPreferencesDialogPrivate { + MateConfClient *client; +}; + +static GObject *instance = NULL; + +static void +pd_check_toggle_cb (GtkWidget *widget, gpointer data) +{ + char *key = NULL; + gboolean invert = FALSE; + gboolean value; + + key = g_object_get_data (G_OBJECT (widget), MATECONF_OBJECT_KEY); + invert = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget), TOGGLE_INVERT_VALUE)); + + value = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)); + + if (key == NULL) return; + + mateconf_client_set_bool (MATECONF_CLIENT (data), + key, + (invert) ? !value : value, + NULL); +} + +static void +pd_spin_button_changed_cb (GtkWidget *widget, gpointer data) +{ + char *key = NULL; + + key = g_object_get_data (G_OBJECT (widget), MATECONF_OBJECT_KEY); + + if (key == NULL) return; + + mateconf_client_set_int (MATECONF_CLIENT (data), + key, + gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (widget)), + NULL); +} + +static void +pd_color_change_cb (GtkColorButton *button, gpointer data) +{ + GdkColor color; + char *key = NULL; + char *value = NULL; + + gtk_color_button_get_color (button, &color); + + value = g_strdup_printf ("#%02X%02X%02X", + color.red / 256, + color.green / 256, + color.blue / 256); + + key = g_object_get_data (G_OBJECT (button), MATECONF_OBJECT_KEY); + + if (key == NULL || value == NULL) + return; + + mateconf_client_set_string (MATECONF_CLIENT (data), + key, + value, + NULL); + g_free (value); +} + +static void +pd_radio_toggle_cb (GtkWidget *widget, gpointer data) +{ + char *key = NULL; + char *value = NULL; + + if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) + return; + + key = g_object_get_data (G_OBJECT (widget), MATECONF_OBJECT_KEY); + value = g_object_get_data (G_OBJECT (widget), MATECONF_OBJECT_VALUE); + + if (key == NULL || value == NULL) + return; + + mateconf_client_set_string (MATECONF_CLIENT (data), + key, + value, + NULL); +} + +static void +eom_preferences_response_cb (GtkDialog *dlg, gint res_id, gpointer data) +{ + switch (res_id) { + case GTK_RESPONSE_HELP: + eom_util_show_help ("eom-prefs", NULL); + break; + default: + gtk_widget_destroy (GTK_WIDGET (dlg)); + instance = NULL; + } +} + +static void +eom_preferences_dialog_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + EomPreferencesDialog *pref_dlg = EOM_PREFERENCES_DIALOG (object); + + switch (prop_id) { + case PROP_MATECONF_CLIENT: + pref_dlg->priv->client = g_value_get_object (value); + break; + } +} + +static void +eom_preferences_dialog_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + EomPreferencesDialog *pref_dlg = EOM_PREFERENCES_DIALOG (object); + + switch (prop_id) { + case PROP_MATECONF_CLIENT: + g_value_set_object (value, pref_dlg->priv->client); + break; + } +} + +static GObject * +eom_preferences_dialog_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_params) + +{ + EomPreferencesDialogPrivate *priv; + GtkWidget *dlg; + GtkWidget *interpolate_check; + GtkWidget *extrapolate_check; + GtkWidget *autorotate_check; + GtkWidget *bg_color_check; + GtkWidget *bg_color_button; + GtkWidget *color_radio; + GtkWidget *checkpattern_radio; + GtkWidget *background_radio; + GtkWidget *color_button; + GtkWidget *upscale_check; + GtkWidget *loop_check; + GtkWidget *seconds_spin; + GtkWidget *plugin_manager; + GtkWidget *plugin_manager_container; + GObject *object; + GdkColor color; + gchar *value; + + object = G_OBJECT_CLASS (eom_preferences_dialog_parent_class)->constructor + (type, n_construct_properties, construct_params); + + priv = EOM_PREFERENCES_DIALOG (object)->priv; + + eom_dialog_construct (EOM_DIALOG (object), + "eom-preferences-dialog.ui", + "eom_preferences_dialog"); + + eom_dialog_get_controls (EOM_DIALOG (object), + "eom_preferences_dialog", &dlg, + "interpolate_check", &interpolate_check, + "extrapolate_check", &extrapolate_check, + "autorotate_check", &autorotate_check, + "bg_color_check", &bg_color_check, + "bg_color_button", &bg_color_button, + "color_radio", &color_radio, + "checkpattern_radio", &checkpattern_radio, + "background_radio", &background_radio, + "color_button", &color_button, + "upscale_check", &upscale_check, + "loop_check", &loop_check, + "seconds_spin", &seconds_spin, + "plugin_manager_container", &plugin_manager_container, + NULL); + + g_signal_connect (G_OBJECT (dlg), + "response", + G_CALLBACK (eom_preferences_response_cb), + dlg); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (interpolate_check), + mateconf_client_get_bool (priv->client, + EOM_CONF_VIEW_INTERPOLATE, + NULL)); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (extrapolate_check), + mateconf_client_get_bool (priv->client, + EOM_CONF_VIEW_EXTRAPOLATE, + NULL)); + + g_object_set_data (G_OBJECT (interpolate_check), + MATECONF_OBJECT_KEY, + EOM_CONF_VIEW_INTERPOLATE); + + g_object_set_data (G_OBJECT (extrapolate_check), + MATECONF_OBJECT_KEY, + EOM_CONF_VIEW_EXTRAPOLATE); + + g_signal_connect (G_OBJECT (interpolate_check), + "toggled", + G_CALLBACK (pd_check_toggle_cb), + priv->client); + + g_signal_connect (G_OBJECT (extrapolate_check), + "toggled", + G_CALLBACK (pd_check_toggle_cb), + priv->client); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (autorotate_check), + mateconf_client_get_bool (priv->client, + EOM_CONF_VIEW_AUTOROTATE, + NULL)); + + g_object_set_data (G_OBJECT (autorotate_check), + MATECONF_OBJECT_KEY, + EOM_CONF_VIEW_AUTOROTATE); + + g_signal_connect (G_OBJECT (autorotate_check), + "toggled", + G_CALLBACK (pd_check_toggle_cb), + priv->client); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (bg_color_check), + mateconf_client_get_bool (priv->client, + EOM_CONF_VIEW_USE_BG_COLOR, NULL)); + g_object_set_data (G_OBJECT (bg_color_check), + MATECONF_OBJECT_KEY, + EOM_CONF_VIEW_USE_BG_COLOR); + g_signal_connect (G_OBJECT (bg_color_check), + "toggled", G_CALLBACK (pd_check_toggle_cb), + priv->client); + + value = mateconf_client_get_string (priv->client, + EOM_CONF_VIEW_BACKGROUND_COLOR, + NULL); + if (gdk_color_parse (value, &color)){ + gtk_color_button_set_color (GTK_COLOR_BUTTON (bg_color_button), + &color); + } + g_free (value); + + g_object_set_data (G_OBJECT (bg_color_button), + MATECONF_OBJECT_KEY, + EOM_CONF_VIEW_BACKGROUND_COLOR); + + g_signal_connect (G_OBJECT (bg_color_button), + "color-set", + G_CALLBACK (pd_color_change_cb), + priv->client); + + + + g_object_set_data (G_OBJECT (color_radio), + MATECONF_OBJECT_KEY, + EOM_CONF_VIEW_TRANSPARENCY); + + g_object_set_data (G_OBJECT (color_radio), + MATECONF_OBJECT_VALUE, + "COLOR"); + + g_signal_connect (G_OBJECT (color_radio), + "toggled", + G_CALLBACK (pd_radio_toggle_cb), + priv->client); + + g_object_set_data (G_OBJECT (checkpattern_radio), + MATECONF_OBJECT_KEY, + EOM_CONF_VIEW_TRANSPARENCY); + + g_object_set_data (G_OBJECT (checkpattern_radio), + MATECONF_OBJECT_VALUE, + "CHECK_PATTERN"); + + g_signal_connect (G_OBJECT (checkpattern_radio), + "toggled", + G_CALLBACK (pd_radio_toggle_cb), + priv->client); + + g_object_set_data (G_OBJECT (background_radio), + MATECONF_OBJECT_KEY, + EOM_CONF_VIEW_TRANSPARENCY); + + g_object_set_data (G_OBJECT (background_radio), + MATECONF_OBJECT_VALUE, + "NONE"); + + g_signal_connect (G_OBJECT (background_radio), + "toggled", + G_CALLBACK (pd_radio_toggle_cb), + priv->client); + + value = mateconf_client_get_string (priv->client, + EOM_CONF_VIEW_TRANSPARENCY, + NULL); + + if (g_ascii_strcasecmp (value, "COLOR") == 0) { + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (color_radio), TRUE); + } + else if (g_ascii_strcasecmp (value, "CHECK_PATTERN") == 0) { + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (checkpattern_radio), TRUE); + } + else { + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (background_radio), TRUE); + } + + g_free (value); + + value = mateconf_client_get_string (priv->client, + EOM_CONF_VIEW_TRANS_COLOR, + NULL); + + if (gdk_color_parse (value, &color)) { + gtk_color_button_set_color (GTK_COLOR_BUTTON (color_button), + &color); + } + + g_object_set_data (G_OBJECT (color_button), + MATECONF_OBJECT_KEY, + EOM_CONF_VIEW_TRANS_COLOR); + + g_signal_connect (G_OBJECT (color_button), + "color-set", + G_CALLBACK (pd_color_change_cb), + priv->client); + + g_free (value); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (upscale_check), + mateconf_client_get_bool (priv->client, + EOM_CONF_FULLSCREEN_UPSCALE, + NULL)); + + g_object_set_data (G_OBJECT (upscale_check), + MATECONF_OBJECT_KEY, + EOM_CONF_FULLSCREEN_UPSCALE); + + g_signal_connect (G_OBJECT (upscale_check), + "toggled", + G_CALLBACK (pd_check_toggle_cb), + priv->client); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (loop_check), + mateconf_client_get_bool (priv->client, + EOM_CONF_FULLSCREEN_LOOP, + NULL)); + + g_object_set_data (G_OBJECT (loop_check), + MATECONF_OBJECT_KEY, + EOM_CONF_FULLSCREEN_LOOP); + + g_signal_connect (G_OBJECT (loop_check), + "toggled", + G_CALLBACK (pd_check_toggle_cb), + priv->client); + + gtk_spin_button_set_value (GTK_SPIN_BUTTON (seconds_spin), + mateconf_client_get_int (priv->client, + EOM_CONF_FULLSCREEN_SECONDS, + NULL)); + + g_object_set_data (G_OBJECT (seconds_spin), + MATECONF_OBJECT_KEY, + EOM_CONF_FULLSCREEN_SECONDS); + + g_signal_connect (G_OBJECT (seconds_spin), + "value-changed", + G_CALLBACK (pd_spin_button_changed_cb), + priv->client); + + plugin_manager = eom_plugin_manager_new (); + + g_assert (plugin_manager != NULL); + + gtk_box_pack_start (GTK_BOX (plugin_manager_container), + plugin_manager, + TRUE, + TRUE, + 0); + + gtk_widget_show_all (plugin_manager); + + return object; +} + +static void +eom_preferences_dialog_class_init (EomPreferencesDialogClass *class) +{ + GObjectClass *g_object_class = (GObjectClass *) class; + + g_object_class->constructor = eom_preferences_dialog_constructor; + g_object_class->set_property = eom_preferences_dialog_set_property; + g_object_class->get_property = eom_preferences_dialog_get_property; + + g_object_class_install_property (g_object_class, + PROP_MATECONF_CLIENT, + g_param_spec_object ("mateconf-client", + "MateConf Client", + "MateConf Client", + MATECONF_TYPE_CLIENT, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | + G_PARAM_STATIC_BLURB)); + + g_type_class_add_private (g_object_class, sizeof (EomPreferencesDialogPrivate)); +} + +static void +eom_preferences_dialog_init (EomPreferencesDialog *pref_dlg) +{ + pref_dlg->priv = EOM_PREFERENCES_DIALOG_GET_PRIVATE (pref_dlg); + + pref_dlg->priv->client = NULL; +} + +GObject * +eom_preferences_dialog_get_instance (GtkWindow *parent, MateConfClient *client) +{ + if (instance == NULL) { + instance = g_object_new (EOM_TYPE_PREFERENCES_DIALOG, + "parent-window", parent, + "mateconf-client", client, + NULL); + } + + return instance; +} diff --git a/src/eom-preferences-dialog.h b/src/eom-preferences-dialog.h new file mode 100644 index 0000000..7a05f67 --- /dev/null +++ b/src/eom-preferences-dialog.h @@ -0,0 +1,66 @@ +/* Eye Of Mate - EOM Preferences Dialog + * + * Copyright (C) 2006 The Free Software Foundation + * + * Author: Lucas Rocha <[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 __EOM_PREFERENCES_DIALOG_H__ +#define __EOM_PREFERENCES_DIALOG_H__ + +#include "eom-dialog.h" +#include "eom-image.h" +#include "eom-thumb-view.h" + +#include <glib.h> +#include <glib-object.h> +#include <gtk/gtk.h> +#include <mateconf/mateconf-client.h> + +G_BEGIN_DECLS + +typedef struct _EomPreferencesDialog EomPreferencesDialog; +typedef struct _EomPreferencesDialogClass EomPreferencesDialogClass; +typedef struct _EomPreferencesDialogPrivate EomPreferencesDialogPrivate; + +#define EOM_TYPE_PREFERENCES_DIALOG (eom_preferences_dialog_get_type ()) +#define EOM_PREFERENCES_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), EOM_TYPE_PREFERENCES_DIALOG, EomPreferencesDialog)) +#define EOM_PREFERENCES_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), EOM_TYPE_PREFERENCES_DIALOG, EomPreferencesDialogClass)) +#define EOM_IS_PREFERENCES_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), EOM_TYPE_PREFERENCES_DIALOG)) +#define EOM_IS_PREFERENCES_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), EOM_TYPE_PREFERENCES_DIALOG)) +#define EOM_PREFERENCES_DIALOG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), EOM_TYPE_PREFERENCES_DIALOG, EomPreferencesDialogClass)) + +struct _EomPreferencesDialog { + EomDialog dialog; + + EomPreferencesDialogPrivate *priv; +}; + +struct _EomPreferencesDialogClass { + EomDialogClass parent_class; +}; + +G_GNUC_INTERNAL +GType eom_preferences_dialog_get_type (void) G_GNUC_CONST; + +G_GNUC_INTERNAL +GObject *eom_preferences_dialog_get_instance (GtkWindow *parent, + MateConfClient *client); + +G_END_DECLS + +#endif /* __EOM_PREFERENCES_DIALOG_H__ */ diff --git a/src/eom-print-image-setup.c b/src/eom-print-image-setup.c new file mode 100644 index 0000000..100d7c9 --- /dev/null +++ b/src/eom-print-image-setup.c @@ -0,0 +1,1074 @@ +/* Eye Of MATE -- Print Dialog Custom Widget + * + * Copyright (C) 2006-2007 The Free Software Foundation + * + * Author: Claudio Saavedra <[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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <gtk/gtk.h> +#include <gtk/gtkunixprint.h> + +#include <glib/gi18n.h> +#include <glib/gprintf.h> + +#ifdef HAVE__NL_MEASUREMENT_MEASUREMENT +#include <langinfo.h> +#endif + +#include "eom-print-image-setup.h" +#include "eom-print-preview.h" + +/** + * SECTION: + * @Title: Printing Custom Widget + * @Short_Description: a custom widget to setup image prints. + * @Stability_Level: Internal + * @See_Also: #EomPrintPreview + * + * This widget is to be used as the custom widget in a #GtkPrintUnixDialog in + * EOM. Through it, you can set the position and scaling of a image + * interactively. + */ + +#define EOM_PRINT_IMAGE_SETUP_GET_PRIVATE(object) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((object), EOM_TYPE_PRINT_IMAGE_SETUP, EomPrintImageSetupPrivate)) + +G_DEFINE_TYPE (EomPrintImageSetup, eom_print_image_setup, GTK_TYPE_TABLE); + +struct EomPrintImageSetupPrivate { + GtkWidget *left; + GtkWidget *right; + GtkWidget *top; + GtkWidget *bottom; + + GtkWidget *center; + + GtkWidget *width; + GtkWidget *height; + + GtkWidget *scaling; + GtkWidget *unit; + + GtkUnit current_unit; + + EomImage *image; + GtkPageSetup *page_setup; + + GtkWidget *preview; +}; + +enum { + PROP_0, + PROP_IMAGE, + PROP_PAGE_SETUP +}; + +enum { + CENTER_NONE, + CENTER_HORIZONTAL, + CENTER_VERTICAL, + CENTER_BOTH +}; + +enum { + CHANGE_HORIZ, + CHANGE_VERT +}; + +enum { + UNIT_INCH, + UNIT_MM +}; + +#define FACTOR_INCH_TO_MM 25.4 +#define FACTOR_INCH_TO_PIXEL 72. +#define FACTOR_MM_TO_INCH 0.03937007874015748 +#define FACTOR_MM_TO_PIXEL 2.834645669 + +static void eom_print_image_setup_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); +static void eom_print_image_setup_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); + +static void on_left_value_changed (GtkSpinButton *spinbutton, gpointer user_data); +static void on_right_value_changed (GtkSpinButton *spinbutton, gpointer user_data); +static void on_top_value_changed (GtkSpinButton *spinbutton, gpointer user_data); +static void on_bottom_value_changed (GtkSpinButton *spinbutton, gpointer user_data); + +static void on_width_value_changed (GtkSpinButton *spinbutton, gpointer user_data); +static void on_height_value_changed (GtkSpinButton *spinbutton, gpointer user_data); + + +static void +block_handlers (EomPrintImageSetup *setup) +{ + EomPrintImageSetupPrivate *priv = setup->priv; + + g_signal_handlers_block_by_func (priv->left, on_left_value_changed, setup); + g_signal_handlers_block_by_func (priv->right, on_right_value_changed, setup); + g_signal_handlers_block_by_func (priv->width, on_width_value_changed, setup); + g_signal_handlers_block_by_func (priv->top, on_top_value_changed, setup); + g_signal_handlers_block_by_func (priv->bottom, on_bottom_value_changed, setup); + g_signal_handlers_block_by_func (priv->height, on_height_value_changed, setup); +} + +static void +unblock_handlers (EomPrintImageSetup *setup) +{ + EomPrintImageSetupPrivate *priv = setup->priv; + + g_signal_handlers_unblock_by_func (priv->left, on_left_value_changed, setup); + g_signal_handlers_unblock_by_func (priv->right, on_right_value_changed, setup); + g_signal_handlers_unblock_by_func (priv->width, on_width_value_changed, setup); + g_signal_handlers_unblock_by_func (priv->top, on_top_value_changed, setup); + g_signal_handlers_unblock_by_func (priv->bottom, on_bottom_value_changed, setup); + g_signal_handlers_unblock_by_func (priv->height, on_height_value_changed, setup); +} + +static gdouble +get_scale_to_px_factor (EomPrintImageSetup *setup) +{ + gdouble factor = 0.; + + switch (setup->priv->current_unit) { + case GTK_UNIT_MM: + factor = FACTOR_MM_TO_PIXEL; + break; + case GTK_UNIT_INCH: + factor = FACTOR_INCH_TO_PIXEL; + break; + default: + g_assert_not_reached (); + } + + return factor; +} + +static gdouble +get_max_percentage (EomPrintImageSetup *setup) +{ + EomPrintImageSetupPrivate *priv = setup->priv; + gdouble p_width, p_height; + gdouble width, height; + gint pix_width, pix_height; + gdouble perc; + + p_width = gtk_page_setup_get_page_width (priv->page_setup, GTK_UNIT_INCH); + p_height = gtk_page_setup_get_page_height (priv->page_setup, GTK_UNIT_INCH); + + eom_image_get_size (priv->image, &pix_width, &pix_height); + + width = (gdouble)pix_width/FACTOR_INCH_TO_PIXEL; + height = (gdouble)pix_height/FACTOR_INCH_TO_PIXEL; + + if (p_width > width && p_height > height) { + perc = 1.; + } else { + perc = MIN (p_width/width, p_height/height); + } + + return perc; +} + +static void +center (gdouble page_width, + gdouble width, + GtkSpinButton *s_left, + GtkSpinButton *s_right) +{ + gdouble left, right; + + left = (page_width - width)/2; + right = page_width - left - width; + gtk_spin_button_set_value (s_left, left); + gtk_spin_button_set_value (s_right, right); +} + +static void +on_center_changed (GtkComboBox *combobox, + gpointer user_data) +{ + EomPrintImageSetup *setup; + EomPrintImageSetupPrivate *priv; + gint active; + + setup = EOM_PRINT_IMAGE_SETUP (user_data); + priv = setup->priv; + + active = gtk_combo_box_get_active (combobox); + + switch (active) { + case CENTER_HORIZONTAL: + center (gtk_page_setup_get_page_width (priv->page_setup, + priv->current_unit), + gtk_spin_button_get_value (GTK_SPIN_BUTTON (priv->width)), + GTK_SPIN_BUTTON (priv->left), + GTK_SPIN_BUTTON (priv->right)); + break; + case CENTER_VERTICAL: + center (gtk_page_setup_get_page_height (priv->page_setup, + priv->current_unit), + gtk_spin_button_get_value (GTK_SPIN_BUTTON (priv->height)), + GTK_SPIN_BUTTON (priv->top), + GTK_SPIN_BUTTON (priv->bottom)); + break; + case CENTER_BOTH: + center (gtk_page_setup_get_page_width (priv->page_setup, + priv->current_unit), + gtk_spin_button_get_value (GTK_SPIN_BUTTON (priv->width)), + GTK_SPIN_BUTTON (priv->left), + GTK_SPIN_BUTTON (priv->right)); + center (gtk_page_setup_get_page_height (priv->page_setup, + priv->current_unit), + gtk_spin_button_get_value (GTK_SPIN_BUTTON (priv->height)), + GTK_SPIN_BUTTON (priv->top), + GTK_SPIN_BUTTON (priv->bottom)); + break; + case CENTER_NONE: + default: + break; + } + + gtk_combo_box_set_active (combobox, active); +} + +static void +update_image_pos_ranges (EomPrintImageSetup *setup, + gdouble page_width, + gdouble page_height, + gdouble width, + gdouble height) +{ + EomPrintImageSetupPrivate *priv; + + priv = setup->priv; + + gtk_spin_button_set_range (GTK_SPIN_BUTTON (priv->left), + 0, page_width - width); + gtk_spin_button_set_range (GTK_SPIN_BUTTON (priv->right), + 0, page_width - width); + gtk_spin_button_set_range (GTK_SPIN_BUTTON (priv->top), + 0, page_height - height); + gtk_spin_button_set_range (GTK_SPIN_BUTTON (priv->bottom), + 0, page_height - height); +} + +static gboolean +on_scale_changed (GtkRange *range, + gpointer user_data) +{ + gdouble scale; + gdouble width, height; + gint pix_width, pix_height; + gdouble left, right, top, bottom; + gdouble page_width, page_height; + EomPrintImageSetupPrivate *priv; + EomPrintImageSetup *setup; + gdouble factor; + EomImage *image; + + setup = EOM_PRINT_IMAGE_SETUP (user_data); + priv = setup->priv; + + gtk_combo_box_set_active (GTK_COMBO_BOX (priv->center), CENTER_NONE); + + image = priv->image; + eom_image_get_size (image, &pix_width, &pix_height); + + factor = get_scale_to_px_factor (setup); + + width = (gdouble)pix_width/factor; + height = (gdouble)pix_height/factor; + + left = gtk_spin_button_get_value (GTK_SPIN_BUTTON (priv->left)); + top = gtk_spin_button_get_value (GTK_SPIN_BUTTON (priv->top)); + + scale = CLAMP (0.01*gtk_range_get_value (range), 0, get_max_percentage (setup)); + + eom_print_preview_set_scale (EOM_PRINT_PREVIEW (priv->preview), scale); + + width *= scale; + height *= scale; + + page_width = gtk_page_setup_get_page_width (priv->page_setup, priv->current_unit); + page_height = gtk_page_setup_get_page_height (priv->page_setup, priv->current_unit); + + update_image_pos_ranges (setup, page_width, page_height, width, height); + + right = page_width - left - width; + bottom = page_height - top - height; + + gtk_spin_button_set_value (GTK_SPIN_BUTTON (priv->width), width); + gtk_spin_button_set_value (GTK_SPIN_BUTTON (priv->height), height); + gtk_spin_button_set_value (GTK_SPIN_BUTTON (priv->right), right); + gtk_spin_button_set_value (GTK_SPIN_BUTTON (priv->bottom), bottom); + + return FALSE; +} + +static gchar * +on_scale_format_value (GtkScale *scale, + gdouble value) +{ + return g_strdup_printf ("%i%%", (gint)value); +} + +static void +position_values_changed (EomPrintImageSetup *setup, + GtkWidget *w_changed, + GtkWidget *w_to_update, + GtkWidget *w_size, + gdouble total_size, + gint change) +{ + EomPrintImageSetupPrivate *priv; + gdouble changed, to_update, size; + gdouble pos; + + priv = setup->priv; + size = gtk_spin_button_get_value (GTK_SPIN_BUTTON (w_size)); + changed = gtk_spin_button_get_value (GTK_SPIN_BUTTON (w_changed)); + + to_update = total_size - changed - size; + gtk_spin_button_set_value (GTK_SPIN_BUTTON (w_to_update), to_update); + gtk_combo_box_set_active (GTK_COMBO_BOX (priv->center), CENTER_NONE); + + switch (change) { + case CHANGE_HORIZ: + pos = gtk_spin_button_get_value (GTK_SPIN_BUTTON (setup->priv->left)); + if (setup->priv->current_unit == GTK_UNIT_MM) { + pos *= FACTOR_MM_TO_INCH; + } + eom_print_preview_set_image_position (EOM_PRINT_PREVIEW (priv->preview), pos, -1); + break; + case CHANGE_VERT: + pos = gtk_spin_button_get_value (GTK_SPIN_BUTTON (setup->priv->top)); + if (setup->priv->current_unit == GTK_UNIT_MM) { + pos *= FACTOR_MM_TO_INCH; + } + eom_print_preview_set_image_position (EOM_PRINT_PREVIEW (priv->preview), -1, pos); + break; + } +} + +static void +on_left_value_changed (GtkSpinButton *spinbutton, + gpointer user_data) +{ + EomPrintImageSetup *setup; + EomPrintImageSetupPrivate *priv; + + setup = EOM_PRINT_IMAGE_SETUP (user_data); + priv = setup->priv; + + position_values_changed (setup, + priv->left, priv->right, priv->width, + gtk_page_setup_get_page_width (priv->page_setup, + priv->current_unit), + CHANGE_HORIZ); +} + +static void +on_right_value_changed (GtkSpinButton *spinbutton, + gpointer user_data) +{ + EomPrintImageSetupPrivate *priv; + + priv = EOM_PRINT_IMAGE_SETUP (user_data)->priv; + + position_values_changed (EOM_PRINT_IMAGE_SETUP (user_data), + priv->right, priv->left, priv->width, + gtk_page_setup_get_page_width (priv->page_setup, + priv->current_unit), + CHANGE_HORIZ); +} + +static void +on_top_value_changed (GtkSpinButton *spinbutton, + gpointer user_data) +{ + EomPrintImageSetupPrivate *priv; + + priv = EOM_PRINT_IMAGE_SETUP (user_data)->priv; + + position_values_changed (EOM_PRINT_IMAGE_SETUP (user_data), + priv->top, priv->bottom, priv->height, + gtk_page_setup_get_page_height (priv->page_setup, + priv->current_unit), + CHANGE_VERT); +} + +static void +on_bottom_value_changed (GtkSpinButton *spinbutton, + gpointer user_data) +{ + EomPrintImageSetupPrivate *priv; + + priv = EOM_PRINT_IMAGE_SETUP (user_data)->priv; + + position_values_changed (EOM_PRINT_IMAGE_SETUP (user_data), + priv->bottom, priv->top, priv->height, + gtk_page_setup_get_page_height (priv->page_setup, + priv->current_unit), + CHANGE_VERT); +} + +static void +size_changed (EomPrintImageSetup *setup, + GtkWidget *w_size_x, + GtkWidget *w_size_y, + GtkWidget *w_margin_x_1, + GtkWidget *w_margin_x_2, + GtkWidget *w_margin_y_1, + GtkWidget *w_margin_y_2, + gdouble page_size_x, + gdouble page_size_y, + gint change) +{ + EomPrintImageSetupPrivate *priv; + gdouble margin_x_1, margin_x_2; + gdouble margin_y_1, margin_y_2; + gdouble orig_size_x = -1, orig_size_y = -1, scale; + gdouble size_x, size_y; + gint pix_width, pix_height; + gdouble factor; + + priv = setup->priv; + + size_x = gtk_spin_button_get_value (GTK_SPIN_BUTTON (w_size_x)); + margin_x_1 = gtk_spin_button_get_value (GTK_SPIN_BUTTON (w_margin_x_1)); + margin_y_1 = gtk_spin_button_get_value (GTK_SPIN_BUTTON (w_margin_y_1)); + + eom_image_get_size (priv->image, &pix_width, &pix_height); + + factor = get_scale_to_px_factor (setup); + + switch (change) { + case CHANGE_HORIZ: + orig_size_x = (gdouble) pix_width / factor; + orig_size_y = (gdouble) pix_height / factor; + break; + case CHANGE_VERT: + orig_size_y = (gdouble) pix_width / factor; + orig_size_x = (gdouble) pix_height / factor; + break; + } + + scale = CLAMP (size_x / orig_size_x, 0, 1); + + size_y = scale * orig_size_y; + + margin_x_2 = page_size_x - margin_x_1 - size_x; + margin_y_2 = page_size_y - margin_y_1 - size_y; + + eom_print_preview_set_scale (EOM_PRINT_PREVIEW (priv->preview), scale); + + switch (change) { + case CHANGE_HORIZ: + update_image_pos_ranges (setup, page_size_x, page_size_y, size_x, size_y); + break; + case CHANGE_VERT: + update_image_pos_ranges (setup, page_size_y, page_size_x, size_y, size_x); + break; + } + + gtk_range_set_value (GTK_RANGE (priv->scaling), 100*scale); + + gtk_spin_button_set_value (GTK_SPIN_BUTTON (w_margin_x_2), margin_x_2); + gtk_spin_button_set_value (GTK_SPIN_BUTTON (w_size_y), size_y); + gtk_spin_button_set_value (GTK_SPIN_BUTTON (w_margin_y_2), margin_y_2); + + gtk_combo_box_set_active (GTK_COMBO_BOX (priv->center), CENTER_NONE); +} + +static void +on_width_value_changed (GtkSpinButton *spinbutton, + gpointer user_data) +{ + EomPrintImageSetupPrivate *priv = EOM_PRINT_IMAGE_SETUP (user_data)->priv; + + size_changed (EOM_PRINT_IMAGE_SETUP (user_data), + priv->width, priv->height, + priv->left, priv->right, + priv->top, priv->bottom, + gtk_page_setup_get_page_width (priv->page_setup, + priv->current_unit), + gtk_page_setup_get_page_height (priv->page_setup, + priv->current_unit), + CHANGE_HORIZ); +} + +static void +on_height_value_changed (GtkSpinButton *spinbutton, + gpointer user_data) +{ + EomPrintImageSetupPrivate *priv = EOM_PRINT_IMAGE_SETUP (user_data)->priv; + + size_changed (EOM_PRINT_IMAGE_SETUP (user_data), + priv->height, priv->width, + priv->top, priv->bottom, + priv->left, priv->right, + gtk_page_setup_get_page_height (priv->page_setup, + priv->current_unit), + gtk_page_setup_get_page_width (priv->page_setup, + priv->current_unit), + CHANGE_VERT); +} + +static void +change_unit (GtkSpinButton *spinbutton, + gdouble factor, + gint digits, + gdouble step, + gdouble page) +{ + gdouble value; + gdouble range; + + gtk_spin_button_get_range (spinbutton, NULL, &range); + range *= factor; + + value = gtk_spin_button_get_value (spinbutton); + value *= factor; + + gtk_spin_button_set_range (spinbutton, 0, range); + gtk_spin_button_set_value (spinbutton, value); + gtk_spin_button_set_digits (spinbutton, digits); + gtk_spin_button_set_increments (spinbutton, step, page); +} + +static void +set_scale_unit (EomPrintImageSetup *setup, + GtkUnit unit) +{ + EomPrintImageSetupPrivate *priv = setup->priv; + gdouble factor; + gdouble step, page; + gint digits; + + if (G_UNLIKELY (priv->current_unit == unit)) + return; + + switch (unit) { + case GTK_UNIT_MM: + factor = FACTOR_INCH_TO_MM; + digits = 0; + step = 1; + page = 10; + break; + case GTK_UNIT_INCH: + factor = FACTOR_MM_TO_INCH; + digits = 2; + step = 0.01; + page = 0.1; + break; + default: + g_assert_not_reached (); + } + + block_handlers (setup); + + change_unit (GTK_SPIN_BUTTON (priv->width), factor, digits, step, page); + change_unit (GTK_SPIN_BUTTON (priv->height), factor, digits, step, page); + change_unit (GTK_SPIN_BUTTON (priv->left), factor, digits, step, page); + change_unit (GTK_SPIN_BUTTON (priv->right), factor, digits, step, page); + change_unit (GTK_SPIN_BUTTON (priv->top), factor, digits, step, page); + change_unit (GTK_SPIN_BUTTON (priv->bottom), factor, digits, step, page); + + unblock_handlers (setup); + + priv->current_unit = unit; +} + +static void +on_unit_changed (GtkComboBox *combobox, + gpointer user_data) +{ + GtkUnit unit = GTK_UNIT_INCH; + + switch (gtk_combo_box_get_active (combobox)) { + case UNIT_INCH: + unit = GTK_UNIT_INCH; + break; + case UNIT_MM: + unit = GTK_UNIT_MM; + break; + default: + g_assert_not_reached (); + } + + set_scale_unit (EOM_PRINT_IMAGE_SETUP (user_data), unit); +} + +static void +on_preview_image_moved (EomPrintPreview *preview, + gpointer user_data) +{ + EomPrintImageSetupPrivate *priv = EOM_PRINT_IMAGE_SETUP (user_data)->priv; + gdouble x, y; + + eom_print_preview_get_image_position (preview, &x, &y); + + if (priv->current_unit == GTK_UNIT_MM) { + x *= FACTOR_INCH_TO_MM; + y *= FACTOR_INCH_TO_MM; + } + + gtk_spin_button_set_value (GTK_SPIN_BUTTON (priv->left), x); + gtk_spin_button_set_value (GTK_SPIN_BUTTON (priv->top), y); +} + +/* Function taken from gtkprintunixdialog.c */ +static GtkWidget * +wrap_in_frame (const gchar *label, + GtkWidget *child) +{ + GtkWidget *frame, *alignment, *label_widget; + gchar *bold_text; + + label_widget = gtk_label_new (""); + gtk_misc_set_alignment (GTK_MISC (label_widget), 0.0, 0.5); + gtk_widget_show (label_widget); + + bold_text = g_markup_printf_escaped ("<b>%s</b>", label); + gtk_label_set_markup (GTK_LABEL (label_widget), bold_text); + g_free (bold_text); + + frame = gtk_vbox_new (FALSE, 6); + gtk_box_pack_start (GTK_BOX (frame), label_widget, FALSE, FALSE, 0); + + alignment = gtk_alignment_new (0.0, 0.0, 1.0, 1.0); + gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), + 0, 0, 12, 0); + gtk_box_pack_start (GTK_BOX (frame), alignment, FALSE, FALSE, 0); + + gtk_container_add (GTK_CONTAINER (alignment), child); + + gtk_widget_show (frame); + gtk_widget_show (alignment); + + return frame; +} + +static GtkWidget * +table_attach_spin_button_with_label (GtkWidget *table, + const gchar* text_label, + gint left, gint top) +{ + GtkWidget *label, *spin_button; + + label = gtk_label_new_with_mnemonic (text_label); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + spin_button = gtk_spin_button_new_with_range (0, 100, 0.01); + gtk_spin_button_set_digits (GTK_SPIN_BUTTON (spin_button), 2); + gtk_entry_set_width_chars (GTK_ENTRY (spin_button), 6); + gtk_table_attach (GTK_TABLE (table), label, left, left + 1, + top, top + 1, GTK_FILL, GTK_FILL, 0, 0); + gtk_table_attach (GTK_TABLE (table), spin_button, left + 1, left + 2, + top, top + 1, GTK_FILL, GTK_FILL, 0, 0); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), spin_button); + + return spin_button; +} + +static void +eom_print_image_setup_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + EomPrintImageSetup *setup = EOM_PRINT_IMAGE_SETUP (object); + EomPrintImageSetupPrivate *priv = setup->priv; + GdkPixbuf *pixbuf; + + switch (prop_id) { + case PROP_IMAGE: + if (priv->image) { + g_object_unref (priv->image); + } + priv->image = EOM_IMAGE (g_value_dup_object (value)); + if (EOM_IS_IMAGE (priv->image)) { + pixbuf = eom_image_get_pixbuf (priv->image); + g_object_set (priv->preview, "image", + pixbuf, NULL); + g_object_unref (pixbuf); + } + break; + case PROP_PAGE_SETUP: + priv->page_setup = g_value_get_object (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +eom_print_image_setup_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + EomPrintImageSetup *setup = EOM_PRINT_IMAGE_SETUP (object); + EomPrintImageSetupPrivate *priv = setup->priv; + + switch (prop_id) { + case PROP_IMAGE: + g_value_set_object (value, priv->image); + break; + case PROP_PAGE_SETUP: + g_value_set_object (value, priv->page_setup); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +set_initial_values (EomPrintImageSetup *setup) +{ + EomPrintImageSetupPrivate *priv; + GtkPageSetup *page_setup; + EomImage *image; + gdouble page_width, page_height; + gint pix_width, pix_height; + gdouble factor; + gdouble width, height; + gdouble max_perc; + + priv = setup->priv; + page_setup = priv->page_setup; + image = priv->image; + + factor = get_scale_to_px_factor (setup); + + eom_image_get_size (image, &pix_width, &pix_height); + width = (gdouble)pix_width/factor; + height = (gdouble)pix_height/factor; + + max_perc = get_max_percentage (setup); + + width *= max_perc; + height *= max_perc; + + gtk_range_set_range (GTK_RANGE (priv->scaling), 1, 100*max_perc); + gtk_range_set_increments (GTK_RANGE (priv->scaling), max_perc, 10*max_perc); + gtk_range_set_value (GTK_RANGE (priv->scaling), 100*max_perc); + + eom_print_preview_set_scale (EOM_PRINT_PREVIEW (priv->preview), max_perc); + gtk_spin_button_set_range (GTK_SPIN_BUTTON (priv->width), 0, width); + gtk_spin_button_set_range (GTK_SPIN_BUTTON (priv->height), 0, height); + gtk_spin_button_set_value (GTK_SPIN_BUTTON (priv->width), width); + gtk_spin_button_set_value (GTK_SPIN_BUTTON (priv->height), height); + + gtk_combo_box_set_active (GTK_COMBO_BOX (priv->center), + CENTER_BOTH); + + center (gtk_page_setup_get_page_width (priv->page_setup, priv->current_unit), + gtk_spin_button_get_value (GTK_SPIN_BUTTON (priv->width)), + GTK_SPIN_BUTTON (priv->left), GTK_SPIN_BUTTON (priv->right)); + center (gtk_page_setup_get_page_height (priv->page_setup, priv->current_unit), + gtk_spin_button_get_value (GTK_SPIN_BUTTON (priv->height)), + GTK_SPIN_BUTTON (priv->top), GTK_SPIN_BUTTON (priv->bottom)); + + page_width = gtk_page_setup_get_page_width (page_setup, priv->current_unit); + page_height = gtk_page_setup_get_page_height (page_setup, priv->current_unit); + + update_image_pos_ranges (setup, page_width, page_height, width, height); + + +} + +static void +connect_signals (EomPrintImageSetup *setup) +{ + EomPrintImageSetupPrivate *priv; + + priv = setup->priv; + + g_signal_connect (G_OBJECT (priv->left), "value-changed", + G_CALLBACK (on_left_value_changed), setup); + g_signal_connect (G_OBJECT (priv->right), "value-changed", + G_CALLBACK (on_right_value_changed), setup); + g_signal_connect (G_OBJECT (priv->top), "value-changed", + G_CALLBACK (on_top_value_changed), setup); + g_signal_connect (G_OBJECT (priv->bottom), "value-changed", + G_CALLBACK (on_bottom_value_changed), setup); + g_signal_connect (G_OBJECT (priv->width), "value-changed", + G_CALLBACK (on_width_value_changed), setup); + g_signal_connect (G_OBJECT (priv->height), "value-changed", + G_CALLBACK (on_height_value_changed), setup); + g_signal_connect (G_OBJECT (priv->scaling), "value-changed", + G_CALLBACK (on_scale_changed), setup); + g_signal_connect (G_OBJECT (priv->scaling), "format-value", + G_CALLBACK (on_scale_format_value), NULL); + g_signal_connect (G_OBJECT (priv->preview), "image-moved", + G_CALLBACK (on_preview_image_moved), setup); +} + +static void +eom_print_image_setup_class_init (EomPrintImageSetupClass *class) +{ + GObjectClass *object_class = (GObjectClass *)class; + + object_class->set_property = eom_print_image_setup_set_property; + object_class->get_property = eom_print_image_setup_get_property; + + g_object_class_install_property (object_class, PROP_IMAGE, + g_param_spec_object ("image", + _("Image"), + _("The image whose printing properties will be set up"), + EOM_TYPE_IMAGE, + G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_PAGE_SETUP, + g_param_spec_object ("page-setup", + _("Page Setup"), + _("The information for the page where the image will be printed"), + GTK_TYPE_PAGE_SETUP, + G_PARAM_READWRITE)); + + g_type_class_add_private (class, sizeof (EomPrintImageSetupPrivate)); +} + +static void +eom_print_image_setup_init (EomPrintImageSetup *setup) +{ + GtkWidget *frame; + GtkWidget *table; + GtkWidget *label; + GtkWidget *hscale; + GtkWidget *combobox; + EomPrintImageSetupPrivate *priv; + +#ifdef HAVE__NL_MEASUREMENT_MEASUREMENT + gchar *locale_scale = NULL; +#endif + + priv = setup->priv = EOM_PRINT_IMAGE_SETUP_GET_PRIVATE (setup); + + priv->image = NULL; + + table = gtk_table_new (3, 4, FALSE); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_table_set_col_spacings (GTK_TABLE (table), 12); + frame = wrap_in_frame (_("Position"), table); + gtk_table_attach (GTK_TABLE (setup), frame, + 0, 1, 0, 1, GTK_FILL, 0, + 0, 0); + + priv->left = table_attach_spin_button_with_label (table, _("_Left:"), 0, 0); + priv->right = table_attach_spin_button_with_label (table, _("_Right:"), 0, 1); + priv->top = table_attach_spin_button_with_label (table, _("_Top:"), 2, 0); + priv->bottom = table_attach_spin_button_with_label (table, _("_Bottom:"), 2, 1); + + label = gtk_label_new_with_mnemonic (_("C_enter:")); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + + combobox = gtk_combo_box_new_text (); + gtk_combo_box_insert_text (GTK_COMBO_BOX (combobox), + CENTER_NONE, _("None")); + gtk_combo_box_insert_text (GTK_COMBO_BOX (combobox), + CENTER_HORIZONTAL, _("Horizontal")); + gtk_combo_box_insert_text (GTK_COMBO_BOX (combobox), + CENTER_VERTICAL, _("Vertical")); + gtk_combo_box_insert_text (GTK_COMBO_BOX (combobox), + CENTER_BOTH, _("Both")); + gtk_combo_box_set_active (GTK_COMBO_BOX (combobox), CENTER_NONE); + gtk_table_attach (GTK_TABLE (table), label, + 0, 1, 2, 3, GTK_FILL, GTK_FILL, + 0, 0); + gtk_table_attach (GTK_TABLE (table), combobox, + 1, 4, 2, 3, GTK_FILL | GTK_EXPAND, GTK_FILL, + 0, 0); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), combobox); + priv->center = combobox; + g_signal_connect (G_OBJECT (combobox), "changed", + G_CALLBACK (on_center_changed), setup); + + table = gtk_table_new (3, 4, FALSE); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_table_set_col_spacings (GTK_TABLE (table), 12); + frame = wrap_in_frame (_("Size"), table); + gtk_table_attach (GTK_TABLE (setup), frame, + 0, 1, 1, 2, GTK_FILL, 0, + 0, 0); + + priv->width = table_attach_spin_button_with_label (table, _("_Width:"), + 0, 0); + priv->height = table_attach_spin_button_with_label (table, _("_Height:"), + 2, 0); + + label = gtk_label_new_with_mnemonic (_("_Scaling:")); + hscale = gtk_hscale_new_with_range (1, 100, 1); + gtk_scale_set_value_pos (GTK_SCALE (hscale), GTK_POS_RIGHT); + gtk_range_set_value (GTK_RANGE (hscale), 100); + gtk_table_attach (GTK_TABLE (table), label, + 0, 1, 1, 2, GTK_FILL, GTK_FILL, + 0, 0); + gtk_table_attach (GTK_TABLE (table), hscale, + 1, 4, 1, 2, GTK_FILL | GTK_EXPAND, GTK_FILL, + 0, 0); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), hscale); + priv->scaling = hscale; + + label = gtk_label_new_with_mnemonic (_("_Unit:")); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + + combobox = gtk_combo_box_new_text (); + gtk_combo_box_insert_text (GTK_COMBO_BOX (combobox), UNIT_MM, + _("Millimeters")); + gtk_combo_box_insert_text (GTK_COMBO_BOX (combobox), UNIT_INCH, + _("Inches")); + +#ifdef HAVE__NL_MEASUREMENT_MEASUREMENT + locale_scale = nl_langinfo (_NL_MEASUREMENT_MEASUREMENT); + if (locale_scale && locale_scale[0] == 2) { + gtk_combo_box_set_active (GTK_COMBO_BOX (combobox), UNIT_INCH); + set_scale_unit (setup, GTK_UNIT_INCH); + } else +#endif + { + gtk_combo_box_set_active (GTK_COMBO_BOX (combobox), UNIT_MM); + set_scale_unit (setup, GTK_UNIT_MM); + } + + gtk_table_attach (GTK_TABLE (table), label, + 0, 1, 2, 3, GTK_FILL, GTK_FILL, + 0, 0); + gtk_table_attach (GTK_TABLE (table), combobox, + 1, 4, 2, 3, GTK_FILL | GTK_EXPAND, GTK_FILL, + 0, 0); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), combobox); + priv->unit = combobox; + g_signal_connect (G_OBJECT (combobox), "changed", + G_CALLBACK (on_unit_changed), setup); + + priv->preview = eom_print_preview_new (); + + /* FIXME: This shouldn't be set by hand */ + gtk_widget_set_size_request (priv->preview, 250, 250); + + frame = wrap_in_frame (_("Preview"), priv->preview); + gtk_table_attach (GTK_TABLE (setup), frame, + 1, 2, 0, 2, GTK_FILL, GTK_FILL, + 0, 0); + + gtk_widget_show_all (GTK_WIDGET (setup)); +} + + +/** + * eom_print_image_setup_new: + * @image: the #EomImage to print + * @page_setup: a #GtkPageSetup specifying the page where + * the image will be print + * + * Creates a new #EomPrintImageSetup widget, to be used as a custom + * widget in a #GtkPrintUnixDialog. This widgets allows to set + * the image position and scale in a page. + * + * Returns: a new #EomPrintImageSetup + **/ +GtkWidget * +eom_print_image_setup_new (EomImage *image, GtkPageSetup *page_setup) +{ + GtkWidget *setup; + GtkWidget *preview; + + setup = g_object_new (EOM_TYPE_PRINT_IMAGE_SETUP, + "n-rows", 2, + "n-columns", 2, + "homogeneous", FALSE, + "row-spacing", 18, + "column-spacing", 18, + "border-width", 12, + "image", image, + "page-setup", page_setup, + NULL); + + set_initial_values (EOM_PRINT_IMAGE_SETUP (setup)); + + preview = EOM_PRINT_IMAGE_SETUP (setup)->priv->preview; + eom_print_preview_set_from_page_setup (EOM_PRINT_PREVIEW (preview), + page_setup); + + connect_signals (EOM_PRINT_IMAGE_SETUP (setup)); + + return setup; +} + +/** + * eom_print_image_setup_get_options: + * @setup: a #EomPrintImageSetup + * @left: a pointer where to store the image's left position + * @top: a pointer where to store the image's top position + * @scale: a pointer where to store the image's scale + * @unit: a pointer where to store the #GtkUnit used by the @left and @top values. + * + * Gets the options set by the #EomPrintImageSetup. + **/ +void +eom_print_image_setup_get_options (EomPrintImageSetup *setup, + gdouble *left, + gdouble *top, + gdouble *scale, + GtkUnit *unit) +{ + EomPrintImageSetupPrivate *priv; + + g_return_if_fail (EOM_IS_PRINT_IMAGE_SETUP (setup)); + + priv = setup->priv; + + *left = gtk_spin_button_get_value (GTK_SPIN_BUTTON (priv->left)); + *top = gtk_spin_button_get_value (GTK_SPIN_BUTTON (priv->top)); + *scale = gtk_range_get_value (GTK_RANGE (priv->scaling)); + *unit = priv->current_unit; +} + +void +eom_print_image_setup_update (GtkPrintOperation *operation, + GtkWidget *custom_widget, + GtkPageSetup *page_setup, + GtkPrintSettings *print_settings, + gpointer user_data) +{ + GtkWidget *preview; + gdouble pos_x; + gdouble pos_y; + EomPrintImageSetup *setup; + + setup = EOM_PRINT_IMAGE_SETUP (custom_widget); + + setup->priv->page_setup = gtk_page_setup_copy (page_setup); + + set_initial_values (EOM_PRINT_IMAGE_SETUP (setup)); + + preview = EOM_PRINT_IMAGE_SETUP (setup)->priv->preview; + eom_print_preview_set_from_page_setup (EOM_PRINT_PREVIEW (preview), + setup->priv->page_setup); + + pos_x = gtk_spin_button_get_value (GTK_SPIN_BUTTON (setup->priv->left)); + pos_y = gtk_spin_button_get_value (GTK_SPIN_BUTTON (setup->priv->top)); + if (setup->priv->current_unit == GTK_UNIT_MM) { + pos_x *= FACTOR_MM_TO_INCH; + pos_y *= FACTOR_MM_TO_INCH; + } + eom_print_preview_set_image_position (EOM_PRINT_PREVIEW (setup->priv->preview), pos_x, pos_y); +} diff --git a/src/eom-print-image-setup.h b/src/eom-print-image-setup.h new file mode 100644 index 0000000..9ed60cd --- /dev/null +++ b/src/eom-print-image-setup.h @@ -0,0 +1,71 @@ +/* Eye Of MATE -- Print Dialog Custom Widget + * + * Copyright (C) 2006-2007 The Free Software Foundation + * + * Author: Claudio Saavedra <[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 "eom-image.h" + +#ifndef EOM_PRINT_IMAGE_SETUP_H +#define EOM_PRINT_IMAGE_SETUP_H + +G_BEGIN_DECLS + +typedef struct _EomPrintImageSetup EomPrintImageSetup; +typedef struct _EomPrintImageSetupClass EomPrintImageSetupClass; +typedef struct EomPrintImageSetupPrivate EomPrintImageSetupPrivate; + +#define EOM_TYPE_PRINT_IMAGE_SETUP (eom_print_image_setup_get_type ()) +#define EOM_PRINT_IMAGE_SETUP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EOM_TYPE_PRINT_IMAGE_SETUP, EomPrintImageSetup)) +#define EOM_PRINT_IMAGE_SETUP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EOM_TYPE_PRINT_IMAGE_SETUP, EomPrintImageSetupClass)) +#define EOM_IS_PRINT_IMAGE_SETUP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EOM_TYPE_PRINT_IMAGE_SETUP)) +#define EOM_IS_PRINT_IMAGE_SETUP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EOM_TYPE_PRINT_IMAGE_SETUP)) +#define EOM_PRINT_IMAGE_SETUP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EOM_TYPE_PRINT_IMAGE_SETUP, EomPrintImageSetupClass)) + +struct _EomPrintImageSetup { + GtkTable parent_instance; + + EomPrintImageSetupPrivate *priv; +}; + +struct _EomPrintImageSetupClass { + GtkTableClass parent_class; +}; + +G_GNUC_INTERNAL +GType eom_print_image_setup_get_type (void) G_GNUC_CONST; + +G_GNUC_INTERNAL +GtkWidget *eom_print_image_setup_new (EomImage *image, + GtkPageSetup *page_setup); + +G_GNUC_INTERNAL +void eom_print_image_setup_get_options (EomPrintImageSetup *setup, + gdouble *left, + gdouble *top, + gdouble *scale, + GtkUnit *unit); +void eom_print_image_setup_update (GtkPrintOperation *operation, + GtkWidget *custom_widget, + GtkPageSetup *page_setup, + GtkPrintSettings *print_settings, + gpointer user_data); + +G_END_DECLS + +#endif /* EOM_PRINT_IMAGE_SETUP_H */ diff --git a/src/eom-print-preview.c b/src/eom-print-preview.c new file mode 100644 index 0000000..13957c6 --- /dev/null +++ b/src/eom-print-preview.c @@ -0,0 +1,1226 @@ +/* Eye Of MATE -- Print Preview Widget + * + * Copyright (C) 2006-2008 The Free Software Foundation + * + * Author: Claudio Saavedra <[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 <gtk/gtk.h> +#include <cairo.h> +#include <gdk/gdkkeysyms.h> + +#include "eom-image.h" +#include "eom-print-preview.h" + +#define EOM_PRINT_PREVIEW_GET_PRIVATE(object) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((object), EOM_TYPE_PRINT_PREVIEW, EomPrintPreviewPrivate)) + +G_DEFINE_TYPE (EomPrintPreview, eom_print_preview, GTK_TYPE_ASPECT_FRAME) + +struct _EomPrintPreviewPrivate { + GtkWidget *area; + GdkPixbuf *image; + GdkPixbuf *image_scaled; + + /* The surface to set to the cairo context, created from the image */ + cairo_surface_t *surface; + + /* Flag whether we have to create surface */ + gboolean flag_create_surface; + + /* the alignment of the image in the page */ + gfloat image_x_align, image_y_align; + + /* real paper size, in inches */ + gfloat p_width, p_height; + + /* page margins, in inches */ + gfloat l_margin, r_margin, t_margin, b_margin; + + /* page margins, relatives to the widget size */ + gint l_rmargin, r_rmargin, t_rmargin, b_rmargin; + + /* image width, relative to the widget size */ + gint r_width, r_height; + + /* scale of the image, as defined by the user */ + gfloat i_scale; + + /* scale of the page, relative to the widget size */ + gfloat p_scale; + + /* whether we are currently grabbing the image */ + gboolean grabbed; + + /* the last cursor position */ + gdouble cursorx, cursory; + + /* if we reject to move the image, + store the delta here */ + gdouble r_dx, r_dy; +}; + +/* Signal IDs */ +enum { + SIGNAL_IMAGE_MOVED, + SIGNAL_LAST +}; +static gint preview_signals [SIGNAL_LAST]; + +enum { + PROP_IMAGE = 1, + PROP_IMAGE_X_ALIGN, + PROP_IMAGE_Y_ALIGN, + PROP_IMAGE_SCALE, + PROP_PAPER_WIDTH, + PROP_PAPER_HEIGHT, + PROP_PAGE_LEFT_MARGIN, + PROP_PAGE_RIGHT_MARGIN, + PROP_PAGE_TOP_MARGIN, + PROP_PAGE_BOTTOM_MARGIN +}; + +static void eom_print_preview_draw (EomPrintPreview *preview, cairo_t *cr); +static void eom_print_preview_finalize (GObject *object); +static void update_relative_sizes (EomPrintPreview *preview); +static void create_surface (EomPrintPreview *preview); +static void create_image_scaled (EomPrintPreview *preview); +static gboolean create_surface_when_idle (EomPrintPreview *preview); + +static void +eom_print_preview_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + EomPrintPreviewPrivate *priv = EOM_PRINT_PREVIEW (object)->priv; + + switch (prop_id) { + case PROP_IMAGE: + g_value_set_object (value, priv->image); + break; + case PROP_IMAGE_X_ALIGN: + g_value_set_float (value, priv->image_x_align); + break; + case PROP_IMAGE_Y_ALIGN: + g_value_set_float (value, priv->image_y_align); + break; + case PROP_IMAGE_SCALE: + g_value_set_float (value, priv->i_scale); + break; + case PROP_PAPER_WIDTH: + g_value_set_float (value, priv->p_width); + break; + case PROP_PAPER_HEIGHT: + g_value_set_float (value, priv->p_height); + break; + case PROP_PAGE_LEFT_MARGIN: + g_value_set_float (value, priv->l_margin); + break; + case PROP_PAGE_RIGHT_MARGIN: + g_value_set_float (value, priv->r_margin); + break; + case PROP_PAGE_TOP_MARGIN: + g_value_set_float (value, priv->t_margin); + break; + case PROP_PAGE_BOTTOM_MARGIN: + g_value_set_float (value, priv->b_margin); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +eom_print_preview_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + EomPrintPreviewPrivate *priv = EOM_PRINT_PREVIEW (object)->priv; + gboolean paper_size_changed = FALSE; + + switch (prop_id) { + case PROP_IMAGE: + if (priv->image) { + g_object_unref (priv->image); + } + priv->image = GDK_PIXBUF (g_value_dup_object (value)); + + if (priv->image_scaled) { + g_object_unref (priv->image_scaled); + priv->image_scaled = NULL; + } + + priv->flag_create_surface = TRUE; + break; + case PROP_IMAGE_X_ALIGN: + priv->image_x_align = g_value_get_float (value); + break; + case PROP_IMAGE_Y_ALIGN: + priv->image_y_align = g_value_get_float (value); + break; + case PROP_IMAGE_SCALE: + priv->i_scale = g_value_get_float (value); + priv->flag_create_surface = TRUE; + break; + case PROP_PAPER_WIDTH: + priv->p_width = g_value_get_float (value); + paper_size_changed = TRUE; + break; + case PROP_PAPER_HEIGHT: + priv->p_height = g_value_get_float (value); + paper_size_changed = TRUE; + break; + case PROP_PAGE_LEFT_MARGIN: + priv->l_margin = g_value_get_float (value); + break; + case PROP_PAGE_RIGHT_MARGIN: + priv->r_margin = g_value_get_float (value); + break; + case PROP_PAGE_TOP_MARGIN: + priv->t_margin = g_value_get_float (value); + break; + case PROP_PAGE_BOTTOM_MARGIN: + priv->b_margin = g_value_get_float (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } + + if (paper_size_changed) { + g_object_set (object, + "ratio", priv->p_width/priv->p_height, + NULL); + } + + update_relative_sizes (EOM_PRINT_PREVIEW (object)); + gtk_widget_queue_draw (priv->area); +} + +static void +eom_print_preview_class_init (EomPrintPreviewClass *klass) +{ + GObjectClass *gobject_class; + + gobject_class = (GObjectClass*) klass; + + gobject_class->get_property = eom_print_preview_get_property; + gobject_class->set_property = eom_print_preview_set_property; + gobject_class->finalize = eom_print_preview_finalize; + +/** + * EomPrintPreview:image: + * + * The "image" property defines the image that is previewed + * in the widget. + */ + g_object_class_install_property (gobject_class, + PROP_IMAGE, + g_param_spec_object ("image", + "Image to show in the preview", + "", + G_TYPE_OBJECT, + G_PARAM_READWRITE)); + +/** + * EomPrintPreview:image-x-align: + * + * The "image-x-align" property defines the horizontal alignment + * of the image in the widget. + */ + g_object_class_install_property (gobject_class, + PROP_IMAGE_X_ALIGN, + g_param_spec_float ("image-x-align", + "Horizontal alignment for the image", + "", + 0, + 1, + 0.5, + G_PARAM_READWRITE)); + +/** + * EomPrintPreview:image-y-align: + * + * The "image-y-align" property defines the horizontal alignment + * of the image in the widget. + */ + g_object_class_install_property (gobject_class, + PROP_IMAGE_Y_ALIGN, + g_param_spec_float ("image-y-align", + "Vertical alignment for the image", + "", + 0, + 1, + 0.5, + G_PARAM_READWRITE)); + +/** + * EomPrintPreview:image-scale: + * + * The "image-scale" property defines the scaling of the image + * that the user wants for the printing. + */ + g_object_class_install_property (gobject_class, + PROP_IMAGE_SCALE, + g_param_spec_float ("image-scale", + "The scale for the image", + "", + 0, + 1, + 1, + G_PARAM_READWRITE)); + +/** + * EomPrintPreview:paper-width: + * + * The width of the previewed paper, in inches. + */ + g_object_class_install_property (gobject_class, + PROP_PAPER_WIDTH, + g_param_spec_float ("paper-width", + "Real paper width in inches", + "", + 0, + 100, + 8.5, + G_PARAM_READWRITE)); + +/** + * EomPrintPreview:paper-height: + * + * The height of the previewed paper, in inches. + */ + g_object_class_install_property (gobject_class, + PROP_PAPER_HEIGHT, + g_param_spec_float ("paper-height", + "Real paper height in inches", + "", + 0, + 200, + 11, + G_PARAM_READWRITE)); + +/** + * EomPrintPreview:page-left-margin: + * + * The size of the page's left margin, in inches. + */ + g_object_class_install_property (gobject_class, + PROP_PAGE_LEFT_MARGIN, + g_param_spec_float ("page-left-margin", + "Left margin of the page in inches", + "", + 0, + 100, + 0.25, + G_PARAM_READWRITE)); + +/** + * EomPrintPreview:page-right-margin: + * + * The size of the page's right margin, in inches. + */ + g_object_class_install_property (gobject_class, + PROP_PAGE_RIGHT_MARGIN, + g_param_spec_float ("page-right-margin", + "Right margin of the page in inches", + "", + 0, + 200, + 0.25, + G_PARAM_READWRITE)); +/** + * EomPrintPreview:page-top-margin: + * + * The size of the page's top margin, in inches. + */ + g_object_class_install_property (gobject_class, + PROP_PAGE_TOP_MARGIN, + g_param_spec_float ("page-top-margin", + "Top margin of the page in inches", + "", + 0, + 100, + 0.25, + G_PARAM_READWRITE)); + +/** + * EomPrintPreview:page-bottom-margin: + * + * The size of the page's bottom margin, in inches. + */ + g_object_class_install_property (gobject_class, + PROP_PAGE_BOTTOM_MARGIN, + g_param_spec_float ("page-bottom-margin", + "Bottom margin of the page in inches", + "", + 0, + 200, + 0.56, + G_PARAM_READWRITE)); + +/** + * EomPrintPreview::image-moved: + * @preview: the object which received the signal + * + * The #EomPrintPreview::image-moved signal is emitted when the position + * of the image is changed. + */ + preview_signals [SIGNAL_IMAGE_MOVED] = + g_signal_new ("image_moved", + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_FIRST, 0, NULL, NULL, + g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, + 0, NULL); + + g_type_class_add_private (klass, sizeof (EomPrintPreviewPrivate)); +} + +static void +eom_print_preview_finalize (GObject *object) +{ + EomPrintPreviewPrivate *priv; + + priv = EOM_PRINT_PREVIEW (object)->priv; + + if (priv->image) { + g_object_unref (priv->image); + priv->image = NULL; + } + + if (priv->image_scaled) { + g_object_unref (priv->image_scaled); + priv->image_scaled = NULL; + } + + if (priv->surface) { + cairo_surface_destroy (priv->surface); + priv->surface = NULL; + } + + G_OBJECT_CLASS (eom_print_preview_parent_class)->finalize (object); +} + +static void +eom_print_preview_init (EomPrintPreview *preview) +{ + EomPrintPreviewPrivate *priv; + gfloat ratio; + + priv = preview->priv = EOM_PRINT_PREVIEW_GET_PRIVATE (preview); + + priv->area = GTK_WIDGET (gtk_drawing_area_new ()); + + gtk_container_add (GTK_CONTAINER (preview), priv->area); + + priv->p_width = 8.5; + priv->p_height = 11.0; + + ratio = priv->p_width/priv->p_height; + + gtk_aspect_frame_set (GTK_ASPECT_FRAME (preview), + 0.5, 0.5, ratio, FALSE); + + priv->image = NULL; + priv->image_scaled = NULL; + priv->image_x_align = 0.5; + priv->image_y_align = 0.5; + priv->i_scale = 1; + + priv->surface = NULL; + priv->flag_create_surface = TRUE; + + priv->p_scale = 0; + + priv->l_margin = 0.25; + priv->r_margin = 0.25; + priv->t_margin = 0.25; + priv->b_margin = 0.56; + + priv->grabbed = FALSE; + priv->cursorx = 0; + priv->cursory = 0; + priv->r_dx = 0; + priv->r_dy = 0; +} + +static gboolean button_press_event_cb (GtkWidget *widget, GdkEventButton *bev, gpointer user_data); +static gboolean button_release_event_cb (GtkWidget *widget, GdkEventButton *bev, gpointer user_data); +static gboolean motion_notify_event_cb (GtkWidget *widget, GdkEventMotion *mev, gpointer user_data); +static gboolean key_press_event_cb (GtkWidget *widget, GdkEventKey *event, gpointer user_data); + +static void expose_event_cb (GtkDrawingArea *drawing_area, GdkEventExpose *eev, gpointer user_data); +static void size_allocate_cb (GtkWidget *widget, GtkAllocation *allocation, gpointer user_data); + +/** + * eom_print_preview_new_with_pixbuf: + * @pixbuf: a #GdkPixbuf + * + * Creates a new #EomPrintPreview widget, and sets the #GdkPixbuf to preview + * on it. + * + * Returns: A new #EomPrintPreview widget. + **/ +GtkWidget * +eom_print_preview_new_with_pixbuf (GdkPixbuf *pixbuf) +{ + EomPrintPreview *preview; + + g_return_val_if_fail (GDK_IS_PIXBUF (pixbuf), NULL); + + preview = EOM_PRINT_PREVIEW (eom_print_preview_new ()); + + preview->priv->image = g_object_ref (pixbuf); + + update_relative_sizes (preview); + + return GTK_WIDGET (preview); +} + +/** + * eom_print_preview_new: + * + * Creates a new #EomPrintPreview widget, setting it to the default values, + * and leaving the page empty. You still need to set the #EomPrintPreview:image + * property to make it useful. + * + * Returns: A new and empty #EomPrintPreview widget. + **/ +GtkWidget * +eom_print_preview_new (void) +{ + EomPrintPreview *preview; + GtkWidget *area; + + preview = g_object_new (EOM_TYPE_PRINT_PREVIEW, NULL); + + area = preview->priv->area; + + gtk_widget_set_events (area, + GDK_EXPOSURE_MASK | + GDK_POINTER_MOTION_MASK | + GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | + GDK_KEY_PRESS_MASK); + + g_object_set (G_OBJECT (area), + "can-focus", TRUE, + NULL); + +/* update_relative_sizes (preview); */ + + g_signal_connect (G_OBJECT (area), "expose-event", + G_CALLBACK (expose_event_cb), preview); + + g_signal_connect (G_OBJECT (area), "motion-notify-event", + G_CALLBACK (motion_notify_event_cb), preview); + + g_signal_connect (G_OBJECT (area), "button-press-event", + G_CALLBACK (button_press_event_cb), preview); + + g_signal_connect (G_OBJECT (area), "button-release-event", + G_CALLBACK (button_release_event_cb), preview); + + g_signal_connect (G_OBJECT (area), "key-press-event", + G_CALLBACK (key_press_event_cb), preview); + + g_signal_connect (area, "size-allocate", + G_CALLBACK (size_allocate_cb), preview); + + return GTK_WIDGET (preview); +} + +static void +expose_event_cb (GtkDrawingArea *drawing_area, + GdkEventExpose *eev, + gpointer user_data) +{ + GtkWidget *widget; + EomPrintPreviewPrivate *priv; + cairo_t *cr; + + widget = GTK_WIDGET (drawing_area); + priv = EOM_PRINT_PREVIEW (user_data)->priv; + + update_relative_sizes (EOM_PRINT_PREVIEW (user_data)); + + cr = gdk_cairo_create (gtk_widget_get_window (widget)); + + eom_print_preview_draw (EOM_PRINT_PREVIEW (user_data), cr); + + if (cairo_status (cr) != CAIRO_STATUS_SUCCESS) { + fprintf (stderr, "Cairo is unhappy: %s\n", + cairo_status_to_string (cairo_status (cr))); + } + + cairo_destroy (cr); + + gdk_window_get_pointer (gtk_widget_get_window (widget), NULL, NULL, NULL); +} + +/** + * get_current_image_coordinates: + * @preview: an #EomPrintPreview + * @x0: A pointer where to store the x coordinate. + * @y0: A pointer where to store the y coordinate. + * + * This function returns the current image coordinates, according + * with the properties of the given @preview widget. + **/ +static void +get_current_image_coordinates (EomPrintPreview *preview, + gint *x0, gint *y0) +{ + EomPrintPreviewPrivate *priv; + GtkAllocation allocation; + + priv = preview->priv; + gtk_widget_get_allocation (GTK_WIDGET (priv->area), &allocation); + + *x0 = (gint)((1 - priv->image_x_align)*priv->l_rmargin + priv->image_x_align*(allocation.width - priv->r_rmargin - priv->r_width)); + *y0 = (gint)((1 - priv->image_y_align)*priv->t_rmargin + priv->image_y_align*(allocation.height - priv->b_rmargin - priv->r_height)); +} + +/** + * press_inside_image_area: + * @preview: an #EomPrintPreview + * @x: the points x coordinate + * @y: the points y coordinate + * + * Returns whether the given point is inside the image area. + * + * Returns: %TRUE if the given point is inside of the image area, + * %FALSE otherwise. + **/ +static gboolean +press_inside_image_area (EomPrintPreview *preview, + guint x, + guint y) +{ + EomPrintPreviewPrivate *priv; + gint x0, y0; + + priv = preview->priv; + get_current_image_coordinates (preview, &x0, &y0); + + if (x >= x0 && y >= y0 && + x <= x0 + priv->r_width && y <= y0 + priv->r_height) + return TRUE; + + return FALSE; +} + +static void +create_image_scaled (EomPrintPreview *preview) +{ + EomPrintPreviewPrivate *priv = preview->priv; + + if (!priv->image_scaled) { + gint i_width, i_height; + GtkAllocation allocation; + + gtk_widget_get_allocation (priv->area, &allocation); + i_width = gdk_pixbuf_get_width (priv->image); + i_height = gdk_pixbuf_get_height (priv->image); + + if ((i_width > allocation.width) || + (i_height > allocation.height)) { + gdouble scale; + scale = MIN ((gdouble) allocation.width/i_width, + (gdouble) allocation.height/i_height); + priv->image_scaled = gdk_pixbuf_scale_simple (priv->image, + i_width*scale, + i_height*scale, + GDK_INTERP_TILES); + } else { + priv->image_scaled = priv->image; + g_object_ref (priv->image_scaled); + } + } +} + +static GdkPixbuf * +create_preview_buffer (EomPrintPreview *preview) +{ + GdkPixbuf *pixbuf; + gint width, height; + GdkInterpType type = GDK_INTERP_TILES; + + if (preview->priv->image == NULL) { + return NULL; + } + + create_image_scaled (preview); + + width = gdk_pixbuf_get_width (preview->priv->image); + height = gdk_pixbuf_get_height (preview->priv->image); + + width *= preview->priv->i_scale * preview->priv->p_scale; + height *= preview->priv->i_scale * preview->priv->p_scale; + + if (width < 1 || height < 1) + return NULL; + + /* to use GDK_INTERP_TILES for small pixbufs is expensive and unnecessary */ + if (width < 25 || height < 25) + type = GDK_INTERP_NEAREST; + + if (preview->priv->image_scaled) { + pixbuf = gdk_pixbuf_scale_simple (preview->priv->image_scaled, + width, height, type); + } else { + pixbuf = gdk_pixbuf_scale_simple (preview->priv->image, + width, height, type); + } + + return pixbuf; +} + +/* + Function inspired from gdk_cairo_set_source_pixbuf (). The main reason is + that I want to save the cairo_surface_t created from the scaled buffer to + improve performance. +*/ +static cairo_surface_t * +create_surface_from_pixbuf (GdkPixbuf *pixbuf) +{ + gint width = gdk_pixbuf_get_width (pixbuf); + gint height = gdk_pixbuf_get_height (pixbuf); + guchar *gdk_pixels = gdk_pixbuf_get_pixels (pixbuf); + int gdk_rowstride = gdk_pixbuf_get_rowstride (pixbuf); + int n_channels = gdk_pixbuf_get_n_channels (pixbuf); + int cairo_stride; + guchar *cairo_pixels; + cairo_format_t format; + cairo_surface_t *surface; + static const cairo_user_data_key_t key; + int j; + + if (n_channels == 3) + format = CAIRO_FORMAT_RGB24; + else + format = CAIRO_FORMAT_ARGB32; + + cairo_stride = cairo_format_stride_for_width (format, width); + cairo_pixels = g_malloc (height * cairo_stride); + surface = cairo_image_surface_create_for_data ((unsigned char *)cairo_pixels, + format, + width, height, cairo_stride); + cairo_surface_set_user_data (surface, &key, + cairo_pixels, (cairo_destroy_func_t)g_free); + + for (j = height; j; j--) + { + guchar *p = gdk_pixels; + guchar *q = cairo_pixels; + + if (n_channels == 3) + { + guchar *end = p + 3 * width; + + while (p < end) + { +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + q[0] = p[2]; + q[1] = p[1]; + q[2] = p[0]; +#else + q[1] = p[0]; + q[2] = p[1]; + q[3] = p[2]; +#endif + p += 3; + q += 4; + } + } + else + { + guchar *end = p + 4 * width; + guint t1,t2,t3; + +#define MULT(d,c,a,t) G_STMT_START { t = c * a + 0x7f; d = ((t >> 8) + t) >> 8; } G_STMT_END + + while (p < end) + { +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + MULT(q[0], p[2], p[3], t1); + MULT(q[1], p[1], p[3], t2); + MULT(q[2], p[0], p[3], t3); + q[3] = p[3]; +#else + q[0] = p[3]; + MULT(q[1], p[0], p[3], t1); + MULT(q[2], p[1], p[3], t2); + MULT(q[3], p[2], p[3], t3); +#endif + + p += 4; + q += 4; + } + +#undef MULT + } + + gdk_pixels += gdk_rowstride; + cairo_pixels += cairo_stride; + } + + return surface; +} + +static void +create_surface (EomPrintPreview *preview) +{ + EomPrintPreviewPrivate *priv = preview->priv; + GdkPixbuf *pixbuf; + + if (priv->surface) { + cairo_surface_destroy (priv->surface); + priv->surface = NULL; + } + + pixbuf = create_preview_buffer (preview); + if (pixbuf) { + priv->surface = create_surface_from_pixbuf (pixbuf); + g_object_unref (pixbuf); + } + priv->flag_create_surface = FALSE; +} + +static gboolean +create_surface_when_idle (EomPrintPreview *preview) +{ + create_surface (preview); + + return FALSE; +} + +static gboolean +button_press_event_cb (GtkWidget *widget, + GdkEventButton *event, + gpointer user_data) +{ + EomPrintPreview *preview = EOM_PRINT_PREVIEW (user_data); + + preview->priv->cursorx = event->x; + preview->priv->cursory = event->y; + + switch (event->button) { + case 1: + preview->priv->grabbed = press_inside_image_area (preview, event->x, event->y); + break; + } + + if (preview->priv->grabbed) { + gtk_widget_queue_draw (GTK_WIDGET (preview)); + } + + gtk_widget_grab_focus (preview->priv->area); + + return FALSE; +} + +static gboolean +button_release_event_cb (GtkWidget *widget, + GdkEventButton *event, + gpointer user_data) +{ + EomPrintPreview *preview = EOM_PRINT_PREVIEW (user_data); + + switch (event->button) { + case 1: + preview->priv->grabbed = FALSE; + preview->priv->r_dx = 0; + preview->priv->r_dy = 0; + gtk_widget_queue_draw (GTK_WIDGET (preview)); + + } + return FALSE; +} + +static gboolean +key_press_event_cb (GtkWidget *widget, + GdkEventKey *event, + gpointer user_data) +{ + EomPrintPreviewPrivate *priv; + gfloat delta, align; + gboolean stop_emission = FALSE; + const gchar *property; + + priv = EOM_PRINT_PREVIEW (user_data)->priv; + + delta = 0; + + switch (event->keyval) { + case GDK_Left: + property = "image-x-align"; + delta = -0.01; + break; + case GDK_Right: + property = "image-x-align"; + delta = 0.01; + break; + case GDK_Up: + property = "image-y-align"; + delta = -0.01; + break; + case GDK_Down: + property = "image-y-align"; + delta = 0.01; + break; + } + + if (delta != 0) { + g_object_get (G_OBJECT (user_data), + property, &align, + NULL); + + align += delta; + align = CLAMP (align, 0, 1); + g_object_set (G_OBJECT (user_data), + property, align, + NULL); + + stop_emission = TRUE; + g_signal_emit (G_OBJECT (user_data), + preview_signals + [SIGNAL_IMAGE_MOVED], 0); + } + + return stop_emission; +} + +static gboolean +motion_notify_event_cb (GtkWidget *widget, + GdkEventMotion *event, + gpointer user_data) +{ + EomPrintPreviewPrivate *priv = EOM_PRINT_PREVIEW (user_data)->priv; + gdouble dx, dy; + GtkAllocation allocation; + + if (priv->grabbed) { + dx = event->x - priv->cursorx; + dy = event->y - priv->cursory; + + gtk_widget_get_allocation (widget, &allocation); + + /* Make sure the image stays inside the margins */ + + priv->image_x_align += (dx + priv->r_dx)/(allocation.width - priv->r_width - priv->l_rmargin - priv->r_rmargin); + if (priv->image_x_align < 0. || priv->image_x_align > 1.) { + priv->image_x_align = CLAMP (priv->image_x_align, 0., 1.); + priv->r_dx += dx; + } + else + priv->r_dx = 0; + + priv->image_y_align += (dy + priv->r_dy)/(allocation.height - priv->r_height - priv->t_rmargin - priv->b_rmargin); + if (priv->image_y_align < 0. || priv->image_y_align > 1.) { + priv->image_y_align = CLAMP (priv->image_y_align, 0., 1.); + priv->r_dy += dy; + } else + priv->r_dy = 0; + + /* we do this to correctly change the property values */ + g_object_set (EOM_PRINT_PREVIEW (user_data), + "image-x-align", priv->image_x_align, + "image-y-align", priv->image_y_align, + NULL); + + priv->cursorx = event->x; + priv->cursory = event->y; + + g_signal_emit (G_OBJECT (user_data), + preview_signals + [SIGNAL_IMAGE_MOVED], 0); + } else { + if (press_inside_image_area (EOM_PRINT_PREVIEW (user_data), event->x, event->y)) { + GdkCursor *cursor; + cursor = gdk_cursor_new_for_display (gtk_widget_get_display (widget), + GDK_FLEUR); + gdk_window_set_cursor (gtk_widget_get_window (widget), + cursor); + gdk_cursor_unref (cursor); + } else { + gdk_window_set_cursor (gtk_widget_get_window (widget), + NULL); + } + } + return FALSE; +} + +static void +size_allocate_cb (GtkWidget *widget, + GtkAllocation *allocation, + gpointer user_data) +{ + EomPrintPreview *preview; + + preview = EOM_PRINT_PREVIEW (user_data); + update_relative_sizes (preview); + + preview->priv->flag_create_surface = TRUE; + + if (preview->priv->image_scaled) { + g_object_unref (preview->priv->image_scaled); + preview->priv->image_scaled = NULL; + } + + g_idle_add ((GSourceFunc) create_surface_when_idle, preview); +} + +static void +eom_print_preview_draw (EomPrintPreview *preview, cairo_t *cr) +{ + EomPrintPreviewPrivate *priv; + GtkWidget *area; + GtkAllocation allocation; + gint x0, y0; + GtkStyle *style; + gboolean has_focus; + + priv = preview->priv; + area = priv->area; + + has_focus = gtk_widget_has_focus (area); + + style = gtk_widget_get_style (area); + + gtk_widget_get_allocation (area, &allocation); + + /* draw the page */ + gdk_cairo_set_source_color (cr, &style->white); + cairo_rectangle (cr, 0, 0, allocation.width, allocation.height); + cairo_fill (cr); + + /* draw the page margins */ + gdk_cairo_set_source_color (cr, &style->black); + cairo_set_line_width (cr, 0.1); + cairo_rectangle (cr, + priv->l_rmargin, priv->t_rmargin, + allocation.width - priv->l_rmargin - priv->r_rmargin, + allocation.height - priv->t_rmargin - priv->b_rmargin); + cairo_stroke (cr); + + get_current_image_coordinates (preview, &x0, &y0); + + if (priv->flag_create_surface) { + create_surface (preview); + } + + if (priv->surface) { + cairo_set_source_surface (cr, priv->surface, x0, y0); + cairo_paint (cr); + } else if (priv->image_scaled) { + /* just in the remote case we don't have the surface */ + + /* adjust (x0, y0) to the new scale */ + gdouble scale = priv->i_scale * priv->p_scale * + gdk_pixbuf_get_width (priv->image) / gdk_pixbuf_get_width (priv->image_scaled); + x0 /= scale; + y0 /= scale; + + cairo_scale (cr, scale, scale); + gdk_cairo_set_source_pixbuf (cr, priv->image_scaled, x0, y0); + cairo_paint (cr); + } else if (priv->image) { + /* just in the remote case we don't have the surface */ + + /* adjust (x0, y0) to the new scale */ + x0 /= priv->i_scale * priv->p_scale; + y0 /= priv->i_scale * priv->p_scale; + + cairo_scale (cr, priv->i_scale*priv->p_scale, priv->i_scale*priv->p_scale); + gdk_cairo_set_source_pixbuf (cr, priv->image, x0, y0); + cairo_paint (cr); + } + + if (has_focus) { + gtk_paint_focus (style, gtk_widget_get_window (area), + GTK_STATE_NORMAL, NULL, NULL, NULL, + 0, 0, allocation.width, allocation.height); + } +} + +static void +update_relative_sizes (EomPrintPreview *preview) +{ + EomPrintPreviewPrivate *priv; + GtkAllocation allocation; + gint i_width, i_height; + + priv = preview->priv; + + if (priv->image != NULL) { + i_width = gdk_pixbuf_get_width (priv->image); + i_height = gdk_pixbuf_get_height (priv->image); + } else { + i_width = i_height = 0; + } + + gtk_widget_get_allocation (priv->area, &allocation); + + priv->p_scale = (gfloat) allocation.width / (priv->p_width * 72.0); + + priv->r_width = (gint) i_width * priv->i_scale * priv->p_scale; + priv->r_height = (gint) i_height * priv->i_scale * priv->p_scale; + + priv->l_rmargin = (gint) (72. * priv->l_margin * priv->p_scale); + priv->r_rmargin = (gint) (72. * priv->r_margin * priv->p_scale); + priv->t_rmargin = (gint) (72. * priv->t_margin * priv->p_scale); + priv->b_rmargin = (gint) (72. * priv->b_margin * priv->p_scale); +} + +/** + * eom_print_preview_set_page_margins: + * @preview: a #EomPrintPreview + * @l_margin: Left margin. + * @r_margin: Right margin. + * @t_margin: Top margin. + * @b_margin: Bottom margin. + * + * Manually set the margins, in inches. + **/ +void +eom_print_preview_set_page_margins (EomPrintPreview *preview, + gfloat l_margin, + gfloat r_margin, + gfloat t_margin, + gfloat b_margin) +{ + g_return_if_fail (EOM_IS_PRINT_PREVIEW (preview)); + + g_object_set (G_OBJECT(preview), + "page-left-margin", l_margin, + "page-right-margin", r_margin, + "page-top-margin", t_margin, + "page-bottom-margin", r_margin, + NULL); +} + +/** + * eom_print_preview_set_from_page_setup: + * @preview: a #EomPrintPreview + * @setup: a #GtkPageSetup to set the properties from + * + * Sets up the page properties from a #GtkPageSetup. Useful when using the + * widget with the GtkPrint API. + **/ +void +eom_print_preview_set_from_page_setup (EomPrintPreview *preview, + GtkPageSetup *setup) +{ + g_return_if_fail (EOM_IS_PRINT_PREVIEW (preview)); + g_return_if_fail (GTK_IS_PAGE_SETUP (setup)); + + g_object_set (G_OBJECT (preview), + "page-left-margin", gtk_page_setup_get_left_margin (setup, GTK_UNIT_INCH), + "page-right-margin", gtk_page_setup_get_right_margin (setup, GTK_UNIT_INCH), + "page-top-margin", gtk_page_setup_get_top_margin (setup, GTK_UNIT_INCH), + "page-bottom-margin", gtk_page_setup_get_bottom_margin (setup, GTK_UNIT_INCH), + "paper-width", gtk_page_setup_get_paper_width (setup, GTK_UNIT_INCH), + "paper-height", gtk_page_setup_get_paper_height (setup, GTK_UNIT_INCH), + NULL); + +} + +/** + * eom_print_preview_get_image_position: + * @preview: a #EomPrintPreview + * @x: a pointer to a #gdouble, or %NULL to ignore it + * @y: a pointer to a #gdouble, or %NULL to ignore it + * + * Gets current image position in inches, relative to the margins. A + * (0, 0) position is the intersection between the left and top margins. + **/ +void +eom_print_preview_get_image_position (EomPrintPreview *preview, + gdouble *x, + gdouble *y) +{ + EomPrintPreviewPrivate *priv; + gdouble width, height; + + g_return_if_fail (EOM_IS_PRINT_PREVIEW (preview)); + + priv = preview->priv; + + if (x != NULL) { + width = gdk_pixbuf_get_width (priv->image) * priv->i_scale / 72.; + *x = priv->image_x_align * (priv->p_width - priv->l_margin - priv->r_margin - width); + } + if (y != NULL) { + height = gdk_pixbuf_get_height (priv->image) * priv->i_scale / 72.; + *y = priv->image_y_align * (priv->p_height - priv->t_margin - priv->b_margin - height); + } +} + +/** + * eom_print_preview_set_image_position: + * @preview: a #EomPrintPreview + * @x: The X coordinate, in inches, or -1 to ignore it. + * @y: The Y coordinate, in inches, or -1 to ignore it. + * + * Sets the image position. You can pass -1 to one of the coordinates if you + * only want to set the other. + **/ +void +eom_print_preview_set_image_position (EomPrintPreview *preview, + gdouble x, + gdouble y) +{ + EomPrintPreviewPrivate *priv; + gfloat x_align, y_align; + gdouble width, height; + + g_return_if_fail (EOM_IS_PRINT_PREVIEW (preview)); + + priv = preview->priv; + + if (x != -1) { + width = gdk_pixbuf_get_width (priv->image) * priv->i_scale / 72.; + x_align = CLAMP (x/(priv->p_width - priv->l_margin - priv->r_margin - width), 0, 1); + g_object_set (preview, "image-x-align", x_align, NULL); + } + + if (y != -1) { + height = gdk_pixbuf_get_height (priv->image) * priv->i_scale / 72.; + y_align = CLAMP (y/(priv->p_height - priv->t_margin - priv->b_margin - height), 0, 1); + g_object_set (preview, "image-y-align", y_align, NULL); + } +} + +/** + * eom_print_preview_set_scale: + * @preview: a #EomPrintPreview + * @scale: a scale value, between 0 and 1. + * + * Sets the scale for the image. + **/ +void +eom_print_preview_set_scale (EomPrintPreview *preview, + gfloat scale) +{ + g_return_if_fail (EOM_IS_PRINT_PREVIEW (preview)); + + g_object_set (preview, + "image-scale", scale, + NULL); +} diff --git a/src/eom-print-preview.h b/src/eom-print-preview.h new file mode 100644 index 0000000..e245146 --- /dev/null +++ b/src/eom-print-preview.h @@ -0,0 +1,84 @@ +/* Eye of MATE -- Print Preview Widget + * + * Copyright (C) 2006-2007 The Free Software Foundation + * + * Author: Claudio Saavedra <[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 _EOM_PRINT_PREVIEW_H_ +#define _EOM_PRINT_PREVIEW_H_ + +G_BEGIN_DECLS + +typedef struct _EomPrintPreview EomPrintPreview; +typedef struct _EomPrintPreviewClass EomPrintPreviewClass; +typedef struct _EomPrintPreviewPrivate EomPrintPreviewPrivate; + +#define EOM_TYPE_PRINT_PREVIEW (eom_print_preview_get_type ()) +#define EOM_PRINT_PREVIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EOM_TYPE_PRINT_PREVIEW, EomPrintPreview)) +#define EOM_PRINT_PREVIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EOM_TYPE_PRINT_PREVIEW, EomPrintPreviewClass)) +#define EOM_IS_PRINT_PREVIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EOM_TYPE_PRINT_PREVIEW)) +#define EOM_IS_PRINT_PREVIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EOM_TYPE_PRINT_PREVIEW)) + +struct _EomPrintPreview { + GtkAspectFrame aspect_frame; + + EomPrintPreviewPrivate *priv; +}; + +struct _EomPrintPreviewClass { + GtkAspectFrameClass parent_class; + +}; + +G_GNUC_INTERNAL +GType eom_print_preview_get_type (void) G_GNUC_CONST; + +G_GNUC_INTERNAL +GtkWidget *eom_print_preview_new (void); + +G_GNUC_INTERNAL +GtkWidget *eom_print_preview_new_with_pixbuf (GdkPixbuf *pixbuf); + +G_GNUC_INTERNAL +void eom_print_preview_set_page_margins (EomPrintPreview *preview, + gfloat l_margin, + gfloat r_margin, + gfloat t_margin, + gfloat b_margin); + +G_GNUC_INTERNAL +void eom_print_preview_set_from_page_setup (EomPrintPreview *preview, + GtkPageSetup *setup); + +G_GNUC_INTERNAL +void eom_print_preview_get_image_position (EomPrintPreview *preview, + gdouble *x, + gdouble *y); + +G_GNUC_INTERNAL +void eom_print_preview_set_image_position (EomPrintPreview *preview, + gdouble x, + gdouble y); + +G_GNUC_INTERNAL +void eom_print_preview_set_scale (EomPrintPreview *preview, + gfloat scale); + +G_END_DECLS + +#endif /* _EOM_PRINT_PREVIEW_H_ */ diff --git a/src/eom-print.c b/src/eom-print.c new file mode 100644 index 0000000..17c0ed9 --- /dev/null +++ b/src/eom-print.c @@ -0,0 +1,369 @@ +/* Eye of Mate - Print Operations + * + * Copyright (C) 2005-2008 The Free Software Foundation + * + * Author: Claudio Saavedra <[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 "config.h" + +#include <gtk/gtk.h> +#include <glib/gi18n.h> +#include "eom-image.h" +#include "eom-print.h" +#include "eom-print-image-setup.h" +#include "eom-util.h" +#include "eom-debug.h" + +#ifdef HAVE_RSVG +#include <librsvg/rsvg-cairo.h> +#endif + +#define EOM_PRINT_SETTINGS_FILE "eom-print-settings.ini" +#define EOM_PAGE_SETUP_GROUP "Page Setup" +#define EOM_PRINT_SETTINGS_GROUP "Print Settings" + +typedef struct { + EomImage *image; + gdouble left_margin; + gdouble top_margin; + gdouble scale_factor; + GtkUnit unit; +} EomPrintData; + +static void +eom_print_draw_page (GtkPrintOperation *operation, + GtkPrintContext *context, + gint page_nr, + gpointer user_data) +{ + cairo_t *cr; + gdouble dpi_x, dpi_y; + gdouble x0, y0; + gdouble scale_factor; + gdouble p_width, p_height; + gint width, height; + EomPrintData *data; + GtkPageSetup *page_setup; + + eom_debug (DEBUG_PRINTING); + + data = (EomPrintData *) user_data; + + scale_factor = data->scale_factor/100; + + dpi_x = gtk_print_context_get_dpi_x (context); + dpi_y = gtk_print_context_get_dpi_y (context); + + switch (data->unit) { + case GTK_UNIT_INCH: + x0 = data->left_margin * dpi_x; + y0 = data->top_margin * dpi_y; + break; + case GTK_UNIT_MM: + x0 = data->left_margin * dpi_x/25.4; + y0 = data->top_margin * dpi_y/25.4; + break; + default: + g_assert_not_reached (); + } + + cr = gtk_print_context_get_cairo_context (context); + + cairo_translate (cr, x0, y0); + + page_setup = gtk_print_context_get_page_setup (context); + p_width = gtk_page_setup_get_page_width (page_setup, GTK_UNIT_POINTS); + p_height = gtk_page_setup_get_page_height (page_setup, GTK_UNIT_POINTS); + + eom_image_get_size (data->image, &width, &height); + + /* this is both a workaround for a bug in cairo's PDF backend, and + a way to ensure we are not printing outside the page margins */ + cairo_rectangle (cr, 0, 0, MIN (width*scale_factor, p_width), MIN (height*scale_factor, p_height)); + cairo_clip (cr); + + cairo_scale (cr, scale_factor, scale_factor); + +#ifdef HAVE_RSVG + if (eom_image_is_svg (data->image)) + { + RsvgHandle *svg = eom_image_get_svg (data->image); + + rsvg_handle_render_cairo (svg, cr); + } else +#endif + { + GdkPixbuf *pixbuf; + + pixbuf = eom_image_get_pixbuf (data->image); + gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0); + cairo_paint (cr); + g_object_unref (pixbuf); + } +} + +static GObject * +eom_print_create_custom_widget (GtkPrintOperation *operation, + gpointer user_data) +{ + GtkPageSetup *page_setup; + EomPrintData *data; + + eom_debug (DEBUG_PRINTING); + + data = (EomPrintData *)user_data; + + page_setup = gtk_print_operation_get_default_page_setup (operation); + + if (page_setup == NULL) + page_setup = gtk_page_setup_new (); + + return G_OBJECT (eom_print_image_setup_new (data->image, page_setup)); +} + +static void +eom_print_custom_widget_apply (GtkPrintOperation *operation, + GtkWidget *widget, + gpointer user_data) +{ + EomPrintData *data; + gdouble left_margin, top_margin, scale_factor; + GtkUnit unit; + + eom_debug (DEBUG_PRINTING); + + data = (EomPrintData *)user_data; + + eom_print_image_setup_get_options (EOM_PRINT_IMAGE_SETUP (widget), + &left_margin, &top_margin, + &scale_factor, &unit); + + data->left_margin = left_margin; + data->top_margin = top_margin; + data->scale_factor = scale_factor; + data->unit = unit; +} + +static void +eom_print_end_print (GtkPrintOperation *operation, + GtkPrintContext *context, + gpointer user_data) +{ + EomPrintData *data = (EomPrintData*) user_data; + + eom_debug (DEBUG_PRINTING); + + g_object_unref (data->image); + g_slice_free (EomPrintData, data); +} + +GtkPrintOperation * +eom_print_operation_new (EomImage *image, + GtkPrintSettings *print_settings, + GtkPageSetup *page_setup) +{ + GtkPrintOperation *print; + EomPrintData *data; + + eom_debug (DEBUG_PRINTING); + + print = gtk_print_operation_new (); + + data = g_slice_new0 (EomPrintData); + + data->left_margin = 0; + data->top_margin = 0; + data->scale_factor = 100; + data->image = g_object_ref (image); + data->unit = GTK_UNIT_INCH; + + gtk_print_operation_set_print_settings (print, print_settings); + gtk_print_operation_set_default_page_setup (print, + page_setup); + gtk_print_operation_set_n_pages (print, 1); + gtk_print_operation_set_job_name (print, + eom_image_get_caption (image)); + gtk_print_operation_set_embed_page_setup (print, TRUE); + + g_signal_connect (print, "draw_page", + G_CALLBACK (eom_print_draw_page), + data); + g_signal_connect (print, "create-custom-widget", + G_CALLBACK (eom_print_create_custom_widget), + data); + g_signal_connect (print, "custom-widget-apply", + G_CALLBACK (eom_print_custom_widget_apply), + data); + g_signal_connect (print, "end-print", + G_CALLBACK (eom_print_end_print), + data); + g_signal_connect (print, "update-custom-widget", + G_CALLBACK (eom_print_image_setup_update), + data); + + gtk_print_operation_set_custom_tab_label (print, _("Image Settings")); + + return print; +} + +static GKeyFile * +eom_print_get_key_file (void) +{ + GKeyFile *key_file; + GError *error = NULL; + gchar *filename; + GFile *file; + const gchar *dot_dir = eom_util_dot_dir (); + + filename = g_build_filename (dot_dir, EOM_PRINT_SETTINGS_FILE, NULL); + + file = g_file_new_for_path (filename); + key_file = g_key_file_new (); + + if (g_file_query_exists (file, NULL)) { + g_key_file_load_from_file (key_file, filename, + G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS, + &error); + if (error) { + g_warning ("Error loading print settings file: %s", error->message); + g_error_free (error); + g_object_unref (file); + g_free (filename); + g_key_file_free (key_file); + return NULL; + } + } + + g_object_unref (file); + g_free (filename); + + return key_file; +} + +GtkPageSetup * +eom_print_get_page_setup (void) +{ + GtkPageSetup *page_setup; + GKeyFile *key_file; + GError *error = NULL; + + key_file = eom_print_get_key_file (); + + if (key_file && g_key_file_has_group (key_file, EOM_PAGE_SETUP_GROUP)) { + page_setup = gtk_page_setup_new_from_key_file (key_file, EOM_PAGE_SETUP_GROUP, &error); + } else { + page_setup = gtk_page_setup_new (); + } + + if (error) { + page_setup = gtk_page_setup_new (); + + g_warning ("Error loading print settings file: %s", error->message); + g_error_free (error); + } + + if (key_file) + g_key_file_free (key_file); + + return page_setup; +} + +static void +eom_print_save_key_file (GKeyFile *key_file) +{ + gchar *filename; + gchar *data; + GError *error = NULL; + const gchar *dot_dir = eom_util_dot_dir (); + + filename = g_build_filename (dot_dir, EOM_PRINT_SETTINGS_FILE, NULL); + + data = g_key_file_to_data (key_file, NULL, NULL); + + g_file_set_contents (filename, data, -1, &error); + + if (error) { + g_warning ("Error saving print settings file: %s", error->message); + g_error_free (error); + } + + g_free (filename); + g_free (data); +} + +void +eom_print_set_page_setup (GtkPageSetup *page_setup) +{ + GKeyFile *key_file; + + key_file = eom_print_get_key_file (); + + if (key_file == NULL) { + key_file = g_key_file_new (); + } + + gtk_page_setup_to_key_file (page_setup, key_file, EOM_PAGE_SETUP_GROUP); + eom_print_save_key_file (key_file); + + g_key_file_free (key_file); +} + +GtkPrintSettings * +eom_print_get_print_settings (void) +{ + GtkPrintSettings *print_settings; + GError *error = NULL; + GKeyFile *key_file; + + key_file = eom_print_get_key_file (); + + if (key_file && g_key_file_has_group (key_file, EOM_PRINT_SETTINGS_GROUP)) { + print_settings = gtk_print_settings_new_from_key_file (key_file, EOM_PRINT_SETTINGS_GROUP, &error); + } else { + print_settings = gtk_print_settings_new (); + } + + if (error) { + print_settings = gtk_print_settings_new (); + + g_warning ("Error loading print settings file: %s", error->message); + g_error_free (error); + } + + if (key_file) + g_key_file_free (key_file); + + return print_settings; +} + +void +eom_print_set_print_settings (GtkPrintSettings *print_settings) +{ + GKeyFile *key_file; + + key_file = eom_print_get_key_file (); + + if (key_file == NULL) { + key_file = g_key_file_new (); + } + + gtk_print_settings_to_key_file (print_settings, key_file, EOM_PRINT_SETTINGS_GROUP); + eom_print_save_key_file (key_file); + + g_key_file_free (key_file); +} diff --git a/src/eom-print.h b/src/eom-print.h new file mode 100644 index 0000000..ab3e857 --- /dev/null +++ b/src/eom-print.h @@ -0,0 +1,49 @@ +/* Eye of Mate - Print Operations + * + * Copyright (C) 2005-2008 The Free Software Foundation + * + * Author: Claudio Saavedra <[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 __EOM_PRINT_H__ +#define __EOM_PRINT_H__ + +#include "eom-image.h" +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +G_GNUC_INTERNAL +GtkPrintOperation* eom_print_operation_new (EomImage *image, + GtkPrintSettings *print_settings, + GtkPageSetup *page_setup); + +G_GNUC_INTERNAL +GtkPageSetup* eom_print_get_page_setup (void); + +G_GNUC_INTERNAL +void eom_print_set_page_setup (GtkPageSetup *page_setup); + +G_GNUC_INTERNAL +GtkPrintSettings * eom_print_get_print_settings (void); + +G_GNUC_INTERNAL +void eom_print_set_print_settings (GtkPrintSettings *print_settings); + +G_END_DECLS + +#endif /* __EOM_PRINT_H__ */ diff --git a/src/eom-properties-dialog.c b/src/eom-properties-dialog.c new file mode 100644 index 0000000..21a346f --- /dev/null +++ b/src/eom-properties-dialog.c @@ -0,0 +1,834 @@ +/* Eye Of Mate - Image Properties Dialog + * + * Copyright (C) 2006 The Free Software Foundation + * + * Author: Lucas Rocha <[email protected]> + * Hubert Figuiere <[email protected]> (XMP support) + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "eom-properties-dialog.h" +#include "eom-image.h" +#include "eom-util.h" +#include "eom-thumb-view.h" + +#if HAVE_EXIF +#include "eom-exif-util.h" +#endif + +#include <glib.h> +#include <glib/gi18n.h> +#include <glib-object.h> +#include <gio/gio.h> +#include <gtk/gtk.h> + +#if HAVE_EXEMPI +#include <exempi/xmp.h> +#include <exempi/xmpconsts.h> +#endif +#if HAVE_EXIF || HAVE_EXEMPI +#define HAVE_METADATA 1 +#endif + +#if HAVE_METADATA +#include "eom-exif-details.h" +#endif + +#define EOM_PROPERTIES_DIALOG_GET_PRIVATE(object) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((object), EOM_TYPE_PROPERTIES_DIALOG, EomPropertiesDialogPrivate)) + +G_DEFINE_TYPE (EomPropertiesDialog, eom_properties_dialog, EOM_TYPE_DIALOG); + +enum { + PROP_0, + PROP_THUMBVIEW, + PROP_NETBOOK_MODE +}; + +struct _EomPropertiesDialogPrivate { + EomThumbView *thumbview; + + gboolean update_page; + EomPropertiesDialogPage current_page; + + GtkWidget *notebook; + GtkWidget *close_button; + GtkWidget *next_button; + GtkWidget *previous_button; + + GtkWidget *general_box; + GtkWidget *thumbnail_image; + GtkWidget *name_label; + GtkWidget *width_label; + GtkWidget *height_label; + GtkWidget *type_label; + GtkWidget *bytes_label; + GtkWidget *location_label; + GtkWidget *created_label; + GtkWidget *modified_label; +#ifdef HAVE_EXIF + GtkWidget *exif_aperture_label; + GtkWidget *exif_exposure_label; + GtkWidget *exif_focal_label; + GtkWidget *exif_flash_label; + GtkWidget *exif_iso_label; + GtkWidget *exif_metering_label; + GtkWidget *exif_model_label; + GtkWidget *exif_date_label; +#endif +#ifdef HAVE_EXEMPI + GtkWidget *xmp_location_label; + GtkWidget *xmp_description_label; + GtkWidget *xmp_keywords_label; + GtkWidget *xmp_creator_label; + GtkWidget *xmp_rights_label; +#endif +#if HAVE_METADATA + GtkWidget *exif_box; + GtkWidget *exif_details_expander; + GtkWidget *exif_details; + GtkWidget *metadata_details_box; + GtkWidget *metadata_details_sw; +#endif + + gboolean netbook_mode; +}; + +static void +pd_update_general_tab (EomPropertiesDialog *prop_dlg, + EomImage *image) +{ + gchar *bytes_str, *dir_str, *uri_str; + gchar *width_str, *height_str; + GFile *file; + GFileInfo *file_info; + const char *mime_str; + char *type_str; + gint width, height; + goffset bytes; + + g_object_set (G_OBJECT (prop_dlg->priv->thumbnail_image), + "pixbuf", eom_image_get_thumbnail (image), + NULL); + + gtk_label_set_text (GTK_LABEL (prop_dlg->priv->name_label), + eom_image_get_caption (image)); + + eom_image_get_size (image, &width, &height); + + width_str = g_strdup_printf ("%d %s", width, + ngettext ("pixel", "pixels", width)); + height_str = g_strdup_printf ("%d %s", height, + ngettext ("pixel", "pixels", height)); + + gtk_label_set_text (GTK_LABEL (prop_dlg->priv->width_label), width_str); + + gtk_label_set_text (GTK_LABEL (prop_dlg->priv->height_label), + height_str); + + g_free (height_str); + g_free (width_str); + + file = eom_image_get_file (image); + file_info = g_file_query_info (file, + G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, + 0, NULL, NULL); + if (file_info == NULL) { + type_str = g_strdup (_("Unknown")); + } else { + mime_str = g_file_info_get_content_type (file_info); + type_str = g_content_type_get_description (mime_str); + g_object_unref (file_info); + } + + gtk_label_set_text (GTK_LABEL (prop_dlg->priv->type_label), type_str); + + bytes = eom_image_get_bytes (image); + bytes_str = g_format_size_for_display (bytes); + + gtk_label_set_text (GTK_LABEL (prop_dlg->priv->bytes_label), bytes_str); + + uri_str = eom_image_get_uri_for_display (image); + dir_str = g_path_get_dirname (uri_str); + gtk_label_set_text (GTK_LABEL (prop_dlg->priv->location_label), + dir_str); + + g_free (type_str); + g_free (bytes_str); + g_free (dir_str); + g_free (uri_str); +} + +#if HAVE_EXIF +static void +eom_exif_set_label (GtkWidget *w, ExifData *exif_data, gint tag_id) +{ + gchar exif_buffer[512]; + const gchar *buf_ptr; + gchar *label_text = NULL; + + if (exif_data) { + buf_ptr = eom_exif_util_get_value (exif_data, tag_id, + exif_buffer, 512); + + if (tag_id == EXIF_TAG_DATE_TIME_ORIGINAL && buf_ptr) + label_text = eom_exif_util_format_date (buf_ptr); + else + label_text = eom_util_make_valid_utf8 (buf_ptr); + } + + gtk_label_set_text (GTK_LABEL (w), label_text); + g_free (label_text); +} + +static void +eom_exif_set_focal_length_label (GtkWidget *w, ExifData *exif_data) +{ + ExifEntry *entry = NULL, *entry35mm = NULL; + ExifByteOrder byte_order; + gfloat f_val = 0.0; + gchar *fl_text = NULL,*fl35_text = NULL; + + /* If no ExifData is supplied the label will be + * cleared later as fl35_text is NULL. */ + if (exif_data != NULL) { + entry = exif_data_get_entry (exif_data, EXIF_TAG_FOCAL_LENGTH); + entry35mm = exif_data_get_entry (exif_data, + EXIF_TAG_FOCAL_LENGTH_IN_35MM_FILM); + byte_order = exif_data_get_byte_order (exif_data); + } + + if (entry && G_LIKELY (entry->format == EXIF_FORMAT_RATIONAL)) { + ExifRational value; + + /* Decode value by hand as libexif is not necessarily returning + * it in the format we want it to be. + */ + value = exif_get_rational (entry->data, byte_order); + /* Guard against div by zero */ + if (G_LIKELY(value.denominator != 0)) + f_val = (gfloat)value.numerator/ + (gfloat)value.denominator; + + /* TRANSLATORS: This is the actual focal length used when + the image was taken.*/ + fl_text = g_strdup_printf (_("%.1f (lens)"), f_val); + + } + if (entry35mm && G_LIKELY (entry35mm->format == EXIF_FORMAT_SHORT)) { + ExifShort s_val; + + s_val = exif_get_short (entry35mm->data, byte_order); + + /* Print as float to get a similar look as above. */ + /* TRANSLATORS: This is the equivalent focal length assuming + a 35mm film camera. */ + fl35_text = g_strdup_printf(_("%.1f (35mm film)"),(float)s_val); + } + + if (fl_text) { + if (fl35_text) { + gchar *merged_txt; + + merged_txt = g_strconcat (fl35_text,", ", fl_text, NULL); + gtk_label_set_text (GTK_LABEL (w), merged_txt); + g_free (merged_txt); + } else { + gtk_label_set_text (GTK_LABEL (w), fl_text); + } + } else { + /* This will also clear the label if no ExifData was supplied */ + gtk_label_set_text (GTK_LABEL (w), fl35_text); + } + + g_free (fl35_text); + g_free (fl_text); + +} +#endif + +#if HAVE_EXEMPI +static void +eom_xmp_set_label (XmpPtr xmp, + const char *ns, + const char *propname, + GtkWidget *w) +{ + uint32_t options; + + XmpStringPtr value = xmp_string_new (); + + if (xmp_get_property (xmp, ns, propname, value, &options)) { + if (XMP_IS_PROP_SIMPLE (options)) { + gtk_label_set_text (GTK_LABEL (w), xmp_string_cstr (value)); + } else if (XMP_IS_PROP_ARRAY (options)) { + XmpIteratorPtr iter = xmp_iterator_new (xmp, + ns, + propname, + XMP_ITER_JUSTLEAFNODES); + + GString *string = g_string_new (""); + + if (iter) { + gboolean first = TRUE; + + while (xmp_iterator_next (iter, NULL, NULL, value, &options) + && !XMP_IS_PROP_QUALIFIER (options)) { + + if (!first) { + g_string_append_printf(string, ", "); + } else { + first = FALSE; + } + + g_string_append_printf (string, + "%s", + xmp_string_cstr (value)); + } + + xmp_iterator_free (iter); + } + + gtk_label_set_text (GTK_LABEL (w), string->str); + g_string_free (string, TRUE); + } + } else { + /* Property was not found */ + /* Clear label so it won't show bogus data */ + gtk_label_set_text (GTK_LABEL (w), NULL); + } + + xmp_string_free (value); +} +#endif + +#if HAVE_METADATA +static void +pd_update_metadata_tab (EomPropertiesDialog *prop_dlg, + EomImage *image) +{ + EomPropertiesDialogPrivate *priv; + GtkNotebook *notebook; +#if HAVE_EXIF + ExifData *exif_data; +#endif +#if HAVE_EXEMPI + XmpPtr xmp_data; +#endif + + g_return_if_fail (EOM_IS_PROPERTIES_DIALOG (prop_dlg)); + + priv = prop_dlg->priv; + + notebook = GTK_NOTEBOOK (priv->notebook); + + if (TRUE +#if HAVE_EXIF + && !eom_image_has_data (image, EOM_IMAGE_DATA_EXIF) +#endif +#if HAVE_EXEMPI + && !eom_image_has_data (image, EOM_IMAGE_DATA_XMP) +#endif + ) { + if (gtk_notebook_get_current_page (notebook) == EOM_PROPERTIES_DIALOG_PAGE_EXIF) { + gtk_notebook_prev_page (notebook); + } else if (gtk_notebook_get_current_page (notebook) == EOM_PROPERTIES_DIALOG_PAGE_DETAILS) { + gtk_notebook_set_current_page (notebook, EOM_PROPERTIES_DIALOG_PAGE_GENERAL); + } + + if (gtk_widget_get_visible (priv->exif_box)) { + gtk_widget_hide_all (priv->exif_box); + } + if (gtk_widget_get_visible (priv->metadata_details_box)) { + gtk_widget_hide_all (priv->metadata_details_box); + } + + return; + } else { + if (!gtk_widget_get_visible (priv->exif_box)) + gtk_widget_show_all (priv->exif_box); + if (priv->netbook_mode && + !gtk_widget_get_visible (priv->metadata_details_box)) { + gtk_widget_show_all (priv->metadata_details_box); + gtk_widget_hide_all (priv->exif_details_expander); + } + } + +#if HAVE_EXIF + exif_data = (ExifData *) eom_image_get_exif_info (image); + + eom_exif_set_label (priv->exif_aperture_label, + exif_data, EXIF_TAG_FNUMBER); + + eom_exif_set_label (priv->exif_exposure_label, + exif_data, EXIF_TAG_EXPOSURE_TIME); + + eom_exif_set_focal_length_label (priv->exif_focal_label, exif_data); + + eom_exif_set_label (priv->exif_flash_label, + exif_data, EXIF_TAG_FLASH); + + eom_exif_set_label (priv->exif_iso_label, + exif_data, EXIF_TAG_ISO_SPEED_RATINGS); + + + eom_exif_set_label (priv->exif_metering_label, + exif_data, EXIF_TAG_METERING_MODE); + + eom_exif_set_label (priv->exif_model_label, + exif_data, EXIF_TAG_MODEL); + + eom_exif_set_label (priv->exif_date_label, + exif_data, EXIF_TAG_DATE_TIME_ORIGINAL); + + eom_exif_details_update (EOM_EXIF_DETAILS (priv->exif_details), + exif_data); + + /* exif_data_unref can handle NULL-values */ + exif_data_unref(exif_data); +#endif + +#if HAVE_EXEMPI + xmp_data = (XmpPtr) eom_image_get_xmp_info (image); + + if (xmp_data != NULL) { + eom_xmp_set_label (xmp_data, + NS_IPTC4XMP, + "Location", + priv->xmp_location_label); + + eom_xmp_set_label (xmp_data, + NS_DC, + "description", + priv->xmp_description_label); + + eom_xmp_set_label (xmp_data, + NS_DC, + "subject", + priv->xmp_keywords_label); + + eom_xmp_set_label (xmp_data, + NS_DC, + "creator", + priv->xmp_creator_label); + + eom_xmp_set_label (xmp_data, + NS_DC, + "rights", + priv->xmp_rights_label); + + eom_exif_details_xmp_update (EOM_EXIF_DETAILS (priv->exif_details), xmp_data); + + xmp_free (xmp_data); + } else { + /* Image has no XMP data */ + + /* Clear the labels so they won't display foreign data.*/ + + gtk_label_set_text (GTK_LABEL (priv->xmp_location_label), NULL); + gtk_label_set_text (GTK_LABEL (priv->xmp_description_label), + NULL); + gtk_label_set_text (GTK_LABEL (priv->xmp_keywords_label), NULL); + gtk_label_set_text (GTK_LABEL (priv->xmp_creator_label), NULL); + gtk_label_set_text (GTK_LABEL (priv->xmp_rights_label), NULL); + } +#endif +} + +static gboolean +pd_resize_dialog (gpointer user_data) +{ + gint width, height; + + gtk_window_get_size (GTK_WINDOW (user_data), + &width, + &height); + + gtk_window_resize (GTK_WINDOW (user_data), width, 1); + + return FALSE; +} + +static void +pd_exif_details_activated_cb (GtkExpander *expander, + GParamSpec *param_spec, + GtkWidget *dialog) +{ + gboolean expanded; + + expanded = gtk_expander_get_expanded (expander); + + /*FIXME: this is depending on the expander animation + * duration. Need to find a safer way for doing that. */ + if (!expanded) + g_timeout_add (150, pd_resize_dialog, dialog); +} +#endif + +static void +pd_close_button_clicked_cb (GtkButton *button, + gpointer user_data) +{ + eom_dialog_hide (EOM_DIALOG (user_data)); +} + +static gboolean +eom_properties_dialog_page_switch (GtkNotebook *notebook, + GtkNotebookPage *page, + gint page_index, + EomPropertiesDialog *prop_dlg) +{ + + if (prop_dlg->priv->update_page) + prop_dlg->priv->current_page = page_index; + + return TRUE; +} + +static gint +eom_properties_dialog_delete (GtkWidget *widget, + GdkEventAny *event, + gpointer user_data) +{ + g_return_val_if_fail (EOM_IS_PROPERTIES_DIALOG (user_data), FALSE); + + eom_dialog_hide (EOM_DIALOG (user_data)); + + return TRUE; +} + +void +eom_properties_dialog_set_netbook_mode (EomPropertiesDialog *dlg, + gboolean enable) +{ + EomPropertiesDialogPrivate *priv; + + g_return_if_fail (EOM_IS_PROPERTIES_DIALOG (dlg)); + + priv = dlg->priv; + + if (priv->netbook_mode == enable) + return; + + priv->netbook_mode = enable; + +#ifdef HAVE_METADATA + if (enable) { + gtk_widget_reparent (priv->metadata_details_sw, + priv->metadata_details_box); + // Only show details box if metadata is being displayed + if (gtk_widget_get_visible (priv->exif_box)) + gtk_widget_show_all (priv->metadata_details_box); + + gtk_widget_hide_all (priv->exif_details_expander); + } else { + gtk_widget_reparent (priv->metadata_details_sw, + priv->exif_details_expander); + gtk_widget_show_all (priv->exif_details_expander); + + if (gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook)) == EOM_PROPERTIES_DIALOG_PAGE_DETAILS) + gtk_notebook_prev_page (GTK_NOTEBOOK (priv->notebook)); + gtk_widget_hide_all (priv->metadata_details_box); + } +#endif +} + +static void +eom_properties_dialog_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + EomPropertiesDialog *prop_dlg = EOM_PROPERTIES_DIALOG (object); + + switch (prop_id) { + case PROP_THUMBVIEW: + prop_dlg->priv->thumbview = g_value_get_object (value); + break; + case PROP_NETBOOK_MODE: + eom_properties_dialog_set_netbook_mode (prop_dlg, + g_value_get_boolean (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, + pspec); + break; + } +} + +static void +eom_properties_dialog_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + EomPropertiesDialog *prop_dlg = EOM_PROPERTIES_DIALOG (object); + + switch (prop_id) { + case PROP_THUMBVIEW: + g_value_set_object (value, prop_dlg->priv->thumbview); + break; + case PROP_NETBOOK_MODE: + g_value_set_boolean (value, + prop_dlg->priv->netbook_mode); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, + pspec); + break; + } +} + +static void +eom_properties_dialog_dispose (GObject *object) +{ + EomPropertiesDialog *prop_dlg; + EomPropertiesDialogPrivate *priv; + + g_return_if_fail (object != NULL); + g_return_if_fail (EOM_IS_PROPERTIES_DIALOG (object)); + + prop_dlg = EOM_PROPERTIES_DIALOG (object); + priv = prop_dlg->priv; + + if (priv->thumbview) { + g_object_unref (priv->thumbview); + priv->thumbview = NULL; + } + + G_OBJECT_CLASS (eom_properties_dialog_parent_class)->dispose (object); +} + +static void +eom_properties_dialog_class_init (EomPropertiesDialogClass *class) +{ + GObjectClass *g_object_class = (GObjectClass *) class; + + g_object_class->dispose = eom_properties_dialog_dispose; + g_object_class->set_property = eom_properties_dialog_set_property; + g_object_class->get_property = eom_properties_dialog_get_property; + + g_object_class_install_property (g_object_class, + PROP_THUMBVIEW, + g_param_spec_object ("thumbview", + "Thumbview", + "Thumbview", + EOM_TYPE_THUMB_VIEW, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | + G_PARAM_STATIC_BLURB)); + g_object_class_install_property (g_object_class, PROP_NETBOOK_MODE, + g_param_spec_boolean ("netbook-mode", + "Netbook Mode", + "Netbook Mode", + FALSE, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + g_type_class_add_private (g_object_class, sizeof (EomPropertiesDialogPrivate)); +} + +static void +eom_properties_dialog_init (EomPropertiesDialog *prop_dlg) +{ + EomPropertiesDialogPrivate *priv; + GtkWidget *dlg; +#ifndef HAVE_EXEMPI + GtkWidget *xmp_box, *xmp_box_label; +#endif +#if HAVE_METADATA + GtkWidget *sw; +#endif + + prop_dlg->priv = EOM_PROPERTIES_DIALOG_GET_PRIVATE (prop_dlg); + + priv = prop_dlg->priv; + + priv->update_page = FALSE; + + eom_dialog_construct (EOM_DIALOG (prop_dlg), + "eom-image-properties-dialog.ui", + "eom_image_properties_dialog"); + + eom_dialog_get_controls (EOM_DIALOG (prop_dlg), + "eom_image_properties_dialog", &dlg, + "notebook", &priv->notebook, + "previous_button", &priv->previous_button, + "next_button", &priv->next_button, + "close_button", &priv->close_button, + "thumbnail_image", &priv->thumbnail_image, + "general_box", &priv->general_box, + "name_label", &priv->name_label, + "width_label", &priv->width_label, + "height_label", &priv->height_label, + "type_label", &priv->type_label, + "bytes_label", &priv->bytes_label, + "location_label", &priv->location_label, + "created_label", &priv->created_label, + "modified_label", &priv->modified_label, +#ifdef HAVE_EXIF + "exif_aperture_label", &priv->exif_aperture_label, + "exif_exposure_label", &priv->exif_exposure_label, + "exif_focal_label", &priv->exif_focal_label, + "exif_flash_label", &priv->exif_flash_label, + "exif_iso_label", &priv->exif_iso_label, + "exif_metering_label", &priv->exif_metering_label, + "exif_model_label", &priv->exif_model_label, + "exif_date_label", &priv->exif_date_label, +#endif +#ifdef HAVE_EXEMPI + "xmp_location_label", &priv->xmp_location_label, + "xmp_description_label", &priv->xmp_description_label, + "xmp_keywords_label", &priv->xmp_keywords_label, + "xmp_creator_label", &priv->xmp_creator_label, + "xmp_rights_label", &priv->xmp_rights_label, +#else + "xmp_box", &xmp_box, + "xmp_box_label", &xmp_box_label, +#endif +#ifdef HAVE_METADATA + "exif_box", &priv->exif_box, + "exif_details_expander", &priv->exif_details_expander, + "metadata_details_box", &priv->metadata_details_box, +#endif + NULL); + + g_signal_connect (dlg, + "delete-event", + G_CALLBACK (eom_properties_dialog_delete), + prop_dlg); + + g_signal_connect (priv->notebook, + "switch-page", + G_CALLBACK (eom_properties_dialog_page_switch), + prop_dlg); + + g_signal_connect (priv->close_button, + "clicked", + G_CALLBACK (pd_close_button_clicked_cb), + prop_dlg); + + gtk_widget_set_size_request (priv->thumbnail_image, 100, 100); + +#ifdef HAVE_METADATA + sw = gtk_scrolled_window_new (NULL, NULL); + + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), + GTK_SHADOW_IN); + + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + + priv->exif_details = eom_exif_details_new (); + gtk_widget_set_size_request (priv->exif_details, -1, 170); + gtk_container_set_border_width (GTK_CONTAINER (sw), 6); + + gtk_container_add (GTK_CONTAINER (sw), priv->exif_details); + gtk_widget_show_all (sw); + + priv->metadata_details_sw = sw; + + if (priv->netbook_mode) { + gtk_widget_hide_all (priv->exif_details_expander); + gtk_box_pack_start (GTK_BOX (priv->metadata_details_box), + sw, TRUE, TRUE, 6); + } else { + gtk_container_add (GTK_CONTAINER (priv->exif_details_expander), + sw); + } + + g_signal_connect_after (G_OBJECT (priv->exif_details_expander), + "notify::expanded", + G_CALLBACK (pd_exif_details_activated_cb), + dlg); + +#ifndef HAVE_EXEMPI + gtk_widget_hide_all (xmp_box); + gtk_widget_hide_all (xmp_box_label); +#endif + +#else + /* Remove pages from back to front. Otherwise the page index + * needs to be adjusted when deleting the next page. */ + gtk_notebook_remove_page (GTK_NOTEBOOK (priv->notebook), + EOM_PROPERTIES_DIALOG_PAGE_DETAILS); + gtk_notebook_remove_page (GTK_NOTEBOOK (priv->notebook), + EOM_PROPERTIES_DIALOG_PAGE_EXIF); +#endif +} + +GObject * +eom_properties_dialog_new (GtkWindow *parent, + EomThumbView *thumbview, + GtkAction *next_image_action, + GtkAction *previous_image_action) +{ + GObject *prop_dlg; + + g_return_val_if_fail (GTK_IS_WINDOW (parent), NULL); + g_return_val_if_fail (EOM_IS_THUMB_VIEW (thumbview), NULL); + g_return_val_if_fail (GTK_IS_ACTION (next_image_action), NULL); + g_return_val_if_fail (GTK_IS_ACTION (previous_image_action), NULL); + + prop_dlg = g_object_new (EOM_TYPE_PROPERTIES_DIALOG, + "parent-window", parent, + "thumbview", thumbview, + NULL); + + gtk_activatable_set_related_action (GTK_ACTIVATABLE (EOM_PROPERTIES_DIALOG (prop_dlg)->priv->next_button), next_image_action); + + gtk_activatable_set_related_action (GTK_ACTIVATABLE (EOM_PROPERTIES_DIALOG (prop_dlg)->priv->previous_button), previous_image_action); + + return prop_dlg; +} + +void +eom_properties_dialog_update (EomPropertiesDialog *prop_dlg, + EomImage *image) +{ + g_return_if_fail (EOM_IS_PROPERTIES_DIALOG (prop_dlg)); + + prop_dlg->priv->update_page = FALSE; + + pd_update_general_tab (prop_dlg, image); + +#ifdef HAVE_METADATA + pd_update_metadata_tab (prop_dlg, image); +#endif + gtk_notebook_set_current_page (GTK_NOTEBOOK (prop_dlg->priv->notebook), + prop_dlg->priv->current_page); + + prop_dlg->priv->update_page = TRUE; +} + +void +eom_properties_dialog_set_page (EomPropertiesDialog *prop_dlg, + EomPropertiesDialogPage page) +{ + g_return_if_fail (EOM_IS_PROPERTIES_DIALOG (prop_dlg)); + + prop_dlg->priv->current_page = page; + + gtk_notebook_set_current_page (GTK_NOTEBOOK (prop_dlg->priv->notebook), + page); +} diff --git a/src/eom-properties-dialog.h b/src/eom-properties-dialog.h new file mode 100644 index 0000000..c95a2dc --- /dev/null +++ b/src/eom-properties-dialog.h @@ -0,0 +1,80 @@ +/* Eye Of Mate - Image Properties Dialog + * + * Copyright (C) 2006 The Free Software Foundation + * + * Author: Lucas Rocha <[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 __EOM_PROPERTIES_DIALOG_H__ +#define __EOM_PROPERTIES_DIALOG_H__ + +#include "eom-dialog.h" +#include "eom-image.h" +#include "eom-thumb-view.h" + +#include <glib.h> +#include <glib-object.h> +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +typedef struct _EomPropertiesDialog EomPropertiesDialog; +typedef struct _EomPropertiesDialogClass EomPropertiesDialogClass; +typedef struct _EomPropertiesDialogPrivate EomPropertiesDialogPrivate; + +#define EOM_TYPE_PROPERTIES_DIALOG (eom_properties_dialog_get_type ()) +#define EOM_PROPERTIES_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), EOM_TYPE_PROPERTIES_DIALOG, EomPropertiesDialog)) +#define EOM_PROPERTIES_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), EOM_TYPE_PROPERTIES_DIALOG, EomPropertiesDialogClass)) +#define EOM_IS_PROPERTIES_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), EOM_TYPE_PROPERTIES_DIALOG)) +#define EOM_IS_PROPERTIES_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), EOM_TYPE_PROPERTIES_DIALOG)) +#define EOM_PROPERTIES_DIALOG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), EOM_TYPE_PROPERTIES_DIALOG, EomPropertiesDialogClass)) + +typedef enum { + EOM_PROPERTIES_DIALOG_PAGE_GENERAL = 0, + EOM_PROPERTIES_DIALOG_PAGE_EXIF, + EOM_PROPERTIES_DIALOG_PAGE_DETAILS, + EOM_PROPERTIES_DIALOG_N_PAGES +} EomPropertiesDialogPage; + +struct _EomPropertiesDialog { + EomDialog dialog; + + EomPropertiesDialogPrivate *priv; +}; + +struct _EomPropertiesDialogClass { + EomDialogClass parent_class; +}; + +GType eom_properties_dialog_get_type (void) G_GNUC_CONST; + +GObject *eom_properties_dialog_new (GtkWindow *parent, + EomThumbView *thumbview, + GtkAction *next_image_action, + GtkAction *previous_image_action); + +void eom_properties_dialog_update (EomPropertiesDialog *prop, + EomImage *image); + +void eom_properties_dialog_set_page (EomPropertiesDialog *prop, + EomPropertiesDialogPage page); + +void eom_properties_dialog_set_netbook_mode (EomPropertiesDialog *dlg, + gboolean enable); +G_END_DECLS + +#endif /* __EOM_PROPERTIES_DIALOG_H__ */ diff --git a/src/eom-python-module.c b/src/eom-python-module.c new file mode 100644 index 0000000..d0e05a5 --- /dev/null +++ b/src/eom-python-module.c @@ -0,0 +1,527 @@ +/* + * eom-python-module.c + * This file is part of eom + * + * Copyright (C) 2005 Raphael Slinckx + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +/* This needs to be included before any standard header + * see http://docs.python.org/c-api/intro.html#include-files */ +#include <Python.h> + +#include <pygobject.h> +#include <pygtk/pygtk.h> + +#include <signal.h> + +#include <gmodule.h> + +#include "eom-python-module.h" +#include "eom-python-plugin.h" +#include "eom-debug.h" + +#if PY_VERSION_HEX < 0x02050000 +typedef int Py_ssize_t; +#define PY_SSIZE_T_MAX INT_MAX +#define PY_SSIZE_T_MIN INT_MIN +#endif + +#define EOM_PYTHON_MODULE_GET_PRIVATE(object) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((object), EOM_TYPE_PYTHON_MODULE, EomPythonModulePrivate)) + +struct _EomPythonModulePrivate { + gchar *module; + gchar *path; + GType type; +}; + +enum { + PROP_0, + PROP_PATH, + PROP_MODULE +}; + +void pyeom_register_classes (PyObject *d); +void pyeom_add_constants (PyObject *module, const gchar *strip_prefix); +extern PyMethodDef pyeom_functions[]; + +static PyTypeObject *PyEomPlugin_Type; + +G_DEFINE_TYPE (EomPythonModule, eom_python_module, G_TYPE_TYPE_MODULE) + +static gboolean +eom_python_module_load (GTypeModule *gmodule) +{ + EomPythonModulePrivate *priv = EOM_PYTHON_MODULE_GET_PRIVATE (gmodule); + PyObject *main_module, *main_locals, *locals, *key, *value; + PyObject *module, *fromlist; + Py_ssize_t pos = 0; + + g_return_val_if_fail (Py_IsInitialized (), FALSE); + + main_module = PyImport_AddModule ("__main__"); + + if (main_module == NULL) { + g_warning ("Could not get __main__."); + return FALSE; + } + + /* If we have a special path, we register it */ + if (priv->path != NULL) { + PyObject *sys_path = PySys_GetObject ("path"); + PyObject *path = PyString_FromString (priv->path); + + if (PySequence_Contains(sys_path, path) == 0) + PyList_Insert (sys_path, 0, path); + + Py_DECREF(path); + } + + main_locals = PyModule_GetDict (main_module); + + /* We need a fromlist to be able to import modules with + * a '.' in the name. */ + fromlist = PyTuple_New(0); + + module = PyImport_ImportModuleEx (priv->module, main_locals, main_locals, fromlist); + + Py_DECREF(fromlist); + + if (!module) { + PyErr_Print (); + return FALSE; + } + + locals = PyModule_GetDict (module); + + while (PyDict_Next (locals, &pos, &key, &value)) { + if (!PyType_Check(value)) + continue; + + if (PyObject_IsSubclass (value, (PyObject*) PyEomPlugin_Type)) { + priv->type = eom_python_plugin_get_type (gmodule, value); + return TRUE; + } + } + + return FALSE; +} + +static void +eom_python_module_unload (GTypeModule *module) +{ + EomPythonModulePrivate *priv = EOM_PYTHON_MODULE_GET_PRIVATE (module); + + eom_debug_message (DEBUG_PLUGINS, "Unloading Python module"); + + priv->type = 0; +} + +GObject * +eom_python_module_new_object (EomPythonModule *module) +{ + EomPythonModulePrivate *priv = EOM_PYTHON_MODULE_GET_PRIVATE (module); + + eom_debug_message (DEBUG_PLUGINS, "Creating object of type %s", g_type_name (priv->type)); + + if (priv->type == 0) + return NULL; + + return g_object_new (priv->type, NULL); +} + +static void +eom_python_module_init (EomPythonModule *module) +{ + eom_debug_message (DEBUG_PLUGINS, "Init of Python module"); +} + +static void +eom_python_module_finalize (GObject *object) +{ + EomPythonModulePrivate *priv = EOM_PYTHON_MODULE_GET_PRIVATE (object); + + eom_debug_message (DEBUG_PLUGINS, "Finalizing Python module %s", g_type_name (priv->type)); + + g_free (priv->module); + g_free (priv->path); + + G_OBJECT_CLASS (eom_python_module_parent_class)->finalize (object); +} + +static void +eom_python_module_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + g_return_if_reached (); +} + +static void +eom_python_module_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + EomPythonModule *mod = EOM_PYTHON_MODULE (object); + + switch (prop_id) { + case PROP_MODULE: + EOM_PYTHON_MODULE_GET_PRIVATE (mod)->module = g_value_dup_string (value); + break; + + case PROP_PATH: + EOM_PYTHON_MODULE_GET_PRIVATE (mod)->path = g_value_dup_string (value); + break; + + default: + g_return_if_reached (); + } +} + +static void +eom_python_module_class_init (EomPythonModuleClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (class); + GTypeModuleClass *module_class = G_TYPE_MODULE_CLASS (class); + + object_class->finalize = eom_python_module_finalize; + object_class->get_property = eom_python_module_get_property; + object_class->set_property = eom_python_module_set_property; + + g_object_class_install_property + (object_class, + PROP_MODULE, + g_param_spec_string ("module", + "Module Name", + "The Python module to load for this plugin", + NULL, + G_PARAM_WRITABLE | G_PARAM_READABLE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property + (object_class, + PROP_PATH, + g_param_spec_string ("path", + "Path", + "The Python path to use when loading this module", + NULL, + G_PARAM_WRITABLE | G_PARAM_READABLE | G_PARAM_CONSTRUCT_ONLY)); + + g_type_class_add_private (object_class, sizeof (EomPythonModulePrivate)); + + module_class->load = eom_python_module_load; + module_class->unload = eom_python_module_unload; +} + +EomPythonModule * +eom_python_module_new (const gchar *path, + const gchar *module) +{ + EomPythonModule *result; + + if (module == NULL || module[0] == '\0') + return NULL; + + result = g_object_new (EOM_TYPE_PYTHON_MODULE, + "module", module, + "path", path, + NULL); + + g_type_module_set_name (G_TYPE_MODULE (result), module); + + return result; +} + + +static gint idle_garbage_collect_id = 0; + +/* C equivalent of + * import pygtk + * pygtk.require ("2.0") + */ +static gboolean +check_pygtk2 (void) +{ + PyObject *pygtk, *mdict, *require; + + /* pygtk.require("2.0") */ + pygtk = PyImport_ImportModule ("pygtk"); + + if (pygtk == NULL) { + g_warning ("Error initializing Python interpreter: could not import pygtk."); + return FALSE; + } + + mdict = PyModule_GetDict (pygtk); + + require = PyDict_GetItemString (mdict, "require"); + + PyObject_CallObject (require, + Py_BuildValue ("(S)", PyString_FromString ("2.0"))); + + if (PyErr_Occurred()) { + g_warning ("Error initializing Python interpreter: pygtk 2 is required."); + return FALSE; + } + + return TRUE; +} + +/* Note: the following two functions are needed because + * init_pyobject and init_pygtk which are *macros* which in case + * case of error set the PyErr and then make the calling + * function return behind our back. + * It's up to the caller to check the result with PyErr_Occurred() + */ +static void +eom_init_pygobject (void) +{ + init_pygobject_check (2, 11, 5); /* FIXME: get from config */ +} + +static void +eom_init_pygtk (void) +{ + PyObject *gtk, *mdict, *version, *required_version; + + init_pygtk (); + + /* There isn't init_pygtk_check(), do the version + * check ourselves */ + gtk = PyImport_ImportModule("gtk"); + + mdict = PyModule_GetDict(gtk); + + version = PyDict_GetItemString (mdict, "pygtk_version"); + + if (!version) { + PyErr_SetString (PyExc_ImportError, + "PyGObject version too old"); + return; + } + + required_version = Py_BuildValue ("(iii)", 2, 4, 0); /* FIXME */ + + if (PyObject_Compare (version, required_version) == -1) { + PyErr_SetString (PyExc_ImportError, + "PyGObject version too old"); + + Py_DECREF (required_version); + return; + } + + Py_DECREF (required_version); +} + +gboolean +eom_python_init (void) +{ + PyObject *mdict, *path, *tuple; + PyObject *sys_path, *eom; + PyObject *gettext, *install, *gettext_args; + struct sigaction old_sigint; + gint res; + /* Workaround for python bug. See #569228. */ + char *argv[] = { "/dev/null/python/is/buggy/eom", NULL }; + + static gboolean init_failed = FALSE; + + if (init_failed) { + /* We already failed to initialized Python, don't need to + * retry again */ + return FALSE; + } + + if (Py_IsInitialized ()) { + /* Python has already been successfully initialized */ + return TRUE; + } + + /* We are trying to initialize Python for the first time, + set init_failed to FALSE only if the entire initialization process + ends with success */ + init_failed = TRUE; + + /* Hack to make python not overwrite SIGINT: this is needed to avoid + * the crash reported on bug #326191 */ + + /* CHECK: can't we use Py_InitializeEx instead of Py_Initialize in order + to avoid to manage signal handlers ? - Paolo (Dec. 31, 2006) */ + + /* Save old handler */ + res = sigaction (SIGINT, NULL, &old_sigint); + + if (res != 0) { + g_warning ("Error initializing Python interpreter: cannot get " + "handler to SIGINT signal (%s)", strerror (errno)); + + return FALSE; + } + + /* Python initialization */ + Py_Initialize (); + + /* Restore old handler */ + res = sigaction (SIGINT, &old_sigint, NULL); + + if (res != 0) { + g_warning ("Error initializing Python interpreter: cannot restore " + "handler to SIGINT signal (%s).", strerror (errno)); + + goto python_init_error; + } + + PySys_SetArgv (1, argv); + + /* Sanitize sys.path */ + PyRun_SimpleString("import sys; sys.path = filter(None, sys.path)"); + + if (!check_pygtk2 ()) { + /* Warning message already printed in check_pygtk2 */ + goto python_init_error; + } + + /* import gobject */ + eom_init_pygobject (); + + if (PyErr_Occurred ()) { + g_warning ("Error initializing Python interpreter: could not import pygobject."); + + goto python_init_error; + } + + /* import gtk */ + eom_init_pygtk (); + + if (PyErr_Occurred ()) { + g_warning ("Error initializing Python interpreter: could not import pygtk."); + + goto python_init_error; + } + + /* sys.path.insert(0, ...) for system-wide plugins */ + sys_path = PySys_GetObject ("path"); + path = PyString_FromString (EOM_PLUGIN_DIR "/"); + PyList_Insert (sys_path, 0, path); + Py_DECREF(path); + + /* import eom */ + eom = Py_InitModule ("eom", pyeom_functions); + mdict = PyModule_GetDict (eom); + + pyeom_register_classes (mdict); + pyeom_add_constants (eom, "EOM_"); + + /* eom version */ + tuple = Py_BuildValue("(iii)", + EOM_MAJOR_VERSION, + EOM_MINOR_VERSION, + EOM_MICRO_VERSION); + PyDict_SetItemString(mdict, "version", tuple); + Py_DECREF(tuple); + + /* Retrieve the Python type for eom.Plugin */ + PyEomPlugin_Type = (PyTypeObject *) PyDict_GetItemString (mdict, "Plugin"); + + if (PyEomPlugin_Type == NULL) { + PyErr_Print (); + + goto python_init_error; + } + + /* i18n support */ + gettext = PyImport_ImportModule ("gettext"); + + if (gettext == NULL) { + g_warning ("Error initializing Python interpreter: could not import gettext."); + + goto python_init_error; + } + + mdict = PyModule_GetDict (gettext); + install = PyDict_GetItemString (mdict, "install"); + gettext_args = Py_BuildValue ("ss", GETTEXT_PACKAGE, EOM_LOCALE_DIR); + PyObject_CallObject (install, gettext_args); + Py_DECREF (gettext_args); + + /* Python has been successfully initialized */ + init_failed = FALSE; + + return TRUE; + +python_init_error: + + g_warning ("Please check the installation of all the Python related packages required " + "by eom and try again."); + + PyErr_Clear (); + + eom_python_shutdown (); + + return FALSE; +} + +void +eom_python_shutdown (void) +{ + if (Py_IsInitialized ()) { + if (idle_garbage_collect_id != 0) { + g_source_remove (idle_garbage_collect_id); + idle_garbage_collect_id = 0; + } + + while (PyGC_Collect ()) + ; + + Py_Finalize (); + } +} + +static gboolean +run_gc (gpointer data) +{ + while (PyGC_Collect ()) + ; + + idle_garbage_collect_id = 0; + + return FALSE; +} + +void +eom_python_garbage_collect (void) +{ + if (Py_IsInitialized()) { + /* + * We both run the GC right now and we schedule + * a further collection in the main loop. + */ + + while (PyGC_Collect ()) + ; + + if (idle_garbage_collect_id == 0) + idle_garbage_collect_id = g_idle_add (run_gc, NULL); + } +} + diff --git a/src/eom-python-module.h b/src/eom-python-module.h new file mode 100644 index 0000000..594884d --- /dev/null +++ b/src/eom-python-module.h @@ -0,0 +1,72 @@ +/* Eye Of Mate - Python Module + * + * Copyright (C) 2007 The Free Software Foundation + * + * Author: Lucas Rocha <[email protected]> + * + * Based on gedit code (gedit/gedit-python-module.h) by: + * - Raphael Slinckx <[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 __EOM_PYTHON_MODULE_H__ +#define __EOM_PYTHON_MODULE_H__ + +#include <glib-object.h> + +G_BEGIN_DECLS + +#define EOM_TYPE_PYTHON_MODULE (eom_python_module_get_type ()) +#define EOM_PYTHON_MODULE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EOM_TYPE_PYTHON_MODULE, EomPythonModule)) +#define EOM_PYTHON_MODULE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EOM_TYPE_PYTHON_MODULE, EomPythonModuleClass)) +#define EOM_IS_PYTHON_MODULE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EOM_TYPE_PYTHON_MODULE)) +#define EOM_IS_PYTHON_MODULE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), EOM_TYPE_PYTHON_MODULE)) +#define EOM_PYTHON_MODULE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), EOM_TYPE_PYTHON_MODULE, EomPythonModuleClass)) + +typedef struct _EomPythonModule EomPythonModule; +typedef struct _EomPythonModuleClass EomPythonModuleClass; +typedef struct _EomPythonModulePrivate EomPythonModulePrivate; + +struct _EomPythonModuleClass { + GTypeModuleClass parent_class; +}; + +struct _EomPythonModule { + GTypeModule parent_instance; +}; + +G_GNUC_INTERNAL +GType eom_python_module_get_type (void) G_GNUC_CONST; + +G_GNUC_INTERNAL +EomPythonModule *eom_python_module_new (const gchar* path, + const gchar *module); + +G_GNUC_INTERNAL +GObject *eom_python_module_new_object (EomPythonModule *module); + +G_GNUC_INTERNAL +gboolean eom_python_init (void); + +G_GNUC_INTERNAL +void eom_python_shutdown (void); + +G_GNUC_INTERNAL +void eom_python_garbage_collect (void); + +G_END_DECLS + +#endif /* __EOM_PYTHON_MODULE_H__ */ diff --git a/src/eom-python-plugin.c b/src/eom-python-plugin.c new file mode 100644 index 0000000..e693b54 --- /dev/null +++ b/src/eom-python-plugin.c @@ -0,0 +1,282 @@ +/* Eye Of Mate - Python Plugin + * + * Copyright (C) 2007 The Free Software Foundation + * + * Author: Lucas Rocha <[email protected]> + * + * Based on gedit code (gedit/gedit-python-module.h) by: + * - Raphael Slinckx <[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 <config.h> + +#include "eom-python-plugin.h" +#include "eom-plugin.h" +#include "eom-debug.h" + +#define NO_IMPORT_PYGOBJECT +#include <pygobject.h> +#include <string.h> + +static GObjectClass *parent_class; + +static PyObject * +call_python_method (EomPythonPlugin *plugin, + EomWindow *window, + gchar *method) +{ + PyObject *py_ret = NULL; + + g_return_val_if_fail (PyObject_HasAttrString (plugin->instance, method), NULL); + + if (window == NULL) { + py_ret = PyObject_CallMethod (plugin->instance, + method, + NULL); + } else { + py_ret = PyObject_CallMethod (plugin->instance, + method, + "(N)", + pygobject_new (G_OBJECT (window))); + } + + if (!py_ret) + PyErr_Print (); + + return py_ret; +} + +static gboolean +check_py_object_is_gtk_widget (PyObject *py_obj) +{ + static PyTypeObject *_PyGtkWidget_Type = NULL; + + if (_PyGtkWidget_Type == NULL) { + PyObject *module; + + if ((module = PyImport_ImportModule ("gtk"))) { + PyObject *moddict = PyModule_GetDict (module); + _PyGtkWidget_Type = (PyTypeObject *) PyDict_GetItemString (moddict, "Widget"); + } + + if (_PyGtkWidget_Type == NULL) { + PyErr_SetString(PyExc_TypeError, "could not find Python gtk widget type"); + PyErr_Print(); + + return FALSE; + } + } + + return PyObject_TypeCheck (py_obj, _PyGtkWidget_Type) ? TRUE : FALSE; +} + +static void +impl_update_ui (EomPlugin *plugin, + EomWindow *window) +{ + PyGILState_STATE state = pyg_gil_state_ensure (); + + EomPythonPlugin *pyplugin = (EomPythonPlugin *) plugin; + + if (PyObject_HasAttrString (pyplugin->instance, "update_ui")) { + PyObject *py_ret = call_python_method (pyplugin, window, "update_ui"); + + if (py_ret) + { + Py_XDECREF (py_ret); + } + } else { + EOM_PLUGIN_CLASS (parent_class)->update_ui (plugin, window); + } + + pyg_gil_state_release (state); +} + +static void +impl_deactivate (EomPlugin *plugin, + EomWindow *window) +{ + PyGILState_STATE state = pyg_gil_state_ensure (); + + EomPythonPlugin *pyplugin = (EomPythonPlugin *) plugin; + + if (PyObject_HasAttrString (pyplugin->instance, "deactivate")) { + PyObject *py_ret = call_python_method (pyplugin, window, "deactivate"); + + if (py_ret) { + Py_XDECREF (py_ret); + } + } else { + EOM_PLUGIN_CLASS (parent_class)->deactivate (plugin, window); + } + + pyg_gil_state_release (state); +} + +static void +impl_activate (EomPlugin *plugin, + EomWindow *window) +{ + PyGILState_STATE state = pyg_gil_state_ensure (); + + EomPythonPlugin *pyplugin = (EomPythonPlugin *) plugin; + + if (PyObject_HasAttrString (pyplugin->instance, "activate")) { + PyObject *py_ret = call_python_method (pyplugin, window, "activate"); + + if (py_ret) { + Py_XDECREF (py_ret); + } + } else { + EOM_PLUGIN_CLASS (parent_class)->activate (plugin, window); + } + + pyg_gil_state_release (state); +} + +static GtkWidget * +impl_create_configure_dialog (EomPlugin *plugin) +{ + PyGILState_STATE state = pyg_gil_state_ensure (); + EomPythonPlugin *pyplugin = (EomPythonPlugin *) plugin; + GtkWidget *ret = NULL; + + if (PyObject_HasAttrString (pyplugin->instance, "create_configure_dialog")) { + PyObject *py_ret = call_python_method (pyplugin, NULL, "create_configure_dialog"); + + if (py_ret) { + if (check_py_object_is_gtk_widget (py_ret)) { + ret = GTK_WIDGET (pygobject_get (py_ret)); + g_object_ref (ret); + } else { + PyErr_SetString(PyExc_TypeError, "Return value for create_configure_dialog is not a GtkWidget"); + PyErr_Print(); + } + + Py_DECREF (py_ret); + } + } else { + ret = EOM_PLUGIN_CLASS (parent_class)->create_configure_dialog (plugin); + } + + pyg_gil_state_release (state); + + return ret; +} + +static gboolean +impl_is_configurable (EomPlugin *plugin) +{ + PyGILState_STATE state = pyg_gil_state_ensure (); + + EomPythonPlugin *pyplugin = (EomPythonPlugin *) plugin; + + PyObject *dict = pyplugin->instance->ob_type->tp_dict; + + gboolean result; + + if (dict == NULL) + result = FALSE; + else if (!PyDict_Check(dict)) + result = FALSE; + else + result = PyDict_GetItemString(dict, "create_configure_dialog") != NULL; + + pyg_gil_state_release (state); + + return result; +} + +static void +eom_python_plugin_init (EomPythonPlugin *plugin) +{ + EomPythonPluginClass *class; + + eom_debug_message (DEBUG_PLUGINS, "Creating Python plugin instance"); + + class = (EomPythonPluginClass*) (((GTypeInstance*) plugin)->g_class); + + plugin->instance = PyObject_CallObject (class->type, NULL); + + if (plugin->instance == NULL) + PyErr_Print(); +} + +static void +eom_python_plugin_finalize (GObject *plugin) +{ + eom_debug_message (DEBUG_PLUGINS, "Finalizing Python plugin instance"); + + Py_DECREF (((EomPythonPlugin *) plugin)->instance); + + G_OBJECT_CLASS (parent_class)->finalize (plugin); +} + +static void +eom_python_plugin_class_init (EomPythonPluginClass *klass, + gpointer class_data) +{ + EomPluginClass *plugin_class = EOM_PLUGIN_CLASS (klass); + + parent_class = g_type_class_peek_parent (klass); + + klass->type = (PyObject*) class_data; + + G_OBJECT_CLASS (klass)->finalize = eom_python_plugin_finalize; + + plugin_class->activate = impl_activate; + plugin_class->deactivate = impl_deactivate; + plugin_class->update_ui = impl_update_ui; + plugin_class->create_configure_dialog = impl_create_configure_dialog; + plugin_class->is_configurable = impl_is_configurable; +} + +GType +eom_python_plugin_get_type (GTypeModule *module, + PyObject *type) +{ + GType gtype; + gchar *type_name; + + GTypeInfo info = { + sizeof (EomPythonPluginClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) eom_python_plugin_class_init, + NULL, /* class_finalize */ + type, /* class_data */ + sizeof (EomPythonPlugin), + 0, /* n_preallocs */ + (GInstanceInitFunc) eom_python_plugin_init, + }; + + Py_INCREF (type); + + type_name = g_strdup_printf ("%s+EomPythonPlugin", + PyString_AsString (PyObject_GetAttrString (type, "__name__"))); + + eom_debug_message (DEBUG_PLUGINS, "Registering Python plugin instance: %s", type_name); + + gtype = g_type_module_register_type (module, + EOM_TYPE_PLUGIN, + type_name, + &info, 0); + + g_free (type_name); + + return gtype; +} diff --git a/src/eom-python-plugin.h b/src/eom-python-plugin.h new file mode 100644 index 0000000..eda7959 --- /dev/null +++ b/src/eom-python-plugin.h @@ -0,0 +1,55 @@ +/* Eye Of Mate - Python Plugin + * + * Copyright (C) 2007 The Free Software Foundation + * + * Author: Lucas Rocha <[email protected]> + * + * Based on gedit code (gedit/gedit-python-module.h) by: + * - Raphael Slinckx <[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 __EOM_PYTHON_PLUGIN_H__ +#define __EOM_PYTHON_PLUGIN_H__ + +#include <Python.h> +#include <glib-object.h> + +#include "eom-plugin.h" + +G_BEGIN_DECLS + +typedef struct _EomPythonPlugin EomPythonPlugin; +typedef struct _EomPythonPluginClass EomPythonPluginClass; + +struct _EomPythonPlugin { + EomPlugin plugin; + + PyObject *instance; +}; + +struct _EomPythonPluginClass { + EomPluginClass parent_class; + + PyObject *type; +}; + +G_GNUC_INTERNAL +GType eom_python_plugin_get_type (GTypeModule *module, PyObject *type); + +G_END_DECLS + +#endif diff --git a/src/eom-save-as-dialog-helper.c b/src/eom-save-as-dialog-helper.c new file mode 100644 index 0000000..e2ff12e --- /dev/null +++ b/src/eom-save-as-dialog-helper.c @@ -0,0 +1,311 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <string.h> +#include <gtk/gtk.h> +#include "eom-save-as-dialog-helper.h" +#include "eom-pixbuf-util.h" +#include "eom-file-chooser.h" + +typedef struct { + GtkWidget *dir_chooser; + GtkWidget *token_entry; + GtkWidget *replace_spaces_check; + GtkWidget *counter_spin; + GtkWidget *preview_label; + GtkWidget *format_combobox; + + guint idle_id; + gint n_images; + EomImage *image; + gint nth_image; +} SaveAsData; + +static GdkPixbufFormat * +get_selected_format (GtkComboBox *combobox) +{ + GdkPixbufFormat *format; + GtkTreeModel *store; + GtkTreeIter iter; + + gtk_combo_box_get_active_iter (combobox, &iter); + store = gtk_combo_box_get_model (combobox); + gtk_tree_model_get (store, &iter, 1, &format, -1); + + return format; +} + +static gboolean +update_preview (gpointer user_data) +{ + SaveAsData *data; + char *preview_str = NULL; + const char *token_str; + gboolean convert_spaces; + gulong counter_start; + GdkPixbufFormat *format; + + data = g_object_get_data (G_OBJECT (user_data), "data"); + g_assert (data != NULL); + + if (data->image == NULL) return FALSE; + + /* obtain required dialog data */ + token_str = gtk_entry_get_text (GTK_ENTRY (data->token_entry)); + convert_spaces = gtk_toggle_button_get_active + (GTK_TOGGLE_BUTTON (data->replace_spaces_check)); + counter_start = gtk_spin_button_get_value_as_int + (GTK_SPIN_BUTTON (data->counter_spin)); + + format = get_selected_format (GTK_COMBO_BOX (data->format_combobox)); + + if (token_str != NULL) { + /* generate preview filename */ + preview_str = eom_uri_converter_preview (token_str, data->image, format, + (counter_start + data->nth_image), + data->n_images, + convert_spaces, '_' /* FIXME: make this editable */); + } + + gtk_label_set_text (GTK_LABEL (data->preview_label), preview_str); + + g_free (preview_str); + + data->idle_id = 0; + + return FALSE; +} + +static void +request_preview_update (GtkWidget *dlg) +{ + SaveAsData *data; + + data = g_object_get_data (G_OBJECT (dlg), "data"); + g_assert (data != NULL); + + if (data->idle_id != 0) + return; + + data->idle_id = g_idle_add (update_preview, dlg); +} + +static void +on_format_combobox_changed (GtkComboBox *widget, gpointer data) +{ + request_preview_update (GTK_WIDGET (data)); +} + +static void +on_token_entry_changed (GtkWidget *widget, gpointer user_data) +{ + SaveAsData *data; + gboolean enable_save; + + data = g_object_get_data (G_OBJECT (user_data), "data"); + g_assert (data != NULL); + + request_preview_update (GTK_WIDGET (user_data)); + + enable_save = (strlen (gtk_entry_get_text (GTK_ENTRY (data->token_entry))) > 0); + gtk_dialog_set_response_sensitive (GTK_DIALOG (user_data), GTK_RESPONSE_OK, + enable_save); +} + +static void +on_replace_spaces_check_clicked (GtkWidget *widget, gpointer data) +{ + request_preview_update (GTK_WIDGET (data)); +} + +static void +on_counter_spin_changed (GtkWidget *widget, gpointer data) +{ + request_preview_update (GTK_WIDGET (data)); +} + +static void +prepare_format_combobox (SaveAsData *data) +{ + GtkComboBox *combobox; + GtkCellRenderer *cell; + GSList *formats; + GtkListStore *store; + GSList *it; + GtkTreeIter iter; + + combobox = GTK_COMBO_BOX (data->format_combobox); + + store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_POINTER); + gtk_combo_box_set_model (combobox, GTK_TREE_MODEL (store)); + + cell = gtk_cell_renderer_text_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combobox), cell, TRUE); + gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (combobox), cell, + "text", 0); + + formats = eom_pixbuf_get_savable_formats (); + for (it = formats; it != NULL; it = it->next) { + GdkPixbufFormat *f; + + f = (GdkPixbufFormat*) it->data; + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, gdk_pixbuf_format_get_name (f), 1, f, -1); + } + g_slist_free (formats); + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, _("as is"), 1, NULL, -1); + gtk_combo_box_set_active_iter (combobox, &iter); + gtk_widget_show_all (GTK_WIDGET (combobox)); +} + +static void +destroy_data_cb (gpointer data) +{ + SaveAsData *sd; + + sd = (SaveAsData*) data; + + if (sd->image != NULL) + g_object_unref (sd->image); + + if (sd->idle_id != 0) + g_source_remove (sd->idle_id); + + g_slice_free (SaveAsData, sd); +} + +static void +set_default_values (GtkWidget *dlg, GFile *base_file) +{ + SaveAsData *sd; + + sd = (SaveAsData*) g_object_get_data (G_OBJECT (dlg), "data"); + + gtk_spin_button_set_value (GTK_SPIN_BUTTON (sd->counter_spin), 0.0); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (sd->replace_spaces_check), + FALSE); + if (base_file != NULL) { + gtk_file_chooser_set_current_folder_file (GTK_FILE_CHOOSER (sd->dir_chooser), base_file, NULL); + } + + /*gtk_dialog_set_response_sensitive (GTK_DIALOG (dlg), GTK_RESPONSE_OK, FALSE);*/ + + request_preview_update (dlg); +} + +GtkWidget* +eom_save_as_dialog_new (GtkWindow *main, GList *images, GFile *base_file) +{ + char *filepath; + GtkBuilder *xml; + GtkWidget *dlg; + SaveAsData *data; + GtkWidget *label; + + filepath = g_build_filename (EOM_DATA_DIR, + "eom-multiple-save-as-dialog.ui", + NULL); + + xml = gtk_builder_new (); + gtk_builder_set_translation_domain (xml, GETTEXT_PACKAGE); + g_assert (gtk_builder_add_from_file (xml, filepath, NULL)); + + g_free (filepath); + + dlg = GTK_WIDGET (g_object_ref (gtk_builder_get_object (xml, "eom_multiple_save_as_dialog"))); + gtk_window_set_transient_for (GTK_WINDOW (dlg), GTK_WINDOW (main)); + gtk_window_set_position (GTK_WINDOW (dlg), GTK_WIN_POS_CENTER_ON_PARENT); + + data = g_slice_new0 (SaveAsData); + /* init widget references */ + data->dir_chooser = GTK_WIDGET (gtk_builder_get_object (xml, + "dir_chooser")); + data->token_entry = GTK_WIDGET (gtk_builder_get_object (xml, + "token_entry")); + data->replace_spaces_check = GTK_WIDGET (gtk_builder_get_object (xml, + "replace_spaces_check")); + data->counter_spin = GTK_WIDGET (gtk_builder_get_object (xml, + "counter_spin")); + data->preview_label = GTK_WIDGET (gtk_builder_get_object (xml, + "preview_label")); + data->format_combobox = GTK_WIDGET (gtk_builder_get_object (xml, + "format_combobox")); + + /* init preview information */ + data->idle_id = 0; + data->n_images = g_list_length (images); + data->nth_image = (int) ((float) data->n_images * rand() / (float) (RAND_MAX+1.0)); + g_assert (data->nth_image >= 0 && data->nth_image < data->n_images); + data->image = g_object_ref (G_OBJECT (g_list_nth_data (images, data->nth_image))); + g_object_set_data_full (G_OBJECT (dlg), "data", data, destroy_data_cb); + + g_signal_connect (G_OBJECT (data->format_combobox), "changed", + (GCallback) on_format_combobox_changed, dlg); + + g_signal_connect (G_OBJECT (data->token_entry), "changed", + (GCallback) on_token_entry_changed, dlg); + + g_signal_connect (G_OBJECT (data->replace_spaces_check), "toggled", + (GCallback) on_replace_spaces_check_clicked, dlg); + + g_signal_connect (G_OBJECT (data->counter_spin), "changed", + (GCallback) on_counter_spin_changed, dlg); + + label = GTK_WIDGET (gtk_builder_get_object (xml, "preview_label_from")); + gtk_label_set_text (GTK_LABEL (label), eom_image_get_caption (data->image)); + + prepare_format_combobox (data); + + set_default_values (dlg, base_file); + g_object_unref (xml); + return dlg; +} + +EomURIConverter* +eom_save_as_dialog_get_converter (GtkWidget *dlg) +{ + EomURIConverter *conv; + + SaveAsData *data; + const char *format_str; + gboolean convert_spaces; + gulong counter_start; + GdkPixbufFormat *format; + GFile *base_file; + + data = g_object_get_data (G_OBJECT (dlg), "data"); + g_assert (data != NULL); + + /* obtain required dialog data */ + format_str = gtk_entry_get_text (GTK_ENTRY (data->token_entry)); + + convert_spaces = gtk_toggle_button_get_active + (GTK_TOGGLE_BUTTON (data->replace_spaces_check)); + + counter_start = gtk_spin_button_get_value_as_int + (GTK_SPIN_BUTTON (data->counter_spin)); + + format = get_selected_format (GTK_COMBO_BOX (data->format_combobox)); + + base_file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (data->dir_chooser)); + + /* create converter object */ + conv = eom_uri_converter_new (base_file, format, format_str); + + /* set other properties */ + g_object_set (G_OBJECT (conv), + "convert-spaces", convert_spaces, + "space-character", '_', + "counter-start", counter_start, + "n-images", data->n_images, + NULL); + + g_object_unref (base_file); + + return conv; +} diff --git a/src/eom-save-as-dialog-helper.h b/src/eom-save-as-dialog-helper.h new file mode 100644 index 0000000..dd72fa9 --- /dev/null +++ b/src/eom-save-as-dialog-helper.h @@ -0,0 +1,20 @@ +#ifndef _EOM_SAVE_AS_DIALOG_HELPER_H_ +#define _EOM_SAVE_AS_DIALOG_HELPER_H_ + +#include <gtk/gtk.h> +#include <gio/gio.h> +#include "eom-uri-converter.h" + + +G_BEGIN_DECLS + +G_GNUC_INTERNAL +GtkWidget* eom_save_as_dialog_new (GtkWindow *main, GList *images, GFile *base_file); + +G_GNUC_INTERNAL +EomURIConverter* eom_save_as_dialog_get_converter (GtkWidget *dlg); + + +G_END_DECLS + +#endif /* _EOM_SAVE_DIALOG_HELPER_H_ */ diff --git a/src/eom-scroll-view.c b/src/eom-scroll-view.c new file mode 100644 index 0000000..f37561c --- /dev/null +++ b/src/eom-scroll-view.c @@ -0,0 +1,2633 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <math.h> +#include <gdk-pixbuf/gdk-pixbuf.h> +#include <gdk/gdkkeysyms.h> +#ifdef HAVE_RSVG +#include <librsvg/rsvg.h> +#include <librsvg/rsvg-cairo.h> +#endif + +#include "eom-marshal.h" +#include "eom-scroll-view.h" +#include "eom-debug.h" +#include "uta.h" +#include "zoom.h" + +/* Maximum size of delayed repaint rectangles */ +#define PAINT_RECT_WIDTH 128 +#define PAINT_RECT_HEIGHT 128 + +/* Scroll step increment */ +#define SCROLL_STEP_SIZE 32 + +/* Maximum zoom factor */ +#define MAX_ZOOM_FACTOR 20 +#define MIN_ZOOM_FACTOR 0.02 + +#define CHECK_MEDIUM 8 +#define CHECK_BLACK 0x00000000 +#define CHECK_DARK 0x00555555 +#define CHECK_GRAY 0x00808080 +#define CHECK_LIGHT 0x00cccccc +#define CHECK_WHITE 0x00ffffff + +/* Default increment for zooming. The current zoom factor is multiplied or + * divided by this amount on every zooming step. For consistency, you should + * use the same value elsewhere in the program. + */ +#define IMAGE_VIEW_ZOOM_MULTIPLIER 1.05 + +/* States for automatically adjusting the zoom factor */ +typedef enum { + ZOOM_MODE_FIT, /* Image is fitted to scroll view even if the latter changes size */ + ZOOM_MODE_FREE /* The image remains at its current zoom factor even if the scrollview changes size */ +} ZoomMode; + +/* Progressive loading state */ +typedef enum { + PROGRESSIVE_NONE, /* We are not loading an image or it is already loaded */ + PROGRESSIVE_LOADING, /* An image is being loaded */ + PROGRESSIVE_POLISHING /* We have finished loading an image but have not scaled it with interpolation */ +} ProgressiveState; + +/* Signal IDs */ +enum { + SIGNAL_ZOOM_CHANGED, + SIGNAL_LAST +}; +static gint view_signals [SIGNAL_LAST]; + +typedef enum { + EOM_SCROLL_VIEW_CURSOR_NORMAL, + EOM_SCROLL_VIEW_CURSOR_HIDDEN, + EOM_SCROLL_VIEW_CURSOR_DRAG +} EomScrollViewCursor; + +/* Drag 'n Drop */ +static GtkTargetEntry target_table[] = { + { "text/uri-list", 0, 0}, +}; + +enum { + PROP_0, + PROP_USE_BG_COLOR, + PROP_BACKGROUND_COLOR +}; + +/* Private part of the EomScrollView structure */ +struct _EomScrollViewPrivate { + /* some widgets we rely on */ + GtkWidget *display; + GtkAdjustment *hadj; + GtkAdjustment *vadj; + GtkWidget *hbar; + GtkWidget *vbar; + GtkWidget *menu; + + /* actual image */ + EomImage *image; + guint image_changed_id; + guint frame_changed_id; + GdkPixbuf *pixbuf; + + /* zoom mode, either ZOOM_MODE_FIT or ZOOM_MODE_FREE */ + ZoomMode zoom_mode; + + /* whether to allow zoom > 1.0 on zoom fit */ + gboolean upscale; + + /* the actual zoom factor */ + double zoom; + + /* the minimum possible (reasonable) zoom factor */ + double min_zoom; + + /* Current scrolling offsets */ + int xofs, yofs; + + /* Microtile arrays for dirty region. This represents the dirty region + * for interpolated drawing. + */ + EomUta *uta; + + /* handler ID for paint idle callback */ + guint idle_id; + + /* Interpolation type when zoomed in*/ + GdkInterpType interp_type_in; + + /* Interpolation type when zoomed out*/ + GdkInterpType interp_type_out; + + /* Scroll wheel zoom */ + gboolean scroll_wheel_zoom; + + /* Scroll wheel zoom */ + gdouble zoom_multiplier; + + /* dragging stuff */ + int drag_anchor_x, drag_anchor_y; + int drag_ofs_x, drag_ofs_y; + guint dragging : 1; + + /* status of progressive loading */ + ProgressiveState progressive_state; + + /* how to indicate transparency in images */ + EomTransparencyStyle transp_style; + guint32 transp_color; + + /* the type of the cursor we are currently showing */ + EomScrollViewCursor cursor; + + gboolean use_bg_color; + GdkColor *background_color; + GdkColor *override_bg_color; + + cairo_surface_t *background_surface; +}; + +static void scroll_by (EomScrollView *view, int xofs, int yofs); +static void set_zoom_fit (EomScrollView *view); +static void request_paint_area (EomScrollView *view, GdkRectangle *area); +static void set_minimum_zoom_factor (EomScrollView *view); + +#define EOM_SCROLL_VIEW_GET_PRIVATE(object) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((object), EOM_TYPE_SCROLL_VIEW, EomScrollViewPrivate)) + +G_DEFINE_TYPE (EomScrollView, eom_scroll_view, GTK_TYPE_TABLE) + + +/*=================================== + widget size changing handler & + util functions + ---------------------------------*/ + +/* Disconnects from the EomImage and removes references to it */ +static void +free_image_resources (EomScrollView *view) +{ + EomScrollViewPrivate *priv; + + priv = view->priv; + + if (priv->image_changed_id > 0) { + g_signal_handler_disconnect (G_OBJECT (priv->image), priv->image_changed_id); + priv->image_changed_id = 0; + } + + if (priv->frame_changed_id > 0) { + g_signal_handler_disconnect (G_OBJECT (priv->image), priv->frame_changed_id); + priv->frame_changed_id = 0; + } + + if (priv->image != NULL) { + eom_image_data_unref (priv->image); + priv->image = NULL; + } + + if (priv->pixbuf != NULL) { + g_object_unref (priv->pixbuf); + priv->pixbuf = NULL; + } +} + +/* Computes the size in pixels of the scaled image */ +static void +compute_scaled_size (EomScrollView *view, double zoom, int *width, int *height) +{ + EomScrollViewPrivate *priv; + + priv = view->priv; + + if (priv->pixbuf) { + *width = floor (gdk_pixbuf_get_width (priv->pixbuf) * zoom + 0.5); + *height = floor (gdk_pixbuf_get_height (priv->pixbuf) * zoom + 0.5); + } else + *width = *height = 0; +} + +/* Computes the offsets for the new zoom value so that they keep the image + * centered on the view. + */ +static void +compute_center_zoom_offsets (EomScrollView *view, + double old_zoom, double new_zoom, + int width, int height, + double zoom_x_anchor, double zoom_y_anchor, + int *xofs, int *yofs) +{ + EomScrollViewPrivate *priv; + int old_scaled_width, old_scaled_height; + int new_scaled_width, new_scaled_height; + double view_cx, view_cy; + + priv = view->priv; + + compute_scaled_size (view, old_zoom, + &old_scaled_width, &old_scaled_height); + + if (old_scaled_width < width) + view_cx = (zoom_x_anchor * old_scaled_width) / old_zoom; + else + view_cx = (priv->xofs + zoom_x_anchor * width) / old_zoom; + + if (old_scaled_height < height) + view_cy = (zoom_y_anchor * old_scaled_height) / old_zoom; + else + view_cy = (priv->yofs + zoom_y_anchor * height) / old_zoom; + + compute_scaled_size (view, new_zoom, + &new_scaled_width, &new_scaled_height); + + if (new_scaled_width < width) + *xofs = 0; + else { + *xofs = floor (view_cx * new_zoom - zoom_x_anchor * width + 0.5); + if (*xofs < 0) + *xofs = 0; + } + + if (new_scaled_height < height) + *yofs = 0; + else { + *yofs = floor (view_cy * new_zoom - zoom_y_anchor * height + 0.5); + if (*yofs < 0) + *yofs = 0; + } +} + +/* Sets the scrollbar values based on the current scrolling offset */ +static void +update_scrollbar_values (EomScrollView *view) +{ + EomScrollViewPrivate *priv; + int scaled_width, scaled_height; + int xofs, yofs; + gdouble page_size,page_increment,step_increment; + gdouble lower, upper, value; + GtkAllocation allocation; + + priv = view->priv; + + if (!gtk_widget_get_visible (GTK_WIDGET (priv->hbar)) + && !gtk_widget_get_visible (GTK_WIDGET (priv->vbar))) + return; + + compute_scaled_size (view, priv->zoom, &scaled_width, &scaled_height); + gtk_widget_get_allocation (GTK_WIDGET (priv->display), &allocation); + + if (gtk_widget_get_visible (GTK_WIDGET (priv->hbar))) { + /* Set scroll increments */ + page_size = MIN (scaled_width, allocation.width); + page_increment = allocation.width / 2; + step_increment = SCROLL_STEP_SIZE; + + /* Set scroll bounds and new offsets */ + lower = 0; + upper = scaled_width; + xofs = CLAMP (priv->xofs, 0, upper - page_size); + if (gtk_adjustment_get_value (priv->hadj) != xofs) { + value = xofs; + priv->xofs = xofs; + + g_signal_handlers_block_matched ( + priv->hadj, G_SIGNAL_MATCH_DATA, + 0, 0, NULL, NULL, view); + + gtk_adjustment_configure (priv->hadj, value, lower, + upper, step_increment, + page_increment, page_size); + + g_signal_handlers_unblock_matched ( + priv->hadj, G_SIGNAL_MATCH_DATA, + 0, 0, NULL, NULL, view); + } + } + + if (gtk_widget_get_visible (GTK_WIDGET (priv->vbar))) { + page_size = MIN (scaled_height, allocation.height); + page_increment = allocation.height / 2; + step_increment = SCROLL_STEP_SIZE; + + lower = 0; + upper = scaled_height; + yofs = CLAMP (priv->yofs, 0, upper - page_size); + + if (gtk_adjustment_get_value (priv->vadj) != yofs) { + value = yofs; + priv->yofs = yofs; + + g_signal_handlers_block_matched ( + priv->vadj, G_SIGNAL_MATCH_DATA, + 0, 0, NULL, NULL, view); + + gtk_adjustment_configure (priv->vadj, value, lower, + upper, step_increment, + page_increment, page_size); + + g_signal_handlers_unblock_matched ( + priv->vadj, G_SIGNAL_MATCH_DATA, + 0, 0, NULL, NULL, view); + } + } +} + +static void +eom_scroll_view_set_cursor (EomScrollView *view, EomScrollViewCursor new_cursor) +{ + GdkCursor *cursor = NULL; + GdkDisplay *display; + GtkWidget *widget; + + if (view->priv->cursor == new_cursor) { + return; + } + + widget = gtk_widget_get_toplevel (GTK_WIDGET (view)); + display = gtk_widget_get_display (widget); + view->priv->cursor = new_cursor; + + switch (new_cursor) { + case EOM_SCROLL_VIEW_CURSOR_NORMAL: + gdk_window_set_cursor (gtk_widget_get_window (widget), NULL); + break; + case EOM_SCROLL_VIEW_CURSOR_HIDDEN: + cursor = gdk_cursor_new (GDK_BLANK_CURSOR); + break; + case EOM_SCROLL_VIEW_CURSOR_DRAG: + cursor = gdk_cursor_new_for_display (display, GDK_FLEUR); + break; + } + + if (cursor) { + gdk_window_set_cursor (gtk_widget_get_window (widget), cursor); + gdk_cursor_unref (cursor); + gdk_flush(); + } +} + +/* Changes visibility of the scrollbars based on the zoom factor and the + * specified allocation, or the current allocation if NULL is specified. + */ +static void +check_scrollbar_visibility (EomScrollView *view, GtkAllocation *alloc) +{ + EomScrollViewPrivate *priv; + int bar_height; + int bar_width; + int img_width; + int img_height; + GtkRequisition req; + int width, height; + gboolean hbar_visible, vbar_visible; + + priv = view->priv; + + if (alloc) { + width = alloc->width; + height = alloc->height; + } else { + GtkAllocation allocation; + + gtk_widget_get_allocation (GTK_WIDGET (view), &allocation); + width = allocation.width; + height = allocation.height; + } + + compute_scaled_size (view, priv->zoom, &img_width, &img_height); + + /* this should work fairly well in this special case for scrollbars */ + gtk_widget_size_request (priv->hbar, &req); + bar_height = req.height; + gtk_widget_size_request (priv->vbar, &req); + bar_width = req.width; + + eom_debug_message (DEBUG_WINDOW, "Widget Size allocate: %i, %i Bar: %i, %i\n", + width, height, bar_width, bar_height); + + hbar_visible = vbar_visible = FALSE; + if (priv->zoom_mode == ZOOM_MODE_FIT) + hbar_visible = vbar_visible = FALSE; + else if (img_width <= width && img_height <= height) + hbar_visible = vbar_visible = FALSE; + else if (img_width > width && img_height > height) + hbar_visible = vbar_visible = TRUE; + else if (img_width > width) { + hbar_visible = TRUE; + if (img_height <= (height - bar_height)) + vbar_visible = FALSE; + else + vbar_visible = TRUE; + } + else if (img_height > height) { + vbar_visible = TRUE; + if (img_width <= (width - bar_width)) + hbar_visible = FALSE; + else + hbar_visible = TRUE; + } + + if (hbar_visible != gtk_widget_get_visible (GTK_WIDGET (priv->hbar))) + g_object_set (G_OBJECT (priv->hbar), "visible", hbar_visible, NULL); + + if (vbar_visible != gtk_widget_get_visible (GTK_WIDGET (priv->vbar))) + g_object_set (G_OBJECT (priv->vbar), "visible", vbar_visible, NULL); +} + +#define DOUBLE_EQUAL_MAX_DIFF 1e-6 +#define DOUBLE_EQUAL(a,b) (fabs (a - b) < DOUBLE_EQUAL_MAX_DIFF) + +/* Returns whether the zoom factor is 1.0 */ +static gboolean +is_unity_zoom (EomScrollView *view) +{ + EomScrollViewPrivate *priv; + + priv = view->priv; + return DOUBLE_EQUAL (priv->zoom, 1.0); +} + +/* Returns whether the image is zoomed in */ +static gboolean +is_zoomed_in (EomScrollView *view) +{ + EomScrollViewPrivate *priv; + + priv = view->priv; + return priv->zoom - 1.0 > DOUBLE_EQUAL_MAX_DIFF; +} + +/* Returns whether the image is zoomed out */ +static gboolean +is_zoomed_out (EomScrollView *view) +{ + EomScrollViewPrivate *priv; + + priv = view->priv; + return DOUBLE_EQUAL_MAX_DIFF + priv->zoom - 1.0 < 0.0; +} + +/* Returns wether the image is movable, that means if it is larger then + * the actual visible area. + */ +static gboolean +is_image_movable (EomScrollView *view) +{ + EomScrollViewPrivate *priv; + + priv = view->priv; + + return (gtk_widget_get_visible (priv->hbar) || gtk_widget_get_visible (priv->vbar)); +} + + +/* Computes the image offsets with respect to the window */ +/* +static void +get_image_offsets (EomScrollView *view, int *xofs, int *yofs) +{ + EomScrollViewPrivate *priv; + int scaled_width, scaled_height; + int width, height; + + priv = view->priv; + + compute_scaled_size (view, priv->zoom, &scaled_width, &scaled_height); + + width = GTK_WIDGET (priv->display)->allocation.width; + height = GTK_WIDGET (priv->display)->allocation.height; + + // Compute image offsets with respect to the window + if (scaled_width <= width) + *xofs = (width - scaled_width) / 2; + else + *xofs = -priv->xofs; + + if (scaled_height <= height) + *yofs = (height - scaled_height) / 2; + else + *yofs = -priv->yofs; +} +*/ + +/*=================================== + drawing core + ---------------------------------*/ + + +/* Pulls a rectangle from the specified microtile array. The rectangle is the + * first one that would be glommed together by art_rect_list_from_uta(), and its + * size is bounded by max_width and max_height. The rectangle is also removed + * from the microtile array. + */ +static void +pull_rectangle (EomUta *uta, EomIRect *rect, int max_width, int max_height) +{ + uta_find_first_glom_rect (uta, rect, max_width, max_height); + uta_remove_rect (uta, rect->x0, rect->y0, rect->x1, rect->y1); +} + +/* Paints a rectangle with the background color if the specified rectangle + * intersects the dirty rectangle. + */ +static void +paint_background (EomScrollView *view, EomIRect *r, EomIRect *rect) +{ + EomScrollViewPrivate *priv; + EomIRect d; + + priv = view->priv; + + eom_irect_intersect (&d, r, rect); + if (!eom_irect_empty (&d)) { + gdk_window_clear_area (gtk_widget_get_window (priv->display), + d.x0, d.y0, + d.x1 - d.x0, d.y1 - d.y0); + } +} + +static void +get_transparency_params (EomScrollView *view, int *size, guint32 *color1, guint32 *color2) +{ + EomScrollViewPrivate *priv; + + priv = view->priv; + + /* Compute transparency parameters */ + switch (priv->transp_style) { + case EOM_TRANSP_BACKGROUND: { + GdkColor color = gtk_widget_get_style (GTK_WIDGET (priv->display))->bg[GTK_STATE_NORMAL]; + + *color1 = *color2 = (((color.red & 0xff00) << 8) + | (color.green & 0xff00) + | ((color.blue & 0xff00) >> 8)); + break; } + + case EOM_TRANSP_CHECKED: + *color1 = CHECK_GRAY; + *color2 = CHECK_LIGHT; + break; + + case EOM_TRANSP_COLOR: + *color1 = *color2 = priv->transp_color; + break; + + default: + g_assert_not_reached (); + }; + + *size = CHECK_MEDIUM; +} + +#ifdef HAVE_RSVG +static cairo_surface_t * +create_background_surface (EomScrollView *view) +{ + int check_size; + guint32 check_1 = 0; + guint32 check_2 = 0; + cairo_surface_t *surface; + cairo_t *check_cr; + + get_transparency_params (view, &check_size, &check_1, &check_2); + + surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, check_size * 2, check_size * 2); + check_cr = cairo_create (surface); + cairo_set_source_rgba (check_cr, + ((check_1 & 0xff0000) >> 16) / 255., + ((check_1 & 0x00ff00) >> 8) / 255., + (check_1 & 0x0000ff) / 255., + 1.); + cairo_rectangle (check_cr, 0., 0., check_size, check_size); + cairo_fill (check_cr); + cairo_translate (check_cr, check_size, check_size); + cairo_rectangle (check_cr, 0., 0., check_size, check_size); + cairo_fill (check_cr); + + cairo_set_source_rgba (check_cr, + ((check_2 & 0xff0000) >> 16) / 255., + ((check_2 & 0x00ff00) >> 8) / 255., + (check_2 & 0x0000ff) / 255., + 1.); + cairo_translate (check_cr, -check_size, 0); + cairo_rectangle (check_cr, 0., 0., check_size, check_size); + cairo_fill (check_cr); + cairo_translate (check_cr, check_size, -check_size); + cairo_rectangle (check_cr, 0., 0., check_size, check_size); + cairo_fill (check_cr); + cairo_destroy (check_cr); + + return surface; +} + +static void +draw_svg_background (EomScrollView *view, cairo_t *cr, EomIRect *render_rect, EomIRect *image_rect) +{ + EomScrollViewPrivate *priv; + + priv = view->priv; + + if (priv->background_surface == NULL) + priv->background_surface = create_background_surface (view); + + cairo_set_source_surface (cr, priv->background_surface, + - (render_rect->x0 - image_rect->x0) % (CHECK_MEDIUM * 2), + - (render_rect->y0 - image_rect->y0) % (CHECK_MEDIUM * 2)); + cairo_pattern_set_extend (cairo_get_source (cr), CAIRO_EXTEND_REPEAT); + cairo_rectangle (cr, + 0, + 0, + render_rect->x1 - render_rect->x0, + render_rect->y1 - render_rect->y0); + cairo_fill (cr); +} + +static cairo_surface_t * +draw_svg_on_image_surface (EomScrollView *view, EomIRect *render_rect, EomIRect *image_rect) +{ + EomScrollViewPrivate *priv; + cairo_t *cr; + cairo_surface_t *surface; + cairo_matrix_t matrix, translate, scale; + EomTransform *transform; + + priv = view->priv; + + surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, + render_rect->x1 - render_rect->x0, + render_rect->y1 - render_rect->y0); + cr = cairo_create (surface); + + cairo_save (cr); + draw_svg_background (view, cr, render_rect, image_rect); + cairo_restore (cr); + + cairo_matrix_init_identity (&matrix); + transform = eom_image_get_transform (priv->image); + if (transform) { + cairo_matrix_t affine; + double image_offset_x = 0., image_offset_y = 0.; + + eom_transform_get_affine (transform, &affine); + cairo_matrix_multiply (&matrix, &affine, &matrix); + + switch (eom_transform_get_transform_type (transform)) { + case EOM_TRANSFORM_ROT_90: + case EOM_TRANSFORM_FLIP_HORIZONTAL: + image_offset_x = (double) gdk_pixbuf_get_width (priv->pixbuf); + break; + case EOM_TRANSFORM_ROT_270: + case EOM_TRANSFORM_FLIP_VERTICAL: + image_offset_y = (double) gdk_pixbuf_get_height (priv->pixbuf); + break; + case EOM_TRANSFORM_ROT_180: + case EOM_TRANSFORM_TRANSPOSE: + case EOM_TRANSFORM_TRANSVERSE: + image_offset_x = (double) gdk_pixbuf_get_width (priv->pixbuf); + image_offset_y = (double) gdk_pixbuf_get_height (priv->pixbuf); + break; + case EOM_TRANSFORM_NONE: + default: + break; + } + + cairo_matrix_init_translate (&translate, image_offset_x, image_offset_y); + cairo_matrix_multiply (&matrix, &matrix, &translate); + } + + cairo_matrix_init_scale (&scale, priv->zoom, priv->zoom); + cairo_matrix_multiply (&matrix, &matrix, &scale); + cairo_matrix_init_translate (&translate, image_rect->x0, image_rect->y0); + cairo_matrix_multiply (&matrix, &matrix, &translate); + cairo_matrix_init_translate (&translate, -render_rect->x0, -render_rect->y0); + cairo_matrix_multiply (&matrix, &matrix, &translate); + + cairo_set_matrix (cr, &matrix); + + rsvg_handle_render_cairo (eom_image_get_svg (priv->image), cr); + cairo_destroy (cr); + + return surface; +} + +static void +draw_svg (EomScrollView *view, EomIRect *render_rect, EomIRect *image_rect) +{ + EomScrollViewPrivate *priv; + cairo_t *cr; + cairo_surface_t *surface; + GdkWindow *window; + + priv = view->priv; + + window = gtk_widget_get_window (GTK_WIDGET (priv->display)); + surface = draw_svg_on_image_surface (view, render_rect, image_rect); + + cr = gdk_cairo_create (window); + cairo_set_source_surface (cr, surface, render_rect->x0, render_rect->y0); + cairo_paint (cr); + cairo_destroy (cr); +} +#endif + +/* Paints a rectangle of the dirty region */ +static void +paint_rectangle (EomScrollView *view, EomIRect *rect, GdkInterpType interp_type) +{ + EomScrollViewPrivate *priv; + GdkPixbuf *tmp; + char *str; + GtkAllocation allocation; + int scaled_width, scaled_height; + int xofs, yofs; + EomIRect r, d; + int check_size; + guint32 check_1 = 0; + guint32 check_2 = 0; + + priv = view->priv; + + if (!gtk_widget_is_drawable (priv->display)) + return; + + compute_scaled_size (view, priv->zoom, &scaled_width, &scaled_height); + + gtk_widget_get_allocation (GTK_WIDGET (priv->display), &allocation); + + if (scaled_width < 1 || scaled_height < 1) + { + r.x0 = 0; + r.y0 = 0; + r.x1 = allocation.width; + r.y1 = allocation.height; + paint_background (view, &r, rect); + return; + } + + /* Compute image offsets with respect to the window */ + + if (scaled_width <= allocation.width) + xofs = (allocation.width - scaled_width) / 2; + else + xofs = -priv->xofs; + + if (scaled_height <= allocation.height) + yofs = (allocation.height - scaled_height) / 2; + else + yofs = -priv->yofs; + + eom_debug_message (DEBUG_WINDOW, "zoom %.2f, xofs: %i, yofs: %i scaled w: %i h: %i\n", + priv->zoom, xofs, yofs, scaled_width, scaled_height); + + /* Draw background if necessary, in four steps */ + + /* Top */ + if (yofs > 0) { + r.x0 = 0; + r.y0 = 0; + r.x1 = allocation.width; + r.y1 = yofs; + paint_background (view, &r, rect); + } + + /* Left */ + if (xofs > 0) { + r.x0 = 0; + r.y0 = yofs; + r.x1 = xofs; + r.y1 = yofs + scaled_height; + paint_background (view, &r, rect); + } + + /* Right */ + if (xofs >= 0) { + r.x0 = xofs + scaled_width; + r.y0 = yofs; + r.x1 = allocation.width; + r.y1 = yofs + scaled_height; + if (r.x0 < r.x1) + paint_background (view, &r, rect); + } + + /* Bottom */ + if (yofs >= 0) { + r.x0 = 0; + r.y0 = yofs + scaled_height; + r.x1 = allocation.width; + r.y1 = allocation.height; + if (r.y0 < r.y1) + paint_background (view, &r, rect); + } + + + /* Draw the scaled image + * + * FIXME: this is not using the color correction tables! + */ + + if (!priv->pixbuf) + return; + + r.x0 = xofs; + r.y0 = yofs; + r.x1 = xofs + scaled_width; + r.y1 = yofs + scaled_height; + + eom_irect_intersect (&d, &r, rect); + if (eom_irect_empty (&d)) + return; + + switch (interp_type) { + case GDK_INTERP_NEAREST: + str = "NEAREST"; + break; + default: + str = "ALIASED"; + } + + eom_debug_message (DEBUG_WINDOW, "%s: x0: %i,\t y0: %i,\t x1: %i,\t y1: %i\n", + str, d.x0, d.y0, d.x1, d.y1); + +#ifdef HAVE_RSVG + if (eom_image_is_svg (view->priv->image) && interp_type != GDK_INTERP_NEAREST) { + draw_svg (view, &d, &r); + return; + } +#endif + /* Short-circuit the fast case to avoid a memcpy() */ + + if (is_unity_zoom (view) + && gdk_pixbuf_get_colorspace (priv->pixbuf) == GDK_COLORSPACE_RGB + && !gdk_pixbuf_get_has_alpha (priv->pixbuf) + && gdk_pixbuf_get_bits_per_sample (priv->pixbuf) == 8) { + guchar *pixels; + int rowstride; + + rowstride = gdk_pixbuf_get_rowstride (priv->pixbuf); + + pixels = (gdk_pixbuf_get_pixels (priv->pixbuf) + + (d.y0 - yofs) * rowstride + + 3 * (d.x0 - xofs)); + + gdk_draw_rgb_image_dithalign (gtk_widget_get_window (GTK_WIDGET (priv->display)), + gtk_widget_get_style (GTK_WIDGET (priv->display))->black_gc, + d.x0, d.y0, + d.x1 - d.x0, d.y1 - d.y0, + GDK_RGB_DITHER_MAX, + pixels, + rowstride, + d.x0 - xofs, d.y0 - yofs); + return; + } + + /* For all other cases, create a temporary pixbuf */ + + tmp = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, d.x1 - d.x0, d.y1 - d.y0); + + if (!tmp) { + g_message ("paint_rectangle(): Could not allocate temporary pixbuf of " + "size (%d, %d); skipping", d.x1 - d.x0, d.y1 - d.y0); + return; + } + + /* Compute transparency parameters */ + get_transparency_params (view, &check_size, &check_1, &check_2); + + /* Draw! */ + gdk_pixbuf_composite_color (priv->pixbuf, + tmp, + 0, 0, + d.x1 - d.x0, d.y1 - d.y0, + -(d.x0 - xofs), -(d.y0 - yofs), + priv->zoom, priv->zoom, + is_unity_zoom (view) ? GDK_INTERP_NEAREST : interp_type, + 255, + d.x0 - xofs, d.y0 - yofs, + check_size, + check_1, check_2); + + gdk_draw_rgb_image_dithalign (gtk_widget_get_window (priv->display), + gtk_widget_get_style (priv->display)->black_gc, + d.x0, d.y0, + d.x1 - d.x0, d.y1 - d.y0, + GDK_RGB_DITHER_MAX, + gdk_pixbuf_get_pixels (tmp), + gdk_pixbuf_get_rowstride (tmp), + d.x0 - xofs, d.y0 - yofs); + + g_object_unref (tmp); +} + + +/* Idle handler for the drawing process. We pull a rectangle from the dirty + * region microtile array, paint it, and leave the rest to the next idle + * iteration. + */ +static gboolean +paint_iteration_idle (gpointer data) +{ + EomScrollView *view; + EomScrollViewPrivate *priv; + EomIRect rect; + + view = EOM_SCROLL_VIEW (data); + priv = view->priv; + + g_assert (priv->uta != NULL); + + pull_rectangle (priv->uta, &rect, PAINT_RECT_WIDTH, PAINT_RECT_HEIGHT); + + if (eom_irect_empty (&rect)) { + eom_uta_free (priv->uta); + priv->uta = NULL; + } else { + if (is_zoomed_in (view)) + paint_rectangle (view, &rect, priv->interp_type_in); + else if (is_zoomed_out (view)) + paint_rectangle (view, &rect, priv->interp_type_out); + else + paint_rectangle (view, &rect, GDK_INTERP_NEAREST); + } + + if (!priv->uta) { + priv->idle_id = 0; + return FALSE; + } + + return TRUE; +} + +/* Paints the requested area in non-interpolated mode. Then, if we are + * configured to use interpolation, we queue an idle handler to redraw the area + * with interpolation. The area is in window coordinates. + */ +static void +request_paint_area (EomScrollView *view, GdkRectangle *area) +{ + EomScrollViewPrivate *priv; + EomIRect r; + GtkAllocation allocation; + + priv = view->priv; + + eom_debug_message (DEBUG_WINDOW, "x: %i, y: %i, width: %i, height: %i\n", + area->x, area->y, area->width, area->height); + + if (!gtk_widget_is_drawable (priv->display)) + return; + + gtk_widget_get_allocation (GTK_WIDGET (priv->display), &allocation); + r.x0 = MAX (0, area->x); + r.y0 = MAX (0, area->y); + r.x1 = MIN (allocation.width, area->x + area->width); + r.y1 = MIN (allocation.height, area->y + area->height); + + eom_debug_message (DEBUG_WINDOW, "r: %i, %i, %i, %i\n", r.x0, r.y0, r.x1, r.y1); + + if (r.x0 >= r.x1 || r.y0 >= r.y1) + return; + + /* Do nearest neighbor, 1:1 zoom or active progressive loading synchronously for speed. */ + if ((is_zoomed_in (view) && priv->interp_type_in == GDK_INTERP_NEAREST) || + (is_zoomed_out (view) && priv->interp_type_out == GDK_INTERP_NEAREST) || + is_unity_zoom (view) || + priv->progressive_state == PROGRESSIVE_LOADING) { + paint_rectangle (view, &r, GDK_INTERP_NEAREST); + return; + } + + if (priv->progressive_state == PROGRESSIVE_POLISHING) + /* We have already a complete image with nearest neighbor mode. + * It's sufficient to add only a antitaliased idle update + */ + priv->progressive_state = PROGRESSIVE_NONE; + else if (!priv->image || !eom_image_is_animation (priv->image)) + /* do nearest neigbor before anti aliased version, + except for animations to avoid a "blinking" effect. */ + paint_rectangle (view, &r, GDK_INTERP_NEAREST); + + /* All other interpolation types are delayed. */ + if (priv->uta) + g_assert (priv->idle_id != 0); + else { + g_assert (priv->idle_id == 0); + priv->idle_id = g_idle_add (paint_iteration_idle, view); + } + + priv->uta = uta_add_rect (priv->uta, r.x0, r.y0, r.x1, r.y1); +} + + +/* ======================================= + + scrolling stuff + + --------------------------------------*/ + + +/* Scrolls the view to the specified offsets. */ +static void +scroll_to (EomScrollView *view, int x, int y, gboolean change_adjustments) +{ + EomScrollViewPrivate *priv; + GtkAllocation allocation; + int xofs, yofs; + GdkWindow *window; + int src_x, src_y; + int dest_x, dest_y; + int twidth, theight; + + priv = view->priv; + + /* Check bounds & Compute offsets */ + if (gtk_widget_get_visible (priv->hbar)) { + x = CLAMP (x, 0, gtk_adjustment_get_upper (priv->hadj) + - gtk_adjustment_get_page_size (priv->hadj)); + xofs = x - priv->xofs; + } else + xofs = 0; + + if (gtk_widget_get_visible (priv->vbar)) { + y = CLAMP (y, 0, gtk_adjustment_get_upper (priv->vadj) + - gtk_adjustment_get_page_size (priv->vadj)); + yofs = y - priv->yofs; + } else + yofs = 0; + + if (xofs == 0 && yofs == 0) + return; + + priv->xofs = x; + priv->yofs = y; + + if (!gtk_widget_is_drawable (priv->display)) + goto out; + + gtk_widget_get_allocation (GTK_WIDGET (priv->display), &allocation); + + if (abs (xofs) >= allocation.width || abs (yofs) >= allocation.height) { + gtk_widget_queue_draw (GTK_WIDGET (priv->display)); + goto out; + } + + window = gtk_widget_get_window (GTK_WIDGET (priv->display)); + + /* Ensure that the uta has the full size */ + + twidth = (allocation.width + EOM_UTILE_SIZE - 1) >> EOM_UTILE_SHIFT; + theight = (allocation.height + EOM_UTILE_SIZE - 1) >> EOM_UTILE_SHIFT; + + if (priv->uta) + g_assert (priv->idle_id != 0); + else + priv->idle_id = g_idle_add (paint_iteration_idle, view); + + priv->uta = uta_ensure_size (priv->uta, 0, 0, twidth, theight); + + /* Copy the uta area. Our synchronous handling of expose events, below, + * will queue the new scrolled-in areas. + */ + src_x = xofs < 0 ? 0 : xofs; + src_y = yofs < 0 ? 0 : yofs; + dest_x = xofs < 0 ? -xofs : 0; + dest_y = yofs < 0 ? -yofs : 0; + + uta_copy_area (priv->uta, + src_x, src_y, + dest_x, dest_y, + allocation.width - abs (xofs), + allocation.height - abs (yofs)); + + /* Scroll the window area and process exposure synchronously. */ + + gdk_window_scroll (window, -xofs, -yofs); + gdk_window_process_updates (window, TRUE); + + out: + if (!change_adjustments) + return; + + g_signal_handlers_block_matched ( + priv->hadj, G_SIGNAL_MATCH_DATA, + 0, 0, NULL, NULL, view); + g_signal_handlers_block_matched ( + priv->vadj, G_SIGNAL_MATCH_DATA, + 0, 0, NULL, NULL, view); + + gtk_adjustment_set_value (priv->hadj, x); + gtk_adjustment_set_value (priv->vadj, y); + + g_signal_handlers_unblock_matched ( + priv->hadj, G_SIGNAL_MATCH_DATA, + 0, 0, NULL, NULL, view); + g_signal_handlers_unblock_matched ( + priv->vadj, G_SIGNAL_MATCH_DATA, + 0, 0, NULL, NULL, view); +} + +/* Scrolls the image view by the specified offsets. Notifies the adjustments + * about their new values. + */ +static void +scroll_by (EomScrollView *view, int xofs, int yofs) +{ + EomScrollViewPrivate *priv; + + priv = view->priv; + + scroll_to (view, priv->xofs + xofs, priv->yofs + yofs, TRUE); +} + + +/* Callback used when an adjustment is changed */ +static void +adjustment_changed_cb (GtkAdjustment *adj, gpointer data) +{ + EomScrollView *view; + EomScrollViewPrivate *priv; + + view = EOM_SCROLL_VIEW (data); + priv = view->priv; + + scroll_to (view, gtk_adjustment_get_value (priv->hadj), + gtk_adjustment_get_value (priv->vadj), FALSE); +} + + +/* Drags the image to the specified position */ +static void +drag_to (EomScrollView *view, int x, int y) +{ + EomScrollViewPrivate *priv; + int dx, dy; + + priv = view->priv; + + dx = priv->drag_anchor_x - x; + dy = priv->drag_anchor_y - y; + + x = priv->drag_ofs_x + dx; + y = priv->drag_ofs_y + dy; + + scroll_to (view, x, y, TRUE); +} + +static void +set_minimum_zoom_factor (EomScrollView *view) +{ + g_return_if_fail (EOM_IS_SCROLL_VIEW (view)); + + view->priv->min_zoom = MAX (1.0 / gdk_pixbuf_get_width (view->priv->pixbuf), + MAX(1.0 / gdk_pixbuf_get_height (view->priv->pixbuf), + MIN_ZOOM_FACTOR) ); + return; +} + +/** + * set_zoom: + * @view: A scroll view. + * @zoom: Zoom factor. + * @have_anchor: Whether the anchor point specified by (@anchorx, @anchory) + * should be used. + * @anchorx: Horizontal anchor point in pixels. + * @anchory: Vertical anchor point in pixels. + * + * Sets the zoom factor for an image view. The anchor point can be used to + * specify the point that stays fixed when the image is zoomed. If @have_anchor + * is %TRUE, then (@anchorx, @anchory) specify the point relative to the image + * view widget's allocation that will stay fixed when zooming. If @have_anchor + * is %FALSE, then the center point of the image view will be used. + **/ +static void +set_zoom (EomScrollView *view, double zoom, + gboolean have_anchor, int anchorx, int anchory) +{ + EomScrollViewPrivate *priv; + GtkAllocation allocation; + int xofs, yofs; + double x_rel, y_rel; + + g_assert (zoom > 0.0); + + priv = view->priv; + + if (priv->pixbuf == NULL) + return; + + if (zoom > MAX_ZOOM_FACTOR) + zoom = MAX_ZOOM_FACTOR; + else if (zoom < MIN_ZOOM_FACTOR) + zoom = MIN_ZOOM_FACTOR; + + if (DOUBLE_EQUAL (priv->zoom, zoom)) + return; + if (DOUBLE_EQUAL (priv->zoom, priv->min_zoom) && zoom < priv->zoom) + return; + + priv->zoom_mode = ZOOM_MODE_FREE; + + gtk_widget_get_allocation (GTK_WIDGET (priv->display), &allocation); + + /* compute new xofs/yofs values */ + if (have_anchor) { + x_rel = (double) anchorx / allocation.width; + y_rel = (double) anchory / allocation.height; + } else { + x_rel = 0.5; + y_rel = 0.5; + } + + compute_center_zoom_offsets (view, priv->zoom, zoom, + allocation.width, allocation.height, + x_rel, y_rel, + &xofs, &yofs); + + /* set new values */ + priv->xofs = xofs; /* (img_width * x_rel * zoom) - anchorx; */ + priv->yofs = yofs; /* (img_height * y_rel * zoom) - anchory; */ +#if 0 + g_print ("xofs: %i yofs: %i\n", priv->xofs, priv->yofs); +#endif + if (zoom <= priv->min_zoom) + priv->zoom = priv->min_zoom; + else + priv->zoom = zoom; + + /* we make use of the new values here */ + check_scrollbar_visibility (view, NULL); + update_scrollbar_values (view); + + /* repaint the whole image */ + gtk_widget_queue_draw (GTK_WIDGET (priv->display)); + + g_signal_emit (view, view_signals [SIGNAL_ZOOM_CHANGED], 0, priv->zoom); +} + +/* Zooms the image to fit the available allocation */ +static void +set_zoom_fit (EomScrollView *view) +{ + EomScrollViewPrivate *priv; + GtkAllocation allocation; + double new_zoom; + + priv = view->priv; + + priv->zoom_mode = ZOOM_MODE_FIT; + + if (!gtk_widget_get_mapped (GTK_WIDGET (view))) + return; + + if (priv->pixbuf == NULL) + return; + + gtk_widget_get_allocation (GTK_WIDGET(priv->display), &allocation); + + new_zoom = zoom_fit_scale (allocation.width, allocation.height, + gdk_pixbuf_get_width (priv->pixbuf), + gdk_pixbuf_get_height (priv->pixbuf), + priv->upscale); + + if (new_zoom > MAX_ZOOM_FACTOR) + new_zoom = MAX_ZOOM_FACTOR; + else if (new_zoom < MIN_ZOOM_FACTOR) + new_zoom = MIN_ZOOM_FACTOR; + + priv->zoom = new_zoom; + priv->xofs = 0; + priv->yofs = 0; + + g_signal_emit (view, view_signals [SIGNAL_ZOOM_CHANGED], 0, priv->zoom); +} + +/*=================================== + + internal signal callbacks + + ---------------------------------*/ + +/* Key press event handler for the image view */ +static gboolean +display_key_press_event (GtkWidget *widget, GdkEventKey *event, gpointer data) +{ + EomScrollView *view; + EomScrollViewPrivate *priv; + GtkAllocation allocation; + gboolean do_zoom; + double zoom; + gboolean do_scroll; + int xofs, yofs; + + view = EOM_SCROLL_VIEW (data); + priv = view->priv; + + do_zoom = FALSE; + do_scroll = FALSE; + xofs = yofs = 0; + zoom = 1.0; + + gtk_widget_get_allocation (GTK_WIDGET (priv->display), &allocation); + + /* EomScrollView doesn't handle/have any Alt+Key combos */ + if (event->state & GDK_MOD1_MASK) { + return FALSE; + } + + switch (event->keyval) { + case GDK_Up: + do_scroll = TRUE; + xofs = 0; + yofs = -SCROLL_STEP_SIZE; + break; + + case GDK_Page_Up: + do_scroll = TRUE; + if (event->state & GDK_CONTROL_MASK) { + xofs = -(allocation.width * 3) / 4; + yofs = 0; + } else { + xofs = 0; + yofs = -(allocation.height * 3) / 4; + } + break; + + case GDK_Down: + do_scroll = TRUE; + xofs = 0; + yofs = SCROLL_STEP_SIZE; + break; + + case GDK_Page_Down: + do_scroll = TRUE; + if (event->state & GDK_CONTROL_MASK) { + xofs = (allocation.width * 3) / 4; + yofs = 0; + } else { + xofs = 0; + yofs = (allocation.height * 3) / 4; + } + break; + + case GDK_Left: + do_scroll = TRUE; + xofs = -SCROLL_STEP_SIZE; + yofs = 0; + break; + + case GDK_Right: + do_scroll = TRUE; + xofs = SCROLL_STEP_SIZE; + yofs = 0; + break; + + case GDK_plus: + case GDK_equal: + case GDK_KP_Add: + do_zoom = TRUE; + zoom = priv->zoom * priv->zoom_multiplier; + break; + + case GDK_minus: + case GDK_KP_Subtract: + do_zoom = TRUE; + zoom = priv->zoom / priv->zoom_multiplier; + break; + + case GDK_1: + do_zoom = TRUE; + zoom = 1.0; + break; + + default: + return FALSE; + } + + if (do_zoom) { + gint x, y; + + gdk_window_get_pointer (gtk_widget_get_window (widget), + &x, &y, NULL); + set_zoom (view, zoom, TRUE, x, y); + } + + if (do_scroll) + scroll_by (view, xofs, yofs); + + return TRUE; +} + + +/* Button press event handler for the image view */ +static gboolean +eom_scroll_view_button_press_event (GtkWidget *widget, GdkEventButton *event, gpointer data) +{ + EomScrollView *view; + EomScrollViewPrivate *priv; + + view = EOM_SCROLL_VIEW (data); + priv = view->priv; + + if (!gtk_widget_has_focus (priv->display)) + gtk_widget_grab_focus (GTK_WIDGET (priv->display)); + + if (priv->dragging) + return FALSE; + + switch (event->button) { + case 1: + case 2: + if (event->button == 1 && !priv->scroll_wheel_zoom && + !(event->state & GDK_CONTROL_MASK)) + break; + + if (is_image_movable (view)) { + eom_scroll_view_set_cursor (view, EOM_SCROLL_VIEW_CURSOR_DRAG); + + priv->dragging = TRUE; + priv->drag_anchor_x = event->x; + priv->drag_anchor_y = event->y; + + priv->drag_ofs_x = priv->xofs; + priv->drag_ofs_y = priv->yofs; + + return TRUE; + } + default: + break; + } + + return FALSE; +} + +static void +eom_scroll_view_style_set (GtkWidget *widget, GtkStyle *old_style) +{ + GtkStyle *style; + EomScrollViewPrivate *priv; + + style = gtk_widget_get_style (widget); + priv = EOM_SCROLL_VIEW (widget)->priv; + + gtk_widget_set_style (priv->display, style); +} + + +/* Button release event handler for the image view */ +static gboolean +eom_scroll_view_button_release_event (GtkWidget *widget, GdkEventButton *event, gpointer data) +{ + EomScrollView *view; + EomScrollViewPrivate *priv; + + view = EOM_SCROLL_VIEW (data); + priv = view->priv; + + if (!priv->dragging) + return FALSE; + + switch (event->button) { + case 1: + case 2: + drag_to (view, event->x, event->y); + priv->dragging = FALSE; + + eom_scroll_view_set_cursor (view, EOM_SCROLL_VIEW_CURSOR_NORMAL); + break; + + default: + break; + } + + return TRUE; +} + +/* Scroll event handler for the image view. We zoom with an event without + * modifiers rather than scroll; we use the Shift modifier to scroll. + * Rationale: images are not primarily vertical, and in EOM you scan scroll by + * dragging the image with button 1 anyways. + */ +static gboolean +eom_scroll_view_scroll_event (GtkWidget *widget, GdkEventScroll *event, gpointer data) +{ + EomScrollView *view; + EomScrollViewPrivate *priv; + double zoom_factor; + int xofs, yofs; + + view = EOM_SCROLL_VIEW (data); + priv = view->priv; + + /* Compute zoom factor and scrolling offsets; we'll only use either of them */ + /* same as in gtkscrolledwindow.c */ + xofs = gtk_adjustment_get_page_increment (priv->hadj) / 2; + yofs = gtk_adjustment_get_page_increment (priv->vadj) / 2; + + switch (event->direction) { + case GDK_SCROLL_UP: + zoom_factor = priv->zoom_multiplier; + xofs = 0; + yofs = -yofs; + break; + + case GDK_SCROLL_LEFT: + zoom_factor = 1.0 / priv->zoom_multiplier; + xofs = -xofs; + yofs = 0; + break; + + case GDK_SCROLL_DOWN: + zoom_factor = 1.0 / priv->zoom_multiplier; + xofs = 0; + yofs = yofs; + break; + + case GDK_SCROLL_RIGHT: + zoom_factor = priv->zoom_multiplier; + xofs = xofs; + yofs = 0; + break; + + default: + g_assert_not_reached (); + return FALSE; + } + + if (priv->scroll_wheel_zoom) { + if (event->state & GDK_SHIFT_MASK) + scroll_by (view, yofs, xofs); + else if (event->state & GDK_CONTROL_MASK) + scroll_by (view, xofs, yofs); + else + set_zoom (view, priv->zoom * zoom_factor, + TRUE, event->x, event->y); + } else { + if (event->state & GDK_SHIFT_MASK) + scroll_by (view, yofs, xofs); + else if (event->state & GDK_CONTROL_MASK) + set_zoom (view, priv->zoom * zoom_factor, + TRUE, event->x, event->y); + else + scroll_by (view, xofs, yofs); + } + + return TRUE; +} + +/* Motion event handler for the image view */ +static gboolean +eom_scroll_view_motion_event (GtkWidget *widget, GdkEventMotion *event, gpointer data) +{ + EomScrollView *view; + EomScrollViewPrivate *priv; + gint x, y; + GdkModifierType mods; + + view = EOM_SCROLL_VIEW (data); + priv = view->priv; + + if (!priv->dragging) + return FALSE; + + if (event->is_hint) + gdk_window_get_pointer (gtk_widget_get_window (GTK_WIDGET (priv->display)), &x, &y, &mods); + else { + x = event->x; + y = event->y; + } + + drag_to (view, x, y); + return TRUE; +} + +static void +display_map_event (GtkWidget *widget, GdkEvent *event, gpointer data) +{ + EomScrollView *view; + EomScrollViewPrivate *priv; + + view = EOM_SCROLL_VIEW (data); + priv = view->priv; + + eom_debug (DEBUG_WINDOW); + + set_zoom_fit (view); + check_scrollbar_visibility (view, NULL); + gtk_widget_queue_draw (GTK_WIDGET (priv->display)); +} + +static void +eom_scroll_view_size_allocate (GtkWidget *widget, GtkAllocation *alloc) +{ + EomScrollView *view; + + view = EOM_SCROLL_VIEW (widget); + check_scrollbar_visibility (view, alloc); + + GTK_WIDGET_CLASS (eom_scroll_view_parent_class)->size_allocate (widget + ,alloc); +} + +static void +display_size_change (GtkWidget *widget, GdkEventConfigure *event, gpointer data) +{ + EomScrollView *view; + EomScrollViewPrivate *priv; + + view = EOM_SCROLL_VIEW (data); + priv = view->priv; + + if (priv->zoom_mode == ZOOM_MODE_FIT) { + GtkAllocation alloc; + + alloc.width = event->width; + alloc.height = event->height; + + set_zoom_fit (view); + check_scrollbar_visibility (view, &alloc); + gtk_widget_queue_draw (GTK_WIDGET (priv->display)); + } else { + int scaled_width, scaled_height; + int x_offset = 0; + int y_offset = 0; + + compute_scaled_size (view, priv->zoom, &scaled_width, &scaled_height); + + if (priv->xofs + event->width > scaled_width) + x_offset = scaled_width - event->width - priv->xofs; + + if (priv->yofs + event->height > scaled_height) + y_offset = scaled_height - event->height - priv->yofs; + + scroll_by (view, x_offset, y_offset); + } + + update_scrollbar_values (view); +} + + +static gboolean +eom_scroll_view_focus_in_event (GtkWidget *widget, + GdkEventFocus *event, + gpointer data) +{ + g_signal_stop_emission_by_name (G_OBJECT (widget), "focus_in_event"); + return FALSE; +} + +static gboolean +eom_scroll_view_focus_out_event (GtkWidget *widget, + GdkEventFocus *event, + gpointer data) +{ + g_signal_stop_emission_by_name (G_OBJECT (widget), "focus_out_event"); + return FALSE; +} + +/* Expose event handler for the drawing area. First we process the whole dirty + * region by drawing a non-interpolated version, which is "instantaneous", and + * we do this synchronously. Then, if we are set to use interpolation, we queue + * an idle handler to handle interpolated drawing there. + */ +static gboolean +display_expose_event (GtkWidget *widget, GdkEventExpose *event, gpointer data) +{ + EomScrollView *view; + GdkRectangle *rects; + gint n_rects; + int i; + + g_return_val_if_fail (GTK_IS_DRAWING_AREA (widget), FALSE); + g_return_val_if_fail (event != NULL, FALSE); + g_return_val_if_fail (EOM_IS_SCROLL_VIEW (data), FALSE); + + view = EOM_SCROLL_VIEW (data); + + gdk_region_get_rectangles (event->region, &rects, &n_rects); + + for (i = 0; i < n_rects; i++) { + request_paint_area (view, rects + i); + } + + g_free (rects); + + return TRUE; +} + + +/*================================== + + image loading callbacks + + -----------------------------------*/ +/* +static void +image_loading_update_cb (EomImage *img, int x, int y, int width, int height, gpointer data) +{ + EomScrollView *view; + EomScrollViewPrivate *priv; + GdkRectangle area; + int xofs, yofs; + int sx0, sy0, sx1, sy1; + + view = (EomScrollView*) data; + priv = view->priv; + + eom_debug_message (DEBUG_IMAGE_LOAD, "x: %i, y: %i, width: %i, height: %i\n", + x, y, width, height); + + if (priv->pixbuf == NULL) { + priv->pixbuf = eom_image_get_pixbuf (img); + set_zoom_fit (view); + check_scrollbar_visibility (view, NULL); + } + priv->progressive_state = PROGRESSIVE_LOADING; + + get_image_offsets (view, &xofs, &yofs); + + sx0 = floor (x * priv->zoom + xofs); + sy0 = floor (y * priv->zoom + yofs); + sx1 = ceil ((x + width) * priv->zoom + xofs); + sy1 = ceil ((y + height) * priv->zoom + yofs); + + area.x = sx0; + area.y = sy0; + area.width = sx1 - sx0; + area.height = sy1 - sy0; + + if (GTK_WIDGET_DRAWABLE (priv->display)) + gdk_window_invalidate_rect (GTK_WIDGET (priv->display)->window, &area, FALSE); +} + + +static void +image_loading_finished_cb (EomImage *img, gpointer data) +{ + EomScrollView *view; + EomScrollViewPrivate *priv; + + view = (EomScrollView*) data; + priv = view->priv; + + if (priv->pixbuf == NULL) { + priv->pixbuf = eom_image_get_pixbuf (img); + priv->progressive_state = PROGRESSIVE_NONE; + set_zoom_fit (view); + check_scrollbar_visibility (view, NULL); + gtk_widget_queue_draw (GTK_WIDGET (priv->display)); + + } + else if (priv->interp_type != GDK_INTERP_NEAREST && + !is_unity_zoom (view)) + { + // paint antialiased image version + priv->progressive_state = PROGRESSIVE_POLISHING; + gtk_widget_queue_draw (GTK_WIDGET (priv->display)); + } +} + +static void +image_loading_failed_cb (EomImage *img, char *msg, gpointer data) +{ + EomScrollViewPrivate *priv; + + priv = EOM_SCROLL_VIEW (data)->priv; + + g_print ("loading failed: %s.\n", msg); + + if (priv->pixbuf != 0) { + g_object_unref (priv->pixbuf); + priv->pixbuf = 0; + } + + if (GTK_WIDGET_DRAWABLE (priv->display)) { + gdk_window_clear (GTK_WIDGET (priv->display)->window); + } +} + +static void +image_loading_cancelled_cb (EomImage *img, gpointer data) +{ + EomScrollViewPrivate *priv; + + priv = EOM_SCROLL_VIEW (data)->priv; + + if (priv->pixbuf != NULL) { + g_object_unref (priv->pixbuf); + priv->pixbuf = NULL; + } + + if (GTK_WIDGET_DRAWABLE (priv->display)) { + gdk_window_clear (GTK_WIDGET (priv->display)->window); + } +} +*/ +static void +image_changed_cb (EomImage *img, gpointer data) +{ + EomScrollViewPrivate *priv; + + priv = EOM_SCROLL_VIEW (data)->priv; + + if (priv->pixbuf != NULL) { + g_object_unref (priv->pixbuf); + priv->pixbuf = NULL; + } + + priv->pixbuf = eom_image_get_pixbuf (img); + + set_zoom_fit (EOM_SCROLL_VIEW (data)); + check_scrollbar_visibility (EOM_SCROLL_VIEW (data), NULL); + + gtk_widget_queue_draw (GTK_WIDGET (priv->display)); +} + +/*=================================== + public API + ---------------------------------*/ + +void +eom_scroll_view_hide_cursor (EomScrollView *view) +{ + eom_scroll_view_set_cursor (view, EOM_SCROLL_VIEW_CURSOR_HIDDEN); +} + +void +eom_scroll_view_show_cursor (EomScrollView *view) +{ + eom_scroll_view_set_cursor (view, EOM_SCROLL_VIEW_CURSOR_NORMAL); +} + +/* general properties */ +void +eom_scroll_view_set_zoom_upscale (EomScrollView *view, gboolean upscale) +{ + EomScrollViewPrivate *priv; + + g_return_if_fail (EOM_IS_SCROLL_VIEW (view)); + + priv = view->priv; + + if (priv->upscale != upscale) { + priv->upscale = upscale; + + if (priv->zoom_mode == ZOOM_MODE_FIT) { + set_zoom_fit (view); + gtk_widget_queue_draw (GTK_WIDGET (priv->display)); + } + } +} + +void +eom_scroll_view_set_antialiasing_in (EomScrollView *view, gboolean state) +{ + EomScrollViewPrivate *priv; + GdkInterpType new_interp_type; + + g_return_if_fail (EOM_IS_SCROLL_VIEW (view)); + + priv = view->priv; + + new_interp_type = state ? GDK_INTERP_BILINEAR : GDK_INTERP_NEAREST; + + if (priv->interp_type_in != new_interp_type) { + priv->interp_type_in = new_interp_type; + gtk_widget_queue_draw (GTK_WIDGET (priv->display)); + } +} + +void +eom_scroll_view_set_antialiasing_out (EomScrollView *view, gboolean state) +{ + EomScrollViewPrivate *priv; + GdkInterpType new_interp_type; + + g_return_if_fail (EOM_IS_SCROLL_VIEW (view)); + + priv = view->priv; + + new_interp_type = state ? GDK_INTERP_BILINEAR : GDK_INTERP_NEAREST; + + if (priv->interp_type_out != new_interp_type) { + priv->interp_type_out = new_interp_type; + gtk_widget_queue_draw (GTK_WIDGET (priv->display)); + } +} + +void +eom_scroll_view_set_transparency (EomScrollView *view, EomTransparencyStyle style, GdkColor *color) +{ + EomScrollViewPrivate *priv; + guint32 col = 0; + guint32 red, green, blue; + gboolean changed = FALSE; + + g_return_if_fail (EOM_IS_SCROLL_VIEW (view)); + + priv = view->priv; + + if (color != NULL) { + red = (color->red >> 8) << 16; + green = (color->green >> 8) << 8; + blue = (color->blue >> 8); + col = red + green + blue; + } + + if (priv->transp_style != style) { + priv->transp_style = style; + changed = TRUE; + } + + if (priv->transp_style == EOM_TRANSP_COLOR && priv->transp_color != col) { + priv->transp_color = col; + changed = TRUE; + } + + if (changed && priv->pixbuf != NULL && gdk_pixbuf_get_has_alpha (priv->pixbuf)) { + if (priv->background_surface) { + cairo_surface_destroy (priv->background_surface); + /* Will be recreated if needed during redraw */ + priv->background_surface = NULL; + } + gtk_widget_queue_draw (GTK_WIDGET (priv->display)); + } +} + +/* zoom api */ + +static double preferred_zoom_levels[] = { + 1.0 / 100, 1.0 / 50, 1.0 / 20, + 1.0 / 10.0, 1.0 / 5.0, 1.0 / 3.0, 1.0 / 2.0, 1.0 / 1.5, + 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, + 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0 +}; +static const gint n_zoom_levels = (sizeof (preferred_zoom_levels) / sizeof (double)); + +void +eom_scroll_view_zoom_in (EomScrollView *view, gboolean smooth) +{ + EomScrollViewPrivate *priv; + double zoom; + + g_return_if_fail (EOM_IS_SCROLL_VIEW (view)); + + priv = view->priv; + + if (smooth) { + zoom = priv->zoom * priv->zoom_multiplier; + } + else { + int i; + int index = -1; + + for (i = 0; i < n_zoom_levels; i++) { + if (preferred_zoom_levels [i] - priv->zoom + > DOUBLE_EQUAL_MAX_DIFF) { + index = i; + break; + } + } + + if (index == -1) { + zoom = priv->zoom; + } + else { + zoom = preferred_zoom_levels [i]; + } + } + set_zoom (view, zoom, FALSE, 0, 0); + +} + +void +eom_scroll_view_zoom_out (EomScrollView *view, gboolean smooth) +{ + EomScrollViewPrivate *priv; + double zoom; + + g_return_if_fail (EOM_IS_SCROLL_VIEW (view)); + + priv = view->priv; + + if (smooth) { + zoom = priv->zoom / priv->zoom_multiplier; + } + else { + int i; + int index = -1; + + for (i = n_zoom_levels - 1; i >= 0; i--) { + if (priv->zoom - preferred_zoom_levels [i] + > DOUBLE_EQUAL_MAX_DIFF) { + index = i; + break; + } + } + if (index == -1) { + zoom = priv->zoom; + } + else { + zoom = preferred_zoom_levels [i]; + } + } + set_zoom (view, zoom, FALSE, 0, 0); +} + +void +eom_scroll_view_zoom_fit (EomScrollView *view) +{ + g_return_if_fail (EOM_IS_SCROLL_VIEW (view)); + + set_zoom_fit (view); + check_scrollbar_visibility (view, NULL); + gtk_widget_queue_draw (GTK_WIDGET (view->priv->display)); +} + +void +eom_scroll_view_set_zoom (EomScrollView *view, double zoom) +{ + g_return_if_fail (EOM_IS_SCROLL_VIEW (view)); + + set_zoom (view, zoom, FALSE, 0, 0); +} + +double +eom_scroll_view_get_zoom (EomScrollView *view) +{ + g_return_val_if_fail (EOM_IS_SCROLL_VIEW (view), 0.0); + + return view->priv->zoom; +} + +gboolean +eom_scroll_view_get_zoom_is_min (EomScrollView *view) +{ + g_return_val_if_fail (EOM_IS_SCROLL_VIEW (view), FALSE); + + set_minimum_zoom_factor (view); + + return DOUBLE_EQUAL (view->priv->zoom, MIN_ZOOM_FACTOR) || + DOUBLE_EQUAL (view->priv->zoom, view->priv->min_zoom); +} + +gboolean +eom_scroll_view_get_zoom_is_max (EomScrollView *view) +{ + g_return_val_if_fail (EOM_IS_SCROLL_VIEW (view), FALSE); + + return DOUBLE_EQUAL (view->priv->zoom, MAX_ZOOM_FACTOR); +} + +static void +display_next_frame_cb (EomImage *image, gint delay, gpointer data) +{ + EomScrollViewPrivate *priv; + EomScrollView *view; + + if (!EOM_IS_SCROLL_VIEW (data)) + return; + + view = EOM_SCROLL_VIEW (data); + priv = view->priv; + + if (priv->pixbuf != NULL) { + g_object_unref (priv->pixbuf); + priv->pixbuf = NULL; + } + + priv->pixbuf = eom_image_get_pixbuf (image); + gtk_widget_queue_draw (GTK_WIDGET (priv->display)); +} + +void +eom_scroll_view_set_image (EomScrollView *view, EomImage *image) +{ + EomScrollViewPrivate *priv; + + g_return_if_fail (EOM_IS_SCROLL_VIEW (view)); + + priv = view->priv; + + if (priv->image == image) { + return; + } + + if (priv->image != NULL) { + free_image_resources (view); + if (gtk_widget_is_drawable (priv->display) && image == NULL) { + gdk_window_clear (gtk_widget_get_window (priv->display)); + } + } + g_assert (priv->image == NULL); + g_assert (priv->pixbuf == NULL); + + priv->progressive_state = PROGRESSIVE_NONE; + if (image != NULL) { + eom_image_data_ref (image); + + if (priv->pixbuf == NULL) { + priv->pixbuf = eom_image_get_pixbuf (image); + priv->progressive_state = PROGRESSIVE_NONE; + set_zoom_fit (view); + check_scrollbar_visibility (view, NULL); + gtk_widget_queue_draw (GTK_WIDGET (priv->display)); + + } + else if ((is_zoomed_in (view) && priv->interp_type_in != GDK_INTERP_NEAREST) || + (is_zoomed_out (view) && priv->interp_type_out != GDK_INTERP_NEAREST)) + { + /* paint antialiased image version */ + priv->progressive_state = PROGRESSIVE_POLISHING; + gtk_widget_queue_draw (GTK_WIDGET (priv->display)); + } + + priv->image_changed_id = g_signal_connect (image, "changed", + (GCallback) image_changed_cb, view); + if (eom_image_is_animation (image) == TRUE ) { + eom_image_start_animation (image); + priv->frame_changed_id = g_signal_connect (image, "next-frame", + (GCallback) display_next_frame_cb, view); + } + } + + priv->image = image; +} + +gboolean +eom_scroll_view_scrollbars_visible (EomScrollView *view) +{ + if (!gtk_widget_get_visible (GTK_WIDGET (view->priv->hbar)) && + !gtk_widget_get_visible (GTK_WIDGET (view->priv->vbar))) + return FALSE; + + return TRUE; +} + +/*=================================== + object creation/freeing + ---------------------------------*/ + +static void +eom_scroll_view_init (EomScrollView *view) +{ + EomScrollViewPrivate *priv; + + priv = view->priv = EOM_SCROLL_VIEW_GET_PRIVATE (view); + + priv->zoom = 1.0; + priv->min_zoom = MIN_ZOOM_FACTOR; + priv->zoom_mode = ZOOM_MODE_FIT; + priv->upscale = FALSE; + priv->uta = NULL; + priv->interp_type_in = GDK_INTERP_BILINEAR; + priv->interp_type_out = GDK_INTERP_BILINEAR; + priv->scroll_wheel_zoom = FALSE; + priv->zoom_multiplier = IMAGE_VIEW_ZOOM_MULTIPLIER; + priv->image = NULL; + priv->pixbuf = NULL; + priv->progressive_state = PROGRESSIVE_NONE; + priv->transp_style = EOM_TRANSP_BACKGROUND; + priv->transp_color = 0; + priv->cursor = EOM_SCROLL_VIEW_CURSOR_NORMAL; + priv->menu = NULL; + priv->background_color = NULL; + priv->override_bg_color = NULL; + priv->background_surface = NULL; +} + +static void +eom_scroll_view_dispose (GObject *object) +{ + EomScrollView *view; + EomScrollViewPrivate *priv; + + g_return_if_fail (EOM_IS_SCROLL_VIEW (object)); + + view = EOM_SCROLL_VIEW (object); + priv = view->priv; + + if (priv->uta != NULL) { + eom_uta_free (priv->uta); + priv->uta = NULL; + } + + if (priv->idle_id != 0) { + g_source_remove (priv->idle_id); + priv->idle_id = 0; + } + + if (priv->background_color != NULL) { + gdk_color_free (priv->background_color); + priv->background_color = NULL; + } + + if (priv->override_bg_color != NULL) { + gdk_color_free (priv->override_bg_color); + priv->override_bg_color = NULL; + } + + if (priv->background_surface != NULL) { + cairo_surface_destroy (priv->background_surface); + priv->background_surface = NULL; + } + + free_image_resources (view); + + G_OBJECT_CLASS (eom_scroll_view_parent_class)->dispose (object); +} + +static void +eom_scroll_view_get_property (GObject *object, guint property_id, + GValue *value, GParamSpec *pspec) +{ + EomScrollView *view; + EomScrollViewPrivate *priv; + + g_return_if_fail (EOM_IS_SCROLL_VIEW (object)); + + view = EOM_SCROLL_VIEW (object); + priv = view->priv; + + switch (property_id) { + case PROP_USE_BG_COLOR: + g_value_set_boolean (value, priv->use_bg_color); + break; + case PROP_BACKGROUND_COLOR: + //FIXME: This doesn't really handle the NULL color. + g_value_set_boxed (value, priv->background_color); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +eom_scroll_view_set_property (GObject *object, guint property_id, + const GValue *value, GParamSpec *pspec) +{ + EomScrollView *view; + EomScrollViewPrivate *priv; + + g_return_if_fail (EOM_IS_SCROLL_VIEW (object)); + + view = EOM_SCROLL_VIEW (object); + priv = view->priv; + + switch (property_id) { + case PROP_USE_BG_COLOR: + eom_scroll_view_set_use_bg_color (view, g_value_get_boolean (value)); + break; + case PROP_BACKGROUND_COLOR: + { + const GdkColor *color = g_value_get_boxed (value); + eom_scroll_view_set_background_color (view, color); + break; + } + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + + +static void +eom_scroll_view_class_init (EomScrollViewClass *klass) +{ + GObjectClass *gobject_class; + GtkWidgetClass *widget_class; + + gobject_class = (GObjectClass*) klass; + widget_class = (GtkWidgetClass*) klass; + + gobject_class->dispose = eom_scroll_view_dispose; + gobject_class->set_property = eom_scroll_view_set_property; + gobject_class->get_property = eom_scroll_view_get_property; + + /** + * EomScrollView:background-color: + * + * This is the default background color used for painting the background + * of the image view. If set to %NULL the color is determined by the + * active GTK theme. + */ + g_object_class_install_property ( + gobject_class, PROP_BACKGROUND_COLOR, + g_param_spec_boxed ("background-color", NULL, NULL, + GDK_TYPE_COLOR, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME)); + + g_object_class_install_property ( + gobject_class, PROP_USE_BG_COLOR, + g_param_spec_boolean ("use-background-color", NULL, NULL, FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME)); + + view_signals [SIGNAL_ZOOM_CHANGED] = + g_signal_new ("zoom_changed", + EOM_TYPE_SCROLL_VIEW, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EomScrollViewClass, zoom_changed), + NULL, NULL, + eom_marshal_VOID__DOUBLE, + G_TYPE_NONE, 1, + G_TYPE_DOUBLE); + + widget_class->size_allocate = eom_scroll_view_size_allocate; + widget_class->style_set = eom_scroll_view_style_set; + + g_type_class_add_private (klass, sizeof (EomScrollViewPrivate)); +} + +static void +view_on_drag_begin_cb (GtkWidget *widget, + GdkDragContext *context, + gpointer user_data) +{ + EomScrollView *view; + EomImage *image; + GdkPixbuf *thumbnail; + gint width, height; + + view = EOM_SCROLL_VIEW (user_data); + image = view->priv->image; + + thumbnail = eom_image_get_thumbnail (image); + + if (thumbnail) { + width = gdk_pixbuf_get_width (thumbnail); + height = gdk_pixbuf_get_height (thumbnail); + gtk_drag_set_icon_pixbuf (context, thumbnail, width/2, height/2); + g_object_unref (thumbnail); + } +} + +static void +view_on_drag_data_get_cb (GtkWidget *widget, + GdkDragContext *drag_context, + GtkSelectionData *data, + guint info, + guint time, + gpointer user_data) +{ + EomScrollView *view; + EomImage *image; + gchar *uris[2]; + GFile *file; + + view = EOM_SCROLL_VIEW (user_data); + + image = view->priv->image; + + file = eom_image_get_file (image); + uris[0] = g_file_get_uri (file); + uris[1] = NULL; + + gtk_selection_data_set_uris (data, uris); + + g_free (uris[0]); + g_object_unref (file); +} + +GtkWidget* +eom_scroll_view_new (void) +{ + GtkWidget *widget; + GtkTable *table; + EomScrollView *view; + EomScrollViewPrivate *priv; + + widget = g_object_new (EOM_TYPE_SCROLL_VIEW, + "can-focus", TRUE, + "n_rows", 2, + "n_columns", 2, + "homogeneous", FALSE, + NULL); + + table = GTK_TABLE (widget); + view = EOM_SCROLL_VIEW (widget); + priv = view->priv; + + priv->hadj = GTK_ADJUSTMENT (gtk_adjustment_new (0, 100, 0, 10, 10, 100)); + g_signal_connect (priv->hadj, "value_changed", + G_CALLBACK (adjustment_changed_cb), + view); + priv->hbar = gtk_hscrollbar_new (priv->hadj); + priv->vadj = GTK_ADJUSTMENT (gtk_adjustment_new (0, 100, 0, 10, 10, 100)); + g_signal_connect (priv->vadj, "value_changed", + G_CALLBACK (adjustment_changed_cb), + view); + priv->vbar = gtk_vscrollbar_new (priv->vadj); + priv->display = g_object_new (GTK_TYPE_DRAWING_AREA, + "can-focus", TRUE, + NULL); + /* We don't want to be double-buffered as we are SuperSmart(tm) */ + gtk_widget_set_double_buffered (GTK_WIDGET (priv->display), FALSE); + + gtk_widget_add_events (GTK_WIDGET (priv->display), + GDK_EXPOSURE_MASK + | GDK_BUTTON_PRESS_MASK + | GDK_BUTTON_RELEASE_MASK + | GDK_POINTER_MOTION_MASK + | GDK_POINTER_MOTION_HINT_MASK + | GDK_SCROLL_MASK + | GDK_KEY_PRESS_MASK); + g_signal_connect (G_OBJECT (priv->display), "configure_event", G_CALLBACK (display_size_change), view); + g_signal_connect (G_OBJECT (priv->display), "expose_event", G_CALLBACK (display_expose_event), view); + g_signal_connect (G_OBJECT (priv->display), "map_event", G_CALLBACK (display_map_event), view); + g_signal_connect (G_OBJECT (priv->display), "button_press_event", G_CALLBACK (eom_scroll_view_button_press_event), view); + g_signal_connect (G_OBJECT (priv->display), "motion_notify_event", G_CALLBACK (eom_scroll_view_motion_event), view); + g_signal_connect (G_OBJECT (priv->display), "button_release_event", G_CALLBACK (eom_scroll_view_button_release_event), view); + g_signal_connect (G_OBJECT (priv->display), "scroll_event", G_CALLBACK (eom_scroll_view_scroll_event), view); + g_signal_connect (G_OBJECT (priv->display), "focus_in_event", G_CALLBACK (eom_scroll_view_focus_in_event), NULL); + g_signal_connect (G_OBJECT (priv->display), "focus_out_event", G_CALLBACK (eom_scroll_view_focus_out_event), NULL); + + g_signal_connect (G_OBJECT (widget), "key_press_event", G_CALLBACK (display_key_press_event), view); + + gtk_drag_source_set (priv->display, GDK_BUTTON1_MASK, + target_table, G_N_ELEMENTS (target_table), + GDK_ACTION_COPY); + g_signal_connect (G_OBJECT (priv->display), "drag-data-get", + G_CALLBACK (view_on_drag_data_get_cb), widget); + g_signal_connect (G_OBJECT (priv->display), "drag-begin", + G_CALLBACK (view_on_drag_begin_cb), widget); + + gtk_table_attach (table, priv->display, + 0, 1, 0, 1, + GTK_EXPAND | GTK_FILL, + GTK_EXPAND | GTK_FILL, + 0,0); + gtk_table_attach (table, priv->hbar, + 0, 1, 1, 2, + GTK_FILL, + GTK_FILL, + 0, 0); + gtk_table_attach (table, priv->vbar, + 1, 2, 0, 1, + GTK_FILL, GTK_FILL, + 0, 0); + + gtk_widget_show_all (widget); + + return widget; +} + +static void +eom_scroll_view_popup_menu (EomScrollView *view, GdkEventButton *event) +{ + GtkWidget *popup; + int button, event_time; + + popup = view->priv->menu; + + if (event) { + button = event->button; + event_time = event->time; + } else { + button = 0; + event_time = gtk_get_current_event_time (); + } + + gtk_menu_popup (GTK_MENU (popup), NULL, NULL, NULL, NULL, + button, event_time); +} + +static gboolean +view_on_button_press_event_cb (GtkWidget *view, GdkEventButton *event, + gpointer user_data) +{ + /* Ignore double-clicks and triple-clicks */ + if (event->button == 3 && event->type == GDK_BUTTON_PRESS) + { + eom_scroll_view_popup_menu (EOM_SCROLL_VIEW (view), event); + + return TRUE; + } + + return FALSE; +} + +void +eom_scroll_view_set_popup (EomScrollView *view, + GtkMenu *menu) +{ + g_return_if_fail (EOM_IS_SCROLL_VIEW (view)); + g_return_if_fail (view->priv->menu == NULL); + + view->priv->menu = g_object_ref (menu); + + gtk_menu_attach_to_widget (GTK_MENU (view->priv->menu), + GTK_WIDGET (view), + NULL); + + g_signal_connect (G_OBJECT (view), "button_press_event", + G_CALLBACK (view_on_button_press_event_cb), NULL); +} + +static gboolean +_eom_gdk_color_equal0 (const GdkColor *a, const GdkColor *b) +{ + if (a == NULL || b == NULL) + return (a == b); + + return gdk_color_equal (a, b); +} + +static gboolean +_eom_replace_gdk_color (GdkColor **dest, const GdkColor *new) +{ + GdkColor *old = *dest; + + if (_eom_gdk_color_equal0 (old, new)) + return FALSE; + + if (old != NULL) + gdk_color_free (old); + + *dest = (new) ? gdk_color_copy (new) : NULL; + + return TRUE; +} + +static void +_eom_scroll_view_update_bg_color (EomScrollView *view) +{ + const GdkColor *selected; + EomScrollViewPrivate *priv = view->priv; + + if (priv->override_bg_color) + selected = priv->override_bg_color; + else if (priv->use_bg_color) + selected = priv->background_color; + else + selected = NULL; + + if (priv->transp_style == EOM_TRANSP_BACKGROUND + && priv->background_surface != NULL) { + /* Delete the SVG background to have it recreated with + * the correct color during the next SVG redraw */ + cairo_surface_destroy (priv->background_surface); + priv->background_surface = NULL; + } + + gtk_widget_modify_bg (GTK_WIDGET (view), + GTK_STATE_NORMAL, + selected); +} + +void +eom_scroll_view_set_background_color (EomScrollView *view, + const GdkColor *color) +{ + g_return_if_fail (EOM_IS_SCROLL_VIEW (view)); + + if (_eom_replace_gdk_color (&view->priv->background_color, color)) + _eom_scroll_view_update_bg_color (view); +} + +void +eom_scroll_view_override_bg_color (EomScrollView *view, + const GdkColor *color) +{ + g_return_if_fail (EOM_IS_SCROLL_VIEW (view)); + + if (_eom_replace_gdk_color (&view->priv->override_bg_color, color)) + _eom_scroll_view_update_bg_color (view); +} + +void +eom_scroll_view_set_use_bg_color (EomScrollView *view, gboolean use) +{ + EomScrollViewPrivate *priv; + + g_return_if_fail (EOM_IS_SCROLL_VIEW (view)); + + priv = view->priv; + + if (use != priv->use_bg_color) { + priv->use_bg_color = use; + + _eom_scroll_view_update_bg_color (view); + + g_object_notify (G_OBJECT (view), "use-background-color"); + } +} + +void +eom_scroll_view_set_scroll_wheel_zoom (EomScrollView *view, + gboolean scroll_wheel_zoom) +{ + g_return_if_fail (EOM_IS_SCROLL_VIEW (view)); + + view->priv->scroll_wheel_zoom = scroll_wheel_zoom; +} + +void +eom_scroll_view_set_zoom_multiplier (EomScrollView *view, + gdouble zoom_multiplier) +{ + g_return_if_fail (EOM_IS_SCROLL_VIEW (view)); + + view->priv->zoom_multiplier = 1.0 + zoom_multiplier; +} diff --git a/src/eom-scroll-view.h b/src/eom-scroll-view.h new file mode 100644 index 0000000..56f8a1b --- /dev/null +++ b/src/eom-scroll-view.h @@ -0,0 +1,73 @@ +#ifndef _EOM_SCROLL_VIEW_H_ +#define _EOM_SCROLL_VIEW_H_ + +#include <gtk/gtk.h> +#include "eom-image.h" + +G_BEGIN_DECLS + +typedef struct _EomScrollView EomScrollView; +typedef struct _EomScrollViewClass EomScrollViewClass; +typedef struct _EomScrollViewPrivate EomScrollViewPrivate; + +#define EOM_TYPE_SCROLL_VIEW (eom_scroll_view_get_type ()) +#define EOM_SCROLL_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EOM_TYPE_SCROLL_VIEW, EomScrollView)) +#define EOM_SCROLL_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EOM_TYPE_SCROLL_VIEW, EomScrollViewClass)) +#define EOM_IS_SCROLL_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EOM_TYPE_SCROLL_VIEW)) +#define EOM_IS_SCROLL_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EOM_TYPE_SCROLL_VIEW)) + + +struct _EomScrollView { + GtkTable widget; + + EomScrollViewPrivate *priv; +}; + +struct _EomScrollViewClass { + GtkTableClass parent_class; + + void (* zoom_changed) (EomScrollView *view, double zoom); +}; + +typedef enum { + EOM_TRANSP_BACKGROUND, + EOM_TRANSP_CHECKED, + EOM_TRANSP_COLOR +} EomTransparencyStyle; + +GType eom_scroll_view_get_type (void) G_GNUC_CONST; +GtkWidget* eom_scroll_view_new (void); + +/* loading stuff */ +void eom_scroll_view_set_image (EomScrollView *view, EomImage *image); + +/* general properties */ +void eom_scroll_view_set_scroll_wheel_zoom (EomScrollView *view, gboolean scroll_wheel_zoom); +void eom_scroll_view_set_zoom_upscale (EomScrollView *view, gboolean upscale); +void eom_scroll_view_set_zoom_multiplier (EomScrollView *view, gdouble multiplier); +void eom_scroll_view_set_antialiasing_in (EomScrollView *view, gboolean state); +void eom_scroll_view_set_antialiasing_out (EomScrollView *view, gboolean state); +void eom_scroll_view_set_transparency (EomScrollView *view, EomTransparencyStyle style, GdkColor *color); +gboolean eom_scroll_view_scrollbars_visible (EomScrollView *view); +void eom_scroll_view_set_popup (EomScrollView *view, GtkMenu *menu); +void eom_scroll_view_set_background_color (EomScrollView *view, + const GdkColor *color); +void eom_scroll_view_override_bg_color (EomScrollView *view, + const GdkColor *color); +void eom_scroll_view_set_use_bg_color (EomScrollView *view, gboolean use); +/* zoom api */ +void eom_scroll_view_zoom_in (EomScrollView *view, gboolean smooth); +void eom_scroll_view_zoom_out (EomScrollView *view, gboolean smooth); +void eom_scroll_view_zoom_fit (EomScrollView *view); +void eom_scroll_view_set_zoom (EomScrollView *view, double zoom); +double eom_scroll_view_get_zoom (EomScrollView *view); +gboolean eom_scroll_view_get_zoom_is_min (EomScrollView *view); +gboolean eom_scroll_view_get_zoom_is_max (EomScrollView *view); +void eom_scroll_view_show_cursor (EomScrollView *view); +void eom_scroll_view_hide_cursor (EomScrollView *view); + +G_END_DECLS + +#endif /* _EOM_SCROLL_VIEW_H_ */ + + diff --git a/src/eom-session.c b/src/eom-session.c new file mode 100644 index 0000000..bc926e5 --- /dev/null +++ b/src/eom-session.c @@ -0,0 +1,52 @@ +/* Eye Of Mate - Session Handler + * + * Copyright (C) 2006 The Free Software Foundation + * + * Author: Lucas Rocha <[email protected]> + * + * Based on gedit code (gedit/gedit-session.h) by: + * - Gedit Team + * - Federico Mena-Quintero <[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. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "eom-session.h" +#include "eom-window.h" +#include "eom-application.h" + +void +eom_session_init (EomApplication *application) +{ + g_return_if_fail (EOM_IS_APPLICATION (application)); + + /* FIXME: Session management is currently a no-op in eom. */ +} + +gboolean +eom_session_is_restored (void) +{ + return FALSE; +} + +gboolean +eom_session_load (void) +{ + return TRUE; +} diff --git a/src/eom-session.h b/src/eom-session.h new file mode 100644 index 0000000..5cf8892 --- /dev/null +++ b/src/eom-session.h @@ -0,0 +1,46 @@ +/* Eye Of Mate - Session Handler + * + * Copyright (C) 2006 The Free Software Foundation + * + * Author: Lucas Rocha <[email protected]> + * + * Based on gedit code (gedit/gedit-session.h) by: + * - Gedit Team + * - Federico Mena-Quintero <[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 __EOM_SESSION_H__ +#define __EOM_SESSION_H__ + +#include "eom-application.h" + +#include <glib.h> + +G_BEGIN_DECLS + +G_GNUC_INTERNAL +void eom_session_init (EomApplication *application); + +G_GNUC_INTERNAL +gboolean eom_session_is_restored (void); + +G_GNUC_INTERNAL +gboolean eom_session_load (void); + +G_END_DECLS + +#endif /* __EOM_SESSION_H__ */ diff --git a/src/eom-sidebar.c b/src/eom-sidebar.c new file mode 100644 index 0000000..c536189 --- /dev/null +++ b/src/eom-sidebar.c @@ -0,0 +1,594 @@ +/* Eye of Mate - Side bar + * + * Copyright (C) 2004 Red Hat, Inc. + * Copyright (C) 2007 The Free Software Foundation + * + * Author: Lucas Rocha <[email protected]> + * + * Based on evince code (shell/ev-sidebar.c) by: + * - Jonathan Blandford <[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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <string.h> +#include <gtk/gtk.h> +#include <gdk/gdkkeysyms.h> + +#include "eom-sidebar.h" + +enum { + PROP_0, + PROP_CURRENT_PAGE +}; + +enum { + PAGE_COLUMN_TITLE, + PAGE_COLUMN_MENU_ITEM, + PAGE_COLUMN_MAIN_WIDGET, + PAGE_COLUMN_NOTEBOOK_INDEX, + PAGE_COLUMN_NUM_COLS +}; + +enum { + SIGNAL_PAGE_ADDED, + SIGNAL_PAGE_REMOVED, + SIGNAL_LAST +}; + +static gint signals[SIGNAL_LAST]; + +struct _EomSidebarPrivate { + GtkWidget *notebook; + GtkWidget *select_button; + GtkWidget *menu; + GtkWidget *hbox; + GtkWidget *label; + + GtkTreeModel *page_model; +}; + +G_DEFINE_TYPE (EomSidebar, eom_sidebar, GTK_TYPE_VBOX) + +#define EOM_SIDEBAR_GET_PRIVATE(object) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((object), EOM_TYPE_SIDEBAR, EomSidebarPrivate)) + +static void +eom_sidebar_destroy (GtkObject *object) +{ + EomSidebar *eom_sidebar = EOM_SIDEBAR (object); + + if (eom_sidebar->priv->menu) { + gtk_menu_detach (GTK_MENU (eom_sidebar->priv->menu)); + eom_sidebar->priv->menu = NULL; + } + + if (eom_sidebar->priv->page_model) { + g_object_unref (eom_sidebar->priv->page_model); + eom_sidebar->priv->page_model = NULL; + } + + (* GTK_OBJECT_CLASS (eom_sidebar_parent_class)->destroy) (object); +} + +static void +eom_sidebar_select_page (EomSidebar *eom_sidebar, GtkTreeIter *iter) +{ + gchar *title; + gint index; + + gtk_tree_model_get (eom_sidebar->priv->page_model, iter, + PAGE_COLUMN_TITLE, &title, + PAGE_COLUMN_NOTEBOOK_INDEX, &index, + -1); + + gtk_notebook_set_current_page (GTK_NOTEBOOK (eom_sidebar->priv->notebook), index); + gtk_label_set_text (GTK_LABEL (eom_sidebar->priv->label), title); + + g_free (title); +} + +void +eom_sidebar_set_page (EomSidebar *eom_sidebar, + GtkWidget *main_widget) +{ + GtkTreeIter iter; + gboolean valid; + + valid = gtk_tree_model_get_iter_first (eom_sidebar->priv->page_model, &iter); + + while (valid) { + GtkWidget *widget; + + gtk_tree_model_get (eom_sidebar->priv->page_model, &iter, + PAGE_COLUMN_MAIN_WIDGET, &widget, + -1); + + if (widget == main_widget) { + eom_sidebar_select_page (eom_sidebar, &iter); + valid = FALSE; + } else { + valid = gtk_tree_model_iter_next (eom_sidebar->priv->page_model, &iter); + } + + g_object_unref (widget); + } + + g_object_notify (G_OBJECT (eom_sidebar), "current-page"); +} + +static GtkWidget * +eom_sidebar_get_current_page (EomSidebar *sidebar) +{ + GtkNotebook *notebook = GTK_NOTEBOOK (sidebar->priv->notebook); + + return gtk_notebook_get_nth_page + (notebook, gtk_notebook_get_current_page (notebook)); +} + +static void +eom_sidebar_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + EomSidebar *sidebar = EOM_SIDEBAR (object); + + switch (prop_id) { + case PROP_CURRENT_PAGE: + eom_sidebar_set_page (sidebar, g_value_get_object (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +eom_sidebar_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + EomSidebar *sidebar = EOM_SIDEBAR (object); + + switch (prop_id) { + case PROP_CURRENT_PAGE: + g_value_set_object (value, eom_sidebar_get_current_page (sidebar)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +eom_sidebar_class_init (EomSidebarClass *eom_sidebar_class) +{ + GObjectClass *g_object_class; + GtkWidgetClass *widget_class; + GtkObjectClass *gtk_object_klass; + + g_object_class = G_OBJECT_CLASS (eom_sidebar_class); + widget_class = GTK_WIDGET_CLASS (eom_sidebar_class); + gtk_object_klass = GTK_OBJECT_CLASS (eom_sidebar_class); + + g_type_class_add_private (g_object_class, sizeof (EomSidebarPrivate)); + + gtk_object_klass->destroy = eom_sidebar_destroy; + g_object_class->get_property = eom_sidebar_get_property; + g_object_class->set_property = eom_sidebar_set_property; + + g_object_class_install_property (g_object_class, + PROP_CURRENT_PAGE, + g_param_spec_object ("current-page", + "Current page", + "The currently visible page", + GTK_TYPE_WIDGET, + G_PARAM_READWRITE)); + + signals[SIGNAL_PAGE_ADDED] = + g_signal_new ("page-added", + EOM_TYPE_SIDEBAR, + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (EomSidebarClass, page_added), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, + 1, + GTK_TYPE_WIDGET); + + signals[SIGNAL_PAGE_REMOVED] = + g_signal_new ("page-removed", + EOM_TYPE_SIDEBAR, + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (EomSidebarClass, page_removed), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, + 1, + GTK_TYPE_WIDGET); +} + +static void +eom_sidebar_menu_position_under (GtkMenu *menu, + gint *x, + gint *y, + gboolean *push_in, + gpointer user_data) +{ + GtkWidget *widget; + GtkAllocation allocation; + + g_return_if_fail (GTK_IS_BUTTON (user_data)); + g_return_if_fail (!gtk_widget_get_has_window (user_data)); + + widget = GTK_WIDGET (user_data); + gtk_widget_get_allocation (widget, &allocation); + + gdk_window_get_origin (gtk_widget_get_window (widget), x, y); + + *x += allocation.x; + *y += allocation.y + allocation.height; + + *push_in = FALSE; +} + +static gboolean +eom_sidebar_select_button_press_cb (GtkWidget *widget, + GdkEventButton *event, + gpointer user_data) +{ + EomSidebar *eom_sidebar = EOM_SIDEBAR (user_data); + + if (event->button == 1) { + GtkRequisition requisition; + GtkAllocation allocation; + + gtk_widget_get_allocation (widget, &allocation); + + gtk_widget_set_size_request (eom_sidebar->priv->menu, -1, -1); + gtk_widget_size_request (eom_sidebar->priv->menu, &requisition); + gtk_widget_set_size_request (eom_sidebar->priv->menu, + MAX (allocation.width, + requisition.width), -1); + + gtk_widget_grab_focus (widget); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), TRUE); + + gtk_menu_popup (GTK_MENU (eom_sidebar->priv->menu), + NULL, NULL, eom_sidebar_menu_position_under, widget, + event->button, event->time); + + return TRUE; + } + + return FALSE; +} + +static gboolean +eom_sidebar_select_button_key_press_cb (GtkWidget *widget, + GdkEventKey *event, + gpointer user_data) +{ + EomSidebar *eom_sidebar = EOM_SIDEBAR (user_data); + + if (event->keyval == GDK_space || + event->keyval == GDK_KP_Space || + event->keyval == GDK_Return || + event->keyval == GDK_KP_Enter) { + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), TRUE); + + gtk_menu_popup (GTK_MENU (eom_sidebar->priv->menu), + NULL, NULL, eom_sidebar_menu_position_under, widget, + 1, event->time); + + return TRUE; + } + + return FALSE; +} + +static void +eom_sidebar_close_clicked_cb (GtkWidget *widget, + gpointer user_data) +{ + EomSidebar *eom_sidebar = EOM_SIDEBAR (user_data); + + gtk_widget_hide (GTK_WIDGET (eom_sidebar)); +} + +static void +eom_sidebar_menu_deactivate_cb (GtkWidget *widget, + gpointer user_data) +{ + GtkWidget *menu_button; + + menu_button = GTK_WIDGET (user_data); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (menu_button), FALSE); +} + +static void +eom_sidebar_menu_detach_cb (GtkWidget *widget, + GtkMenu *menu) +{ + EomSidebar *eom_sidebar = EOM_SIDEBAR (widget); + + eom_sidebar->priv->menu = NULL; +} + +static void +eom_sidebar_menu_item_activate_cb (GtkWidget *widget, + gpointer user_data) +{ + EomSidebar *eom_sidebar = EOM_SIDEBAR (user_data); + GtkTreeIter iter; + GtkWidget *menu_item, *item; + gboolean valid; + + menu_item = gtk_menu_get_active (GTK_MENU (eom_sidebar->priv->menu)); + valid = gtk_tree_model_get_iter_first (eom_sidebar->priv->page_model, &iter); + + while (valid) { + gtk_tree_model_get (eom_sidebar->priv->page_model, &iter, + PAGE_COLUMN_MENU_ITEM, &item, + -1); + + if (item == menu_item) { + eom_sidebar_select_page (eom_sidebar, &iter); + valid = FALSE; + } else { + valid = gtk_tree_model_iter_next (eom_sidebar->priv->page_model, &iter); + } + + g_object_unref (item); + } + + g_object_notify (G_OBJECT (eom_sidebar), "current-page"); +} + +static void +eom_sidebar_init (EomSidebar *eom_sidebar) +{ + GtkWidget *hbox; + GtkWidget *close_button; + GtkWidget *select_hbox; + GtkWidget *arrow; + GtkWidget *image; + + eom_sidebar->priv = EOM_SIDEBAR_GET_PRIVATE (eom_sidebar); + + /* data model */ + eom_sidebar->priv->page_model = (GtkTreeModel *) + gtk_list_store_new (PAGE_COLUMN_NUM_COLS, + G_TYPE_STRING, + GTK_TYPE_WIDGET, + GTK_TYPE_WIDGET, + G_TYPE_INT); + + /* top option menu */ + hbox = gtk_hbox_new (FALSE, 0); + eom_sidebar->priv->hbox = hbox; + gtk_box_pack_start (GTK_BOX (eom_sidebar), hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + eom_sidebar->priv->select_button = gtk_toggle_button_new (); + gtk_button_set_relief (GTK_BUTTON (eom_sidebar->priv->select_button), + GTK_RELIEF_NONE); + + g_signal_connect (eom_sidebar->priv->select_button, "button_press_event", + G_CALLBACK (eom_sidebar_select_button_press_cb), + eom_sidebar); + + g_signal_connect (eom_sidebar->priv->select_button, "key_press_event", + G_CALLBACK (eom_sidebar_select_button_key_press_cb), + eom_sidebar); + + select_hbox = gtk_hbox_new (FALSE, 0); + + eom_sidebar->priv->label = gtk_label_new (""); + + gtk_box_pack_start (GTK_BOX (select_hbox), + eom_sidebar->priv->label, + FALSE, FALSE, 0); + + gtk_widget_show (eom_sidebar->priv->label); + + arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_NONE); + gtk_box_pack_end (GTK_BOX (select_hbox), arrow, FALSE, FALSE, 0); + gtk_widget_show (arrow); + + gtk_container_add (GTK_CONTAINER (eom_sidebar->priv->select_button), select_hbox); + gtk_widget_show (select_hbox); + + gtk_box_pack_start (GTK_BOX (hbox), eom_sidebar->priv->select_button, TRUE, TRUE, 0); + gtk_widget_show (eom_sidebar->priv->select_button); + + close_button = gtk_button_new (); + + gtk_button_set_relief (GTK_BUTTON (close_button), GTK_RELIEF_NONE); + + g_signal_connect (close_button, "clicked", + G_CALLBACK (eom_sidebar_close_clicked_cb), + eom_sidebar); + + image = gtk_image_new_from_stock (GTK_STOCK_CLOSE, + GTK_ICON_SIZE_MENU); + gtk_container_add (GTK_CONTAINER (close_button), image); + gtk_widget_show (image); + + gtk_box_pack_end (GTK_BOX (hbox), close_button, FALSE, FALSE, 0); + gtk_widget_show (close_button); + + eom_sidebar->priv->menu = gtk_menu_new (); + + g_signal_connect (eom_sidebar->priv->menu, "deactivate", + G_CALLBACK (eom_sidebar_menu_deactivate_cb), + eom_sidebar->priv->select_button); + + gtk_menu_attach_to_widget (GTK_MENU (eom_sidebar->priv->menu), + GTK_WIDGET (eom_sidebar), + eom_sidebar_menu_detach_cb); + + gtk_widget_show (eom_sidebar->priv->menu); + + eom_sidebar->priv->notebook = gtk_notebook_new (); + + gtk_notebook_set_show_border (GTK_NOTEBOOK (eom_sidebar->priv->notebook), FALSE); + gtk_notebook_set_show_tabs (GTK_NOTEBOOK (eom_sidebar->priv->notebook), FALSE); + + gtk_box_pack_start (GTK_BOX (eom_sidebar), eom_sidebar->priv->notebook, + TRUE, TRUE, 0); + + gtk_widget_show (eom_sidebar->priv->notebook); +} + +GtkWidget * +eom_sidebar_new (void) +{ + GtkWidget *eom_sidebar; + + eom_sidebar = g_object_new (EOM_TYPE_SIDEBAR, NULL); + + return eom_sidebar; +} + +void +eom_sidebar_add_page (EomSidebar *eom_sidebar, + const gchar *title, + GtkWidget *main_widget) +{ + GtkTreeIter iter; + GtkWidget *menu_item; + gchar *label_title; + gint index; + + g_return_if_fail (EOM_IS_SIDEBAR (eom_sidebar)); + g_return_if_fail (GTK_IS_WIDGET (main_widget)); + + index = gtk_notebook_append_page (GTK_NOTEBOOK (eom_sidebar->priv->notebook), + main_widget, NULL); + + menu_item = gtk_image_menu_item_new_with_label (title); + + g_signal_connect (menu_item, "activate", + G_CALLBACK (eom_sidebar_menu_item_activate_cb), + eom_sidebar); + + gtk_widget_show (menu_item); + + gtk_menu_shell_append (GTK_MENU_SHELL (eom_sidebar->priv->menu), + menu_item); + + /* Insert and move to end */ + gtk_list_store_insert_with_values (GTK_LIST_STORE (eom_sidebar->priv->page_model), + &iter, 0, + PAGE_COLUMN_TITLE, title, + PAGE_COLUMN_MENU_ITEM, menu_item, + PAGE_COLUMN_MAIN_WIDGET, main_widget, + PAGE_COLUMN_NOTEBOOK_INDEX, index, + -1); + + gtk_list_store_move_before (GTK_LIST_STORE(eom_sidebar->priv->page_model), + &iter, + NULL); + + /* Set the first item added as active */ + gtk_tree_model_get_iter_first (eom_sidebar->priv->page_model, &iter); + gtk_tree_model_get (eom_sidebar->priv->page_model, + &iter, + PAGE_COLUMN_TITLE, &label_title, + PAGE_COLUMN_NOTEBOOK_INDEX, &index, + -1); + + gtk_menu_set_active (GTK_MENU (eom_sidebar->priv->menu), index); + + gtk_label_set_text (GTK_LABEL (eom_sidebar->priv->label), label_title); + + gtk_notebook_set_current_page (GTK_NOTEBOOK (eom_sidebar->priv->notebook), + index); + + g_free (label_title); + + g_signal_emit (G_OBJECT (eom_sidebar), + signals[SIGNAL_PAGE_ADDED], 0, main_widget); +} + +void +eom_sidebar_remove_page (EomSidebar *eom_sidebar, GtkWidget *main_widget) +{ + GtkTreeIter iter; + GtkWidget *widget, *menu_item; + gboolean valid; + gint index; + + g_return_if_fail (EOM_IS_SIDEBAR (eom_sidebar)); + g_return_if_fail (GTK_IS_WIDGET (main_widget)); + + valid = gtk_tree_model_get_iter_first (eom_sidebar->priv->page_model, &iter); + + while (valid) { + gtk_tree_model_get (eom_sidebar->priv->page_model, &iter, + PAGE_COLUMN_NOTEBOOK_INDEX, &index, + PAGE_COLUMN_MENU_ITEM, &menu_item, + PAGE_COLUMN_MAIN_WIDGET, &widget, + -1); + + if (widget == main_widget) { + break; + } else { + valid = gtk_tree_model_iter_next (eom_sidebar->priv->page_model, + &iter); + } + + g_object_unref (menu_item); + g_object_unref (widget); + } + + if (valid) { + gtk_notebook_remove_page (GTK_NOTEBOOK (eom_sidebar->priv->notebook), + index); + + gtk_container_remove (GTK_CONTAINER (eom_sidebar->priv->menu), menu_item); + + gtk_list_store_remove (GTK_LIST_STORE (eom_sidebar->priv->page_model), + &iter); + + g_signal_emit (G_OBJECT (eom_sidebar), + signals[SIGNAL_PAGE_REMOVED], 0, main_widget); + } +} + +gint +eom_sidebar_get_n_pages (EomSidebar *eom_sidebar) +{ + g_return_val_if_fail (EOM_IS_SIDEBAR (eom_sidebar), TRUE); + + return gtk_tree_model_iter_n_children ( + GTK_TREE_MODEL (eom_sidebar->priv->page_model), NULL); +} + +gboolean +eom_sidebar_is_empty (EomSidebar *eom_sidebar) +{ + g_return_val_if_fail (EOM_IS_SIDEBAR (eom_sidebar), TRUE); + + return gtk_tree_model_iter_n_children ( + GTK_TREE_MODEL (eom_sidebar->priv->page_model), NULL) == 0; +} diff --git a/src/eom-sidebar.h b/src/eom-sidebar.h new file mode 100644 index 0000000..864c873 --- /dev/null +++ b/src/eom-sidebar.h @@ -0,0 +1,82 @@ +/* Eye of Mate - Side bar + * + * Copyright (C) 2004 Red Hat, Inc. + * Copyright (C) 2007 The Free Software Foundation + * + * Author: Lucas Rocha <[email protected]> + * + * Based on evince code (shell/ev-sidebar.h) by: + * - Jonathan Blandford <[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 __EOM_SIDEBAR_H__ +#define __EOM_SIDEBAR_H__ + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +typedef struct _EomSidebar EomSidebar; +typedef struct _EomSidebarClass EomSidebarClass; +typedef struct _EomSidebarPrivate EomSidebarPrivate; + +#define EOM_TYPE_SIDEBAR (eom_sidebar_get_type()) +#define EOM_SIDEBAR(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), EOM_TYPE_SIDEBAR, EomSidebar)) +#define EOM_SIDEBAR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), EOM_TYPE_SIDEBAR, EomSidebarClass)) +#define EOM_IS_SIDEBAR(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), EOM_TYPE_SIDEBAR)) +#define EOM_IS_SIDEBAR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), EOM_TYPE_SIDEBAR)) +#define EOM_SIDEBAR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), EOM_TYPE_SIDEBAR, EomSidebarClass)) + +struct _EomSidebar { + GtkVBox base_instance; + + EomSidebarPrivate *priv; +}; + +struct _EomSidebarClass { + GtkVBoxClass base_class; + + void (* page_added) (EomSidebar *eom_sidebar, + GtkWidget *main_widget); + + void (* page_removed) (EomSidebar *eom_sidebar, + GtkWidget *main_widget); +}; + +GType eom_sidebar_get_type (void); + +GtkWidget *eom_sidebar_new (void); + +void eom_sidebar_add_page (EomSidebar *eom_sidebar, + const gchar *title, + GtkWidget *main_widget); + +void eom_sidebar_remove_page (EomSidebar *eom_sidebar, + GtkWidget *main_widget); + +void eom_sidebar_set_page (EomSidebar *eom_sidebar, + GtkWidget *main_widget); + +gint eom_sidebar_get_n_pages (EomSidebar *eom_sidebar); + +gboolean eom_sidebar_is_empty (EomSidebar *eom_sidebar); + +G_END_DECLS + +#endif /* __EOM_SIDEBAR_H__ */ + + diff --git a/src/eom-statusbar.c b/src/eom-statusbar.c new file mode 100644 index 0000000..7b4b993 --- /dev/null +++ b/src/eom-statusbar.c @@ -0,0 +1,157 @@ +/* Eye of Mate - Statusbar + * + * Copyright (C) 2000-2006 The Free Software Foundation + * + * Author: Federico Mena-Quintero <[email protected]> + * Jens Finke <[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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "eom-statusbar.h" + +#include <string.h> +#include <glib/gi18n.h> +#include <gtk/gtk.h> + +#define EOM_STATUSBAR_GET_PRIVATE(object) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((object), EOM_TYPE_STATUSBAR, EomStatusbarPrivate)) + +G_DEFINE_TYPE (EomStatusbar, eom_statusbar, GTK_TYPE_STATUSBAR) + +struct _EomStatusbarPrivate +{ + GtkWidget *progressbar; + GtkWidget *img_num_statusbar; +}; + +static void +eom_statusbar_class_init (EomStatusbarClass *klass) +{ + GObjectClass *g_object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (g_object_class, sizeof (EomStatusbarPrivate)); +} + +static void +eom_statusbar_init (EomStatusbar *statusbar) +{ + EomStatusbarPrivate *priv; + GtkWidget *vbox; + + statusbar->priv = EOM_STATUSBAR_GET_PRIVATE (statusbar); + priv = statusbar->priv; + + gtk_statusbar_set_has_resize_grip (GTK_STATUSBAR (statusbar), TRUE); + + priv->img_num_statusbar = gtk_statusbar_new (); + gtk_statusbar_set_has_resize_grip (GTK_STATUSBAR (priv->img_num_statusbar), FALSE); + gtk_widget_set_size_request (priv->img_num_statusbar, 100, 10); + gtk_widget_show (priv->img_num_statusbar); + + gtk_box_pack_end (GTK_BOX (statusbar), + priv->img_num_statusbar, + FALSE, + TRUE, + 0); + + vbox = gtk_vbox_new (FALSE, 0); + + gtk_box_pack_end (GTK_BOX (statusbar), + vbox, + FALSE, + FALSE, + 2); + + statusbar->priv->progressbar = gtk_progress_bar_new (); + + gtk_box_pack_end (GTK_BOX (vbox), + priv->progressbar, + TRUE, + TRUE, + 2); + + gtk_widget_set_size_request (priv->progressbar, -1, 10); + + gtk_widget_show (vbox); + + gtk_widget_hide (statusbar->priv->progressbar); + +} + +GtkWidget * +eom_statusbar_new (void) +{ + return GTK_WIDGET (g_object_new (EOM_TYPE_STATUSBAR, NULL)); +} + +void +eom_statusbar_set_image_number (EomStatusbar *statusbar, + gint num, + gint tot) +{ + gchar *msg; + + g_return_if_fail (EOM_IS_STATUSBAR (statusbar)); + + gtk_statusbar_pop (GTK_STATUSBAR (statusbar->priv->img_num_statusbar), 0); + + /* Translators: This string is displayed in the statusbar. + * The first token is the image number, the second is total image + * count. + * + * Translate to "%Id" if you want to use localized digits, or + * translate to "%d" otherwise. + * + * Note that translating this doesn't guarantee that you get localized + * digits. That needs support from your system and locale definition + * too.*/ + msg = g_strdup_printf (_("%d / %d"), num, tot); + + gtk_statusbar_push (GTK_STATUSBAR (statusbar->priv->img_num_statusbar), 0, msg); + + g_free (msg); +} + +void +eom_statusbar_set_progress (EomStatusbar *statusbar, + gdouble progress) +{ + g_return_if_fail (EOM_IS_STATUSBAR (statusbar)); + + gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (statusbar->priv->progressbar), + progress); + + if (progress > 0 && progress < 1) { + gtk_widget_show (statusbar->priv->progressbar); + gtk_widget_hide (statusbar->priv->img_num_statusbar); + } else { + gtk_widget_hide (statusbar->priv->progressbar); + gtk_widget_show (statusbar->priv->img_num_statusbar); + } +} + +void +eom_statusbar_set_has_resize_grip (EomStatusbar *statusbar, gboolean has_resize_grip) +{ + g_return_if_fail (EOM_IS_STATUSBAR (statusbar)); + + gtk_statusbar_set_has_resize_grip (GTK_STATUSBAR (statusbar), + has_resize_grip); +} diff --git a/src/eom-statusbar.h b/src/eom-statusbar.h new file mode 100644 index 0000000..13cd192 --- /dev/null +++ b/src/eom-statusbar.h @@ -0,0 +1,71 @@ +/* Eye of Mate - Statusbar + * + * Copyright (C) 2000-2006 The Free Software Foundation + * + * Author: Federico Mena-Quintero <[email protected]> + * Jens Finke <[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 __EOM_STATUSBAR_H__ +#define __EOM_STATUSBAR_H__ + +#include <glib.h> +#include <glib-object.h> +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +typedef struct _EomStatusbar EomStatusbar; +typedef struct _EomStatusbarPrivate EomStatusbarPrivate; +typedef struct _EomStatusbarClass EomStatusbarClass; + +#define EOM_TYPE_STATUSBAR (eom_statusbar_get_type ()) +#define EOM_STATUSBAR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EOM_TYPE_STATUSBAR, EomStatusbar)) +#define EOM_STATUSBAR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), EOM_TYPE_STATUSBAR, EomStatusbarClass)) +#define EOM_IS_STATUSBAR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EOM_TYPE_STATUSBAR)) +#define EOM_IS_STATUSBAR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EOM_TYPE_STATUSBAR)) +#define EOM_STATUSBAR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EOM_TYPE_STATUSBAR, EomStatusbarClass)) + +struct _EomStatusbar +{ + GtkStatusbar parent; + + EomStatusbarPrivate *priv; +}; + +struct _EomStatusbarClass +{ + GtkStatusbarClass parent_class; +}; + +GType eom_statusbar_get_type (void) G_GNUC_CONST; + +GtkWidget *eom_statusbar_new (void); + +void eom_statusbar_set_image_number (EomStatusbar *statusbar, + gint num, + gint tot); + +void eom_statusbar_set_progress (EomStatusbar *statusbar, + gdouble progress); + +void eom_statusbar_set_has_resize_grip (EomStatusbar *statusbar, + gboolean has_resize_grip); + +G_END_DECLS + +#endif /* __EOM_STATUSBAR_H__ */ diff --git a/src/eom-thumb-nav.c b/src/eom-thumb-nav.c new file mode 100644 index 0000000..7ccc352 --- /dev/null +++ b/src/eom-thumb-nav.c @@ -0,0 +1,591 @@ +/* Eye Of Mate - Thumbnail Navigator + * + * Copyright (C) 2006 The Free Software Foundation + * + * Author: Lucas Rocha <[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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "eom-thumb-nav.h" +#include "eom-thumb-view.h" + +#include <glib.h> +#include <glib/gi18n.h> +#include <glib-object.h> +#include <gtk/gtk.h> +#include <string.h> + +#define EOM_THUMB_NAV_GET_PRIVATE(object) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((object), EOM_TYPE_THUMB_NAV, EomThumbNavPrivate)) + +G_DEFINE_TYPE (EomThumbNav, eom_thumb_nav, GTK_TYPE_HBOX); + +#define EOM_THUMB_NAV_SCROLL_INC 20 +#define EOM_THUMB_NAV_SCROLL_MOVE 20 +#define EOM_THUMB_NAV_SCROLL_TIMEOUT 20 + +enum +{ + PROP_SHOW_BUTTONS = 1, + PROP_THUMB_VIEW, + PROP_MODE +}; + +struct _EomThumbNavPrivate { + EomThumbNavMode mode; + + gboolean show_buttons; + gboolean scroll_dir; + gint scroll_pos; + gint scroll_id; + + GtkWidget *button_left; + GtkWidget *button_right; + GtkWidget *sw; + GtkWidget *thumbview; + GtkAdjustment *adj; +}; + +static gboolean +eom_thumb_nav_scroll_event (GtkWidget *widget, GdkEventScroll *event, gpointer user_data) +{ + EomThumbNav *nav = EOM_THUMB_NAV (user_data); + gint inc = EOM_THUMB_NAV_SCROLL_INC * 3; + + if (nav->priv->mode != EOM_THUMB_NAV_MODE_ONE_ROW) + return FALSE; + + switch (event->direction) { + case GDK_SCROLL_UP: + case GDK_SCROLL_LEFT: + inc *= -1; + break; + + case GDK_SCROLL_DOWN: + case GDK_SCROLL_RIGHT: + break; + + default: + g_assert_not_reached (); + return FALSE; + } + + if (inc < 0) + gtk_adjustment_set_value (nav->priv->adj, MAX (0, gtk_adjustment_get_value (nav->priv->adj) + inc)); + else + gtk_adjustment_set_value (nav->priv->adj, MIN (gtk_adjustment_get_upper (nav->priv->adj) - gtk_adjustment_get_page_size (nav->priv->adj), gtk_adjustment_get_value (nav->priv->adj) + inc)); + + return TRUE; +} + +static void +eom_thumb_nav_adj_changed (GtkAdjustment *adj, gpointer user_data) +{ + EomThumbNav *nav; + EomThumbNavPrivate *priv; + gboolean ltr; + + nav = EOM_THUMB_NAV (user_data); + priv = EOM_THUMB_NAV_GET_PRIVATE (nav); + ltr = gtk_widget_get_direction (priv->sw) == GTK_TEXT_DIR_LTR; + + gtk_widget_set_sensitive (ltr ? priv->button_right : priv->button_left, + gtk_adjustment_get_value (adj) + < gtk_adjustment_get_upper (adj) + - gtk_adjustment_get_page_size (adj)); +} + +static void +eom_thumb_nav_adj_value_changed (GtkAdjustment *adj, gpointer user_data) +{ + EomThumbNav *nav; + EomThumbNavPrivate *priv; + gboolean ltr; + + nav = EOM_THUMB_NAV (user_data); + priv = EOM_THUMB_NAV_GET_PRIVATE (nav); + ltr = gtk_widget_get_direction (priv->sw) == GTK_TEXT_DIR_LTR; + + gtk_widget_set_sensitive (ltr ? priv->button_left : priv->button_right, + gtk_adjustment_get_value (adj) > 0); + + gtk_widget_set_sensitive (ltr ? priv->button_right : priv->button_left, + gtk_adjustment_get_value (adj) + < gtk_adjustment_get_upper (adj) + - gtk_adjustment_get_page_size (adj)); +} + +static gboolean +eom_thumb_nav_scroll_step (gpointer user_data) +{ + EomThumbNav *nav = EOM_THUMB_NAV (user_data); + GtkAdjustment *adj = nav->priv->adj; + gint delta; + + if (nav->priv->scroll_pos < 10) + delta = EOM_THUMB_NAV_SCROLL_INC; + else if (nav->priv->scroll_pos < 20) + delta = EOM_THUMB_NAV_SCROLL_INC * 2; + else if (nav->priv->scroll_pos < 30) + delta = EOM_THUMB_NAV_SCROLL_INC * 2 + 5; + else + delta = EOM_THUMB_NAV_SCROLL_INC * 2 + 12; + + if (!nav->priv->scroll_dir) + delta *= -1; + + if ((gint) (gtk_adjustment_get_value (adj) + (gdouble) delta) >= 0 && + (gint) (gtk_adjustment_get_value (adj) + (gdouble) delta) <= gtk_adjustment_get_upper (adj) - gtk_adjustment_get_page_size (adj)) { + gtk_adjustment_set_value(adj, + gtk_adjustment_get_value (adj) + (gdouble) delta); + nav->priv->scroll_pos++; + } else { + if (delta > 0) + gtk_adjustment_set_value (adj, + gtk_adjustment_get_upper (adj) - gtk_adjustment_get_page_size (adj)); + else + gtk_adjustment_set_value (adj, 0); + + nav->priv->scroll_pos = 0; + + return FALSE; + } + + return TRUE; +} + +static void +eom_thumb_nav_button_clicked (GtkButton *button, EomThumbNav *nav) +{ + nav->priv->scroll_pos = 0; + + nav->priv->scroll_dir = gtk_widget_get_direction (GTK_WIDGET (button)) == GTK_TEXT_DIR_LTR ? + GTK_WIDGET (button) == nav->priv->button_right : + GTK_WIDGET (button) == nav->priv->button_left; + + eom_thumb_nav_scroll_step (nav); +} + +static void +eom_thumb_nav_start_scroll (GtkButton *button, EomThumbNav *nav) +{ + nav->priv->scroll_dir = gtk_widget_get_direction (GTK_WIDGET (button)) == GTK_TEXT_DIR_LTR ? + GTK_WIDGET (button) == nav->priv->button_right : + GTK_WIDGET (button) == nav->priv->button_left; + + nav->priv->scroll_id = g_timeout_add (EOM_THUMB_NAV_SCROLL_TIMEOUT, + eom_thumb_nav_scroll_step, + nav); +} + +static void +eom_thumb_nav_stop_scroll (GtkButton *button, EomThumbNav *nav) +{ + if (nav->priv->scroll_id > 0) { + g_source_remove (nav->priv->scroll_id); + nav->priv->scroll_id = 0; + nav->priv->scroll_pos = 0; + } +} + +static void +eom_thumb_nav_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + EomThumbNav *nav = EOM_THUMB_NAV (object); + + switch (property_id) + { + case PROP_SHOW_BUTTONS: + g_value_set_boolean (value, + eom_thumb_nav_get_show_buttons (nav)); + break; + + case PROP_THUMB_VIEW: + g_value_set_object (value, nav->priv->thumbview); + break; + + case PROP_MODE: + g_value_set_int (value, + eom_thumb_nav_get_mode (nav)); + break; + } +} + +static void +eom_thumb_nav_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + EomThumbNav *nav = EOM_THUMB_NAV (object); + + switch (property_id) + { + case PROP_SHOW_BUTTONS: + eom_thumb_nav_set_show_buttons (nav, + g_value_get_boolean (value)); + break; + + case PROP_THUMB_VIEW: + nav->priv->thumbview = + GTK_WIDGET (g_value_get_object (value)); + break; + + case PROP_MODE: + eom_thumb_nav_set_mode (nav, + g_value_get_int (value)); + break; + } +} + +static GObject * +eom_thumb_nav_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_params) +{ + GObject *object; + EomThumbNavPrivate *priv; + + object = G_OBJECT_CLASS (eom_thumb_nav_parent_class)->constructor + (type, n_construct_properties, construct_params); + + priv = EOM_THUMB_NAV (object)->priv; + + if (priv->thumbview != NULL) { + gtk_container_add (GTK_CONTAINER (priv->sw), priv->thumbview); + gtk_widget_show_all (priv->sw); + } + + return object; +} + +static void +eom_thumb_nav_class_init (EomThumbNavClass *class) +{ + GObjectClass *g_object_class = (GObjectClass *) class; + + g_object_class->constructor = eom_thumb_nav_constructor; + g_object_class->get_property = eom_thumb_nav_get_property; + g_object_class->set_property = eom_thumb_nav_set_property; + + g_object_class_install_property (g_object_class, + PROP_SHOW_BUTTONS, + g_param_spec_boolean ("show-buttons", + "Show Buttons", + "Whether to show navigation buttons or not", + TRUE, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + + g_object_class_install_property (g_object_class, + PROP_THUMB_VIEW, + g_param_spec_object ("thumbview", + "Thumbnail View", + "The internal thumbnail viewer widget", + EOM_TYPE_THUMB_VIEW, + (G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READABLE | + G_PARAM_WRITABLE))); + + g_object_class_install_property (g_object_class, + PROP_MODE, + g_param_spec_int ("mode", + "Mode", + "Thumb navigator mode", + EOM_THUMB_NAV_MODE_ONE_ROW, + EOM_THUMB_NAV_MODE_MULTIPLE_ROWS, + EOM_THUMB_NAV_MODE_ONE_ROW, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + + g_type_class_add_private (g_object_class, sizeof (EomThumbNavPrivate)); +} + +static void +eom_thumb_nav_init (EomThumbNav *nav) +{ + EomThumbNavPrivate *priv; + GtkWidget *arrow; + + nav->priv = EOM_THUMB_NAV_GET_PRIVATE (nav); + + priv = nav->priv; + + priv->mode = EOM_THUMB_NAV_MODE_ONE_ROW; + + priv->show_buttons = TRUE; + + priv->button_left = gtk_button_new (); + gtk_button_set_relief (GTK_BUTTON (priv->button_left), GTK_RELIEF_NONE); + + arrow = gtk_arrow_new (GTK_ARROW_LEFT, GTK_SHADOW_ETCHED_IN); + gtk_container_add (GTK_CONTAINER (priv->button_left), arrow); + + gtk_widget_set_size_request (GTK_WIDGET (priv->button_left), 25, 0); + + gtk_box_pack_start (GTK_BOX (nav), priv->button_left, FALSE, FALSE, 0); + + g_signal_connect (priv->button_left, + "clicked", + G_CALLBACK (eom_thumb_nav_button_clicked), + nav); + + g_signal_connect (priv->button_left, + "pressed", + G_CALLBACK (eom_thumb_nav_start_scroll), + nav); + + g_signal_connect (priv->button_left, + "released", + G_CALLBACK (eom_thumb_nav_stop_scroll), + nav); + + priv->sw = gtk_scrolled_window_new (NULL, NULL); + + gtk_widget_set_name (gtk_scrolled_window_get_hscrollbar (GTK_SCROLLED_WINDOW (priv->sw)), "eom-image-collection-scrollbar"); + + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (priv->sw), + GTK_SHADOW_IN); + + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->sw), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_NEVER); + + g_signal_connect (priv->sw, + "scroll-event", + G_CALLBACK (eom_thumb_nav_scroll_event), + nav); + + priv->adj = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (priv->sw)); + + g_signal_connect (priv->adj, + "changed", + G_CALLBACK (eom_thumb_nav_adj_changed), + nav); + + g_signal_connect (priv->adj, + "value-changed", + G_CALLBACK (eom_thumb_nav_adj_value_changed), + nav); + + gtk_box_pack_start (GTK_BOX (nav), priv->sw, TRUE, TRUE, 0); + + priv->button_right = gtk_button_new (); + gtk_button_set_relief (GTK_BUTTON (priv->button_right), GTK_RELIEF_NONE); + + arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE); + gtk_container_add (GTK_CONTAINER (priv->button_right), arrow); + + gtk_widget_set_size_request (GTK_WIDGET (priv->button_right), 25, 0); + + gtk_box_pack_start (GTK_BOX (nav), priv->button_right, FALSE, FALSE, 0); + + g_signal_connect (priv->button_right, + "clicked", + G_CALLBACK (eom_thumb_nav_button_clicked), + nav); + + g_signal_connect (priv->button_right, + "pressed", + G_CALLBACK (eom_thumb_nav_start_scroll), + nav); + + g_signal_connect (priv->button_right, + "released", + G_CALLBACK (eom_thumb_nav_stop_scroll), + nav); + + gtk_adjustment_value_changed (priv->adj); +} + +/** + * eom_thumb_nav_new: + * @thumbview: an #EomThumbView to embed in the navigation widget. + * @mode: The navigation mode. + * @show_buttons: Whether to show the navigation buttons. + * + * Creates a new thumbnail navigation widget. + * + * Returns: a new #EomThumbNav object. + **/ +GtkWidget * +eom_thumb_nav_new (GtkWidget *thumbview, + EomThumbNavMode mode, + gboolean show_buttons) +{ + GObject *nav; + + nav = g_object_new (EOM_TYPE_THUMB_NAV, + "show-buttons", show_buttons, + "mode", mode, + "thumbview", thumbview, + "homogeneous", FALSE, + "spacing", 0, + NULL); + + return GTK_WIDGET (nav); +} + +/** + * eom_thumb_nav_get_show_buttons: + * @nav: an #EomThumbNav. + * + * Gets whether the navigation buttons are visible. + * + * Returns: %TRUE if the navigation buttons are visible, + * %FALSE otherwise. + **/ +gboolean +eom_thumb_nav_get_show_buttons (EomThumbNav *nav) +{ + g_return_val_if_fail (EOM_IS_THUMB_NAV (nav), FALSE); + + return nav->priv->show_buttons; +} + +/** + * eom_thumb_nav_set_show_buttons: + * @nav: an #EomThumbNav. + * @show_buttons: %TRUE to show the buttons, %FALSE to hide them. + * + * Sets whether the navigation buttons to the left and right of the + * widget should be visible. + **/ +void +eom_thumb_nav_set_show_buttons (EomThumbNav *nav, gboolean show_buttons) +{ + g_return_if_fail (EOM_IS_THUMB_NAV (nav)); + g_return_if_fail (nav->priv->button_left != NULL); + g_return_if_fail (nav->priv->button_right != NULL); + + nav->priv->show_buttons = show_buttons; + + if (show_buttons && + nav->priv->mode == EOM_THUMB_NAV_MODE_ONE_ROW) { + gtk_widget_show_all (nav->priv->button_left); + gtk_widget_show_all (nav->priv->button_right); + } else { + gtk_widget_hide_all (nav->priv->button_left); + gtk_widget_hide_all (nav->priv->button_right); + } +} + +/** + * eom_thumb_nav_get_mode: + * @nav: an #EomThumbNav. + * + * Gets the navigation mode in @nav. + * + * Returns: A value in #EomThumbNavMode. + **/ +EomThumbNavMode +eom_thumb_nav_get_mode (EomThumbNav *nav) +{ + g_return_val_if_fail (EOM_IS_THUMB_NAV (nav), FALSE); + + return nav->priv->mode; +} + +/** + * eom_thumb_nav_set_mode: + * @nav: An #EomThumbNav. + * @mode: One of #EomThumbNavMode. + * + * Sets the navigation mode in @nav. See #EomThumbNavMode for details. + **/ +void +eom_thumb_nav_set_mode (EomThumbNav *nav, EomThumbNavMode mode) +{ + EomThumbNavPrivate *priv; + + g_return_if_fail (EOM_IS_THUMB_NAV (nav)); + + priv = nav->priv; + + priv->mode = mode; + + switch (mode) + { + case EOM_THUMB_NAV_MODE_ONE_ROW: + gtk_icon_view_set_columns (GTK_ICON_VIEW (priv->thumbview), + G_MAXINT); + + gtk_widget_set_size_request (priv->thumbview, -1, -1); + eom_thumb_view_set_item_height (EOM_THUMB_VIEW (priv->thumbview), + 115); + + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->sw), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_NEVER); + + eom_thumb_nav_set_show_buttons (nav, priv->show_buttons); + + break; + + case EOM_THUMB_NAV_MODE_ONE_COLUMN: + gtk_icon_view_set_columns (GTK_ICON_VIEW (priv->thumbview), 1); + + gtk_widget_set_size_request (priv->thumbview, -1, -1); + eom_thumb_view_set_item_height (EOM_THUMB_VIEW (priv->thumbview), + -1); + + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->sw), + GTK_POLICY_NEVER, + GTK_POLICY_AUTOMATIC); + + gtk_widget_hide_all (priv->button_left); + gtk_widget_hide_all (priv->button_right); + + break; + + case EOM_THUMB_NAV_MODE_MULTIPLE_ROWS: + gtk_icon_view_set_columns (GTK_ICON_VIEW (priv->thumbview), -1); + + gtk_widget_set_size_request (priv->thumbview, -1, 220); + eom_thumb_view_set_item_height (EOM_THUMB_VIEW (priv->thumbview), + -1); + + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->sw), + GTK_POLICY_NEVER, + GTK_POLICY_AUTOMATIC); + + gtk_widget_hide_all (priv->button_left); + gtk_widget_hide_all (priv->button_right); + + break; + + case EOM_THUMB_NAV_MODE_MULTIPLE_COLUMNS: + gtk_icon_view_set_columns (GTK_ICON_VIEW (priv->thumbview), -1); + + gtk_widget_set_size_request (priv->thumbview, 230, -1); + eom_thumb_view_set_item_height (EOM_THUMB_VIEW (priv->thumbview), + -1); + + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->sw), + GTK_POLICY_NEVER, + GTK_POLICY_AUTOMATIC); + + gtk_widget_hide_all (priv->button_left); + gtk_widget_hide_all (priv->button_right); + + break; + } +} diff --git a/src/eom-thumb-nav.h b/src/eom-thumb-nav.h new file mode 100644 index 0000000..0b67664 --- /dev/null +++ b/src/eom-thumb-nav.h @@ -0,0 +1,79 @@ +/* Eye Of Mate - Thumbnail Navigator + * + * Copyright (C) 2006 The Free Software Foundation + * + * Author: Lucas Rocha <[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 __EOM_THUMB_NAV_H__ +#define __EOM_THUMB_NAV_H__ + +#include "eom-thumb-view.h" + +#include <gtk/gtk.h> +#include <glib.h> +#include <glib-object.h> + +G_BEGIN_DECLS + +typedef struct _EomThumbNav EomThumbNav; +typedef struct _EomThumbNavClass EomThumbNavClass; +typedef struct _EomThumbNavPrivate EomThumbNavPrivate; + +#define EOM_TYPE_THUMB_NAV (eom_thumb_nav_get_type ()) +#define EOM_THUMB_NAV(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), EOM_TYPE_THUMB_NAV, EomThumbNav)) +#define EOM_THUMB_NAV_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), EOM_TYPE_THUMB_NAV, EomThumbNavClass)) +#define EOM_IS_THUMB_NAV(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), EOM_TYPE_THUMB_NAV)) +#define EOM_IS_THUMB_NAV_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), EOM_TYPE_THUMB_NAV)) +#define EOM_THUMB_NAV_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), EOM_TYPE_THUMB_NAV, EomThumbNavClass)) + +typedef enum { + EOM_THUMB_NAV_MODE_ONE_ROW, + EOM_THUMB_NAV_MODE_ONE_COLUMN, + EOM_THUMB_NAV_MODE_MULTIPLE_ROWS, + EOM_THUMB_NAV_MODE_MULTIPLE_COLUMNS +} EomThumbNavMode; + +struct _EomThumbNav { + GtkHBox base_instance; + + EomThumbNavPrivate *priv; +}; + +struct _EomThumbNavClass { + GtkHBoxClass parent_class; +}; + +GType eom_thumb_nav_get_type (void) G_GNUC_CONST; + +GtkWidget *eom_thumb_nav_new (GtkWidget *thumbview, + EomThumbNavMode mode, + gboolean show_buttons); + +gboolean eom_thumb_nav_get_show_buttons (EomThumbNav *nav); + +void eom_thumb_nav_set_show_buttons (EomThumbNav *nav, + gboolean show_buttons); + +EomThumbNavMode eom_thumb_nav_get_mode (EomThumbNav *nav); + +void eom_thumb_nav_set_mode (EomThumbNav *nav, + EomThumbNavMode mode); + +G_END_DECLS + +#endif /* __EOM_THUMB_NAV_H__ */ diff --git a/src/eom-thumb-view.c b/src/eom-thumb-view.c new file mode 100644 index 0000000..f0444a3 --- /dev/null +++ b/src/eom-thumb-view.c @@ -0,0 +1,918 @@ +/* Eye Of Mate - Thumbnail View + * + * Copyright (C) 2006-2008 The Free Software Foundation + * + * Author: Claudio Saavedra <[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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "eom-thumb-view.h" +#include "eom-list-store.h" +#include "eom-image.h" +#include "eom-job-queue.h" + +#ifdef HAVE_EXIF +#include "eom-exif-util.h" +#include <libexif/exif-data.h> +#endif + +#include <gtk/gtk.h> +#include <glib/gi18n.h> +#include <stdlib.h> +#include <string.h> + +#define EOM_THUMB_VIEW_SPACING 0 + +#define EOM_THUMB_VIEW_GET_PRIVATE(object) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((object), EOM_TYPE_THUMB_VIEW, EomThumbViewPrivate)) + +G_DEFINE_TYPE (EomThumbView, eom_thumb_view, GTK_TYPE_ICON_VIEW); + +static EomImage* eom_thumb_view_get_image_from_path (EomThumbView *thumbview, + GtkTreePath *path); + +static void eom_thumb_view_popup_menu (EomThumbView *widget, + GdkEventButton *event); + +struct _EomThumbViewPrivate { + gint start_thumb; /* the first visible thumbnail */ + gint end_thumb; /* the last visible thumbnail */ + GtkWidget *menu; /* a contextual menu for thumbnails */ + GtkCellRenderer *pixbuf_cell; +}; + +/* Drag 'n Drop */ + +static void +eom_thumb_view_finalize (GObject *object) +{ + EomThumbView *thumbview; + g_return_if_fail (EOM_IS_THUMB_VIEW (object)); + thumbview = EOM_THUMB_VIEW (object); + + G_OBJECT_CLASS (eom_thumb_view_parent_class)->finalize (object); +} + +static void +eom_thumb_view_destroy (GtkObject *object) +{ + EomThumbView *thumbview; + g_return_if_fail (EOM_IS_THUMB_VIEW (object)); + thumbview = EOM_THUMB_VIEW (object); + + GTK_OBJECT_CLASS (eom_thumb_view_parent_class)->destroy (object); +} + +static void +eom_thumb_view_class_init (EomThumbViewClass *class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (class); + GtkObjectClass *object_class = GTK_OBJECT_CLASS (class); + + gobject_class->finalize = eom_thumb_view_finalize; + object_class->destroy = eom_thumb_view_destroy; + + g_type_class_add_private (class, sizeof (EomThumbViewPrivate)); +} + +static void +eom_thumb_view_clear_range (EomThumbView *thumbview, + const gint start_thumb, + const gint end_thumb) +{ + GtkTreePath *path; + GtkTreeIter iter; + EomListStore *store = EOM_LIST_STORE (gtk_icon_view_get_model (GTK_ICON_VIEW (thumbview))); + gint thumb = start_thumb; + gboolean result; + + g_assert (start_thumb <= end_thumb); + + path = gtk_tree_path_new_from_indices (start_thumb, -1); + for (result = gtk_tree_model_get_iter (GTK_TREE_MODEL (store), &iter, path); + result && thumb <= end_thumb; + result = gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter), thumb++) { + eom_list_store_thumbnail_unset (store, &iter); + } + gtk_tree_path_free (path); +} + +static void +eom_thumb_view_add_range (EomThumbView *thumbview, + const gint start_thumb, + const gint end_thumb) +{ + GtkTreePath *path; + GtkTreeIter iter; + EomListStore *store = EOM_LIST_STORE (gtk_icon_view_get_model (GTK_ICON_VIEW (thumbview))); + gint thumb = start_thumb; + gboolean result; + + g_assert (start_thumb <= end_thumb); + + path = gtk_tree_path_new_from_indices (start_thumb, -1); + for (result = gtk_tree_model_get_iter (GTK_TREE_MODEL (store), &iter, path); + result && thumb <= end_thumb; + result = gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter), thumb++) { + eom_list_store_thumbnail_set (store, &iter); + } + gtk_tree_path_free (path); +} + +static void +eom_thumb_view_update_visible_range (EomThumbView *thumbview, + const gint start_thumb, + const gint end_thumb) +{ + EomThumbViewPrivate *priv = thumbview->priv; + int old_start_thumb, old_end_thumb; + + old_start_thumb= priv->start_thumb; + old_end_thumb = priv->end_thumb; + + if (start_thumb == old_start_thumb && + end_thumb == old_end_thumb) { + return; + } + + if (old_start_thumb < start_thumb) + eom_thumb_view_clear_range (thumbview, old_start_thumb, MIN (start_thumb - 1, old_end_thumb)); + + if (old_end_thumb > end_thumb) + eom_thumb_view_clear_range (thumbview, MAX (end_thumb + 1, old_start_thumb), old_end_thumb); + + eom_thumb_view_add_range (thumbview, start_thumb, end_thumb); + + priv->start_thumb = start_thumb; + priv->end_thumb = end_thumb; +} + +static void +thumbview_on_visible_range_changed_cb (EomThumbView *thumbview, + gpointer user_data) +{ + GtkTreePath *path1, *path2; + + if (!gtk_icon_view_get_visible_range (GTK_ICON_VIEW (thumbview), &path1, &path2)) { + return; + } + + if (path1 == NULL) { + path1 = gtk_tree_path_new_first (); + } + if (path2 == NULL) { + gint n_items = gtk_tree_model_iter_n_children (gtk_icon_view_get_model (GTK_ICON_VIEW (thumbview)), NULL); + path2 = gtk_tree_path_new_from_indices (n_items - 1 , -1); + } + + eom_thumb_view_update_visible_range (thumbview, gtk_tree_path_get_indices (path1) [0], + gtk_tree_path_get_indices (path2) [0]); + + gtk_tree_path_free (path1); + gtk_tree_path_free (path2); +} + +static void +thumbview_on_adjustment_changed_cb (EomThumbView *thumbview, + gpointer user_data) +{ + GtkTreePath *path1, *path2; + gint start_thumb, end_thumb; + + if (!gtk_icon_view_get_visible_range (GTK_ICON_VIEW (thumbview), &path1, &path2)) { + return; + } + + if (path1 == NULL) { + path1 = gtk_tree_path_new_first (); + } + if (path2 == NULL) { + gint n_items = gtk_tree_model_iter_n_children (gtk_icon_view_get_model (GTK_ICON_VIEW (thumbview)), NULL); + path2 = gtk_tree_path_new_from_indices (n_items - 1 , -1); + } + + start_thumb = gtk_tree_path_get_indices (path1) [0]; + end_thumb = gtk_tree_path_get_indices (path2) [0]; + + eom_thumb_view_add_range (thumbview, start_thumb, end_thumb); + + /* case we added an image, we need to make sure that the shifted thumbnail is cleared */ + eom_thumb_view_clear_range (thumbview, end_thumb + 1, end_thumb + 1); + + thumbview->priv->start_thumb = start_thumb; + thumbview->priv->end_thumb = end_thumb; + + gtk_tree_path_free (path1); + gtk_tree_path_free (path2); +} + +static void +thumbview_on_parent_set_cb (GtkWidget *widget, + GtkObject *old_parent, + gpointer user_data) +{ + EomThumbView *thumbview = EOM_THUMB_VIEW (widget); + GtkScrolledWindow *sw; + GtkAdjustment *hadjustment; + GtkAdjustment *vadjustment; + + GtkWidget *parent = gtk_widget_get_parent (GTK_WIDGET (thumbview)); + if (!GTK_IS_SCROLLED_WINDOW (parent)) { + return; + } + + /* if we have been set to a ScrolledWindow, we connect to the callback + to set and unset thumbnails. */ + sw = GTK_SCROLLED_WINDOW (parent); + hadjustment = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (sw)); + vadjustment = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (sw)); + + /* when scrolling */ + g_signal_connect_data (G_OBJECT (hadjustment), "value-changed", + G_CALLBACK (thumbview_on_visible_range_changed_cb), + thumbview, NULL, G_CONNECT_SWAPPED | G_CONNECT_AFTER); + g_signal_connect_data (G_OBJECT (vadjustment), "value-changed", + G_CALLBACK (thumbview_on_visible_range_changed_cb), + thumbview, NULL, G_CONNECT_SWAPPED | G_CONNECT_AFTER); + + /* when the adjustment is changed, ie. probably we have new images added. */ + g_signal_connect_data (G_OBJECT (hadjustment), "changed", + G_CALLBACK (thumbview_on_adjustment_changed_cb), + thumbview, NULL, G_CONNECT_SWAPPED | G_CONNECT_AFTER); + g_signal_connect_data (G_OBJECT (vadjustment), "changed", + G_CALLBACK (thumbview_on_adjustment_changed_cb), + thumbview, NULL, G_CONNECT_SWAPPED | G_CONNECT_AFTER); + + /* when resizing the scrolled window */ + g_signal_connect_swapped (G_OBJECT (sw), "size-allocate", + G_CALLBACK (thumbview_on_visible_range_changed_cb), + thumbview); +} + +static gboolean +thumbview_on_button_press_event_cb (GtkWidget *thumbview, GdkEventButton *event, + gpointer user_data) +{ + GtkTreePath *path; + + /* Ignore double-clicks and triple-clicks */ + if (event->button == 3 && event->type == GDK_BUTTON_PRESS) + { + path = gtk_icon_view_get_path_at_pos (GTK_ICON_VIEW (thumbview), + (gint) event->x, (gint) event->y); + if (path == NULL) { + return FALSE; + } + + if (!gtk_icon_view_path_is_selected (GTK_ICON_VIEW (thumbview), path) || + eom_thumb_view_get_n_selected (EOM_THUMB_VIEW (thumbview)) != 1) { + gtk_icon_view_unselect_all (GTK_ICON_VIEW (thumbview)); + gtk_icon_view_select_path (GTK_ICON_VIEW (thumbview), path); + gtk_icon_view_set_cursor (GTK_ICON_VIEW (thumbview), path, NULL, FALSE); + } + eom_thumb_view_popup_menu (EOM_THUMB_VIEW (thumbview), event); + + gtk_tree_path_free (path); + + return TRUE; + } + + return FALSE; +} + +static void +thumbview_on_drag_data_get_cb (GtkWidget *widget, + GdkDragContext *drag_context, + GtkSelectionData *data, + guint info, + guint time, + gpointer user_data) +{ + GList *list; + GList *node; + EomImage *image; + GFile *file; + gchar **uris = NULL; + gint i = 0, n_images; + + list = eom_thumb_view_get_selected_images (EOM_THUMB_VIEW (widget)); + n_images = eom_thumb_view_get_n_selected (EOM_THUMB_VIEW (widget)); + + uris = g_new (gchar *, n_images + 1); + + for (node = list; node != NULL; node = node->next, i++) { + image = EOM_IMAGE (node->data); + file = eom_image_get_file (image); + uris[i] = g_file_get_uri (file); + g_object_unref (image); + g_object_unref (file); + } + uris[i] = NULL; + + gtk_selection_data_set_uris (data, uris); + g_strfreev (uris); + g_list_free (list); +} + +static gchar * +thumbview_get_tooltip_string (EomImage *image) +{ + gchar *bytes; + char *type_str; + gint width, height; + GFile *file; + GFileInfo *file_info; + const char *mime_str; + gchar *tooltip_string; +#ifdef HAVE_EXIF + ExifData *exif_data; +#endif + + bytes = g_format_size_for_display (eom_image_get_bytes (image)); + + eom_image_get_size (image, &width, &height); + + file = eom_image_get_file (image); + file_info = g_file_query_info (file, + G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, + 0, NULL, NULL); + g_object_unref (file); + if (file_info == NULL) { + return NULL; + } + + mime_str = g_file_info_get_content_type (file_info); + + if (G_UNLIKELY (mime_str == NULL)) { + g_free (bytes); + g_object_unref (image); + return NULL; + } + + type_str = g_content_type_get_description (mime_str); + g_object_unref (file_info); + + if (width > -1 && height > -1) { + tooltip_string = g_markup_printf_escaped ("<b><big>%s</big></b>\n" + "%i x %i %s\n" + "%s\n" + "%s", + eom_image_get_caption (image), + width, + height, + ngettext ("pixel", + "pixels", + height), + bytes, + type_str); + } else { + tooltip_string = g_markup_printf_escaped ("<b><big>%s</big></b>\n" + "%s\n" + "%s", + eom_image_get_caption (image), + bytes, + type_str); + + } + +#ifdef HAVE_EXIF + exif_data = (ExifData *) eom_image_get_exif_info (image); + + if (exif_data) { + gchar *extra_info, *tmp, *date; + /* The EXIF standard says that the DATE_TIME tag is + * 20 bytes long. A 32-byte buffer should be large enough. */ + gchar time_buffer[32]; + + date = eom_exif_util_format_date ( + eom_exif_util_get_value (exif_data, EXIF_TAG_DATE_TIME_ORIGINAL, time_buffer, 32)); + + if (date) { + extra_info = g_strdup_printf ("\n%s %s", _("Taken on"), date); + + tmp = g_strconcat (tooltip_string, extra_info, NULL); + + g_free (date); + g_free (extra_info); + g_free (tooltip_string); + + tooltip_string = tmp; + } + exif_data_unref (exif_data); + } +#endif + + g_free (type_str); + g_free (bytes); + + return tooltip_string; +} + +static void +on_data_loaded_cb (EomJob *job, gpointer data) +{ + if (!job->error) { + gtk_tooltip_trigger_tooltip_query (gdk_display_get_default()); + } +} + +static gboolean +thumbview_on_query_tooltip_cb (GtkWidget *widget, + gint x, + gint y, + gboolean keyboard_mode, + GtkTooltip *tooltip, + gpointer user_data) +{ + GtkTreePath *path; + EomImage *image; + gchar *tooltip_string; + EomImageData data = 0; + + if (!gtk_icon_view_get_tooltip_context (GTK_ICON_VIEW (widget), + &x, &y, keyboard_mode, + NULL, &path, NULL)) { + return FALSE; + } + + image = eom_thumb_view_get_image_from_path (EOM_THUMB_VIEW (widget), + path); + gtk_tree_path_free (path); + + if (image == NULL) { + return FALSE; + } + + if (!eom_image_has_data (image, EOM_IMAGE_DATA_EXIF) && + eom_image_get_metadata_status (image) == EOM_IMAGE_METADATA_NOT_READ) { + data = EOM_IMAGE_DATA_EXIF; + } + + if (!eom_image_has_data (image, EOM_IMAGE_DATA_DIMENSION)) { + data |= EOM_IMAGE_DATA_DIMENSION; + } + + if (data) { + EomJob *job; + + job = eom_job_load_new (image, data); + g_signal_connect (G_OBJECT (job), "finished", + G_CALLBACK (on_data_loaded_cb), + widget); + eom_job_queue_add_job (job); + g_object_unref (image); + g_object_unref (job); + return FALSE; + } + + tooltip_string = thumbview_get_tooltip_string (image); + g_object_unref (image); + + if (tooltip_string == NULL) { + return FALSE; + } + + gtk_tooltip_set_markup (tooltip, tooltip_string); + g_free (tooltip_string); + + return TRUE; +} + +static void +eom_thumb_view_init (EomThumbView *thumbview) +{ + thumbview->priv = EOM_THUMB_VIEW_GET_PRIVATE (thumbview); + + thumbview->priv->pixbuf_cell = gtk_cell_renderer_pixbuf_new (); + + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (thumbview), + thumbview->priv->pixbuf_cell, + FALSE); + + g_object_set (thumbview->priv->pixbuf_cell, + "follow-state", FALSE, + "height", 100, + "width", 115, + "yalign", 0.5, + "xalign", 0.5, + NULL); + + gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (thumbview), + thumbview->priv->pixbuf_cell, + "pixbuf", EOM_LIST_STORE_THUMBNAIL, + NULL); + + gtk_icon_view_set_selection_mode (GTK_ICON_VIEW (thumbview), + GTK_SELECTION_MULTIPLE); + + gtk_icon_view_set_column_spacing (GTK_ICON_VIEW (thumbview), + EOM_THUMB_VIEW_SPACING); + + gtk_icon_view_set_row_spacing (GTK_ICON_VIEW (thumbview), + EOM_THUMB_VIEW_SPACING); + + g_object_set (thumbview, "has-tooltip", TRUE, NULL); + + g_signal_connect (thumbview, + "query-tooltip", + G_CALLBACK (thumbview_on_query_tooltip_cb), + NULL); + + thumbview->priv->start_thumb = 0; + thumbview->priv->end_thumb = 0; + thumbview->priv->menu = NULL; + + g_signal_connect (G_OBJECT (thumbview), "parent-set", + G_CALLBACK (thumbview_on_parent_set_cb), NULL); + + gtk_icon_view_enable_model_drag_source (GTK_ICON_VIEW (thumbview), 0, + NULL, 0, + GDK_ACTION_COPY); + gtk_drag_source_add_uri_targets (GTK_WIDGET (thumbview)); + + g_signal_connect (G_OBJECT (thumbview), "drag-data-get", + G_CALLBACK (thumbview_on_drag_data_get_cb), NULL); +} + +/** + * eom_thumb_view_new: + * + * Creates a new #EomThumbView object. + * + * Returns: a newly created #EomThumbView. + **/ +GtkWidget * +eom_thumb_view_new (void) +{ + EomThumbView *thumbview; + + thumbview = g_object_new (EOM_TYPE_THUMB_VIEW, NULL); + + return GTK_WIDGET (thumbview); +} + +/** + * eom_thumb_view_set_model: + * @thumbview: A #EomThumbView. + * @store: A #EomListStore. + * + * Sets the #EomListStore to be used with @thumbview. If an initial image + * was set during @store creation, its thumbnail will be selected and visible. + * + **/ +void +eom_thumb_view_set_model (EomThumbView *thumbview, EomListStore *store) +{ + gint index; + + g_return_if_fail (EOM_IS_THUMB_VIEW (thumbview)); + g_return_if_fail (EOM_IS_LIST_STORE (store)); + + index = eom_list_store_get_initial_pos (store); + + gtk_icon_view_set_model (GTK_ICON_VIEW (thumbview), GTK_TREE_MODEL (store)); + + if (index >= 0) { + GtkTreePath *path = gtk_tree_path_new_from_indices (index, -1); + gtk_icon_view_select_path (GTK_ICON_VIEW (thumbview), path); + gtk_icon_view_set_cursor (GTK_ICON_VIEW (thumbview), path, NULL, FALSE); + gtk_icon_view_scroll_to_path (GTK_ICON_VIEW (thumbview), path, FALSE, 0, 0); + gtk_tree_path_free (path); + } +} + +/** + * eom_thumb_view_set_item_height: + * @thumbview: A #EomThumbView. + * @height: The desired height. + * + * Sets the height of each thumbnail in @thumbview. + * + **/ +void +eom_thumb_view_set_item_height (EomThumbView *thumbview, gint height) +{ + g_return_if_fail (EOM_IS_THUMB_VIEW (thumbview)); + + g_object_set (thumbview->priv->pixbuf_cell, + "height", height, + NULL); +} + +static void +eom_thumb_view_get_n_selected_helper (GtkIconView *thumbview, + GtkTreePath *path, + gpointer data) +{ + /* data is of type (guint *) */ + (*(guint *) data) ++; +} + +/** + * eom_thumb_view_get_n_selected: + * @thumbview: An #EomThumbView. + * + * Gets the number of images that are currently selected in @thumbview. + * + * Returns: the number of selected images in @thumbview. + **/ +guint +eom_thumb_view_get_n_selected (EomThumbView *thumbview) +{ + guint count = 0; + gtk_icon_view_selected_foreach (GTK_ICON_VIEW (thumbview), + eom_thumb_view_get_n_selected_helper, + (&count)); + return count; +} + +/** + * eom_thumb_view_get_image_from_path: + * @thumbview: A #EomThumbView. + * @path: A #GtkTreePath pointing to a #EomImage in the model for @thumbview. + * + * Gets the #EomImage stored in @thumbview's #EomListStore at the position indicated + * by @path. + * + * Returns: A #EomImage. + **/ +static EomImage * +eom_thumb_view_get_image_from_path (EomThumbView *thumbview, GtkTreePath *path) +{ + GtkTreeModel *model; + GtkTreeIter iter; + EomImage *image; + + model = gtk_icon_view_get_model (GTK_ICON_VIEW (thumbview)); + gtk_tree_model_get_iter (model, &iter, path); + + gtk_tree_model_get (model, &iter, + EOM_LIST_STORE_EOM_IMAGE, &image, + -1); + + return image; +} + +/** + * eom_thumb_view_get_first_selected_image: + * @thumbview: A #EomThumbView. + * + * Returns the first selected image. Note that the returned #EomImage + * is not ensured to be really the first selected image in @thumbview, but + * generally, it will be. + * + * Returns: A #EomImage. + **/ +EomImage * +eom_thumb_view_get_first_selected_image (EomThumbView *thumbview) +{ + /* The returned list is not sorted! We need to find the + smaller tree path value => tricky and expensive. Do we really need this? + */ + EomImage *image; + GtkTreePath *path; + GList *list = gtk_icon_view_get_selected_items (GTK_ICON_VIEW (thumbview)); + + if (list == NULL) { + return NULL; + } + + path = (GtkTreePath *) (list->data); + + image = eom_thumb_view_get_image_from_path (thumbview, path); + + g_list_foreach (list, (GFunc) gtk_tree_path_free , NULL); + g_list_free (list); + + return image; +} + +/** + * eom_thumb_view_get_selected_images: + * @thumbview: A #EomThumbView. + * + * Gets a list with the currently selected images. Note that a new reference is + * hold for each image and the list must be freed with g_list_free(). + * + * Returns: A newly allocated list of #EomImage's. + **/ +GList * +eom_thumb_view_get_selected_images (EomThumbView *thumbview) +{ + GList *l, *item; + GList *list = NULL; + + GtkTreePath *path; + + l = gtk_icon_view_get_selected_items (GTK_ICON_VIEW (thumbview)); + + for (item = l; item != NULL; item = item->next) { + path = (GtkTreePath *) item->data; + list = g_list_prepend (list, eom_thumb_view_get_image_from_path (thumbview, path)); + gtk_tree_path_free (path); + } + + g_list_free (l); + list = g_list_reverse (list); + + return list; +} + +/** + * eom_thumb_view_set_current_image: + * @thumbview: A #EomThumbView. + * @image: The image to be selected. + * @deselect_other: Whether to deselect currently selected images. + * + * Changes the status of a image, marking it as currently selected. + * If @deselect_other is %TRUE, all other selected images will be + * deselected. + * + **/ +void +eom_thumb_view_set_current_image (EomThumbView *thumbview, EomImage *image, + gboolean deselect_other) +{ + GtkTreePath *path; + EomListStore *store; + gint pos; + + store = EOM_LIST_STORE (gtk_icon_view_get_model (GTK_ICON_VIEW (thumbview))); + pos = eom_list_store_get_pos_by_image (store, image); + path = gtk_tree_path_new_from_indices (pos, -1); + + if (path == NULL) { + return; + } + + if (deselect_other) { + gtk_icon_view_unselect_all (GTK_ICON_VIEW (thumbview)); + } + + gtk_icon_view_select_path (GTK_ICON_VIEW (thumbview), path); + gtk_icon_view_set_cursor (GTK_ICON_VIEW (thumbview), path, NULL, FALSE); + gtk_icon_view_scroll_to_path (GTK_ICON_VIEW (thumbview), path, FALSE, 0, 0); + + gtk_tree_path_free (path); +} + +/** + * eom_thumb_view_select_single: + * @thumbview: A #EomThumbView. + * @change: A #EomThumbViewSelectionChange, describing the + * desired selection change. + * + * Changes the current selection according to a single movement + * described by #EomThumbViewSelectionChange. If there are no + * thumbnails currently selected, one is selected according to the + * natural selection according to the #EomThumbViewSelectionChange + * used, p.g., when %EOM_THUMB_VIEW_SELECT_RIGHT is the selected change, + * the first thumbnail will be selected. + * + **/ +void +eom_thumb_view_select_single (EomThumbView *thumbview, + EomThumbViewSelectionChange change) +{ + GtkTreePath *path = NULL; + GtkTreeModel *model; + GList *list; + gint n_items; + + g_return_if_fail (EOM_IS_THUMB_VIEW (thumbview)); + + model = gtk_icon_view_get_model (GTK_ICON_VIEW (thumbview)); + + n_items = eom_list_store_length (EOM_LIST_STORE (model)); + + if (n_items == 0) { + return; + } + + if (eom_thumb_view_get_n_selected (thumbview) == 0) { + switch (change) { + case EOM_THUMB_VIEW_SELECT_CURRENT: + break; + case EOM_THUMB_VIEW_SELECT_RIGHT: + case EOM_THUMB_VIEW_SELECT_FIRST: + path = gtk_tree_path_new_first (); + break; + case EOM_THUMB_VIEW_SELECT_LEFT: + case EOM_THUMB_VIEW_SELECT_LAST: + path = gtk_tree_path_new_from_indices (n_items - 1, -1); + break; + case EOM_THUMB_VIEW_SELECT_RANDOM: + path = gtk_tree_path_new_from_indices ((int)(((float)(n_items - 1) * rand()) / (float)(RAND_MAX + 1.f)), -1); + break; + } + } else { + list = gtk_icon_view_get_selected_items (GTK_ICON_VIEW (thumbview)); + path = gtk_tree_path_copy ((GtkTreePath *) list->data); + g_list_foreach (list, (GFunc) gtk_tree_path_free , NULL); + g_list_free (list); + + gtk_icon_view_unselect_all (GTK_ICON_VIEW (thumbview)); + + switch (change) { + case EOM_THUMB_VIEW_SELECT_CURRENT: + break; + case EOM_THUMB_VIEW_SELECT_LEFT: + if (!gtk_tree_path_prev (path)) { + gtk_tree_path_free (path); + path = gtk_tree_path_new_from_indices (n_items - 1, -1); + } + break; + case EOM_THUMB_VIEW_SELECT_RIGHT: + if (gtk_tree_path_get_indices (path) [0] == n_items - 1) { + gtk_tree_path_free (path); + path = gtk_tree_path_new_first (); + } else { + gtk_tree_path_next (path); + } + break; + case EOM_THUMB_VIEW_SELECT_FIRST: + gtk_tree_path_free (path); + path = gtk_tree_path_new_first (); + break; + case EOM_THUMB_VIEW_SELECT_LAST: + gtk_tree_path_free (path); + path = gtk_tree_path_new_from_indices (n_items - 1, -1); + break; + case EOM_THUMB_VIEW_SELECT_RANDOM: + gtk_tree_path_free (path); + path = gtk_tree_path_new_from_indices ((int)(((float)(n_items - 1) * rand()) / (float)(RAND_MAX + 1.f)), -1); + break; + } + } + + gtk_icon_view_select_path (GTK_ICON_VIEW (thumbview), path); + gtk_icon_view_set_cursor (GTK_ICON_VIEW (thumbview), path, NULL, FALSE); + gtk_icon_view_scroll_to_path (GTK_ICON_VIEW (thumbview), path, FALSE, 0, 0); + gtk_tree_path_free (path); +} + + +/** + * eom_thumb_view_set_thumbnail_popup: + * @thumbview: An #EomThumbView. + * @menu: A #GtkMenu. + * + * Set the contextual menu to be used with the thumbnails in the + * widget. This can be done only once. + * + **/ +void +eom_thumb_view_set_thumbnail_popup (EomThumbView *thumbview, + GtkMenu *menu) +{ + g_return_if_fail (EOM_IS_THUMB_VIEW (thumbview)); + g_return_if_fail (thumbview->priv->menu == NULL); + + thumbview->priv->menu = g_object_ref (menu); + + gtk_menu_attach_to_widget (GTK_MENU (thumbview->priv->menu), + GTK_WIDGET (thumbview), + NULL); + + g_signal_connect (G_OBJECT (thumbview), "button_press_event", + G_CALLBACK (thumbview_on_button_press_event_cb), NULL); + +} + + +static void +eom_thumb_view_popup_menu (EomThumbView *thumbview, GdkEventButton *event) +{ + GtkWidget *popup; + int button, event_time; + + popup = thumbview->priv->menu; + + if (event) { + button = event->button; + event_time = event->time; + } else { + button = 0; + event_time = gtk_get_current_event_time (); + } + + gtk_menu_popup (GTK_MENU (popup), NULL, NULL, NULL, NULL, + button, event_time); +} diff --git a/src/eom-thumb-view.h b/src/eom-thumb-view.h new file mode 100644 index 0000000..a0f7bf1 --- /dev/null +++ b/src/eom-thumb-view.h @@ -0,0 +1,87 @@ +/* Eye Of Mate - Thumbnail View + * + * Copyright (C) 2006 The Free Software Foundation + * + * Author: Claudio Saavedra <[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 EOM_THUMB_VIEW_H +#define EOM_THUMB_VIEW_H + +#include "eom-image.h" +#include "eom-list-store.h" + +G_BEGIN_DECLS + +#define EOM_TYPE_THUMB_VIEW (eom_thumb_view_get_type ()) +#define EOM_THUMB_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EOM_TYPE_THUMB_VIEW, EomThumbView)) +#define EOM_THUMB_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EOM_TYPE_THUMB_VIEW, EomThumbViewClass)) +#define EOM_IS_THUMB_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EOM_TYPE_THUMB_VIEW)) +#define EOM_IS_THUMB_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EOM_TYPE_THUMB_VIEW)) +#define EOM_THUMB_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EOM_TYPE_THUMB_VIEW, EomThumbViewClass)) + +typedef struct _EomThumbView EomThumbView; +typedef struct _EomThumbViewClass EomThumbViewClass; +typedef struct _EomThumbViewPrivate EomThumbViewPrivate; + +typedef enum { + EOM_THUMB_VIEW_SELECT_CURRENT = 0, + EOM_THUMB_VIEW_SELECT_LEFT, + EOM_THUMB_VIEW_SELECT_RIGHT, + EOM_THUMB_VIEW_SELECT_FIRST, + EOM_THUMB_VIEW_SELECT_LAST, + EOM_THUMB_VIEW_SELECT_RANDOM +} EomThumbViewSelectionChange; + +struct _EomThumbView { + GtkIconView icon_view; + EomThumbViewPrivate *priv; +}; + +struct _EomThumbViewClass { + GtkIconViewClass icon_view_class; +}; + +GType eom_thumb_view_get_type (void) G_GNUC_CONST; + +GtkWidget *eom_thumb_view_new (void); + +void eom_thumb_view_set_model (EomThumbView *thumbview, + EomListStore *store); + +void eom_thumb_view_set_item_height (EomThumbView *thumbview, + gint height); + +guint eom_thumb_view_get_n_selected (EomThumbView *thumbview); + +EomImage *eom_thumb_view_get_first_selected_image (EomThumbView *thumbview); + +GList *eom_thumb_view_get_selected_images (EomThumbView *thumbview); + +void eom_thumb_view_select_single (EomThumbView *thumbview, + EomThumbViewSelectionChange change); + +void eom_thumb_view_set_current_image (EomThumbView *thumbview, + EomImage *image, + gboolean deselect_other); + +void eom_thumb_view_set_thumbnail_popup (EomThumbView *thumbview, + GtkMenu *menu); + +G_END_DECLS + +#endif /* EOM_THUMB_VIEW_H */ diff --git a/src/eom-thumbnail.c b/src/eom-thumbnail.c new file mode 100644 index 0000000..d84cb24 --- /dev/null +++ b/src/eom-thumbnail.c @@ -0,0 +1,509 @@ +/* Eye Of Mate - Thumbnailing functions + * + * Copyright (C) 2000-2008 The Free Software Foundation + * + * Author: Lucas Rocha <[email protected]> + * + * Based on eel code (eel/eel-graphic-effects.c) by: + * - Andy Hertzfeld <[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. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +/* We must define MATE_DESKTOP_USE_UNSTABLE_API to be able + to use MateDesktopThumbnail */ +#ifndef MATE_DESKTOP_USE_UNSTABLE_API +#define MATE_DESKTOP_USE_UNSTABLE_API +#endif +#include <libmateui/mate-desktop-thumbnail.h> + +#include "eom-thumbnail.h" +#include "eom-list-store.h" +#include "eom-debug.h" + +#define EOM_THUMB_ERROR eom_thumb_error_quark () + +static MateDesktopThumbnailFactory *factory = NULL; +static GdkPixbuf *frame = NULL; + +typedef enum { + EOM_THUMB_ERROR_VFS, + EOM_THUMB_ERROR_GENERIC, + EOM_THUMB_ERROR_UNKNOWN +} EomThumbError; + +typedef struct { + char *uri_str; + char *thumb_path; + time_t mtime; + char *mime_type; + gboolean thumb_exists; + gboolean failed_thumb_exists; + gboolean can_read; +} EomThumbData; + +static GQuark +eom_thumb_error_quark (void) +{ + static GQuark q = 0; + if (q == 0) + q = g_quark_from_static_string ("eom-thumb-error-quark"); + + return q; +} + +static void +set_vfs_error (GError **error, GError *ioerror) +{ + g_set_error (error, + EOM_THUMB_ERROR, + EOM_THUMB_ERROR_VFS, + "%s", ioerror ? ioerror->message : "VFS error making a thumbnail"); +} + +static void +set_thumb_error (GError **error, int error_id, const char *string) +{ + g_set_error (error, + EOM_THUMB_ERROR, + error_id, + "%s", string); +} + +static GdkPixbuf* +get_valid_thumbnail (EomThumbData *data, GError **error) +{ + GdkPixbuf *thumb = NULL; + + g_return_val_if_fail (data != NULL, NULL); + + /* does a thumbnail under the path exists? */ + if (data->thumb_exists) { + thumb = gdk_pixbuf_new_from_file (data->thumb_path, error); + + /* is this thumbnail file up to date? */ + if (thumb != NULL && !mate_desktop_thumbnail_is_valid (thumb, data->uri_str, data->mtime)) { + g_object_unref (thumb); + thumb = NULL; + } + } + + return thumb; +} + +static GdkPixbuf * +create_thumbnail_from_pixbuf (EomThumbData *data, + GdkPixbuf *pixbuf, + GError **error) +{ + GdkPixbuf *thumb; + gint width, height; + gfloat perc; + + g_assert (factory != NULL); + + width = gdk_pixbuf_get_width (pixbuf); + height = gdk_pixbuf_get_height (pixbuf); + + perc = CLAMP (128.0/(MAX (width, height)), 0, 1); + + thumb = mate_desktop_thumbnail_scale_down_pixbuf (pixbuf, + width*perc, + height*perc); + + return thumb; +} + +static void +eom_thumb_data_free (EomThumbData *data) +{ + if (data == NULL) + return; + + g_free (data->thumb_path); + g_free (data->mime_type); + g_free (data->uri_str); + + g_slice_free (EomThumbData, data); +} + +static EomThumbData* +eom_thumb_data_new (GFile *file, GError **error) +{ + EomThumbData *data; + GFileInfo *file_info; + GError *ioerror = NULL; + + g_return_val_if_fail (file != NULL, NULL); + g_return_val_if_fail (error != NULL && *error == NULL, NULL); + + data = g_slice_new0 (EomThumbData); + + data->uri_str = g_file_get_uri (file); + data->thumb_path = mate_desktop_thumbnail_path_for_uri (data->uri_str, MATE_DESKTOP_THUMBNAIL_SIZE_NORMAL); + + file_info = g_file_query_info (file, + G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE "," + G_FILE_ATTRIBUTE_TIME_MODIFIED "," + G_FILE_ATTRIBUTE_THUMBNAIL_PATH "," + G_FILE_ATTRIBUTE_THUMBNAILING_FAILED "," + G_FILE_ATTRIBUTE_ACCESS_CAN_READ, + 0, NULL, &ioerror); + if (file_info == NULL) + { + set_vfs_error (error, ioerror); + g_clear_error (&ioerror); + } + + if (*error == NULL) { + /* if available, copy data */ + data->mtime = g_file_info_get_attribute_uint64 (file_info, + G_FILE_ATTRIBUTE_TIME_MODIFIED); + data->mime_type = g_strdup (g_file_info_get_content_type (file_info)); + + data->thumb_exists = (g_file_info_get_attribute_byte_string (file_info, + G_FILE_ATTRIBUTE_THUMBNAIL_PATH) != NULL); + data->failed_thumb_exists = g_file_info_get_attribute_boolean (file_info, + G_FILE_ATTRIBUTE_THUMBNAILING_FAILED); + data->can_read = TRUE; + if (g_file_info_has_attribute (file_info, G_FILE_ATTRIBUTE_ACCESS_CAN_READ)) { + data->can_read = g_file_info_get_attribute_boolean (file_info, + G_FILE_ATTRIBUTE_ACCESS_CAN_READ); + } + } + else { + eom_thumb_data_free (data); + data = NULL; + g_clear_error (&ioerror); + } + + g_object_unref (file_info); + + return data; +} + +static void +draw_frame_row (GdkPixbuf *frame_image, + gint target_width, + gint source_width, + gint source_v_position, + gint dest_v_position, + GdkPixbuf *result_pixbuf, + gint left_offset, + gint height) +{ + gint remaining_width, h_offset, slab_width; + + remaining_width = target_width; + h_offset = 0; + + while (remaining_width > 0) { + slab_width = remaining_width > source_width ? + source_width : remaining_width; + + gdk_pixbuf_copy_area (frame_image, + left_offset, + source_v_position, + slab_width, + height, + result_pixbuf, + left_offset + h_offset, + dest_v_position); + + remaining_width -= slab_width; + h_offset += slab_width; + } +} + +static void +draw_frame_column (GdkPixbuf *frame_image, + gint target_height, + gint source_height, + gint source_h_position, + gint dest_h_position, + GdkPixbuf *result_pixbuf, + gint top_offset, + gint width) +{ + gint remaining_height, v_offset, slab_height; + + remaining_height = target_height; + v_offset = 0; + + while (remaining_height > 0) { + slab_height = remaining_height > source_height ? + source_height : remaining_height; + + gdk_pixbuf_copy_area (frame_image, + source_h_position, + top_offset, + width, + slab_height, + result_pixbuf, + dest_h_position, + top_offset + v_offset); + + remaining_height -= slab_height; + v_offset += slab_height; + } +} + +static GdkPixbuf * +eom_thumbnail_stretch_frame_image (GdkPixbuf *frame_image, + gint left_offset, + gint top_offset, + gint right_offset, + gint bottom_offset, + gint dest_width, + gint dest_height, + gboolean fill_flag) +{ + GdkPixbuf *result_pixbuf; + gint frame_width, frame_height; + gint target_width, target_frame_width; + gint target_height, target_frame_height; + + frame_width = gdk_pixbuf_get_width (frame_image); + frame_height = gdk_pixbuf_get_height (frame_image); + + if (fill_flag) { + result_pixbuf = gdk_pixbuf_scale_simple (frame_image, + dest_width, + dest_height, + GDK_INTERP_NEAREST); + } else { + result_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, + TRUE, + 8, + dest_width, + dest_height); + + /* Clear pixbuf to fully opaque white */ + gdk_pixbuf_fill (result_pixbuf, 0xffffffff); + } + + target_width = dest_width - left_offset - right_offset; + target_frame_width = frame_width - left_offset - right_offset; + + target_height = dest_height - top_offset - bottom_offset; + target_frame_height = frame_height - top_offset - bottom_offset; + + /* Draw the left top corner and top row */ + gdk_pixbuf_copy_area (frame_image, + 0, 0, + left_offset, + top_offset, + result_pixbuf, + 0, 0); + + draw_frame_row (frame_image, + target_width, + target_frame_width, + 0, 0, + result_pixbuf, + left_offset, + top_offset); + + /* Draw the right top corner and left column */ + gdk_pixbuf_copy_area (frame_image, + frame_width - right_offset, + 0, + right_offset, + top_offset, + result_pixbuf, + dest_width - right_offset, + 0); + + draw_frame_column (frame_image, + target_height, + target_frame_height, + 0, 0, + result_pixbuf, + top_offset, + left_offset); + + /* Draw the bottom right corner and bottom row */ + gdk_pixbuf_copy_area (frame_image, + frame_width - right_offset, + frame_height - bottom_offset, + right_offset, + bottom_offset, + result_pixbuf, + dest_width - right_offset, + dest_height - bottom_offset); + + draw_frame_row (frame_image, + target_width, + target_frame_width, + frame_height - bottom_offset, + dest_height - bottom_offset, + result_pixbuf, + left_offset, bottom_offset); + + /* Draw the bottom left corner and the right column */ + gdk_pixbuf_copy_area (frame_image, + 0, + frame_height - bottom_offset, + left_offset, + bottom_offset, + result_pixbuf, + 0, + dest_height - bottom_offset); + + draw_frame_column (frame_image, + target_height, + target_frame_height, + frame_width - right_offset, + dest_width - right_offset, + result_pixbuf, top_offset, + right_offset); + + return result_pixbuf; +} + +GdkPixbuf * +eom_thumbnail_add_frame (GdkPixbuf *thumbnail) +{ + GdkPixbuf *result_pixbuf; + gint source_width, source_height; + gint dest_width, dest_height; + + source_width = gdk_pixbuf_get_width (thumbnail); + source_height = gdk_pixbuf_get_height (thumbnail); + + dest_width = source_width + 9; + dest_height = source_height + 9; + + result_pixbuf = eom_thumbnail_stretch_frame_image (frame, + 3, 3, 6, 6, + dest_width, + dest_height, + FALSE); + + gdk_pixbuf_copy_area (thumbnail, + 0, 0, + source_width, + source_height, + result_pixbuf, + 3, 3); + + return result_pixbuf; +} + +GdkPixbuf * +eom_thumbnail_fit_to_size (GdkPixbuf *thumbnail, gint dimension) +{ + gint width, height; + + width = gdk_pixbuf_get_width (thumbnail); + height = gdk_pixbuf_get_height (thumbnail); + + if (width > dimension || height > dimension) { + GdkPixbuf *result_pixbuf; + gfloat factor; + + if (width > height) { + factor = (gfloat) dimension / (gfloat) width; + } else { + factor = (gfloat) dimension / (gfloat) height; + } + + width = MAX (width * factor, 1); + height = MAX (height * factor, 1); + + result_pixbuf = mate_desktop_thumbnail_scale_down_pixbuf (thumbnail, width, height); + + return result_pixbuf; + } + return gdk_pixbuf_copy (thumbnail); +} + +GdkPixbuf* +eom_thumbnail_load (EomImage *image, GError **error) +{ + GdkPixbuf *thumb = NULL; + GFile *file; + EomThumbData *data; + GdkPixbuf *pixbuf; + + g_return_val_if_fail (image != NULL, NULL); + g_return_val_if_fail (error != NULL && *error == NULL, NULL); + + file = eom_image_get_file (image); + data = eom_thumb_data_new (file, error); + g_object_unref (file); + + if (data == NULL) + return NULL; + + if (!data->can_read || + (data->failed_thumb_exists && mate_desktop_thumbnail_factory_has_valid_failed_thumbnail (factory, data->uri_str, data->mtime))) { + eom_debug_message (DEBUG_THUMBNAIL, "%s: bad permissions or valid failed thumbnail present",data->uri_str); + set_thumb_error (error, EOM_THUMB_ERROR_GENERIC, "Thumbnail creation failed"); + return NULL; + } + + /* check if there is already a valid cached thumbnail */ + thumb = get_valid_thumbnail (data, error); + + if (thumb != NULL) { + eom_debug_message (DEBUG_THUMBNAIL, "%s: loaded from cache",data->uri_str); + } else if (mate_desktop_thumbnail_factory_can_thumbnail (factory, data->uri_str, data->mime_type, data->mtime)) { + pixbuf = eom_image_get_pixbuf (image); + + if (pixbuf != NULL) { + /* generate a thumbnail from the in-memory image, + if we have already loaded the image */ + eom_debug_message (DEBUG_THUMBNAIL, "%s: creating from pixbuf",data->uri_str); + thumb = create_thumbnail_from_pixbuf (data, pixbuf, error); + g_object_unref (pixbuf); + } else { + /* generate a thumbnail from the file */ + eom_debug_message (DEBUG_THUMBNAIL, "%s: creating from file",data->uri_str); + thumb = mate_desktop_thumbnail_factory_generate_thumbnail (factory, data->uri_str, data->mime_type); + } + + if (thumb != NULL) { + /* Save the new thumbnail */ + mate_desktop_thumbnail_factory_save_thumbnail (factory, thumb, data->uri_str, data->mtime); + eom_debug_message (DEBUG_THUMBNAIL, "%s: normal thumbnail saved",data->uri_str); + } else { + /* Save a failed thumbnail, to stop further thumbnail attempts */ + mate_desktop_thumbnail_factory_create_failed_thumbnail (factory, data->uri_str, data->mtime); + eom_debug_message (DEBUG_THUMBNAIL, "%s: failed thumbnail saved",data->uri_str); + set_thumb_error (error, EOM_THUMB_ERROR_GENERIC, "Thumbnail creation failed"); + } + } + + eom_thumb_data_free (data); + + return thumb; +} + +void +eom_thumbnail_init (void) +{ + if (factory == NULL) { + factory = mate_desktop_thumbnail_factory_new (MATE_DESKTOP_THUMBNAIL_SIZE_NORMAL); + } + + if (frame == NULL) { + frame = gdk_pixbuf_new_from_file (EOM_DATA_DIR "/pixmaps/thumbnail-frame.png", NULL); + } +} diff --git a/src/eom-thumbnail.h b/src/eom-thumbnail.h new file mode 100644 index 0000000..68b155f --- /dev/null +++ b/src/eom-thumbnail.h @@ -0,0 +1,48 @@ +/* Eye Of Mate - Thumbnailing functions + * + * Copyright (C) 2000-2007 The Free Software Foundation + * + * Author: Lucas Rocha <[email protected]> + * + * Based on caja code (libcaja-private/caja-thumbnail.c) by: + * - Andy Hertzfeld <[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 _EOM_THUMBNAIL_H_ +#define _EOM_THUMBNAIL_H_ + +#include <gdk-pixbuf/gdk-pixbuf.h> +#include "eom-image.h" + +G_BEGIN_DECLS + +void eom_thumbnail_init (void); + +GdkPixbuf* eom_thumbnail_fit_to_size (GdkPixbuf *thumbnail, + gint dimension); + +GdkPixbuf* eom_thumbnail_add_frame (GdkPixbuf *thumbnail); + +GdkPixbuf* eom_thumbnail_load (EomImage *image, + GError **error); + +#define EOM_THUMBNAIL_ORIGINAL_WIDTH "eom-thumbnail-orig-width" +#define EOM_THUMBNAIL_ORIGINAL_HEIGHT "eom-thumbnail-orig-height" + +G_END_DECLS + +#endif /* _EOM_THUMBNAIL_H_ */ diff --git a/src/eom-transform.c b/src/eom-transform.c new file mode 100644 index 0000000..ab6935d --- /dev/null +++ b/src/eom-transform.c @@ -0,0 +1,418 @@ +/* Eye Of MATE -- Affine Transformations + * + * Copyright (C) 2003-2009 The Free Software Foundation + * + * Portions based on code from libart_lgpl by Raph Levien. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <time.h> +#include <stdlib.h> +#include <math.h> +#include <gtk/gtk.h> +#include <cairo/cairo.h> + +#include "eom-transform.h" +#include "eom-jobs.h" + +/* The number of progress updates per transformation */ +#define EOM_TRANSFORM_N_PROG_UPDATES 20 + +struct _EomTransformPrivate { + cairo_matrix_t affine; +}; + +typedef struct { + gdouble x; + gdouble y; +} EomPoint; + +/* Convert degrees into radians */ +#define EOM_DEG_TO_RAD(degree) ((degree) * (G_PI/180.0)) + +#define EOM_TRANSFORM_GET_PRIVATE(object) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((object), EOM_TYPE_TRANSFORM, EomTransformPrivate)) + +G_DEFINE_TYPE (EomTransform, eom_transform, G_TYPE_OBJECT) + +static void +eom_transform_init (EomTransform *trans) +{ + trans->priv = EOM_TRANSFORM_GET_PRIVATE (trans); +} + +static void +eom_transform_class_init (EomTransformClass *klass) +{ + g_type_class_add_private (klass, sizeof (EomTransformPrivate)); +} + +GdkPixbuf* +eom_transform_apply (EomTransform *trans, GdkPixbuf *pixbuf, EomJob *job) +{ + EomPoint dest_top_left; + EomPoint dest_bottom_right; + EomPoint vertices[4] = { {0, 0}, {1, 0}, {1, 1}, {0, 1} }; + double r_det; + int inverted [6]; + EomPoint dest; + + int src_width; + int src_height; + int src_rowstride; + int src_n_channels; + guchar *src_buffer; + + GdkPixbuf *dest_pixbuf; + int dest_width; + int dest_height; + int dest_rowstride; + int dest_n_channels; + guchar *dest_buffer; + + guchar *src_pos; + guchar *dest_pos; + int dx, dy, sx, sy; + int i, x, y; + + int progress_delta; + + g_return_val_if_fail (pixbuf != NULL, NULL); + + g_object_ref (pixbuf); + + src_width = gdk_pixbuf_get_width (pixbuf); + src_height = gdk_pixbuf_get_height (pixbuf); + src_rowstride = gdk_pixbuf_get_rowstride (pixbuf); + src_n_channels = gdk_pixbuf_get_n_channels (pixbuf); + src_buffer = gdk_pixbuf_get_pixels (pixbuf); + + /* find out the dimension of the destination pixbuf */ + dest_top_left.x = 100000; + dest_top_left.y = 100000; + dest_bottom_right.x = -100000; + dest_bottom_right.y = -100000; + + for (i = 0; i < 4; i++) { + dest.x = vertices[i].x * (src_width - 1); + dest.y = vertices[i].y * (src_height -1); + + cairo_matrix_transform_point (&trans->priv->affine, + &dest.x, &dest.y); + + dest_top_left.x = MIN (dest_top_left.x, dest.x); + dest_top_left.y = MIN (dest_top_left.y, dest.y); + + dest_bottom_right.x = MAX (dest_bottom_right.x, dest.x); + dest_bottom_right.y = MAX (dest_bottom_right.y, dest.y); + } + + /* create the resulting pixbuf */ + dest_width = abs (dest_bottom_right.x - dest_top_left.x + 1); + dest_height = abs (dest_bottom_right.y - dest_top_left.y + 1); + + dest_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, + gdk_pixbuf_get_has_alpha (pixbuf), + gdk_pixbuf_get_bits_per_sample (pixbuf), + dest_width, + dest_height); + dest_rowstride = gdk_pixbuf_get_rowstride (dest_pixbuf); + dest_n_channels = gdk_pixbuf_get_n_channels (dest_pixbuf); + dest_buffer = gdk_pixbuf_get_pixels (dest_pixbuf); + + /* invert the matrix so that we can compute the source pixel + from the target pixel and convert the values to integer + ones (faster!) FIXME: Maybe we can do some more + improvements by using special mmx/3dnow features if + available. + */ + r_det = 1.0 / (trans->priv->affine.xx * trans->priv->affine.yy - trans->priv->affine.yx * trans->priv->affine.xy); + inverted[0] = trans->priv->affine.yy * r_det; + inverted[1] = -trans->priv->affine.yx * r_det; + inverted[2] = -trans->priv->affine.xy * r_det; + inverted[3] = trans->priv->affine.xx * r_det; + inverted[4] = -trans->priv->affine.x0 * inverted[0] - trans->priv->affine.y0 * inverted[2]; + inverted[5] = -trans->priv->affine.x0 * inverted[1] - trans->priv->affine.y0 * inverted[3]; + + progress_delta = MAX (1, dest_height / EOM_TRANSFORM_N_PROG_UPDATES); + + /* + * for every destination pixel (dx,dy) compute the source pixel (sx, sy) + * and copy the color values + */ + for (y = 0, dy = dest_top_left.y; y < dest_height; y++, dy++) { + for (x = 0, dx = dest_top_left.x; x < dest_width; x++, dx++) { + + sx = dx * inverted[0] + dy * inverted[2] + inverted[4]; + sy = dx * inverted[1] + dy * inverted[3] + inverted[5]; + + if (sx >= 0 && sx < src_width && sy >= 0 && sy < src_height) { + src_pos = src_buffer + sy * src_rowstride + sx * src_n_channels; + dest_pos = dest_buffer + y * dest_rowstride + x * dest_n_channels; + + for (i = 0; i < src_n_channels; i++) { + dest_pos[i] = src_pos[i]; + } + } + } + + if (job != NULL && y % progress_delta == 0) { + gfloat progress; + + progress = (gfloat) (y + 1.0) / (gfloat) dest_height; + + eom_job_set_progress (job, progress); + } + } + + g_object_unref (pixbuf); + + if (job != NULL) { + eom_job_set_progress (job, 1.0); + } + + return dest_pixbuf; +} + +static void +_eom_cairo_matrix_copy (const cairo_matrix_t *src, cairo_matrix_t *dest) +{ + cairo_matrix_init (dest, src->xx, src->yx, src->xy, src->yy, src->x0, src->y0); +} + +#define DOUBLE_EQUAL_MAX_DIFF 1e-6 +#define DOUBLE_EQUAL(a,b) (fabs (a - b) < DOUBLE_EQUAL_MAX_DIFF) +/* art_affine_equal modified to work with cairo_matrix_t */ +static gboolean +_eom_cairo_matrix_equal (const cairo_matrix_t *a, const cairo_matrix_t *b) +{ + return (DOUBLE_EQUAL (a->xx, b->xx) && DOUBLE_EQUAL (a->yx, b->yx) && + DOUBLE_EQUAL (a->xy, b->xy) && DOUBLE_EQUAL (a->yy, b->yy) && + DOUBLE_EQUAL (a->x0, b->x0) && DOUBLE_EQUAL (a->y0, b->y0) ); +} + +/* art_affine_flip modified to work with cairo_matrix_t */ +static void +_eom_cairo_matrix_flip (cairo_matrix_t *dst, const cairo_matrix_t *src, gboolean horiz, gboolean vert) +{ + dst->xx = horiz ? -src->xx : src->xx; + dst->yx = horiz ? -src->yx : src->yx; + dst->xy = vert ? -src->xy : src->xy; + dst->yy = vert ? -src->yy : src->yy; + dst->x0 = horiz ? -src->x0 : src->x0; + dst->y0 = vert ? -src->y0 : src->y0; +} + +EomTransform* +eom_transform_reverse (EomTransform *trans) +{ + EomTransform *reverse; + + g_return_val_if_fail (EOM_IS_TRANSFORM (trans), NULL); + + reverse = EOM_TRANSFORM (g_object_new (EOM_TYPE_TRANSFORM, NULL)); + + _eom_cairo_matrix_copy (&trans->priv->affine, &reverse->priv->affine); + + g_return_val_if_fail (cairo_matrix_invert (&reverse->priv->affine) == CAIRO_STATUS_SUCCESS, reverse); + + return reverse; +} + +EomTransform* +eom_transform_compose (EomTransform *trans, EomTransform *compose) +{ + EomTransform *composition; + + g_return_val_if_fail (EOM_IS_TRANSFORM (trans), NULL); + g_return_val_if_fail (EOM_IS_TRANSFORM (compose), NULL); + + composition = EOM_TRANSFORM (g_object_new (EOM_TYPE_TRANSFORM, NULL)); + + cairo_matrix_multiply (&composition->priv->affine, + &trans->priv->affine, + &compose->priv->affine); + + return composition; +} + +gboolean +eom_transform_is_identity (EomTransform *trans) +{ + static const cairo_matrix_t identity = { 1, 0, 0, 1, 0, 0 }; + + g_return_val_if_fail (EOM_IS_TRANSFORM (trans), FALSE); + + return _eom_cairo_matrix_equal (&identity, &trans->priv->affine); +} + +EomTransform* +eom_transform_identity_new (void) +{ + EomTransform *trans; + + trans = EOM_TRANSFORM (g_object_new (EOM_TYPE_TRANSFORM, NULL)); + + cairo_matrix_init_identity (&trans->priv->affine); + + return trans; +} + +EomTransform* +eom_transform_rotate_new (int degree) +{ + EomTransform *trans; + + trans = EOM_TRANSFORM (g_object_new (EOM_TYPE_TRANSFORM, NULL)); + + cairo_matrix_init_rotate (&trans->priv->affine, EOM_DEG_TO_RAD(degree)); + + return trans; +} + +EomTransform* +eom_transform_flip_new (EomTransformType type) +{ + EomTransform *trans; + gboolean horiz, vert; + + trans = EOM_TRANSFORM (g_object_new (EOM_TYPE_TRANSFORM, NULL)); + + cairo_matrix_init_identity (&trans->priv->affine); + + horiz = (type == EOM_TRANSFORM_FLIP_HORIZONTAL); + vert = (type == EOM_TRANSFORM_FLIP_VERTICAL); + + _eom_cairo_matrix_flip (&trans->priv->affine, + &trans->priv->affine, + horiz, vert); + + return trans; +} + +EomTransform* +eom_transform_new (EomTransformType type) +{ + EomTransform *trans = NULL; + EomTransform *temp1 = NULL, *temp2 = NULL; + + switch (type) { + case EOM_TRANSFORM_NONE: + trans = eom_transform_identity_new (); + break; + case EOM_TRANSFORM_FLIP_HORIZONTAL: + trans = eom_transform_flip_new (EOM_TRANSFORM_FLIP_HORIZONTAL); + break; + case EOM_TRANSFORM_ROT_180: + trans = eom_transform_rotate_new (180); + break; + case EOM_TRANSFORM_FLIP_VERTICAL: + trans = eom_transform_flip_new (EOM_TRANSFORM_FLIP_VERTICAL); + break; + case EOM_TRANSFORM_TRANSPOSE: + temp1 = eom_transform_rotate_new (90); + temp2 = eom_transform_flip_new (EOM_TRANSFORM_FLIP_HORIZONTAL); + trans = eom_transform_compose (temp1, temp2); + g_object_unref (temp1); + g_object_unref (temp2); + break; + case EOM_TRANSFORM_ROT_90: + trans = eom_transform_rotate_new (90); + break; + case EOM_TRANSFORM_TRANSVERSE: + temp1 = eom_transform_rotate_new (90); + temp2 = eom_transform_flip_new (EOM_TRANSFORM_FLIP_VERTICAL); + trans = eom_transform_compose (temp1, temp2); + g_object_unref (temp1); + g_object_unref (temp2); + break; + case EOM_TRANSFORM_ROT_270: + trans = eom_transform_rotate_new (270); + break; + default: + trans = eom_transform_identity_new (); + break; + } + + return trans; +} + +EomTransformType +eom_transform_get_transform_type (EomTransform *trans) +{ + cairo_matrix_t affine; + EomTransformPrivate *priv; + + g_return_val_if_fail (EOM_IS_TRANSFORM (trans), EOM_TRANSFORM_NONE); + + priv = trans->priv; + + cairo_matrix_init_rotate (&affine, EOM_DEG_TO_RAD(90)); + if (_eom_cairo_matrix_equal (&affine, &priv->affine)) { + return EOM_TRANSFORM_ROT_90; + } + + cairo_matrix_init_rotate (&affine, EOM_DEG_TO_RAD(180)); + if (_eom_cairo_matrix_equal (&affine, &priv->affine)) { + return EOM_TRANSFORM_ROT_180; + } + + cairo_matrix_init_rotate (&affine, EOM_DEG_TO_RAD(270)); + if (_eom_cairo_matrix_equal (&affine, &priv->affine)) { + return EOM_TRANSFORM_ROT_270; + } + + cairo_matrix_init_identity (&affine); + _eom_cairo_matrix_flip (&affine, &affine, TRUE, FALSE); + if (_eom_cairo_matrix_equal (&affine, &priv->affine)) { + return EOM_TRANSFORM_FLIP_HORIZONTAL; + } + + cairo_matrix_init_identity (&affine); + _eom_cairo_matrix_flip (&affine, &affine, FALSE, TRUE); + if (_eom_cairo_matrix_equal (&affine, &priv->affine)) { + return EOM_TRANSFORM_FLIP_VERTICAL; + } + + cairo_matrix_init_rotate (&affine, EOM_DEG_TO_RAD(90)); + _eom_cairo_matrix_flip (&affine, &affine, TRUE, FALSE); + if (_eom_cairo_matrix_equal (&affine, &priv->affine)) { + return EOM_TRANSFORM_TRANSPOSE; + } + + cairo_matrix_init_rotate (&affine, EOM_DEG_TO_RAD(90)); + _eom_cairo_matrix_flip (&affine, &affine, FALSE, TRUE); + if (_eom_cairo_matrix_equal (&affine, &priv->affine)) { + return EOM_TRANSFORM_TRANSVERSE; + } + + return EOM_TRANSFORM_NONE; +} + +gboolean +eom_transform_get_affine (EomTransform *trans, cairo_matrix_t *affine) +{ + g_return_val_if_fail (EOM_IS_TRANSFORM (trans), FALSE); + + _eom_cairo_matrix_copy (&trans->priv->affine, affine); + + return TRUE; +} + diff --git a/src/eom-transform.h b/src/eom-transform.h new file mode 100644 index 0000000..a321e03 --- /dev/null +++ b/src/eom-transform.h @@ -0,0 +1,75 @@ +#ifndef _EOM_TRANSFORM_H_ +#define _EOM_TRANSFORM_H_ + +#include <glib-object.h> +#include <gdk-pixbuf/gdk-pixbuf.h> + +G_BEGIN_DECLS + +#ifndef __EOM_JOB_DECLR__ +#define __EOM_JOB_DECLR__ +typedef struct _EomJob EomJob; +#endif + +typedef enum { + EOM_TRANSFORM_NONE, + EOM_TRANSFORM_ROT_90, + EOM_TRANSFORM_ROT_180, + EOM_TRANSFORM_ROT_270, + EOM_TRANSFORM_FLIP_HORIZONTAL, + EOM_TRANSFORM_FLIP_VERTICAL, + EOM_TRANSFORM_TRANSPOSE, + EOM_TRANSFORM_TRANSVERSE +} EomTransformType; + +#define EOM_TYPE_TRANSFORM (eom_transform_get_type ()) +#define EOM_TRANSFORM(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), EOM_TYPE_TRANSFORM, EomTransform)) +#define EOM_TRANSFORM_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), EOM_TYPE_TRANSFORM, EomTransformClass)) +#define EOM_IS_TRANSFORM(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), EOM_TYPE_TRANSFORM)) +#define EOM_IS_TRANSFORM_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), EOM_TYPE_TRANSFORM)) +#define EOM_TRANSFORM_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), EOM_TYPE_TRANSFORM, EomTransformClass)) + +/* ========================================= + + GObjecat wrapper around an affine transformation + + ----------------------------------------*/ + +typedef struct _EomTransform EomTransform; +typedef struct _EomTransformClass EomTransformClass; +typedef struct _EomTransformPrivate EomTransformPrivate; + +struct _EomTransform { + GObject parent; + + EomTransformPrivate *priv; +}; + +struct _EomTransformClass { + GObjectClass parent_klass; +}; + +GType eom_transform_get_type (void) G_GNUC_CONST; + +GdkPixbuf* eom_transform_apply (EomTransform *trans, GdkPixbuf *pixbuf, EomJob *job); +EomTransform* eom_transform_reverse (EomTransform *trans); +EomTransform* eom_transform_compose (EomTransform *trans, EomTransform *compose); +gboolean eom_transform_is_identity (EomTransform *trans); + +EomTransform* eom_transform_identity_new (void); +EomTransform* eom_transform_rotate_new (int degree); +EomTransform* eom_transform_flip_new (EomTransformType type /* only EOM_TRANSFORM_FLIP_* are valid */); +#if 0 +EomTransform* eom_transform_scale_new (double sx, double sy); +#endif +EomTransform* eom_transform_new (EomTransformType trans); + +EomTransformType eom_transform_get_transform_type (EomTransform *trans); + +gboolean eom_transform_get_affine (EomTransform *trans, cairo_matrix_t *affine); + +G_END_DECLS + +#endif /* _EOM_TRANSFORM_H_ */ + + diff --git a/src/eom-uri-converter.c b/src/eom-uri-converter.c new file mode 100644 index 0000000..738820c --- /dev/null +++ b/src/eom-uri-converter.c @@ -0,0 +1,988 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <math.h> +#include <string.h> +#include <glib.h> + +#include "eom-uri-converter.h" +#include "eom-pixbuf-util.h" + +enum { + PROP_0, + PROP_CONVERT_SPACES, + PROP_SPACE_CHARACTER, + PROP_COUNTER_START, + PROP_COUNTER_N_DIGITS, + PROP_N_IMAGES +}; + +typedef struct { + EomUCType type; + union { + char *string; /* if type == EOM_UC_STRING */ + gulong counter; /* if type == EOM_UC_COUNTER */ + } data; +} EomUCToken; + + +struct _EomURIConverterPrivate { + GFile *base_file; + GList *token_list; + char *suffix; + GdkPixbufFormat *img_format; + gboolean requires_exif; + + /* options */ + gboolean convert_spaces; + gchar space_character; + gulong counter_start; + guint counter_n_digits; +}; + +static void eom_uri_converter_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); + +static void eom_uri_converter_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +#define EOM_URI_CONVERTER_GET_PRIVATE(object) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((object), EOM_TYPE_URI_CONVERTER, EomURIConverterPrivate)) + +G_DEFINE_TYPE (EomURIConverter, eom_uri_converter, G_TYPE_OBJECT) + +static void +free_token (gpointer data) +{ + EomUCToken *token = (EomUCToken*) data; + + if (token->type == EOM_UC_STRING) { + g_free (token->data.string); + } + + g_slice_free (EomUCToken, token); +} + +static void +eom_uri_converter_dispose (GObject *object) +{ + EomURIConverter *instance = EOM_URI_CONVERTER (object); + EomURIConverterPrivate *priv; + + priv = instance->priv; + + if (priv->base_file) { + g_object_unref (priv->base_file); + priv->base_file = NULL; + } + + if (priv->token_list) { + g_list_foreach (priv->token_list, (GFunc) free_token, NULL); + g_list_free (priv->token_list); + priv->token_list = NULL; + } + + if (priv->suffix) { + g_free (priv->suffix); + priv->suffix = NULL; + } + + + G_OBJECT_CLASS (eom_uri_converter_parent_class)->dispose (object); +} + +static void +eom_uri_converter_init (EomURIConverter *obj) +{ + EomURIConverterPrivate *priv; + + priv = obj->priv = EOM_URI_CONVERTER_GET_PRIVATE (obj); + + priv->convert_spaces = FALSE; + priv->space_character = '_'; + priv->counter_start = 0; + priv->counter_n_digits = 1; + priv->requires_exif = FALSE; +} + +static void +eom_uri_converter_class_init (EomURIConverterClass *klass) +{ + GObjectClass *object_class = (GObjectClass*) klass; + + object_class->dispose = eom_uri_converter_dispose; + + /* GObjectClass */ + object_class->set_property = eom_uri_converter_set_property; + object_class->get_property = eom_uri_converter_get_property; + + /* Properties */ + g_object_class_install_property ( + object_class, + PROP_CONVERT_SPACES, + g_param_spec_boolean ("convert-spaces", NULL, NULL, + FALSE, G_PARAM_READWRITE)); + + g_object_class_install_property ( + object_class, + PROP_SPACE_CHARACTER, + g_param_spec_char ("space-character", NULL, NULL, + ' ', '~', '_', G_PARAM_READWRITE)); + + g_object_class_install_property ( + object_class, + PROP_COUNTER_START, + g_param_spec_ulong ("counter-start", NULL, NULL, + 0, + G_MAXULONG, + 1, + G_PARAM_READWRITE)); + + g_object_class_install_property ( + object_class, + PROP_COUNTER_N_DIGITS, + g_param_spec_uint ("counter-n-digits", NULL, NULL, + 1, + G_MAXUINT, + 1, + G_PARAM_READWRITE)); + + + g_object_class_install_property ( + object_class, + PROP_N_IMAGES, + g_param_spec_uint ("n-images", NULL, NULL, + 1, + G_MAXUINT, + 1, + G_PARAM_WRITABLE)); + + g_type_class_add_private (klass, sizeof (EomURIConverterPrivate)); +} + +GQuark +eom_uc_error_quark (void) +{ + static GQuark q = 0; + if (q == 0) + q = g_quark_from_static_string ("eom-uri-converter-error-quark"); + + return q; +} + + +static void +eom_uri_converter_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + EomURIConverter *conv; + EomURIConverterPrivate *priv; + + g_return_if_fail (EOM_IS_URI_CONVERTER (object)); + + conv = EOM_URI_CONVERTER (object); + priv = conv->priv; + + switch (property_id) + { + case PROP_CONVERT_SPACES: + priv->convert_spaces = g_value_get_boolean (value); + break; + + case PROP_SPACE_CHARACTER: + priv->space_character = g_value_get_char (value); + break; + + case PROP_COUNTER_START: + { + guint new_n_digits; + + priv->counter_start = g_value_get_ulong (value); + + new_n_digits = ceil (log10 (priv->counter_start + pow (10, priv->counter_n_digits) - 1)); + + if (new_n_digits != priv->counter_n_digits) { + priv->counter_n_digits = ceil (MIN (log10 (G_MAXULONG), new_n_digits)); + } + break; + } + + case PROP_COUNTER_N_DIGITS: + priv->counter_n_digits = ceil (MIN (log10 (G_MAXULONG), g_value_get_uint (value))); + break; + + case PROP_N_IMAGES: + priv->counter_n_digits = ceil (MIN (log10 (G_MAXULONG), + log10 (priv->counter_start + g_value_get_uint (value)))); + break; + + default: + g_assert_not_reached (); + } +} + +static void +eom_uri_converter_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + EomURIConverter *conv; + EomURIConverterPrivate *priv; + + g_return_if_fail (EOM_IS_URI_CONVERTER (object)); + + conv = EOM_URI_CONVERTER (object); + priv = conv->priv; + + switch (property_id) + { + case PROP_CONVERT_SPACES: + g_value_set_boolean (value, priv->convert_spaces); + break; + + case PROP_SPACE_CHARACTER: + g_value_set_char (value, priv->space_character); + break; + + case PROP_COUNTER_START: + g_value_set_ulong (value, priv->counter_start); + break; + + case PROP_COUNTER_N_DIGITS: + g_value_set_uint (value, priv->counter_n_digits); + break; + + default: + g_assert_not_reached (); + } +} + +/* parser states */ +enum { + PARSER_NONE, + PARSER_STRING, + PARSER_TOKEN +}; + +static EomUCToken* +create_token_string (const char *string, int substr_start, int substr_len) +{ + char *start_byte; + char *end_byte; + int n_bytes; + EomUCToken *token; + + if (string == NULL) return NULL; + if (substr_len <= 0) return NULL; + + start_byte = g_utf8_offset_to_pointer (string, substr_start); + end_byte = g_utf8_offset_to_pointer (string, substr_start + substr_len); + + /* FIXME: is this right? */ + n_bytes = end_byte - start_byte; + + token = g_slice_new0 (EomUCToken); + token->type = EOM_UC_STRING; + token->data.string = g_new0 (char, n_bytes); + token->data.string = g_utf8_strncpy (token->data.string, start_byte, substr_len); + + return token; +} + +static EomUCToken* +create_token_counter (int start_counter) +{ + EomUCToken *token; + + token = g_slice_new0 (EomUCToken); + token->type = EOM_UC_COUNTER; + token->data.counter = 0; + + return token; +} + +static EomUCToken* +create_token_other (EomUCType type) +{ + EomUCToken *token; + + token = g_slice_new0 (EomUCToken); + token->type = type; + + return token; +} + +static GList* +eom_uri_converter_parse_string (EomURIConverter *conv, const char *string) +{ + EomURIConverterPrivate *priv; + GList *list = NULL; + gulong len; + int i; + int state = PARSER_NONE; + int start = -1; + int substr_len = 0; + gunichar c; + const char *s; + EomUCToken *token; + + g_return_val_if_fail (EOM_IS_URI_CONVERTER (conv), NULL); + + priv = conv->priv; + + if (string == NULL) return NULL; + + if (!g_utf8_validate (string, -1, NULL)) + return NULL; + + len = g_utf8_strlen (string, -1); + s = string; + + for (i = 0; i < len; i++) { + c = g_utf8_get_char (s); + token = NULL; + + switch (state) { + case PARSER_NONE: + if (c == '%') { + start = -1; + state = PARSER_TOKEN; + } else { + start = i; + substr_len = 1; + state = PARSER_STRING; + } + break; + + case PARSER_STRING: + if (c == '%') { + if (start != -1) { + token = create_token_string (string, start, substr_len); + } + + state = PARSER_TOKEN; + start = -1; + } else { + substr_len++; + } + break; + + case PARSER_TOKEN: { + EomUCType type = EOM_UC_END; + + if (c == 'f') { + type = EOM_UC_FILENAME; + } + else if (c == 'n') { + type = EOM_UC_COUNTER; + token = create_token_counter (priv->counter_start); + } + else if (c == 'c') { + type = EOM_UC_COMMENT; + } + else if (c == 'd') { + type = EOM_UC_DATE; + } + else if (c == 't') { + type = EOM_UC_TIME; + } + else if (c == 'a') { + type = EOM_UC_DAY; + } + else if (c == 'm') { + type = EOM_UC_MONTH; + } + else if (c == 'y') { + type = EOM_UC_YEAR; + } + else if (c == 'h') { + type = EOM_UC_HOUR; + } + else if (c == 'i') { + type = EOM_UC_MINUTE; + } + else if (c == 's') { + type = EOM_UC_SECOND; + } + + if (type != EOM_UC_END && token == NULL) { + token = create_token_other (type); + priv->requires_exif = TRUE; + } + state = PARSER_NONE; + break; + } + default: + g_assert_not_reached (); + } + + + if (token != NULL) { + list = g_list_append (list, token); + } + + s = g_utf8_next_char (s); + } + + if (state != PARSER_TOKEN && start >= 0) { + /* add remaining chars as string token */ + list = g_list_append (list, create_token_string (string, start, substr_len)); + } + + return list; +} + +void +eom_uri_converter_print_list (EomURIConverter *conv) +{ + EomURIConverterPrivate *priv; + GList *it; + + g_return_if_fail (EOM_URI_CONVERTER (conv)); + + priv = conv->priv; + + for (it = priv->token_list; it != NULL; it = it->next) { + EomUCToken *token; + char *str; + + token = (EomUCToken*) it->data; + + switch (token->type) { + case EOM_UC_STRING: + str = g_strdup_printf ("string [%s]", token->data.string); + break; + case EOM_UC_FILENAME: + str = "filename"; + break; + case EOM_UC_COUNTER: + str = g_strdup_printf ("counter [%lu]", token->data.counter); + break; + case EOM_UC_COMMENT: + str = "comment"; + break; + case EOM_UC_DATE: + str = "date"; + break; + case EOM_UC_TIME: + str = "time"; + break; + case EOM_UC_DAY: + str = "day"; + break; + case EOM_UC_MONTH: + str = "month"; + break; + case EOM_UC_YEAR: + str = "year"; + break; + case EOM_UC_HOUR: + str = "hour"; + break; + case EOM_UC_MINUTE: + str = "minute"; + break; + case EOM_UC_SECOND: + str = "second"; + break; + default: + str = "unknown"; + break; + } + + g_print ("- %s\n", str); + + if (token->type == EOM_UC_STRING || token->type == EOM_UC_COUNTER) { + g_free (str); + } + } +} + + +EomURIConverter* +eom_uri_converter_new (GFile *base_file, GdkPixbufFormat *img_format, const char *format_str) +{ + EomURIConverter *conv; + + g_return_val_if_fail (format_str != NULL, NULL); + + conv = g_object_new (EOM_TYPE_URI_CONVERTER, NULL); + + if (base_file != NULL) { + conv->priv->base_file = g_object_ref (base_file); + } + else { + conv->priv->base_file = NULL; + } + conv->priv->img_format = img_format; + conv->priv->token_list = eom_uri_converter_parse_string (conv, format_str); + + return conv; +} + +static GFile* +get_file_directory (EomURIConverter *conv, EomImage *image) +{ + GFile *file = NULL; + EomURIConverterPrivate *priv; + + g_return_val_if_fail (EOM_IS_URI_CONVERTER (conv), NULL); + g_return_val_if_fail (EOM_IS_IMAGE (image), NULL); + + priv = conv->priv; + + if (priv->base_file != NULL) { + file = g_object_ref (priv->base_file); + } + else { + GFile *img_file; + + img_file = eom_image_get_file (image); + g_assert (img_file != NULL); + + file = g_file_get_parent (img_file); + + g_object_unref (img_file); + } + + return file; +} + +static void +split_filename (GFile *file, char **name, char **suffix) +{ + char *basename; + char *suffix_start; + guint len; + + *name = NULL; + *suffix = NULL; + + /* get unescaped string */ + basename = g_file_get_basename (file); + + /* FIXME: does this work for all locales? */ + suffix_start = g_utf8_strrchr (basename, -1, '.'); + + if (suffix_start == NULL) { /* no suffix found */ + *name = g_strdup (basename); + } + else { + len = (suffix_start - basename); + *name = g_strndup (basename, len); + + len = strlen (basename) - len - 1; + *suffix = g_strndup (suffix_start+1, len); + } + + g_free (basename); +} + +static GString* +append_filename (GString *str, EomImage *img) +{ + /* appends the name of the original file without + filetype suffix */ + GFile *img_file; + char *name; + char *suffix; + GString *result; + + img_file = eom_image_get_file (img); + split_filename (img_file, &name, &suffix); + + result = g_string_append (str, name); + + g_free (name); + g_free (suffix); + + g_object_unref (img_file); + + return result; +} + +static GString* +append_counter (GString *str, gulong counter, EomURIConverter *conv) +{ + EomURIConverterPrivate *priv; + + priv = conv->priv; + + g_string_append_printf (str, "%.*lu", priv->counter_n_digits, counter); + + return str; +} + + +static void +build_absolute_file (EomURIConverter *conv, EomImage *image, GString *str, /* input */ + GFile **file, GdkPixbufFormat **format) /* output */ +{ + GFile *dir_file; + EomURIConverterPrivate *priv; + + *file = NULL; + if (format != NULL) + *format = NULL; + + g_return_if_fail (EOM_IS_URI_CONVERTER (conv)); + g_return_if_fail (EOM_IS_IMAGE (image)); + g_return_if_fail (file != NULL); + g_return_if_fail (str != NULL); + + priv = conv->priv; + + dir_file = get_file_directory (conv, image); + g_assert (dir_file != NULL); + + if (priv->img_format == NULL) { + /* use same file type/suffix */ + char *name; + char *old_suffix; + GFile *img_file; + + img_file = eom_image_get_file (image); + split_filename (img_file, &name, &old_suffix); + + g_assert (old_suffix != NULL); + + g_string_append_unichar (str, '.'); + g_string_append (str, old_suffix); + + if (format != NULL) + *format = eom_pixbuf_get_format_by_suffix (old_suffix); + + g_object_unref (img_file); + } else { + if (priv->suffix == NULL) + priv->suffix = eom_pixbuf_get_common_suffix (priv->img_format); + + g_string_append_unichar (str, '.'); + g_string_append (str, priv->suffix); + + if (format != NULL) + *format = priv->img_format; + } + + *file = g_file_get_child (dir_file, str->str); + + g_object_unref (dir_file); +} + + +static GString* +replace_remove_chars (GString *str, gboolean convert_spaces, gunichar space_char) +{ + GString *result; + guint len; + char *s; + int i; + gunichar c; + + g_return_val_if_fail (str != NULL, NULL); + + if (!g_utf8_validate (str->str, -1, NULL)) + return NULL; + + result = g_string_new (NULL); + + len = g_utf8_strlen (str->str, -1); + s = str->str; + + for (i = 0; i < len; i++, s = g_utf8_next_char (s)) { + c = g_utf8_get_char (s); + + if (c == '/') { + continue; + } + else if (g_unichar_isspace (c) && convert_spaces) { + result = g_string_append_unichar (result, space_char); + } + else { + result = g_string_append_unichar (result, c); + } + } + + /* ensure maximum length of 250 characters */ + len = MIN (result->len, 250); + result = g_string_truncate (result, len); + + return result; +} + +/* + * This function converts the uri of the EomImage object, according to the + * EomUCToken list. The absolute uri (converted filename appended to base uri) + * is returned in uri and the image format will be in the format pointer. + */ +gboolean +eom_uri_converter_do (EomURIConverter *conv, EomImage *image, + GFile **file, GdkPixbufFormat **format, GError **error) +{ + EomURIConverterPrivate *priv; + GList *it; + GString *str; + GString *repl_str; + + g_return_val_if_fail (EOM_IS_URI_CONVERTER (conv), FALSE); + + priv = conv->priv; + + *file = NULL; + if (format != NULL) + *format = NULL; + + str = g_string_new (""); + + for (it = priv->token_list; it != NULL; it = it->next) { + EomUCToken *token = (EomUCToken*) it->data; + + switch (token->type) { + case EOM_UC_STRING: + str = g_string_append (str, token->data.string); + break; + + case EOM_UC_FILENAME: + str = append_filename (str, image); + break; + + case EOM_UC_COUNTER: { + if (token->data.counter < priv->counter_start) + token->data.counter = priv->counter_start; + + str = append_counter (str, token->data.counter++, conv); + break; + } +#if 0 + case EOM_UC_COMMENT: + str = g_string_append_printf (); + str = "comment"; + break; + case EOM_UC_DATE: + str = "date"; + break; + case EOM_UC_TIME: + str = "time"; + break; + case EOM_UC_DAY: + str = "day"; + break; + case EOM_UC_MONTH: + str = "month"; + break; + case EOM_UC_YEAR: + str = "year"; + break; + case EOM_UC_HOUR: + str = "hour"; + break; + case EOM_UC_MINUTE: + str = "minute"; + break; + case EOM_UC_SECOND: + str = "second"; + break; +#endif + default: + /* skip all others */ + + break; + } + } + + repl_str = replace_remove_chars (str, priv->convert_spaces, priv->space_character); + + if (repl_str->len > 0) { + build_absolute_file (conv, image, repl_str, file, format); + } + + g_string_free (repl_str, TRUE); + g_string_free (str, TRUE); + + + return (*file != NULL); +} + + +char* +eom_uri_converter_preview (const char *format_str, EomImage *img, GdkPixbufFormat *format, + gulong counter, guint n_images, + gboolean convert_spaces, gunichar space_char) +{ + GString *str; + GString *repl_str; + guint n_digits; + guint len; + int i; + const char *s; + gunichar c; + char *filename; + gboolean token_next; + + g_return_val_if_fail (format_str != NULL, NULL); + g_return_val_if_fail (EOM_IS_IMAGE (img), NULL); + + if (n_images == 0) return NULL; + + n_digits = ceil (MIN (log10 (G_MAXULONG), MAX (log10 (counter), log10 (n_images)))); + + str = g_string_new (""); + + if (!g_utf8_validate (format_str, -1, NULL)) + return NULL; + + len = g_utf8_strlen (format_str, -1); + s = format_str; + token_next = FALSE; + + for (i = 0; i < len; i++, s = g_utf8_next_char (s)) { + c = g_utf8_get_char (s); + + if (token_next) { + if (c == 'f') { + str = append_filename (str, img); + } + else if (c == 'n') { + g_string_append_printf (str, "%.*lu", + n_digits ,counter); + + } +#if 0 /* ignore the rest for now */ + else if (c == 'c') { + type = EOM_UC_COMMENT; + } + else if (c == 'd') { + type = EOM_UC_DATE; + } + else if (c == 't') { + type = EOM_UC_TIME; + } + else if (c == 'a') { + type = EOM_UC_DAY; + } + else if (c == 'm') { + type = EOM_UC_MONTH; + } + else if (c == 'y') { + type = EOM_UC_YEAR; + } + else if (c == 'h') { + type = EOM_UC_HOUR; + } + else if (c == 'i') { + type = EOM_UC_MINUTE; + } + else if (c == 's') { + type = EOM_UC_SECOND; + } +#endif + token_next = FALSE; + } + else if (c == '%') { + token_next = TRUE; + } + else { + str = g_string_append_unichar (str, c); + } + } + + + filename = NULL; + repl_str = replace_remove_chars (str, convert_spaces, space_char); + + if (repl_str->len > 0) { + if (format == NULL) { + /* use same file type/suffix */ + char *name; + char *old_suffix; + GFile *img_file; + + img_file = eom_image_get_file (img); + split_filename (img_file, &name, &old_suffix); + + g_assert (old_suffix != NULL); + + g_string_append_unichar (repl_str, '.'); + g_string_append (repl_str, old_suffix); + + g_free (old_suffix); + g_free (name); + g_object_unref (img_file); + } + else { + char *suffix = eom_pixbuf_get_common_suffix (format); + + g_string_append_unichar (repl_str, '.'); + g_string_append (repl_str, suffix); + + g_free (suffix); + } + + filename = repl_str->str; + } + + g_string_free (repl_str, FALSE); + g_string_free (str, TRUE); + + return filename; +} + +gboolean +eom_uri_converter_requires_exif (EomURIConverter *converter) +{ + g_return_val_if_fail (EOM_IS_URI_CONVERTER (converter), FALSE); + + return converter->priv->requires_exif; +} + +gboolean +eom_uri_converter_check (EomURIConverter *converter, GList *img_list, GError **error) +{ + GList *it; + GList *file_list = NULL; + gboolean all_different = TRUE; + + g_return_val_if_fail (EOM_IS_URI_CONVERTER (converter), FALSE); + + /* convert all image uris */ + for (it = img_list; it != NULL; it = it->next) { + gboolean result; + GFile *file; + GError *conv_error = NULL; + + result = eom_uri_converter_do (converter, EOM_IMAGE (it->data), + &file, NULL, &conv_error); + + if (result) { + file_list = g_list_prepend (file_list, file); + } + } + + /* check for all different uris */ + for (it = file_list; it != NULL && all_different; it = it->next) { + GList *p; + GFile *file; + + file = (GFile*) it->data; + + for (p = it->next; p != NULL && all_different; p = p->next) { + all_different = !g_file_equal (file, (GFile*) p->data); + } + } + + if (!all_different) { + g_set_error (error, EOM_UC_ERROR, + EOM_UC_ERROR_EQUAL_FILENAMES, + _("At least two file names are equal.")); + } + + return all_different; +} diff --git a/src/eom-uri-converter.h b/src/eom-uri-converter.h new file mode 100644 index 0000000..b1cbe94 --- /dev/null +++ b/src/eom-uri-converter.h @@ -0,0 +1,107 @@ +#ifndef _EOM_URI_CONVERTER_H_ +#define _EOM_URI_CONVERTER_H_ + +#include <glib-object.h> +#include <glib/gi18n.h> +#include "eom-image.h" + +G_BEGIN_DECLS + +#define EOM_TYPE_URI_CONVERTER (eom_uri_converter_get_type ()) +#define EOM_URI_CONVERTER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), EOM_TYPE_URI_CONVERTER, EomURIConverter)) +#define EOM_URI_CONVERTER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), EOM_TYPE_URI_CONVERTER, EomURIConverterClass)) +#define EOM_IS_URI_CONVERTER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), EOM_TYPE_URI_CONVERTER)) +#define EOM_IS_URI_CONVERTER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), EOM_TYPE_URI_CONVERTER)) +#define EOM_URI_CONVERTER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), EOM_TYPE_URI_CONVERTER, EomURIConverterClass)) + +#ifndef __EOM_URI_CONVERTER_DECLR__ +#define __EOM_URI_CONVERTER_DECLR__ +typedef struct _EomURIConverter EomURIConverter; +#endif +typedef struct _EomURIConverterClass EomURIConverterClass; +typedef struct _EomURIConverterPrivate EomURIConverterPrivate; + +typedef enum { + EOM_UC_STRING, + EOM_UC_FILENAME, + EOM_UC_COUNTER, + EOM_UC_COMMENT, + EOM_UC_DATE, + EOM_UC_TIME, + EOM_UC_DAY, + EOM_UC_MONTH, + EOM_UC_YEAR, + EOM_UC_HOUR, + EOM_UC_MINUTE, + EOM_UC_SECOND, + EOM_UC_END +} EomUCType; + +typedef struct { + char *description; + char *rep; + gboolean req_exif; +} EomUCInfo; + +typedef enum { + EOM_UC_ERROR_INVALID_UNICODE, + EOM_UC_ERROR_INVALID_CHARACTER, + EOM_UC_ERROR_EQUAL_FILENAMES, + EOM_UC_ERROR_UNKNOWN +} EomUCError; + +#define EOM_UC_ERROR eom_uc_error_quark () + + +struct _EomURIConverter { + GObject parent; + + EomURIConverterPrivate *priv; +}; + +struct _EomURIConverterClass { + GObjectClass parent_klass; +}; + +G_GNUC_INTERNAL +GType eom_uri_converter_get_type (void) G_GNUC_CONST; + +G_GNUC_INTERNAL +GQuark eom_uc_error_quark (void); + +G_GNUC_INTERNAL +EomURIConverter* eom_uri_converter_new (GFile *base_file, + GdkPixbufFormat *img_format, + const char *format_string); + +G_GNUC_INTERNAL +gboolean eom_uri_converter_check (EomURIConverter *converter, + GList *img_list, + GError **error); + +G_GNUC_INTERNAL +gboolean eom_uri_converter_requires_exif (EomURIConverter *converter); + +G_GNUC_INTERNAL +gboolean eom_uri_converter_do (EomURIConverter *converter, + EomImage *image, + GFile **file, + GdkPixbufFormat **format, + GError **error); + +G_GNUC_INTERNAL +char* eom_uri_converter_preview (const char *format_str, + EomImage *img, + GdkPixbufFormat *format, + gulong counter, + guint n_images, + gboolean convert_spaces, + gunichar space_char); + +/* for debugging purpose only */ +G_GNUC_INTERNAL +void eom_uri_converter_print_list (EomURIConverter *conv); + +G_END_DECLS + +#endif /* _EOM_URI_CONVERTER_H_ */ diff --git a/src/eom-util.c b/src/eom-util.c new file mode 100644 index 0000000..2d74305 --- /dev/null +++ b/src/eom-util.c @@ -0,0 +1,348 @@ +/* Eye Of Mate - General Utilities + * + * Copyright (C) 2006 The Free Software Foundation + * + * Author: Lucas Rocha <[email protected]> + * + * Based on code by: + * - Jens Finke <[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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <sys/time.h> +#ifdef HAVE_STRPTIME +#define _XOPEN_SOURCE +#endif /* HAVE_STRPTIME */ + +#include <time.h> + +#include "eom-util.h" + +#include <errno.h> +#include <string.h> +#include <glib.h> +#include <glib/gprintf.h> +#include <glib/gstdio.h> +#include <gtk/gtk.h> +#include <gio/gio.h> +#include <glib/gi18n.h> + +void +eom_util_show_help (const gchar *section, GtkWindow *parent) +{ + GError *error = NULL; + gchar *uri = NULL; + + if (section) + uri = g_strdup_printf ("ghelp:eom?%s", section); + + gtk_show_uri (NULL, ((uri != NULL) ? uri : "ghelp:eom"), + gtk_get_current_event_time (), &error); + + g_free (uri); + + if (error) { + GtkWidget *dialog; + + dialog = gtk_message_dialog_new (parent, + 0, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_OK, + _("Could not display help for Eye of MATE")); + + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), + "%s", error->message); + + g_signal_connect_swapped (dialog, "response", + G_CALLBACK (gtk_widget_destroy), + dialog); + gtk_widget_show (dialog); + + g_error_free (error); + } +} + +gchar * +eom_util_make_valid_utf8 (const gchar *str) +{ + GString *string; + const char *remainder, *invalid; + int remaining_bytes, valid_bytes; + + string = NULL; + remainder = str; + remaining_bytes = strlen (str); + + while (remaining_bytes != 0) { + if (g_utf8_validate (remainder, remaining_bytes, &invalid)) { + break; + } + + valid_bytes = invalid - remainder; + + if (string == NULL) { + string = g_string_sized_new (remaining_bytes); + } + + g_string_append_len (string, remainder, valid_bytes); + g_string_append_c (string, '?'); + + remaining_bytes -= valid_bytes + 1; + remainder = invalid + 1; + } + + if (string == NULL) { + return g_strdup (str); + } + + g_string_append (string, remainder); + g_string_append (string, _(" (invalid Unicode)")); + + g_assert (g_utf8_validate (string->str, -1, NULL)); + + return g_string_free (string, FALSE); +} + +GSList* +eom_util_parse_uri_string_list_to_file_list (const gchar *uri_list) +{ + GSList* file_list = NULL; + gsize i = 0; + gchar **uris; + + uris = g_uri_list_extract_uris (uri_list); + + while (uris[i] != NULL) { + file_list = g_slist_append (file_list, g_file_new_for_uri (uris[i])); + i++; + } + + g_strfreev (uris); + + return file_list; +} + +GSList* +eom_util_string_list_to_file_list (GSList *string_list) +{ + GSList *it = NULL; + GSList *file_list = NULL; + + for (it = string_list; it != NULL; it = it->next) { + char *uri_str; + + uri_str = (gchar *) it->data; + + file_list = g_slist_prepend (file_list, + g_file_new_for_uri (uri_str)); + } + + return g_slist_reverse (file_list); +} + +#ifdef HAVE_DBUS +GSList* +eom_util_strings_to_file_list (gchar **strings) +{ + int i; + GSList *file_list = NULL; + + for (i = 0; strings[i]; i++) { + file_list = g_slist_prepend (file_list, + g_file_new_for_uri (strings[i])); + } + + return g_slist_reverse (file_list); +} +#endif + +GSList* +eom_util_string_array_to_list (const gchar **files, gboolean create_uri) +{ + gint i; + GSList *list = NULL; + + if (files == NULL) return list; + + for (i = 0; files[i]; i++) { + char *str; + + if (create_uri) { + GFile *file; + + file = g_file_new_for_commandline_arg (files[i]); + str = g_file_get_uri (file); + + g_object_unref (file); + } else { + str = g_strdup (files[i]); + } + + if (str) { + list = g_slist_prepend (list, g_strdup (str)); + g_free (str); + } + } + + return g_slist_reverse (list); +} + +gchar ** +eom_util_string_array_make_absolute (gchar **files) +{ + int i; + int size; + gchar **abs_files; + GFile *file; + + if (files == NULL) + return NULL; + + size = g_strv_length (files); + + /* Ensure new list is NULL-terminated */ + abs_files = g_new0 (gchar *, size+1); + + for (i = 0; i < size; i++) { + file = g_file_new_for_commandline_arg (files[i]); + abs_files[i] = g_file_get_uri (file); + + g_object_unref (file); + } + + return abs_files; +} + +static gchar *dot_dir = NULL; + +static gboolean +ensure_dir_exists (const char *dir) +{ + if (g_file_test (dir, G_FILE_TEST_IS_DIR)) + return TRUE; + + if (g_mkdir_with_parents (dir, 0700) == 0) + return TRUE; + + if (errno == EEXIST) + return g_file_test (dir, G_FILE_TEST_IS_DIR); + + g_warning ("Failed to create directory %s: %s", dir, strerror (errno)); + return FALSE; +} + +const gchar * +eom_util_dot_dir (void) +{ + if (dot_dir == NULL) { + gboolean exists; + + dot_dir = g_build_filename (g_get_home_dir (), + ".mate2", + "eom", + NULL); + + exists = ensure_dir_exists (dot_dir); + + if (G_UNLIKELY (!exists)) { + static gboolean printed_warning = FALSE; + + if (!printed_warning) { + g_warning ("EOM could not save some of your preferences in its settings directory due to a file with the same name (%s) blocking its creation. Please remove that file, or move it away.", dot_dir); + printed_warning = TRUE; + } + dot_dir = NULL; + return NULL; + } + } + + return dot_dir; +} + +/* Based on eel_filename_strip_extension() */ + +/** + * eom_util_filename_get_extension: + * @filename: a filename + * + * Returns a reasonably good guess of the file extension of @filename. + * + * Returns: a newly allocated string with the file extension of @filename. + **/ +char * +eom_util_filename_get_extension (const char * filename) +{ + char *begin, *begin2; + + if (filename == NULL) { + return NULL; + } + + begin = strrchr (filename, '.'); + + if (begin && begin != filename) { + if (strcmp (begin, ".gz") == 0 || + strcmp (begin, ".bz2") == 0 || + strcmp (begin, ".sit") == 0 || + strcmp (begin, ".Z") == 0) { + begin2 = begin - 1; + while (begin2 > filename && + *begin2 != '.') { + begin2--; + } + if (begin2 != filename) { + begin = begin2; + } + } + begin ++; + } else { + return NULL; + } + + return g_strdup (begin); +} + + +/** + * eom_util_file_is_persistent: + * @file: a #GFile + * + * Checks whether @file is a non-removable local mount. + * + * Returns: %TRUE if @file is in a non-removable mount, + * %FALSE otherwise or when it is remote. + **/ +gboolean +eom_util_file_is_persistent (GFile *file) +{ + GMount *mount; + + if (!g_file_is_native (file)) + return FALSE; + + mount = g_file_find_enclosing_mount (file, NULL, NULL); + if (mount) { + if (g_mount_can_unmount (mount)) { + return FALSE; + } + } + + return TRUE; +} diff --git a/src/eom-util.h b/src/eom-util.h new file mode 100644 index 0000000..e9a3dc8 --- /dev/null +++ b/src/eom-util.h @@ -0,0 +1,72 @@ +/* Eye Of Mate - General Utilities + * + * Copyright (C) 2006 The Free Software Foundation + * + * Author: Lucas Rocha <[email protected]> + * + * Based on code by: + * - Jens Finke <[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 __EOM_UTIL_H__ +#define __EOM_UTIL_H__ + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +G_GNUC_INTERNAL +void eom_util_show_help (const gchar *section, + GtkWindow *parent); + +G_GNUC_INTERNAL +gchar *eom_util_make_valid_utf8 (const gchar *name); + +G_GNUC_INTERNAL +GSList *eom_util_parse_uri_string_list_to_file_list (const gchar *uri_list); + +G_GNUC_INTERNAL +GSList *eom_util_string_list_to_file_list (GSList *string_list); + +#ifdef HAVE_DBUS +G_GNUC_INTERNAL +GSList *eom_util_strings_to_file_list (gchar **strings); +#endif + +G_GNUC_INTERNAL +GSList *eom_util_string_array_to_list (const gchar **files, + gboolean create_uri); + +G_GNUC_INTERNAL +gchar **eom_util_string_array_make_absolute (gchar **files); + +G_GNUC_INTERNAL +gboolean eom_util_launch_desktop_file (const gchar *filename, + guint32 user_time); + +G_GNUC_INTERNAL +const gchar *eom_util_dot_dir (void); + +G_GNUC_INTERNAL +char * eom_util_filename_get_extension (const char * filename_with_extension); + +G_GNUC_INTERNAL +gboolean eom_util_file_is_persistent (GFile *file); + +G_END_DECLS + +#endif /* __EOM_UTIL_H__ */ diff --git a/src/eom-window.c b/src/eom-window.c new file mode 100644 index 0000000..a5a85e9 --- /dev/null +++ b/src/eom-window.c @@ -0,0 +1,5796 @@ +/* Eye Of Mate - Main Window + * + * Copyright (C) 2000-2008 The Free Software Foundation + * + * Author: Lucas Rocha <[email protected]> + * + * Based on code by: + * - Federico Mena-Quintero <[email protected]> + * - Jens Finke <[email protected]> + * Based on evince code (shell/ev-window.c) by: + * - Martin Kretzschmar <[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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <math.h> + +#include "eom-window.h" +#include "eom-scroll-view.h" +#include "eom-debug.h" +#include "eom-file-chooser.h" +#include "eom-thumb-view.h" +#include "eom-list-store.h" +#include "eom-sidebar.h" +#include "eom-statusbar.h" +#include "eom-preferences-dialog.h" +#include "eom-properties-dialog.h" +#include "eom-print.h" +#include "eom-error-message-area.h" +#include "eom-application.h" +#include "eom-thumb-nav.h" +#include "eom-config-keys.h" +#include "eom-job-queue.h" +#include "eom-jobs.h" +#include "eom-util.h" +#include "eom-save-as-dialog-helper.h" +#include "eom-plugin-engine.h" +#include "eom-close-confirmation-dialog.h" + +#include "eom-enum-types.h" + +#include "egg-toolbar-editor.h" +#include "egg-editable-toolbar.h" +#include "egg-toolbars-model.h" + +#include <glib.h> +#include <glib-object.h> +#include <glib/gi18n.h> +#include <gio/gio.h> +#include <gdk/gdkkeysyms.h> +#include <gtk/gtk.h> +#include <mateconf/mateconf-client.h> + +#if HAVE_LCMS +#include <X11/Xlib.h> +#include <X11/Xatom.h> +#ifdef GDK_WINDOWING_X11 +#include <gdk/gdkx.h> +#endif +#include <lcms.h> +#endif + +#define EOM_WINDOW_GET_PRIVATE(object) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((object), EOM_TYPE_WINDOW, EomWindowPrivate)) + +G_DEFINE_TYPE (EomWindow, eom_window, GTK_TYPE_WINDOW); + +#define EOM_WINDOW_MIN_WIDTH 440 +#define EOM_WINDOW_MIN_HEIGHT 350 + +#define EOM_WINDOW_DEFAULT_WIDTH 540 +#define EOM_WINDOW_DEFAULT_HEIGHT 450 + +#define EOM_WINDOW_FULLSCREEN_TIMEOUT 5 * 1000 +#define EOM_WINDOW_FULLSCREEN_POPUP_THRESHOLD 5 + +#define EOM_RECENT_FILES_GROUP "Graphics" +#define EOM_RECENT_FILES_APP_NAME "Eye of MATE Image Viewer" +#define EOM_RECENT_FILES_LIMIT 5 + +#define EOM_WALLPAPER_FILENAME "eom-wallpaper" + +#define is_rtl (gtk_widget_get_default_direction () == GTK_TEXT_DIR_RTL) + +typedef enum { + EOM_WINDOW_STATUS_UNKNOWN, + EOM_WINDOW_STATUS_INIT, + EOM_WINDOW_STATUS_NORMAL +} EomWindowStatus; + +enum { + PROP_0, + PROP_STARTUP_FLAGS +}; + +enum { + SIGNAL_PREPARED, + SIGNAL_LAST +}; + +static gint signals[SIGNAL_LAST]; + +/* MateConfNotifications */ +enum { + EOM_WINDOW_NOTIFY_INTERPOLATE, + EOM_WINDOW_NOTIFY_EXTRAPOLATE, + EOM_WINDOW_NOTIFY_SCROLLWHEEL_ZOOM, + EOM_WINDOW_NOTIFY_ZOOM_MULTIPLIER, + EOM_WINDOW_NOTIFY_BACKGROUND_COLOR, + EOM_WINDOW_NOTIFY_USE_BG_COLOR, + EOM_WINDOW_NOTIFY_TRANSPARENCY, + EOM_WINDOW_NOTIFY_TRANS_COLOR, + EOM_WINDOW_NOTIFY_SCROLL_BUTTONS, + EOM_WINDOW_NOTIFY_COLLECTION_POS, + EOM_WINDOW_NOTIFY_COLLECTION_RESIZABLE, + EOM_WINDOW_NOTIFY_CAN_SAVE, + EOM_WINDOW_NOTIFY_PROPSDIALOG_NETBOOK_MODE, + EOM_WINDOW_NOTIFY_LENGTH +}; + +struct _EomWindowPrivate { + MateConfClient *client; + guint client_notifications[EOM_WINDOW_NOTIFY_LENGTH]; + + EomListStore *store; + EomImage *image; + EomWindowMode mode; + EomWindowStatus status; + + GtkUIManager *ui_mgr; + GtkWidget *box; + GtkWidget *layout; + GtkWidget *cbox; + GtkWidget *view; + GtkWidget *sidebar; + GtkWidget *thumbview; + GtkWidget *statusbar; + GtkWidget *nav; + GtkWidget *message_area; + GtkWidget *toolbar; + GObject *properties_dlg; + + GtkActionGroup *actions_window; + GtkActionGroup *actions_image; + GtkActionGroup *actions_collection; + GtkActionGroup *actions_recent; + + GtkWidget *fullscreen_popup; + GSource *fullscreen_timeout_source; + + gboolean slideshow_loop; + gint slideshow_switch_timeout; + GSource *slideshow_switch_source; + + guint recent_menu_id; + + EomJob *load_job; + EomJob *transform_job; + EomJob *save_job; + GFile *last_save_as_folder; + EomJob *copy_job; + + guint image_info_message_cid; + guint tip_message_cid; + guint copy_file_cid; + + EomStartupFlags flags; + GSList *file_list; + + gint collection_position; + gboolean collection_resizable; + + GtkActionGroup *actions_open_with; + guint open_with_menu_id; + + gboolean save_disabled; + gboolean needs_reload_confirmation; + +#ifdef HAVE_LCMS + cmsHPROFILE *display_profile; +#endif +}; + +static void eom_window_cmd_fullscreen (GtkAction *action, gpointer user_data); +static void eom_window_run_fullscreen (EomWindow *window, gboolean slideshow); +static void eom_window_cmd_slideshow (GtkAction *action, gpointer user_data); +static void eom_window_cmd_pause_slideshow (GtkAction *action, gpointer user_data); +static void eom_window_stop_fullscreen (EomWindow *window, gboolean slideshow); +static void eom_job_load_cb (EomJobLoad *job, gpointer data); +static void eom_job_save_progress_cb (EomJobSave *job, float progress, gpointer data); +static void eom_job_progress_cb (EomJobLoad *job, float progress, gpointer data); +static void eom_job_transform_cb (EomJobTransform *job, gpointer data); +static void fullscreen_set_timeout (EomWindow *window); +static void fullscreen_clear_timeout (EomWindow *window); +static void update_action_groups_state (EomWindow *window); +static void open_with_launch_application_cb (GtkAction *action, gpointer callback_data); +static void eom_window_update_openwith_menu (EomWindow *window, EomImage *image); +static void eom_window_list_store_image_added (GtkTreeModel *tree_model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer user_data); +static void eom_window_list_store_image_removed (GtkTreeModel *tree_model, + GtkTreePath *path, + gpointer user_data); +static void eom_window_set_wallpaper (EomWindow *window, const gchar *filename, const gchar *visible_filename); +static gboolean eom_window_save_images (EomWindow *window, GList *images); +static void eom_window_finish_saving (EomWindow *window); + +static GQuark +eom_window_error_quark (void) +{ + static GQuark q = 0; + + if (q == 0) + q = g_quark_from_static_string ("eom-window-error-quark"); + + return q; +} + +static void +eom_window_interp_in_type_changed_cb (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data) +{ + EomWindowPrivate *priv; + gboolean interpolate_in = TRUE; + + eom_debug (DEBUG_PREFERENCES); + + g_return_if_fail (EOM_IS_WINDOW (user_data)); + + priv = EOM_WINDOW (user_data)->priv; + + g_return_if_fail (EOM_IS_SCROLL_VIEW (priv->view)); + + if (entry->value != NULL && entry->value->type == MATECONF_VALUE_BOOL) { + interpolate_in = mateconf_value_get_bool (entry->value); + } + + eom_scroll_view_set_antialiasing_in (EOM_SCROLL_VIEW (priv->view), + interpolate_in); +} + +static void +eom_window_interp_out_type_changed_cb (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data) +{ + EomWindowPrivate *priv; + gboolean interpolate_out = TRUE; + + eom_debug (DEBUG_PREFERENCES); + + g_return_if_fail (EOM_IS_WINDOW (user_data)); + + priv = EOM_WINDOW (user_data)->priv; + + g_return_if_fail (EOM_IS_SCROLL_VIEW (priv->view)); + + if (entry->value != NULL && entry->value->type == MATECONF_VALUE_BOOL) { + interpolate_out = mateconf_value_get_bool (entry->value); + } + + eom_scroll_view_set_antialiasing_out (EOM_SCROLL_VIEW (priv->view), + interpolate_out); +} + +static void +eom_window_scroll_wheel_zoom_changed_cb (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data) +{ + EomWindowPrivate *priv; + gboolean scroll_wheel_zoom = FALSE; + + eom_debug (DEBUG_PREFERENCES); + + g_return_if_fail (EOM_IS_WINDOW (user_data)); + + priv = EOM_WINDOW (user_data)->priv; + + g_return_if_fail (EOM_IS_SCROLL_VIEW (priv->view)); + + if (entry->value != NULL && entry->value->type == MATECONF_VALUE_BOOL) { + scroll_wheel_zoom = mateconf_value_get_bool (entry->value); + } + + eom_scroll_view_set_scroll_wheel_zoom (EOM_SCROLL_VIEW (priv->view), + scroll_wheel_zoom); +} + +static void +eom_window_zoom_multiplier_changed_cb (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data) +{ + EomWindowPrivate *priv; + gdouble multiplier = 0.05; + + eom_debug (DEBUG_PREFERENCES); + + g_return_if_fail (EOM_IS_WINDOW (user_data)); + + priv = EOM_WINDOW (user_data)->priv; + + g_return_if_fail (EOM_IS_SCROLL_VIEW (priv->view)); + + if (entry->value != NULL && entry->value->type == MATECONF_VALUE_FLOAT) { + multiplier = mateconf_value_get_float (entry->value); + } + + eom_scroll_view_set_zoom_multiplier (EOM_SCROLL_VIEW (priv->view), + multiplier); +} + +static void +eom_window_transparency_changed_cb (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data) +{ + EomWindowPrivate *priv; + const char *value = NULL; + + g_return_if_fail (EOM_IS_WINDOW (user_data)); + + eom_debug (DEBUG_PREFERENCES); + + priv = EOM_WINDOW (user_data)->priv; + + g_return_if_fail (EOM_IS_SCROLL_VIEW (priv->view)); + + if (entry->value != NULL && entry->value->type == MATECONF_VALUE_STRING) { + value = mateconf_value_get_string (entry->value); + } + + if (value == NULL) { + return; + } else if (g_ascii_strcasecmp (value, "COLOR") == 0) { + GdkColor color; + char *color_str; + + color_str = mateconf_client_get_string (priv->client, + EOM_CONF_VIEW_TRANS_COLOR, NULL); + if (gdk_color_parse (color_str, &color)) { + eom_scroll_view_set_transparency (EOM_SCROLL_VIEW (priv->view), + EOM_TRANSP_COLOR, &color); + } + g_free (color_str); + } else if (g_ascii_strcasecmp (value, "CHECK_PATTERN") == 0) { + eom_scroll_view_set_transparency (EOM_SCROLL_VIEW (priv->view), + EOM_TRANSP_CHECKED, NULL); + } else { + eom_scroll_view_set_transparency (EOM_SCROLL_VIEW (priv->view), + EOM_TRANSP_BACKGROUND, NULL); + } +} + +static void +eom_window_bg_color_changed_cb (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data) +{ + EomWindowPrivate *priv; + GdkColor color; + const char *color_str; + + g_return_if_fail (EOM_IS_WINDOW (user_data)); + + eom_debug (DEBUG_PREFERENCES); + + priv = EOM_WINDOW (user_data)->priv; + + g_return_if_fail (EOM_IS_SCROLL_VIEW (priv->view)); + + if (entry->value != NULL && entry->value->type == MATECONF_VALUE_STRING) { + color_str = mateconf_value_get_string (entry->value); + + if (gdk_color_parse (color_str, &color)) { + eom_scroll_view_set_background_color (EOM_SCROLL_VIEW (priv->view), &color); + } + } +} + +static void +eom_window_use_bg_color_changed_cb (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data) +{ + EomWindowPrivate *priv; + gboolean use_bg_color = TRUE; + + eom_debug (DEBUG_PREFERENCES); + + g_return_if_fail (EOM_IS_WINDOW (user_data)); + + priv = EOM_WINDOW (user_data)->priv; + + g_return_if_fail (EOM_IS_SCROLL_VIEW (priv->view)); + + if (entry->value != NULL && entry->value->type == MATECONF_VALUE_BOOL) { + use_bg_color = mateconf_value_get_bool (entry->value); + } + + eom_scroll_view_set_use_bg_color (EOM_SCROLL_VIEW (priv->view), + use_bg_color); +} + + +static void +eom_window_trans_color_changed_cb (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data) +{ + EomWindowPrivate *priv; + GdkColor color; + const char *color_str; + char *value; + + g_return_if_fail (EOM_IS_WINDOW (user_data)); + + eom_debug (DEBUG_PREFERENCES); + + priv = EOM_WINDOW (user_data)->priv; + + g_return_if_fail (EOM_IS_SCROLL_VIEW (priv->view)); + + value = mateconf_client_get_string (priv->client, + EOM_CONF_VIEW_TRANSPARENCY, + NULL); + + if (!value || g_ascii_strcasecmp (value, "COLOR") != 0) { + g_free (value); + return; + } + + if (entry->value != NULL && entry->value->type == MATECONF_VALUE_STRING) { + color_str = mateconf_value_get_string (entry->value); + + if (gdk_color_parse (color_str, &color)) { + eom_scroll_view_set_transparency (EOM_SCROLL_VIEW (priv->view), + EOM_TRANSP_COLOR, &color); + } + } + g_free (value); +} + +static void +eom_window_scroll_buttons_changed_cb (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data) +{ + EomWindowPrivate *priv; + gboolean show_buttons = TRUE; + + eom_debug (DEBUG_PREFERENCES); + + g_return_if_fail (EOM_IS_WINDOW (user_data)); + + priv = EOM_WINDOW (user_data)->priv; + + g_return_if_fail (EOM_IS_SCROLL_VIEW (priv->view)); + + if (entry->value != NULL && entry->value->type == MATECONF_VALUE_BOOL) { + show_buttons = mateconf_value_get_bool (entry->value); + } + + eom_thumb_nav_set_show_buttons (EOM_THUMB_NAV (priv->nav), + show_buttons); +} + +static void +eom_window_collection_mode_changed_cb (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data) +{ + EomWindowPrivate *priv; + MateConfEntry *mode_entry; + GtkWidget *hpaned; + EomThumbNavMode mode = EOM_THUMB_NAV_MODE_ONE_ROW; + gint position = 0; + gboolean resizable = FALSE; + + eom_debug (DEBUG_PREFERENCES); + + g_return_if_fail (EOM_IS_WINDOW (user_data)); + + priv = EOM_WINDOW (user_data)->priv; + + mode_entry = mateconf_client_get_entry (priv->client, + EOM_CONF_UI_IMAGE_COLLECTION_POSITION, + NULL, TRUE, NULL); + + if (G_LIKELY (mode_entry != NULL)) { + if (mode_entry->value != NULL && + mode_entry->value->type == MATECONF_VALUE_INT) { + position = mateconf_value_get_int (mode_entry->value); + } + mateconf_entry_unref (mode_entry); + } + + mode_entry = mateconf_client_get_entry (priv->client, + EOM_CONF_UI_IMAGE_COLLECTION_RESIZABLE, + NULL, TRUE, NULL); + + if (G_LIKELY (mode_entry != NULL)) { + if (mode_entry->value != NULL && + mode_entry->value->type == MATECONF_VALUE_BOOL) { + resizable = mateconf_value_get_bool (mode_entry->value); + } + mateconf_entry_unref (mode_entry); + } + + if (priv->collection_position == position && + priv->collection_resizable == resizable) + return; + + priv->collection_position = position; + priv->collection_resizable = resizable; + + hpaned = gtk_widget_get_parent (priv->sidebar); + + g_object_ref (hpaned); + g_object_ref (priv->nav); + + gtk_container_remove (GTK_CONTAINER (priv->layout), hpaned); + gtk_container_remove (GTK_CONTAINER (priv->layout), priv->nav); + + gtk_widget_destroy (priv->layout); + + switch (position) { + case 0: + case 2: + if (resizable) { + mode = EOM_THUMB_NAV_MODE_MULTIPLE_ROWS; + + priv->layout = gtk_vpaned_new (); + + if (position == 0) { + gtk_paned_pack1 (GTK_PANED (priv->layout), hpaned, TRUE, FALSE); + gtk_paned_pack2 (GTK_PANED (priv->layout), priv->nav, FALSE, TRUE); + } else { + gtk_paned_pack1 (GTK_PANED (priv->layout), priv->nav, FALSE, TRUE); + gtk_paned_pack2 (GTK_PANED (priv->layout), hpaned, TRUE, FALSE); + } + } else { + mode = EOM_THUMB_NAV_MODE_ONE_ROW; + + priv->layout = gtk_vbox_new (FALSE, 2); + + if (position == 0) { + gtk_box_pack_start (GTK_BOX (priv->layout), hpaned, TRUE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (priv->layout), priv->nav, FALSE, FALSE, 0); + } else { + gtk_box_pack_start (GTK_BOX (priv->layout), priv->nav, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (priv->layout), hpaned, TRUE, TRUE, 0); + } + } + break; + + case 1: + case 3: + if (resizable) { + mode = EOM_THUMB_NAV_MODE_MULTIPLE_COLUMNS; + + priv->layout = gtk_hpaned_new (); + + if (position == 1) { + gtk_paned_pack1 (GTK_PANED (priv->layout), priv->nav, FALSE, TRUE); + gtk_paned_pack2 (GTK_PANED (priv->layout), hpaned, TRUE, FALSE); + } else { + gtk_paned_pack1 (GTK_PANED (priv->layout), hpaned, TRUE, FALSE); + gtk_paned_pack2 (GTK_PANED (priv->layout), priv->nav, FALSE, TRUE); + } + } else { + mode = EOM_THUMB_NAV_MODE_ONE_COLUMN; + + priv->layout = gtk_hbox_new (FALSE, 2); + + if (position == 1) { + gtk_box_pack_start (GTK_BOX (priv->layout), priv->nav, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (priv->layout), hpaned, TRUE, TRUE, 0); + } else { + gtk_box_pack_start (GTK_BOX (priv->layout), hpaned, TRUE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (priv->layout), priv->nav, FALSE, FALSE, 0); + } + } + + break; + } + + gtk_box_pack_end (GTK_BOX (priv->cbox), priv->layout, TRUE, TRUE, 0); + + eom_thumb_nav_set_mode (EOM_THUMB_NAV (priv->nav), mode); + + if (priv->mode != EOM_WINDOW_STATUS_UNKNOWN) { + update_action_groups_state (EOM_WINDOW (user_data)); + } +} + +static void +eom_window_can_save_changed_cb (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data) +{ + EomWindowPrivate *priv; + EomWindow *window; + gboolean save_disabled = FALSE; + GtkAction *action_save, *action_save_as; + + eom_debug (DEBUG_PREFERENCES); + + g_return_if_fail (EOM_IS_WINDOW (user_data)); + + window = EOM_WINDOW (user_data); + priv = EOM_WINDOW (user_data)->priv; + + if (entry->value != NULL && entry->value->type == MATECONF_VALUE_BOOL) { + save_disabled = mateconf_value_get_bool (entry->value); + } + + priv->save_disabled = save_disabled; + + action_save = + gtk_action_group_get_action (priv->actions_image, "ImageSave"); + action_save_as = + gtk_action_group_get_action (priv->actions_image, "ImageSaveAs"); + + if (priv->save_disabled) { + gtk_action_set_sensitive (action_save, FALSE); + gtk_action_set_sensitive (action_save_as, FALSE); + } else { + EomImage *image = eom_window_get_image (window); + + if (EOM_IS_IMAGE (image)) { + gtk_action_set_sensitive (action_save, + eom_image_is_modified (image)); + + gtk_action_set_sensitive (action_save_as, TRUE); + } + } +} + +static void +eom_window_pd_nbmode_changed_cb (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data) +{ + EomWindow *window = EOM_WINDOW (user_data); + + if (window->priv->properties_dlg != NULL) { + gboolean netbook_mode; + EomPropertiesDialog *dlg; + + netbook_mode = mateconf_value_get_bool (entry->value); + dlg = EOM_PROPERTIES_DIALOG (window->priv->properties_dlg); + + eom_properties_dialog_set_netbook_mode (dlg, netbook_mode); + } +} + +#ifdef HAVE_LCMS +static cmsHPROFILE * +eom_window_get_display_profile (GdkScreen *screen) +{ + Display *dpy; + Atom icc_atom, type; + int format; + gulong nitems; + gulong bytes_after; + gulong length; + guchar *str; + int result; + cmsHPROFILE *profile; + char *atom_name; + int lcms_error_action; + + dpy = GDK_DISPLAY_XDISPLAY (gdk_screen_get_display (screen)); + + if (gdk_screen_get_number (screen) > 0) + atom_name = g_strdup_printf ("_ICC_PROFILE_%d", gdk_screen_get_number (screen)); + else + atom_name = g_strdup ("_ICC_PROFILE"); + + icc_atom = gdk_x11_get_xatom_by_name_for_display (gdk_screen_get_display (screen), atom_name); + + g_free (atom_name); + + result = XGetWindowProperty (dpy, + GDK_WINDOW_XID (gdk_screen_get_root_window (screen)), + icc_atom, + 0, + G_MAXLONG, + False, + XA_CARDINAL, + &type, + &format, + &nitems, + &bytes_after, + (guchar **)&str); + + /* TODO: handle bytes_after != 0 */ + + if ((result == Success) && (type == XA_CARDINAL) && (nitems > 0)) { + switch (format) + { + case 8: + length = nitems; + break; + case 16: + length = sizeof(short) * nitems; + break; + case 32: + length = sizeof(long) * nitems; + break; + default: + eom_debug_message (DEBUG_LCMS, "Unable to read profile, not correcting"); + + XFree (str); + return NULL; + } + + /* Make lcms errors non-fatal here, as it is possible + * to load invalid profiles with XICC. + * We don't want lcms to abort EOM in that case. + */ + lcms_error_action = cmsErrorAction (LCMS_ERROR_IGNORE); + + profile = cmsOpenProfileFromMem (str, length); + + // Restore the previous error setting + cmsErrorAction (lcms_error_action); + + if (G_UNLIKELY (profile == NULL)) { + eom_debug_message (DEBUG_LCMS, + "Invalid display profile, " + "not correcting"); + } + + XFree (str); + } else { + profile = NULL; + eom_debug_message (DEBUG_LCMS, "No profile, not correcting"); + } + + return profile; +} +#endif + +static void +update_image_pos (EomWindow *window) +{ + EomWindowPrivate *priv; + gint pos, n_images; + + priv = window->priv; + + n_images = eom_list_store_length (EOM_LIST_STORE (priv->store)); + + if (n_images > 0) { + pos = eom_list_store_get_pos_by_image (EOM_LIST_STORE (priv->store), + priv->image); + + /* Images: (image pos) / (n_total_images) */ + eom_statusbar_set_image_number (EOM_STATUSBAR (priv->statusbar), + pos + 1, + n_images); + } +} + +static void +update_status_bar (EomWindow *window) +{ + EomWindowPrivate *priv; + char *str = NULL; + + g_return_if_fail (EOM_IS_WINDOW (window)); + + eom_debug (DEBUG_WINDOW); + + priv = window->priv; + + if (priv->image != NULL && + eom_image_has_data (priv->image, EOM_IMAGE_DATA_ALL)) { + int zoom, width, height; + goffset bytes = 0; + + zoom = floor (100 * eom_scroll_view_get_zoom (EOM_SCROLL_VIEW (priv->view)) + 0.5); + + eom_image_get_size (priv->image, &width, &height); + + bytes = eom_image_get_bytes (priv->image); + + if ((width > 0) && (height > 0)) { + char *size_string; + + size_string = g_format_size_for_display (bytes); + + /* Translators: This is the string displayed in the statusbar + * The tokens are from left to right: + * - image width + * - image height + * - image size in bytes + * - zoom in percent */ + str = g_strdup_printf (ngettext("%i × %i pixel %s %i%%", + "%i × %i pixels %s %i%%", height), + width, + height, + size_string, + zoom); + + g_free (size_string); + } + + update_image_pos (window); + } + + gtk_statusbar_pop (GTK_STATUSBAR (priv->statusbar), + priv->image_info_message_cid); + + gtk_statusbar_push (GTK_STATUSBAR (priv->statusbar), + priv->image_info_message_cid, str ? str : ""); + + g_free (str); +} + +static void +eom_window_set_message_area (EomWindow *window, + GtkWidget *message_area) +{ + if (window->priv->message_area == message_area) + return; + + if (window->priv->message_area != NULL) + gtk_widget_destroy (window->priv->message_area); + + window->priv->message_area = message_area; + + if (message_area == NULL) return; + + gtk_box_pack_start (GTK_BOX (window->priv->cbox), + window->priv->message_area, + FALSE, + FALSE, + 0); + + g_object_add_weak_pointer (G_OBJECT (window->priv->message_area), + (void *) &window->priv->message_area); +} + +static void +update_action_groups_state (EomWindow *window) +{ + EomWindowPrivate *priv; + GtkAction *action_collection; + GtkAction *action_sidebar; + GtkAction *action_fscreen; + GtkAction *action_sshow; + GtkAction *action_print; + gboolean print_disabled = FALSE; + gboolean page_setup_disabled = FALSE; + gboolean show_image_collection = FALSE; + gint n_images = 0; + + g_return_if_fail (EOM_IS_WINDOW (window)); + + eom_debug (DEBUG_WINDOW); + + priv = window->priv; + + action_collection = + gtk_action_group_get_action (priv->actions_window, + "ViewImageCollection"); + + action_sidebar = + gtk_action_group_get_action (priv->actions_window, + "ViewSidebar"); + + action_fscreen = + gtk_action_group_get_action (priv->actions_image, + "ViewFullscreen"); + + action_sshow = + gtk_action_group_get_action (priv->actions_collection, + "ViewSlideshow"); + + action_print = + gtk_action_group_get_action (priv->actions_image, + "ImagePrint"); + + g_assert (action_collection != NULL); + g_assert (action_sidebar != NULL); + g_assert (action_fscreen != NULL); + g_assert (action_sshow != NULL); + g_assert (action_print != NULL); + + if (priv->store != NULL) { + n_images = eom_list_store_length (EOM_LIST_STORE (priv->store)); + } + + if (n_images == 0) { + gtk_widget_hide (priv->layout); + + gtk_action_group_set_sensitive (priv->actions_window, TRUE); + gtk_action_group_set_sensitive (priv->actions_image, FALSE); + gtk_action_group_set_sensitive (priv->actions_collection, FALSE); + + gtk_action_set_sensitive (action_fscreen, FALSE); + gtk_action_set_sensitive (action_sshow, FALSE); + + /* If there are no images on model, initialization + stops here. */ + if (priv->status == EOM_WINDOW_STATUS_INIT) { + priv->status = EOM_WINDOW_STATUS_NORMAL; + } + } else { + if (priv->flags & EOM_STARTUP_DISABLE_COLLECTION) { + mateconf_client_set_bool (priv->client, + EOM_CONF_UI_IMAGE_COLLECTION, + FALSE, + NULL); + + show_image_collection = FALSE; + } else { + show_image_collection = + mateconf_client_get_bool (priv->client, + EOM_CONF_UI_IMAGE_COLLECTION, + NULL); + } + + show_image_collection = show_image_collection && + n_images > 1 && + priv->mode != EOM_WINDOW_MODE_SLIDESHOW; + + gtk_widget_show (priv->layout); + + if (show_image_collection) + gtk_widget_show (priv->nav); + + gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action_collection), + show_image_collection); + + gtk_action_group_set_sensitive (priv->actions_window, TRUE); + gtk_action_group_set_sensitive (priv->actions_image, TRUE); + + gtk_action_set_sensitive (action_fscreen, TRUE); + + if (n_images == 1) { + gtk_action_group_set_sensitive (priv->actions_collection, FALSE); + gtk_action_set_sensitive (action_collection, FALSE); + gtk_action_set_sensitive (action_sshow, FALSE); + } else { + gtk_action_group_set_sensitive (priv->actions_collection, TRUE); + gtk_action_set_sensitive (action_sshow, TRUE); + } + + if (show_image_collection) + gtk_widget_grab_focus (priv->thumbview); + else + gtk_widget_grab_focus (priv->view); + } + + print_disabled = mateconf_client_get_bool (priv->client, + EOM_CONF_DESKTOP_CAN_PRINT, + NULL); + + if (print_disabled) { + gtk_action_set_sensitive (action_print, FALSE); + } + + page_setup_disabled = mateconf_client_get_bool (priv->client, + EOM_CONF_DESKTOP_CAN_SETUP_PAGE, + NULL); + + if (eom_sidebar_is_empty (EOM_SIDEBAR (priv->sidebar))) { + gtk_action_set_sensitive (action_sidebar, FALSE); + gtk_widget_hide (priv->sidebar); + } +} + +static void +update_selection_ui_visibility (EomWindow *window) +{ + EomWindowPrivate *priv; + GtkAction *wallpaper_action; + gint n_selected; + + priv = window->priv; + + n_selected = eom_thumb_view_get_n_selected (EOM_THUMB_VIEW (priv->thumbview)); + + wallpaper_action = + gtk_action_group_get_action (priv->actions_image, + "ImageSetAsWallpaper"); + + if (n_selected == 1) { + gtk_action_set_sensitive (wallpaper_action, TRUE); + } else { + gtk_action_set_sensitive (wallpaper_action, FALSE); + } +} + +static gboolean +add_file_to_recent_files (GFile *file) +{ + gchar *text_uri; + GFileInfo *file_info; + GtkRecentData *recent_data; + static gchar *groups[2] = { EOM_RECENT_FILES_GROUP , NULL }; + + if (file == NULL) return FALSE; + + /* The password gets stripped here because ~/.recently-used.xbel is + * readable by everyone (chmod 644). It also makes the workaround + * for the bug with gtk_recent_info_get_uri_display() easier + * (see the comment in eom_window_update_recent_files_menu()). */ + text_uri = g_file_get_uri (file); + + if (text_uri == NULL) + return FALSE; + + file_info = g_file_query_info (file, + G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, + 0, NULL, NULL); + if (file_info == NULL) + return FALSE; + + recent_data = g_slice_new (GtkRecentData); + recent_data->display_name = NULL; + recent_data->description = NULL; + recent_data->mime_type = (gchar *) g_file_info_get_content_type (file_info); + recent_data->app_name = EOM_RECENT_FILES_APP_NAME; + recent_data->app_exec = g_strjoin(" ", g_get_prgname (), "%u", NULL); + recent_data->groups = groups; + recent_data->is_private = FALSE; + + gtk_recent_manager_add_full (gtk_recent_manager_get_default (), + text_uri, + recent_data); + + g_free (recent_data->app_exec); + g_free (text_uri); + g_object_unref (file_info); + + g_slice_free (GtkRecentData, recent_data); + + return FALSE; +} + +static void +image_thumb_changed_cb (EomImage *image, gpointer data) +{ + EomWindow *window; + EomWindowPrivate *priv; + GdkPixbuf *thumb; + + g_return_if_fail (EOM_IS_WINDOW (data)); + + window = EOM_WINDOW (data); + priv = window->priv; + + thumb = eom_image_get_thumbnail (image); + + if (thumb != NULL) { + gtk_window_set_icon (GTK_WINDOW (window), thumb); + + if (window->priv->properties_dlg != NULL) { + eom_properties_dialog_update (EOM_PROPERTIES_DIALOG (priv->properties_dlg), + image); + } + + g_object_unref (thumb); + } else if (!gtk_widget_get_visible (window->priv->nav)) { + gint img_pos = eom_list_store_get_pos_by_image (window->priv->store, image); + GtkTreePath *path = gtk_tree_path_new_from_indices (img_pos,-1); + GtkTreeIter iter; + + gtk_tree_model_get_iter (GTK_TREE_MODEL (window->priv->store), &iter, path); + eom_list_store_thumbnail_set (window->priv->store, &iter); + gtk_tree_path_free (path); + } +} + +static void +file_changed_info_bar_response (GtkInfoBar *info_bar, + gint response, + EomWindow *window) +{ + if (response == GTK_RESPONSE_YES) { + eom_window_reload_image (window); + } + + window->priv->needs_reload_confirmation = TRUE; + + eom_window_set_message_area (window, NULL); +} +static void +image_file_changed_cb (EomImage *img, EomWindow *window) +{ + GtkWidget *info_bar; + gchar *text, *markup; + GtkWidget *image; + GtkWidget *label; + GtkWidget *hbox; + + if (window->priv->needs_reload_confirmation == FALSE) + return; + + window->priv->needs_reload_confirmation = FALSE; + + info_bar = gtk_info_bar_new_with_buttons (_("_Reload"), + GTK_RESPONSE_YES, + C_("MessageArea", "Hi_de"), + GTK_RESPONSE_NO, NULL); + gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), + GTK_MESSAGE_QUESTION); + image = gtk_image_new_from_stock (GTK_STOCK_DIALOG_QUESTION, + GTK_ICON_SIZE_DIALOG); + label = gtk_label_new (NULL); + + /* The newline character is currently necessary due to a problem + * with the automatic line break. */ + text = g_strdup_printf (_("The image \"%s\" has been modified by an external application." + "\nWould you like to reload it?"), eom_image_get_caption (img)); + markup = g_markup_printf_escaped ("<b>%s</b>", text); + gtk_label_set_markup (GTK_LABEL (label), markup); + g_free (text); + g_free (markup); + + hbox = gtk_hbox_new (FALSE, 8); + gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0); + gtk_misc_set_alignment (GTK_MISC (image), 0.5, 0); + gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 0); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5); + gtk_box_pack_start (GTK_BOX (gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar))), hbox, TRUE, TRUE, 0); + gtk_widget_show_all (hbox); + gtk_widget_show (info_bar); + + eom_window_set_message_area (window, info_bar); + g_signal_connect (info_bar, "response", + G_CALLBACK (file_changed_info_bar_response), window); +} + +static void +eom_window_display_image (EomWindow *window, EomImage *image) +{ + EomWindowPrivate *priv; + GFile *file; + + g_return_if_fail (EOM_IS_WINDOW (window)); + g_return_if_fail (EOM_IS_IMAGE (image)); + + eom_debug (DEBUG_WINDOW); + + g_assert (eom_image_has_data (image, EOM_IMAGE_DATA_ALL)); + + priv = window->priv; + + if (image != NULL) { + g_signal_connect (image, + "thumbnail_changed", + G_CALLBACK (image_thumb_changed_cb), + window); + g_signal_connect (image, "file-changed", + G_CALLBACK (image_file_changed_cb), + window); + + image_thumb_changed_cb (image, window); + } + + priv->needs_reload_confirmation = TRUE; + + eom_scroll_view_set_image (EOM_SCROLL_VIEW (priv->view), image); + + gtk_window_set_title (GTK_WINDOW (window), eom_image_get_caption (image)); + + update_status_bar (window); + + file = eom_image_get_file (image); + g_idle_add_full (G_PRIORITY_LOW, + (GSourceFunc) add_file_to_recent_files, + file, + (GDestroyNotify) g_object_unref); + + eom_window_update_openwith_menu (window, image); +} + +static void +open_with_launch_application_cb (GtkAction *action, gpointer data) { + EomImage *image; + GAppInfo *app; + GFile *file; + GList *files = NULL; + + image = EOM_IMAGE (data); + file = eom_image_get_file (image); + + app = g_object_get_data (G_OBJECT (action), "app"); + files = g_list_append (files, file); + g_app_info_launch (app, + files, + NULL, NULL); + + g_object_unref (file); + g_list_free (files); +} + +static void +eom_window_update_openwith_menu (EomWindow *window, EomImage *image) +{ + GFile *file; + GFileInfo *file_info; + GList *iter; + gchar *label, *tip; + const gchar *mime_type; + GtkAction *action; + EomWindowPrivate *priv; + GList *apps; + guint action_id = 0; + GIcon *app_icon; + char *path; + GtkWidget *menuitem; + + priv = window->priv; + + file = eom_image_get_file (image); + file_info = g_file_query_info (file, + G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, + 0, NULL, NULL); + + if (file_info == NULL) + return; + else { + mime_type = g_file_info_get_content_type (file_info); + } + + if (priv->open_with_menu_id != 0) { + gtk_ui_manager_remove_ui (priv->ui_mgr, priv->open_with_menu_id); + priv->open_with_menu_id = 0; + } + + if (priv->actions_open_with != NULL) { + gtk_ui_manager_remove_action_group (priv->ui_mgr, priv->actions_open_with); + priv->actions_open_with = NULL; + } + + if (mime_type == NULL) { + g_object_unref (file_info); + return; + } + + apps = g_app_info_get_all_for_type (mime_type); + + g_object_unref (file_info); + + if (!apps) + return; + + priv->actions_open_with = gtk_action_group_new ("OpenWithActions"); + gtk_ui_manager_insert_action_group (priv->ui_mgr, priv->actions_open_with, -1); + + priv->open_with_menu_id = gtk_ui_manager_new_merge_id (priv->ui_mgr); + + for (iter = apps; iter; iter = iter->next) { + GAppInfo *app = iter->data; + gchar name[64]; + + /* Do not include eom itself */ + if (g_ascii_strcasecmp (g_app_info_get_executable (app), + g_get_prgname ()) == 0) { + g_object_unref (app); + continue; + } + + g_snprintf (name, sizeof (name), "OpenWith%u", action_id++); + + label = g_strdup (g_app_info_get_name (app)); + tip = g_strdup_printf (_("Use \"%s\" to open the selected image"), g_app_info_get_name (app)); + + action = gtk_action_new (name, label, tip, NULL); + + app_icon = g_app_info_get_icon (app); + if (G_LIKELY (app_icon != NULL)) { + g_object_ref (app_icon); + gtk_action_set_gicon (action, app_icon); + g_object_unref (app_icon); + } + + g_free (label); + g_free (tip); + + g_object_set_data_full (G_OBJECT (action), "app", app, + (GDestroyNotify) g_object_unref); + + g_signal_connect (action, + "activate", + G_CALLBACK (open_with_launch_application_cb), + image); + + gtk_action_group_add_action (priv->actions_open_with, action); + g_object_unref (action); + + gtk_ui_manager_add_ui (priv->ui_mgr, + priv->open_with_menu_id, + "/MainMenu/Image/ImageOpenWith/Applications Placeholder", + name, + name, + GTK_UI_MANAGER_MENUITEM, + FALSE); + + gtk_ui_manager_add_ui (priv->ui_mgr, + priv->open_with_menu_id, + "/ThumbnailPopup/ImageOpenWith/Applications Placeholder", + name, + name, + GTK_UI_MANAGER_MENUITEM, + FALSE); + gtk_ui_manager_add_ui (priv->ui_mgr, + priv->open_with_menu_id, + "/ViewPopup/ImageOpenWith/Applications Placeholder", + name, + name, + GTK_UI_MANAGER_MENUITEM, + FALSE); + + path = g_strdup_printf ("/MainMenu/Image/ImageOpenWith/Applications Placeholder/%s", name); + + menuitem = gtk_ui_manager_get_widget (priv->ui_mgr, path); + + /* Only force displaying the icon if it is an application icon */ + gtk_image_menu_item_set_always_show_image (GTK_IMAGE_MENU_ITEM (menuitem), app_icon != NULL); + + g_free (path); + + path = g_strdup_printf ("/ThumbnailPopup/ImageOpenWith/Applications Placeholder/%s", name); + + menuitem = gtk_ui_manager_get_widget (priv->ui_mgr, path); + + /* Only force displaying the icon if it is an application icon */ + gtk_image_menu_item_set_always_show_image (GTK_IMAGE_MENU_ITEM (menuitem), app_icon != NULL); + + g_free (path); + + path = g_strdup_printf ("/ViewPopup/ImageOpenWith/Applications Placeholder/%s", name); + + menuitem = gtk_ui_manager_get_widget (priv->ui_mgr, path); + + /* Only force displaying the icon if it is an application icon */ + gtk_image_menu_item_set_always_show_image (GTK_IMAGE_MENU_ITEM (menuitem), app_icon != NULL); + + g_free (path); + } + + g_list_free (apps); +} + +static void +eom_window_clear_load_job (EomWindow *window) +{ + EomWindowPrivate *priv = window->priv; + + if (priv->load_job != NULL) { + if (!priv->load_job->finished) + eom_job_queue_remove_job (priv->load_job); + + g_signal_handlers_disconnect_by_func (priv->load_job, + eom_job_progress_cb, + window); + + g_signal_handlers_disconnect_by_func (priv->load_job, + eom_job_load_cb, + window); + + eom_image_cancel_load (EOM_JOB_LOAD (priv->load_job)->image); + + g_object_unref (priv->load_job); + priv->load_job = NULL; + + /* Hide statusbar */ + eom_statusbar_set_progress (EOM_STATUSBAR (priv->statusbar), 0); + } +} + +static void +eom_job_progress_cb (EomJobLoad *job, float progress, gpointer user_data) +{ + EomWindow *window; + + g_return_if_fail (EOM_IS_WINDOW (user_data)); + + window = EOM_WINDOW (user_data); + + eom_statusbar_set_progress (EOM_STATUSBAR (window->priv->statusbar), + progress); +} + +static void +eom_job_save_progress_cb (EomJobSave *job, float progress, gpointer user_data) +{ + EomWindowPrivate *priv; + EomWindow *window; + + static EomImage *image = NULL; + + g_return_if_fail (EOM_IS_WINDOW (user_data)); + + window = EOM_WINDOW (user_data); + priv = window->priv; + + eom_statusbar_set_progress (EOM_STATUSBAR (priv->statusbar), + progress); + + if (image != job->current_image) { + gchar *str_image, *status_message; + guint n_images; + + image = job->current_image; + + n_images = g_list_length (job->images); + + str_image = eom_image_get_uri_for_display (image); + + /* Translators: This string is displayed in the statusbar + * while saving images. The tokens are from left to right: + * - the original filename + * - the current image's position in the queue + * - the total number of images queued for saving */ + status_message = g_strdup_printf (_("Saving image \"%s\" (%u/%u)"), + str_image, + job->current_pos + 1, + n_images); + g_free (str_image); + + gtk_statusbar_pop (GTK_STATUSBAR (priv->statusbar), + priv->image_info_message_cid); + + gtk_statusbar_push (GTK_STATUSBAR (priv->statusbar), + priv->image_info_message_cid, + status_message); + + g_free (status_message); + } + + if (progress == 1.0) + image = NULL; +} + +static void +eom_window_obtain_desired_size (EomImage *image, + gint width, + gint height, + EomWindow *window) +{ + GdkScreen *screen; + GdkRectangle monitor; + GtkAllocation allocation; + gint final_width, final_height; + gint screen_width, screen_height; + gint window_width, window_height; + gint img_width, img_height; + gint view_width, view_height; + gint deco_width, deco_height; + + update_action_groups_state (window); + + img_width = width; + img_height = height; + +#if GTK_CHECK_VERSION (2, 20, 0) + if (!gtk_widget_get_realized (window->priv->view)) { +#else + if (!GTK_WIDGET_REALIZED (window->priv->view)) { +#endif + gtk_widget_realize (window->priv->view); + } + + gtk_widget_get_allocation (window->priv->view, &allocation); + view_width = allocation.width; + view_height = allocation.height; + +#if GTK_CHECK_VERSION (2, 20, 0) + if (!gtk_widget_get_realized (GTK_WIDGET (window))) { +#else + if (!GTK_WIDGET_REALIZED (GTK_WIDGET (window))) { +#endif + gtk_widget_realize (GTK_WIDGET (window)); + } + + gtk_widget_get_allocation (GTK_WIDGET (window), &allocation); + window_width = allocation.width; + window_height = allocation.height; + + screen = gtk_window_get_screen (GTK_WINDOW (window)); + + gdk_screen_get_monitor_geometry (screen, + gdk_screen_get_monitor_at_window (screen, + gtk_widget_get_window (GTK_WIDGET (window))), + &monitor); + + screen_width = monitor.width; + screen_height = monitor.height; + + deco_width = window_width - view_width; + deco_height = window_height - view_height; + + if (img_width > 0 && img_height > 0) { + if ((img_width + deco_width > screen_width) || + (img_height + deco_height > screen_height)) + { + double factor; + + if (img_width > img_height) { + factor = (screen_width * 0.75 - deco_width) / (double) img_width; + } else { + factor = (screen_height * 0.75 - deco_height) / (double) img_height; + } + + img_width = img_width * factor; + img_height = img_height * factor; + } + } + + final_width = MAX (EOM_WINDOW_MIN_WIDTH, img_width + deco_width); + final_height = MAX (EOM_WINDOW_MIN_HEIGHT, img_height + deco_height); + + eom_debug_message (DEBUG_WINDOW, "Setting window size: %d x %d", final_width, final_height); + + gtk_window_set_default_size (GTK_WINDOW (window), final_width, final_height); + + g_signal_emit (window, signals[SIGNAL_PREPARED], 0); +} + +static void +eom_window_error_message_area_response (GtkInfoBar *message_area, + gint response_id, + EomWindow *window) +{ + if (response_id != GTK_RESPONSE_OK) { + eom_window_set_message_area (window, NULL); + + return; + } + + /* Trigger loading for current image again */ + eom_thumb_view_select_single (EOM_THUMB_VIEW (window->priv->thumbview), + EOM_THUMB_VIEW_SELECT_CURRENT); +} + +static void +eom_job_load_cb (EomJobLoad *job, gpointer data) +{ + EomWindow *window; + EomWindowPrivate *priv; + GtkAction *action_undo, *action_save; + + g_return_if_fail (EOM_IS_WINDOW (data)); + + eom_debug (DEBUG_WINDOW); + + window = EOM_WINDOW (data); + priv = window->priv; + + eom_statusbar_set_progress (EOM_STATUSBAR (priv->statusbar), 0.0); + + gtk_statusbar_pop (GTK_STATUSBAR (window->priv->statusbar), + priv->image_info_message_cid); + + if (priv->image != NULL) { + g_signal_handlers_disconnect_by_func (priv->image, + image_thumb_changed_cb, + window); + g_signal_handlers_disconnect_by_func (priv->image, + image_file_changed_cb, + window); + + g_object_unref (priv->image); + } + + priv->image = g_object_ref (job->image); + + if (EOM_JOB (job)->error == NULL) { +#ifdef HAVE_LCMS + eom_image_apply_display_profile (job->image, + priv->display_profile); +#endif + + gtk_action_group_set_sensitive (priv->actions_image, TRUE); + + eom_window_display_image (window, job->image); + } else { + GtkWidget *message_area; + + message_area = eom_image_load_error_message_area_new ( + eom_image_get_caption (job->image), + EOM_JOB (job)->error); + + g_signal_connect (message_area, + "response", + G_CALLBACK (eom_window_error_message_area_response), + window); + + gtk_window_set_icon (GTK_WINDOW (window), NULL); + gtk_window_set_title (GTK_WINDOW (window), + eom_image_get_caption (job->image)); + + eom_window_set_message_area (window, message_area); + + gtk_info_bar_set_default_response (GTK_INFO_BAR (message_area), + GTK_RESPONSE_CANCEL); + + gtk_widget_show (message_area); + + update_status_bar (window); + + eom_scroll_view_set_image (EOM_SCROLL_VIEW (priv->view), NULL); + + if (window->priv->status == EOM_WINDOW_STATUS_INIT) { + update_action_groups_state (window); + + g_signal_emit (window, signals[SIGNAL_PREPARED], 0); + } + + gtk_action_group_set_sensitive (priv->actions_image, FALSE); + } + + eom_window_clear_load_job (window); + + if (window->priv->status == EOM_WINDOW_STATUS_INIT) { + window->priv->status = EOM_WINDOW_STATUS_NORMAL; + + g_signal_handlers_disconnect_by_func + (job->image, + G_CALLBACK (eom_window_obtain_desired_size), + window); + } + + action_save = gtk_action_group_get_action (priv->actions_image, "ImageSave"); + action_undo = gtk_action_group_get_action (priv->actions_image, "EditUndo"); + + /* Set Save and Undo sensitive according to image state. + * Respect lockdown in case of Save.*/ + gtk_action_set_sensitive (action_save, (!priv->save_disabled && eom_image_is_modified (job->image))); + gtk_action_set_sensitive (action_undo, eom_image_is_modified (job->image)); + + g_object_unref (job->image); +} + +static void +eom_window_clear_transform_job (EomWindow *window) +{ + EomWindowPrivate *priv = window->priv; + + if (priv->transform_job != NULL) { + if (!priv->transform_job->finished) + eom_job_queue_remove_job (priv->transform_job); + + g_signal_handlers_disconnect_by_func (priv->transform_job, + eom_job_transform_cb, + window); + g_object_unref (priv->transform_job); + priv->transform_job = NULL; + } +} + +static void +eom_job_transform_cb (EomJobTransform *job, gpointer data) +{ + EomWindow *window; + GtkAction *action_undo, *action_save; + EomImage *image; + + g_return_if_fail (EOM_IS_WINDOW (data)); + + window = EOM_WINDOW (data); + + eom_window_clear_transform_job (window); + + action_undo = + gtk_action_group_get_action (window->priv->actions_image, "EditUndo"); + action_save = + gtk_action_group_get_action (window->priv->actions_image, "ImageSave"); + + image = eom_window_get_image (window); + + gtk_action_set_sensitive (action_undo, eom_image_is_modified (image)); + + if (!window->priv->save_disabled) + { + gtk_action_set_sensitive (action_save, eom_image_is_modified (image)); + } +} + +static void +apply_transformation (EomWindow *window, EomTransform *trans) +{ + EomWindowPrivate *priv; + GList *images; + + g_return_if_fail (EOM_IS_WINDOW (window)); + + priv = window->priv; + + images = eom_thumb_view_get_selected_images (EOM_THUMB_VIEW (priv->thumbview)); + + eom_window_clear_transform_job (window); + + priv->transform_job = eom_job_transform_new (images, trans); + + g_signal_connect (priv->transform_job, + "finished", + G_CALLBACK (eom_job_transform_cb), + window); + + g_signal_connect (priv->transform_job, + "progress", + G_CALLBACK (eom_job_progress_cb), + window); + + eom_job_queue_add_job (priv->transform_job); +} + +static void +handle_image_selection_changed_cb (EomThumbView *thumbview, EomWindow *window) +{ + EomWindowPrivate *priv; + EomImage *image; + gchar *status_message; + gchar *str_image; + + priv = window->priv; + + if (eom_thumb_view_get_n_selected (EOM_THUMB_VIEW (priv->thumbview)) == 0) + return; + + update_selection_ui_visibility (window); + + image = eom_thumb_view_get_first_selected_image (EOM_THUMB_VIEW (priv->thumbview)); + + g_assert (EOM_IS_IMAGE (image)); + + eom_window_clear_load_job (window); + + eom_window_set_message_area (window, NULL); + + gtk_statusbar_pop (GTK_STATUSBAR (priv->statusbar), + priv->image_info_message_cid); + + if (image == priv->image) { + update_status_bar (window); + return; + } + + if (eom_image_has_data (image, EOM_IMAGE_DATA_ALL)) { + eom_window_display_image (window, image); + return; + } + + if (priv->status == EOM_WINDOW_STATUS_INIT) { + g_signal_connect (image, + "size-prepared", + G_CALLBACK (eom_window_obtain_desired_size), + window); + } + + priv->load_job = eom_job_load_new (image, EOM_IMAGE_DATA_ALL); + + g_signal_connect (priv->load_job, + "finished", + G_CALLBACK (eom_job_load_cb), + window); + + g_signal_connect (priv->load_job, + "progress", + G_CALLBACK (eom_job_progress_cb), + window); + + eom_job_queue_add_job (priv->load_job); + + str_image = eom_image_get_uri_for_display (image); + + status_message = g_strdup_printf (_("Opening image \"%s\""), + str_image); + + g_free (str_image); + + gtk_statusbar_push (GTK_STATUSBAR (priv->statusbar), + priv->image_info_message_cid, status_message); + + g_free (status_message); +} + +static void +view_zoom_changed_cb (GtkWidget *widget, double zoom, gpointer user_data) +{ + EomWindow *window; + GtkAction *action_zoom_in; + GtkAction *action_zoom_out; + + g_return_if_fail (EOM_IS_WINDOW (user_data)); + + window = EOM_WINDOW (user_data); + + update_status_bar (window); + + action_zoom_in = + gtk_action_group_get_action (window->priv->actions_image, + "ViewZoomIn"); + + action_zoom_out = + gtk_action_group_get_action (window->priv->actions_image, + "ViewZoomOut"); + + gtk_action_set_sensitive (action_zoom_in, + !eom_scroll_view_get_zoom_is_max (EOM_SCROLL_VIEW (window->priv->view))); + gtk_action_set_sensitive (action_zoom_out, + !eom_scroll_view_get_zoom_is_min (EOM_SCROLL_VIEW (window->priv->view))); +} + +static void +eom_window_open_recent_cb (GtkAction *action, EomWindow *window) +{ + GtkRecentInfo *info; + const gchar *uri; + GSList *list = NULL; + + info = g_object_get_data (G_OBJECT (action), "gtk-recent-info"); + g_return_if_fail (info != NULL); + + uri = gtk_recent_info_get_uri (info); + list = g_slist_prepend (list, g_strdup (uri)); + + eom_application_open_uri_list (EOM_APP, + list, + GDK_CURRENT_TIME, + 0, + NULL); + + g_slist_foreach (list, (GFunc) g_free, NULL); + g_slist_free (list); +} + +static void +file_open_dialog_response_cb (GtkWidget *chooser, + gint response_id, + EomWindow *ev_window) +{ + if (response_id == GTK_RESPONSE_OK) { + GSList *uris; + + uris = gtk_file_chooser_get_uris (GTK_FILE_CHOOSER (chooser)); + + eom_application_open_uri_list (EOM_APP, + uris, + GDK_CURRENT_TIME, + 0, + NULL); + + g_slist_foreach (uris, (GFunc) g_free, NULL); + g_slist_free (uris); + } + + gtk_widget_destroy (chooser); +} + +static void +eom_window_update_fullscreen_action (EomWindow *window) +{ + GtkAction *action; + + action = gtk_action_group_get_action (window->priv->actions_image, + "ViewFullscreen"); + + g_signal_handlers_block_by_func + (action, G_CALLBACK (eom_window_cmd_fullscreen), window); + + gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), + window->priv->mode == EOM_WINDOW_MODE_FULLSCREEN); + + g_signal_handlers_unblock_by_func + (action, G_CALLBACK (eom_window_cmd_fullscreen), window); +} + +static void +eom_window_update_slideshow_action (EomWindow *window) +{ + GtkAction *action; + + action = gtk_action_group_get_action (window->priv->actions_collection, + "ViewSlideshow"); + + g_signal_handlers_block_by_func + (action, G_CALLBACK (eom_window_cmd_slideshow), window); + + gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), + window->priv->mode == EOM_WINDOW_MODE_SLIDESHOW); + + g_signal_handlers_unblock_by_func + (action, G_CALLBACK (eom_window_cmd_slideshow), window); +} + +static void +eom_window_update_pause_slideshow_action (EomWindow *window) +{ + GtkAction *action; + + action = gtk_action_group_get_action (window->priv->actions_image, + "PauseSlideshow"); + + g_signal_handlers_block_by_func + (action, G_CALLBACK (eom_window_cmd_pause_slideshow), window); + + gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), + window->priv->mode != EOM_WINDOW_MODE_SLIDESHOW); + + g_signal_handlers_unblock_by_func + (action, G_CALLBACK (eom_window_cmd_pause_slideshow), window); +} + +static void +eom_window_update_fullscreen_popup (EomWindow *window) +{ + GtkWidget *popup = window->priv->fullscreen_popup; + GdkRectangle screen_rect; + GdkScreen *screen; + + g_return_if_fail (popup != NULL); + + if (gtk_widget_get_window (GTK_WIDGET (window)) == NULL) return; + + screen = gtk_widget_get_screen (GTK_WIDGET (window)); + + gdk_screen_get_monitor_geometry (screen, + gdk_screen_get_monitor_at_window + (screen, + gtk_widget_get_window (GTK_WIDGET (window))), + &screen_rect); + + gtk_widget_set_size_request (popup, + screen_rect.width, + -1); + + gtk_window_move (GTK_WINDOW (popup), screen_rect.x, screen_rect.y); +} + +static void +screen_size_changed_cb (GdkScreen *screen, EomWindow *window) +{ + eom_window_update_fullscreen_popup (window); +} + +static void +fullscreen_popup_size_request_cb (GtkWidget *popup, + GtkRequisition *req, + EomWindow *window) +{ + eom_window_update_fullscreen_popup (window); +} + +static gboolean +fullscreen_timeout_cb (gpointer data) +{ + EomWindow *window = EOM_WINDOW (data); + + gtk_widget_hide_all (window->priv->fullscreen_popup); + + eom_scroll_view_hide_cursor (EOM_SCROLL_VIEW (window->priv->view)); + + fullscreen_clear_timeout (window); + + return FALSE; +} + +static gboolean +slideshow_is_loop_end (EomWindow *window) +{ + EomWindowPrivate *priv = window->priv; + EomImage *image = NULL; + gint pos; + + image = eom_thumb_view_get_first_selected_image (EOM_THUMB_VIEW (priv->thumbview)); + + pos = eom_list_store_get_pos_by_image (priv->store, image); + + return (pos == (eom_list_store_length (priv->store) - 1)); +} + +static gboolean +slideshow_switch_cb (gpointer data) +{ + EomWindow *window = EOM_WINDOW (data); + EomWindowPrivate *priv = window->priv; + + eom_debug (DEBUG_WINDOW); + + if (!priv->slideshow_loop && slideshow_is_loop_end (window)) { + eom_window_stop_fullscreen (window, TRUE); + return FALSE; + } + + eom_thumb_view_select_single (EOM_THUMB_VIEW (priv->thumbview), + EOM_THUMB_VIEW_SELECT_RIGHT); + + return TRUE; +} + +static void +fullscreen_clear_timeout (EomWindow *window) +{ + eom_debug (DEBUG_WINDOW); + + if (window->priv->fullscreen_timeout_source != NULL) { + g_source_unref (window->priv->fullscreen_timeout_source); + g_source_destroy (window->priv->fullscreen_timeout_source); + } + + window->priv->fullscreen_timeout_source = NULL; +} + +static void +fullscreen_set_timeout (EomWindow *window) +{ + GSource *source; + + eom_debug (DEBUG_WINDOW); + + fullscreen_clear_timeout (window); + + source = g_timeout_source_new (EOM_WINDOW_FULLSCREEN_TIMEOUT); + g_source_set_callback (source, fullscreen_timeout_cb, window, NULL); + + g_source_attach (source, NULL); + + window->priv->fullscreen_timeout_source = source; + + eom_scroll_view_show_cursor (EOM_SCROLL_VIEW (window->priv->view)); +} + +static void +slideshow_clear_timeout (EomWindow *window) +{ + eom_debug (DEBUG_WINDOW); + + if (window->priv->slideshow_switch_source != NULL) { + g_source_unref (window->priv->slideshow_switch_source); + g_source_destroy (window->priv->slideshow_switch_source); + } + + window->priv->slideshow_switch_source = NULL; +} + +static void +slideshow_set_timeout (EomWindow *window) +{ + GSource *source; + + eom_debug (DEBUG_WINDOW); + + slideshow_clear_timeout (window); + + if (window->priv->slideshow_switch_timeout <= 0) + return; + + source = g_timeout_source_new (window->priv->slideshow_switch_timeout * 1000); + g_source_set_callback (source, slideshow_switch_cb, window, NULL); + + g_source_attach (source, NULL); + + window->priv->slideshow_switch_source = source; +} + +static void +show_fullscreen_popup (EomWindow *window) +{ + eom_debug (DEBUG_WINDOW); + + if (!gtk_widget_get_visible (window->priv->fullscreen_popup)) { + gtk_widget_show_all (GTK_WIDGET (window->priv->fullscreen_popup)); + } + + fullscreen_set_timeout (window); +} + +static gboolean +fullscreen_motion_notify_cb (GtkWidget *widget, + GdkEventMotion *event, + gpointer user_data) +{ + EomWindow *window = EOM_WINDOW (user_data); + + eom_debug (DEBUG_WINDOW); + + if (event->y < EOM_WINDOW_FULLSCREEN_POPUP_THRESHOLD) { + show_fullscreen_popup (window); + } else { + fullscreen_set_timeout (window); + } + + return FALSE; +} + +static gboolean +fullscreen_leave_notify_cb (GtkWidget *widget, + GdkEventCrossing *event, + gpointer user_data) +{ + EomWindow *window = EOM_WINDOW (user_data); + + eom_debug (DEBUG_WINDOW); + + fullscreen_clear_timeout (window); + + return FALSE; +} + +static void +exit_fullscreen_button_clicked_cb (GtkWidget *button, EomWindow *window) +{ + GtkAction *action; + + eom_debug (DEBUG_WINDOW); + + if (window->priv->mode == EOM_WINDOW_MODE_SLIDESHOW) { + action = gtk_action_group_get_action (window->priv->actions_collection, + "ViewSlideshow"); + } else { + action = gtk_action_group_get_action (window->priv->actions_image, + "ViewFullscreen"); + } + g_return_if_fail (action != NULL); + + gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), FALSE); +} + +static GtkWidget * +eom_window_get_exit_fullscreen_button (EomWindow *window) +{ + GtkWidget *button; + + button = gtk_button_new_from_stock (GTK_STOCK_LEAVE_FULLSCREEN); + + g_signal_connect (button, "clicked", + G_CALLBACK (exit_fullscreen_button_clicked_cb), + window); + + return button; +} + +static GtkWidget * +eom_window_create_fullscreen_popup (EomWindow *window) +{ + GtkWidget *popup; + GtkWidget *hbox; + GtkWidget *button; + GtkWidget *toolbar; + GdkScreen *screen; + + eom_debug (DEBUG_WINDOW); + + popup = gtk_window_new (GTK_WINDOW_POPUP); + + hbox = gtk_hbox_new (FALSE, 0); + gtk_container_add (GTK_CONTAINER (popup), hbox); + + toolbar = gtk_ui_manager_get_widget (window->priv->ui_mgr, + "/FullscreenToolbar"); + g_assert (GTK_IS_WIDGET (toolbar)); + gtk_toolbar_set_style (GTK_TOOLBAR (toolbar), GTK_TOOLBAR_ICONS); + gtk_box_pack_start (GTK_BOX (hbox), toolbar, TRUE, TRUE, 0); + + button = eom_window_get_exit_fullscreen_button (window); + gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0); + + gtk_window_set_resizable (GTK_WINDOW (popup), FALSE); + + screen = gtk_widget_get_screen (GTK_WIDGET (window)); + + g_signal_connect_object (screen, "size-changed", + G_CALLBACK (screen_size_changed_cb), + window, 0); + + g_signal_connect_object (popup, "size_request", + G_CALLBACK (fullscreen_popup_size_request_cb), + window, 0); + + g_signal_connect (popup, + "enter-notify-event", + G_CALLBACK (fullscreen_leave_notify_cb), + window); + + gtk_window_set_screen (GTK_WINDOW (popup), screen); + + return popup; +} + +static void +update_ui_visibility (EomWindow *window) +{ + EomWindowPrivate *priv; + + GtkAction *action; + GtkWidget *menubar; + GError *error = NULL; + + gboolean fullscreen_mode, visible; + + g_return_if_fail (EOM_IS_WINDOW (window)); + + eom_debug (DEBUG_WINDOW); + + priv = window->priv; + + fullscreen_mode = priv->mode == EOM_WINDOW_MODE_FULLSCREEN || + priv->mode == EOM_WINDOW_MODE_SLIDESHOW; + + menubar = gtk_ui_manager_get_widget (priv->ui_mgr, "/MainMenu"); + g_assert (GTK_IS_WIDGET (menubar)); + + visible = mateconf_client_get_bool (priv->client, EOM_CONF_UI_TOOLBAR, &error); + visible = visible && !fullscreen_mode; + if (error) { + g_error_free (error); + error = NULL; + visible = !fullscreen_mode; + } + action = gtk_ui_manager_get_action (priv->ui_mgr, "/MainMenu/View/ToolbarToggle"); + g_assert (action != NULL); + gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), visible); + g_object_set (G_OBJECT (priv->toolbar), "visible", visible, NULL); + + visible = mateconf_client_get_bool (priv->client, EOM_CONF_UI_STATUSBAR, &error); + visible = visible && !fullscreen_mode; + if (error) { + g_error_free (error); + error = NULL; + visible = !fullscreen_mode; + } + action = gtk_ui_manager_get_action (priv->ui_mgr, "/MainMenu/View/StatusbarToggle"); + g_assert (action != NULL); + gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), visible); + g_object_set (G_OBJECT (priv->statusbar), "visible", visible, NULL); + + if (priv->status != EOM_WINDOW_STATUS_INIT) { + visible = mateconf_client_get_bool (priv->client, EOM_CONF_UI_IMAGE_COLLECTION, NULL); + visible = visible && priv->mode != EOM_WINDOW_MODE_SLIDESHOW; + action = gtk_ui_manager_get_action (priv->ui_mgr, "/MainMenu/View/ImageCollectionToggle"); + g_assert (action != NULL); + gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), visible); + if (visible) { + gtk_widget_show (priv->nav); + } else { + gtk_widget_hide (priv->nav); + } + } + + visible = mateconf_client_get_bool (priv->client, EOM_CONF_UI_SIDEBAR, NULL); + visible = visible && !fullscreen_mode; + action = gtk_ui_manager_get_action (priv->ui_mgr, "/MainMenu/View/SidebarToggle"); + g_assert (action != NULL); + gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), visible); + if (visible) { + gtk_widget_show (priv->sidebar); + } else { + gtk_widget_hide (priv->sidebar); + } + + if (priv->fullscreen_popup != NULL) { + gtk_widget_hide_all (priv->fullscreen_popup); + } +} + +static void +eom_window_run_fullscreen (EomWindow *window, gboolean slideshow) +{ + EomWindowPrivate *priv; + GtkWidget *menubar; + gboolean upscale; + + eom_debug (DEBUG_WINDOW); + + priv = window->priv; + + if (slideshow) { + priv->mode = EOM_WINDOW_MODE_SLIDESHOW; + } else { + /* Stop the timer if we come from slideshowing */ + if (priv->mode == EOM_WINDOW_MODE_SLIDESHOW) + slideshow_clear_timeout (window); + + priv->mode = EOM_WINDOW_MODE_FULLSCREEN; + } + + if (window->priv->fullscreen_popup == NULL) + priv->fullscreen_popup + = eom_window_create_fullscreen_popup (window); + + update_ui_visibility (window); + + menubar = gtk_ui_manager_get_widget (priv->ui_mgr, "/MainMenu"); + g_assert (GTK_IS_WIDGET (menubar)); + gtk_widget_hide (menubar); + + g_signal_connect (priv->view, + "motion-notify-event", + G_CALLBACK (fullscreen_motion_notify_cb), + window); + + g_signal_connect (priv->view, + "leave-notify-event", + G_CALLBACK (fullscreen_leave_notify_cb), + window); + + g_signal_connect (priv->thumbview, + "motion-notify-event", + G_CALLBACK (fullscreen_motion_notify_cb), + window); + + g_signal_connect (priv->thumbview, + "leave-notify-event", + G_CALLBACK (fullscreen_leave_notify_cb), + window); + + fullscreen_set_timeout (window); + + if (slideshow) { + priv->slideshow_loop = + mateconf_client_get_bool (priv->client, + EOM_CONF_FULLSCREEN_LOOP, + NULL); + + priv->slideshow_switch_timeout = + mateconf_client_get_int (priv->client, + EOM_CONF_FULLSCREEN_SECONDS, + NULL); + + slideshow_set_timeout (window); + } + + upscale = mateconf_client_get_bool (priv->client, + EOM_CONF_FULLSCREEN_UPSCALE, + NULL); + + eom_scroll_view_set_zoom_upscale (EOM_SCROLL_VIEW (priv->view), + upscale); + + gtk_widget_grab_focus (priv->view); + + eom_scroll_view_override_bg_color (EOM_SCROLL_VIEW (window->priv->view), + &(gtk_widget_get_style (GTK_WIDGET (window))->black)); + + { + GtkStyle *style; + + style = gtk_style_copy (gtk_widget_get_style (gtk_widget_get_parent (priv->view))); + + style->xthickness = 0; + style->ythickness = 0; + + gtk_widget_set_style (gtk_widget_get_parent (priv->view), + style); + + g_object_unref (style); + } + + gtk_window_fullscreen (GTK_WINDOW (window)); + eom_window_update_fullscreen_popup (window); + +#ifdef HAVE_DBUS + eom_application_screensaver_disable (EOM_APP); +#endif + + /* Update both actions as we could've already been in one those modes */ + eom_window_update_slideshow_action (window); + eom_window_update_fullscreen_action (window); + eom_window_update_pause_slideshow_action (window); +} + +static void +eom_window_stop_fullscreen (EomWindow *window, gboolean slideshow) +{ + EomWindowPrivate *priv; + GtkWidget *menubar; + + eom_debug (DEBUG_WINDOW); + + priv = window->priv; + + if (priv->mode != EOM_WINDOW_MODE_SLIDESHOW && + priv->mode != EOM_WINDOW_MODE_FULLSCREEN) return; + + priv->mode = EOM_WINDOW_MODE_NORMAL; + + fullscreen_clear_timeout (window); + + if (slideshow) { + slideshow_clear_timeout (window); + } + + g_signal_handlers_disconnect_by_func (priv->view, + (gpointer) fullscreen_motion_notify_cb, + window); + + g_signal_handlers_disconnect_by_func (priv->view, + (gpointer) fullscreen_leave_notify_cb, + window); + + g_signal_handlers_disconnect_by_func (priv->thumbview, + (gpointer) fullscreen_motion_notify_cb, + window); + + g_signal_handlers_disconnect_by_func (priv->thumbview, + (gpointer) fullscreen_leave_notify_cb, + window); + + update_ui_visibility (window); + + menubar = gtk_ui_manager_get_widget (priv->ui_mgr, "/MainMenu"); + g_assert (GTK_IS_WIDGET (menubar)); + gtk_widget_show (menubar); + + eom_scroll_view_set_zoom_upscale (EOM_SCROLL_VIEW (priv->view), FALSE); + + eom_scroll_view_override_bg_color (EOM_SCROLL_VIEW (window->priv->view), + NULL); + gtk_widget_set_style (gtk_widget_get_parent (window->priv->view), NULL); + gtk_window_unfullscreen (GTK_WINDOW (window)); + + if (slideshow) { + eom_window_update_slideshow_action (window); + } else { + eom_window_update_fullscreen_action (window); + } + + eom_scroll_view_show_cursor (EOM_SCROLL_VIEW (priv->view)); + +#ifdef HAVE_DBUS + eom_application_screensaver_enable (EOM_APP); +#endif +} + +static void +eom_window_print (EomWindow *window) +{ + GtkWidget *dialog; + GError *error = NULL; + GtkPrintOperation *print; + GtkPrintOperationResult res; + GtkPageSetup *page_setup; + GtkPrintSettings *print_settings; + + eom_debug (DEBUG_PRINTING); + + print_settings = eom_print_get_print_settings (); + page_setup = eom_print_get_page_setup (); + + /* Make sure the window stays valid while printing */ + g_object_ref (window); + + print = eom_print_operation_new (window->priv->image, + print_settings, + page_setup); + + res = gtk_print_operation_run (print, + GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG, + GTK_WINDOW (window), &error); + + if (res == GTK_PRINT_OPERATION_RESULT_ERROR) { + dialog = gtk_message_dialog_new (GTK_WINDOW (window), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + _("Error printing file:\n%s"), + error->message); + g_signal_connect (dialog, "response", + G_CALLBACK (gtk_widget_destroy), NULL); + gtk_widget_show (dialog); + g_error_free (error); + } else if (res == GTK_PRINT_OPERATION_RESULT_APPLY) { + eom_print_set_print_settings (gtk_print_operation_get_print_settings (print)); + eom_print_set_page_setup (gtk_print_operation_get_default_page_setup (print)); + } + + g_object_unref (page_setup); + g_object_unref (print_settings); + g_object_unref (window); +} + +static void +eom_window_cmd_file_open (GtkAction *action, gpointer user_data) +{ + EomWindow *window; + EomWindowPrivate *priv; + EomImage *current; + GtkWidget *dlg; + + g_return_if_fail (EOM_IS_WINDOW (user_data)); + + window = EOM_WINDOW (user_data); + + priv = window->priv; + + dlg = eom_file_chooser_new (GTK_FILE_CHOOSER_ACTION_OPEN); + + current = eom_thumb_view_get_first_selected_image (EOM_THUMB_VIEW (priv->thumbview)); + + if (current != NULL) { + gchar *dir_uri, *file_uri; + + file_uri = eom_image_get_uri_for_display (current); + dir_uri = g_path_get_dirname (file_uri); + + gtk_file_chooser_set_current_folder_uri (GTK_FILE_CHOOSER (dlg), + dir_uri); + g_free (file_uri); + g_free (dir_uri); + g_object_unref (current); + } else { + /* If desired by the user, + fallback to the XDG_PICTURES_DIR (if available) */ + const gchar *pics_dir; + gboolean use_fallback; + + use_fallback = mateconf_client_get_bool (priv->client, + EOM_CONF_UI_FILECHOOSER_XDG_FALLBACK, + NULL); + pics_dir = g_get_user_special_dir (G_USER_DIRECTORY_PICTURES); + if (use_fallback && pics_dir) { + gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dlg), + pics_dir); + } + } + + g_signal_connect (dlg, "response", + G_CALLBACK (file_open_dialog_response_cb), + window); + + gtk_widget_show_all (dlg); +} + +static void +eom_job_close_save_cb (EomJobSave *job, gpointer user_data) +{ + EomWindow *window = EOM_WINDOW (user_data); + + g_signal_handlers_disconnect_by_func (job, + eom_job_close_save_cb, + window); + + gtk_widget_destroy (GTK_WIDGET (window)); +} + +static void +close_confirmation_dialog_response_handler (EomCloseConfirmationDialog *dlg, + gint response_id, + EomWindow *window) +{ + GList *selected_images; + EomWindowPrivate *priv; + + priv = window->priv; + + switch (response_id) + { + case GTK_RESPONSE_YES: + /* save selected images */ + selected_images = eom_close_confirmation_dialog_get_selected_images (dlg); + eom_close_confirmation_dialog_set_sensitive (dlg, FALSE); + if (eom_window_save_images (window, selected_images)) { + g_signal_connect (priv->save_job, + "finished", + G_CALLBACK (eom_job_close_save_cb), + window); + + eom_job_queue_add_job (priv->save_job); + } + + break; + + case GTK_RESPONSE_NO: + /* dont save */ + gtk_widget_destroy (GTK_WIDGET (window)); + break; + + default: + /* Cancel */ + gtk_widget_destroy (GTK_WIDGET (dlg)); + break; + } +} + +static gboolean +eom_window_unsaved_images_confirm (EomWindow *window) +{ + EomWindowPrivate *priv; + GtkWidget *dialog; + GList *list; + EomImage *image; + GtkTreeIter iter; + + priv = window->priv; + + if (window->priv->save_disabled) { + return FALSE; + } + + list = NULL; + if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (priv->store), &iter)) { + do { + gtk_tree_model_get (GTK_TREE_MODEL (priv->store), &iter, + EOM_LIST_STORE_EOM_IMAGE, &image, + -1); + if (!image) + continue; + + if (eom_image_is_modified (image)) { + list = g_list_append (list, image); + } + } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (priv->store), &iter)); + } + + if (list) { + dialog = eom_close_confirmation_dialog_new (GTK_WINDOW (window), + list); + + g_signal_connect (dialog, + "response", + G_CALLBACK (close_confirmation_dialog_response_handler), + window); + gtk_window_set_destroy_with_parent (GTK_WINDOW (dialog), TRUE); + + gtk_widget_show (dialog); + return TRUE; + + } + return FALSE; +} + +static void +eom_window_cmd_close_window (GtkAction *action, gpointer user_data) +{ + EomWindow *window; + EomWindowPrivate *priv; + + g_return_if_fail (EOM_IS_WINDOW (user_data)); + + window = EOM_WINDOW (user_data); + priv = window->priv; + + if (priv->save_job != NULL) { + eom_window_finish_saving (window); + } + + if (!eom_window_unsaved_images_confirm (window)) { + gtk_widget_destroy (GTK_WIDGET (user_data)); + } +} + +static void +eom_window_cmd_preferences (GtkAction *action, gpointer user_data) +{ + EomWindow *window; + GObject *pref_dlg; + + g_return_if_fail (EOM_IS_WINDOW (user_data)); + + window = EOM_WINDOW (user_data); + + pref_dlg = eom_preferences_dialog_get_instance (GTK_WINDOW (window), + window->priv->client); + + eom_dialog_show (EOM_DIALOG (pref_dlg)); +} + +#define EOM_TB_EDITOR_DLG_RESET_RESPONSE 128 + +static void +eom_window_cmd_edit_toolbar_cb (GtkDialog *dialog, gint response, gpointer data) +{ + EomWindow *window = EOM_WINDOW (data); + + if (response == EOM_TB_EDITOR_DLG_RESET_RESPONSE) { + EggToolbarsModel *model; + EggToolbarEditor *editor; + + editor = g_object_get_data (G_OBJECT (dialog), + "EggToolbarEditor"); + + g_return_if_fail (editor != NULL); + + egg_editable_toolbar_set_edit_mode + (EGG_EDITABLE_TOOLBAR (window->priv->toolbar), FALSE); + + eom_application_reset_toolbars_model (EOM_APP); + model = eom_application_get_toolbars_model (EOM_APP); + egg_editable_toolbar_set_model + (EGG_EDITABLE_TOOLBAR (window->priv->toolbar), model); + egg_toolbar_editor_set_model (editor, model); + + /* Toolbar would be uneditable now otherwise */ + egg_editable_toolbar_set_edit_mode + (EGG_EDITABLE_TOOLBAR (window->priv->toolbar), TRUE); + } else if (response == GTK_RESPONSE_HELP) { + eom_util_show_help ("eom-toolbareditor", NULL); + } else { + egg_editable_toolbar_set_edit_mode + (EGG_EDITABLE_TOOLBAR (window->priv->toolbar), FALSE); + + eom_application_save_toolbars_model (EOM_APP); + + gtk_widget_destroy (GTK_WIDGET (dialog)); + } +} + +static void +eom_window_cmd_edit_toolbar (GtkAction *action, gpointer *user_data) +{ + EomWindow *window; + GtkWidget *dialog; + GtkWidget *editor; + + g_return_if_fail (EOM_IS_WINDOW (user_data)); + + window = EOM_WINDOW (user_data); + + dialog = gtk_dialog_new_with_buttons (_("Toolbar Editor"), + GTK_WINDOW (window), + GTK_DIALOG_DESTROY_WITH_PARENT, + _("_Reset to Default"), + EOM_TB_EDITOR_DLG_RESET_RESPONSE, + GTK_STOCK_CLOSE, + GTK_RESPONSE_CLOSE, + GTK_STOCK_HELP, + GTK_RESPONSE_HELP, + NULL); + + gtk_dialog_set_default_response (GTK_DIALOG (dialog), + GTK_RESPONSE_CLOSE); + + gtk_container_set_border_width (GTK_CONTAINER (dialog), 5); + + gtk_box_set_spacing (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), 2); + + gtk_dialog_set_has_separator (GTK_DIALOG (dialog), FALSE); + + gtk_window_set_default_size (GTK_WINDOW (dialog), 500, 400); + + editor = egg_toolbar_editor_new (window->priv->ui_mgr, + eom_application_get_toolbars_model (EOM_APP)); + + gtk_container_set_border_width (GTK_CONTAINER (editor), 5); + + gtk_box_set_spacing (GTK_BOX (EGG_TOOLBAR_EDITOR (editor)), 5); + + gtk_container_add (GTK_CONTAINER (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), editor); + + egg_editable_toolbar_set_edit_mode + (EGG_EDITABLE_TOOLBAR (window->priv->toolbar), TRUE); + + g_object_set_data (G_OBJECT (dialog), "EggToolbarEditor", editor); + + g_signal_connect (dialog, + "response", + G_CALLBACK (eom_window_cmd_edit_toolbar_cb), + window); + + gtk_widget_show_all (dialog); +} + +static void +eom_window_cmd_help (GtkAction *action, gpointer user_data) +{ + EomWindow *window; + + g_return_if_fail (EOM_IS_WINDOW (user_data)); + + window = EOM_WINDOW (user_data); + + eom_util_show_help (NULL, GTK_WINDOW (window)); +} + +static void +eom_window_cmd_about (GtkAction *action, gpointer user_data) +{ + EomWindow *window; + + g_return_if_fail (EOM_IS_WINDOW (user_data)); + + static const char *authors[] = { + "Claudio Saavedra <[email protected]> (maintainer)", + "Felix Riemann <[email protected]> (maintainer)", + "", + "Lucas Rocha <[email protected]>", + "Tim Gerla <[email protected]>", + "Philip Van Hoof <[email protected]>", + "Paolo Borelli <[email protected]>", + "Jens Finke <[email protected]>", + "Martin Baulig <[email protected]>", + "Arik Devens <[email protected]>", + "Michael Meeks <[email protected]>", + "Federico Mena-Quintero <[email protected]>", + "Lutz M\xc3\xbcller <[email protected]>", + NULL + }; + + static const char *documenters[] = { + "Eliot Landrum <[email protected]>", + "Federico Mena-Quintero <[email protected]>", + "Sun MATE Documentation Team <[email protected]>", + NULL + }; + + const char *translators; + + translators = _("translator-credits"); + + const char *license[] = { + N_("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.\n"), + N_("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.\n"), + N_("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.") + }; + + char *license_trans; + + license_trans = g_strconcat (_(license[0]), "\n", _(license[1]), "\n", + _(license[2]), "\n", NULL); + + window = EOM_WINDOW (user_data); + + gtk_show_about_dialog (GTK_WINDOW (window), + "program-name", _("Eye of MATE"), + "version", VERSION, + "copyright", "Copyright \xc2\xa9 2000-2010 Free Software Foundation, Inc.", + "comments",_("The MATE image viewer."), + "authors", authors, + "documenters", documenters, + "translator-credits", translators, + "website", "http://projects.gnome.org/eom/", + "logo-icon-name", "eom", + "wrap-license", TRUE, + "license", license_trans, + NULL); + + g_free (license_trans); +} + +static void +eom_window_cmd_show_hide_bar (GtkAction *action, gpointer user_data) +{ + EomWindow *window; + EomWindowPrivate *priv; + gboolean visible; + + g_return_if_fail (EOM_IS_WINDOW (user_data)); + + window = EOM_WINDOW (user_data); + priv = window->priv; + + if (priv->mode != EOM_WINDOW_MODE_NORMAL && + priv->mode != EOM_WINDOW_MODE_FULLSCREEN) return; + + visible = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)); + + if (g_ascii_strcasecmp (gtk_action_get_name (action), "ViewToolbar") == 0) { + g_object_set (G_OBJECT (priv->toolbar), "visible", visible, NULL); + + if (priv->mode == EOM_WINDOW_MODE_NORMAL) + mateconf_client_set_bool (priv->client, EOM_CONF_UI_TOOLBAR, visible, NULL); + + } else if (g_ascii_strcasecmp (gtk_action_get_name (action), "ViewStatusbar") == 0) { + g_object_set (G_OBJECT (priv->statusbar), "visible", visible, NULL); + + if (priv->mode == EOM_WINDOW_MODE_NORMAL) + mateconf_client_set_bool (priv->client, EOM_CONF_UI_STATUSBAR, visible, NULL); + + } else if (g_ascii_strcasecmp (gtk_action_get_name (action), "ViewImageCollection") == 0) { + if (visible) { + /* Make sure the focus widget is realized to + * avoid warnings on keypress events */ +#if GTK_CHECK_VERSION (2, 20, 0) + if (!gtk_widget_get_realized (window->priv->thumbview)) +#else + if (!GTK_WIDGET_REALIZED (window->priv->thumbview)) +#endif + gtk_widget_realize (window->priv->thumbview); + + gtk_widget_show (priv->nav); + gtk_widget_grab_focus (priv->thumbview); + } else { + /* Make sure the focus widget is realized to + * avoid warnings on keypress events. + * Don't do it during init phase or the view + * will get a bogus allocation. */ +#if GTK_CHECK_VERSION (2, 20, 0) + if (!gtk_widget_get_realized (priv->view) +#else + if (!GTK_WIDGET_REALIZED (priv->view) +#endif + && priv->status == EOM_WINDOW_STATUS_NORMAL) + gtk_widget_realize (priv->view); + + gtk_widget_hide (priv->nav); + +#if GTK_CHECK_VERSION (2, 20, 0) + if (gtk_widget_get_realized (priv->view)) +#else + if (GTK_WIDGET_REALIZED (priv->view)) +#endif + gtk_widget_grab_focus (priv->view); + } + mateconf_client_set_bool (priv->client, EOM_CONF_UI_IMAGE_COLLECTION, visible, NULL); + + } else if (g_ascii_strcasecmp (gtk_action_get_name (action), "ViewSidebar") == 0) { + if (visible) { + gtk_widget_show (priv->sidebar); + } else { + gtk_widget_hide (priv->sidebar); + } + mateconf_client_set_bool (priv->client, EOM_CONF_UI_SIDEBAR, visible, NULL); + } +} + +static void +wallpaper_info_bar_response (GtkInfoBar *bar, gint response, EomWindow *window) +{ + if (response == GTK_RESPONSE_YES) { + GdkScreen *screen; + + screen = gtk_widget_get_screen (GTK_WIDGET (window)); + gdk_spawn_command_line_on_screen (screen, + "mate-appearance-properties" + " --show-page=background", + NULL); + } + + /* Close message area on every response */ + eom_window_set_message_area (window, NULL); +} + +static void +eom_window_set_wallpaper (EomWindow *window, const gchar *filename, const gchar *visible_filename) +{ + EomWindowPrivate *priv = EOM_WINDOW_GET_PRIVATE (window); + GtkWidget *info_bar; + GtkWidget *image; + GtkWidget *label; + GtkWidget *hbox; + gchar *markup; + gchar *text; + gchar *basename; + + + mateconf_client_set_string (priv->client, + EOM_CONF_DESKTOP_WALLPAPER, + filename, + NULL); + + /* I18N: When setting mnemonics for these strings, watch out to not + clash with mnemonics from eom's menubar */ + info_bar = gtk_info_bar_new_with_buttons (_("_Open Background Preferences"), + GTK_RESPONSE_YES, + C_("MessageArea","Hi_de"), + GTK_RESPONSE_NO, NULL); + gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), + GTK_MESSAGE_QUESTION); + + image = gtk_image_new_from_stock (GTK_STOCK_DIALOG_QUESTION, + GTK_ICON_SIZE_DIALOG); + label = gtk_label_new (NULL); + + if (!visible_filename) + basename = g_path_get_basename (filename); + + /* The newline character is currently necessary due to a problem + * with the automatic line break. */ + text = g_strdup_printf (_("The image \"%s\" has been set as Desktop Background." + "\nWould you like to modify its appearance?"), + visible_filename ? visible_filename : basename); + markup = g_markup_printf_escaped ("<b>%s</b>", text); + gtk_label_set_markup (GTK_LABEL (label), markup); + g_free (markup); + g_free (text); + if (!visible_filename) + g_free (basename); + + hbox = gtk_hbox_new (FALSE, 8); + gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0); + gtk_misc_set_alignment (GTK_MISC (image), 0.5, 0); + gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 0); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5); + gtk_box_pack_start (GTK_BOX (gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar))), hbox, TRUE, TRUE, 0); + gtk_widget_show_all (hbox); + gtk_widget_show (info_bar); + + + eom_window_set_message_area (window, info_bar); + gtk_info_bar_set_default_response (GTK_INFO_BAR (info_bar), + GTK_RESPONSE_YES); + g_signal_connect (info_bar, "response", + G_CALLBACK (wallpaper_info_bar_response), window); +} + +static void +eom_job_save_cb (EomJobSave *job, gpointer user_data) +{ + EomWindow *window = EOM_WINDOW (user_data); + GtkAction *action_save; + + g_signal_handlers_disconnect_by_func (job, + eom_job_save_cb, + window); + + g_signal_handlers_disconnect_by_func (job, + eom_job_save_progress_cb, + window); + + g_object_unref (window->priv->save_job); + window->priv->save_job = NULL; + + update_status_bar (window); + action_save = gtk_action_group_get_action (window->priv->actions_image, + "ImageSave"); + gtk_action_set_sensitive (action_save, FALSE); +} + +static void +eom_job_copy_cb (EomJobCopy *job, gpointer user_data) +{ + EomWindow *window = EOM_WINDOW (user_data); + gchar *filepath, *basename, *filename, *extension; + GtkAction *action; + GFile *source_file, *dest_file; + + /* Create source GFile */ + basename = g_file_get_basename (job->images->data); + filepath = g_build_filename (job->dest, basename, NULL); + source_file = g_file_new_for_path (filepath); + g_free (filepath); + + /* Create destination GFile */ + extension = eom_util_filename_get_extension (basename); + filename = g_strdup_printf ("%s.%s", EOM_WALLPAPER_FILENAME, extension); + filepath = g_build_filename (job->dest, filename, NULL); + dest_file = g_file_new_for_path (filepath); + g_free (filename); + g_free (extension); + + /* Move the file */ + g_file_move (source_file, dest_file, G_FILE_COPY_OVERWRITE, + NULL, NULL, NULL, NULL); + + /* Set the wallpaper */ + eom_window_set_wallpaper (window, filepath, basename); + g_free (basename); + g_free (filepath); + + gtk_statusbar_pop (GTK_STATUSBAR (window->priv->statusbar), + window->priv->copy_file_cid); + action = gtk_action_group_get_action (window->priv->actions_image, + "ImageSetAsWallpaper"); + gtk_action_set_sensitive (action, TRUE); + + window->priv->copy_job = NULL; + + g_object_unref (source_file); + g_object_unref (dest_file); + g_object_unref (G_OBJECT (job->images->data)); + g_list_free (job->images); + g_object_unref (job); +} + +static gboolean +eom_window_save_images (EomWindow *window, GList *images) +{ + EomWindowPrivate *priv; + + priv = window->priv; + + if (window->priv->save_job != NULL) + return FALSE; + + priv->save_job = eom_job_save_new (images); + + g_signal_connect (priv->save_job, + "finished", + G_CALLBACK (eom_job_save_cb), + window); + + g_signal_connect (priv->save_job, + "progress", + G_CALLBACK (eom_job_save_progress_cb), + window); + + return TRUE; +} + +static void +eom_window_cmd_save (GtkAction *action, gpointer user_data) +{ + EomWindowPrivate *priv; + EomWindow *window; + GList *images; + + window = EOM_WINDOW (user_data); + priv = window->priv; + + if (window->priv->save_job != NULL) + return; + + images = eom_thumb_view_get_selected_images (EOM_THUMB_VIEW (priv->thumbview)); + + if (eom_window_save_images (window, images)) { + eom_job_queue_add_job (priv->save_job); + } +} + +static GFile* +eom_window_retrieve_save_as_file (EomWindow *window, EomImage *image) +{ + GtkWidget *dialog; + GFile *save_file = NULL; + GFile *last_dest_folder; + gint response; + + g_assert (image != NULL); + + dialog = eom_file_chooser_new (GTK_FILE_CHOOSER_ACTION_SAVE); + + last_dest_folder = window->priv->last_save_as_folder; + + if (last_dest_folder && g_file_query_exists (last_dest_folder, NULL)) { + gtk_file_chooser_set_current_folder_file (GTK_FILE_CHOOSER (dialog), last_dest_folder, NULL); + gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog), + eom_image_get_caption (image)); + } else { + GFile *image_file; + + image_file = eom_image_get_file (image); + /* Setting the file will also navigate to its parent folder */ + gtk_file_chooser_set_file (GTK_FILE_CHOOSER (dialog), + image_file, NULL); + g_object_unref (image_file); + } + + response = gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_hide (dialog); + + if (response == GTK_RESPONSE_OK) { + save_file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog)); + if (window->priv->last_save_as_folder) + g_object_unref (window->priv->last_save_as_folder); + window->priv->last_save_as_folder = g_file_get_parent (save_file); + } + gtk_widget_destroy (dialog); + + return save_file; +} + +static void +eom_window_cmd_save_as (GtkAction *action, gpointer user_data) +{ + EomWindowPrivate *priv; + EomWindow *window; + GList *images; + guint n_images; + + window = EOM_WINDOW (user_data); + priv = window->priv; + + if (window->priv->save_job != NULL) + return; + + images = eom_thumb_view_get_selected_images (EOM_THUMB_VIEW (priv->thumbview)); + n_images = g_list_length (images); + + if (n_images == 1) { + GFile *file; + + file = eom_window_retrieve_save_as_file (window, images->data); + + if (!file) { + g_list_free (images); + return; + } + + priv->save_job = eom_job_save_as_new (images, NULL, file); + + g_object_unref (file); + } else if (n_images > 1) { + GFile *base_file; + GtkWidget *dialog; + gchar *basedir; + EomURIConverter *converter; + + basedir = g_get_current_dir (); + base_file = g_file_new_for_path (basedir); + g_free (basedir); + + dialog = eom_save_as_dialog_new (GTK_WINDOW (window), + images, + base_file); + + gtk_widget_show_all (dialog); + + if (gtk_dialog_run (GTK_DIALOG (dialog)) != GTK_RESPONSE_OK) { + g_object_unref (base_file); + g_list_free (images); + gtk_widget_destroy (dialog); + + return; + } + + converter = eom_save_as_dialog_get_converter (dialog); + + g_assert (converter != NULL); + + priv->save_job = eom_job_save_as_new (images, converter, NULL); + + gtk_widget_destroy (dialog); + + g_object_unref (converter); + g_object_unref (base_file); + } else { + /* n_images = 0 -- No Image selected */ + return; + } + + g_signal_connect (priv->save_job, + "finished", + G_CALLBACK (eom_job_save_cb), + window); + + g_signal_connect (priv->save_job, + "progress", + G_CALLBACK (eom_job_save_progress_cb), + window); + + eom_job_queue_add_job (priv->save_job); +} + +static void +eom_window_cmd_print (GtkAction *action, gpointer user_data) +{ + EomWindow *window = EOM_WINDOW (user_data); + + eom_window_print (window); +} + +static void +eom_window_cmd_properties (GtkAction *action, gpointer user_data) +{ + EomWindow *window = EOM_WINDOW (user_data); + EomWindowPrivate *priv; + GtkAction *next_image_action, *previous_image_action; + + priv = window->priv; + + next_image_action = + gtk_action_group_get_action (priv->actions_collection, + "GoNext"); + + previous_image_action = + gtk_action_group_get_action (priv->actions_collection, + "GoPrevious"); + + if (window->priv->properties_dlg == NULL) { + gboolean netbook_mode; + + window->priv->properties_dlg = + eom_properties_dialog_new (GTK_WINDOW (window), + EOM_THUMB_VIEW (priv->thumbview), + next_image_action, + previous_image_action); + + eom_properties_dialog_update (EOM_PROPERTIES_DIALOG (priv->properties_dlg), + priv->image); + netbook_mode = + mateconf_client_get_bool (priv->client, + EOM_CONF_UI_PROPSDIALOG_NETBOOK_MODE, + NULL); + eom_properties_dialog_set_netbook_mode (EOM_PROPERTIES_DIALOG (priv->properties_dlg), + netbook_mode); + } + + eom_dialog_show (EOM_DIALOG (window->priv->properties_dlg)); +} + +static void +eom_window_cmd_undo (GtkAction *action, gpointer user_data) +{ + g_return_if_fail (EOM_IS_WINDOW (user_data)); + + apply_transformation (EOM_WINDOW (user_data), NULL); +} + +static void +eom_window_cmd_flip_horizontal (GtkAction *action, gpointer user_data) +{ + g_return_if_fail (EOM_IS_WINDOW (user_data)); + + apply_transformation (EOM_WINDOW (user_data), + eom_transform_flip_new (EOM_TRANSFORM_FLIP_HORIZONTAL)); +} + +static void +eom_window_cmd_flip_vertical (GtkAction *action, gpointer user_data) +{ + g_return_if_fail (EOM_IS_WINDOW (user_data)); + + apply_transformation (EOM_WINDOW (user_data), + eom_transform_flip_new (EOM_TRANSFORM_FLIP_VERTICAL)); +} + +static void +eom_window_cmd_rotate_90 (GtkAction *action, gpointer user_data) +{ + g_return_if_fail (EOM_IS_WINDOW (user_data)); + + apply_transformation (EOM_WINDOW (user_data), + eom_transform_rotate_new (90)); +} + +static void +eom_window_cmd_rotate_270 (GtkAction *action, gpointer user_data) +{ + g_return_if_fail (EOM_IS_WINDOW (user_data)); + + apply_transformation (EOM_WINDOW (user_data), + eom_transform_rotate_new (270)); +} + +static void +eom_window_cmd_wallpaper (GtkAction *action, gpointer user_data) +{ + EomWindow *window; + EomWindowPrivate *priv; + EomImage *image; + GFile *file; + char *filename = NULL; + + g_return_if_fail (EOM_IS_WINDOW (user_data)); + + window = EOM_WINDOW (user_data); + priv = window->priv; + + /* If currently copying an image to set it as wallpaper, return. */ + if (priv->copy_job != NULL) + return; + + image = eom_thumb_view_get_first_selected_image (EOM_THUMB_VIEW (priv->thumbview)); + + g_return_if_fail (EOM_IS_IMAGE (image)); + + file = eom_image_get_file (image); + + filename = g_file_get_path (file); + + /* Currently only local files can be set as wallpaper */ + if (filename == NULL || !eom_util_file_is_persistent (file)) + { + GList *files = NULL; + GtkAction *action; + + action = gtk_action_group_get_action (window->priv->actions_image, + "ImageSetAsWallpaper"); + gtk_action_set_sensitive (action, FALSE); + + priv->copy_file_cid = gtk_statusbar_get_context_id (GTK_STATUSBAR (priv->statusbar), + "copy_file_cid"); + gtk_statusbar_push (GTK_STATUSBAR (priv->statusbar), + priv->copy_file_cid, + _("Saving image locally…")); + + files = g_list_append (files, eom_image_get_file (image)); + priv->copy_job = eom_job_copy_new (files, g_get_user_data_dir ()); + g_signal_connect (priv->copy_job, + "finished", + G_CALLBACK (eom_job_copy_cb), + window); + g_signal_connect (priv->copy_job, + "progress", + G_CALLBACK (eom_job_progress_cb), + window); + eom_job_queue_add_job (priv->copy_job); + + g_object_unref (file); + g_free (filename); + return; + } + + g_object_unref (file); + + eom_window_set_wallpaper (window, filename, NULL); + + g_free (filename); +} + +static gboolean +eom_window_all_images_trasheable (GList *images) +{ + GFile *file; + GFileInfo *file_info; + GList *iter; + EomImage *image; + gboolean can_trash = TRUE; + + for (iter = images; iter != NULL; iter = g_list_next (iter)) { + image = (EomImage *) iter->data; + file = eom_image_get_file (image); + file_info = g_file_query_info (file, + G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH, + 0, NULL, NULL); + can_trash = g_file_info_get_attribute_boolean (file_info, + G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH); + + g_object_unref (file_info); + g_object_unref (file); + + if (can_trash == FALSE) + break; + } + + return can_trash; +} + +static int +show_move_to_trash_confirm_dialog (EomWindow *window, GList *images, gboolean can_trash) +{ + GtkWidget *dlg; + char *prompt; + int response; + int n_images; + EomImage *image; + static gboolean dontaskagain = FALSE; + gboolean neverask = FALSE; + GtkWidget* dontask_cbutton = NULL; + + /* Check if the user never wants to be bugged. Ignore the error as + * it returns FALSE for safety anyway */ + neverask = mateconf_client_get_bool (window->priv->client, + EOM_CONF_UI_DISABLE_TRASH_CONFIRMATION, + NULL); + + /* Assume agreement, if the user doesn't want to be + * asked and the trash is available */ + if (can_trash && (dontaskagain || neverask)) + return GTK_RESPONSE_OK; + + n_images = g_list_length (images); + + if (n_images == 1) { + image = EOM_IMAGE (images->data); + if (can_trash) { + prompt = g_strdup_printf (_("Are you sure you want to move\n\"%s\" to the trash?"), + eom_image_get_caption (image)); + } else { + prompt = g_strdup_printf (_("A trash for \"%s\" couldn't be found. Do you want to remove " + "this image permanently?"), eom_image_get_caption (image)); + } + } else { + if (can_trash) { + prompt = g_strdup_printf (ngettext("Are you sure you want to move\n" + "the selected image to the trash?", + "Are you sure you want to move\n" + "the %d selected images to the trash?", n_images), n_images); + } else { + prompt = g_strdup (_("Some of the selected images can't be moved to the trash " + "and will be removed permanently. Are you sure you want " + "to proceed?")); + } + } + + dlg = gtk_message_dialog_new_with_markup (GTK_WINDOW (window), + GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_WARNING, + GTK_BUTTONS_NONE, + "<span weight=\"bold\" size=\"larger\">%s</span>", + prompt); + g_free (prompt); + + gtk_dialog_add_button (GTK_DIALOG (dlg), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL); + + if (can_trash) { + gtk_dialog_add_button (GTK_DIALOG (dlg), _("Move to _Trash"), GTK_RESPONSE_OK); + + dontask_cbutton = gtk_check_button_new_with_mnemonic (_("_Do not ask again during this session")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dontask_cbutton), FALSE); + + gtk_box_pack_end (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dlg))), dontask_cbutton, TRUE, TRUE, 0); + } else { + if (n_images == 1) { + gtk_dialog_add_button (GTK_DIALOG (dlg), GTK_STOCK_DELETE, GTK_RESPONSE_OK); + } else { + gtk_dialog_add_button (GTK_DIALOG (dlg), GTK_STOCK_YES, GTK_RESPONSE_OK); + } + } + + gtk_dialog_set_default_response (GTK_DIALOG (dlg), GTK_RESPONSE_OK); + gtk_window_set_title (GTK_WINDOW (dlg), ""); + gtk_widget_show_all (dlg); + + response = gtk_dialog_run (GTK_DIALOG (dlg)); + + /* Only update the property if the user has accepted */ + if (can_trash && response == GTK_RESPONSE_OK) + dontaskagain = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dontask_cbutton)); + + /* The checkbutton is destroyed together with the dialog */ + gtk_widget_destroy (dlg); + + return response; +} + +static gboolean +move_to_trash_real (EomImage *image, GError **error) +{ + GFile *file; + GFileInfo *file_info; + gboolean can_trash, result; + + g_return_val_if_fail (EOM_IS_IMAGE (image), FALSE); + + file = eom_image_get_file (image); + file_info = g_file_query_info (file, + G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH, + 0, NULL, NULL); + if (file_info == NULL) { + g_set_error (error, + EOM_WINDOW_ERROR, + EOM_WINDOW_ERROR_TRASH_NOT_FOUND, + _("Couldn't access trash.")); + return FALSE; + } + + can_trash = g_file_info_get_attribute_boolean (file_info, + G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH); + g_object_unref (file_info); + if (can_trash) + { + result = g_file_trash (file, NULL, NULL); + if (result == FALSE) { + g_set_error (error, + EOM_WINDOW_ERROR, + EOM_WINDOW_ERROR_TRASH_NOT_FOUND, + _("Couldn't access trash.")); + } + } else { + result = g_file_delete (file, NULL, NULL); + if (result == FALSE) { + g_set_error (error, + EOM_WINDOW_ERROR, + EOM_WINDOW_ERROR_IO, + _("Couldn't delete file")); + } + } + + g_object_unref (file); + + return result; +} + +static void +eom_window_cmd_move_to_trash (GtkAction *action, gpointer user_data) +{ + GList *images; + GList *it; + EomWindowPrivate *priv; + EomListStore *list; + int pos; + EomImage *img; + EomWindow *window; + int response; + int n_images; + gboolean success; + gboolean can_trash; + + g_return_if_fail (EOM_IS_WINDOW (user_data)); + + window = EOM_WINDOW (user_data); + priv = window->priv; + list = priv->store; + + n_images = eom_thumb_view_get_n_selected (EOM_THUMB_VIEW (priv->thumbview)); + + if (n_images < 1) return; + + /* save position of selected image after the deletion */ + images = eom_thumb_view_get_selected_images (EOM_THUMB_VIEW (priv->thumbview)); + + g_assert (images != NULL); + + /* HACK: eom_list_store_get_n_selected return list in reverse order */ + images = g_list_reverse (images); + + can_trash = eom_window_all_images_trasheable (images); + + if (g_ascii_strcasecmp (gtk_action_get_name (action), "Delete") == 0 || + can_trash == FALSE) { + response = show_move_to_trash_confirm_dialog (window, images, can_trash); + + if (response != GTK_RESPONSE_OK) return; + } + + pos = eom_list_store_get_pos_by_image (list, EOM_IMAGE (images->data)); + + /* FIXME: make a nice progress dialog */ + /* Do the work actually. First try to delete the image from the disk. If this + * is successfull, remove it from the screen. Otherwise show error dialog. + */ + for (it = images; it != NULL; it = it->next) { + GError *error = NULL; + EomImage *image; + + image = EOM_IMAGE (it->data); + + success = move_to_trash_real (image, &error); + + if (success) { + eom_list_store_remove_image (list, image); + } else { + char *header; + GtkWidget *dlg; + + header = g_strdup_printf (_("Error on deleting image %s"), + eom_image_get_caption (image)); + + dlg = gtk_message_dialog_new (GTK_WINDOW (window), + GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_OK, + "%s", header); + + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dlg), + "%s", error->message); + + gtk_dialog_run (GTK_DIALOG (dlg)); + + gtk_widget_destroy (dlg); + + g_free (header); + } + } + + /* free list */ + g_list_foreach (images, (GFunc) g_object_unref, NULL); + g_list_free (images); + + /* select image at previously saved position */ + pos = MIN (pos, eom_list_store_length (list) - 1); + + if (pos >= 0) { + img = eom_list_store_get_image_by_pos (list, pos); + + eom_thumb_view_set_current_image (EOM_THUMB_VIEW (priv->thumbview), + img, + TRUE); + + if (img != NULL) { + g_object_unref (img); + } + } +} + +static void +eom_window_cmd_fullscreen (GtkAction *action, gpointer user_data) +{ + EomWindow *window; + gboolean fullscreen; + + g_return_if_fail (EOM_IS_WINDOW (user_data)); + + eom_debug (DEBUG_WINDOW); + + window = EOM_WINDOW (user_data); + + fullscreen = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)); + + if (fullscreen) { + eom_window_run_fullscreen (window, FALSE); + } else { + eom_window_stop_fullscreen (window, FALSE); + } +} + +static void +eom_window_cmd_slideshow (GtkAction *action, gpointer user_data) +{ + EomWindow *window; + gboolean slideshow; + + g_return_if_fail (EOM_IS_WINDOW (user_data)); + + eom_debug (DEBUG_WINDOW); + + window = EOM_WINDOW (user_data); + + slideshow = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)); + + if (slideshow) { + eom_window_run_fullscreen (window, TRUE); + } else { + eom_window_stop_fullscreen (window, TRUE); + } +} + +static void +eom_window_cmd_pause_slideshow (GtkAction *action, gpointer user_data) +{ + EomWindow *window; + gboolean slideshow; + + g_return_if_fail (EOM_IS_WINDOW (user_data)); + + eom_debug (DEBUG_WINDOW); + + window = EOM_WINDOW (user_data); + + slideshow = window->priv->mode == EOM_WINDOW_MODE_SLIDESHOW; + + if (!slideshow && window->priv->mode != EOM_WINDOW_MODE_FULLSCREEN) + return; + + eom_window_run_fullscreen (window, !slideshow); +} + +static void +eom_window_cmd_zoom_in (GtkAction *action, gpointer user_data) +{ + EomWindowPrivate *priv; + + g_return_if_fail (EOM_IS_WINDOW (user_data)); + + eom_debug (DEBUG_WINDOW); + + priv = EOM_WINDOW (user_data)->priv; + + if (priv->view) { + eom_scroll_view_zoom_in (EOM_SCROLL_VIEW (priv->view), FALSE); + } +} + +static void +eom_window_cmd_zoom_out (GtkAction *action, gpointer user_data) +{ + EomWindowPrivate *priv; + + g_return_if_fail (EOM_IS_WINDOW (user_data)); + + eom_debug (DEBUG_WINDOW); + + priv = EOM_WINDOW (user_data)->priv; + + if (priv->view) { + eom_scroll_view_zoom_out (EOM_SCROLL_VIEW (priv->view), FALSE); + } +} + +static void +eom_window_cmd_zoom_normal (GtkAction *action, gpointer user_data) +{ + EomWindowPrivate *priv; + + g_return_if_fail (EOM_IS_WINDOW (user_data)); + + eom_debug (DEBUG_WINDOW); + + priv = EOM_WINDOW (user_data)->priv; + + if (priv->view) { + eom_scroll_view_set_zoom (EOM_SCROLL_VIEW (priv->view), 1.0); + } +} + +static void +eom_window_cmd_zoom_fit (GtkAction *action, gpointer user_data) +{ + EomWindowPrivate *priv; + + g_return_if_fail (EOM_IS_WINDOW (user_data)); + + eom_debug (DEBUG_WINDOW); + + priv = EOM_WINDOW (user_data)->priv; + + if (priv->view) { + eom_scroll_view_zoom_fit (EOM_SCROLL_VIEW (priv->view)); + } +} + +static void +eom_window_cmd_go_prev (GtkAction *action, gpointer user_data) +{ + EomWindowPrivate *priv; + + g_return_if_fail (EOM_IS_WINDOW (user_data)); + + eom_debug (DEBUG_WINDOW); + + priv = EOM_WINDOW (user_data)->priv; + + eom_thumb_view_select_single (EOM_THUMB_VIEW (priv->thumbview), + EOM_THUMB_VIEW_SELECT_LEFT); +} + +static void +eom_window_cmd_go_next (GtkAction *action, gpointer user_data) +{ + EomWindowPrivate *priv; + + g_return_if_fail (EOM_IS_WINDOW (user_data)); + + eom_debug (DEBUG_WINDOW); + + priv = EOM_WINDOW (user_data)->priv; + + eom_thumb_view_select_single (EOM_THUMB_VIEW (priv->thumbview), + EOM_THUMB_VIEW_SELECT_RIGHT); +} + +static void +eom_window_cmd_go_first (GtkAction *action, gpointer user_data) +{ + EomWindowPrivate *priv; + + g_return_if_fail (EOM_IS_WINDOW (user_data)); + + eom_debug (DEBUG_WINDOW); + + priv = EOM_WINDOW (user_data)->priv; + + eom_thumb_view_select_single (EOM_THUMB_VIEW (priv->thumbview), + EOM_THUMB_VIEW_SELECT_FIRST); +} + +static void +eom_window_cmd_go_last (GtkAction *action, gpointer user_data) +{ + EomWindowPrivate *priv; + + g_return_if_fail (EOM_IS_WINDOW (user_data)); + + eom_debug (DEBUG_WINDOW); + + priv = EOM_WINDOW (user_data)->priv; + + eom_thumb_view_select_single (EOM_THUMB_VIEW (priv->thumbview), + EOM_THUMB_VIEW_SELECT_LAST); +} + +static void +eom_window_cmd_go_random (GtkAction *action, gpointer user_data) +{ + EomWindowPrivate *priv; + + g_return_if_fail (EOM_IS_WINDOW (user_data)); + + eom_debug (DEBUG_WINDOW); + + priv = EOM_WINDOW (user_data)->priv; + + eom_thumb_view_select_single (EOM_THUMB_VIEW (priv->thumbview), + EOM_THUMB_VIEW_SELECT_RANDOM); +} + +static const GtkActionEntry action_entries_window[] = { + { "Image", NULL, N_("_Image") }, + { "Edit", NULL, N_("_Edit") }, + { "View", NULL, N_("_View") }, + { "Go", NULL, N_("_Go") }, + { "Tools", NULL, N_("_Tools") }, + { "Help", NULL, N_("_Help") }, + + { "ImageOpen", GTK_STOCK_OPEN, N_("_Open…"), "<control>O", + N_("Open a file"), + G_CALLBACK (eom_window_cmd_file_open) }, + { "ImageClose", GTK_STOCK_CLOSE, N_("_Close"), "<control>W", + N_("Close window"), + G_CALLBACK (eom_window_cmd_close_window) }, + { "EditToolbar", NULL, N_("T_oolbar"), NULL, + N_("Edit the application toolbar"), + G_CALLBACK (eom_window_cmd_edit_toolbar) }, + { "EditPreferences", GTK_STOCK_PREFERENCES, N_("Prefere_nces"), NULL, + N_("Preferences for Eye of MATE"), + G_CALLBACK (eom_window_cmd_preferences) }, + { "HelpManual", GTK_STOCK_HELP, N_("_Contents"), "F1", + N_("Help on this application"), + G_CALLBACK (eom_window_cmd_help) }, + { "HelpAbout", GTK_STOCK_ABOUT, N_("_About"), NULL, + N_("About this application"), + G_CALLBACK (eom_window_cmd_about) } +}; + +static const GtkToggleActionEntry toggle_entries_window[] = { + { "ViewToolbar", NULL, N_("_Toolbar"), NULL, + N_("Changes the visibility of the toolbar in the current window"), + G_CALLBACK (eom_window_cmd_show_hide_bar), TRUE }, + { "ViewStatusbar", NULL, N_("_Statusbar"), NULL, + N_("Changes the visibility of the statusbar in the current window"), + G_CALLBACK (eom_window_cmd_show_hide_bar), TRUE }, + { "ViewImageCollection", "eom-image-collection", N_("_Image Collection"), "F9", + N_("Changes the visibility of the image collection pane in the current window"), + G_CALLBACK (eom_window_cmd_show_hide_bar), TRUE }, + { "ViewSidebar", NULL, N_("Side _Pane"), "<control>F9", + N_("Changes the visibility of the side pane in the current window"), + G_CALLBACK (eom_window_cmd_show_hide_bar), TRUE }, +}; + +static const GtkActionEntry action_entries_image[] = { + { "ImageSave", GTK_STOCK_SAVE, N_("_Save"), "<control>s", + N_("Save changes in currently selected images"), + G_CALLBACK (eom_window_cmd_save) }, + { "ImageOpenWith", NULL, N_("Open _with"), NULL, + N_("Open the selected image with a different application"), + NULL}, + { "ImageSaveAs", GTK_STOCK_SAVE_AS, N_("Save _As…"), "<control><shift>s", + N_("Save the selected images with a different name"), + G_CALLBACK (eom_window_cmd_save_as) }, + { "ImagePrint", GTK_STOCK_PRINT, N_("_Print…"), "<control>p", + N_("Print the selected image"), + G_CALLBACK (eom_window_cmd_print) }, + { "ImageProperties", GTK_STOCK_PROPERTIES, N_("Prope_rties"), "<alt>Return", + N_("Show the properties and metadata of the selected image"), + G_CALLBACK (eom_window_cmd_properties) }, + { "EditUndo", GTK_STOCK_UNDO, N_("_Undo"), "<control>z", + N_("Undo the last change in the image"), + G_CALLBACK (eom_window_cmd_undo) }, + { "EditFlipHorizontal", "object-flip-horizontal", N_("Flip _Horizontal"), NULL, + N_("Mirror the image horizontally"), + G_CALLBACK (eom_window_cmd_flip_horizontal) }, + { "EditFlipVertical", "object-flip-vertical", N_("Flip _Vertical"), NULL, + N_("Mirror the image vertically"), + G_CALLBACK (eom_window_cmd_flip_vertical) }, + { "EditRotate90", "object-rotate-right", N_("_Rotate Clockwise"), "<control>r", + N_("Rotate the image 90 degrees to the right"), + G_CALLBACK (eom_window_cmd_rotate_90) }, + { "EditRotate270", "object-rotate-left", N_("Rotate Counterc_lockwise"), "<ctrl><shift>r", + N_("Rotate the image 90 degrees to the left"), + G_CALLBACK (eom_window_cmd_rotate_270) }, + { "ImageSetAsWallpaper", NULL, N_("Set as _Desktop Background"), + "<control>F8", N_("Set the selected image as the desktop background"), + G_CALLBACK (eom_window_cmd_wallpaper) }, + { "EditMoveToTrash", "user-trash", N_("Move to _Trash"), NULL, + N_("Move the selected image to the trash folder"), + G_CALLBACK (eom_window_cmd_move_to_trash) }, + { "ViewZoomIn", GTK_STOCK_ZOOM_IN, N_("_Zoom In"), "<control>plus", + N_("Enlarge the image"), + G_CALLBACK (eom_window_cmd_zoom_in) }, + { "ViewZoomOut", GTK_STOCK_ZOOM_OUT, N_("Zoom _Out"), "<control>minus", + N_("Shrink the image"), + G_CALLBACK (eom_window_cmd_zoom_out) }, + { "ViewZoomNormal", GTK_STOCK_ZOOM_100, N_("_Normal Size"), "<control>0", + N_("Show the image at its normal size"), + G_CALLBACK (eom_window_cmd_zoom_normal) }, + { "ViewZoomFit", GTK_STOCK_ZOOM_FIT, N_("Best _Fit"), "F", + N_("Fit the image to the window"), + G_CALLBACK (eom_window_cmd_zoom_fit) }, + { "ControlEqual", GTK_STOCK_ZOOM_IN, N_("_Zoom In"), "<control>equal", + N_("Enlarge the image"), + G_CALLBACK (eom_window_cmd_zoom_in) }, + { "ControlKpAdd", GTK_STOCK_ZOOM_IN, N_("_Zoom In"), "<control>KP_Add", + N_("Shrink the image"), + G_CALLBACK (eom_window_cmd_zoom_in) }, + { "ControlKpSub", GTK_STOCK_ZOOM_OUT, N_("Zoom _Out"), "<control>KP_Subtract", + N_("Shrink the image"), + G_CALLBACK (eom_window_cmd_zoom_out) }, + { "Delete", NULL, N_("Move to _Trash"), "Delete", + NULL, + G_CALLBACK (eom_window_cmd_move_to_trash) }, +}; + +static const GtkToggleActionEntry toggle_entries_image[] = { + { "ViewFullscreen", GTK_STOCK_FULLSCREEN, N_("_Fullscreen"), "F11", + N_("Show the current image in fullscreen mode"), + G_CALLBACK (eom_window_cmd_fullscreen), FALSE }, + { "PauseSlideshow", "media-playback-pause", N_("Pause Slideshow"), + NULL, N_("Pause or resume the slideshow"), + G_CALLBACK (eom_window_cmd_pause_slideshow), FALSE }, +}; + +static const GtkActionEntry action_entries_collection[] = { + { "GoPrevious", GTK_STOCK_GO_BACK, N_("_Previous Image"), "<Alt>Left", + N_("Go to the previous image of the collection"), + G_CALLBACK (eom_window_cmd_go_prev) }, + { "GoNext", GTK_STOCK_GO_FORWARD, N_("_Next Image"), "<Alt>Right", + N_("Go to the next image of the collection"), + G_CALLBACK (eom_window_cmd_go_next) }, + { "GoFirst", GTK_STOCK_GOTO_FIRST, N_("_First Image"), "<Alt>Home", + N_("Go to the first image of the collection"), + G_CALLBACK (eom_window_cmd_go_first) }, + { "GoLast", GTK_STOCK_GOTO_LAST, N_("_Last Image"), "<Alt>End", + N_("Go to the last image of the collection"), + G_CALLBACK (eom_window_cmd_go_last) }, + { "GoRandom", NULL, N_("_Random Image"), "<control>M", + N_("Go to a random image of the collection"), + G_CALLBACK (eom_window_cmd_go_random) }, + { "BackSpace", NULL, N_("_Previous Image"), "BackSpace", + NULL, + G_CALLBACK (eom_window_cmd_go_prev) }, + { "Home", NULL, N_("_First Image"), "Home", + NULL, + G_CALLBACK (eom_window_cmd_go_first) }, + { "End", NULL, N_("_Last Image"), "End", + NULL, + G_CALLBACK (eom_window_cmd_go_last) }, +}; + +static const GtkToggleActionEntry toggle_entries_collection[] = { + { "ViewSlideshow", "slideshow-play", N_("_Slideshow"), "F5", + N_("Start a slideshow view of the images"), + G_CALLBACK (eom_window_cmd_slideshow), FALSE }, +}; + +static void +menu_item_select_cb (GtkMenuItem *proxy, EomWindow *window) +{ + GtkAction *action; + char *message; + + action = gtk_activatable_get_related_action (GTK_ACTIVATABLE (proxy)); + + g_return_if_fail (action != NULL); + + g_object_get (G_OBJECT (action), "tooltip", &message, NULL); + + if (message) { + gtk_statusbar_push (GTK_STATUSBAR (window->priv->statusbar), + window->priv->tip_message_cid, message); + g_free (message); + } +} + +static void +menu_item_deselect_cb (GtkMenuItem *proxy, EomWindow *window) +{ + gtk_statusbar_pop (GTK_STATUSBAR (window->priv->statusbar), + window->priv->tip_message_cid); +} + +static void +connect_proxy_cb (GtkUIManager *manager, + GtkAction *action, + GtkWidget *proxy, + EomWindow *window) +{ + if (GTK_IS_MENU_ITEM (proxy)) { + g_signal_connect (proxy, "select", + G_CALLBACK (menu_item_select_cb), window); + g_signal_connect (proxy, "deselect", + G_CALLBACK (menu_item_deselect_cb), window); + } +} + +static void +disconnect_proxy_cb (GtkUIManager *manager, + GtkAction *action, + GtkWidget *proxy, + EomWindow *window) +{ + if (GTK_IS_MENU_ITEM (proxy)) { + g_signal_handlers_disconnect_by_func + (proxy, G_CALLBACK (menu_item_select_cb), window); + g_signal_handlers_disconnect_by_func + (proxy, G_CALLBACK (menu_item_deselect_cb), window); + } +} + +static void +set_action_properties (GtkActionGroup *window_group, + GtkActionGroup *image_group, + GtkActionGroup *collection_group) +{ + GtkAction *action; + + action = gtk_action_group_get_action (collection_group, "GoPrevious"); + g_object_set (action, "short_label", _("Previous"), NULL); + g_object_set (action, "is-important", TRUE, NULL); + + action = gtk_action_group_get_action (collection_group, "GoNext"); + g_object_set (action, "short_label", _("Next"), NULL); + g_object_set (action, "is-important", TRUE, NULL); + + action = gtk_action_group_get_action (image_group, "EditRotate90"); + g_object_set (action, "short_label", _("Right"), NULL); + + action = gtk_action_group_get_action (image_group, "EditRotate270"); + g_object_set (action, "short_label", _("Left"), NULL); + + action = gtk_action_group_get_action (image_group, "ViewZoomIn"); + g_object_set (action, "short_label", _("In"), NULL); + + action = gtk_action_group_get_action (image_group, "ViewZoomOut"); + g_object_set (action, "short_label", _("Out"), NULL); + + action = gtk_action_group_get_action (image_group, "ViewZoomNormal"); + g_object_set (action, "short_label", _("Normal"), NULL); + + action = gtk_action_group_get_action (image_group, "ViewZoomFit"); + g_object_set (action, "short_label", _("Fit"), NULL); + + action = gtk_action_group_get_action (window_group, "ViewImageCollection"); + g_object_set (action, "short_label", _("Collection"), NULL); + + action = gtk_action_group_get_action (image_group, "EditMoveToTrash"); + g_object_set (action, "short_label", C_("action (to trash)", "Trash"), NULL); +} + +static gint +sort_recents_mru (GtkRecentInfo *a, GtkRecentInfo *b) +{ + gboolean has_eom_a, has_eom_b; + + /* We need to check this first as gtk_recent_info_get_application_info + * will treat it as a non-fatal error when the GtkRecentInfo doesn't + * have the application registered. */ + has_eom_a = gtk_recent_info_has_application (a, + EOM_RECENT_FILES_APP_NAME); + has_eom_b = gtk_recent_info_has_application (b, + EOM_RECENT_FILES_APP_NAME); + if (has_eom_a && has_eom_b) { + time_t time_a, time_b; + + /* These should not fail as we already checked that + * the application is registered with the info objects */ + gtk_recent_info_get_application_info (a, + EOM_RECENT_FILES_APP_NAME, + NULL, + NULL, + &time_a); + gtk_recent_info_get_application_info (b, + EOM_RECENT_FILES_APP_NAME, + NULL, + NULL, + &time_b); + + return (time_b - time_a); + } else if (has_eom_a) { + return -1; + } else if (has_eom_b) { + return 1; + } + + return 0; +} + +static void +eom_window_update_recent_files_menu (EomWindow *window) +{ + EomWindowPrivate *priv; + GList *actions = NULL, *li = NULL, *items = NULL; + guint count_recent = 0; + + priv = window->priv; + + if (priv->recent_menu_id != 0) + gtk_ui_manager_remove_ui (priv->ui_mgr, priv->recent_menu_id); + + actions = gtk_action_group_list_actions (priv->actions_recent); + + for (li = actions; li != NULL; li = li->next) { + g_signal_handlers_disconnect_by_func (GTK_ACTION (li->data), + G_CALLBACK(eom_window_open_recent_cb), + window); + + gtk_action_group_remove_action (priv->actions_recent, + GTK_ACTION (li->data)); + } + + g_list_free (actions); + + priv->recent_menu_id = gtk_ui_manager_new_merge_id (priv->ui_mgr); + items = gtk_recent_manager_get_items (gtk_recent_manager_get_default()); + items = g_list_sort (items, (GCompareFunc) sort_recents_mru); + + for (li = items; li != NULL && count_recent < EOM_RECENT_FILES_LIMIT; li = li->next) { + gchar *action_name; + gchar *label; + gchar *tip; + gchar **display_name; + gchar *label_filename; + GtkAction *action; + GtkRecentInfo *info = li->data; + + /* Sorting moves non-EOM files to the end of the list. + * So no file of interest will follow if this test fails */ + if (!gtk_recent_info_has_application (info, EOM_RECENT_FILES_APP_NAME)) + break; + + count_recent++; + + action_name = g_strdup_printf ("recent-info-%d", count_recent); + display_name = g_strsplit (gtk_recent_info_get_display_name (info), "_", -1); + label_filename = g_strjoinv ("__", display_name); + label = g_strdup_printf ("%s_%d. %s", + (is_rtl ? "\xE2\x80\x8F" : ""), count_recent, label_filename); + g_free (label_filename); + g_strfreev (display_name); + + tip = gtk_recent_info_get_uri_display (info); + + /* This is a workaround for a bug (#351945) regarding + * gtk_recent_info_get_uri_display() and remote URIs. + * mate_vfs_format_uri_for_display is sufficient here + * since the password gets stripped when adding the + * file to the recently used list. */ + if (tip == NULL) + tip = g_uri_unescape_string (gtk_recent_info_get_uri (info), NULL); + + action = gtk_action_new (action_name, label, tip, NULL); + gtk_action_set_always_show_image (action, TRUE); + + g_object_set_data_full (G_OBJECT (action), "gtk-recent-info", + gtk_recent_info_ref (info), + (GDestroyNotify) gtk_recent_info_unref); + + g_object_set (G_OBJECT (action), "icon-name", "image-x-generic", NULL); + + g_signal_connect (action, "activate", + G_CALLBACK (eom_window_open_recent_cb), + window); + + gtk_action_group_add_action (priv->actions_recent, action); + + g_object_unref (action); + + gtk_ui_manager_add_ui (priv->ui_mgr, priv->recent_menu_id, + "/MainMenu/Image/RecentDocuments", + action_name, action_name, + GTK_UI_MANAGER_AUTO, FALSE); + + g_free (action_name); + g_free (label); + g_free (tip); + } + + g_list_foreach (items, (GFunc) gtk_recent_info_unref, NULL); + g_list_free (items); +} + +static void +eom_window_recent_manager_changed_cb (GtkRecentManager *manager, EomWindow *window) +{ + eom_window_update_recent_files_menu (window); +} + +static void +eom_window_drag_data_received (GtkWidget *widget, + GdkDragContext *context, + gint x, gint y, + GtkSelectionData *selection_data, + guint info, guint time) +{ + GSList *file_list; + EomWindow *window; + GdkAtom target; + + target = gtk_selection_data_get_target (selection_data); + + if (!gtk_targets_include_uri (&target, 1)) + return; + + if (context->suggested_action == GDK_ACTION_COPY) { + window = EOM_WINDOW (widget); + + file_list = eom_util_parse_uri_string_list_to_file_list ((const gchar *) gtk_selection_data_get_data (selection_data)); + + eom_window_open_file_list (window, file_list); + } +} + +static void +eom_window_set_drag_dest (EomWindow *window) +{ + gtk_drag_dest_set (GTK_WIDGET (window), + GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_DROP, + NULL, 0, + GDK_ACTION_COPY | GDK_ACTION_ASK); + gtk_drag_dest_add_uri_targets (GTK_WIDGET (window)); +} + +static void +eom_window_sidebar_visibility_changed (GtkWidget *widget, EomWindow *window) +{ + GtkAction *action; + gboolean visible; + + visible = gtk_widget_get_visible (window->priv->sidebar); + + mateconf_client_set_bool (window->priv->client, + EOM_CONF_UI_SIDEBAR, + visible, + NULL); + + action = gtk_action_group_get_action (window->priv->actions_window, + "ViewSidebar"); + + if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)) != visible) + gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), visible); + + /* Focus the image */ + if (!visible && window->priv->image != NULL) + gtk_widget_grab_focus (window->priv->view); +} + +static void +eom_window_sidebar_page_added (EomSidebar *sidebar, + GtkWidget *main_widget, + EomWindow *window) +{ + if (eom_sidebar_get_n_pages (sidebar) == 1) { + GtkAction *action; + gboolean show; + + action = gtk_action_group_get_action (window->priv->actions_window, + "ViewSidebar"); + + gtk_action_set_sensitive (action, TRUE); + + show = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)); + + if (show) + gtk_widget_show (GTK_WIDGET (sidebar)); + } +} +static void +eom_window_sidebar_page_removed (EomSidebar *sidebar, + GtkWidget *main_widget, + EomWindow *window) +{ + if (eom_sidebar_is_empty (sidebar)) { + GtkAction *action; + + gtk_widget_hide (GTK_WIDGET (sidebar)); + + action = gtk_action_group_get_action (window->priv->actions_window, + "ViewSidebar"); + + gtk_action_set_sensitive (action, FALSE); + } +} + +static void +eom_window_finish_saving (EomWindow *window) +{ + EomWindowPrivate *priv = window->priv; + + gtk_widget_set_sensitive (GTK_WIDGET (window), FALSE); + + do { + gtk_main_iteration (); + } while (priv->save_job != NULL); +} + +static void +eom_window_construct_ui (EomWindow *window) +{ + EomWindowPrivate *priv; + + GError *error = NULL; + + GtkWidget *menubar; + GtkWidget *thumb_popup; + GtkWidget *view_popup; + GtkWidget *hpaned; + GtkWidget *menuitem; + + MateConfEntry *entry; + + g_return_if_fail (EOM_IS_WINDOW (window)); + + priv = window->priv; + + priv->box = gtk_vbox_new (FALSE, 0); + gtk_container_add (GTK_CONTAINER (window), priv->box); + gtk_widget_show (priv->box); + + priv->ui_mgr = gtk_ui_manager_new (); + + priv->actions_window = gtk_action_group_new ("MenuActionsWindow"); + + gtk_action_group_set_translation_domain (priv->actions_window, + GETTEXT_PACKAGE); + + gtk_action_group_add_actions (priv->actions_window, + action_entries_window, + G_N_ELEMENTS (action_entries_window), + window); + + gtk_action_group_add_toggle_actions (priv->actions_window, + toggle_entries_window, + G_N_ELEMENTS (toggle_entries_window), + window); + + gtk_ui_manager_insert_action_group (priv->ui_mgr, priv->actions_window, 0); + + priv->actions_image = gtk_action_group_new ("MenuActionsImage"); + gtk_action_group_set_translation_domain (priv->actions_image, + GETTEXT_PACKAGE); + + gtk_action_group_add_actions (priv->actions_image, + action_entries_image, + G_N_ELEMENTS (action_entries_image), + window); + + gtk_action_group_add_toggle_actions (priv->actions_image, + toggle_entries_image, + G_N_ELEMENTS (toggle_entries_image), + window); + + gtk_ui_manager_insert_action_group (priv->ui_mgr, priv->actions_image, 0); + + priv->actions_collection = gtk_action_group_new ("MenuActionsCollection"); + gtk_action_group_set_translation_domain (priv->actions_collection, + GETTEXT_PACKAGE); + + gtk_action_group_add_actions (priv->actions_collection, + action_entries_collection, + G_N_ELEMENTS (action_entries_collection), + window); + + gtk_action_group_add_toggle_actions (priv->actions_collection, + toggle_entries_collection, + G_N_ELEMENTS (toggle_entries_collection), + window); + + set_action_properties (priv->actions_window, + priv->actions_image, + priv->actions_collection); + + gtk_ui_manager_insert_action_group (priv->ui_mgr, priv->actions_collection, 0); + + if (!gtk_ui_manager_add_ui_from_file (priv->ui_mgr, + EOM_DATA_DIR"/eom-ui.xml", + &error)) { + g_warning ("building menus failed: %s", error->message); + g_error_free (error); + } + + g_signal_connect (priv->ui_mgr, "connect_proxy", + G_CALLBACK (connect_proxy_cb), window); + g_signal_connect (priv->ui_mgr, "disconnect_proxy", + G_CALLBACK (disconnect_proxy_cb), window); + + menubar = gtk_ui_manager_get_widget (priv->ui_mgr, "/MainMenu"); + g_assert (GTK_IS_WIDGET (menubar)); + gtk_box_pack_start (GTK_BOX (priv->box), menubar, FALSE, FALSE, 0); + gtk_widget_show (menubar); + + menuitem = gtk_ui_manager_get_widget (priv->ui_mgr, + "/MainMenu/Edit/EditFlipHorizontal"); + gtk_image_menu_item_set_always_show_image ( + GTK_IMAGE_MENU_ITEM (menuitem), TRUE); + + menuitem = gtk_ui_manager_get_widget (priv->ui_mgr, + "/MainMenu/Edit/EditFlipVertical"); + gtk_image_menu_item_set_always_show_image ( + GTK_IMAGE_MENU_ITEM (menuitem), TRUE); + + menuitem = gtk_ui_manager_get_widget (priv->ui_mgr, + "/MainMenu/Edit/EditRotate90"); + gtk_image_menu_item_set_always_show_image ( + GTK_IMAGE_MENU_ITEM (menuitem), TRUE); + + menuitem = gtk_ui_manager_get_widget (priv->ui_mgr, + "/MainMenu/Edit/EditRotate270"); + gtk_image_menu_item_set_always_show_image ( + GTK_IMAGE_MENU_ITEM (menuitem), TRUE); + + priv->toolbar = GTK_WIDGET + (g_object_new (EGG_TYPE_EDITABLE_TOOLBAR, + "ui-manager", priv->ui_mgr, + "model", eom_application_get_toolbars_model (EOM_APP), + NULL)); + + egg_editable_toolbar_show (EGG_EDITABLE_TOOLBAR (priv->toolbar), + "Toolbar"); + + gtk_box_pack_start (GTK_BOX (priv->box), + priv->toolbar, + FALSE, + FALSE, + 0); + + gtk_widget_show (priv->toolbar); + + gtk_window_add_accel_group (GTK_WINDOW (window), + gtk_ui_manager_get_accel_group (priv->ui_mgr)); + + priv->actions_recent = gtk_action_group_new ("RecentFilesActions"); + gtk_action_group_set_translation_domain (priv->actions_recent, + GETTEXT_PACKAGE); + + g_signal_connect (gtk_recent_manager_get_default (), "changed", + G_CALLBACK (eom_window_recent_manager_changed_cb), + window); + + eom_window_update_recent_files_menu (window); + + gtk_ui_manager_insert_action_group (priv->ui_mgr, priv->actions_recent, 0); + + priv->cbox = gtk_vbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (priv->box), priv->cbox, TRUE, TRUE, 0); + gtk_widget_show (priv->cbox); + + priv->statusbar = eom_statusbar_new (); + gtk_box_pack_end (GTK_BOX (priv->box), + GTK_WIDGET (priv->statusbar), + FALSE, FALSE, 0); + gtk_widget_show (priv->statusbar); + + priv->image_info_message_cid = + gtk_statusbar_get_context_id (GTK_STATUSBAR (priv->statusbar), + "image_info_message"); + priv->tip_message_cid = + gtk_statusbar_get_context_id (GTK_STATUSBAR (priv->statusbar), + "tip_message"); + + priv->layout = gtk_vbox_new (FALSE, 2); + + hpaned = gtk_hpaned_new (); + + priv->sidebar = eom_sidebar_new (); + /* The sidebar shouldn't be shown automatically on show_all(), + but only when the user actually wants it. */ + gtk_widget_set_no_show_all (priv->sidebar, TRUE); + + gtk_widget_set_size_request (priv->sidebar, 210, -1); + + g_signal_connect_after (priv->sidebar, + "show", + G_CALLBACK (eom_window_sidebar_visibility_changed), + window); + + g_signal_connect_after (priv->sidebar, + "hide", + G_CALLBACK (eom_window_sidebar_visibility_changed), + window); + + g_signal_connect_after (priv->sidebar, + "page-added", + G_CALLBACK (eom_window_sidebar_page_added), + window); + + g_signal_connect_after (priv->sidebar, + "page-removed", + G_CALLBACK (eom_window_sidebar_page_removed), + window); + + priv->view = eom_scroll_view_new (); + gtk_widget_set_size_request (GTK_WIDGET (priv->view), 100, 100); + g_signal_connect (G_OBJECT (priv->view), + "zoom_changed", + G_CALLBACK (view_zoom_changed_cb), + window); + + view_popup = gtk_ui_manager_get_widget (priv->ui_mgr, "/ViewPopup"); + eom_scroll_view_set_popup (EOM_SCROLL_VIEW (priv->view), + GTK_MENU (view_popup)); + + gtk_paned_pack1 (GTK_PANED (hpaned), + priv->sidebar, + FALSE, + FALSE); + + gtk_paned_pack2 (GTK_PANED (hpaned), + priv->view, + TRUE, + FALSE); + + gtk_widget_show_all (hpaned); + + gtk_box_pack_start (GTK_BOX (priv->layout), hpaned, TRUE, TRUE, 0); + + priv->thumbview = eom_thumb_view_new (); + + /* giving shape to the view */ + gtk_icon_view_set_margin (GTK_ICON_VIEW (priv->thumbview), 4); + gtk_icon_view_set_row_spacing (GTK_ICON_VIEW (priv->thumbview), 0); + + g_signal_connect (G_OBJECT (priv->thumbview), "selection_changed", + G_CALLBACK (handle_image_selection_changed_cb), window); + + priv->nav = eom_thumb_nav_new (priv->thumbview, + EOM_THUMB_NAV_MODE_ONE_ROW, + mateconf_client_get_bool (priv->client, + EOM_CONF_UI_SCROLL_BUTTONS, + NULL)); + + thumb_popup = gtk_ui_manager_get_widget (priv->ui_mgr, "/ThumbnailPopup"); + eom_thumb_view_set_thumbnail_popup (EOM_THUMB_VIEW (priv->thumbview), + GTK_MENU (thumb_popup)); + + gtk_box_pack_start (GTK_BOX (priv->layout), priv->nav, FALSE, FALSE, 0); + + gtk_box_pack_end (GTK_BOX (priv->cbox), priv->layout, TRUE, TRUE, 0); + + + entry = mateconf_client_get_entry (priv->client, + EOM_CONF_VIEW_EXTRAPOLATE, + NULL, TRUE, NULL); + if (entry != NULL) { + eom_window_interp_in_type_changed_cb (priv->client, 0, entry, window); + mateconf_entry_unref (entry); + entry = NULL; + } + + entry = mateconf_client_get_entry (priv->client, + EOM_CONF_VIEW_INTERPOLATE, + NULL, TRUE, NULL); + if (entry != NULL) { + eom_window_interp_out_type_changed_cb (priv->client, 0, entry, window); + mateconf_entry_unref (entry); + entry = NULL; + } + + entry = mateconf_client_get_entry (priv->client, + EOM_CONF_VIEW_SCROLL_WHEEL_ZOOM, + NULL, TRUE, NULL); + if (entry != NULL) { + eom_window_scroll_wheel_zoom_changed_cb (priv->client, 0, entry, window); + mateconf_entry_unref (entry); + entry = NULL; + } + + entry = mateconf_client_get_entry (priv->client, + EOM_CONF_VIEW_ZOOM_MULTIPLIER, + NULL, TRUE, NULL); + if (entry != NULL) { + eom_window_zoom_multiplier_changed_cb (priv->client, 0, entry, window); + mateconf_entry_unref (entry); + entry = NULL; + } + + entry = mateconf_client_get_entry (priv->client, + EOM_CONF_VIEW_BACKGROUND_COLOR, + NULL, TRUE, NULL); + if (entry != NULL) { + eom_window_bg_color_changed_cb (priv->client, 0, entry, window); + mateconf_entry_unref (entry); + entry = NULL; + } + + entry = mateconf_client_get_entry (priv->client, + EOM_CONF_VIEW_USE_BG_COLOR, + NULL, TRUE, NULL); + if (entry != NULL) { + eom_window_use_bg_color_changed_cb (priv->client, 0, entry, window); + mateconf_entry_unref (entry); + entry = NULL; + } + + entry = mateconf_client_get_entry (priv->client, + EOM_CONF_VIEW_TRANSPARENCY, + NULL, TRUE, NULL); + if (entry != NULL) { + eom_window_transparency_changed_cb (priv->client, 0, entry, window); + mateconf_entry_unref (entry); + entry = NULL; + } + + entry = mateconf_client_get_entry (priv->client, + EOM_CONF_VIEW_TRANS_COLOR, + NULL, TRUE, NULL); + if (entry != NULL) { + eom_window_trans_color_changed_cb (priv->client, 0, entry, window); + mateconf_entry_unref (entry); + entry = NULL; + } + + entry = mateconf_client_get_entry (priv->client, + EOM_CONF_UI_IMAGE_COLLECTION_POSITION, + NULL, TRUE, NULL); + if (entry != NULL) { + eom_window_collection_mode_changed_cb (priv->client, 0, entry, window); + mateconf_entry_unref (entry); + entry = NULL; + } + + entry = mateconf_client_get_entry (priv->client, + EOM_CONF_DESKTOP_CAN_SAVE, + NULL, TRUE, NULL); + if (entry != NULL) { + eom_window_can_save_changed_cb (priv->client, 0, entry, window); + mateconf_entry_unref (entry); + entry = NULL; + } + + if ((priv->flags & EOM_STARTUP_FULLSCREEN) || + (priv->flags & EOM_STARTUP_SLIDE_SHOW)) { + eom_window_run_fullscreen (window, (priv->flags & EOM_STARTUP_SLIDE_SHOW)); + } else { + priv->mode = EOM_WINDOW_MODE_NORMAL; + update_ui_visibility (window); + } + + eom_window_set_drag_dest (window); +} + +static void +eom_window_init (EomWindow *window) +{ + GdkGeometry hints; + GdkScreen *screen; + EomWindowPrivate *priv; + + eom_debug (DEBUG_WINDOW); + + hints.min_width = EOM_WINDOW_MIN_WIDTH; + hints.min_height = EOM_WINDOW_MIN_HEIGHT; + + screen = gtk_widget_get_screen (GTK_WIDGET (window)); + + priv = window->priv = EOM_WINDOW_GET_PRIVATE (window); + + priv->client = mateconf_client_get_default (); + + mateconf_client_add_dir (window->priv->client, EOM_CONF_DIR, + MATECONF_CLIENT_PRELOAD_RECURSIVE, NULL); + + priv->client_notifications[EOM_WINDOW_NOTIFY_EXTRAPOLATE] = + mateconf_client_notify_add (window->priv->client, + EOM_CONF_VIEW_EXTRAPOLATE, + eom_window_interp_in_type_changed_cb, + window, NULL, NULL); + + priv->client_notifications[EOM_WINDOW_NOTIFY_INTERPOLATE] = + mateconf_client_notify_add (window->priv->client, + EOM_CONF_VIEW_INTERPOLATE, + eom_window_interp_out_type_changed_cb, + window, NULL, NULL); + + priv->client_notifications[EOM_WINDOW_NOTIFY_SCROLLWHEEL_ZOOM] = + mateconf_client_notify_add (window->priv->client, + EOM_CONF_VIEW_SCROLL_WHEEL_ZOOM, + eom_window_scroll_wheel_zoom_changed_cb, + window, NULL, NULL); + + priv->client_notifications[EOM_WINDOW_NOTIFY_ZOOM_MULTIPLIER] = + mateconf_client_notify_add (window->priv->client, + EOM_CONF_VIEW_ZOOM_MULTIPLIER, + eom_window_zoom_multiplier_changed_cb, + window, NULL, NULL); + + priv->client_notifications[EOM_WINDOW_NOTIFY_BACKGROUND_COLOR] = + mateconf_client_notify_add (window->priv->client, + EOM_CONF_VIEW_BACKGROUND_COLOR, + eom_window_bg_color_changed_cb, + window, NULL, NULL); + + priv->client_notifications[EOM_WINDOW_NOTIFY_USE_BG_COLOR] = + mateconf_client_notify_add (window->priv->client, + EOM_CONF_VIEW_USE_BG_COLOR, + eom_window_use_bg_color_changed_cb, + window, NULL, NULL); + + priv->client_notifications[EOM_WINDOW_NOTIFY_TRANSPARENCY] = + mateconf_client_notify_add (window->priv->client, + EOM_CONF_VIEW_TRANSPARENCY, + eom_window_transparency_changed_cb, + window, NULL, NULL); + + priv->client_notifications[EOM_WINDOW_NOTIFY_TRANS_COLOR] = + mateconf_client_notify_add (window->priv->client, + EOM_CONF_VIEW_TRANS_COLOR, + eom_window_trans_color_changed_cb, + window, NULL, NULL); + + priv->client_notifications[EOM_WINDOW_NOTIFY_SCROLL_BUTTONS] = + mateconf_client_notify_add (window->priv->client, + EOM_CONF_UI_SCROLL_BUTTONS, + eom_window_scroll_buttons_changed_cb, + window, NULL, NULL); + + priv->client_notifications[EOM_WINDOW_NOTIFY_COLLECTION_POS] = + mateconf_client_notify_add (window->priv->client, + EOM_CONF_UI_IMAGE_COLLECTION_POSITION, + eom_window_collection_mode_changed_cb, + window, NULL, NULL); + + priv->client_notifications[EOM_WINDOW_NOTIFY_COLLECTION_RESIZABLE] = + mateconf_client_notify_add (window->priv->client, + EOM_CONF_UI_IMAGE_COLLECTION_RESIZABLE, + eom_window_collection_mode_changed_cb, + window, NULL, NULL); + + priv->client_notifications[EOM_WINDOW_NOTIFY_CAN_SAVE] = + mateconf_client_notify_add (window->priv->client, + EOM_CONF_DESKTOP_CAN_SAVE, + eom_window_can_save_changed_cb, + window, NULL, NULL); + + priv->client_notifications[EOM_WINDOW_NOTIFY_PROPSDIALOG_NETBOOK_MODE] = + mateconf_client_notify_add (window->priv->client, + EOM_CONF_UI_PROPSDIALOG_NETBOOK_MODE, + eom_window_pd_nbmode_changed_cb, + window, NULL, NULL); + + window->priv->store = NULL; + window->priv->image = NULL; + + window->priv->fullscreen_popup = NULL; + window->priv->fullscreen_timeout_source = NULL; + window->priv->slideshow_loop = FALSE; + window->priv->slideshow_switch_timeout = 0; + window->priv->slideshow_switch_source = NULL; + + gtk_window_set_geometry_hints (GTK_WINDOW (window), + GTK_WIDGET (window), + &hints, + GDK_HINT_MIN_SIZE); + + gtk_window_set_default_size (GTK_WINDOW (window), + EOM_WINDOW_DEFAULT_WIDTH, + EOM_WINDOW_DEFAULT_HEIGHT); + + gtk_window_set_position (GTK_WINDOW (window), GTK_WIN_POS_CENTER); + + window->priv->mode = EOM_WINDOW_MODE_UNKNOWN; + window->priv->status = EOM_WINDOW_STATUS_UNKNOWN; + +#ifdef HAVE_LCMS + window->priv->display_profile = + eom_window_get_display_profile (screen); +#endif + + window->priv->recent_menu_id = 0; + + window->priv->collection_position = 0; + window->priv->collection_resizable = FALSE; + + window->priv->save_disabled = FALSE; +} + +static void +eom_window_dispose (GObject *object) +{ + EomWindow *window; + EomWindowPrivate *priv; + + g_return_if_fail (object != NULL); + g_return_if_fail (EOM_IS_WINDOW (object)); + + eom_debug (DEBUG_WINDOW); + + window = EOM_WINDOW (object); + priv = window->priv; + + eom_plugin_engine_garbage_collect (); + + if (priv->store != NULL) { + g_signal_handlers_disconnect_by_func (priv->store, + eom_window_list_store_image_added, + window); + g_signal_handlers_disconnect_by_func (priv->store, + eom_window_list_store_image_removed, + window); + g_object_unref (priv->store); + priv->store = NULL; + } + + if (priv->image != NULL) { + g_signal_handlers_disconnect_by_func (priv->image, + image_thumb_changed_cb, + window); + g_signal_handlers_disconnect_by_func (priv->image, + image_file_changed_cb, + window); + g_object_unref (priv->image); + priv->image = NULL; + } + + if (priv->actions_window != NULL) { + g_object_unref (priv->actions_window); + priv->actions_window = NULL; + } + + if (priv->actions_image != NULL) { + g_object_unref (priv->actions_image); + priv->actions_image = NULL; + } + + if (priv->actions_collection != NULL) { + g_object_unref (priv->actions_collection); + priv->actions_collection = NULL; + } + + if (priv->actions_recent != NULL) { + g_object_unref (priv->actions_recent); + priv->actions_recent = NULL; + } + + if (priv->actions_open_with != NULL) { + g_object_unref (priv->actions_open_with); + priv->actions_open_with = NULL; + } + + fullscreen_clear_timeout (window); + + if (window->priv->fullscreen_popup != NULL) { + gtk_widget_destroy (priv->fullscreen_popup); + priv->fullscreen_popup = NULL; + } + + slideshow_clear_timeout (window); + + g_signal_handlers_disconnect_by_func (gtk_recent_manager_get_default (), + G_CALLBACK (eom_window_recent_manager_changed_cb), + window); + + priv->recent_menu_id = 0; + + eom_window_clear_load_job (window); + + eom_window_clear_transform_job (window); + + if (priv->client) { + int i; + + for (i = 0; i < EOM_WINDOW_NOTIFY_LENGTH; ++i) { + mateconf_client_notify_remove (priv->client, + priv->client_notifications[i]); + } + mateconf_client_remove_dir (priv->client, EOM_CONF_DIR, NULL); + g_object_unref (priv->client); + priv->client = NULL; + } + + if (priv->file_list != NULL) { + g_slist_foreach (priv->file_list, (GFunc) g_object_unref, NULL); + g_slist_free (priv->file_list); + priv->file_list = NULL; + } + +#ifdef HAVE_LCMS + if (priv->display_profile != NULL) { + cmsCloseProfile (priv->display_profile); + priv->display_profile = NULL; + } +#endif + + if (priv->last_save_as_folder != NULL) { + g_object_unref (priv->last_save_as_folder); + priv->last_save_as_folder = NULL; + } + + eom_plugin_engine_garbage_collect (); + + G_OBJECT_CLASS (eom_window_parent_class)->dispose (object); +} + +static void +eom_window_finalize (GObject *object) +{ + GList *windows = eom_application_get_windows (EOM_APP); + + g_return_if_fail (EOM_IS_WINDOW (object)); + + eom_debug (DEBUG_WINDOW); + + if (windows == NULL) { + eom_application_shutdown (EOM_APP); + } else { + g_list_free (windows); + } + + G_OBJECT_CLASS (eom_window_parent_class)->finalize (object); +} + +static gint +eom_window_delete (GtkWidget *widget, GdkEventAny *event) +{ + EomWindow *window; + EomWindowPrivate *priv; + + g_return_val_if_fail (EOM_IS_WINDOW (widget), FALSE); + + window = EOM_WINDOW (widget); + priv = window->priv; + + if (priv->save_job != NULL) { + eom_window_finish_saving (window); + } + + if (eom_window_unsaved_images_confirm (window)) { + return TRUE; + } + + gtk_widget_destroy (widget); + + return TRUE; +} + +static gint +eom_window_key_press (GtkWidget *widget, GdkEventKey *event) +{ + GtkContainer *tbcontainer = GTK_CONTAINER ((EOM_WINDOW (widget)->priv->toolbar)); + gint result = FALSE; + gboolean handle_selection = FALSE; + + switch (event->keyval) { + case GDK_space: + if (event->state & GDK_CONTROL_MASK) { + handle_selection = TRUE; + break; + } + case GDK_Return: + if (gtk_container_get_focus_child (tbcontainer) == NULL) { + /* Image properties dialog case */ + if (event->state & GDK_MOD1_MASK) { + result = FALSE; + break; + } + + if (event->state & GDK_SHIFT_MASK) { + eom_window_cmd_go_prev (NULL, EOM_WINDOW (widget)); + } else { + eom_window_cmd_go_next (NULL, EOM_WINDOW (widget)); + } + result = TRUE; + } + break; + case GDK_p: + case GDK_P: + if (EOM_WINDOW (widget)->priv->mode == EOM_WINDOW_MODE_FULLSCREEN || EOM_WINDOW (widget)->priv->mode == EOM_WINDOW_MODE_SLIDESHOW) { + gboolean slideshow; + + slideshow = EOM_WINDOW (widget)->priv->mode == EOM_WINDOW_MODE_SLIDESHOW; + eom_window_run_fullscreen (EOM_WINDOW (widget), !slideshow); + } + break; + case GDK_Q: + case GDK_q: + case GDK_Escape: + if (EOM_WINDOW (widget)->priv->mode == EOM_WINDOW_MODE_FULLSCREEN) { + eom_window_stop_fullscreen (EOM_WINDOW (widget), FALSE); + } else if (EOM_WINDOW (widget)->priv->mode == EOM_WINDOW_MODE_SLIDESHOW) { + eom_window_stop_fullscreen (EOM_WINDOW (widget), TRUE); + } else { + eom_window_cmd_close_window (NULL, EOM_WINDOW (widget)); + return TRUE; + } + break; + case GDK_Left: + if (event->state & GDK_MOD1_MASK) { + /* Alt+Left moves to previous image */ + if (is_rtl) { /* move to next in RTL mode */ + eom_window_cmd_go_next (NULL, EOM_WINDOW (widget)); + } else { + eom_window_cmd_go_prev (NULL, EOM_WINDOW (widget)); + } + result = TRUE; + break; + } /* else fall-trough is intended */ + case GDK_Up: + if (eom_scroll_view_scrollbars_visible (EOM_SCROLL_VIEW (EOM_WINDOW (widget)->priv->view))) { + /* break to let scrollview handle the key */ + break; + } + if (gtk_container_get_focus_child (tbcontainer) != NULL) + break; + if (!gtk_widget_get_visible (EOM_WINDOW (widget)->priv->nav)) { + if (is_rtl && event->keyval == GDK_Left) { + /* handle RTL fall-through, + * need to behave like GDK_Down then */ + eom_window_cmd_go_next (NULL, + EOM_WINDOW (widget)); + } else { + eom_window_cmd_go_prev (NULL, + EOM_WINDOW (widget)); + } + result = TRUE; + break; + } + case GDK_Right: + if (event->state & GDK_MOD1_MASK) { + /* Alt+Right moves to next image */ + if (is_rtl) { /* move to previous in RTL mode */ + eom_window_cmd_go_prev (NULL, EOM_WINDOW (widget)); + } else { + eom_window_cmd_go_next (NULL, EOM_WINDOW (widget)); + } + result = TRUE; + break; + } /* else fall-trough is intended */ + case GDK_Down: + if (eom_scroll_view_scrollbars_visible (EOM_SCROLL_VIEW (EOM_WINDOW (widget)->priv->view))) { + /* break to let scrollview handle the key */ + break; + } + if (gtk_container_get_focus_child (tbcontainer) != NULL) + break; + if (!gtk_widget_get_visible (EOM_WINDOW (widget)->priv->nav)) { + if (is_rtl && event->keyval == GDK_Right) { + /* handle RTL fall-through, + * need to behave like GDK_Up then */ + eom_window_cmd_go_prev (NULL, + EOM_WINDOW (widget)); + } else { + eom_window_cmd_go_next (NULL, + EOM_WINDOW (widget)); + } + result = TRUE; + break; + } + case GDK_Page_Up: + if (!eom_scroll_view_scrollbars_visible (EOM_SCROLL_VIEW (EOM_WINDOW (widget)->priv->view))) { + if (!gtk_widget_get_visible (EOM_WINDOW (widget)->priv->nav)) { + /* If the iconview is not visible skip to the + * previous image manually as it won't handle + * the keypress then. */ + eom_window_cmd_go_prev (NULL, + EOM_WINDOW (widget)); + result = TRUE; + } else + handle_selection = TRUE; + } + break; + case GDK_Page_Down: + if (!eom_scroll_view_scrollbars_visible (EOM_SCROLL_VIEW (EOM_WINDOW (widget)->priv->view))) { + if (!gtk_widget_get_visible (EOM_WINDOW (widget)->priv->nav)) { + /* If the iconview is not visible skip to the + * next image manually as it won't handle + * the keypress then. */ + eom_window_cmd_go_next (NULL, + EOM_WINDOW (widget)); + result = TRUE; + } else + handle_selection = TRUE; + } + break; + } + + /* Update slideshow timeout */ + if (result && (EOM_WINDOW (widget)->priv->mode == EOM_WINDOW_MODE_SLIDESHOW)) { + slideshow_set_timeout (EOM_WINDOW (widget)); + } + + if (handle_selection == TRUE && result == FALSE) { + gtk_widget_grab_focus (GTK_WIDGET (EOM_WINDOW (widget)->priv->thumbview)); + + result = gtk_widget_event (GTK_WIDGET (EOM_WINDOW (widget)->priv->thumbview), + (GdkEvent *) event); + } + + /* If the focus is not in the toolbar and we still haven't handled the + event, give the scrollview a chance to do it. */ + if (!gtk_container_get_focus_child (tbcontainer) && result == FALSE && +#if GTK_CHECK_VERSION (2, 20, 0) + gtk_widget_get_realized (GTK_WIDGET (EOM_WINDOW (widget)->priv->view))) { +#else + GTK_WIDGET_REALIZED(GTK_WIDGET (EOM_WINDOW (widget)->priv->view))) { +#endif + result = gtk_widget_event (GTK_WIDGET (EOM_WINDOW (widget)->priv->view), + (GdkEvent *) event); + } + + if (result == FALSE && GTK_WIDGET_CLASS (eom_window_parent_class)->key_press_event) { + result = (* GTK_WIDGET_CLASS (eom_window_parent_class)->key_press_event) (widget, event); + } + + return result; +} + +static gint +eom_window_button_press (GtkWidget *widget, GdkEventButton *event) +{ + EomWindow *window = EOM_WINDOW (widget); + gint result = FALSE; + + if (event->type == GDK_BUTTON_PRESS) { + switch (event->button) { + case 6: + eom_thumb_view_select_single (EOM_THUMB_VIEW (window->priv->thumbview), + EOM_THUMB_VIEW_SELECT_LEFT); + result = TRUE; + break; + case 7: + eom_thumb_view_select_single (EOM_THUMB_VIEW (window->priv->thumbview), + EOM_THUMB_VIEW_SELECT_RIGHT); + result = TRUE; + break; + } + } + + if (result == FALSE && GTK_WIDGET_CLASS (eom_window_parent_class)->button_press_event) { + result = (* GTK_WIDGET_CLASS (eom_window_parent_class)->button_press_event) (widget, event); + } + + return result; +} + +static gboolean +eom_window_configure_event (GtkWidget *widget, GdkEventConfigure *event) +{ + EomWindow *window; + + g_return_val_if_fail (EOM_IS_WINDOW (widget), TRUE); + + window = EOM_WINDOW (widget); + + GTK_WIDGET_CLASS (eom_window_parent_class)->configure_event (widget, event); + + return FALSE; +} + +static gboolean +eom_window_window_state_event (GtkWidget *widget, + GdkEventWindowState *event) +{ + EomWindow *window; + + g_return_val_if_fail (EOM_IS_WINDOW (widget), TRUE); + + window = EOM_WINDOW (widget); + + if (event->changed_mask & + (GDK_WINDOW_STATE_MAXIMIZED | GDK_WINDOW_STATE_FULLSCREEN)) { + gboolean show; + + show = !(event->new_window_state & + (GDK_WINDOW_STATE_MAXIMIZED | GDK_WINDOW_STATE_FULLSCREEN)); + + eom_statusbar_set_has_resize_grip (EOM_STATUSBAR (window->priv->statusbar), + show); + } + + return FALSE; +} + +static void +eom_window_unrealize (GtkWidget *widget) +{ + EomWindow *window; + + g_return_if_fail (EOM_IS_WINDOW (widget)); + + window = EOM_WINDOW (widget); + + GTK_WIDGET_CLASS (eom_window_parent_class)->unrealize (widget); +} + + +static gboolean +eom_window_focus_out_event (GtkWidget *widget, GdkEventFocus *event) +{ + EomWindow *window = EOM_WINDOW (widget); + EomWindowPrivate *priv = window->priv; + gboolean fullscreen; + + eom_debug (DEBUG_WINDOW); + + fullscreen = priv->mode == EOM_WINDOW_MODE_FULLSCREEN || + priv->mode == EOM_WINDOW_MODE_SLIDESHOW; + + if (fullscreen) { + gtk_widget_hide_all (priv->fullscreen_popup); + } + + return GTK_WIDGET_CLASS (eom_window_parent_class)->focus_out_event (widget, event); +} + +static void +eom_window_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + EomWindow *window; + EomWindowPrivate *priv; + + g_return_if_fail (EOM_IS_WINDOW (object)); + + window = EOM_WINDOW (object); + priv = window->priv; + + switch (property_id) { + case PROP_STARTUP_FLAGS: + priv->flags = g_value_get_flags (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +eom_window_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + EomWindow *window; + EomWindowPrivate *priv; + + g_return_if_fail (EOM_IS_WINDOW (object)); + + window = EOM_WINDOW (object); + priv = window->priv; + + switch (property_id) { + case PROP_STARTUP_FLAGS: + g_value_set_flags (value, priv->flags); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static GObject * +eom_window_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_params) +{ + GObject *object; + + object = G_OBJECT_CLASS (eom_window_parent_class)->constructor + (type, n_construct_properties, construct_params); + + eom_window_construct_ui (EOM_WINDOW (object)); + + eom_plugin_engine_update_plugins_ui (EOM_WINDOW (object), TRUE); + + return object; +} + +static void +eom_window_class_init (EomWindowClass *class) +{ + GObjectClass *g_object_class = (GObjectClass *) class; + GtkWidgetClass *widget_class = (GtkWidgetClass *) class; + + g_object_class->constructor = eom_window_constructor; + g_object_class->dispose = eom_window_dispose; + g_object_class->finalize = eom_window_finalize; + g_object_class->set_property = eom_window_set_property; + g_object_class->get_property = eom_window_get_property; + + widget_class->delete_event = eom_window_delete; + widget_class->key_press_event = eom_window_key_press; + widget_class->button_press_event = eom_window_button_press; + widget_class->drag_data_received = eom_window_drag_data_received; + widget_class->configure_event = eom_window_configure_event; + widget_class->window_state_event = eom_window_window_state_event; + widget_class->unrealize = eom_window_unrealize; + widget_class->focus_out_event = eom_window_focus_out_event; + +/** + * EomWindow:startup-flags: + * + * A bitwise OR of #EomStartupFlags elements, indicating how the window + * should behave upon creation. + */ + g_object_class_install_property (g_object_class, + PROP_STARTUP_FLAGS, + g_param_spec_flags ("startup-flags", + NULL, + NULL, + EOM_TYPE_STARTUP_FLAGS, + 0, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + +/** + * EomWindow::prepared: + * @window: the object which received the signal. + * + * The #EomWindow::prepared signal is emitted when the @window is ready + * to be shown. + */ + signals [SIGNAL_PREPARED] = + g_signal_new ("prepared", + EOM_TYPE_WINDOW, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EomWindowClass, prepared), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + g_type_class_add_private (g_object_class, sizeof (EomWindowPrivate)); +} + +/** + * eom_window_new: + * @flags: the initialization parameters for the new window. + * + * + * Creates a new and empty #EomWindow. Use @flags to indicate + * if the window should be initialized fullscreen, in slideshow mode, + * and/or without the thumbnails collection visible. See #EomStartupFlags. + * + * Returns: a newly created #EomWindow. + **/ +GtkWidget* +eom_window_new (EomStartupFlags flags) +{ + EomWindow *window; + + eom_debug (DEBUG_WINDOW); + + window = EOM_WINDOW (g_object_new (EOM_TYPE_WINDOW, + "type", GTK_WINDOW_TOPLEVEL, + "startup-flags", flags, + NULL)); + + return GTK_WIDGET (window); +} + +static void +eom_window_list_store_image_added (GtkTreeModel *tree_model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer user_data) +{ + EomWindow *window = EOM_WINDOW (user_data); + + update_image_pos (window); + update_action_groups_state (window); +} + +static void +eom_window_list_store_image_removed (GtkTreeModel *tree_model, + GtkTreePath *path, + gpointer user_data) +{ + EomWindow *window = EOM_WINDOW (user_data); + + update_image_pos (window); + update_action_groups_state (window); +} + +static void +eom_job_model_cb (EomJobModel *job, gpointer data) +{ + EomWindow *window; + EomWindowPrivate *priv; + gint n_images; + + eom_debug (DEBUG_WINDOW); + +#ifdef HAVE_EXIF + int i; + EomImage *image; +#endif + + g_return_if_fail (EOM_IS_WINDOW (data)); + + window = EOM_WINDOW (data); + priv = window->priv; + + if (priv->store != NULL) { + g_object_unref (priv->store); + priv->store = NULL; + } + + priv->store = g_object_ref (job->store); + + n_images = eom_list_store_length (EOM_LIST_STORE (priv->store)); + +#ifdef HAVE_EXIF + if (mateconf_client_get_bool (priv->client, EOM_CONF_VIEW_AUTOROTATE, NULL)) { + for (i = 0; i < n_images; i++) { + image = eom_list_store_get_image_by_pos (priv->store, i); + eom_image_autorotate (image); + g_object_unref (image); + } + } +#endif + + eom_thumb_view_set_model (EOM_THUMB_VIEW (priv->thumbview), priv->store); + + g_signal_connect (G_OBJECT (priv->store), + "row-inserted", + G_CALLBACK (eom_window_list_store_image_added), + window); + + g_signal_connect (G_OBJECT (priv->store), + "row-deleted", + G_CALLBACK (eom_window_list_store_image_removed), + window); + + if (n_images == 0) { + gint n_files; + + priv->status = EOM_WINDOW_STATUS_NORMAL; + update_action_groups_state (window); + + n_files = g_slist_length (priv->file_list); + + if (n_files > 0) { + GtkWidget *message_area; + GFile *file = NULL; + + if (n_files == 1) { + file = (GFile *) priv->file_list->data; + } + + message_area = eom_no_images_error_message_area_new (file); + + eom_window_set_message_area (window, message_area); + + gtk_widget_show (message_area); + } + + g_signal_emit (window, signals[SIGNAL_PREPARED], 0); + } +} + +/** + * eom_window_open_file_list: + * @window: An #EomWindow. + * @file_list: A %NULL-terminated list of #GFile's. + * + * Opens a list of files, adding them to the collection in @window. + * Files will be checked to be readable and later filtered according + * with eom_list_store_add_files(). + **/ +void +eom_window_open_file_list (EomWindow *window, GSList *file_list) +{ + EomJob *job; + + eom_debug (DEBUG_WINDOW); + + window->priv->status = EOM_WINDOW_STATUS_INIT; + + g_slist_foreach (file_list, (GFunc) g_object_ref, NULL); + window->priv->file_list = file_list; + + job = eom_job_model_new (file_list); + + g_signal_connect (job, + "finished", + G_CALLBACK (eom_job_model_cb), + window); + + eom_job_queue_add_job (job); + g_object_unref (job); +} + +/** + * eom_window_get_ui_manager: + * @window: An #EomWindow. + * + * Gets the #GtkUIManager that describes the UI of @window. + * + * Returns: A #GtkUIManager. + **/ +GtkUIManager * +eom_window_get_ui_manager (EomWindow *window) +{ + g_return_val_if_fail (EOM_IS_WINDOW (window), NULL); + + return window->priv->ui_mgr; +} + +/** + * eom_window_get_mode: + * @window: An #EomWindow. + * + * Gets the mode of @window. See #EomWindowMode for details. + * + * Returns: An #EomWindowMode. + **/ +EomWindowMode +eom_window_get_mode (EomWindow *window) +{ + g_return_val_if_fail (EOM_IS_WINDOW (window), EOM_WINDOW_MODE_UNKNOWN); + + return window->priv->mode; +} + +/** + * eom_window_set_mode: + * @window: an #EomWindow. + * @mode: an #EomWindowMode value. + * + * Changes the mode of @window to normal, fullscreen, or slideshow. + * See #EomWindowMode for details. + **/ +void +eom_window_set_mode (EomWindow *window, EomWindowMode mode) +{ + g_return_if_fail (EOM_IS_WINDOW (window)); + + if (window->priv->mode == mode) + return; + + switch (mode) { + case EOM_WINDOW_MODE_NORMAL: + eom_window_stop_fullscreen (window, + window->priv->mode == EOM_WINDOW_MODE_SLIDESHOW); + break; + case EOM_WINDOW_MODE_FULLSCREEN: + eom_window_run_fullscreen (window, FALSE); + break; + case EOM_WINDOW_MODE_SLIDESHOW: + eom_window_run_fullscreen (window, TRUE); + break; + case EOM_WINDOW_MODE_UNKNOWN: + break; + } +} + +/** + * eom_window_get_store: + * @window: An #EomWindow. + * + * Gets the #EomListStore that contains the images in the collection + * of @window. + * + * Returns: an #EomListStore. + **/ +EomListStore * +eom_window_get_store (EomWindow *window) +{ + g_return_val_if_fail (EOM_IS_WINDOW (window), NULL); + + return EOM_LIST_STORE (window->priv->store); +} + +/** + * eom_window_get_view: + * @window: An #EomWindow. + * + * Gets the #EomScrollView in the window. + * + * Returns: the #EomScrollView. + **/ +GtkWidget * +eom_window_get_view (EomWindow *window) +{ + g_return_val_if_fail (EOM_IS_WINDOW (window), NULL); + + return window->priv->view; +} + +/** + * eom_window_get_sidebar: + * @window: An #EomWindow. + * + * Gets the sidebar widget of @window. + * + * Returns: the #EomSidebar. + **/ +GtkWidget * +eom_window_get_sidebar (EomWindow *window) +{ + g_return_val_if_fail (EOM_IS_WINDOW (window), NULL); + + return window->priv->sidebar; +} + +/** + * eom_window_get_thumb_view: + * @window: an #EomWindow. + * + * Gets the thumbnails view in @window. + * + * Returns: an #EomThumbView. + **/ +GtkWidget * +eom_window_get_thumb_view (EomWindow *window) +{ + g_return_val_if_fail (EOM_IS_WINDOW (window), NULL); + + return window->priv->thumbview; +} + +/** + * eom_window_get_thumb_nav: + * @window: an #EomWindow. + * + * Gets the thumbnails navigation pane in @window. + * + * Returns: an #EomThumbNav. + **/ +GtkWidget * +eom_window_get_thumb_nav (EomWindow *window) +{ + g_return_val_if_fail (EOM_IS_WINDOW (window), NULL); + + return window->priv->nav; +} + +/** + * eom_window_get_statusbar: + * @window: an #EomWindow. + * + * Gets the statusbar in @window. + * + * Returns: a #EomStatusBar. + **/ +GtkWidget * +eom_window_get_statusbar (EomWindow *window) +{ + g_return_val_if_fail (EOM_IS_WINDOW (window), NULL); + + return window->priv->statusbar; +} + +/** + * eom_window_get_image: + * @window: an #EomWindow. + * + * Gets the image currently displayed in @window or %NULL if + * no image is being displayed. + * + * Returns: an #EomImage. + **/ +EomImage * +eom_window_get_image (EomWindow *window) +{ + g_return_val_if_fail (EOM_IS_WINDOW (window), NULL); + + return window->priv->image; +} + +/** + * eom_window_is_empty: + * @window: an #EomWindow. + * + * Tells whether @window is currently empty or not. + * + * Returns: %TRUE if @window has no images, %FALSE otherwise. + **/ +gboolean +eom_window_is_empty (EomWindow *window) +{ + EomWindowPrivate *priv; + gboolean empty = TRUE; + + eom_debug (DEBUG_WINDOW); + + g_return_val_if_fail (EOM_IS_WINDOW (window), FALSE); + + priv = window->priv; + + if (priv->store != NULL) { + empty = (eom_list_store_length (EOM_LIST_STORE (priv->store)) == 0); + } + + return empty; +} + +void +eom_window_reload_image (EomWindow *window) +{ + GtkWidget *view; + + g_return_if_fail (EOM_IS_WINDOW (window)); + + if (window->priv->image == NULL) + return; + + g_object_unref (window->priv->image); + window->priv->image = NULL; + + view = eom_window_get_view (window); + eom_scroll_view_set_image (EOM_SCROLL_VIEW (view), NULL); + + eom_thumb_view_select_single (EOM_THUMB_VIEW (window->priv->thumbview), + EOM_THUMB_VIEW_SELECT_CURRENT); +} diff --git a/src/eom-window.h b/src/eom-window.h new file mode 100644 index 0000000..5e507c6 --- /dev/null +++ b/src/eom-window.h @@ -0,0 +1,122 @@ +/* Eye of Mate - Main Window + * + * Copyright (C) 2000-2008 The Free Software Foundation + * + * Author: Lucas Rocha <[email protected]> + * + * Based on code by: + * - Federico Mena-Quintero <[email protected]> + * - Jens Finke <[email protected]> + * Based on evince code (shell/ev-window.c) by: + * - Martin Kretzschmar <[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 __EOM_WINDOW_H__ +#define __EOM_WINDOW_H__ + +#include "eom-list-store.h" +#include "eom-image.h" + +#include <glib.h> +#include <glib-object.h> +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +typedef struct _EomWindow EomWindow; +typedef struct _EomWindowClass EomWindowClass; +typedef struct _EomWindowPrivate EomWindowPrivate; + +#define EOM_TYPE_WINDOW (eom_window_get_type ()) +#define EOM_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EOM_TYPE_WINDOW, EomWindow)) +#define EOM_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EOM_TYPE_WINDOW, EomWindowClass)) +#define EOM_IS_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EOM_TYPE_WINDOW)) +#define EOM_IS_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EOM_TYPE_WINDOW)) +#define EOM_WINDOW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EOM_TYPE_WINDOW, EomWindowClass)) + +#define EOM_WINDOW_ERROR (eom_window_error_quark ()) + +typedef enum { + EOM_WINDOW_MODE_UNKNOWN, + EOM_WINDOW_MODE_NORMAL, + EOM_WINDOW_MODE_FULLSCREEN, + EOM_WINDOW_MODE_SLIDESHOW +} EomWindowMode; + +//TODO +typedef enum { + EOM_WINDOW_ERROR_CONTROL_NOT_FOUND, + EOM_WINDOW_ERROR_UI_NOT_FOUND, + EOM_WINDOW_ERROR_NO_PERSIST_FILE_INTERFACE, + EOM_WINDOW_ERROR_IO, + EOM_WINDOW_ERROR_TRASH_NOT_FOUND, + EOM_WINDOW_ERROR_GENERIC, + EOM_WINDOW_ERROR_UNKNOWN +} EomWindowError; + +typedef enum { + EOM_STARTUP_FULLSCREEN = 1 << 0, + EOM_STARTUP_SLIDE_SHOW = 1 << 1, + EOM_STARTUP_DISABLE_COLLECTION = 1 << 2 +} EomStartupFlags; + +struct _EomWindow { + GtkWindow win; + + EomWindowPrivate *priv; +}; + +struct _EomWindowClass { + GtkWindowClass parent_class; + + void (* prepared) (EomWindow *window); +}; + +GType eom_window_get_type (void) G_GNUC_CONST; + +GtkWidget *eom_window_new (EomStartupFlags flags); + +EomWindowMode eom_window_get_mode (EomWindow *window); + +void eom_window_set_mode (EomWindow *window, + EomWindowMode mode); + +GtkUIManager *eom_window_get_ui_manager (EomWindow *window); + +EomListStore *eom_window_get_store (EomWindow *window); + +GtkWidget *eom_window_get_view (EomWindow *window); + +GtkWidget *eom_window_get_sidebar (EomWindow *window); + +GtkWidget *eom_window_get_thumb_view (EomWindow *window); + +GtkWidget *eom_window_get_thumb_nav (EomWindow *window); + +GtkWidget *eom_window_get_statusbar (EomWindow *window); + +EomImage *eom_window_get_image (EomWindow *window); + +void eom_window_open_file_list (EomWindow *window, + GSList *file_list); + +gboolean eom_window_is_empty (EomWindow *window); + +void eom_window_reload_image (EomWindow *window); +G_END_DECLS + +#endif diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..f4232ef --- /dev/null +++ b/src/main.c @@ -0,0 +1,270 @@ +/* Eye Of Mate - Main + * + * Copyright (C) 2000-2006 The Free Software Foundation + * + * Author: Lucas Rocha <[email protected]> + * + * Based on code by: + * - Federico Mena-Quintero <[email protected]> + * - Jens Finke <[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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#ifdef HAVE_DBUS +#include <dbus/dbus-glib-bindings.h> +#include <gdk/gdkx.h> +#endif + +#include "eom-session.h" +#include "eom-debug.h" +#include "eom-thumbnail.h" +#include "eom-job-queue.h" +#include "eom-application.h" +#include "eom-plugin-engine.h" +#include "eom-util.h" + +#include <string.h> +#include <stdlib.h> +#include <glib/gi18n.h> + +#if HAVE_EXEMPI +#include <exempi/xmp.h> +#endif + +static EomStartupFlags flags; + +static gboolean fullscreen = FALSE; +static gboolean slide_show = FALSE; +static gboolean disable_collection = FALSE; +#if HAVE_DBUS +static gboolean force_new_instance = FALSE; +#endif +static gchar **startup_files = NULL; + +static gboolean +_print_version_and_exit (const gchar *option_name, + const gchar *value, + gpointer data, + GError **error) +{ + g_print("%s %s\n", _("Eye of MATE Image Viewer"), VERSION); + exit (EXIT_SUCCESS); + return TRUE; +} + +static const GOptionEntry goption_options[] = +{ + { "fullscreen", 'f', 0, G_OPTION_ARG_NONE, &fullscreen, N_("Open in fullscreen mode"), NULL }, + { "disable-image-collection", 'c', 0, G_OPTION_ARG_NONE, &disable_collection, N_("Disable image collection"), NULL }, + { "slide-show", 's', 0, G_OPTION_ARG_NONE, &slide_show, N_("Open in slideshow mode"), NULL }, +#if HAVE_DBUS + { "new-instance", 'n', 0, G_OPTION_ARG_NONE, &force_new_instance, N_("Start a new instance instead of reusing an existing one"), NULL }, +#endif + { "version", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, + _print_version_and_exit, N_("Show the application's version"), NULL}, + { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &startup_files, NULL, N_("[FILE…]") }, + { NULL } +}; + +static void +set_startup_flags (void) +{ + if (fullscreen) + flags |= EOM_STARTUP_FULLSCREEN; + + if (disable_collection) + flags |= EOM_STARTUP_DISABLE_COLLECTION; + + if (slide_show) + flags |= EOM_STARTUP_SLIDE_SHOW; +} + +static void +load_files (void) +{ + GSList *files = NULL; + + files = eom_util_string_array_to_list ((const gchar **) startup_files, TRUE); + + eom_application_open_uri_list (EOM_APP, + files, + GDK_CURRENT_TIME, + flags, + NULL); + + g_slist_foreach (files, (GFunc) g_free, NULL); + g_slist_free (files); +} + +#ifdef HAVE_DBUS +static gboolean +load_files_remote (void) +{ + GError *error = NULL; + DBusGConnection *connection; + DBusGProxy *remote_object; + gboolean result = TRUE; + GdkDisplay *display; + guint32 timestamp; + gchar **files; + + display = gdk_display_get_default (); + + timestamp = gdk_x11_display_get_user_time (display); + connection = dbus_g_bus_get (DBUS_BUS_STARTER, &error); + + if (connection == NULL) { + g_warning ("%s", error->message); + g_error_free (error); + + return FALSE; + } + + files = eom_util_string_array_make_absolute (startup_files); + + remote_object = dbus_g_proxy_new_for_name (connection, + "org.mate.eom.ApplicationService", + "/org/mate/eom/Eom", + "org.mate.eom.Application"); + + if (!files) { + if (!dbus_g_proxy_call (remote_object, "OpenWindow", &error, + G_TYPE_UINT, timestamp, + G_TYPE_UCHAR, flags, + G_TYPE_INVALID, + G_TYPE_INVALID)) { + g_warning ("%s", error->message); + g_clear_error (&error); + + result = FALSE; + } + } else { + if (!dbus_g_proxy_call (remote_object, "OpenUris", &error, + G_TYPE_STRV, files, + G_TYPE_UINT, timestamp, + G_TYPE_UCHAR, flags, + G_TYPE_INVALID, + G_TYPE_INVALID)) { + g_warning ("%s", error->message); + g_clear_error (&error); + + result = FALSE; + } + + g_strfreev (files); + } + + g_object_unref (remote_object); + dbus_g_connection_unref (connection); + + gdk_notify_startup_complete (); + + return result; +} +#endif /* HAVE_DBUS */ + +int +main (int argc, char **argv) +{ + GError *error = NULL; + GOptionContext *ctx; + + if (!g_thread_supported ()) + g_thread_init (NULL); + + bindtextdomain (PACKAGE, EOM_LOCALE_DIR); + bind_textdomain_codeset (PACKAGE, "UTF-8"); + textdomain (PACKAGE); + + gtk_rc_parse (EOM_DATA_DIR G_DIR_SEPARATOR_S "gtkrc"); + + ctx = g_option_context_new (NULL); + g_option_context_add_main_entries (ctx, goption_options, PACKAGE); + /* Option groups are free'd together with the context + * Using gtk_get_option_group here initializes gtk during parsing */ + g_option_context_add_group (ctx, gtk_get_option_group (TRUE)); + + if (!g_option_context_parse (ctx, &argc, &argv, &error)) { + gchar *help_msg; + + /* I18N: The '%s' is replaced with eom's command name. */ + help_msg = g_strdup_printf (_("Run '%s --help' to see a full " + "list of available command line " + "options."), argv[0]); + g_printerr ("%s\n%s\n", error->message, help_msg); + g_error_free (error); + g_free (help_msg); + g_option_context_free (ctx); + + return 1; + } + g_option_context_free (ctx); + + + set_startup_flags (); + +#ifdef HAVE_DBUS + if (!force_new_instance && + !eom_application_register_service (EOM_APP)) { + if (load_files_remote ()) { + return 0; + } + } +#endif /* HAVE_DBUS */ + +#ifdef HAVE_EXEMPI + xmp_init(); +#endif +#ifdef HAVE_RSVG + rsvg_init(); +#endif + eom_debug_init (); + eom_job_queue_init (); + gdk_threads_init (); + eom_thumbnail_init (); + eom_plugin_engine_init (); + + /* Add application specific icons to search path */ + gtk_icon_theme_append_search_path (gtk_icon_theme_get_default (), + EOM_DATA_DIR G_DIR_SEPARATOR_S "icons"); + + gtk_window_set_default_icon_name ("eom"); + g_set_application_name (_("Eye of MATE Image Viewer")); + + load_files (); + + gdk_threads_enter (); + + gtk_main (); + + gdk_threads_leave (); + + if (startup_files) + g_strfreev (startup_files); + + eom_plugin_engine_shutdown (); + +#ifdef HAVE_RSVG + rsvg_term(); +#endif +#ifdef HAVE_EXEMPI + xmp_terminate(); +#endif + return 0; +} diff --git a/src/uta.c b/src/uta.c new file mode 100644 index 0000000..8df97bc --- /dev/null +++ b/src/uta.c @@ -0,0 +1,1116 @@ +/* Eye of Mate image viewer - Microtile array utilities + * + * Copyright (C) 2000-2009 The Free Software Foundation + * + * Author: Federico Mena-Quintero <[email protected]> + * + * Portions based on code from libart_lgpl by Raph Levien. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include <config.h> +#include <glib.h> +#include "uta.h" + +#define EOM_UTA_BBOX_CONS(x0, y0, x1, y1) (((x0) << 24) | ((y0) << 16) | \ + ((x1) << 8) | (y1)) + +#define EOM_UTA_BBOX_X0(ub) ((ub) >> 24) +#define EOM_UTA_BBOX_Y0(ub) (((ub) >> 16) & 0xff) +#define EOM_UTA_BBOX_X1(ub) (((ub) >> 8) & 0xff) +#define EOM_UTA_BBOX_Y1(ub) ((ub) & 0xff) + +#define eom_renew(p, type, n) ((type *)g_realloc (p, (n) * sizeof(type))) +/* This one must be used carefully - in particular, p and max should + be variables. They can also be pstruct->el lvalues. */ +#define eom_expand(p, type, max) do { if(max) { p = eom_renew (p, type, max <<= 1); } else { max = 1; p = g_new(type, 1); } } while (0) + + + +/** + * eom_uta_new: Allocate a new uta. + * @x0: Left coordinate of uta. + * @y0: Top coordinate of uta. + * @x1: Right coordinate of uta. + * @y1: Bottom coordinate of uta. + * + * Allocates a new microtile array. The arguments are in units of + * tiles, not pixels. + * + * Returns: the newly allocated #EomUta. + **/ +static EomUta * +eom_uta_new (int x0, int y0, int x1, int y1) +{ + EomUta *uta; + + uta = g_new (EomUta, 1); + uta->x0 = x0; + uta->y0 = y0; + uta->width = x1 - x0; + uta->height = y1 - y0; + + uta->utiles = g_new0 (EomUtaBbox, uta->width * uta->height); + + return uta; +} + +/** + * eom_uta_free: Free a uta. + * @uta: The uta to free. + * + * Frees the microtile array structure, including the actual microtile + * data. + **/ +void +eom_uta_free (EomUta *uta) +{ + g_free (uta->utiles); + g_free (uta); +} + +/** + * eom_irect_intersect: Find intersection of two integer rectangles. + * @dest: Where the result is stored. + * @src1: A source rectangle. + * @src2: Another source rectangle. + * + * Finds the intersection of @src1 and @src2. + **/ +void +eom_irect_intersect (EomIRect *dest, const EomIRect *src1, const EomIRect *src2) { + dest->x0 = MAX (src1->x0, src2->x0); + dest->y0 = MAX (src1->y0, src2->y0); + dest->x1 = MIN (src1->x1, src2->x1); + dest->y1 = MIN (src1->y1, src2->y1); +} + +/** + * eom_irect_empty: Determine whether integer rectangle is empty. + * @src: The source rectangle. + * + * Return value: TRUE if @src is an empty rectangle, FALSE otherwise. + **/ +int +eom_irect_empty (const EomIRect *src) { + return (src->x1 <= src->x0 || src->y1 <= src->y0); +} + +/** + * eom_uta_from_irect: Generate uta covering a rectangle. + * @bbox: The source rectangle. + * + * Generates a uta exactly covering @bbox. Please do not call this + * function with a @bbox with zero height or width. + * + * Return value: the new uta. + **/ +static EomUta * +eom_uta_from_irect (EomIRect *bbox) +{ + EomUta *uta; + EomUtaBbox *utiles; + EomUtaBbox bb; + int width, height; + int x, y; + int xf0, yf0, xf1, yf1; + int ix; + + uta = g_new (EomUta, 1); + uta->x0 = bbox->x0 >> EOM_UTILE_SHIFT; + uta->y0 = bbox->y0 >> EOM_UTILE_SHIFT; + width = ((bbox->x1 + EOM_UTILE_SIZE - 1) >> EOM_UTILE_SHIFT) - uta->x0; + height = ((bbox->y1 + EOM_UTILE_SIZE - 1) >> EOM_UTILE_SHIFT) - uta->y0; + utiles = g_new (EomUtaBbox, width * height); + + uta->width = width; + uta->height = height; + uta->utiles = utiles; + + xf0 = bbox->x0 & (EOM_UTILE_SIZE - 1); + yf0 = bbox->y0 & (EOM_UTILE_SIZE - 1); + xf1 = ((bbox->x1 - 1) & (EOM_UTILE_SIZE - 1)) + 1; + yf1 = ((bbox->y1 - 1) & (EOM_UTILE_SIZE - 1)) + 1; + if (height == 1) + { + if (width == 1) + utiles[0] = EOM_UTA_BBOX_CONS (xf0, yf0, xf1, yf1); + else + { + utiles[0] = EOM_UTA_BBOX_CONS (xf0, yf0, EOM_UTILE_SIZE, yf1); + bb = EOM_UTA_BBOX_CONS (0, yf0, EOM_UTILE_SIZE, yf1); + for (x = 1; x < width - 1; x++) + utiles[x] = bb; + utiles[x] = EOM_UTA_BBOX_CONS (0, yf0, xf1, yf1); + } + } + else + { + if (width == 1) + { + utiles[0] = EOM_UTA_BBOX_CONS (xf0, yf0, xf1, EOM_UTILE_SIZE); + bb = EOM_UTA_BBOX_CONS (xf0, 0, xf1, EOM_UTILE_SIZE); + for (y = 1; y < height - 1; y++) + utiles[y] = bb; + utiles[y] = EOM_UTA_BBOX_CONS (xf0, 0, xf1, yf1); + } + else + { + utiles[0] = + EOM_UTA_BBOX_CONS (xf0, yf0, EOM_UTILE_SIZE, EOM_UTILE_SIZE); + bb = EOM_UTA_BBOX_CONS (0, yf0, EOM_UTILE_SIZE, EOM_UTILE_SIZE); + for (x = 1; x < width - 1; x++) + utiles[x] = bb; + utiles[x] = EOM_UTA_BBOX_CONS (0, yf0, xf1, EOM_UTILE_SIZE); + ix = width; + for (y = 1; y < height - 1; y++) + { + utiles[ix++] = + EOM_UTA_BBOX_CONS (xf0, 0, EOM_UTILE_SIZE, EOM_UTILE_SIZE); + bb = EOM_UTA_BBOX_CONS (0, 0, EOM_UTILE_SIZE, EOM_UTILE_SIZE); + for (x = 1; x < width - 1; x++) + utiles[ix++] = bb; + utiles[ix++] = EOM_UTA_BBOX_CONS (0, 0, xf1, EOM_UTILE_SIZE); + } + utiles[ix++] = EOM_UTA_BBOX_CONS (xf0, 0, EOM_UTILE_SIZE, yf1); + bb = EOM_UTA_BBOX_CONS (0, 0, EOM_UTILE_SIZE, yf1); + for (x = 1; x < width - 1; x++) + utiles[ix++] = bb; + utiles[ix++] = EOM_UTA_BBOX_CONS (0, 0, xf1, yf1); + } + } + return uta; +} + + +/** + * uta_ensure_size: + * @uta: A microtile array. + * @x1: Left microtile coordinate that must fit in new array. + * @y1: Top microtile coordinate that must fit in new array. + * @x2: Right microtile coordinate that must fit in new array. + * @y2: Bottom microtile coordinate that must fit in new array. + * + * Ensures that the size of a microtile array is big enough to fit the specified + * microtile coordinates. If it is not big enough, the specified @uta will be + * freed and a new one will be returned. Otherwise, the original @uta will be + * returned. If a new microtile array needs to be created, this function will + * copy the @uta's contents to the new array. + * + * Note that the specified coordinates must have already been scaled down by the + * ART_UTILE_SHIFT factor. + * + * Return value: The same value as @uta if the original microtile array was + * big enough to fit the specified microtile coordinates, or a new array if + * it needed to be grown. In the second case, the original @uta will be + * freed automatically. + **/ +EomUta * +uta_ensure_size (EomUta *uta, int x1, int y1, int x2, int y2) +{ + EomUta *new_uta; + EomUtaBbox *utiles, *new_utiles; + int new_ofs, ofs; + int x, y; + + g_return_val_if_fail (x1 < x2, NULL); + g_return_val_if_fail (y1 < y2, NULL); + + if (!uta) + return eom_uta_new (x1, y1, x2, y2); + + if (x1 >= uta->x0 + && y1 >= uta->y0 + && x2 <= uta->x0 + uta->width + && y2 <= uta->y0 + uta->height) + return uta; + + new_uta = g_new (EomUta, 1); + + new_uta->x0 = MIN (uta->x0, x1); + new_uta->y0 = MIN (uta->y0, y1); + new_uta->width = MAX (uta->x0 + uta->width, x2) - new_uta->x0; + new_uta->height = MAX (uta->y0 + uta->height, y2) - new_uta->y0; + new_uta->utiles = g_new (EomUtaBbox, new_uta->width * new_uta->height); + + utiles = uta->utiles; + new_utiles = new_uta->utiles; + + new_ofs = 0; + + for (y = new_uta->y0; y < new_uta->y0 + new_uta->height; y++) { + if (y < uta->y0 || y >= uta->y0 + uta->height) + for (x = 0; x < new_uta->width; x++) + new_utiles[new_ofs++] = 0; + else { + ofs = (y - uta->y0) * uta->width; + + for (x = new_uta->x0; x < new_uta->x0 + new_uta->width; x++) + if (x < uta->x0 || x >= uta->x0 + uta->width) + new_utiles[new_ofs++] = 0; + else + new_utiles[new_ofs++] = utiles[ofs++]; + } + } + + eom_uta_free (uta); + return new_uta; +} + +/** + * uta_add_rect: + * @uta: A microtile array, or NULL if a new array should be created. + * @x1: Left coordinate of rectangle. + * @y1: Top coordinate of rectangle. + * @x2: Right coordinate of rectangle. + * @y2: Bottom coordinate of rectangle. + * + * Adds the specified rectangle to a microtile array. The array is + * grown to fit the rectangle if necessary. + * + * Return value: The original @uta, or a new microtile array if the original one + * needed to be grown to fit the specified rectangle. In the second case, the + * original @uta will be freed automatically. + **/ +EomUta * +uta_add_rect (EomUta *uta, int x1, int y1, int x2, int y2) +{ + EomUtaBbox *utiles; + EomUtaBbox bb; + int rect_x1, rect_y1, rect_x2, rect_y2; + int xf1, yf1, xf2, yf2; + int x, y; + int ofs; + + g_return_val_if_fail (x1 < x2, NULL); + g_return_val_if_fail (y1 < y2, NULL); + + /* Empty uta */ + + if (!uta) { + EomIRect r; + + r.x0 = x1; + r.y0 = y1; + r.x1 = x2; + r.y1 = y2; + + return eom_uta_from_irect (&r); + } + + /* Grow the uta if necessary */ + + rect_x1 = x1 >> EOM_UTILE_SHIFT; + rect_y1 = y1 >> EOM_UTILE_SHIFT; + rect_x2 = (x2 + EOM_UTILE_SIZE - 1) >> EOM_UTILE_SHIFT; + rect_y2 = (y2 + EOM_UTILE_SIZE - 1) >> EOM_UTILE_SHIFT; + + uta = uta_ensure_size (uta, rect_x1, rect_y1, rect_x2, rect_y2); + + /* Add the rectangle */ + + xf1 = x1 & (EOM_UTILE_SIZE - 1); + yf1 = y1 & (EOM_UTILE_SIZE - 1); + xf2 = ((x2 - 1) & (EOM_UTILE_SIZE - 1)) + 1; + yf2 = ((y2 - 1) & (EOM_UTILE_SIZE - 1)) + 1; + + utiles = uta->utiles; + + ofs = (rect_y1 - uta->y0) * uta->width + rect_x1 - uta->x0; + + if (rect_y2 - rect_y1 == 1) { + if (rect_x2 - rect_x1 == 1) { + bb = utiles[ofs]; + if (bb == 0) + utiles[ofs] = EOM_UTA_BBOX_CONS ( + xf1, yf1, xf2, yf2); + else + utiles[ofs] = EOM_UTA_BBOX_CONS ( + MIN (EOM_UTA_BBOX_X0 (bb), xf1), + MIN (EOM_UTA_BBOX_Y0 (bb), yf1), + MAX (EOM_UTA_BBOX_X1 (bb), xf2), + MAX (EOM_UTA_BBOX_Y1 (bb), yf2)); + } else { + /* Leftmost tile */ + bb = utiles[ofs]; + if (bb == 0) + utiles[ofs++] = EOM_UTA_BBOX_CONS ( + xf1, yf1, EOM_UTILE_SIZE, yf2); + else + utiles[ofs++] = EOM_UTA_BBOX_CONS ( + MIN (EOM_UTA_BBOX_X0 (bb), xf1), + MIN (EOM_UTA_BBOX_Y0 (bb), yf1), + EOM_UTILE_SIZE, + MAX (EOM_UTA_BBOX_Y1 (bb), yf2)); + + /* Tiles in between */ + for (x = rect_x1 + 1; x < rect_x2 - 1; x++) { + bb = utiles[ofs]; + if (bb == 0) + utiles[ofs++] = EOM_UTA_BBOX_CONS ( + 0, yf1, EOM_UTILE_SIZE, yf2); + else + utiles[ofs++] = EOM_UTA_BBOX_CONS ( + 0, + MIN (EOM_UTA_BBOX_Y0 (bb), yf1), + EOM_UTILE_SIZE, + MAX (EOM_UTA_BBOX_Y1 (bb), yf2)); + } + + /* Rightmost tile */ + bb = utiles[ofs]; + if (bb == 0) + utiles[ofs] = EOM_UTA_BBOX_CONS ( + 0, yf1, xf2, yf2); + else + utiles[ofs] = EOM_UTA_BBOX_CONS ( + 0, + MIN (EOM_UTA_BBOX_Y0 (bb), yf1), + MAX (EOM_UTA_BBOX_X1 (bb), xf2), + MAX (EOM_UTA_BBOX_Y1 (bb), yf2)); + } + } else { + if (rect_x2 - rect_x1 == 1) { + /* Topmost tile */ + bb = utiles[ofs]; + if (bb == 0) + utiles[ofs] = EOM_UTA_BBOX_CONS ( + xf1, yf1, xf2, EOM_UTILE_SIZE); + else + utiles[ofs] = EOM_UTA_BBOX_CONS ( + MIN (EOM_UTA_BBOX_X0 (bb), xf1), + MIN (EOM_UTA_BBOX_Y0 (bb), yf1), + MAX (EOM_UTA_BBOX_X1 (bb), xf2), + EOM_UTILE_SIZE); + ofs += uta->width; + + /* Tiles in between */ + for (y = rect_y1 + 1; y < rect_y2 - 1; y++) { + bb = utiles[ofs]; + if (bb == 0) + utiles[ofs] = EOM_UTA_BBOX_CONS ( + xf1, 0, xf2, EOM_UTILE_SIZE); + else + utiles[ofs] = EOM_UTA_BBOX_CONS ( + MIN (EOM_UTA_BBOX_X0 (bb), xf1), + 0, + MAX (EOM_UTA_BBOX_X1 (bb), xf2), + EOM_UTILE_SIZE); + ofs += uta->width; + } + + /* Bottommost tile */ + bb = utiles[ofs]; + if (bb == 0) + utiles[ofs] = EOM_UTA_BBOX_CONS ( + xf1, 0, xf2, yf2); + else + utiles[ofs] = EOM_UTA_BBOX_CONS ( + MIN (EOM_UTA_BBOX_X0 (bb), xf1), + 0, + MAX (EOM_UTA_BBOX_X1 (bb), xf2), + MAX (EOM_UTA_BBOX_Y1 (bb), yf2)); + } else { + /* Top row, leftmost tile */ + bb = utiles[ofs]; + if (bb == 0) + utiles[ofs++] = EOM_UTA_BBOX_CONS ( + xf1, yf1, EOM_UTILE_SIZE, EOM_UTILE_SIZE); + else + utiles[ofs++] = EOM_UTA_BBOX_CONS ( + MIN (EOM_UTA_BBOX_X0 (bb), xf1), + MIN (EOM_UTA_BBOX_Y0 (bb), yf1), + EOM_UTILE_SIZE, + EOM_UTILE_SIZE); + + /* Top row, in between */ + for (x = rect_x1 + 1; x < rect_x2 - 1; x++) { + bb = utiles[ofs]; + if (bb == 0) + utiles[ofs++] = EOM_UTA_BBOX_CONS ( + 0, yf1, EOM_UTILE_SIZE, EOM_UTILE_SIZE); + else + utiles[ofs++] = EOM_UTA_BBOX_CONS ( + 0, + MIN (EOM_UTA_BBOX_Y0 (bb), yf1), + EOM_UTILE_SIZE, + EOM_UTILE_SIZE); + } + + /* Top row, rightmost tile */ + bb = utiles[ofs]; + if (bb == 0) + utiles[ofs] = EOM_UTA_BBOX_CONS ( + 0, yf1, xf2, EOM_UTILE_SIZE); + else + utiles[ofs] = EOM_UTA_BBOX_CONS ( + 0, + MIN (EOM_UTA_BBOX_Y0 (bb), yf1), + MAX (EOM_UTA_BBOX_X1 (bb), xf2), + EOM_UTILE_SIZE); + + ofs += uta->width - (rect_x2 - rect_x1 - 1); + + /* Rows in between */ + for (y = rect_y1 + 1; y < rect_y2 - 1; y++) { + /* Leftmost tile */ + bb = utiles[ofs]; + if (bb == 0) + utiles[ofs++] = EOM_UTA_BBOX_CONS ( + xf1, 0, EOM_UTILE_SIZE, EOM_UTILE_SIZE); + else + utiles[ofs++] = EOM_UTA_BBOX_CONS ( + MIN (EOM_UTA_BBOX_X0 (bb), xf1), + 0, + EOM_UTILE_SIZE, + EOM_UTILE_SIZE); + + /* Tiles in between */ + bb = EOM_UTA_BBOX_CONS (0, 0, EOM_UTILE_SIZE, EOM_UTILE_SIZE); + for (x = rect_x1 + 1; x < rect_x2 - 1; x++) + utiles[ofs++] = bb; + + /* Rightmost tile */ + bb = utiles[ofs]; + if (bb == 0) + utiles[ofs] = EOM_UTA_BBOX_CONS ( + 0, 0, xf2, EOM_UTILE_SIZE); + else + utiles[ofs] = EOM_UTA_BBOX_CONS ( + 0, + 0, + MAX (EOM_UTA_BBOX_X1 (bb), xf2), + EOM_UTILE_SIZE); + + ofs += uta->width - (rect_x2 - rect_x1 - 1); + } + + /* Bottom row, leftmost tile */ + bb = utiles[ofs]; + if (bb == 0) + utiles[ofs++] = EOM_UTA_BBOX_CONS ( + xf1, 0, EOM_UTILE_SIZE, yf2); + else + utiles[ofs++] = EOM_UTA_BBOX_CONS ( + MIN (EOM_UTA_BBOX_X0 (bb), xf1), + 0, + EOM_UTILE_SIZE, + MAX (EOM_UTA_BBOX_Y1 (bb), yf2)); + + /* Bottom row, tiles in between */ + for (x = rect_x1 + 1; x < rect_x2 - 1; x++) { + bb = utiles[ofs]; + if (bb == 0) + utiles[ofs++] = EOM_UTA_BBOX_CONS ( + 0, 0, EOM_UTILE_SIZE, yf2); + else + utiles[ofs++] = EOM_UTA_BBOX_CONS ( + 0, + 0, + EOM_UTILE_SIZE, + MAX (EOM_UTA_BBOX_Y1 (bb), yf2)); + } + + /* Bottom row, rightmost tile */ + bb = utiles[ofs]; + if (bb == 0) + utiles[ofs] = EOM_UTA_BBOX_CONS ( + 0, 0, xf2, yf2); + else + utiles[ofs] = EOM_UTA_BBOX_CONS ( + 0, + 0, + MAX (EOM_UTA_BBOX_X1 (bb), xf2), + MAX (EOM_UTA_BBOX_Y1 (bb), yf2)); + } + } + + return uta; +} + +/** + * uta_remove_rect: + * @uta: A microtile array. + * @x1: Left coordinate of rectangle. + * @y1: Top coordinate of rectangle. + * @x2: Right coordinate of rectangle. + * @y2: Bottom coordinate of rectangle. + * + * Removes a rectangular region from the specified microtile array. Due to the + * way microtile arrays represent regions, the tiles at the edge of the + * rectangle may not be clipped exactly. + **/ +void +uta_remove_rect (EomUta *uta, int x1, int y1, int x2, int y2) +{ + EomUtaBbox *utiles; + int rect_x1, rect_y1, rect_x2, rect_y2; + int clip_x1, clip_y1, clip_x2, clip_y2; + int xf1, yf1, xf2, yf2; + int ofs; + int x, y; + + g_return_if_fail (uta != NULL); + g_return_if_fail (x1 <= x2); + g_return_if_fail (y1 <= y2); + + if (x1 == x2 || y1 == y2) + return; + + rect_x1 = x1 >> EOM_UTILE_SHIFT; + rect_y1 = y1 >> EOM_UTILE_SHIFT; + rect_x2 = (x2 + EOM_UTILE_SIZE - 1) >> EOM_UTILE_SHIFT; + rect_y2 = (y2 + EOM_UTILE_SIZE - 1) >> EOM_UTILE_SHIFT; + + clip_x1 = MAX (rect_x1, uta->x0); + clip_y1 = MAX (rect_y1, uta->y0); + clip_x2 = MIN (rect_x2, uta->x0 + uta->width); + clip_y2 = MIN (rect_y2, uta->y0 + uta->height); + + if (clip_x1 >= clip_x2 || clip_y1 >= clip_y2) + return; + + xf1 = x1 & (EOM_UTILE_SIZE - 1); + yf1 = y1 & (EOM_UTILE_SIZE - 1); + xf2 = ((x2 - 1) & (EOM_UTILE_SIZE - 1)) + 1; + yf2 = ((y2 - 1) & (EOM_UTILE_SIZE - 1)) + 1; + + utiles = uta->utiles; + + ofs = (clip_y1 - uta->y0) * uta->width + clip_x1 - uta->x0; + + for (y = clip_y1; y < clip_y2; y++) { + int cy1, cy2; + + if (y == rect_y1) + cy1 = yf1; + else + cy1 = 0; + + if (y == rect_y2 - 1) + cy2 = yf2; + else + cy2 = EOM_UTILE_SIZE; + + for (x = clip_x1; x < clip_x2; x++) { + int cx1, cx2; + EomUtaBbox bb; + int bb_x1, bb_y1, bb_x2, bb_y2; + int bb_cx1, bb_cy1, bb_cx2, bb_cy2; + + bb = utiles[ofs]; + bb_x1 = EOM_UTA_BBOX_X0 (bb); + bb_y1 = EOM_UTA_BBOX_Y0 (bb); + bb_x2 = EOM_UTA_BBOX_X1 (bb); + bb_y2 = EOM_UTA_BBOX_Y1 (bb); + + if (x == rect_x1) + cx1 = xf1; + else + cx1 = 0; + + if (x == rect_x2 - 1) + cx2 = xf2; + else + cx2 = EOM_UTILE_SIZE; + + /* Clip top and bottom */ + + if (cx1 <= bb_x1 && cx2 >= bb_x2) { + if (cy1 <= bb_y1 && cy2 > bb_y1) + bb_cy1 = cy2; + else + bb_cy1 = bb_y1; + + if (cy1 < bb_y2 && cy2 >= bb_y2) + bb_cy2 = cy1; + else + bb_cy2 = bb_y2; + } else { + bb_cy1 = bb_y1; + bb_cy2 = bb_y2; + } + + /* Clip left and right */ + + if (cy1 <= bb_y1 && cy2 >= bb_y2) { + if (cx1 <= bb_x1 && cx2 > bb_x1) + bb_cx1 = cx2; + else + bb_cx1 = bb_x1; + + if (cx1 < bb_x2 && cx2 >= bb_x2) + bb_cx2 = cx1; + else + bb_cx2 = bb_x2; + } else { + bb_cx1 = bb_x1; + bb_cx2 = bb_x2; + } + + if (bb_cx1 < bb_cx2 && bb_cy1 < bb_cy2) + utiles[ofs] = EOM_UTA_BBOX_CONS (bb_cx1, bb_cy1, + bb_cx2, bb_cy2); + else + utiles[ofs] = 0; + + ofs++; + } + + ofs += uta->width - (clip_x2 - clip_x1); + } +} + +void +uta_find_first_glom_rect (EomUta *uta, EomIRect *rect, int max_width, int max_height) +{ + EomIRect *rects; + int n_rects, n_rects_max; + int x, y; + int width, height; + int ix; + int left_ix; + EomUtaBbox *utiles; + EomUtaBbox bb; + int x0, y0, x1, y1; + int *glom; + int glom_rect; + + n_rects = 0; + n_rects_max = 1; + rects = g_new (EomIRect, n_rects_max); + + width = uta->width; + height = uta->height; + utiles = uta->utiles; + + glom = g_new (int, width * height); + for (ix = 0; ix < width * height; ix++) + glom[ix] = -1; + + ix = 0; + for (y = 0; y < height; y++) + for (x = 0; x < width; x++) + { + bb = utiles[ix]; + if (bb) + { + x0 = ((uta->x0 + x) << EOM_UTILE_SHIFT) + EOM_UTA_BBOX_X0(bb); + y0 = ((uta->y0 + y) << EOM_UTILE_SHIFT) + EOM_UTA_BBOX_Y0(bb); + y1 = ((uta->y0 + y) << EOM_UTILE_SHIFT) + EOM_UTA_BBOX_Y1(bb); + + left_ix = ix; + /* now try to extend to the right */ + while (x != width - 1 && + EOM_UTA_BBOX_X1(bb) == EOM_UTILE_SIZE && + (((bb & 0xffffff) ^ utiles[ix + 1]) & 0xffff00ff) == 0 && + (((uta->x0 + x + 1) << EOM_UTILE_SHIFT) + + EOM_UTA_BBOX_X1(utiles[ix + 1]) - + x0) <= max_width) + { + bb = utiles[ix + 1]; + ix++; + x++; + } + x1 = ((uta->x0 + x) << EOM_UTILE_SHIFT) + EOM_UTA_BBOX_X1(bb); + + + /* if rectangle nonempty */ + if ((x1 ^ x0) || (y1 ^ y0)) + { + /* try to glom onto an existing rectangle */ + glom_rect = glom[left_ix]; + if (glom_rect != -1 && + x0 == rects[glom_rect].x0 && + x1 == rects[glom_rect].x1 && + y0 == rects[glom_rect].y1 && + y1 - rects[glom_rect].y0 <= max_height) + { + rects[glom_rect].y1 = y1; + } + else + { + if (n_rects == n_rects_max) + eom_expand (rects, EomIRect, n_rects_max); + rects[n_rects].x0 = x0; + rects[n_rects].y0 = y0; + rects[n_rects].x1 = x1; + rects[n_rects].y1 = y1; + glom_rect = n_rects; + n_rects++; + } + if (y != height - 1) + glom[left_ix + width] = glom_rect; + } + } + ix++; + } + + if (n_rects > 0) { + rect->x0 = rects[0].x0; + rect->y0 = rects[0].y0; + rect->x1 = rects[0].x1; + rect->y1 = rects[0].y1; + } else + rect->x0 = rect->y0 = rect->x1 = rect->y1 = 0; + + g_free (glom); + g_free (rects); +} + +#if 0 + +void +uta_find_first_glom_rect (EomUta *uta, EomIRect *rect, int max_width, int max_height) +{ + EomUtaBbox *utiles; + EomUtaBbox bb; + int width, height; + int ofs; + int x, y; + int x1, y1, x2, y2; + + g_return_if_fail (uta != NULL); + g_return_if_fail (rect != NULL); + g_return_if_fail (max_width > 0 && max_height > 0); + + utiles = uta->utiles; + width = uta->width; + height = uta->height; + + ofs = 0; + + /* We find the first nonempty tile, and then grow the rectangle to the + * right and then down. + */ + + x1 = y1 = x2 = y2 = 0; + + for (y = 0; y < height; y++) { + for (x = 0; x < width; x++) { + bb = utiles[ofs]; + + if (!bb) { + ofs++; + continue; + } + + x1 = ((uta->x0 + x) << EOM_UTILE_SHIFT) + EOM_UTA_BBOX_X0 (bb); + y1 = ((uta->y0 + y) << EOM_UTILE_SHIFT) + EOM_UTA_BBOX_Y0 (bb); + y2 = ((uta->y0 + y) << EOM_UTILE_SHIFT) + EOM_UTA_BBOX_Y1 (bb); + + /* Grow to the right */ + + while (x != width - 1 + && EOM_UTA_BBOX_X1 (bb) == EOM_UTILE_SIZE + && (((bb & 0xffffff) ^ utiles[ofs + 1]) & 0xffff00ff) == 0 + && (((uta->x0 + x + 1) << EOM_UTILE_SHIFT) + + EOM_UTA_BBOX_X1 (utiles[ofs + 1]) + - x1) <= max_width) { + ofs++; + bb = utiles[ofs]; + x++; + } + + x2 = ((uta->x0 + x) << EOM_UTILE_SHIFT) + EOM_UTA_BBOX_X1 (bb); + goto grow_down; + } + } + + grow_down: + +} + +#endif + +/* Copies a single microtile to another location in the UTA, offsetted by the + * specified distance. A microtile can thus end up being added in a single part + * to another microtile, in two parts to two horizontally or vertically adjacent + * microtiles, or in four parts to a 2x2 square of microtiles. + * + * This is basically a normal BitBlt but with copying-forwards-to-the-destination + * instead of fetching-backwards-from-the-source. + */ +static void +copy_tile (EomUta *uta, int x, int y, int xofs, int yofs) +{ + EomUtaBbox *utiles; + EomUtaBbox bb, dbb; + int t_x1, t_y1, t_x2, t_y2; + int d_x1, d_y1, d_x2, d_y2; + int d_tx1, d_ty1; + int d_xf1, d_yf1, d_xf2, d_yf2; + int dofs; + + utiles = uta->utiles; + + bb = utiles[(y - uta->y0) * uta->width + x - uta->x0]; + + if (bb == 0) + return; + + t_x1 = EOM_UTA_BBOX_X0 (bb) + (x << EOM_UTILE_SHIFT); + t_y1 = EOM_UTA_BBOX_Y0 (bb) + (y << EOM_UTILE_SHIFT); + t_x2 = EOM_UTA_BBOX_X1 (bb) + (x << EOM_UTILE_SHIFT); + t_y2 = EOM_UTA_BBOX_Y1 (bb) + (y << EOM_UTILE_SHIFT); + + d_x1 = t_x1 + xofs; + d_y1 = t_y1 + yofs; + d_x2 = t_x2 + xofs; + d_y2 = t_y2 + yofs; + + d_tx1 = d_x1 >> EOM_UTILE_SHIFT; + d_ty1 = d_y1 >> EOM_UTILE_SHIFT; + + dofs = (d_ty1 - uta->y0) * uta->width + d_tx1 - uta->x0; + + d_xf1 = d_x1 & (EOM_UTILE_SIZE - 1); + d_yf1 = d_y1 & (EOM_UTILE_SIZE - 1); + d_xf2 = ((d_x2 - 1) & (EOM_UTILE_SIZE - 1)) + 1; + d_yf2 = ((d_y2 - 1) & (EOM_UTILE_SIZE - 1)) + 1; + + if (d_x2 - d_x1 <= EOM_UTILE_SIZE - d_xf1) { + if (d_y2 - d_y1 <= EOM_UTILE_SIZE - d_yf1) { + if (d_tx1 >= uta->x0 && d_tx1 < uta->x0 + uta->width + && d_ty1 >= uta->y0 && d_ty1 < uta->y0 + uta->height) { + dbb = utiles[dofs]; + if (dbb == 0) + utiles[dofs] = EOM_UTA_BBOX_CONS ( + d_xf1, d_yf1, d_xf2, d_yf2); + else + utiles[dofs] = EOM_UTA_BBOX_CONS ( + MIN (EOM_UTA_BBOX_X0 (dbb), d_xf1), + MIN (EOM_UTA_BBOX_Y0 (dbb), d_yf1), + MAX (EOM_UTA_BBOX_X1 (dbb), d_xf2), + MAX (EOM_UTA_BBOX_Y1 (dbb), d_yf2)); + } + } else { + /* Top tile */ + if (d_tx1 >= uta->x0 && d_tx1 < uta->x0 + uta->width + && d_ty1 >= uta->y0 && d_ty1 < uta->y0 + uta->height) { + dbb = utiles[dofs]; + if (dbb == 0) + utiles[dofs] = EOM_UTA_BBOX_CONS ( + d_xf1, d_yf1, d_xf2, EOM_UTILE_SIZE); + else + utiles[dofs] = EOM_UTA_BBOX_CONS ( + MIN (EOM_UTA_BBOX_X0 (dbb), d_xf1), + MIN (EOM_UTA_BBOX_Y0 (dbb), d_yf1), + MAX (EOM_UTA_BBOX_X1 (dbb), d_xf2), + EOM_UTILE_SIZE); + } + + dofs += uta->width; + + /* Bottom tile */ + if (d_tx1 >= uta->x0 && d_tx1 < uta->x0 + uta->width + && d_ty1 + 1 >= uta->y0 && d_ty1 + 1 < uta->y0 + uta->height) { + dbb = utiles[dofs]; + if (dbb == 0) + utiles[dofs] = EOM_UTA_BBOX_CONS ( + d_xf1, 0, d_xf2, d_yf2); + else + utiles[dofs] = EOM_UTA_BBOX_CONS ( + MIN (EOM_UTA_BBOX_X0 (dbb), d_xf1), + 0, + MAX (EOM_UTA_BBOX_X1 (dbb), d_xf2), + MAX (EOM_UTA_BBOX_Y1 (dbb), d_yf2)); + } + } + } else { + if (d_y2 - d_y1 <= EOM_UTILE_SIZE - d_yf1) { + /* Left tile */ + if (d_tx1 >= uta->x0 && d_tx1 < uta->x0 + uta->width + && d_ty1 >= uta->y0 && d_ty1 < uta->y0 + uta->height) { + dbb = utiles[dofs]; + if (dbb == 0) + utiles[dofs] = EOM_UTA_BBOX_CONS ( + d_xf1, d_yf1, EOM_UTILE_SIZE, d_yf2); + else + utiles[dofs] = EOM_UTA_BBOX_CONS ( + MIN (EOM_UTA_BBOX_X0 (dbb), d_xf1), + MIN (EOM_UTA_BBOX_Y0 (dbb), d_yf1), + EOM_UTILE_SIZE, + MAX (EOM_UTA_BBOX_Y1 (dbb), d_yf2)); + } + + dofs++; + + /* Right tile */ + if (d_tx1 + 1 >= uta->x0 && d_tx1 + 1 < uta->x0 + uta->width + && d_ty1 >= uta->y0 && d_ty1 < uta->y0 + uta->height) { + dbb = utiles[dofs]; + if (dbb == 0) + utiles[dofs] = EOM_UTA_BBOX_CONS ( + 0, d_yf1, d_xf2, d_yf2); + else + utiles[dofs] = EOM_UTA_BBOX_CONS ( + 0, + MIN (EOM_UTA_BBOX_Y0 (dbb), d_yf1), + MAX (EOM_UTA_BBOX_X1 (dbb), d_xf2), + MAX (EOM_UTA_BBOX_Y1 (dbb), d_yf2)); + } + } else { + /* Top left tile */ + if (d_tx1 >= uta->x0 && d_tx1 < uta->x0 + uta->width + && d_ty1 >= uta->y0 && d_ty1 < uta->y0 + uta->height) { + dbb = utiles[dofs]; + if (dbb == 0) + utiles[dofs] = EOM_UTA_BBOX_CONS ( + d_xf1, d_yf1, EOM_UTILE_SIZE, EOM_UTILE_SIZE); + else + utiles[dofs] = EOM_UTA_BBOX_CONS ( + MIN (EOM_UTA_BBOX_X0 (dbb), d_xf1), + MIN (EOM_UTA_BBOX_Y0 (dbb), d_yf1), + EOM_UTILE_SIZE, + EOM_UTILE_SIZE); + } + + dofs++; + + /* Top right tile */ + if (d_tx1 + 1 >= uta->x0 && d_tx1 + 1 < uta->x0 + uta->width + && d_ty1 >= uta->y0 && d_ty1 < uta->y0 + uta->height) { + dbb = utiles[dofs]; + if (dbb == 0) + utiles[dofs] = EOM_UTA_BBOX_CONS ( + 0, d_yf1, d_xf2, EOM_UTILE_SIZE); + else + utiles[dofs] = EOM_UTA_BBOX_CONS ( + 0, + MIN (EOM_UTA_BBOX_Y0 (dbb), d_yf1), + MAX (EOM_UTA_BBOX_X1 (dbb), d_xf2), + EOM_UTILE_SIZE); + } + + dofs += uta->width - 1; + + /* Bottom left tile */ + if (d_tx1 >= uta->x0 && d_tx1 < uta->x0 + uta->width + && d_ty1 + 1 >= uta->y0 && d_ty1 + 1 < uta->y0 + uta->height) { + dbb = utiles[dofs]; + if (dbb == 0) + utiles[dofs] = EOM_UTA_BBOX_CONS ( + d_xf1, 0, EOM_UTILE_SIZE, d_yf2); + else + utiles[dofs] = EOM_UTA_BBOX_CONS ( + MIN (EOM_UTA_BBOX_X0 (dbb), d_xf1), + 0, + EOM_UTILE_SIZE, + MAX (EOM_UTA_BBOX_Y1 (dbb), d_yf2)); + } + + dofs++; + + /* Bottom right tile */ + if (d_tx1 + 1 >= uta->x0 && d_tx1 + 1 < uta->x0 + uta->width + && d_ty1 + 1 >= uta->y0 && d_ty1 + 1 < uta->y0 + uta->height) { + dbb = utiles[dofs]; + if (dbb == 0) + utiles[dofs] = EOM_UTA_BBOX_CONS ( + 0, 0, d_xf2, d_yf2); + else + utiles[dofs] = EOM_UTA_BBOX_CONS ( + 0, + 0, + MAX (EOM_UTA_BBOX_X1 (dbb), d_xf2), + MAX (EOM_UTA_BBOX_Y1 (dbb), d_yf2)); + } + } + } +} + +/** + * uta_copy_area: + * @uta: A microtile array. + * @src_x: Left coordinate of source rectangle. + * @src_y: Top coordinate of source rectangle. + * @dest_x: Left coordinate of destination. + * @dest_y: Top coordinate of destination. + * @width: Width of region to copy. + * @height: Height of region to copy. + * + * Copies a rectangular region within a microtile array. The array will not be + * expanded if the destination area does not fit within it; rather only the area + * that fits will be copied. The source rectangle must be completely contained + * within the microtile array. + **/ +void +uta_copy_area (EomUta *uta, int src_x, int src_y, int dest_x, int dest_y, int width, int height) +{ + int rect_x1, rect_y1, rect_x2, rect_y2; + gboolean top_to_bottom, left_to_right; + int xofs, yofs; + int x, y; + + g_return_if_fail (uta != NULL); + g_return_if_fail (width >= 0 && height >= 0); + g_return_if_fail (src_x >= uta->x0 << EOM_UTILE_SHIFT); + g_return_if_fail (src_y >= uta->y0 << EOM_UTILE_SHIFT); + g_return_if_fail (src_x + width <= (uta->x0 + uta->width) << EOM_UTILE_SHIFT); + g_return_if_fail (src_y + height <= (uta->y0 + uta->height) << EOM_UTILE_SHIFT); + + if ((src_x == dest_x && src_y == dest_y) || width == 0 || height == 0) + return; + + /* FIXME: This function is not perfect. It *adds* the copied/offsetted + * area to the original contents of the microtile array, thus growing + * the region more than needed. The effect should be to "overwrite" the + * original contents, just like XCopyArea() does. Care needs to be + * taken when the edges of the rectangle do not fall on microtile + * boundaries, because tiles may need to be "split". + * + * Maybe this will work: + * + * 1. Copy the rectangular array of tiles that form the region to a + * temporary buffer. + * + * 2. uta_remove_rect() the *destination* rectangle from the original + * microtile array. + * + * 3. Copy back the temporary buffer to the original array while + * offsetting it in the same way as copy_tile() does. + */ + + rect_x1 = src_x >> EOM_UTILE_SHIFT; + rect_y1 = src_y >> EOM_UTILE_SHIFT; + rect_x2 = (src_x + width + EOM_UTILE_SIZE - 1) >> EOM_UTILE_SHIFT; + rect_y2 = (src_y + height + EOM_UTILE_SIZE - 1) >> EOM_UTILE_SHIFT; + + xofs = dest_x - src_x; + yofs = dest_y - src_y; + + left_to_right = xofs < 0; + top_to_bottom = yofs < 0; + + if (top_to_bottom && left_to_right) { + for (y = rect_y1; y < rect_y2; y++) + for (x = rect_x1; x < rect_x2; x++) + copy_tile (uta, x, y, xofs, yofs); + } else if (top_to_bottom && !left_to_right) { + for (y = rect_y1; y < rect_y2; y++) + for (x = rect_x2 - 1; x >= rect_x1; x--) + copy_tile (uta, x, y, xofs, yofs); + } else if (!top_to_bottom && left_to_right) { + for (y = rect_y2 - 1; y >= rect_y1; y--) + for (x = rect_x1; x < rect_x2; x++) + copy_tile (uta, x, y, xofs, yofs); + } else if (!top_to_bottom && !left_to_right) { + for (y = rect_y2 - 1; y >= rect_y1; y--) + for (x = rect_x2 - 1; x >= rect_x1; x--) + copy_tile (uta, x, y, xofs, yofs); + } +} diff --git a/src/uta.h b/src/uta.h new file mode 100644 index 0000000..4152136 --- /dev/null +++ b/src/uta.h @@ -0,0 +1,75 @@ +/* Eye of Mate image viewer - Microtile array utilities + * + * Copyright (C) 2000-2009 The Free Software Foundation + * + * Author: Federico Mena-Quintero <[email protected]> + * + * Portions based on code from libart_lgpl by Raph Levien. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef UTA_H +#define UTA_H + +#define EOM_UTILE_SHIFT 5 +#define EOM_UTILE_SIZE (1 << EOM_UTILE_SHIFT) + +typedef guint32 EomUtaBbox; + +struct _EomIRect { + int x0, y0, x1, y1; +}; + +struct _EomUta { + int x0; + int y0; + int width; + int height; + EomUtaBbox *utiles; +}; + +typedef struct _EomIRect EomIRect; +typedef struct _EomUta EomUta; + + + +G_GNUC_INTERNAL +void eom_uta_free (EomUta *uta); + +G_GNUC_INTERNAL +void eom_irect_intersect (EomIRect *dest, + const EomIRect *src1, const EomIRect *src2); +G_GNUC_INTERNAL +int eom_irect_empty (const EomIRect *src); + +G_GNUC_INTERNAL +EomUta *uta_ensure_size (EomUta *uta, int x1, int y1, int x2, int y2); + +G_GNUC_INTERNAL +EomUta *uta_add_rect (EomUta *uta, int x1, int y1, int x2, int y2); + +G_GNUC_INTERNAL +void uta_remove_rect (EomUta *uta, int x1, int y1, int x2, int y2); + +G_GNUC_INTERNAL +void uta_find_first_glom_rect (EomUta *uta, EomIRect *rect, int max_width, int max_height); + +G_GNUC_INTERNAL +void uta_copy_area (EomUta *uta, int src_x, int src_y, int dest_x, int dest_y, int width, int height); + + + +#endif diff --git a/src/zoom.c b/src/zoom.c new file mode 100644 index 0000000..aa027de --- /dev/null +++ b/src/zoom.c @@ -0,0 +1,118 @@ +/* Eye of Mate image viewer - utility functions for computing zoom factors + * + * Copyright (C) 2000 The Free Software Foundation + * + * Author: Federico Mena-Quintero <[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 <config.h> +#include <math.h> +#include "zoom.h" + + + +/** + * zoom_fit_size: + * @dest_width: Width of destination area. + * @dest_height: Height of destination area. + * @src_width: Width of source image. + * @src_height: Height of source image. + * @upscale_smaller: Whether to scale up images smaller than the destination size. + * @width: Return value for image width. + * @height: Return value for image height. + * + * Computes the final dimensions of an image that is to be scaled to fit to a + * certain size. If @upscale_smaller is TRUE, then images smaller than the + * destination size will be scaled up; otherwise, they will be left at their + * original size. + **/ +void +zoom_fit_size (guint dest_width, guint dest_height, + guint src_width, guint src_height, + gboolean upscale_smaller, + guint *width, guint *height) +{ + guint w, h; + + g_return_if_fail (width != NULL); + g_return_if_fail (height != NULL); + + if (src_width == 0 || src_height == 0) { + *width = 0; + *height = 0; + return; + } + + if (src_width <= dest_width && src_height <= dest_height && !upscale_smaller) { + *width = src_width; + *height = src_height; + return; + } + + w = dest_width; + h = floor ((double) (src_height * w) / src_width + 0.5); + + if (h > dest_height) { + h = dest_height; + w = floor ((double) (src_width * h) / src_height + 0.5); + } + + g_assert (w <= dest_width); + g_assert (h <= dest_height); + + *width = w; + *height = h; +} + +/** + * zoom_fit_scale: + * @dest_width: Width of destination area. + * @dest_height: Height of destination area. + * @src_width: Width of source image. + * @src_height: Height of source image. + * @upscale_smaller: Whether to scale up images smaller than the destination size. + * + * Similar to zoom_fit_size(), but returns the zoom factor of the final image + * with respect to the original image's size. + * + * Return value: Zoom factor with respect to the original size. + **/ +double +zoom_fit_scale (guint dest_width, guint dest_height, + guint src_width, guint src_height, + gboolean upscale_smaller) +{ + guint w, h; + double wfactor, hfactor; + double factor; + + if (src_width == 0 || src_height == 0) + return 1.0; + + if (dest_width == 0 || dest_height == 0) + return 0.0; + + zoom_fit_size (dest_width, dest_height, src_width, src_height, upscale_smaller, &w, &h); + + wfactor = (double) w / src_width; + hfactor = (double) h / src_height; + + factor = MIN (wfactor, hfactor); + + return factor; +} + diff --git a/src/zoom.h b/src/zoom.h new file mode 100644 index 0000000..dc91505 --- /dev/null +++ b/src/zoom.h @@ -0,0 +1,38 @@ +/* Eye of Mate image viewer - utility functions for computing zoom factors + * + * Copyright (C) 2000 The Free Software Foundation + * + * Author: Federico Mena-Quintero <[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 ZOOM_H +#define ZOOM_H + +#include <glib.h> + +G_GNUC_INTERNAL +void zoom_fit_size (guint dest_width, guint dest_height, + guint src_width, guint src_height, + gboolean upscale_smaller, + guint *width, guint *height); + +G_GNUC_INTERNAL +double zoom_fit_scale (guint dest_width, guint dest_height, + guint src_width, guint src_height, + gboolean upscale_smaller); + +#endif |